Comment obtenir des données de pixel d’un UIImage (Cocoa Touch) ou CGImage (Core Graphics)?

J’ai un UIImage (Cocoa Touch). À partir de là, je suis heureux d’obtenir une image CGImage ou toute autre chose que vous souhaitez. J’aimerais écrire cette fonction:

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

Merci!

Pour info, j’ai combiné la réponse de Keremk avec mon schéma original, nettoyé les fautes de frappe, généralisé pour renvoyer un tableau de couleurs et obtenu la compilation complète. Voici le résultat:

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

Une façon de le faire est de dessiner l’image dans un contexte bitmap qui est sauvegardé par un tampon donné pour un espace couleur donné (dans ce cas, il s’agit de RVB): (notez que cela copiera les données image dans ce tampon). voulez le mettre en cache au lieu de faire cette opération chaque fois que vous devez obtenir des valeurs de pixels)

Voir ci-dessous comme un échantillon:

 // 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 QA1509 d’ Apple relative à la technique présente l’approche simple suivante:

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

Utilisez CFDataGetBytePtr pour accéder aux octets réels (et aux différentes méthodes CGImageGet* pour comprendre comment les interpréter).

Voici un thread SO où @Matt ne restitue que le pixel souhaité dans un contexte 1×1 en déplaçant l’image de sorte que le pixel souhaité s’aligne avec le pixel du contexte.

 NSSsortingng * 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 est un wrapper dont les octets sont CGImage ou CIImage

Selon la référence Apple sur UIImage, l’object est immuable et vous n’avez pas access aux octets de sauvegarde. S’il est vrai que vous pouvez accéder aux données CGImage si vous avez renseigné l’ UIImage avec un CGImage (explicitement ou implicitement), il retournera NULL si l’ UIImage est soutenu par un CIImage et vice-versa.

Les objects image ne fournissent pas un access direct aux données d’image sous-jacentes. Cependant, vous pouvez récupérer les données d’image dans d’autres formats pour les utiliser dans votre application. Plus précisément, vous pouvez utiliser les propriétés cgImage et ciImage pour récupérer des versions de l’image compatibles avec Core Graphics et Core Image, respectivement. Vous pouvez également utiliser les fonctions UIImagePNGRepresentation ( 🙂 et UIImageJPEGRepresentation ( : _ 🙂 pour générer un object NSData contenant les données d’image au format PNG ou JPEG.

Astuces communes pour contourner ce problème

Comme indiqué vos options sont

  • UIImagePNGRpresentation ou JPEG
  • Déterminez si l’image contient des données de sauvegarde CGImage ou CIImage et obtenez-la

Ni l’une ni l’autre ne sont des astuces particulièrement utiles si vous souhaitez des sorties qui ne sont pas des données ARGB, PNG ou JPEG et dont les données ne sont pas déjà sauvegardées par CIImage.

Ma recommandation, essayez CIImage

Tout en développant votre projet, il peut être plus judicieux d’éviter complètement UIImage et de choisir autre chose. UIImage, en tant qu’emballeur d’images Obj-C, est souvent soutenu par CGImage au point où nous le prenons pour acquis. CIImage a tendance à être un meilleur format d’encapsuleur dans la mesure où vous pouvez utiliser CIContext pour obtenir le format souhaité sans avoir besoin de savoir comment il a été créé. Dans votre cas, obtenir le bitmap serait une question d’appeler

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

En prime, vous pouvez commencer à manipuler l’image en enchaînant les filtres sur l’image. Cela résout beaucoup de problèmes où l’image est à l’envers ou doit être pivotée / mise à l’échelle, etc.

S’appuyant sur la réponse d’Olie et d’Algal, voici une réponse mise à jour pour 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.. 

rapide

Je ne pouvais pas croire qu’il n’y ait pas une seule réponse correcte ici. Pas besoin d’allouer des pointeurs, et les valeurs non multipliées doivent encore être normalisées. Pour couper à la chasse, voici la version correcte pour Swift 4. Pour UIImage utilisez simplement .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) } } } 

Basé sur des réponses différentes mais principalement sur ceci , cela fonctionne pour ce dont j’ai besoin:

 UIImage *image1 = ...; // The image from where you want a pixel data int pixelX = ...; // The X coordinate of the pixel you want to resortingeve int pixelY = ...; // The Y coordinate of the pixel you want to resortingeve 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); 

À la suite de ces lignes, vous aurez un pixel au format AARRGGBB avec alpha toujours défini sur FF dans le pixel1 entier non signé de 4 pixel1 .