Tuesday 17 February 2009

Getting at pixel data from a UIImage for iPhone dev

[This blog has moved and this article can now be found at http://www.levelofindirection.com/journal/2009/9/24/getting-at-pixel-data-from-a-uiimage-for-iphone-dev.html. It still appears here for archival purposes]


UIImage is a fairly thin wrapper for a CGImage (so most of this applies to standard Mac dev too), but CGImage is an opaque type. You can't get at the raw image data (actually you can, but it's in it's encoded form).

If you want to test individual pixel values you must render the image into a bitmap context, having supplied the context with a memory buffer that you control.

This is not as complex as it sounds, but still has quite a few steps - and hunting around for the information may take you down a few blind alleys or unnecessary steps.

Erica Sadun, in her iPhone Developer's Cookbook has a recipe for this, but I'd already come up with some code beforehand. I believe mine is more concise, and doesn't suffer from the sub-pixel problem (that an earlier version of mine had, and I believe Sadun's is susceptible to too). So while I recommend the book, I'm presenting my code here too.

As written it is only interested in alpha values, but some small tweaks will let you get at the colour information too.


//
// AlphaPixels.h
//
// Created by Phil Nash on 26/10/2008.
// Copyright 2008 Two Blue Cubes Software
//
// Distributed under the Boost Software License, Version 1.0.
// (see: http://www.boost.org/LICENSE_1_0.txt)

#import <UIKit/UIKit.h>

@interface AlphaPixels : NSObject
{
unsigned char* pixelData;
int width;
}

-(id) initWithImage: (UIImage*) image;
-(float) alphaAtX:(int) x y:(int) y;

@end



//
// AlphaPixels.m
//
// Created by Phil Nash on 26/10/2008.
// Copyright 2008 Two Blue Cubes Software
//
// Distributed under the Boost Software License, Version 1.0.
// (see: http://www.boost.org/LICENSE_1_0.txt)
//

#import "AlphaPixels.h"

@implementation AlphaPixels

-(id) initWithImage: (UIImage*) image
{
// We'll be needing a chunk of memory to hold the rendered (alpha channel of the) image...
width = image.size.width;
int height = image.size.height;
pixelData = malloc( width * height );

if( !pixelData )
{
NSException *exception = [NSException exceptionWithName:@"AlphaPixelsException"
reason:@"Unable to allocate memory for pixel data"
userInfo:nil];
@throw exception;
}

// ... and a bitmap context to describe how to render - the alpha only constant
// is the important bit here (no need to provide a colorspace)
CGContextRef context = CGBitmapContextCreate ( pixelData,
width,
height,
8,
width,
NULL,
kCGImageAlphaOnly );

if( !context )
{
NSException *exception = [NSException exceptionWithName:@"AlphaPixelsException"
reason:@"Unable to create bitmap context"
userInfo:nil];
@throw exception;
}

// Render the image into the context (ending up in our buffer)
CGContextDrawImage( context, CGRectMake(0, 0, width, height), image.CGImage );
CGContextRelease( context );

return self;
}

-(void) dealloc
{
free( pixelData );
[super dealloc];
}

-(float) alphaAtX:(int) x y:(int) y
{
// Simple calculation to get the offset into the buffer for the coordinate values
// - but note these are all integer values. Floats representing sub-pixel values would
// need to be rounded before doing something similar
return pixelData[y * width + x]/255;
}

@end

3 comments:

Anonymous said...

Great job!
But I still have problem in getting all the ARGB components.

I expand the data space 4 times and use 'KCGImageAlphaFirst', but an error occurs when creating the bitmap context.

Any ideas about how to pull out ARGB components?

Anonymous said...

Can you provide any more information about getting the color data instead of just the alpha channel?

I have 4 channel pixelData, but I don't know how to get separate red, blue, and green data.

Anonymous said...

great ! but it was not working perfectly for me until i added after the malloc a reset of the allocated memory...

-(id) initWithImage: (UIImage*) image
{
// We'll be needing a chunk of memory to hold the rendered
// (alpha channel of the) image...
width = image.size.width;
int height = image.size.height;
pixelData = malloc( width * height );

// reset of memory :
for (int i=0; i<width*height; i++) {
pixelData[i] = 0;
}
...
...

ilboued !