J'écris une application iPhone et j'ai essentiellement besoin d'implémenter quelque chose d'équivalent à l'outil `` Pipette '' dans Photoshop, où vous pouvez toucher un point sur l'image et capturer les valeurs RVB du pixel en question pour déterminer et faire correspondre sa couleur. Obtenir la UIImage est la partie facile, mais existe-t-il un moyen de convertir les données UIImage en une représentation bitmap dans laquelle je pourrais extraire ces informations pour un pixel donné? Un exemple de code de travail serait très apprécié, et notez que je ne suis pas concerné par la valeur alpha.
J'ai posté plus tôt ce soir avec une consolidation et un petit ajout à ce qui avait été dit sur cette page - qui se trouve au bas de ce post. Je modifie le message à ce stade, cependant, pour publier ce que je propose est (au moins pour mes besoins, qui incluent la modification des données de pixels) une meilleure méthode, car elle fournit des données inscriptibles (alors que, si je comprends bien, la méthode fournie par les articles précédents et au bas de cet article fournit une référence en lecture seule aux données).
Méthode 1: informations sur les pixels inscriptibles
J'ai défini des constantes
#define RGBA 4
#define RGBA_8_BIT 8
Dans ma sous-classe UIImage, j'ai déclaré des variables d'instance:
size_t bytesPerRow;
size_t byteCount;
size_t pixelCount;
CGContextRef context;
CGColorSpaceRef colorSpace;
UInt8 *pixelByteData;
// A pointer to an array of RGBA bytes in memory
RPVW_RGBAPixel *pixelData;
La structure des pixels (avec alpha dans cette version)
typedef struct RGBAPixel {
byte red;
byte green;
byte blue;
byte alpha;
} RGBAPixel;
Fonction bitmap (retourne RGBA pré-calculé; divisez RGB par A pour obtenir RGB non modifié):
-(RGBAPixel*) bitmap {
NSLog( @"Returning bitmap representation of UIImage." );
// 8 bits each of red, green, blue, and alpha.
[self setBytesPerRow:self.size.width * RGBA];
[self setByteCount:bytesPerRow * self.size.height];
[self setPixelCount:self.size.width * self.size.height];
// Create RGB color space
[self setColorSpace:CGColorSpaceCreateDeviceRGB()];
if (!colorSpace)
{
NSLog(@"Error allocating color space.");
return nil;
}
[self setPixelData:malloc(byteCount)];
if (!pixelData)
{
NSLog(@"Error allocating bitmap memory. Releasing color space.");
CGColorSpaceRelease(colorSpace);
return nil;
}
// Create the bitmap context.
// Pre-multiplied RGBA, 8-bits per component.
// The source image format will be converted to the format specified here by CGBitmapContextCreate.
[self setContext:CGBitmapContextCreate(
(void*)pixelData,
self.size.width,
self.size.height,
RGBA_8_BIT,
bytesPerRow,
colorSpace,
kCGImageAlphaPremultipliedLast
)];
// Make sure we have our context
if (!context) {
free(pixelData);
NSLog(@"Context not created!");
}
// Draw the image to the bitmap context.
// The memory allocated for the context for rendering will then contain the raw image pixelData in the specified color space.
CGRect rect = { { 0 , 0 }, { self.size.width, self.size.height } };
CGContextDrawImage( context, rect, self.CGImage );
// Now we can get a pointer to the image pixelData associated with the bitmap context.
pixelData = (RGBAPixel*) CGBitmapContextGetData(context);
return pixelData;
}
Étape 1. J'ai déclaré un type d'octet:
typedef unsigned char byte;
Étape 2. J'ai déclaré qu'une structure correspond à un pixel:
typedef struct RGBPixel{
byte red;
byte green;
byte blue;
}
RGBPixel;
Étape 3. J'ai sous-classé UIImageView et déclaré (avec les propriétés synthétisées correspondantes):
// Reference to Quartz CGImage for receiver (self)
CFDataRef bitmapData;
// Buffer holding raw pixel data copied from Quartz CGImage held in receiver (self)
UInt8* pixelByteData;
// A pointer to the first pixel element in an array
RGBPixel* pixelData;
Étape 4. Code de sous-classe J'ai mis une méthode nommée bitmap (pour renvoyer les données de pixel bitmap):
//Get the bitmap data from the receiver's CGImage (see UIImage docs)
[self setBitmapData: CGDataProviderCopyData(CGImageGetDataProvider([self CGImage]))];
//Create a buffer to store bitmap data (unitialized memory as long as the data)
[self setPixelBitData:malloc(CFDataGetLength(bitmapData))];
//Copy image data into allocated buffer
CFDataGetBytes(bitmapData,CFRangeMake(0,CFDataGetLength(bitmapData)),pixelByteData);
//Cast a pointer to the first element of pixelByteData
//Essentially what we're doing is making a second pointer that divides the byteData's units differently - instead of dividing each unit as 1 byte we will divide each unit as 3 bytes (1 pixel).
pixelData = (RGBPixel*) pixelByteData;
//Now you can access pixels by index: pixelData[ index ]
NSLog(@"Pixel data one red (%i), green (%i), blue (%i).", pixelData[0].red, pixelData[0].green, pixelData[0].blue);
//You can determine the desired index by multiplying row * column.
return pixelData;
Étape 5. J'ai créé une méthode d'accesseur:
-(RGBPixel*)pixelDataForRow:(int)row column:(int)column{
//Return a pointer to the pixel data
return &pixelData[row * column];
}
Voici ma solution pour échantillonner la couleur d'un UIImage.
Cette approche convertit le pixel demandé en un grand tampon RGBA 1px et renvoie les valeurs de couleur résultantes en tant qu'objet UIColor. C'est beaucoup plus rapide que la plupart des autres approches que j'ai vues et utilise très peu de mémoire.
Cela devrait fonctionner assez bien pour quelque chose comme un sélecteur de couleurs, où vous n'avez généralement besoin que de la valeur d'un pixel spécifique à un moment donné.
iimage + Picker.h
#import <UIKit/UIKit.h>
@interface UIImage (Picker)
- (UIColor *)colorAtPosition:(CGPoint)position;
@end
iimage + Picker.m
#import "UIImage+Picker.h"
@implementation UIImage (Picker)
- (UIColor *)colorAtPosition:(CGPoint)position {
CGRect sourceRect = CGRectMake(position.x, position.y, 1.f, 1.f);
CGImageRef imageRef = CGImageCreateWithImageInRect(self.CGImage, sourceRect);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
unsigned char *buffer = malloc(4);
CGBitmapInfo bitmapInfo = kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big;
CGContextRef context = CGBitmapContextCreate(buffer, 1, 1, 8, 4, colorSpace, bitmapInfo);
CGColorSpaceRelease(colorSpace);
CGContextDrawImage(context, CGRectMake(0.f, 0.f, 1.f, 1.f), imageRef);
CGImageRelease(imageRef);
CGContextRelease(context);
CGFloat r = buffer[0] / 255.f;
CGFloat g = buffer[1] / 255.f;
CGFloat b = buffer[2] / 255.f;
CGFloat a = buffer[3] / 255.f;
free(buffer);
return [UIColor colorWithRed:r green:g blue:b alpha:a];
}
@end
Vous ne pouvez pas accéder directement aux données bitmap d'un UIImage.
Vous devez obtenir la représentation CGImage de l'UIImage. Obtenez ensuite le fournisseur de données de CGImage, à partir de là une représentation CFData du bitmap. Assurez-vous de libérer le CFData lorsque vous avez terminé.
CGImageRef cgImage = [image CGImage];
CGDataProviderRef provider = CGImageGetDataProvider(cgImage);
CFDataRef bitmapData = CGDataProviderCopyData(provider);
Vous voudrez probablement regarder les informations bitmap du CGImage pour obtenir l'ordre des pixels, les dimensions de l'image, etc.
La réponse de Lajos a fonctionné pour moi. Pour obtenir les données de pixels sous forme de tableau d'octets, j'ai fait ceci:
UInt8* data = CFDataGetBytePtr(bitmapData);
Plus d'informations: CFDataRef documentation .
N'oubliez pas non plus d'inclure CoreGraphics.framework
Merci tout le monde! En rassemblant quelques-unes de ces réponses, j'obtiens:
- (UIColor*)colorFromImage:(UIImage*)image sampledAtPoint:(CGPoint)p {
CGImageRef cgImage = [image CGImage];
CGDataProviderRef provider = CGImageGetDataProvider(cgImage);
CFDataRef bitmapData = CGDataProviderCopyData(provider);
const UInt8* data = CFDataGetBytePtr(bitmapData);
size_t bytesPerRow = CGImageGetBytesPerRow(cgImage);
size_t width = CGImageGetWidth(cgImage);
size_t height = CGImageGetHeight(cgImage);
int col = p.x*(width-1);
int row = p.y*(height-1);
const UInt8* pixel = data + row*bytesPerRow+col*4;
UIColor* returnColor = [UIColor colorWithRed:pixel[0]/255. green:pixel[1]/255. blue:pixel[2]/255. alpha:1.0];
CFRelease(bitmapData);
return returnColor;
}
Cela prend simplement une plage de points de 0,0 à 1,0 pour x et y. Exemple:
UIColor* sampledColor = [self colorFromImage:image
sampledAtPoint:CGPointMake(p.x/imageView.frame.size.width,
p.y/imageView.frame.size.height)];
Cela fonctionne très bien pour moi. Je fais quelques hypothèses comme les bits par pixel et l'espace colorimétrique RGBA, mais cela devrait fonctionner dans la plupart des cas.
Une autre note - cela fonctionne à la fois sur le simulateur et sur l'appareil pour moi - j'ai eu des problèmes avec cela dans le passé en raison de l'optimisation PNG qui s'est produite quand il est allé sur l'appareil.
Utilisez ANImageBitmapRep qui donne un accès au niveau pixel (lecture/écriture).
Pour faire quelque chose de similaire dans mon application, j'ai créé un petit CGImageContext hors écran, puis y ai rendu l'UIImage. Cela m'a permis d'extraire rapidement un certain nombre de pixels à la fois. Cela signifie que vous pouvez configurer le bitmap cible dans un format que vous trouvez facile à analyser, et laisser CoreGraphics effectuer le dur travail de conversion entre les modèles de couleurs ou les formats bitmap.
Je ne sais pas comment indexer correctement les données d'image sur la base d'une cordination X, Y donnée. Est-ce que quelqu'un sait?
pixelPosition = (x + (y * ((largeur d'image) * BytesPerPixel)));
// le pitch n'est pas un problème avec cet appareil pour autant que je sache et peut être laissé à zéro ... // (ou retiré du calcul).