Monday, November 29, 2010

Drawing Part of a UIImage

Happy Thanksgiving, and sorry for the relative lack of posts here lately. Things are crazier than ever and it's been a challenge finding time to shower, let alone blog.

I do have something to share, today, though. No, it's not the next chapter of OpenGL ES 2.0 for iOS. It's a category that some of you may find useful: a method that allows you to draw only part of a UIImage rather than the entire thing.

On the Mac, NSImage has a handy instance method called drawInRect:fromRect:operation:fraction: that lets you specify exactly which part of an image to draw. On UIImage, we've only got the ability to draw the entire image either unless we drop down to Core Graphics calls. We don't have a nice, easy, convenient way using just UIImage to draw a portion of the image it represents.

I needed this ability in an application I'm working on, so I hacked out the following category. At first glance, this may look to be inefficient, since we're making a copy of the instance's backing CGImage in order to create the sub-image, however I believe that CGImageCreateWithImageInRect() references the original image's bitmap data. I haven't confirmed that it doesn't make a copy of the bitmap data, but the documentation certainly seems to imply it. Anyone know?

Anyway, here is the category; I've even commented the code more pedantically than is normal for me in case anyone might be confused about what's going on. Improvements and bug fixes are, as always, welcome.

@implementation UIImage(MCDrawSubImage)
- (void)drawInRect:(CGRect)drawRect fromRect:(CGRect)fromRect blendMode:(CGBlendMode)blendMode alpha:(CGFloat)alpha
{
CGImageRef drawImage = CGImageCreateWithImageInRect(self.CGImage, fromRect);
if (drawImage != NULL)
{
CGContextRef context = UIGraphicsGetCurrentContext();

// Push current graphics state so we can restore later
CGContextSaveGState(context);

// Set the alpha and blend based on passed in settings
CGContextSetBlendMode(context, blendMode);
CGContextSetAlpha(context, alpha);

// Take care of Y-axis inversion problem by translating the context on the y axis
CGContextTranslateCTM(context, 0, drawRect.origin.y + fromRect.size.height);

// Scaling -1.0 on y-axis to flip
CGContextScaleCTM(context, 1.0, -1.0);

// Then accommodate the translate by adjusting the draw rect
drawRect.origin.y = 0.0f;

// Draw the image
CGContextDrawImage(context, drawRect, drawImage);

// Clean up memory and restore previous state
CGImageRelease(drawImage);

// Restore previous graphics state to what it was before we tweaked it
CGContextRestoreGState(context);
}

}

@end





10 comments:

Don said...

I just had goosebumps.. I was just looking for a solution to this problem and up popped your post in my reader. Love it when this happens. :)

anonymouse said...

>I haven't confirmed that it doesn't make a copy of the
>bitmap data, but the documentation certainly seems to
>imply it.

I don't but surely running your app under alloc tool is the best way to find out.

Melvin said...

I have used it and it is really very interesting application category. I really enjoyed drawing my picture with UIImange. I really liked it.

iphone app development

Chris said...

thanks for this. im not very good with using CG layer so this comes in handy. Its a shame that this method hasnt been ported over from NSImage. BTW, I was just about to use some locked-down scroll views to achieve the same result--do you think that is the way Apple would recommend solving this?

fericia said...

According to this link(http://lists.apple.com/archives/quartz-dev/2009/May/msg00054.html), CGImageCreateWithImageInRect() is referencing the original data provider (mostly compressed data). The performance may be poor because of decompressing process.

Kaushal said...

hey hi
i have a compaq laptop with core2duo processor i want to install mac os and sdk application for developing iphone games.
can i use this in mah lappy./?
reply me @ skill.gyl@gmail.com

Taber said...
This comment has been removed by the author.
hollance said...

In the Y-axis correction bit, you might want to use drawRect.size.height instead of fromRect. Otherwise scaling the image (where the drawRect has different dimensions than the fromRect) won't work properly.

vignesh.cse said...

Hi jeff,
I have been reading your blog for few months. Its is really wonderful to read your posts. I have to draw a part of an image, but the part is not a rectangle.How do i go about it?. Should i use CGPath and ContextClip?. Or any other easy way to do it?.

Gustavo said...

Thanks for the article Jeff. I just used some of this code for one of my projects. I describe what I did in a post here: http://blog.codecropper.com/2011/04/ios-color-picker-the-easy-way/