Domanda

Sto scrivendo un'applicazione per iPhone e devo essenzialmente implementare qualcosa di equivalente allo strumento 'contagocce' in Photoshop, dove puoi toccare un punto sull'immagine e catturare i valori RGB per il pixel in questione per determinare e abbinare il suo colore . Ottenere UIImage è la parte facile, ma c'è un modo per convertire i dati UIImage in una rappresentazione bitmap in cui potrei estrarre queste informazioni per un dato pixel? Un esempio di codice funzionante sarebbe molto apprezzato e nota che non mi interessa il valore alfa.

È stato utile?

Soluzione

Un po 'più di dettaglio ...

Ho pubblicato stasera in precedenza con un consolidamento e una piccola aggiunta a quanto era stato detto in questa pagina, che può essere trovato in fondo a questo post. Sto modificando il post a questo punto, tuttavia, per pubblicare ciò che propongo è (almeno per i miei requisiti, che includono la modifica dei dati pixel) un metodo migliore, in quanto fornisce dati scrivibili (mentre, a mio avviso, il metodo fornito dai post precedenti e nella parte inferiore di questo post fornisce un riferimento di sola lettura ai dati).

Metodo 1: informazioni sui pixel scrivibili

  1. Ho definito costanti

    #define RGBA        4
    #define RGBA_8_BIT  8
    
  2. Nella mia sottoclasse UIImage ho dichiarato le variabili di istanza:

    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 struttura dei pixel (con alfa in questa versione)

    typedef struct RGBAPixel {
        byte red;
        byte green;
        byte blue;
        byte alpha;
    } RGBAPixel;
    
  4. Funzione bitmap (restituisce RGBA precalcolato; dividi RGB per A per ottenere RGB non modificato):

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

Dati di sola lettura (informazioni precedenti) - metodo 2:


Passaggio 1. Ho dichiarato un tipo per byte:

 typedef unsigned char byte;

Passaggio 2. Ho dichiarato che uno struct corrisponde a un pixel:

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

Passaggio 3. Ho eseguito la sottoclasse di UIImageView e dichiarato (con le corrispondenti proprietà sintetizzate):

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

Passaggio 4. Codice della sottoclasse Ho inserito un metodo denominato bitmap (per restituire i dati 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;

Passaggio 5. Ho creato un metodo di accesso:

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

Altri suggerimenti

Ecco la mia soluzione per campionare il colore di un UIImage.

Questo approccio trasforma il pixel richiesto in un buffer RGBA grande 1px e restituisce i valori di colore risultanti come oggetto UIColor. Questo è molto più veloce della maggior parte degli altri approcci che ho visto e utilizza solo pochissima memoria.

Questo dovrebbe funzionare abbastanza bene per qualcosa come un selettore di colori, in cui in genere è necessario solo il valore di un pixel specifico in un dato 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 

Non è possibile accedere direttamente ai dati bitmap di un UIImage.

Devi ottenere la rappresentazione CGImage di UIImage. Quindi ottenere il fornitore di dati del CGImage, da quello una rappresentazione CFData della bitmap. Assicurati di rilasciare CFData al termine.

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

Probabilmente vorrai guardare le informazioni bitmap di CGImage per ottenere l'ordine dei pixel, le dimensioni dell'immagine, ecc.

La risposta di Lajos ha funzionato per me. Per ottenere i dati pixel come una matrice di byte, ho fatto questo:

UInt8 * data = CFDataGetBytePtr (bitmapData);

Ulteriori informazioni: CFDataRef documentazione .

Inoltre, ricorda di includere CoreGraphics.framework

Grazie a tutti! Mettendo insieme alcune di queste risposte ottengo:

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

Questo richiede solo un intervallo di punti 0,0-1,0 sia per x che per y. Esempio:

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

Questo funziona alla grande per me. Sto facendo un paio di ipotesi come bit per pixel e spazio colore RGBA, ma questo dovrebbe funzionare nella maggior parte dei casi.

Un'altra nota - sta funzionando sia sul simulatore che sul dispositivo per me - ho avuto problemi con quello in passato a causa dell'ottimizzazione del PNG avvenuta quando è andato sul dispositivo.

Per fare qualcosa di simile nella mia applicazione, ho creato un piccolo CGImageContext fuori dallo schermo, e quindi ho reso UIImage in esso. Questo mi ha permesso di estrarre rapidamente un numero di pixel contemporaneamente. Ciò significa che puoi impostare la bitmap di destinazione in un formato che ritieni facile da analizzare e lasciare che CoreGraphics faccia il duro lavoro di conversione tra modelli di colore o formati di bitmap.

  

Non so come indicizzare correttamente i dati delle immagini in base alla coordinazione X, Y data. Qualcuno lo sa?

pixelPosition = (x + (y * ((imagewidth) * BytesPerPixel)));

// pitch non è un problema con questo dispositivo per quanto ne so e può essere lasciato zero ... // (o tirato fuori dalla matematica).

Utilizza ANImageBitmapRep che consente l'accesso a livello di pixel (lettura / scrittura).

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top