BuySellAds.com

If you like my tutorials, you will love my book . It is chockful of advanced programming techniques and the only comprehensive barcode reference for serious iOS developers.
Our DNA is written in Objective-C
Jump

Drawing on UIImages

Jayesh asks:

Thanks for your article on UIImage from UIView.

Need one more favor; I am in situation where I want to draw line or area (e.g. rectangle) on UI Image (e.g. Blue print) and save it again.

Basically I will show image and put circle / rectangle on image showing area in blue print and save it.

How should I do that? Can you please suggest approach, sample codes etc?

So the task is to take a UIImage, make it writable in some way and then make a new image out of that for later use. Off the top of my head, I can immediately think of two ways to do that: with UIKit and with CoreGraphics. CoreGraphics has a slight advantage, being lower level, of being thread-safe. But for a simple graphical addition to an existing image I see nothing wrong with UIKit. As usual you should only do very quick operations with UIKit because it requires to be run on the main thread which is the only thread updating the user interface.

So, first, let’s create a method that adds a circle to a passed UIImage.

- (UIImage *)imageByDrawingCircleOnImage:(UIImage *)image
{
	// begin a graphics context of sufficient size
	UIGraphicsBeginImageContext(image.size);
 
	// draw original image into the context
	[image drawAtPoint:CGPointZero];
 
	// get the context for CoreGraphics
	CGContextRef ctx = UIGraphicsGetCurrentContext();
 
	// set stroking color and draw circle
	[[UIColor redColor] setStroke];
 
	// make circle rect 5 px from border
	CGRect circleRect = CGRectMake(0, 0,
				image.size.width,
				image.size.height);
	circleRect = CGRectInset(circleRect, 5, 5);
 
	// draw circle
	CGContextStrokeEllipseInRect(ctx, circleRect);
 
	// make image out of bitmap context
	UIImage *retImage = UIGraphicsGetImageFromCurrentImageContext();
 
	// free the context
	UIGraphicsEndImageContext();
 
	return retImage;
}

This code is only using UIKit methods, except for the lonely CGContextStrokeEllipseInRect. This is the reason why we need to retrieve a handle for the current context. UIKit has it’s own reference so that all UI* methods work without specifically setting a context. Should you ever need to force a specific context that you had created elsewhere, then you can use UIGraphicsPushContext to put this other context “topmost” and UIGraphicsPopContext to clean up.

To use the above mentioned function all you need is this:

// load image from bundle
UIImage *image = [UIImage imageNamed:@"user-1.png"];
 
// call circle drawing
UIImage *imageWithCircle = [self imageByDrawingCircleOnImage:image];
 
// save it to documents
NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
							NSUserDomainMask, YES) lastObject];
NSString *filePath = [documentsPath stringByAppendingPathComponent:@"output.png"];
NSData *imageData = UIImagePNGRepresentation(imageWithCircle);
[imageData writeToFile:filePath atomically:YES];
 
NSLog(@"Saved new image to %@", filePath);

So, that’s the UIKit variant. Sometimes though you need to draw on a background thread and thus have to be sure to use thread-safe methods.

The iOS 4 release notes mention that drawing images and fonts is thread-safe as of SDK 4.0.

Drawing to a graphics context in UIKit is now thread-safe. Specifically:

  • The routines used to access and manipulate the graphics context can now correctly handle contexts residing on different threads.
  • String and image drawing is now thread-safe.

Using color and font objects in multiple threads is now safe to do.

So the above sample should be fine for most purposes. Calling it on a background thread would work on 4.0 and above, but cause weird error messages on earlier SDK versions. One example might be if you needed to draw contents of a CATiledLayer, which is occuring on a special background thread.

So how would this example look in CoreGraphics and without UIKit functions?

First we would require a method to create a bitmap context of sufficient size. I took an example I found somewhere and modified it to my needs. I actually discovered that you don’t have to allocate the storage for the bitmap context yourself, it works fine as of 3.2, even though the documentation states that you should not do that before 4.0. Well, it works, I’ll stick with it.

