Hoe krijg ik pixelgegevens van een UIImage (Cocoa Touch) of CGImage (Core Graphics)?

Ik heb een UIImage (Cocoa Touch). Van daaruit ontvang ik graag een CGImage of iets anders dat u maar wilt dat beschikbaar is. Ik wil deze functie graag schrijven:

- (int)getRGBAFromImage:(UIImage *)image atX:(int)xx andY:(int)yy {
  // [...]
  // What do I want to read about to help
  // me fill in this bit, here?
  // [...]
  int result = (red << 24) | (green << 16) | (blue << 8) | alpha;
  return result;
}

Antwoord 1, autoriteit 100%

Ter info, ik heb Keremk’s antwoord gecombineerd met mijn originele schets, de typefouten opgeschoond, gegeneraliseerd om een ​​reeks kleuren terug te geven en het hele ding te compileren. Hier is het resultaat:

+ (NSArray*)getRGBAsFromImage:(UIImage*)image atX:(int)x andY:(int)y count:(int)count
{
    NSMutableArray *result = [NSMutableArray arrayWithCapacity:count];
    // First get the image into your data buffer
    CGImageRef imageRef = [image CGImage];
    NSUInteger width = CGImageGetWidth(imageRef);
    NSUInteger height = CGImageGetHeight(imageRef);
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    unsigned char *rawData = (unsigned char*) calloc(height * width * 4, sizeof(unsigned char));
    NSUInteger bytesPerPixel = 4;
    NSUInteger bytesPerRow = bytesPerPixel * width;
    NSUInteger bitsPerComponent = 8;
    CGContextRef context = CGBitmapContextCreate(rawData, width, height,
                    bitsPerComponent, bytesPerRow, colorSpace,
                    kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
    CGColorSpaceRelease(colorSpace);
    CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
    CGContextRelease(context);
    // Now your rawData contains the image data in the RGBA8888 pixel format.
    NSUInteger byteIndex = (bytesPerRow * y) + x * bytesPerPixel;
    for (int i = 0 ; i < count ; ++i)
    {
        CGFloat alpha = ((CGFloat) rawData[byteIndex + 3] ) / 255.0f;
        CGFloat red   = ((CGFloat) rawData[byteIndex]     ) / alpha;
        CGFloat green = ((CGFloat) rawData[byteIndex + 1] ) / alpha;
        CGFloat blue  = ((CGFloat) rawData[byteIndex + 2] ) / alpha;
        byteIndex += bytesPerPixel;
        UIColor *acolor = [UIColor colorWithRed:red green:green blue:blue alpha:alpha];
        [result addObject:acolor];
    }
  free(rawData);
  return result;
}

Antwoord 2, autoriteit 19%

Een manier om dit te doen is om de afbeelding naar een bitmapcontext te tekenen die wordt ondersteund door een bepaalde buffer voor een bepaalde kleurruimte (in dit geval is het RGB): (merk op dat hierdoor de afbeeldingsgegevens naar die buffer worden gekopieerd, dus je wilt het in de cache opslaan in plaats van deze bewerking elke keer uit te voeren als je pixelwaarden nodig hebt)

Zie hieronder als voorbeeld:

// First get the image into your data buffer
CGImageRef image = [myUIImage CGImage];
NSUInteger width = CGImageGetWidth(image);
NSUInteger height = CGImageGetHeight(image);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
unsigned char *rawData = malloc(height * width * 4);
NSUInteger bytesPerPixel = 4;
NSUInteger bytesPerRow = bytesPerPixel * width;
NSUInteger bitsPerComponent = 8;
CGContextRef context = CGBitmapContextCreate(rawData, width, height, bitsPerComponent, bytesPerRow, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
CGColorSpaceRelease(colorSpace);
CGContextDrawImage(context, CGRectMake(0, 0, width, height));
CGContextRelease(context);
// Now your rawData contains the image data in the RGBA8888 pixel format.
int byteIndex = (bytesPerRow * yy) + xx * bytesPerPixel;
red = rawData[byteIndex];
green = rawData[byteIndex + 1];
blue = rawData[byteIndex + 2];
alpha = rawData[byteIndex + 3];

Antwoord 3, autoriteit 10%

Apple’s Technische V&A QA1509toont de volgende eenvoudige benadering:

CFDataRef CopyImagePixels(CGImageRef inImage)
{
    return CGDataProviderCopyData(CGImageGetDataProvider(inImage));
}

Gebruik CFDataGetBytePtrom bij de werkelijke bytes te komen (en verschillende CGImageGet*-methoden om te begrijpen hoe ze moeten worden geïnterpreteerd).


Antwoord 4, autoriteit 9%

Ik kon niet geloven dat er geen enkel juist antwoordis. Het is niet nodig om pointers toe te wijzen en de niet-vermenigvuldigde waarden moeten nog worden genormaliseerd. Om tot de ontdekking te komen, hier is de juiste versie voor Swift 4. Gebruik voor UIImagegewoon .cgImage.

extension CGImage {
    func colors(at: [CGPoint]) -> [UIColor]? {
        let colorSpace = CGColorSpaceCreateDeviceRGB()
        let bytesPerPixel = 4
        let bytesPerRow = bytesPerPixel * width
        let bitsPerComponent = 8
        let bitmapInfo: UInt32 = CGImageAlphaInfo.premultipliedLast.rawValue | CGBitmapInfo.byteOrder32Big.rawValue
        guard let context = CGContext(data: nil, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo),
            let ptr = context.data?.assumingMemoryBound(to: UInt8.self) else {
            return nil
        }
        context.draw(self, in: CGRect(x: 0, y: 0, width: width, height: height))
        return at.map { p in
            let i = bytesPerRow * Int(p.y) + bytesPerPixel * Int(p.x)
            let a = CGFloat(ptr[i + 3]) / 255.0
            let r = (CGFloat(ptr[i]) / a) / 255.0
            let g = (CGFloat(ptr[i + 1]) / a) / 255.0
            let b = (CGFloat(ptr[i + 2]) / a) / 255.0
            return UIColor(red: r, green: g, blue: b, alpha: a)
        }
    }
}

De reden dat u de afbeelding eerst in een buffer moet tekenen/converteren, is omdat afbeeldingen verschillende formaten kunnen hebben. Deze stap is vereist om het te converteren naar een consistent formaat dat u kunt lezen.


Antwoord 5, autoriteit 4%

NSString * path = [[NSBundle mainBundle] pathForResource:@"filename" ofType:@"jpg"];
UIImage * img = [[UIImage alloc]initWithContentsOfFile:path];
CGImageRef image = [img CGImage];
CFDataRef data = CGDataProviderCopyData(CGImageGetDataProvider(image));
const unsigned char * buffer =  CFDataGetBytePtr(data);

Antwoord 6, autoriteit 4%

Hier is een ZO threadwaar @Matt rendert alleen de gewenste pixel in een 1×1-context door de afbeelding te verplaatsen zodat de gewenste pixel uitgelijnd is met de ene pixel in de context.


Antwoord 7, autoriteit 3%

UIImage is een wrapper, de bytes zijn CGImage of CIImage

Volgens de Apple Reference op UIImagehet object is onveranderlijk en u hebt geen toegang tot de backing-bytes. Hoewel het waar is dat je toegang hebt tot de CGImage-gegevens als je de UIImagehebt ingevuld met een CGImage(expliciet of impliciet), zal het NULLretourneren als de UIImagewordt ondersteund door een CIImageen vice versa.

Afbeeldingsobjecten bieden geen directe toegang tot hun onderliggende afbeelding
gegevens. U kunt de afbeeldingsgegevens echter in andere formaten ophalen voor:
gebruiken in uw app. In het bijzonder kunt u de cgImage en ciImage
eigenschappen om versies van de afbeelding op te halen die compatibel zijn met
respectievelijk Core Graphics en Core Image. U kunt ook de
UIImagePNGRepresentation(🙂 en UIImageJPEGRepresentation(:_:)
functies om een ​​NSData-object te genereren met de afbeeldingsgegevens in
ofwel het PNG- of JPEG-formaat.

Veelgebruikte trucs om dit probleem te omzeilen

Zoals vermeld zijn uw opties

  • UIImagePNG-weergave of JPEG
  • Bepaal of de afbeelding CGImage- of CIImage-back-upgegevens heeft en haal deze daar op

Dit zijn geen van beide bijzonder goede trucs als u uitvoer wilt die geen ARGB-, PNG- of JPEG-gegevens zijn en de gegevens nog niet worden ondersteund door CIImage.

Mijn aanbeveling, probeer CIImage

Tijdens het ontwikkelen van uw project kan het logischer zijn om UIImage helemaal te vermijden en iets anders te kiezen. UIImage, als een Obj-C-afbeeldingswrapper, wordt vaak ondersteund door CGImage tot het punt waarop we het als vanzelfsprekend beschouwen. CIImage is meestal een beter wrapper-formaat omdat je een CIContextom het gewenste formaat te krijgen zonder dat u hoeft te weten hoe het is gemaakt. In uw geval zou het verkrijgen van de bitmap een kwestie zijn van

– render:toBitmap:rowBytes:bounds:format:colorSpace:

Als een toegevoegde bonus kun je leuke manipulaties aan de afbeelding gaan doen door filters aan de afbeelding te koppelen. Dit lost veel van de problemen op waarbij de afbeelding ondersteboven staat of moet worden geroteerd/geschaald enz.


Antwoord 8, autoriteit 3%

Voortbouwend op het antwoord van Olie en Algal, hier is een bijgewerkt antwoord voor Swift 3

public func getRGBAs(fromImage image: UIImage, x: Int, y: Int, count: Int) -> [UIColor] {
var result = [UIColor]()
// First get the image into your data buffer
guard let cgImage = image.cgImage else {
    print("CGContext creation failed")
    return []
}
let width = cgImage.width
let height = cgImage.height
let colorSpace = CGColorSpaceCreateDeviceRGB()
let rawdata = calloc(height*width*4, MemoryLayout<CUnsignedChar>.size)
let bytesPerPixel = 4
let bytesPerRow = bytesPerPixel * width
let bitsPerComponent = 8
let bitmapInfo: UInt32 = CGImageAlphaInfo.premultipliedLast.rawValue | CGBitmapInfo.byteOrder32Big.rawValue
guard let context = CGContext(data: rawdata, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo) else {
    print("CGContext creation failed")
    return result
}
context.draw(cgImage, in: CGRect(x: 0, y: 0, width: width, height: height))
// Now your rawData contains the image data in the RGBA8888 pixel format.
var byteIndex = bytesPerRow * y + bytesPerPixel * x
for _ in 0..<count {
    let alpha = CGFloat(rawdata!.load(fromByteOffset: byteIndex + 3, as: UInt8.self)) / 255.0
    let red = CGFloat(rawdata!.load(fromByteOffset: byteIndex, as: UInt8.self)) / alpha
    let green = CGFloat(rawdata!.load(fromByteOffset: byteIndex + 1, as: UInt8.self)) / alpha
    let blue = CGFloat(rawdata!.load(fromByteOffset: byteIndex + 2, as: UInt8.self)) / alpha
    byteIndex += bytesPerPixel
    let aColor = UIColor(red: red, green: green, blue: blue, alpha: alpha)
    result.append(aColor)
}
free(rawdata)
return result
}


Antwoord 9, autoriteit 2%

Swift 5-versie

De antwoorden die hier worden gegeven zijn verouderd of onjuist omdat ze geen rekening houden met het volgende:

  1. De pixelgrootte van de afbeelding kan verschillen van de puntgrootte die wordt geretourneerd door image.size.width/image.size.height.
  2. Er kunnen verschillende lay-outs zijn die worden gebruikt door pixelcomponenten in de afbeelding, zoals BGRA, ABGR, ARGB enz., of er kunnen helemaal geen alfacomponenten zijn, zoals BGR en RGB. De methode UIView.drawHierarchy(in:afterScreenUpdates:)kan bijvoorbeeld BGRA-afbeeldingen produceren.
  3. Kleurcomponenten kunnen vooraf worden vermenigvuldigd met de alfa voor alle pixels in de afbeelding en moeten worden gedeeld door alfa om de oorspronkelijke kleur te herstellen.
  4. Voor geheugenoptimalisatie die wordt gebruikt door CGImage, kan de grootte van een pixelrij in bytes groter zijn dan de loutere vermenigvuldiging van de pixelbreedte met 4.

De onderstaande code is om een ​​universele Swift 5-oplossing te bieden om de UIColorvan een pixel te krijgen voor al dergelijke speciale gevallen. De code is geoptimaliseerd voor bruikbaarheid en duidelijkheid, nietvoor prestaties.

public extension UIImage {
    var pixelWidth: Int {
        return cgImage?.width ?? 0
    }
    var pixelHeight: Int {
        return cgImage?.height ?? 0
    }
    func pixelColor(x: Int, y: Int) -> UIColor {
        assert(
            0..<pixelWidth ~= x && 0..<pixelHeight ~= y,
            "Pixel coordinates are out of bounds")
        guard
            let cgImage = cgImage,
            let data = cgImage.dataProvider?.data,
            let dataPtr = CFDataGetBytePtr(data),
            let colorSpaceModel = cgImage.colorSpace?.model,
            let componentLayout = cgImage.bitmapInfo.componentLayout
        else {
            assertionFailure("Could not get a pixel of an image")
            return .clear
        }
        assert(
            colorSpaceModel == .rgb,
            "The only supported color space model is RGB")
        assert(
            cgImage.bitsPerPixel == 32 || cgImage.bitsPerPixel == 24,
            "A pixel is expected to be either 4 or 3 bytes in size")
        let bytesPerRow = cgImage.bytesPerRow
        let bytesPerPixel = cgImage.bitsPerPixel/8
        let pixelOffset = y*bytesPerRow + x*bytesPerPixel
        if componentLayout.count == 4 {
            let components = (
                dataPtr[pixelOffset + 0],
                dataPtr[pixelOffset + 1],
                dataPtr[pixelOffset + 2],
                dataPtr[pixelOffset + 3]
            )
            var alpha: UInt8 = 0
            var red: UInt8 = 0
            var green: UInt8 = 0
            var blue: UInt8 = 0
            switch componentLayout {
            case .bgra:
                alpha = components.3
                red = components.2
                green = components.1
                blue = components.0
            case .abgr:
                alpha = components.0
                red = components.3
                green = components.2
                blue = components.1
            case .argb:
                alpha = components.0
                red = components.1
                green = components.2
                blue = components.3
            case .rgba:
                alpha = components.3
                red = components.0
                green = components.1
                blue = components.2
            default:
                return .clear
            }
            // If chroma components are premultiplied by alpha and the alpha is `0`,
            // keep the chroma components to their current values.
            if cgImage.bitmapInfo.chromaIsPremultipliedByAlpha && alpha != 0 {
                let invUnitAlpha = 255/CGFloat(alpha)
                red = UInt8((CGFloat(red)*invUnitAlpha).rounded())
                green = UInt8((CGFloat(green)*invUnitAlpha).rounded())
                blue = UInt8((CGFloat(blue)*invUnitAlpha).rounded())
            }
            return .init(red: red, green: green, blue: blue, alpha: alpha)
        } else if componentLayout.count == 3 {
            let components = (
                dataPtr[pixelOffset + 0],
                dataPtr[pixelOffset + 1],
                dataPtr[pixelOffset + 2]
            )
            var red: UInt8 = 0
            var green: UInt8 = 0
            var blue: UInt8 = 0
            switch componentLayout {
            case .bgr:
                red = components.2
                green = components.1
                blue = components.0
            case .rgb:
                red = components.0
                green = components.1
                blue = components.2
            default:
                return .clear
            }
            return .init(red: red, green: green, blue: blue, alpha: UInt8(255))
        } else {
            assertionFailure("Unsupported number of pixel components")
            return .clear
        }
    }
}
public extension UIColor {
    convenience init(red: UInt8, green: UInt8, blue: UInt8, alpha: UInt8) {
        self.init(
            red: CGFloat(red)/255,
            green: CGFloat(green)/255,
            blue: CGFloat(blue)/255,
            alpha: CGFloat(alpha)/255)
    }
}
public extension CGBitmapInfo {
    enum ComponentLayout {
        case bgra
        case abgr
        case argb
        case rgba
        case bgr
        case rgb
        var count: Int {
            switch self {
            case .bgr, .rgb: return 3
            default: return 4
            }
        }
    }
    var componentLayout: ComponentLayout? {
        guard let alphaInfo = CGImageAlphaInfo(rawValue: rawValue & Self.alphaInfoMask.rawValue) else { return nil }
        let isLittleEndian = contains(.byteOrder32Little)
        if alphaInfo == .none {
            return isLittleEndian ? .bgr : .rgb
        }
        let alphaIsFirst = alphaInfo == .premultipliedFirst || alphaInfo == .first || alphaInfo == .noneSkipFirst
        if isLittleEndian {
            return alphaIsFirst ? .bgra : .abgr
        } else {
            return alphaIsFirst ? .argb : .rgba
        }
    }
    var chromaIsPremultipliedByAlpha: Bool {
        let alphaInfo = CGImageAlphaInfo(rawValue: rawValue & Self.alphaInfoMask.rawValue)
        return alphaInfo == .premultipliedFirst || alphaInfo == .premultipliedLast
    }
}

Antwoord 10, autoriteit 2%

Gebruik de onderliggende CGImage en de bijbehorende dataProvider om toegang te krijgen tot de onbewerkte RGB-waarden van een UIImage in Swift 5:

import UIKit
let image = UIImage(named: "example.png")!
guard let cgImage = image.cgImage,
    let data = cgImage.dataProvider?.data,
    let bytes = CFDataGetBytePtr(data) else {
    fatalError("Couldn't access image data")
}
assert(cgImage.colorSpace?.model == .rgb)
let bytesPerPixel = cgImage.bitsPerPixel / cgImage.bitsPerComponent
for y in 0 ..< cgImage.height {
    for x in 0 ..< cgImage.width {
        let offset = (y * cgImage.bytesPerRow) + (x * bytesPerPixel)
        let components = (r: bytes[offset], g: bytes[offset + 1], b: bytes[offset + 2])
        print("[x:\(x), y:\(y)] \(components)")
    }
    print("---")
}

https://www.ralfebert.de/ ios/examples/image-processing/uiimage-raw-pixels/


Antwoord 11

Op basis van verschillende antwoorden, maar vooral op dit, werkt dit voor wat ik nodig heb:

UIImage *image1 = ...; // The image from where you want a pixel data
int pixelX = ...; // The X coordinate of the pixel you want to retrieve
int pixelY = ...; // The Y coordinate of the pixel you want to retrieve
uint32_t pixel1; // Where the pixel data is to be stored
CGContextRef context1 = CGBitmapContextCreate(&pixel1, 1, 1, 8, 4, CGColorSpaceCreateDeviceRGB(), kCGImageAlphaNoneSkipFirst);
CGContextDrawImage(context1, CGRectMake(-pixelX, -pixelY, CGImageGetWidth(image1.CGImage), CGImageGetHeight(image1.CGImage)), image1.CGImage);
CGContextRelease(context1);

Als gevolg van deze regels heb je een pixel in AARRGGBB-indeling met alfa altijd ingesteld op FF in het niet-ondertekende gehele getal van 4 bytes pixel1.

Other episodes