Pergunta

Eu estou escrevendo um aplicativo para iPhone e necessidade de essencialmente implementar algo equivalente à ferramenta 'conta-gotas' em photoshop, onde você pode tocar um ponto na imagem e capturar os valores RGB para o pixel em questão para determinar e combinar sua cor . Recebendo o UIImage é a parte fácil, mas há uma maneira de converter os dados UIImage em uma representação bitmap em que eu poderia extrair essas informações para um determinado pixel? Um exemplo de código de trabalho seria mais apreciado, e nota que eu não estou preocupado com o valor alfa.

Foi útil?

Solução

Um pouco mais em detalhe ...

Eu postei no início desta noite com uma consolidação e pequeno acréscimo ao que havia sido dito sobre esta página - que pode ser encontrado na parte inferior deste post. Estou editando o post neste momento, no entanto, para postar o que proponho é (pelo menos para as minhas necessidades, que incluem a modificação de dados pixel) um método melhor, uma vez que fornece dados graváveis ??(que, como eu o entendo, o método previsto por mensagens anteriores e na parte inferior deste post fornece uma referência só de leitura para dados).

Método 1: gravável Pixel Informação

  1. I definido constantes

    #define RGBA        4
    #define RGBA_8_BIT  8
    
  2. Na minha UIImage subclasse I declarou variáveis ??de instância:

    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. A estrutura pixel (com alfa nesta versão)

    typedef struct RGBAPixel {
        byte red;
        byte green;
        byte blue;
        byte alpha;
    } RGBAPixel;
    
  4. função Bitmap (retornos pré-calculados RGBA; divisão RGB por A para obter RGB não modificada):

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

Read-Only dados (informações Anterior) - Método 2:


Passo 1. Eu declarei um tipo de byte:

 typedef unsigned char byte;

Passo 2. I declarou um struct para corresponder a um pixel:

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

Passo 3. eu subclasse UIImageView e declarada (com propriedades sintetizados correspondente):

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

Passo 4. Código subclasse Eu coloquei em um método chamado bitmap (para retornar os dados 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;

Passo 5. Eu fiz um método de acesso:

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

Outras dicas

Aqui está a minha solução para a amostragem de cores de um UIImage.

Esta abordagem torna o pixel solicitado para uma grande tampão e retorna os valores de cor resultante como um objecto UIColor 1 px rgba. Isto é muito mais rápido do que a maioria das outras abordagens que eu vi e usa muito pouca memória.

Isso deve funcionar muito bem para algo como um seletor de cores, onde você normalmente só precisa o valor de um pixel específico em um determinado momento.

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 

Você não pode acessar os dados de bitmap de um UIImage diretamente.

Você precisa obter a representação CGImage do UIImage. Em seguida, obter provedor de dados do CGImage, de que uma representação CFData do bitmap. Certifique-se de liberar o CFData quando terminar.

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

Você provavelmente vai querer olhar para a informação bitmap do CGImage para obter fim pixel, dimensões da imagem, etc.

A resposta de Lajos funcionou para mim. Para obter os dados de pixel como uma matriz de bytes, eu fiz isso:

UInt8* data = CFDataGetBytePtr(bitmapData);

Mais informações: CFDataRef documentação .

Além disso, lembre-se de incluir CoreGraphics.framework

Obrigado a todos! Colocar algumas dessas respostas juntos eu recebo:

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

Isso só leva um intervalo de pontos 0.0-1.0 para ambos x e y. Exemplo:

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

Isso funciona muito bem para mim. Estou fazendo algumas suposições como bits por pixel e RGBA espaço de cores, mas isso deve funcionar para a maioria dos casos.

Outra nota - que está trabalhando em ambos Simulator e dispositivo para mim -. Eu tive problemas com isso no passado por causa da otimização PNG que aconteceu quando ele passou o dispositivo

Para fazer algo semelhante no meu aplicativo, eu criei um pequeno off-screen CGImageContext, e depois tornou a UIImage nele. Isso permitiu-me uma maneira rápida para extrair um número de pixels de uma só vez. Isso significa que você pode configurar o bitmap de destino em um formato que você encontrar fácil de analisar, e deixar CoreGraphics fazer o trabalho duro de converter entre modelos de cores ou formatos bitmap.

Eu não sei como índice para os dados de imagem corretamente com base em dado X, Y cordination. Alguém sabe?

pixelPosition = (x + (Y * ((ImageWidth) * BytesPerPixel)));

// campo não é um problema com este dispositivo, tanto quanto eu sei e pode ser deixado de zero ... // (ou puxado para fora da matemática).

Use ANImageBitmapRep que dá acesso a nível de pixel (leitura / gravação).

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top