Scaling UIImages without losing orientation

May 11, 2009
 

Most of my meal pictures, I’ve noticed, are horizontal.  Food doesn’t tower vertically, it spreads across the plate.  That’s I never got to the picture orientation bug that’s been plaguing the iPhone application until recently.  I’ve fixed it now with the help of some cool code from the blog Logic High, and want to share that here as my first technical post.  If you’re building an iPhone app and have to scale images, it’s useful to know — most of the code out there doesn’t seem to address image orientation.

The problem in short: when you go to submit applications over 3G or (shudder) EDGE, the app automatically shrinks the image to a more transmittable size (800×600, specifically).  Previously, I just drew the image in a smaller box using drawInRect and grabbed the new image data using UIGraphicsGetImageFromCurrentImageContext.  That shrank the image fine, but lost the orientation data — every image ended up horizontal.  (This didn’t affect images sent over wifi or uploaded through the website.)  Here’s an example of why that’s no good:

Improperly Horizontalized Pictures

Improperly Horizontalized Pictures

The solution: an awesome method from Kevin Lohman’s Logic High blog.  It digs much deeper into the underlying OS X image handling mechanisms to generate a properly-oriented image.  In short, his code gets the image’s orientation, applies an appropriate transformation — rotation, mirroring, etc. — as well as resizing it.  Voila!  New image with pixels arranged appropriately.  Instead of an image and an orientation value, you have a properly-oriented image that mealstrom.com (or your service) will handle properly.

I made slight changes to Kevin’s code to allow it to take and use a new size (the original code changed the image to a constant square size).  I implemented it as an extension to the UIImage class (e.g. UIImage *newImage = [image scaledCopyOfSize:CGSizeMake(800, 600)]).  I’m going to test this and a few other changes for a few more days, then submit Mealstrom 1.3 to make it available to all iPhone users.

Thanks to Kevin Lohman again for the code, and to all of you who might be reading, happy eating and goodnight!

Alex

- (UIImage *)scaledCopyOfSize:(CGSize)newSize {
    CGImageRef imgRef = self.CGImage;

    CGFloat width = CGImageGetWidth(imgRef);
    CGFloat height = CGImageGetHeight(imgRef);

    CGAffineTransform transform = CGAffineTransformIdentity;
    CGRect bounds = CGRectMake(0, 0, width, height);
    if (width > newSize.width || height > newSize.height) {
        CGFloat ratio = width/height;
        if (ratio > 1) {
            bounds.size.width = newSize.width;
            bounds.size.height = bounds.size.width / ratio;
        }
        else {
            bounds.size.height = newSize.height;
            bounds.size.width = bounds.size.height * ratio;
        }
    }

    CGFloat scaleRatio = bounds.size.width / width;
    CGSize imageSize = CGSizeMake(CGImageGetWidth(imgRef), CGImageGetHeight(imgRef));
    CGFloat boundHeight;
    UIImageOrientation orient = self.imageOrientation;
    switch(orient) {

        case UIImageOrientationUp: //EXIF = 1
            transform = CGAffineTransformIdentity;
            break;

        case UIImageOrientationUpMirrored: //EXIF = 2
            transform = CGAffineTransformMakeTranslation(imageSize.width, 0.0);
            transform = CGAffineTransformScale(transform, -1.0, 1.0);
            break;

        case UIImageOrientationDown: //EXIF = 3
            transform = CGAffineTransformMakeTranslation(imageSize.width, imageSize.height);
            transform = CGAffineTransformRotate(transform, M_PI);
            break;

        case UIImageOrientationDownMirrored: //EXIF = 4
            transform = CGAffineTransformMakeTranslation(0.0, imageSize.height);
            transform = CGAffineTransformScale(transform, 1.0, -1.0);
            break;

        case UIImageOrientationLeftMirrored: //EXIF = 5
            boundHeight = bounds.size.height;
            bounds.size.height = bounds.size.width;
            bounds.size.width = boundHeight;
            transform = CGAffineTransformMakeTranslation(imageSize.height, imageSize.width);
            transform = CGAffineTransformScale(transform, -1.0, 1.0);
            transform = CGAffineTransformRotate(transform, 3.0 * M_PI / 2.0);
            break;

        case UIImageOrientationLeft: //EXIF = 6
            boundHeight = bounds.size.height;
            bounds.size.height = bounds.size.width;
            bounds.size.width = boundHeight;
            transform = CGAffineTransformMakeTranslation(0.0, imageSize.width);
            transform = CGAffineTransformRotate(transform, 3.0 * M_PI / 2.0);
            break;

        case UIImageOrientationRightMirrored: //EXIF = 7
            boundHeight = bounds.size.height;
            bounds.size.height = bounds.size.width;
            bounds.size.width = boundHeight;
            transform = CGAffineTransformMakeScale(-1.0, 1.0);
            transform = CGAffineTransformRotate(transform, M_PI / 2.0);
            break;

        case UIImageOrientationRight: //EXIF = 8
            boundHeight = bounds.size.height;
            bounds.size.height = bounds.size.width;
            bounds.size.width = boundHeight;
            transform = CGAffineTransformMakeTranslation(imageSize.height, 0.0);
            transform = CGAffineTransformRotate(transform, M_PI / 2.0);
            break;

        default:
            [NSException raise:NSInternalInconsistencyException format:@"Invalid image orientation"];

    }

    UIGraphicsBeginImageContext(bounds.size);

    CGContextRef context = UIGraphicsGetCurrentContext();

    if (orient == UIImageOrientationRight || orient == UIImageOrientationLeft) {
        CGContextScaleCTM(context, -scaleRatio, scaleRatio);
        CGContextTranslateCTM(context, -height, 0);
    }
    else {
        CGContextScaleCTM(context, scaleRatio, -scaleRatio);
        CGContextTranslateCTM(context, 0, -height);
    }

    CGContextConcatCTM(context, transform);

    CGContextDrawImage(UIGraphicsGetCurrentContext(), CGRectMake(0, 0, width, height), imgRef);
    UIImage *imageCopy = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    return imageCopy;
}

6 Responses to “Scaling UIImages without losing orientation”

  1. [...] Scaling UIImages without losing orientation [...]

  2. This is a great little function and I use it all the time! A quick note however, it contains a slight little bug. Depending on the height of the input image, you may end up with a slight 1px white line on top of the image. This is due to rounding and the fix would be a simple floor():

    bounds.size.height = floor(bounds.size.width / ratio);

  3. I have been searching for sites related to this. Glad I found you. Thanks

  4. [...] Read this entry on blog.twoalex.com Categories: iPhone, tech Tags: Comments (2) Trackbacks (1) Leave a comment Trackback [...]

  5. Note that to get correct behaviour on Retina displays (i.e. you want high resolution images) you need to use this code when you begin the context:

    if (UIGraphicsBeginImageContextWithOptions) {
    UIGraphicsBeginImageContextWithOptions(bounds.size, NO,
    /* 0.0f will scale to 1.0/2.0 depending on if the
    device has a high-resolution screen */
    0.0f);
    } else {
    UIGraphicsBeginImageContext(bounds.size);
    }

  6. awesome, it is really helpful.!

Leave a Reply