Pregunta

Estoy escribiendo una aplicación para iPhone y necesito implementar esencialmente algo equivalente a la herramienta 'eyedropper' en photoshop, donde puedes tocar un punto en la imagen y capturar los valores RGB para el píxel en cuestión para determinar y hacer coincidir su color. . Obtener el UIImage es la parte fácil, pero ¿hay alguna forma de convertir los datos del UIImage en una representación de mapa de bits en la que pueda extraer esta información para un píxel determinado? Un ejemplo de código de trabajo sería lo más apreciado y tenga en cuenta que no me preocupa el valor alfa.

¿Fue útil?

Solución

Un poco más de detalle ...

Lo publiqué temprano esta noche con una consolidación y una pequeña adición a lo que se había dicho en esta página, que se puede encontrar al final de esta publicación. Sin embargo, estoy editando la publicación en este punto para publicar lo que propongo (al menos para mis requisitos, que incluye modificar los datos de píxeles) un método mejor, ya que proporciona datos de escritura (mientras que, según tengo entendido, el método proporcionado en publicaciones anteriores y al final de esta publicación se proporciona una referencia de solo lectura a los datos).

Método 1: Información de píxeles que se puede escribir

  1. He definido constantes

    #define RGBA        4
    #define RGBA_8_BIT  8
    
  2. En mi subclase UIImage declaré variables de instancia:

    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;
    
  3. La estructura de píxeles (con alfa en esta versión)

    typedef struct RGBAPixel {
        byte red;
        byte green;
        byte blue;
        byte alpha;
    } RGBAPixel;
    
  4. Función de mapa de bits (devuelve RGBA precalculado; divide RGB por A para obtener RGB sin modificar):

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

Datos de solo lectura (Información anterior) - método 2:


Paso 1. He declarado un tipo para el byte:

 typedef unsigned char byte;

Paso 2. He declarado que una estructura corresponde a un píxel:

 typedef struct RGBPixel{
    byte red;
    byte green;
    byte blue;  
    }   
RGBPixel;

Paso 3. Sub-clasifiqué UIImageView y declaré (con las propiedades sintetizadas correspondientes):

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

Paso 4. Código de subclase que puse en un método llamado mapa de bits (para devolver los datos de píxeles del mapa de bits):

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

Paso 5. Hice un método de acceso:

-(RGBPixel*)pixelDataForRow:(int)row column:(int)column{
    //Return a pointer to the pixel data
    return &pixelData[row * column];           
}

Otros consejos

Aquí está mi solución para muestrear el color de un UIImage.

Este enfoque convierte el píxel solicitado en un búfer RGBA grande de 1px y devuelve los valores de color resultantes como un objeto UIColor. Esto es mucho más rápido que la mayoría de los otros enfoques que he visto y utiliza muy poca memoria.

Esto debería funcionar bastante bien para algo como un selector de color, donde normalmente solo necesitas el valor de un píxel específico en un momento dado.

Uiimage+Picker.h

#import <UIKit/UIKit.h>


@interface UIImage (Picker)

- (UIColor *)colorAtPosition:(CGPoint)position;

@end

Uiimage+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 

No puede acceder directamente a los datos de mapa de bits de un UIImage.

Necesitas obtener la representación CGImage del UIImage. Luego, obtenga el proveedor de datos de CGImage, a partir de eso, una representación de datos de bits del mapa de bits. Asegúrese de liberar los datos CFD cuando haya terminado.

CGImageRef cgImage = [image CGImage];
CGDataProviderRef provider = CGImageGetDataProvider(cgImage);
CFDataRef bitmapData = CGDataProviderCopyData(provider);

Probablemente querrá mirar la información de mapa de bits de CGImage para obtener el orden de píxeles, las dimensiones de la imagen, etc.

La respuesta de Lajos funcionó para mí. Para obtener los datos de píxeles como una matriz de bytes, hice esto:

UInt8 * data = CFDataGetBytePtr (bitmapData);

Más información: CFDataRef documentación .

Además, recuerde incluir CoreGraphics.framework

Gracias a todos! Al juntar algunas de estas respuestas, obtengo:

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

Esto solo toma un rango de puntos 0.0-1.0 tanto para x como para y. Ejemplo:

UIColor* sampledColor = [self colorFromImage:image
         sampledAtPoint:CGPointMake(p.x/imageView.frame.size.width,
                                    p.y/imageView.frame.size.height)];

Esto funciona muy bien para mí. Estoy haciendo un par de suposiciones como bits por píxel y espacio de color RGBA, pero esto debería funcionar en la mayoría de los casos.

Otra nota: está funcionando tanto en el simulador como en el dispositivo. He tenido problemas con eso en el pasado debido a la optimización de PNG que ocurrió cuando se conectó al dispositivo.

Para hacer algo similar en mi aplicación, creé un pequeño CGImageContext fuera de la pantalla y luego rendericé el UIImage en él. Esto me permitió una manera rápida de extraer una cantidad de píxeles a la vez. Esto significa que puede configurar el mapa de bits de destino en un formato que le resulte fácil de analizar y dejar que CoreGraphics haga el trabajo duro de conversión entre modelos de color o formatos de mapa de bits.

  

No sé cómo indexar correctamente los datos de la imagen en función de la acordación de X, Y dada. ¿Alguien sabe?

pixelPosition = (x + (y * ((ancho de imagen) * BytesPerPixel)));

// el tono no es un problema con este dispositivo por lo que sé y se puede dejar en cero ... // (o sacado de las matemáticas).

Utilice ANImageBitmapRep que proporciona acceso a nivel de píxeles (lectura / escritura).

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top