web-dev-qa-db-fra.com

Comment obtenir des données de pixels à partir d'un UIImage (Cocoa Touch) ou d'un CGImage (Core Graphics)?

J'ai un UIImage (Cocoa Touch). À partir de là, je suis heureux d’obtenir une image CGImage ou tout autre élément de votre choix disponible. 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!

195
Olie

Pour info, j'ai combiné la réponse de Keremk avec mon contour d'origine, nettoyé les fautes de frappe, généralisé pour renvoyer un tableau de couleurs et obtenu le tout pour compiler. 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;
}
247
Olie

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

Voir ci-dessous à titre d'exemple:

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

Le Questions techniques QA1509 d'Apple présente l'approche simple suivante:

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

Utilisez CFDataGetBytePtr pour obtenir les octets réels (et divers CGImageGet* méthodes pour comprendre comment les interpréter).

26
yakovlev

Impossible de croire qu'il n'y a pas une seule bonne réponse ici. Il n'est pas nécessaire d'allouer des pointeurs et les valeurs non multipliées doivent encore être normalisées. Pour aller droit au but, 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(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)
        }
    }
}
14
Erik Aigner

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

9
eddy
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);
9
Nidal Fakhouri

UIImage est un wrapper dont les octets sont CGImage ou CIImage

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

Les objets image ne fournissent pas d'accès direct à leurs 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 objet NSData contenant les données d'image au format PNG ou JPEG. format.

Trucs habituels pour contourner ce problème

Comme indiqué, vos options sont

  • UIImagePNGRepresentation ou JPEG
  • Déterminez si l'image contient des données de sauvegarde CGImage ou CIImage et obtenez-les là

Aucune de ces méthodes n'est particulièrement utile si vous souhaitez obtenir une sortie qui n'est pas au format ARGB, PNG ou JPEG et que les données ne sont pas déjà sauvegardées par CIImage.

Ma recommandation, essayez CIImage

Lors du développement de votre projet, il serait peut-être plus judicieux d’éviter UIImage et de choisir autre chose. UIImage, en tant qu'encapsuleur d'image Obj-C, est souvent pris en charge par CGImage au point où nous le prenons pour acquis. CIImage a tendance à être un meilleur format de wrapper dans la mesure où vous pouvez utiliser un CIContext pour obtenir le format souhaité sans avoir besoin de savoir comment il a été créé. Dans votre cas, obtenir le bitmap consisterait à appeler

- render: toBitmap: rowBytes: bounds: format: colorSpace:

En prime, vous pouvez commencer à effectuer de jolies manipulations sur l'image en chaînant les filtres sur l'image. Cela résout beaucoup de problèmes lorsque l'image est à l'envers ou doit être pivotée/réduite, etc.

7

S'appuyant sur la réponse de Olie et 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<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
}

rapide

6
swillsea

Basé sur différentes réponses mais principalement sur this , 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 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);

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

2
cprcrack