Heh, it seems nobody felt like chiming in on this one. Turns out the answer is straightforward. Images captured via the stillImageOutput captureStillImageAsynchronouslyFromConnection:...
method always end up with the following properties:
- UIImage orientation = always UIImageOrientationRight regardless of device orientation
- UIImage size = W x H (e.g. portrait width x portrait height, depends on your camera resolution)
- CGImage size = depends on device orientation (e.g. portrait or landscape)
So the solution to rotate the image up is to use the device orientation in conjunction with the CGImage size to apply an appropriate affine transform. As I'm answering my own question, I'm not the solution in code but I ended up writing a routine called:
- (UIImage *)imageRotatedUpForDeviceOrientation:(UIDeviceOrientation)deviceOrientation
in a UIImage category containing various image processing enhancements.
EDIT - Implementation Example
I've received a number of requests for functional code on this. I've extracted the relevant implementation from a working app.
// this method is implemented in your capture session manager (wherever AVCaptureSession is used)
// capture a still image and save the device orientation
- (void)captureStillImage
{
UIDeviceOrientation currentDeviceOrientation = UIDevice.currentDevice.orientation;
[self.stillImageOutput
captureStillImageAsynchronouslyFromConnection:self.videoConnection
completionHandler:^(CMSampleBufferRef imageSampleBuffer, NSError *error) {
NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageSampleBuffer];
if (imageData) {
UIImage *image = [UIImage imageWithData:imageData];
NSDictionary *captureInfo = {
@"image" : image,
@"deviceOrientation" : @(currentDeviceOrientation)
};
// TODO: send image & orientation to delegate or post notification to observers
}
else {
// TODO: handle image capture error
}
}];
}
// this method rotates the UIImage captured by the capture session manager based on the
// device orientation when the image was captured
- (UIImage *)imageRotatedUpFromCaptureInfo:(NSDictionary *)captureInfo
{
UIImage *image = [captureInfo objectForKey:@"image"];
UIDeviceOrientation deviceOrientation = [[captureInfo objectForKey:@"deviceOrientation"] integerValue];
UIImageOrientation rotationOrientation = [self rotationNeededForImageCapturedWithDeviceOrientation:deviceOrientation];
// TODO: scale the image if desired
CGSize newSize = image.size;
return [imageScaledToSize:newSize andRotatedByOrientation:rotationOrientation];
}
// return a scaled and rotated an image
- (UIImage *)imageScaledToSize:(CGSize)newSize andRotatedByOrientation:(UIImageOrientation)orientation
{
CGImageRef imageRef = self.CGImage;
CGRect imageRect = CGRectMake(0.0, 0.0, newSize.width, newSize.height);
CGRect contextRect = imageRect;
CGAffineTransform transform = CGAffineTransformIdentity;
switch (orientation)
{
case UIImageOrientationDown: { // rotate 180 deg
transform = CGAffineTransformTranslate(transform, imageRect.size.width, imageRect.size.height);
transform = CGAffineTransformRotate(transform, M_PI);
} break;
case UIImageOrientationLeft: { // rotate 90 deg left
contextRect = CGRectTranspose(contextRect);
transform = CGAffineTransformTranslate(transform, imageRect.size.height, 0.0);
transform = CGAffineTransformRotate(transform, M_PI / 2.0);
} break;
case UIImageOrientationRight: { // rotate 90 deg right
contextRect = CGRectTranspose(contextRect);
transform = CGAffineTransformTranslate(transform, 0.0, imageRect.size.width);
transform = CGAffineTransformRotate(transform, 3.0 * M_PI / 2.0);
} break;
case UIImageOrientationUp: // no rotation
default:
break;
}
CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef);
CGColorSpaceRef colorSpaceRef = CGImageGetColorSpace(imageRef);
// madify bitmapInfo to work with PNG if necessary
if (bitmapInfo == kCGImageAlphaNone) {
bitmapInfo = kCGImageAlphaNoneSkipLast;
}
else if (bitmapInfo == kCGImageAlphaLast) {
bitmapInfo = kCGImageAlphaPremultipliedLast;
}
// Build a context that's the same dimensions as the new size
CGContextRef context = CGBitmapContextCreate(NULL,
contextRect.size.width,
contextRect.size.height,
CGImageGetBitsPerComponent(imageRef),
0,
colorSpaceRef,
bitmapInfo);
CGContextConcatCTM(context, transform);
CGContextDrawImage(context, imageRect, imageRef);
// Get the rotated image from the context and a UIImage
CGImageRef rotatedImageRef = CGBitmapContextCreateImage(context);
UIImage *rotatedImage = [UIImage imageWithCGImage:rotatedImageRef];
// Clean up
CGImageRelease(rotatedImageRef);
CGContextRelease(context);
return rotatedImage;
}
// return the UIImageOrientation needed for an image captured with a specific deviceOrientation
- (UIImageOrientation)rotationNeededForImageCapturedWithDeviceOrientation:(UIDeviceOrientation)deviceOrientation
{
UIImageOrientation rotationOrientation;
switch (deviceOrientation) {
case UIDeviceOrientationPortraitUpsideDown: {
rotationOrientation = UIImageOrientationLeft;
} break;
case UIDeviceOrientationLandscapeRight: {
rotationOrientation = UIImageOrientationDown;
} break;
case UIDeviceOrientationLandscapeLeft: {
rotationOrientation = UIImageOrientationUp;
} break;
case UIDeviceOrientationPortrait:
default: {
rotationOrientation = UIImageOrientationRight;
} break;
}
return rotationOrientation;
}
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…