How to get the RGB values for a pixel on an image on the iphone
Solution 1
A little more detail...
I posted earlier this evening with a consolidation and small addition to what had been said on this page - that can be found at the bottom of this post. I am editing the post at this point, however, to post what I propose is (at least for my requirements, which include modifying pixel data) a better method, as it provides writable data (whereas, as I understand it, the method provided by previous posts and at the bottom of this post provides a read-only reference to data).
Method 1: Writable Pixel Information
I defined constants
#define RGBA 4 #define RGBA_8_BIT 8
In my UIImage subclass I declared instance variables:
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;
The pixel struct (with alpha in this version)
typedef struct RGBAPixel { byte red; byte green; byte blue; byte alpha; } RGBAPixel;
Bitmap function (returns pre-calculated RGBA; divide RGB by A to get unmodified 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; }
Read-Only Data (Previous information) - method 2:
Step 1. I declared a type for byte:
typedef unsigned char byte;
Step 2. I declared a struct to correspond to a pixel:
typedef struct RGBPixel{
byte red;
byte green;
byte blue;
}
RGBPixel;
Step 3. I subclassed UIImageView and declared (with corresponding synthesized properties):
// 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;
Step 4. Subclass code I put in a method named bitmap (to return the bitmap pixel data):
//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;
Step 5. I made an accessor method:
-(RGBPixel*)pixelDataForRow:(int)row column:(int)column{
//Return a pointer to the pixel data
return &pixelData[row * column];
}
Solution 2
Here is my solution for sampling color of an UIImage.
This approach renders the requested pixel into a 1px large RGBA buffer and returns the resulting color values as an UIColor object. This is much faster than most other approaches I've seen and uses only very little memory.
This should work pretty well for something like a color picker, where you typically only need the value of one specific pixel at a any given time.
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
Solution 3
You can't access the bitmap data of a UIImage directly.
You need to get the CGImage representation of the UIImage. Then get the CGImage's data provider, from that a CFData representation of the bitmap. Make sure to release the CFData when done.
CGImageRef cgImage = [image CGImage];
CGDataProviderRef provider = CGImageGetDataProvider(cgImage);
CFDataRef bitmapData = CGDataProviderCopyData(provider);
You will probably want to look at the bitmap info of the CGImage to get pixel order, image dimensions, etc.
Solution 4
Lajos's answer worked for me. To get the pixel data as an array of bytes, I did this:
UInt8* data = CFDataGetBytePtr(bitmapData);
More info: CFDataRef documentation.
Also, remember to include CoreGraphics.framework
Solution 5
Thanks everyone! Putting a few of these answers together I get:
- (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;
}
This just takes a point range 0.0-1.0 for both x and y. Example:
UIColor* sampledColor = [self colorFromImage:image
sampledAtPoint:CGPointMake(p.x/imageView.frame.size.width,
p.y/imageView.frame.size.height)];
This works great for me. I am making a couple assumptions like bits per pixel and RGBA colorspace, but this should work for most cases.
Another note - it is working on both Simulator and device for me - I have had problems with that in the past because of the PNG optimization that happened when it went on the device.
Peter
Technical senior executive specializing in creation and leadership of top-tier engineering teams that care deeply about the underlying business. Experienced developer and architect, specializing in highly scalable cloud-based SaaS applications.
Updated on July 30, 2020Comments
-
Peter almost 4 years
I am writing an iPhone application and need to essentially implement something equivalent to the 'eyedropper' tool in photoshop, where you can touch a point on the image and capture the RGB values for the pixel in question to determine and match its color. Getting the UIImage is the easy part, but is there a way to convert the UIImage data into a bitmap representation in which I could extract this information for a given pixel? A working code sample would be most appreciated, and note that I am not concerned with the alpha value.
-
user2633501 over 14 yearsGreat detail but this answer could do with some tidying up. The code needs to be properly marked as code so it presents properly. I'd do it myself but I don't yet have the ability to edit other people's answers.
-
Marius over 13 yearskeep in mind that
CFDataGetBytePtr(bitmapData)
returns aconst UInt 8*
that's a const and thus modifying it may lead to unpredictability. -
John Riselvato about 11 yearsI did some timestamp testing and this code is actually faster then stackoverflow.com/questions/1911360/…
-
Victor Engel about 11 yearsHmmm. This seems to work except for transparency. I added the methods to convert to HSBA and RGBA components, and when I query a transparent part of an image (alpha=0.0), what I actually get is 0,0,0,1.0 for both HSBA and RGBA rather than ?,?,?,0.0 as I expected. Do you have an explanation for this? I could just check for the first three components being 0.0, but that would be indistinguishable from black. Edit: I see you've hard coded alpha. Any way to get it from the data instead?
-
Victor Engel about 11 yearsI figured it out and edited your solution to handle alpha properly.
-
Victor Engel almost 11 yearsThis needs to be modified for retina displays. The size of a UIImage is in points, not pixels. The size needs to be multiplied by the scaling factor (self.scale).
-
Victor Engel almost 11 yearsMy project that uses code based upon this solution seems to have a memory leak. I don't really understand what's happening with memory here. Perhaps someone can point me to an explanation. I omitted the CFRelease statement because I didn't think it was needed with ARC. Adding it back didn't fix the leak but I haven't been able to discern what's using up all the memory yet. If I had a better understanding of what's happening with memory here, I think that might help me find the problem.
-
Victor Engel almost 11 yearsShows up in allocations instrument as UIDeviceRGBColor - 32 byte growth instances (tons of them).
-
Victor Engel almost 11 yearsI had been using the instrument on my device, a retina iPad. Switching to the ipad simulator (with retina selected) I do not see the same leak I was seeing before. I'm new to using instruments. Should I expect to see a different result from my device vs. the simulator? Which should I believe?
-
Victor Engel over 10 yearsI was getting unexpected results using another method on an image that is created using
UIGraphicsBeginImageContext
,CGContextAddArc
, etc. into the image property of a UIImageView that starts out nil. The image displays fine, but I'm unable to get a color from a point. So I tried this method, and I get all zeroes for r,g,b, and 192 for alpha, not just for the blank image but even after it has been drown with circles. I thought this category would work because you're forcing a color space that you want, etc., but it's not working. Any ideas? -
Victor Engel over 10 yearsI take it back. r,g,b are always 0. Alpha appears to be random per session.
-
Victor Engel over 10 years@Matej, I figured out my problem. I was using a retina device, so the images had a scale factor of 2. I would suggest adding an argument to this method, scale, which is multiplied by position.x and position.y to get the correct location in the image to check the color.
-
Victor Engel over 10 yearsEdited code to include multiplication of self.scale. No additional argument is necessary because scale is a property of UIImage objects.
-
Matej Bukovinski over 10 yearsI have to admit, I never tested this with @2x images. I thought CGImageCreateWithImageInRect is supposed to do the right thing here. What I did see though were some problems with images that have different imageOrientation values applies (mainly from the device camera) - gist.github.com/matej/8052724 . So you're saying CGRectMake(position.x * self.scale, position.y * self.scale, 1.f, 1.f); fixes your issue?
-
Robert J. Clegg about 10 yearsThis code works perfectly and does as I needed. Thanks!!