iOS UIImagePickerController上传后的结果图像方向问题

349

我正在iOS 3.1.3的iPhone上测试我的iPhone应用程序。 我使用UIImagePickerController选择/捕获图像:

UIImagePickerController *imagePicker = [[UIImagePickerController alloc] init];
[imagePicker setSourceType:UIImagePickerControllerSourceTypeCamera];
[imagePicker setDelegate:self];
[self.navigationController presentModalViewController:imagePicker animated:YES];
[imagePicker release];



- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
    self.image = [info objectForKey:UIImagePickerControllerOriginalImage];
    imageView.image = self.image;
    [self.navigationController dismissModalViewControllerAnimated:YES];
    submitButton.enabled = YES;
}

然后我使用ASI类将其发送到了我的Web服务器:

ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:[NSURL URLWithString:@"http://example.com/myscript.php"]];
[request setDelegate:self];
[request setStringEncoding:NSUTF8StringEncoding];
[request setShouldContinueWhenAppEntersBackground:YES];
//other post keys/values
[request setFile:UIImageJPEGRepresentation(self.image, 100.0f) withFileName:[NSString stringWithFormat:@"%d.jpg", [[NSDate date] timeIntervalSinceNow]] andContentType:@"image/jpg" forKey:@"imageFile"];
[request startAsynchronous];

问题: 当我用iPhone横向拍照时,上传的图片在服务器上查看时如预期般显示。但是在竖向拍照时上传的图像会被旋转90度后查看。

我的应用程序仅在纵向模式(倒置和常规)下工作。

如何在上传后始终显示图像的正确方向?

UIImageView中显示的图像(在拍摄后直接显示)似乎是正确的,但在服务器上查看时则不然。

20个回答

515

UIImage有一个属性imageOrientation,用于指示UIImageView和其他UIImage使用者旋转原始图像数据。很可能这个标志保存在上传的JPEG图像的EXIF数据中,但是您所使用的程序并没有尊重该标志。

为了使上传的UIImage正确显示,您可以使用如下的类别:

UIImage+fixOrientation.h

@interface UIImage (fixOrientation)

- (UIImage *)fixOrientation;

@end

UIImage+fixOrientation.m

:这是一个文件名,可能是一个用于处理图片方向的 Objective-C 类或扩展。该文件中的代码可以帮助解决在 iOS 应用程序中处理图片时可能出现的方向问题。
@implementation UIImage (fixOrientation)

- (UIImage *)fixOrientation {

    // No-op if the orientation is already correct
    if (self.imageOrientation == UIImageOrientationUp) return self;

    // We need to calculate the proper transformation to make the image upright.
    // We do it in 2 steps: Rotate if Left/Right/Down, and then flip if Mirrored.
    CGAffineTransform transform = CGAffineTransformIdentity;

    switch (self.imageOrientation) {
        case UIImageOrientationDown:
        case UIImageOrientationDownMirrored:
            transform = CGAffineTransformTranslate(transform, self.size.width, self.size.height);
            transform = CGAffineTransformRotate(transform, M_PI);
            break;

        case UIImageOrientationLeft:
        case UIImageOrientationLeftMirrored:
            transform = CGAffineTransformTranslate(transform, self.size.width, 0);
            transform = CGAffineTransformRotate(transform, M_PI_2);
            break;

        case UIImageOrientationRight:
        case UIImageOrientationRightMirrored:
            transform = CGAffineTransformTranslate(transform, 0, self.size.height);
            transform = CGAffineTransformRotate(transform, -M_PI_2);
            break;
        case UIImageOrientationUp:
        case UIImageOrientationUpMirrored:
            break;
    }

    switch (self.imageOrientation) {
        case UIImageOrientationUpMirrored:
        case UIImageOrientationDownMirrored:
            transform = CGAffineTransformTranslate(transform, self.size.width, 0);
            transform = CGAffineTransformScale(transform, -1, 1);
            break;

        case UIImageOrientationLeftMirrored:
        case UIImageOrientationRightMirrored:
            transform = CGAffineTransformTranslate(transform, self.size.height, 0);
            transform = CGAffineTransformScale(transform, -1, 1);
            break;
        case UIImageOrientationUp:
        case UIImageOrientationDown:
        case UIImageOrientationLeft:
        case UIImageOrientationRight:
            break;
    }

    // Now we draw the underlying CGImage into a new context, applying the transform
    // calculated above.
    CGContextRef ctx = CGBitmapContextCreate(NULL, self.size.width, self.size.height,
                                             CGImageGetBitsPerComponent(self.CGImage), 0,
                                             CGImageGetColorSpace(self.CGImage),
                                             CGImageGetBitmapInfo(self.CGImage));
    CGContextConcatCTM(ctx, transform);
    switch (self.imageOrientation) {
        case UIImageOrientationLeft:
        case UIImageOrientationLeftMirrored:
        case UIImageOrientationRight:
        case UIImageOrientationRightMirrored:
            // Grr...
            CGContextDrawImage(ctx, CGRectMake(0,0,self.size.height,self.size.width), self.CGImage);
            break;

        default:
            CGContextDrawImage(ctx, CGRectMake(0,0,self.size.width,self.size.height), self.CGImage);
            break;
    }

    // And now we just create a new UIImage from the drawing context
    CGImageRef cgimg = CGBitmapContextCreateImage(ctx);
    UIImage *img = [UIImage imageWithCGImage:cgimg];
    CGContextRelease(ctx);
    CGImageRelease(cgimg);
    return img;
}

