Jak uzyskać wartości RGB dla piksela na obrazie na iPhonie

Piszę aplikację na iPhone 'a i muszę zasadniczo zaimplementować coś równoważnego narzędziu "kroplomierz" w Photoshopie, gdzie można dotknąć punktu na obrazie i przechwycić wartości RGB dla danego piksela, aby określić i dopasować jego kolor. Uzyskanie interfejsu użytkownika jest łatwą częścią, ale czy istnieje sposób na przekonwertowanie danych interfejsu użytkownika na reprezentację bitmapową, w której mógłbym wyodrębnić te informacje dla danego piksela? Próbka kodu roboczego byłaby najbardziej doceniam i zauważ, że nie interesuje mnie wartość alfa.

Author: Chris Hanson, 2008-09-27

8 answers

Trochę więcej szczegółów...

Opublikowałem dziś wieczorem z konsolidacją i małym dodatkiem do tego, co zostało powiedziane na tej stronie - które można znaleźć na dole tego postu. Edytuję post w tym momencie, jednak, aby opublikować to, co proponuję (przynajmniej dla moich wymagań, które obejmują modyfikowanie danych pikseli) lepszą metodą, ponieważ zapewnia dane zapisywalne (podczas gdy, jak rozumiem, metoda przewidziana w poprzednich postach i na dole tego postu zapewnia lepszą odniesienie tylko do odczytu danych).

Metoda 1: Zapisywalne Informacje O Pikselach

  1. Zdefiniowałem stałe

    #define RGBA        4
    #define RGBA_8_BIT  8
    
  2. W mojej podklasie UIImage zadeklarowałem zmienne instancji:

    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. Struktura pikseli (z alfą w tej wersji)

    typedef struct RGBAPixel {
        byte red;
        byte green;
        byte blue;
        byte alpha;
    } RGBAPixel;
    
  4. Funkcja Bitmap (zwraca wstępnie obliczone RGBA; dzieli RGB przez A, aby uzyskać niezmodyfikowane RGB):
    -(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;
    }
    

Dane tylko do odczytu ( poprzednie informacje) - metoda 2:


Krok 1. I zadeklarował typ dla bajtu:

 typedef unsigned char byte;

Krok 2. Zadeklarowałem strukturę odpowiadającą pikselowi:

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

Krok 3. Podklasowałem UIImageView i zadeklarowałem (z odpowiednimi syntetyzowanymi właściwościami):

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

Krok 4. Kod podklasy umieszczam w metodzie o nazwie bitmap (aby zwrócić dane o pikselach 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;

Krok 5. Zrobiłem metodę accessor:

-(RGBPixel*)pixelDataForRow:(int)row column:(int)column{
    //Return a pointer to the pixel data
    return &pixelData[row * column];           
}
 38
Author: Asher,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-29 11:24:48

Oto moje rozwiązanie do próbkowania koloru interfejsu.

To podejście renderuje żądany piksel do dużego bufora RGBA o rozmiarze 1px i zwraca wynikowe wartości kolorów jako obiekt UIColor. Jest to znacznie szybsze niż większość innych podejść, które widziałem i wykorzystuje tylko bardzo mało pamięci.

Powinno to działać całkiem dobrze dla czegoś takiego jak próbnik kolorów, gdzie zazwyczaj potrzebujesz tylko wartości jednego określonego piksela w danym momencie.

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 
 22
Author: Matej Bukovinski,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-09-17 09:15:31

Nie można uzyskać dostępu do danych bitmapowych interfejsu użytkownika bezpośrednio.

Musisz uzyskać reprezentację Cgimage UIImage. Następnie pobierz dostawcę danych CGImage, z którego pochodzi Reprezentacja cfdata bitmapy. Upewnij się, że zwolnisz CFData po zakończeniu.

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

Prawdopodobnie będziesz chciał spojrzeć na informacje o bitmapie CGImage, aby uzyskać kolejność pikseli, wymiary obrazu itp.

 11
Author: lajos,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-28 01:12:55

Odpowiedź Lajosa zadziałała. Aby uzyskać dane pikseli jako tablicę bajtów, zrobiłem to:

UInt8* data = CFDataGetBytePtr(bitmapData);

Więcej informacji: cfdataref dokumentacja .

Pamiętaj również o dołączeniu CoreGraphics.framework

 5
Author: iggames,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-12-29 01:09:01

Dziękuję wszystkim! Składając kilka z tych odpowiedzi otrzymuję:

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

To tylko przyjmuje zakres punktowy 0.0 - 1.0 zarówno dla x jak i y. przykład:

UIColor* sampledColor = [self colorFromImage:image
         sampledAtPoint:CGPointMake(p.x/imageView.frame.size.width,
                                    p.y/imageView.frame.size.height)];
To mi pasuje. Robię kilka założeń, takich jak bity na piksel i przestrzeń kolorów RGBA, ale to powinno działać w większości przypadków.

Kolejna uwaga - działa zarówno na symulatorze, jak i urządzeniu - miałem z tym problemy w przeszłości z powodu optymalizacji PNG, która miała miejsce, gdy poszedł na urządzenie.

 3
Author: garafajon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-08-20 04:00:58

Aby zrobić coś podobnego w mojej aplikacji, stworzyłem mały cgimagecontext poza ekranem, a następnie wyrenderowałem do niego interfejs. To pozwoliło mi na szybki sposób wyodrębnić kilka pikseli na raz. Oznacza to, że można skonfigurować docelową bitmapę w formacie, który można łatwo przeanalizować, i pozwolić firmie CoreGraphics wykonywać ciężką pracę polegającą na konwersji między modelami kolorów lub formatami bitmap.

 1
Author: Mark Bessey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-01 21:37:36

Nie wiem, jak poprawnie indeksować dane obrazkowe na podstawie podanej korekcji X, Y. Czy ktoś wie?

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

/ / pitch nie jest problemem z tym urządzeniem o ile wiem i może być zerowy... // (lub wycofany z matematyki).

 1
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-03-14 04:04:57

Użyj ANImageBitmapRep , który daje dostęp na poziomie pikseli (Odczyt/Zapis).

 1
Author: smdvlpr,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-04-13 17:15:42