UIImage fix

If you aren’t interested in Objective C Cocoa development for the iPhone… read on.

The problems:

  • UIImage provides no easy way to scale, rotate or transform the underlying CGImage.  While it is easy to transform it by placing the image in a view, if you need the actual image changed then good luck.
  • UIImageJPEGRepresentation(), the easiest (only?) way to convert a UIImage to a JPEG uses the underlying CGImage, and ignores your UIImage imageOrientation, so regardless of camera position when the picture was taken the exported JPEG will always be oriented in landscape or right mode, ending up with pictures that need rotation.

The solution:  Thanks to some help in the Apple Support forums, and the #iphonedev IRC chat groups, I was able to clean up a function that takes a UIImage, and fixes it’s underlying core image.

Update: The “Forever” setting on Pastebin doesn’t seem to be so forever… but still lots of interest in this fix, so enjoy below:

UIImage *scaleAndRotateImage(UIImage *image)
{
    int kMaxResolution = 320; // Or whatever

    CGImageRef imgRef = image.CGImage;

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

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

    CGFloat scaleRatio = bounds.size.width / width;
    CGSize imageSize = CGSizeMake(CGImageGetWidth(imgRef), CGImageGetHeight(imgRef));
    CGFloat boundHeight;
    UIImageOrientation orient = image.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;
}
  • Share/Bookmark