@end

9
谢谢,非常完美。以前从未使用过分类或了解它们,直到现在! - james
3
我尝试了相同的方法,效果很好,但旋转3264*2448分辨率的图片需要大约2-3秒。因此,请建议改进版本。 - Laxmi Lal Menaria
3
我会尽力进行翻译。以下是需要翻译的内容:I consider https://dev59.com/PGox5IYBdhLWcg3w44Lb a better answer. - pojomx
2
这个问题还有另一个解决方案在这里(与此相似),其中包括对实际情况的详细讨论。 - Gallymon
1
这种方法在处理5MB的图像时会消耗50MB的内存,导致偶尔出现与内存相关的崩溃。我强烈建议选择@an0的方法。 - Yerk
显示剩余20条评论

243

我找到了一个简单得多的方法:

- (UIImage *)normalizedImage {
    if (self.imageOrientation == UIImageOrientationUp) return self; 

    UIGraphicsBeginImageContextWithOptions(self.size, NO, self.scale);
    [self drawInRect:(CGRect){0, 0, self.size}];
    UIImage *normalizedImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return normalizedImage;
}

顺便说一下:@Anomie的代码没有考虑到scale,因此不适用于2x图像。


2
@Odelya 将此方法添加到您的UIImage类别中,然后调用[image normalizedImage]。或者直接使用该方法的代码,将self替换为您的UIImage对象。 - an0
1
@an0 这个方法大约需要一秒钟的时间,消耗了很多时间。有没有办法可以改进它? - Dejell
这似乎是最简单的解决方案,而且在iOS 6上对我有效。运行速度也相当快。 - Pat McG
1
谢谢你。从我的测试结果来看,这种方法似乎比选择的答案快一些,大约在我的iphone4上快20%左右。 - chrisben
7
这个问题还有另一种解决方法在这里,并附有详细的解释。 - Gallymon
显示剩余5条评论

129

这里是@an0的Swift版本答案:

func normalizedImage() -> UIImage {

  if (self.imageOrientation == UIImageOrientation.Up) { 
      return self;
  }

  UIGraphicsBeginImageContextWithOptions(self.size, false, self.scale);
  let rect = CGRect(x: 0, y: 0, width: self.size.width, height: self.size.height)
  self.drawInRect(rect)

  let normalizedImage : UIImage = UIGraphicsGetImageFromCurrentImageContext()
  UIGraphicsEndImageContext();
  return normalizedImage;
}

同时在一个更普遍的功能中:

func fixOrientation(img:UIImage) -> UIImage {

  if (img.imageOrientation == UIImageOrientation.Up) { 
      return img;
  }

  UIGraphicsBeginImageContextWithOptions(img.size, false, img.scale);
  let rect = CGRect(x: 0, y: 0, width: img.size.width, height: img.size.height)
  img.drawInRect(rect)

  let normalizedImage : UIImage = UIGraphicsGetImageFromCurrentImageContext()
  UIGraphicsEndImageContext();
  return normalizedImage;

}

