Como obter dados de pixel de um UIImage (Cocoa Touch) ou CGImage (Core Graphics)?

Eu tenho um UIImage (Cocoa Touch). A partir disso, fico feliz em receber um CGImage ou qualquer outra coisa que você queira que esteja disponível. Eu gostaria de escrever esta function:

- (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; } 

Obrigado!

Para sua informação, eu combinei a resposta de Keremk com meu esboço original, limpei os erros de digitação, generalizei-o para retornar uma matriz de colors e fiz a coisa toda para compilar. Aqui está o resultado:

 + (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; } 

Uma maneira de fazer isso é desenhar a imagem em um contexto de bitmap que é apoiado por um determinado buffer para um dado espaço de colors (neste caso, é RGB): (note que isto irá copiar os dados da imagem para aquele buffer, então você faz deseja armazená-lo em cache ao invés de fazer esta operação toda vez que você precisar obter valores de pixel)

Veja abaixo como uma amostra:

 // 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]; 

O QA da QA1509 da Apple mostra a seguinte abordagem simples:

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

Use CFDataGetBytePtr para obter os bytes reais (e vários methods CGImageGet* para entender como interpretá-los).

Aqui está um encadeamento SO, em que @Matt processa apenas o pixel desejado em um contexto 1×1, deslocando a imagem para que o pixel desejado se alinhe com o pixel no contexto.

 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); 

UIImage é um wrapper cujos bytes são CGImage ou CIImage

De acordo com a Referência da Apple no UIImage, o object é imutável e você não tem access aos bytes de apoio. Embora seja verdade que você possa acessar os dados do CGImage se você tiver preenchido o UIImage com um CGImage (explicitamente ou implicitamente), ele retornará NULL se o UIImage for suportado por um CIImage e vice-versa.

Objetos de imagem não fornecem access direto aos dados de imagem subjacentes. No entanto, você pode recuperar os dados da imagem em outros formatos para uso no seu aplicativo. Especificamente, você pode usar as propriedades cgImage e ciImage para recuperar versões da imagem compatíveis com Core Graphics e Core Image, respectivamente. Você também pode usar as funções UIImagePNGRepresentation ( 🙂 e UIImageJPEGRepresentation ( : _ 🙂 para gerar um object NSData contendo os dados da imagem no formato PNG ou JPEG.

Truques comuns para contornar esse problema

Como afirmado, suas opções são

  • UIImagePNGRepresentação ou JPEG
  • Determine se a imagem possui dados de apoio CGImage ou CIImage e acesse-a

Nenhum desses truques é particularmente bom se você deseja uma saída que não seja ARGB, PNG ou JPEG, e os dados ainda não tenham sido suportados pelo CIImage.

Minha recomendação, tente CIImage

Ao desenvolver seu projeto, talvez faça mais sentido evitar a UIImage e escolher outra coisa. UIImage, como um invólucro de imagem Obj-C, é frequentemente apoiado por CGImage ao ponto em que nós tomamos como certo. CIImage tende a ser um melhor formato de wrapper em que você pode usar um CIContext para obter o formato desejado sem precisar saber como ele foi criado. No seu caso, obter o bitmap seria uma questão de chamar

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

Como um bônus adicional, você pode começar a fazer boas manipulações na imagem, encadeando filtros na imagem. Isso resolve muitos dos problemas em que a imagem está de cabeça para baixo ou precisa ser girada / dimensionada, etc.

Baseando-se na resposta de Olie e Algal, aqui está uma resposta atualizada para o 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.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.. 

rápido

Não foi possível acreditar que não há uma única resposta correta aqui. Não há necessidade de alocar pointers e os valores não multiplicados ainda precisam ser normalizados. Para ir .cgImage , aqui está a versão correta para o Swift 4. Para o UIImage basta usar o .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(py) + bytesPerPixel * Int(px) 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) } } } 

Com base em respostas diferentes, mas principalmente sobre isso , isso funciona para o que eu preciso:

 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); 

Como resultado dessas linhas, você terá um pixel no formato AARRGGBB com alfa sempre definido como FF no pixel1 inteiro sem sinal de 4 pixel1 .