32 Comments

  • By Michael, July 23, 2008 @ 3:59 am

    Any chance you can re-link the solution’s source…it’s no longer available on pastbin.

    Thanks, Michael.

  • By Mihael, December 19, 2008 @ 7:42 am

    Thanks.

  • By Emilian, January 15, 2009 @ 1:44 am

    Thank you. Just saved a lot of searching and testing. I assume we can use the code a “public domain” no ?

  • By badpirate, January 15, 2009 @ 10:58 am

    Public domain indeed.

  • By Layne, February 6, 2009 @ 3:16 pm

    Thanks SOOO much for this. Helped me out greatly. You rock!

  • By TheSquad, February 10, 2009 @ 8:00 am

    There is a memory leak on this code…

    for fixing it, add those lines before the return :

    CGContextRelease(context);
    CGImageRelease(imgRef);

  • By TheSquad, February 10, 2009 @ 8:09 am

    In fact Fotget about the first line :

    CGContextRelease(context);

    Just use :

    CGImageRelease(imgRef);

    And it will be fine

  • By Kevin, February 10, 2009 @ 10:14 am

    CGImageRelease(imgRef); will cause a crash. imgRef is not allocated but assigned (CGImageRef imgRef = image.CGImage;) therefore it’s not scale and rotates job to deallocate that ref.

    Correct me if I’m wrong…

  • By TheSquad, February 12, 2009 @ 4:27 am

    Yes my bad !

    that was my code :

    UIImage *img = [[UIImage alloc] initWithData:data];
    [data release];
    if ((img.size.width / img.size.height) > 1)
    img = [[self scaleAndRotateImage:img] retain];

    since the old img couldn’t not be released, I was stuck with extra memory allocated, that’s why I had to release the Ref.

    That you for the correction Kevin, I was wrong !

  • By iPhoner, February 12, 2009 @ 2:06 pm

    I’m simply trying to rotate an image to landscape. I’m having troubles picking apart your code, on why you are doing 3*M_PI/2 in certain places, whats the 3* for? Basically if the image is a portrait image, i want to rotate it 90 degrees CCW. Any help is appreciated!

  • By kidproquo, March 23, 2009 @ 12:09 pm

    This code works wonders with camera-sourced images! Thanks for your work. One question – the orientation is messed up for camera-sourced images and is fine for other type images (screenshots, saved images from other apps). So, for camera-sourced images I need to call your method and for other I need to call another. How do I distinguish between the two (camera-sourced/others)?

  • By steve, April 19, 2009 @ 9:27 am

    I’ve got the same issue as kidproquo. Is there any easy way to know it was taken with the iPhone or do I need to parse the exif?

  • By Ken, April 25, 2009 @ 5:45 am

    Hi Guys,

    Please tell me how I can download the entire solution’s source.

    Thanks,

  • By //\\//\\ e [+_+], April 25, 2009 @ 5:03 pm

    good job kevin.

  • By Chris, May 6, 2009 @ 9:24 pm

    Many thanks for this! You just saved me a lot of frustration.

  • By Andy, May 9, 2009 @ 12:09 am

    I think there is an easier solution using the drawAtPoint function. The API documentation says: “This method draws the entire image in the current graphics context, respecting the image’s orientation setting.”

    I did a small test and it seems to work just fine:

    static uint8* GetPixelBufferFromImage(UIImage* image)
    {
    int32 width = image.size.width;
    int32 height = image.size.height;

    uint8* pBuffer = (uint8*)malloc(height*width*4);

    CGColorSpaceRef pColorSpace = CGColorSpaceCreateDeviceRGB();

    CGContextRef pContext = CGBitmapContextCreate(pBuffer,
    width, height,
    8, width*4,
    pColorSpace, kCGImageAlphaNoneSkipLast);

    // Must adjust to upside-down coordinate system
    CGContextTranslateCTM(pContext, 0.0, height);
    CGContextScaleCTM(pContext, 1.0, -1.0);

    UIGraphicsPushContext(pContext);
    [image drawAtPoint:CGPointMake(0,0)];
    UIGraphicsPopContext();

    CGContextRelease(pContext);
    CGColorSpaceRelease(pColorSpace);

    return pBuffer;
    }

  • By Ed, May 28, 2009 @ 11:15 pm

    Holy cow!!! This code rocks! You are the man.

    Its amazing that it takes this much code to perform a simple basic task like take an image from the photo library and display it properly! Apple still has some work to do.

    Anyway, you just saved me like 5 hours of hair pulling. Thanks so much!

  • By raleighr3, June 20, 2009 @ 2:59 pm

    So can anyone explain the magic behind the rotation math (e.g. 3*M_PI/2)? What’s the 3* for? I would like to rotate by arbitrary angles and am trying to understand this code so I can do that. Thanks!

  • By Mike, June 24, 2009 @ 10:19 am

    Where, exactly, can this code be called from? I’ve tried it in a few different places such as the from the imagePickerController’s didFinishPickingMediaWithInfo and the context is always null:

    CGContextRef context = UIGraphicsGetCurrentContext();

    I’ve tried using the code within my viewcontroller as well as a static method in a helper class. Always the same null context.

  • By Kevin Lohman, June 24, 2009 @ 2:00 pm

    the resulting image is retrieved using a command like:

    UIImage *myRotateImage = scaleAndRotateImage(originalImage);

    If your question was a broader one about accessing an image context, you have to first create one before you can call UIGraphicsGetCurrentContext(), notice the:

    UIGraphicsBeginImageContext(bounds.size);

    Command, this begins the context.

  • By jongsma, June 28, 2009 @ 11:39 am

    Do note that dividing M_PI by 2, what you are doing quite often, it not necessary. The variable M_PI_2 represents that value.

  • By zoso, July 5, 2009 @ 12:04 am

    Images taken with the camera are all 1200×1600 so do something like if (imgDimension == 1600){call method}

  • By badpirate, July 20, 2009 @ 5:58 pm

    Not quite that simple, because you can hold the phone any of 4 ways. So if I take a shot with the phone tilted left it would have the same dimensions as a photo with the camera tilted right, but would be 180 degrees out.

  • By AC, March 11, 2010 @ 10:58 am

    Thank you for sharing this code.

  • By RK Tweet, March 15, 2010 @ 9:37 pm

    I get a huge spike in memory usage (~10-12 MB) when I access this code. Anyone else? Anyway around it?

  • By Manoj, April 8, 2010 @ 6:40 pm

    Thanks, this is what i was looking for.

  • By sbudhram, June 24, 2010 @ 10:42 pm

    I literally cut and paste this code, and it fixed my problem. That never happens. Thanks for your generous contribution.

  • By Danny, July 15, 2010 @ 8:55 pm

    Amazing Work. Thank you.

Other Links to this Post

  1. Snippet: UIImage alterations at Under The Bridge — January 4, 2009 @ 6:06 pm

  2. rotate a UIImage help - iPhone Dev SDK Forum — February 3, 2009 @ 12:50 pm

  3. i’m so full of ideas » Blog Archive » iPhone: UIImage rotation and mirroring — March 29, 2009 @ 10:19 pm

  4. Mmm…food » Scaling UIImages without losing orientation — May 11, 2009 @ 8:15 pm

RSS feed for comments on this post. TrackBack URI

Leave a comment

WordPress Themes