Swift 3 版本:

func fixOrientation(img: UIImage) -> UIImage {
    if (img.imageOrientation == .up) {
        return img
    }

    UIGraphicsBeginImageContextWithOptions(img.size, false, img.scale)
    let rect = CGRect(x: 0, y: 0, width: img.size.width, height: img.size.height)
    img.draw(in: rect)

    let normalizedImage = UIGraphicsGetImageFromCurrentImageContext()!
    UIGraphicsEndImageContext()

    return normalizedImage
}

10
很少需要滚动到底部才能找到最佳答案。点赞! - sudo make install
我应该在哪里使用fixOrientation?我有一个saveImage函数,我应该在那里使用它还是在我的imageHolder.image上使用? - alex
如果你想使用上面更通用的函数,那么你可以在你的saveImage函数中使用它。只需将你的UIImage对象传递给fixOrientation函数,它将返回一个正确方向的UIImage版本,该版本是你传递给它的UIImage的正确方向版本。 - prajna
1
感谢提供 Swift 3 版本。 - Shadros

44

解决Swift 3.1相机拍照时方向问题的方案。

我已更新Jake和Metal Heart所提供的解决方案。

UIImage扩展

//MARK:- Image Orientation fix

extension UIImage {

    func fixOrientation() -> UIImage {

        // No-op if the orientation is already correct
        if ( self.imageOrientation == UIImageOrientation.up ) {
            return self;
        }

        // We need to calculate the proper transformation to make the image upright.
        // We do it in 2 steps: Rotate if Left/Right/Down, and then flip if Mirrored.
        var transform: CGAffineTransform = CGAffineTransform.identity

        if ( self.imageOrientation == UIImageOrientation.down || self.imageOrientation == UIImageOrientation.downMirrored ) {
            transform = transform.translatedBy(x: self.size.width, y: self.size.height)
            transform = transform.rotated(by: CGFloat(Double.pi))
        }

        if ( self.imageOrientation == UIImageOrientation.left || self.imageOrientation == UIImageOrientation.leftMirrored ) {
            transform = transform.translatedBy(x: self.size.width, y: 0)
            transform = transform.rotated(by: CGFloat(Double.pi / 2.0))
        }

        if ( self.imageOrientation == UIImageOrientation.right || self.imageOrientation == UIImageOrientation.rightMirrored ) {
            transform = transform.translatedBy(x: 0, y: self.size.height);
            transform = transform.rotated(by: CGFloat(-Double.pi / 2.0));
        }

        if ( self.imageOrientation == UIImageOrientation.upMirrored || self.imageOrientation == UIImageOrientation.downMirrored ) {
            transform = transform.translatedBy(x: self.size.width, y: 0)
            transform = transform.scaledBy(x: -1, y: 1)
        }

        if ( self.imageOrientation == UIImageOrientation.leftMirrored || self.imageOrientation == UIImageOrientation.rightMirrored ) {
            transform = transform.translatedBy(x: self.size.height, y: 0);
            transform = transform.scaledBy(x: -1, y: 1);
        }

        // Now we draw the underlying CGImage into a new context, applying the transform
        // calculated above.
        let ctx: CGContext = CGContext(data: nil, width: Int(self.size.width), height: Int(self.size.height),
                                                      bitsPerComponent: self.cgImage!.bitsPerComponent, bytesPerRow: 0,
                                                      space: self.cgImage!.colorSpace!,
                                                      bitmapInfo: self.cgImage!.bitmapInfo.rawValue)!;

        ctx.concatenate(transform)

        if ( self.imageOrientation == UIImageOrientation.left ||
            self.imageOrientation == UIImageOrientation.leftMirrored ||
            self.imageOrientation == UIImageOrientation.right ||
            self.imageOrientation == UIImageOrientation.rightMirrored ) {
            ctx.draw(self.cgImage!, in: CGRect(x: 0,y: 0,width: self.size.height,height: self.size.width))
        } else {
            ctx.draw(self.cgImage!, in: CGRect(x: 0,y: 0,width: self.size.width,height: self.size.height))
        }

        // And now we just create a new UIImage from the drawing context and return it
        return UIImage(cgImage: ctx.makeImage()!)
    }
}

