views:

535

answers:

2

When you take photos witha UIImagePickerController on the iPhone, I and many internet denizens have found you need to run the images through this function to orient them properly (and scale them while you're at it).

There is a giant thread about it here: http://discussions.apple.com/message.jspa?messageID=7276709

Unfortunately, this function can take seconds to run and it's really a nuisance. If you want to use this and let users take pictures fast, you even need to add an NSLock or you will get some crashes.

How can I speed up or eliminate this code?

// orient photos and scale them to kMaxResolution 
+ (UIImage*)scaleAndRotateImage:(UIImage *)image {

  // we upload photos at a maximum resolution of 2048 x 1536
    int kMaxResolution = 2048; 

    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;
}
+10  A: 

Do you really need to do this operation instantly? The screen resolution of the iPhone is 320x400 - scale the image down to at least this, then apply the transformations (at least for displaying, then later if the user wants to do something with the image, you run the more costly version in the background). Take a look at that first line:

// we upload photos at a maximum resolution of 2048 x 1536
int kMaxResolution = 2048; 

For most applications, you're rotating this gigantic image, then proceeding to throw away most of that calculated data when you shrink it way down. Throw away the data first by scaling (afaik, a far cheaper operation), then rotate.

Paul Betts
This would be my answer. A lot of speed issues are in fact user perception issues. For example, people are sensitive to speed when taking a picture but they're conditioned to wait for uploads over IP. You could store the images quickly in the default format when they're taken and then only process them when they need to be moved/uploaded.
TechZen
A: 

I wonder why you think that you need to scale and rotate an image that comes from the UIImagePicker. I have written a couple of different apps that use the camera, and I've never needed to do this. If you explain in more detail what you're trying to do, maybe we can give you more specific suggestions.

Mark Bessey
You need to do this when you aren't saving to the Camera Roll. Are you able to upload a photo you take with the UIImagePickerController to Flickr or Picasa without them being oriented wrong?
Andrew Johnson
I haven't tried uploading photos directly to Flickr from my app, but if Flickr doesn't understand the orientation flag, how do they do the right thing with camera uploads? Very odd.
Mark Bessey