Thursday, October 18, 2012

Drawing UIImage's with blocks

I have a slight love affair with Quartz's drawing routines. I don't know what it is about them, but there is something I love about stroking rects and filling paths. It's like OpenGL for stupid people.

Anyways, during my love affair I got bit annoyed at having to subclass UIView every time i wanted to make a quick sprite, or overlay a one image on top of another.

CALayer's a are awesome and everything, but i still need to subclass UIView unless i want a bunch of layout mess every where.

So I thought wouldn't it be cool to be able to create UIImage's quickly and then just throw them into a CALayers or UIImageView's and worry about the rest later.
#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>
typedef void(^UIImageRenderBlock)(CGContextRef context);
@interface UIImage (render)
+(UIImage *)imageWithSize:(CGSize) canvasSize block:(UIImageRenderBlock) aBlock;
@end
@implementation UIImage (render)
+(UIImage *)imageWithSize:(CGSize) canvasSize block:(UIImageRenderBlock) aBlock {
CGContextRef context;
void *bitmapData;
CGColorSpaceRef colorSpace;
int bitmapByteCount;
int bitmapBytesPerRow;
CGImageRef resultImage;
UIImage *image;
//
bitmapBytesPerRow = canvasSize.width * 4;
bitmapByteCount = (bitmapBytesPerRow * canvasSize.height);
//Create the color space
colorSpace = CGColorSpaceCreateDeviceRGB();
bitmapData = malloc( bitmapByteCount );
//Check the the buffer is alloc'd
if( bitmapData == NULL ){
NSLog(@"Buffer could not be alloc'd");
}
//Create the context
context = CGBitmapContextCreate(bitmapData, canvasSize.width, canvasSize.height, 8, bitmapBytesPerRow, colorSpace, kCGImageAlphaPremultipliedLast);
if( context == NULL ){
NSLog(@"Context could not be created");
}
//Render user data
aBlock(context);
//The contents of the context could be saved out as follows
//Get the result image
resultImage = CGBitmapContextCreateImage(context);
//Save the image
image = [UIImage imageWithCGImage:resultImage];
//Cleanup
CGImageRelease(resultImage);
free(bitmapData);
CGColorSpaceRelease(colorSpace);
return image;
}
@end

The above does just that, you give it a canvas size or more informally the size of the UIImage that you wish to generate.

It will create the CGBitmapContext with a RGB colorspace, and then you can draw to your hearts content.

You can treat the block almost like a you would drawRect. I say almost because it's not inserted into the graphics context stack, so some of the helper methods like [[UIColor blackColor] setFill] will not work, and you'll have to learn how to do it the 'proper way' (CGColorSetFillColor()). Just consider it a general rule, that if it renders something and doesn't take a CGContextRef you need an alternative method to use it inside this block.