BuySellAds.com

Until Dec 3rd, 44% off all Manning books, including Barcodes with iOS! Promo code: mobicftw
Our DNA is written in Objective-C
Jump

UIImage from UIView

This is a neat trick that I developed when I needed a PNG Image with the same content that I was already drawing in a UIView. I had finished the broad strokes of a small app I am developing and then I figured it would be a neat trick to also be able to e-mail a graphic I was drawing as an attached image.

Turns out to be not that hard to do – after a couple of hours of trial and error. Though attaching said PNG file to an e-mail is only something that’s available with SDK 3.0 and above. But that’s another story, let’s stick to the image generation technique I developed. And one more thing: I am NOT taking about the contents of subviews to be put into an image, strictly what’s the result of the UIView’s drawRect method.

That’s actually the first hint. You override a view’s drawRect method to have it draw just about anything you can imagine. Usually with CoreGraphics methods which are also the namesake for all the method names beginning with CG. One parameter of all of these methods is a drawing context which is sort of a handle for your drawing area.

- (void)drawRect:(CGRect)rect
{
	CGContextRef context = UIGraphicsGetCurrentContext();
	// do your drawing
}

Inside this drawRect you have the UIView’s dimensions in parameter rect and by the function shown you can retrieve the current context to draw on. If you want to draw text there are several methods: draw glyphs, use CGShowText methods, or use the UIKit additions to NSString. The latter is the easiest as you don’t have to modify the text matrix for your letters not be be upside down. Also the UIKit category methods automatically use the current context, so you don’t have to retrieve it.

clock faceSo I set up all my drawing to draw a nice clock face and also added capability of showing a red arrow from any hour to any other hour. I will spare you the nitty gritty details, but if you ever want or need to draw a clock like this you are welcome to contact me, like most of my code you can have a look for a small donation.

Having spent some hours on getting the look right with all the details I naturally was inclined to find a way to reuse it to also generate a UIImage with it. Could not be that hard, could it?

The first obvious thought is to try and simple try to generate a bitmap out of the drawing context for the view, but that does not work because internally a bitmap context and a view drawing context are laid out differently. But this got my train of thought going. If I could refactor the drawing into a seperate method which would take the drawing rectangle and the context I should be able to reuse the same code for both kinds of drawing.

So I moved the code into a seperate method and passed the rect and current context to it. It was still drawing nicely. So the next step was to create a bitmap context to draw upon instead. In the documentation I found a nice method which I slightly modified.

- (CGContextRef) createBitmapContextSuitableForView
{
	int pixelsWide = self.bounds.size.width;
	int pixelsHigh = self.bounds.size.height;
    CGContextRef    context = NULL;
    CGColorSpaceRef colorSpace;
    void *          bitmapData;
    int             bitmapByteCount;
    int             bitmapBytesPerRow;
 
    bitmapBytesPerRow   = (pixelsWide * 4);// 1
    bitmapByteCount     = (bitmapBytesPerRow * pixelsHigh);
 
    colorSpace = CGColorSpaceCreateDeviceRGB();  // modification from sample
    bitmapData = malloc( bitmapByteCount );
 
	memset(bitmapData, 255, bitmapByteCount);  // wipe with 100% white
    if (bitmapData == NULL)
    {
        fprintf (stderr, "Memory not allocated!");
        return NULL;
    }
    context = CGBitmapContextCreate (bitmapData,
			pixelsWide,
			pixelsHigh,
			8,      // bits per component
			bitmapBytesPerRow,
			colorSpace,
			kCGImageAlphaPremultipliedLast);
    if (context== NULL)
    {
        free (bitmapData);
        fprintf (stderr, "Context not created!");
        return NULL;
    }
    CGColorSpaceRelease( colorSpace );
 
    return context;
}

Inside my UIView subclass I know the size so it’s not necessary to pass a size parameter like in the sample. Also one modification was necessary because the generic RGB color space is retrieved differently on the iphone, namely by means of CGColorSpaceCreateDeviceRGB. Also note the extra line with the memset which essentially wipes the memory clean. Malloc does not clear it out and if you don’t draw a background you will end up with crap pixels appearing where you did not put pixels yourself.

Getting there, only 2 more challenges left. First problem: Text drawn with the UIKit Additions is not showing on the image. Second problem: It’s all upside down on the bitmap.

The first problem can be solved by pushing the bitmap context as active because the drawing methods will always use the current context. So I am pushing the passed context at the beginning of my clock face drawing method and of course popping at the end. An extra-pushing of the view drawing context does not hurt if you pop afterwards and for the bitmap context this is necessary to make it active for the easy text drawing to work.

The second problem comes from the coordinate system for views having their 0,0-point on the upper left, whereas the coordinate system for bitmap contexts has it in the lower left corner. So I added a parameter that would allow me to flip the coordinate system by applying a negative 1 factor to y and then translating it all back into view. For the regular view drawing the flipping is off, for the bitmap is is on.

- (void)drawClockInRect:(CGRect)rect context:(CGContextRef)context flipped:(BOOL)isFlipped
{
	UIGraphicsPushContext(context);
 
	if (isFlipped)
	{
		CGContextGetCTM(context);
		CGContextScaleCTM(context, 1, -1);
		CGContextTranslateCTM(context, 0,
				-self.bounds.size.height);
	}
 
	// amazing drawing technique omitted for brevity ;-)
 
	UIGraphicsPopContext();
}
 
- (void)drawRect:(CGRect)rect
{
 	CGContextRef context = UIGraphicsGetCurrentContext();
	[self drawClockInRect:rect context:context flipped:NO];
}

That takes care of all the drawing modifications necessary. To wrap up all I needed to add is a method that creates the bitmap contexts, converts it into a UIImage and returns this after cleaning up.

- (UIImage *) imageFromView
{
	CGContextRef bitmapContext = [self createBitmapContextSuitableForView];
	[self drawClockInRect:CGRectMake(0, 0, self.bounds.size.width,
		 self.bounds.size.height) context:bitmapContext flipped:YES];
	CGImageRef image = CGBitmapContextCreateImage(bitmapContext);
	UIImage *newImage = [UIImage imageWithCGImage:image];
	CGContextRelease(bitmapContext);
	CGImageRelease(image);
 
	return newImage;
}

To attach this newly created image to an e-mail mail you need to get a PNG representation for it in an NSData and attach it.

MFMailComposeViewController *mailView = [[[MFMailComposeViewController alloc] init]
									autorelease];
 
UIImage *clockImage = [clock imageFromView];
NSData *imageData = UIImagePNGRepresentation (clockImage);
[mailView addAttachmentData:imageData  mimeType:@"image/png" fileName:@"clock.png"];
// set message body and present the view controller

Here you have an easy way to reuse drawing code you already have to also create bitmaps that you can let your users mail or maybe copy/paste. Instead of adding the flipping to the drawing method you could also modify the coordinate system of bitmap context. But for now I was satisfied with the result and so I stopped tinkering while it was working.


Categories: Recipes

%d bloggers like this: