i’m so full of ideas

iPhone: UIImage rotation and mirroring

March 30, 2009 2:19 am

Update: This post has been superseded. Please go read this instead: UIImage rotation and scaling.

My access logs show that you webbernauts like my iPhone articles better than anything else on this site. Very well then, here’s another one.

I recently found myself with a need to rotate the contents of a UIImage 90 degrees to the right or left and then display it. Apparently this is easy if your image is inside a UIImageView and you’re willing to rotate the whole view, but that’s not what I have in mind. Rotating the UIImage itself turns out to be pretty danged complicated, and requires a great deal of math. I suck at math.

Fortunately for me, the code given in this guy’s blog post tackles 95 percent of the problem, and 100 percent of the math. Heh! It’s not quite what I want, however. The function given in that blog post queries the EXIF data inside the image and rotates it based on that. I want the method’s caller to be able to specify what type of rotation should be done. I tinkered with the code for awhile and got exactly what I wanted.

The original blog post’s code was written as a standalone C function. I wrote my version as a category attached to UIImage. Say you’ve got a UIImage you want to rotate 90 degrees left. It’s done like this:

    newImage = [oldImage rotate:UIImageOrientationLeft];

newImage will be a newly-created copy of oldImage, rotated 90 degrees left. Other options include UIImageOrientationRight, UIImageOrientationDown (for a new image that’s an upside-down copy of the original), and so on. There are also “mirrored” variants, which both rotate the image and mirror its contents left-to-right.

In the process of getting it to work the way I wanted, I made a bunch of changes to the code. I reformatted it so that the lines are no longer than 78 characters. (One of these days I’m going to write a blog post about why I think that’s a good thing.) The original function had two or more unnecessary copies of some data and superfluous calls to external functions, which I eliminated. The old code trimmed the size of the original image if it was bigger than a certain size, which I eliminated. The original had several copies of an identical clause for swapping the width and height of a CGRect, which I refactored into a separate helper function. There are other changes as well, but I’ll spare you the details.

Okay, one more change that I’m going to document. I am 85 percent sure that the original code has a bug. It does not work properly when the input orientation is UIImageOrientationLeftMirrored or UIImageOrientationRightMirrored. The new image gets partially chopped off along one edge, which I fixed. It could be that it was not a bug in the context where the original code was being used, but it definitely is in this new context. I’ve tested all orientations in the iPhone simulator, and they all work.

First, here’s UKImage.h, which defines the category.

// UKImage.h -- extra UIImage methods // by allen brunson  march 29 2009 #ifndef UKIMAGE_H #define UKIMAGE_H #import <UIKit/UIKit.h> @interface UIImage (UKImage) -(UIImage*)rotate:(UIImageOrientation)orient; @end #endif  // UKIMAGE_H

Now here’s UKImage.mm, which defines the rotate: method. You can rename this file to UKImage.m if you never use any C++ constructs in your code.

// UKImage.mm -- extra UIImage methods // by allen brunson  march 29 2009 // based on original code by Kevin Lohman: // http://blog.logichigh.com/2008/06/05/uiimage-fix/ #include "UKImage.h" static CGRect swapWidthAndHeight(CGRect rect) {     CGFloat  swap = rect.size.width;         rect.size.width  = rect.size.height;     rect.size.height = swap;         return rect; } @implementation UIImage (UKImage) -(UIImage*)rotate:(UIImageOrientation)orient {     CGRect             bnds = CGRectZero;     UIImage*           copy = nil;     CGContextRef       ctxt = nil;     CGImageRef         imag = self.CGImage;     CGRect             rect = CGRectZero;     CGAffineTransform  tran = CGAffineTransformIdentity;     rect.size.width  = CGImageGetWidth(imag);     rect.size.height = CGImageGetHeight(imag);         bnds = rect;         switch (orient)     {         case UIImageOrientationUp:         // would get you an exact copy of the original         assert(false);         return nil;                 case UIImageOrientationUpMirrored:         tran = CGAffineTransformMakeTranslation(rect.size.width, 0.0);         tran = CGAffineTransformScale(tran, -1.0, 1.0);         break;         case UIImageOrientationDown:         tran = CGAffineTransformMakeTranslation(rect.size.width,          rect.size.height);         tran = CGAffineTransformRotate(tran, M_PI);         break;         case UIImageOrientationDownMirrored:         tran = CGAffineTransformMakeTranslation(0.0, rect.size.height);         tran = CGAffineTransformScale(tran, 1.0, -1.0);         break;         case UIImageOrientationLeft:         bnds = swapWidthAndHeight(bnds);         tran = CGAffineTransformMakeTranslation(0.0, rect.size.width);         tran = CGAffineTransformRotate(tran, 3.0 * M_PI / 2.0);         break;         case UIImageOrientationLeftMirrored:         bnds = swapWidthAndHeight(bnds);         tran = CGAffineTransformMakeTranslation(rect.size.height,          rect.size.width);         tran = CGAffineTransformScale(tran, -1.0, 1.0);         tran = CGAffineTransformRotate(tran, 3.0 * M_PI / 2.0);         break;         case UIImageOrientationRight:         bnds = swapWidthAndHeight(bnds);         tran = CGAffineTransformMakeTranslation(rect.size.height, 0.0);         tran = CGAffineTransformRotate(tran, M_PI / 2.0);         break;         case UIImageOrientationRightMirrored:         bnds = swapWidthAndHeight(bnds);         tran = CGAffineTransformMakeScale(-1.0, 1.0);         tran = CGAffineTransformRotate(tran, M_PI / 2.0);         break;         default:         // orientation value supplied is invalid         assert(false);         return nil;     }     UIGraphicsBeginImageContext(bnds.size);     ctxt = UIGraphicsGetCurrentContext();     switch (orient)     {         case UIImageOrientationLeft:         case UIImageOrientationLeftMirrored:         case UIImageOrientationRight:         case UIImageOrientationRightMirrored:         CGContextScaleCTM(ctxt, -1.0, 1.0);         CGContextTranslateCTM(ctxt, -rect.size.height, 0.0);         break;                 default:         CGContextScaleCTM(ctxt, 1.0, -1.0);         CGContextTranslateCTM(ctxt, 0.0, -rect.size.height);         break;     }     CGContextConcatCTM(ctxt, tran);     CGContextDrawImage(UIGraphicsGetCurrentContext(), rect, imag);         copy = UIGraphicsGetImageFromCurrentImageContext();     UIGraphicsEndImageContext();     return copy; } @end

No Responses to “iPhone: UIImage rotation and mirroring”