Swift 2.0

//MARK:- Image Orientation fix

extension UIImage {

    func fixOrientation() -> UIImage {

        // No-op if the orientation is already correct
        if ( self.imageOrientation == UIImageOrientation.Up ) {
            return self;
        }

        // We need to calculate the proper transformation to make the image upright.
        // We do it in 2 steps: Rotate if Left/Right/Down, and then flip if Mirrored.
        var transform: CGAffineTransform = CGAffineTransformIdentity

        if ( self.imageOrientation == UIImageOrientation.Down || self.imageOrientation == UIImageOrientation.DownMirrored ) {
            transform = CGAffineTransformTranslate(transform, self.size.width, self.size.height)
            transform = CGAffineTransformRotate(transform, CGFloat(M_PI))
        }

        if ( self.imageOrientation == UIImageOrientation.Left || self.imageOrientation == UIImageOrientation.LeftMirrored ) {
            transform = CGAffineTransformTranslate(transform, self.size.width, 0)
            transform = CGAffineTransformRotate(transform, CGFloat(M_PI_2))
        }

        if ( self.imageOrientation == UIImageOrientation.Right || self.imageOrientation == UIImageOrientation.RightMirrored ) {
            transform = CGAffineTransformTranslate(transform, 0, self.size.height);
            transform = CGAffineTransformRotate(transform,  CGFloat(-M_PI_2));
        }

        if ( self.imageOrientation == UIImageOrientation.UpMirrored || self.imageOrientation == UIImageOrientation.DownMirrored ) {
            transform = CGAffineTransformTranslate(transform, self.size.width, 0)
            transform = CGAffineTransformScale(transform, -1, 1)
        }

        if ( self.imageOrientation == UIImageOrientation.LeftMirrored || self.imageOrientation == UIImageOrientation.RightMirrored ) {
            transform = CGAffineTransformTranslate(transform, self.size.height, 0);
            transform = CGAffineTransformScale(transform, -1, 1);
        }

        // Now we draw the underlying CGImage into a new context, applying the transform
        // calculated above.
        let ctx: CGContextRef = CGBitmapContextCreate(nil, Int(self.size.width), Int(self.size.height),
            CGImageGetBitsPerComponent(self.CGImage), 0,
            CGImageGetColorSpace(self.CGImage),
            CGImageGetBitmapInfo(self.CGImage).rawValue)!;

        CGContextConcatCTM(ctx, transform)

        if ( self.imageOrientation == UIImageOrientation.Left ||
            self.imageOrientation == UIImageOrientation.LeftMirrored ||
            self.imageOrientation == UIImageOrientation.Right ||
            self.imageOrientation == UIImageOrientation.RightMirrored ) {
                CGContextDrawImage(ctx, CGRectMake(0,0,self.size.height,self.size.width), self.CGImage)
        } else {
            CGContextDrawImage(ctx, CGRectMake(0,0,self.size.width,self.size.height), self.CGImage)
        }

        // And now we just create a new UIImage from the drawing context and return it
        return UIImage(CGImage: CGBitmapContextCreateImage(ctx)!)
    }
}

在您的代码中使用此UIImage扩展:

let fixOrientationImage = chosenImage.fixOrientation()

将其放置在图像选择器的委托方法中,如下所示:

Swift 3.1

//MARK: Image Picker Delegates
    func imagePickerController(
        _ picker: UIImagePickerController,
        didFinishPickingMediaWithInfo info: [String : Any]){
        let chosenImage = info[UIImagePickerControllerOriginalImage] as! UIImage
        profileImg.contentMode = .scaleAspectFill
        let fixOrientationImage=chosenImage.fixOrientation()
        profileImg.image = fixOrientationImage

        dismiss(animated: true, completion: nil)
    }

Swift 2.0

