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;
}

59 thoughts on “UIImage fix”

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

    Thanks, Michael.

  2. There is a memory leak on this code…

    for fixing it, add those lines before the return :

    CGContextRelease(context);
    CGImageRelease(imgRef);

  3. 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…

  4. 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 !

  5. 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!

    1. 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!

  6. 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)?

      1. 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.

  7. 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?

  8. 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;
    }

  9. 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!

  10. 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.

    1. 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.

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

  12. This didn’t 100% work for me…

    Scaling was off for some reason in LeftMirrored and RightMirrored code. I ended up using something like

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

    CGContextTranslateCTM(context, 0, -width);

    }
    else {
    CGContextScaleCTM(context, scaleRatio, -scaleRatio);
    CGContextTranslateCTM(context, 0, -height);
    }

    I don’t understand 100% of the intricacies of this function, but this did fix my issue.

  13. Thanks for this!

    One small thing to add. A small bug in this code, when you’re calculating the bounds near the top of this method, you’ll need to take the floor of the result after calculating in the ratio. If you do not do this, it’ll be rounded up to the next integer and leave a white strip on the right or bottom edge. So, just change it to this to fix it:

    if (ratio > 1) {
    bounds.size.width = kMaxResolution;
    bounds.size.height = floor(bounds.size.width / ratio);
    }
    else {
    bounds.size.height = kMaxResolution;
    bounds.size.width = floor(bounds.size.height * ratio);
    }

  14. Thanks for sharing this code, i works on iOS4 but not on iOS3.1 : i have a “EXC_BAD_ACCESS” for the line :
    “CGContextDrawImage(UIGraphicsGetCurrentContext(), CGRectMake(0, 0, width, height), imgRef);”

    How can i fix it ? Please

  15. Saved me from my ignorance of image formats…THANKS for your excellent code that just WORKED first time out of the gate!

  16. This code is way over my head.
    I Really Really need to figure out how to use it.
    In my app I am attaching a saved image to an email body so I don’t have a UIImage to manipulate. I only specify a path to the image in the documents directory, pull it into NSData and attach it as a mime type.jpg. It works great but there again the image is turned left 90deg when the recipient views the email. Does anyone have a snippet of code that will help me understand how to get the saves image into UIImage so I can expose it to this function?
    Thanks for the code though. Once I grasp this my app is gonna rock big time!

  17. Hooray!! I did get this code to work but I guessed my way through it. Still don’t understand it but maybe I will. Thanks so much for the code!!

  18. Can anybody get this to work in Monotouch (C#)? I feel like I’ve translated the code correctly but when I apply the resulting UIImage to a UIImageView, the result is a blank (white) image view. Has anyone gotten this to work in Mono?

  19. I have been using this method to rotate image for long time now it crashes in iOS 6 beta, did you experience this issue as well? it seems to work when I step through the code but in normal run it crashes app every time. Any ideas here will be greatly appreciated.

  20. WHY are not working with me?….Im been struggle several day with this issue and trying other alternatives, this is the code (ios 5.0) :

    – (void)getNSDATAfromSelectedPicturesArrayURLs:(NSString*)urlString {
    ALAssetsLibraryAssetForURLResultBlock resultblock = ^(ALAsset *myasset)
    {

    ALAssetRepresentation *rep = [myasset defaultRepresentation];

    CGImageRef iref = [rep fullResolutionImage];

    UIImage *coolImage;
    UIImage *toNSData;

    if (iref) {
    coolImage = [UIImage imageWithCGImage:iref];

    }
    toNSData = [self scaleAndRotateImage:coolImage];
    NSData *dataTemp =UIImageJPEGRepresentation(toNSData, .3);
    [PicturesArrayNSDATA addObject:dataTemp];
    [self performSelectorOnMainThread:@selector(checkConversionToNSDATA) withObject:nil waitUntilDone:YES];
    }

  21. Good job!
    I’m using this code to get the image data:

    NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageSampleBuffer];
    UIImage *image = [[UIImage alloc] initWithData:imageData];
    self.stillImage = [self scaleAndRotateImage:image];

    I find it really interesting that the EXIF information is stored unused in the UIImage (as I understand):

    UIImageOrientation orient = image.imageOrientation;
    switch(orient) {

    How can I know if the reorientation is already applied to the image data or not?

  22. OK, obviously, the EXIF data (eg reorientation) should not be applied to the image data. I’m also using a non-lazy loading image method (via an UIImage category) that ignores the orientation and the other meta data…

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s