347UIImage → pixelData → UIImage Rountrip

The questions is rather simple: How to manipulate single pixels of an UIImage? The answer is rather long; but includes a joyful trip into Quartz 2D Graphic land...

// load image, convert to CGImageRef
UIImage *c = [UIImage imageNamed:@"c.png"];
CGImageRef cRef = CGImageRetain(c.CGImage);

// png alpha to mask
NSData* pixelData = (NSData*) CGDataProviderCopyData(CGImageGetDataProvider(cRef));
// image raw data

//NSData* pixelDataRep = UIImagePNGRepresentation(c);
// compressed png data

//NSLog(@"pixelData %i", [pixelData length]);
//NSLog(@"pixelDataRep %i", [pixelDataRep length]);
//NSLog(@"pixelDataRep equal to pixelData: %@", [pixelData isEqualToData:pixelDataRep] ? @"YES" : @"NO");

//UIImage* newImage = [UIImage imageWithData:pixelData];
//[newImage drawInRect:CGRectMake(10, 340, 65, 65)];

//NSLog(@"pixelData %@", pixelData);

unsigned char* pixelBytes = (unsigned char *)[pixelData bytes];
// return pointer to data

// step through char data
for(int i = 0; i < [pixelData length]; i += 4) {
    // change accordingly
    pixelBytes[i] = pixelBytes[i];
    pixelBytes[i+1] = pixelBytes[i+1];
    pixelBytes[i+2] = pixelBytes[i+2];
    pixelBytes[i+3] = 255;
}
//1ms in Simulator , 5ms on iPhone 3GS , 65x65 pixel

// copy bytes in new NSData
NSData* newPixelData = [NSData dataWithBytes:pixelBytes length:[pixelData length]];
//NSLog(@"newPixelData %@", newPixelData);
//NSLog(@"newPixelData: %@", newPixelData ? @"ok" : @"nil");
//NSLog(@"newPixelData equal to pixelData: %@", [pixelData isEqualToData:newPixelData] ? @"YES" : @"NO");

// cast NSData as CFDataRef
CFDataRef imgData = (CFDataRef)pixelData;

//NSLog(@"CFDataGetLength %i", CFDataGetLength(imgData) );

    // Make a data provider from CFData
    CGDataProviderRef imgDataProvider = CGDataProviderCreateWithCFData(imgData);

    // testing... create data provider from file.... works
    //NSString* imageFileName = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"c.png"];
    //CGDataProviderRef imgDataProvider = CGDataProviderCreateWithFilename([imageFileName UTF8String]);

// does not work like that
// new image needs to get PNG properties
//CGImageRef throughCGImage = CGImageCreateWithPNGDataProvider(imgDataProvider, NULL, true, kCGRenderingIntentDefault);

// get PNG properties from cRef
size_t width = CGImageGetWidth(cRef);
size_t height = CGImageGetHeight(cRef);
size_t bitsPerComponent = CGImageGetBitsPerComponent(cRef);
size_t bitsPerPixel = CGImageGetBitsPerPixel(cRef);
size_t bytesPerRow = CGImageGetBytesPerRow(cRef);
CGColorSpaceRef colorSpace = CGImageGetColorSpace(cRef);
CGBitmapInfo info = CGImageGetBitmapInfo(cRef);
CGFloat *decode = NULL;
BOOL shouldInteroplate = NO;
CGColorRenderingIntent intent = CGImageGetRenderingIntent(cRef);

// cRef PNG properties + imgDataProvider's data
CGImageRef throughCGImage = CGImageCreate(width, height, bitsPerComponent, bitsPerPixel, bytesPerRow, colorSpace, info, imgDataProvider, decode, shouldInteroplate, intent);
CGDataProviderRelease(imgDataProvider);

//NSLog(@"c %i, throughCGImage: %i", CGImageGetHeight(cRef), CGImageGetHeight(throughCGImage) );
CGImageRelease(throughCGImage);

// make UIImage with CGImage
UIImage* newImage = [UIImage imageWithCGImage:throughCGImage];
//NSLog(@"newImage: %@", newImage);

// draw UIImage
[newImage drawInRect:CGRectMake(10, 340, 65, 65)];

References:

334Combining Images with UIImage & CGContext – (Offscreen drawing)

In Cocoa NSImage has a lockFocus method, that allows to draw images offscreen and combine them into one.

[img lockFocus];
//...
[img unlockFocus];

On the iPhone, UIImage lacks the lockFocus methods, instead the following:

// Create new offscreen context with desired size
UIGraphicsBeginImageContext(CGSizeMake(64.0f, 64.0f));

// draw img at 0,0 in the context
[img drawAtPoint:CGPointZero];

// draw another at 0,0 in the context, maybe with an alpha value
[another drawAtPoint:CGPointZero];

// ... and other operations

// assign context to UIImage
UIImage *outputImg = UIGraphicsGetImageFromCurrentImageContext();

// end context
UIGraphicsEndImageContext();