//MARK: Image Picker Delegates
    func imagePickerController(
        picker: UIImagePickerController,
        didFinishPickingMediaWithInfo info: [String : AnyObject])
    {
        let chosenImage = info[UIImagePickerControllerOriginalImage] as! UIImage
        profileImg.contentMode = .ScaleAspectFill
        **//Fix the image orientation**
         let fixOrientationImage=chosenImage.fixOrientation()
        profileImg.image = fixOrientationImage

        dismissViewControllerAnimated(true, completion: nil)
    }

3
谢谢,这对我帮助很大。 - kevinrodriguez-io
可以工作。但是我想知道是否有任何方法可以避免调用与 CG 相关的 API,因为这会导致大量的内存分配。只有当我们不再保留 UIImage 时,它才会被释放。 - Tony Fung
1
这在 iOS 10 上运行良好。在 iOS 11 中执行此操作后,我得到了一个黑色图像。我已经做了一个小改动(用 Double.pi 替代了 M_PI)。我正在使用带有 Swift 3.2 的 XCode 9。 - Mahbub Morshed

33

@an0的解决方案的Swift 4.x/5.0版本:

extension UIImage {
    func upOrientationImage() -> UIImage? {
        switch imageOrientation {
        case .up:
            return self
        default:
            UIGraphicsBeginImageContextWithOptions(size, false, scale)
            draw(in: CGRect(origin: .zero, size: size))
            let result = UIGraphicsGetImageFromCurrentImageContext()
            UIGraphicsEndImageContext()
            return result
        }
    }
}

1
谢谢。它完美地工作了。最被低估的答案。 - Vinayak Bhor
感谢您!在imagePickerController中调用此函数,它可以修复图像方向。 - Caterina Dmitrieva

25

使用Swift编程 ;)

更新至Swift 3.0 :D

func sFunc_imageFixOrientation(img:UIImage) -> UIImage {


    // No-op if the orientation is already correct
    if (img.imageOrientation == UIImageOrientation.up) {
        return img;
    }
    // We need to calculate the proper transformation to make the image upright.
    // We do it in 2 steps: Rotate if Left/Right/Down, and then flip if Mirrored.
    var transform:CGAffineTransform = CGAffineTransform.identity

    if (img.imageOrientation == UIImageOrientation.down
        || img.imageOrientation == UIImageOrientation.downMirrored) {

        transform = transform.translatedBy(x: img.size.width, y: img.size.height)
        transform = transform.rotated(by: CGFloat(M_PI))
    }

    if (img.imageOrientation == UIImageOrientation.left
        || img.imageOrientation == UIImageOrientation.leftMirrored) {

        transform = transform.translatedBy(x: img.size.width, y: 0)
        transform = transform.rotated(by: CGFloat(M_PI_2))
    }

    if (img.imageOrientation == UIImageOrientation.right
        || img.imageOrientation == UIImageOrientation.rightMirrored) {

        transform = transform.translatedBy(x: 0, y: img.size.height);
        transform = transform.rotated(by: CGFloat(-M_PI_2));
    }

    if (img.imageOrientation == UIImageOrientation.upMirrored
        || img.imageOrientation == UIImageOrientation.downMirrored) {

        transform = transform.translatedBy(x: img.size.width, y: 0)
        transform = transform.scaledBy(x: -1, y: 1)
    }

    if (img.imageOrientation == UIImageOrientation.leftMirrored
        || img.imageOrientation == UIImageOrientation.rightMirrored) {

        transform = transform.translatedBy(x: img.size.height, y: 0);
        transform = transform.scaledBy(x: -1, y: 1);
    }


    // Now we draw the underlying CGImage into a new context, applying the transform
    // calculated above.
    let ctx:CGContext = CGContext(data: nil, width: Int(img.size.width), height: Int(img.size.height),
                                  bitsPerComponent: img.cgImage!.bitsPerComponent, bytesPerRow: 0,
                                  space: img.cgImage!.colorSpace!,
                                  bitmapInfo: img.cgImage!.bitmapInfo.rawValue)!

    ctx.concatenate(transform)


    if (img.imageOrientation == UIImageOrientation.left
        || img.imageOrientation == UIImageOrientation.leftMirrored
        || img.imageOrientation == UIImageOrientation.right
        || img.imageOrientation == UIImageOrientation.rightMirrored
        ) {


        ctx.draw(img.cgImage!, in: CGRect(x:0,y:0,width:img.size.height,height:img.size.width))

    } else {
        ctx.draw(img.cgImage!, in: CGRect(x:0,y:0,width:img.size.width,height:img.size.height))
    }


    // And now we just create a new UIImage from the drawing context
    let cgimg:CGImage = ctx.makeImage()!
    let imgEnd:UIImage = UIImage(cgImage: cgimg)

    return imgEnd
}

