tutorial cocoa cocoa-touch core-graphics

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



cocoa touch tutorial (9)

UIImage es un contenedor, los bytes son CGImage o CIImage

Según Apple Reference 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 de CGImage si llenó 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 la imagen en otros formatos para utilizarlos 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 la imagen en formato PNG o JPEG.

Trucos comunes para solucionar este problema

Como se indica sus opciones son

  • UIImagePNGRepresentation o JPEG
  • Determine si la imagen tiene datos de respaldo CGImage o CIImage y consígalos allí

Ninguno de estos son trucos particularmente buenos si desea una salida que no sea datos ARGB, PNG o JPEG y los datos aún no están respaldados por CIImage.

Mi recomendación, prueba CIImage

Al desarrollar su proyecto, podría tener más sentido para usted evitar UIImage por completo y elegir otra cosa. UIImage, como envoltorio de imagen 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 envoltura, ya que puede usar un CIContext para obtener el formato que desee sin necesidad de saber cómo se creó. En su caso, obtener el mapa de bits sería una cuestión de llamada.

- render: toBitmap: rowBytes: lines: format: colorSpace:

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

Tengo un UIImage (Cocoa Touch). A partir de eso, estoy feliz de obtener un CGImage o cualquier otra cosa que desees 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!


Aquí hay un thread SO donde @Matt procesa solo el píxel deseado en un contexto 1x1 desplazando la imagen para que el píxel deseado se alinee con el píxel en el contexto.


Basado en diferentes respuestas pero principalmente en this , 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 establecido en FF en el pixel1 entero sin signo de 4 bytes pixel1 .


Las preguntas y respuestas técnicas de Apple QA1509 muestran el siguiente enfoque simple:

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

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


No podía creer que no hay una sola respuesta correcta aquí. No es necesario asignar punteros, y los valores no multiplicados aún deben normalizarse. Para pasar a la persecución, 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(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) } } }


Para su información, combiné la respuesta de Keremk con mi esquema original, limpié los errores tipográficos, lo generalicé para devolver una variedad de colores y compilé todo el conjunto. 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; }


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

swift


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

Vea a continuación una 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];


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