¿Cómo obtener datos de píxeles de un UIImage (Cocoa Touch) o CGImage (Core Graphics)?

Tengo un UIImage (Cocoa Touch). A partir de ahí, me complace obtener un CGImage o cualquier otra cosa que quiera que esté disponible. Me gustaría escribir esta función:

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

¡Gracias!

FYI, combiné la respuesta de Keremk con mi esquema original, limpié los errores tipográficos, generalicé para devolver una serie de colores y obtuve todo para comstackr. Aquí está el 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; } 

Una forma de hacerlo es dibujar la imagen en un contexto de bitmap respaldado por un búfer determinado para un espacio de color dado (en este caso es RGB): (tenga en cuenta que esto copiará los datos de imagen en ese búfer, por lo que lo hace desea almacenarlo en caché en lugar de hacer esta operación cada vez que necesite obtener valores de píxel)

Vea a continuación como muestra:

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

La Pregunta técnica QA1509 de Apple muestra el siguiente enfoque simple:

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

Utilice CFDataGetBytePtr para obtener los bytes reales (y varios métodos CGImageGet* para comprender cómo interpretarlos).

Aquí hay un subproceso SO donde @Matt representa solo el píxel deseado en un contexto de 1×1 desplazando la imagen para que el píxel deseado se alinee con el píxel del 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 es un contenedor que los bytes son CGImage o CIImage

Según la Referencia de Apple en UIImage, el objeto es inmutable y no tiene acceso a los bytes de respaldo. Si bien es cierto que puede acceder a los datos CGImage si ha poblado el UIImage con un CGImage (explícita o implícitamente), devolverá NULL si el UIImage está respaldado por un CIImage y viceversa.

Los objetos de imagen no proporcionan acceso directo a sus datos de imagen subyacentes. Sin embargo, puede recuperar los datos de imagen en otros formatos para usar en su aplicación. Específicamente, puede usar las propiedades cgImage y ciImage para recuperar versiones de la imagen que sean compatibles con Core Graphics y Core Image, respectivamente. También puede usar las funciones UIImagePNGRepresentation ( 🙂 y UIImageJPEGRepresentation ( : _ 🙂 para generar un objeto NSData que contenga los datos de imagen en formato PNG o JPEG.

Trucos comunes para evitar este problema

Como se dijo, sus opciones son

  • UIImagePNGRpresentación o JPEG
  • Determine si la imagen tiene datos de respaldo de CGImage o CIImage y llévela allí

Ninguno de estos son trucos particularmente buenos si desea resultados que no sean datos ARGB, PNG o JPEG y los datos no estén respaldados por CIImage.

Mi recomendación, prueba CIImage

Mientras desarrollas tu proyecto, puede que tenga más sentido evitar el UIImage por completo y elegir algo más. UImage, como contenedor de imágenes Obj-C, a menudo está respaldado por CGImage hasta el punto en que lo damos por sentado. CIImage tiende a ser un mejor formato de contenedor en el que puede usar un CIContext para obtener el formato que desea sin tener que saber cómo se creó. En su caso, obtener el bitmap sería una cuestión de llamar

– render: toBitmap: rowBytes: bounds: formato: colorSpace:

Como una ventaja adicional, puede comenzar a realizar agradables manipulaciones a la imagen encadenando filtros en la imagen. Esto resuelve muchos de los problemas donde la imagen está al revés o necesita ser rotada / escalada, etc.

Sobre la base de la respuesta de Olie y Algal, aquí hay una respuesta actualizada para 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

No podía creer que no hay una sola respuesta correcta aquí. No es necesario asignar punteros, y los valores sin multiplicar aún deben ser normalizados. Para ir al grano, aquí está la versión correcta para Swift 4. Para UIImage solo use .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) } } } 

Basado en diferentes respuestas pero principalmente en esto , esto funciona para lo que necesito:

 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 de estas líneas, tendrá un píxel en formato AARRGGBB con alfa siempre configurado en FF en el entero de 1 pixel1 sin signo pixel1 .