经过数周的挣扎和尝试其他几种解决方案,这个最终完美地解决了问题。如果我可以点赞+10,我会的。谢谢。 :) - SirNod
@MetalHeart2003 这个方法极大地增加了我的图像文件大小,我的图片从200kb变成了4mb,有没有办法阻止这种情况发生? - TimWhiting
2
@TimWhiting 你似乎是从一个压缩的图像开始的。但这种方法可以在没有任何压缩的情况下重新绘制旋转后的图像。之后,你可以使用UIImageJPEGRepresentation()重新压缩位图图像。 - Richard Venable

12

在设计我的拍照应用程序时,我使用了这个页面。我发现以下方法可以更正方向并且比之前的答案使用更少的内存和处理器:

CGImageRef cgRef = image.CGImage;
image = [[UIImage alloc] initWithCGImage:cgRef scale:1.0 orientation:UIImageOrientationUp];

这基本上只是使用一个新的方向重新包装实际的图像数据。我之前使用了@an0的代码,但它会在内存中创建一个新的图像,对于你可能从相机中获得的3264x2448像素的图像来说,这可能会很耗费资源。


1
我在iPhone 5s上使用iOS 7.1测试了这个答案,但它没有起作用。在Windows机器[MS-Paint,Win照片查看器]和UIImageView中,图像的方向与原始方向相差90度!请注意,这些图像是使用默认相机触摸按钮拍摄的,而不是音量增加按钮。 - Jawad Al Shaikh
3
似乎只是更改了方向标志...需要旋转实际位。 - hamsterdam

11
如果您启用编辑,则编辑后的图像(而不是原始图像)将按预期方向定位:
UIImagePickerController *imagePickerController = [[UIImagePickerController alloc] init];
imagePickerController.allowsEditing = YES;
// set delegate and present controller

- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
    UIImage *photo = [info valueForKey:UIImagePickerControllerEditedImage];
    // do whatever
}

启用编辑功能可以让用户在点击“使用照片”之前调整和移动图像的大小。


遗憾的是,这个解决方案不能水平翻转图像,甚至不能进行编辑。 - King-Wizard
4
该功能可以正常运作,但需要用户将图像裁剪成正方形,除非你愿意增加更多的代码来允许可变的裁剪尺寸。 - user3000868

7
我可以通过编写以下几行代码来实现这一点。
extension UIImage {

    public func correctlyOrientedImage() -> UIImage {
        guard imageOrientation != .up else { return self }

        UIGraphicsBeginImageContextWithOptions(size, false, scale)
        draw(in: CGRect(origin: .zero, size: size))
        let normalizedImage: UIImage = UIGraphicsGetImageFromCurrentImageContext()!
        UIGraphicsEndImageContext()

        return normalizedImage
    }
}

6
这是我找到的解决方案,用于解决方向问题。
UIImage *initialImage = [info objectForKey:@"UIImagePickerControllerOriginalImage"];
NSData *data = UIImagePNGRepresentation(self.initialImage);

UIImage *tempImage = [UIImage imageWithData:data];
UIImage *fixedOrientationImage = [UIImage imageWithCGImage:tempImage.CGImage
                                     scale:initialImage.scale
                               orientation:self.initialImage.imageOrientation];
initialImage = fixedOrientationImage;

编辑:

UIImage *initialImage = [info objectForKey:@"UIImagePickerControllerOriginalImage"];
NSData *data = UIImagePNGRepresentation(self.initialImage);

initialImage = [UIImage imageWithCGImage:[UIImage imageWithData:data].CGImage
                                                     scale:initialImage.scale
                                               orientation:self.initialImage.imageOrientation];

2
图像与PNG之间的频繁转换使得成本过高。@an0的答案更好。 - lxcid

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接