CGContextRef newBitmapContextSuitableForSize(CGSize size)
{
	int pixelsWide = size.width;
	int pixelsHigh = size.height;
    CGContextRef    context = NULL;
    CGColorSpaceRef colorSpace;
   // void *          bitmapData;
    int             bitmapByteCount;
    int             bitmapBytesPerRow;
 
    bitmapBytesPerRow   = (pixelsWide * 4); //4
    bitmapByteCount     = (bitmapBytesPerRow * pixelsHigh);
 
   /* bitmapData = malloc( bitmapByteCount );
 
	memset(bitmapData, 0, bitmapByteCount);  // set memory to black, alpha 0
 
    if (bitmapData == NULL)
    {
        return NULL;
    }
*/
	colorSpace = CGColorSpaceCreateDeviceRGB();
 
	context = CGBitmapContextCreate ( NULL, // instead of bitmapData
					pixelsWide,
					pixelsHigh,
					8,      // bits per component
					bitmapBytesPerRow,
					colorSpace,
					kCGImageAlphaPremultipliedFirst);
	CGColorSpaceRelease( colorSpace );
 
    if (context== NULL)
    {
       // free (bitmapData);
        return NULL;
    }
 
    return context;
}

Note that if you would put the malloc back in then you also need to free the memory pointer after you’re done. Previously I did not know that so all this memory leaked. This approach of passing NULL instead of a pointer apparently works on 3.2 and above, officially from 4.0.

- (UIImage *)imageByDrawingCircleOnImageCG:(UIImage *)image
{
	// begin a graphics context of sufficient size
	CGContextRef ctx = newBitmapContextSuitableForSize(image.size);
 
	// need to flip the transform matrix
	// CoreGraphics has (0,0) in lower left
	CGContextScaleCTM(ctx, 1, -1);
	CGContextTranslateCTM(ctx, 0, -image.size.height);
 
	// draw original image into the context
	CGRect imageRect = CGRectMake(0, 0, image.size.width, image.size.height);
	CGContextDrawImage(ctx, imageRect, image.CGImage);
 
	// set stroking color and draw circle
	CGContextSetRGBStrokeColor(ctx, 1, 0, 0, 1);
 
	// make circle rect 5 px from border
	CGRect circleRect = CGRectMake(0, 0,
			image.size.width,
			image.size.height);
	circleRect = CGRectInset(circleRect, 5, 5);
 
	// draw circle
	CGContextStrokeEllipseInRect(ctx, circleRect);
 
	// make image out of bitmap context
	CGImageRef cgImage = CGBitmapContextCreateImage(ctx);
	UIImage *retImage = [UIImage imageWithCGImage:cgImage];
	CGImageRelease(cgImage);
 
	// free the context
	CGContextRelease(ctx);
 
	return retImage;
}

Besides of having to use this helper function to create a suitable bitmap context you should also note that we have to go via creating a CGImage first. From this we can then create a UIImage. Also you might have spotted that we need to turn the transformation matrix upside down because otherwise all our graphics would be that. CoreGraphics generally assumes the origin of drawing in the lower left hand corner.

I’ve shown you both methods of drawing on images, now you can take your pick which one you prefer.


Categories: Q&A

5 Comments »

  1. Thanks for your article on Drawing on UIImage.

    Need one favor. I am in situation where I want to draw line or area (e.g. rectangle) on UI Image at run time and save it again.

    Basically I will show image and put square / rectangle on image showing area in image and save it.How should I do that? Can you please suggest approach, sample codes etc?

  2. In order to get images to look right on retina you should use UIGraphicsBeginImageContextWithOptions(imageSize, NO, 0.0); instead of UIGraphicsBeginImageContext(imageSize); else there will be fuzzing.

  3. Hi,

    I am trying to draw a UIImage in CGContext with CGAffineTransform, the probem is when image is applied with rotation transform the actual output image from CGBitmapContextCreateImage(..) is not the same as what is shown when same transform is applied to the UIImageView in UIView. I have posted a question on Stackoverflow as well, It would be kind of you if you can have a look and provide any guidence.
    Question detail: http://stackoverflow.com/questions/17888671/uiimage-with-cgaffinetransform-drawn-in-cgcontext-strange-output

    Thanks .

  4. Thanks – excellent code snippet.
    It needs a very small change to work with Retina displays:

    // begin a graphics context of sufficient size
    float scale=[[UIScreen mainScreen] scale];
    UIGraphicsBeginImageContextWithOptions(outline.size, /*opaque*/NO, scale);

    This tells the new graphics context to use the scale of your main screen. Otherwise the images get created at half resolution and then scaled up 2x which isn’t pretty.

Trackbacks

  1. Anti Alias, UIImage and Performance | DL-UAT

Leave a Comment

%d bloggers like this: