markjnet

What Color is My Pixel? Image based color picker on iPhone

Color PickerMy Postcards app has a design screen where you can change colors of things, so I needed a color picker. A very easy way to make a color picker on just about any platform is to make some kind of color pallet image file, display it for the user to click on, and do a call like: getPixelColorAtScreenLocation(x,y)

This was available back in 1983 on my ZX-Spectrum, but alas Cocoa Touch doesn’t have it. (If I missed something I hope a reader will set me straight.) What are some alternatives then?

  • Ditch the color pallet and use sliders to mix colors. Not very user friendly for the everyday user.
  • Manually build a lookup table for position to color. Hard work and what if I want to use a different image?
  • Parse the image file myself and find the color at x,y using some third party library. Maybe…
  • Bend core graphics to my will and have it ‘parse’ the image file for me and make the uncompressed pixels available.

Googling around turned up a way to make the last technique work, which is good because we’re using existing APIs, and it will work for any image:

  1. Make an off screen bitmap image context that is uncompressed and has a known byte to pixel mapping.
  2. Re-draw the color picker image into that context.
  3. Turn x,y touch co-ordinates into a position in the context’s bitmap buffer.
  4. Turn the pixel bytes at the offset position into a UIColor.

I’ve wrapped this up in ColorPickerImageView, a subclass of UIImageView.  When you tap it, it uses this approach to get the pixel color you tapped.  You can also register a simple delegate with it to get a callback with the picked color. You put your own image in it, so you can make it a little color pallet in a corner or make it fill the screen. As it’s derived from UIImageView, you use it just like UIImageView in code or in Interface Builder. The  pixel grabbing source code is below, and the whole class is included in: ColorPicker code to download and copy.

- (UIColor*) getPixelColorAtLocation:(CGPoint)point {
	UIColor* color = nil;
	CGImageRef inImage = self.image.CGImage;
	// Create off screen bitmap context to draw the image into. Format ARGB is 4 bytes for each pixel: Alpa, Red, Green, Blue
	CGContextRef cgctx = [self createARGBBitmapContextFromImage:inImage];
	if (cgctx == NULL) { return nil; /* error */ }
 
    size_t w = CGImageGetWidth(inImage);
	size_t h = CGImageGetHeight(inImage);
	CGRect rect = {{0,0},{w,h}}; 
 
	// Draw the image to the bitmap context. Once we draw, the memory
	// allocated for the context for rendering will then contain the
	// raw image data in the specified color space.
	CGContextDrawImage(cgctx, rect, inImage); 
 
	// Now we can get a pointer to the image data associated with the bitmap
	// context.
	unsigned char* data = CGBitmapContextGetData (cgctx);
	if (data != NULL) {
		//offset locates the pixel in the data from x,y.
		//4 for 4 bytes of data per pixel, w is width of one row of data.
		int offset = 4*((w*round(point.y))+round(point.x));
		int alpha =  data[offset];
		int red = data[offset+1];
		int green = data[offset+2];
		int blue = data[offset+3];
		NSLog(@"offset: %i colors: RGB A %i %i %i  %i",offset,red,green,blue,alpha);
		color = [UIColor colorWithRed:(red/255.0f) green:(green/255.0f) blue:(blue/255.0f) alpha:(alpha/255.0f)];
	}
 
	// When finished, release the context
	CGContextRelease(cgctx);
	// Free image data memory for the context
	if (data) { free(data); }
 
	return color;
}
 
- (CGContextRef) createARGBBitmapContextFromImage:(CGImageRef) inImage {
 
	CGContextRef    context = NULL;
	CGColorSpaceRef colorSpace;
	void *          bitmapData;
	int             bitmapByteCount;
	int             bitmapBytesPerRow;
 
	// Get image width, height. We'll use the entire image.
	size_t pixelsWide = CGImageGetWidth(inImage);
	size_t pixelsHigh = CGImageGetHeight(inImage);
 
	// Declare the number of bytes per row. Each pixel in the bitmap in this
	// example is represented by 4 bytes; 8 bits each of red, green, blue, and
	// alpha.
	bitmapBytesPerRow   = (pixelsWide * 4);
	bitmapByteCount     = (bitmapBytesPerRow * pixelsHigh);
 
	// Use the generic RGB color space.
	colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
	if (colorSpace == NULL)
	{
		fprintf(stderr, "Error allocating color space\n");
		return NULL;
	}
 
	// Allocate memory for image data. This is the destination in memory
	// where any drawing to the bitmap context will be rendered.
	bitmapData = malloc( bitmapByteCount );
	if (bitmapData == NULL)
	{
		fprintf (stderr, "Memory not allocated!");
		CGColorSpaceRelease( colorSpace );
		return NULL;
	}
 
	// Create the bitmap context. We want pre-multiplied ARGB, 8-bits
	// per component. Regardless of what the source image format is
	// (CMYK, Grayscale, and so on) it will be converted over to the format
	// specified here by CGBitmapContextCreate.
	context = CGBitmapContextCreate (bitmapData,
									 pixelsWide,
									 pixelsHigh,
									 8,      // bits per component
									 bitmapBytesPerRow,
									 colorSpace,
									 kCGImageAlphaPremultipliedFirst);
	if (context == NULL)
	{
		free (bitmapData);
		fprintf (stderr, "Context not created!");
	}
 
	// Make sure and release colorspace before returning
	CGColorSpaceRelease( colorSpace );
 
	return context;
}

[Slashdot] [Digg] [del.icio.us] [StumbleUpon]

Comments

20 Responses to “What Color is My Pixel? Image based color picker on iPhone”
  1. Mark Thomas says:

    Awesome.

  2. Tim Burks says:

    This was on my wish list. Thanks for sharing it!

  3. AsadUllah says:

    i like this topic very much.
    extremely happy with this stuff.

  4. Excellent post!
    I did get a few warnings, but took care of those with one line change.
    http://discussions.apple.com/thread.jspa?messageID=8508469

    Again, thank you so much for this tutorial!

  5. Hello,
    great idea, I am reusing it to convert one color from a colorspace to another by creating a 1 pixel bitmap context filling it with the color.
    It worked fine but I ran into trouble when I tried to get fancy and to use a context with floating point components, according to the doc it seems supported but I couldn’t get it to work

  6. @bin says:

    Hi,
    Thanks a lot for this tutorial.without this i would be in great trouble.Also the above link helps me to fix the warnings i got in my app.

  7. David says:

    FINALLY! Do you know how hard it is to find a stupid getPixel function for IPhone? I can’t believe such a simple operation is so over complicated.

    THANK YOU SO MUCH! You’ve saved me such a huge headache! YOUlRE THE MAN!

  8. Devraj says:

    Thanks a lot for this post. What license is your code published under and are you OK with other people using the code in their projects?

  9. markj says:

    Please use the code for whatever you want. Public domain, free, no restrictions.

  10. Randall says:

    Hi,

    It works good in its original resolution, but not for a different resolution .png file. I’m using a 320×400 file.
    The pixel color is wrong in that res, but is okay in the original lower res.

    I wonder why? Any ideas? Hmmmnnnn, must study and figure it out. I need a full screen app.

    :-)

  11. markj says:

    This class will work for an image displayed at 100% resolution, but not if Core Graphics is scaling the image for the screen.

  12. hassanvfx says:

    version 3.0 on iphone frameworks is given me some erros so simple replace this line

    colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);

    by this one

    colorSpace = CGColorSpaceCreateDeviceRGB();

    thanks master is a great contribution.

  13. Sébastien says:

    Hi, Thanks for your post, excellent job. Have you an example how to create a new UIImage from the modified data ?

  14. Tuannd says:

    Awesome, THANKS A LOT. You’re so AMAZING

  15. John says:

    Have you found a way to do the pixel color with a scaled down image? I am trying to take a picture of something, and then click the image and get the color….any ideas on how to manage the scaling? I am trying to determine an algorithm for changing the “width” based on the scale….you think something like that would work?

    Thanks! Code is great, like the port from the Apple sample code. Very cool stuff.

  16. Reed Olsen says:

    Awesome stuff – you just saved me a few hours.

  17. Reed Olsen says:

    Just an FYI – I used XCode’s static analyzer on my project, and got the following warnings:

    CGContextRelease(cgctx);
    Incorrect decrement of the reference count of an object is not owned at this point by the caller

    and

    return context;
    Potential leak of an object allocated on line 116 and stored into ‘context’

    Quite frankly, I haven’t messed around with much CoreGraphics stuff, and this is gibberish to me, but I thought I’d let you know.

    Again, I appreciate you sharing this bit of code with us. Thanks!

  18. Usman Munier says:

    Mark,

    it was a nice article, mark can we also set the color of the pixel ?? i m trying to do that but still not able to find out the way in Quartz2d. your help would highly be appriciated.

    Regards,
    Usman

  19. Carl says:

    Mark,

    Looks great. I also found another color picker at http://www.v-vent.com/blog/?p=27 which might be able to be combined to have a pointer on the screen to show what pixel is being examined in the image.

    A comment on scaling for those who have asked about it. You should be able to take the x,y of the current touch, subtract any offset to match the image frame, then scale the x,y point by the inverse scaling factor from the image in memory to the screen image, which should point to the correct pixel in the original unscaled image.

    It only becomes a problem if you allow the user to modify the scaling factor by pinching the image, which then means you have to do a little more work to find out what part of the image is being displayed, and how it fits into the original image.

    Great post! And thanks, Mark, for the re-usable class.

Speak Your Mind

Tell us what you're thinking...
and oh, if you want a pic to show with your comment, go get a gravatar!

  • Categories

  • markjnet