iOS - UIImageView - 如何处理UIImage图像方向

86

是否可以设置UIImageView来处理图像方向?当我将UIImageView设置为带有方向RIGHT的图像(它是来自相机胶卷的照片)时,图像会向右旋转,但我希望以正确的方向显示它,就像拍摄时一样。

我知道我可以旋转图像数据,但有没有更优雅的方法?

14个回答

147

如果我理解正确,您想要做的是忽略UIImage的方向吗?如果是这样,您可以这样做:

UIImage *originalImage = [... whatever ...];

UIImage *imageToDisplay =
     [UIImage imageWithCGImage:[originalImage CGImage]
              scale:[originalImage scale]
              orientation: UIImageOrientationUp];

你正在创建一个新的UIImage,其像素数据与原始图像相同(通过其CGImage属性引用),但指定了一个不旋转数据的方向。


4
顺便问一下,我该如何旋转图像数据? - Wang Liang
8
我猜你需要创建一个适当大小的CGContext,可以使用CGBitmapContextCreate(或使用UIGraphicsBeginImageContext简写),然后使用CGContextRotateCTM来设置旋转角度。接着,可以使用UIImagedrawInRect:方法或使用图像的CGImage属性和CGContextDrawImage方法将图像绘制到上下文中。最后,如果使用UIKit创建上下文,则可以使用UIGraphicsGetImageFromCurrentImageContext(然后使用UIGraphicsEndImageContext)将上下文转换为图像;如果坚持使用Core Graphics,则可以使用CGBitmapContextCreateImage。需要注意的是,UIKit在多线程环境下并不安全,但其代码更整洁。 - Tommy
当我把图像放在ImageView中时,我无法让它工作...即使我使用镜像方向创建图像:[UIImage imageWithCGImage:image.CGImage scale:image.scale orientation:UIImageOrientationUpMirrored],它仍然显示带有原始方向的图像...OP询问了如何在ImageView中使用,但我无法让这个解决方案在ImageView中起作用... - Ethan G
1
适用于PNG,但不适用于HEIC。 - DawnSong

54

您可以完全避免手动执行变换和缩放,正如an0在此答案中所建议的这里:

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

UIImage类的文档明确说明UIImage方法sizedrawInRect会考虑图像的方向。


3
这真是救命稻草。 - Quark
我应该把这个粘贴到哪里? - Frostmourne

35

Swift 3.1

func fixImageOrientation(_ image: UIImage)->UIImage {
    UIGraphicsBeginImageContext(image.size)
    image.draw(at: .zero)
    let newImage = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    return newImage ?? image
}

3
这是一个完美的解决方案 :) - miff
你值得拥有所有的比特,我的朋友。 - Mitchell Gant
这比那个更好。这适用于Swift 4,但那个不行。 - AechoLiu
1
谢谢。这个可以上传您的图像到服务器,而不仅仅是像其他答案一样显示。 - Nomad Developer
工作得很好。可以处理任何图像并将其保存为.up方向。 - zumzum
显示剩余3条评论

32

这种方法首先检查UIImage的当前方向,然后顺时针改变方向并返回UIImage。您可以像下面这样显示此图像:

self.imageView.image = rotateImage(currentUIImage)

   func rotateImage(image:UIImage)->UIImage
    {
        var rotatedImage = UIImage();
        switch image.imageOrientation
        {
            case UIImageOrientation.Right:
            rotatedImage = UIImage(CGImage:image.CGImage!, scale: 1, orientation:UIImageOrientation.Down);
            
           case UIImageOrientation.Down:
            rotatedImage = UIImage(CGImage:image.CGImage!, scale: 1, orientation:UIImageOrientation.Left);
            
            case UIImageOrientation.Left:
            rotatedImage = UIImage(CGImage:image.CGImage!, scale: 1, orientation:UIImageOrientation.Up);
    
             default:
            rotatedImage = UIImage(CGImage:image.CGImage!, scale: 1, orientation:UIImageOrientation.Right);
        }
        return rotatedImage;
    }

Swift 4版本

extension UIImage {

func rotate() -> UIImage {
    var rotatedImage = UIImage()
    guard let cgImage = cgImage else {
        print("could not rotate image")
        return self
    }
    switch imageOrientation {
    case .right:
        rotatedImage = UIImage(cgImage: cgImage, scale: scale, orientation: .down)
    case .down:
        rotatedImage = UIImage(cgImage: cgImage, scale: scale, orientation: .left)
    case .left:
        rotatedImage = UIImage(cgImage: cgImage, scale: scale, orientation: .up)
    default:
        rotatedImage = UIImage(cgImage: cgImage, scale: scale, orientation: .right)
    }
    
    return rotatedImage
}
}

嗨Varender,感谢您在SO上的帮助。您能详细说明一下您认为这个解决方案如何帮助Martin吗?相比于旋转数据,您的解决方案有多么优雅? - J. Chomel
  1. 我们将从UIImageView中获取当前的UIImage。
  2. 我们将调用这个方法(rotateImage),并将当前的UIImage作为参数传递。
  3. 将返回值存储到UIImageView.image中。/* 这将旋转图像视图中的数据 :) :)
- varender singh
先生,依我之见,这是最佳答案,因为如果我们旋转整个UIImageView,那么UIImageView的X值将成为负值。但是,如果我们旋转数据,则UIImageView将保持在原地,只有数据会被旋转。如果您使用正方形UIImageView,则可以使用CGAffineTransformMakeRotate简单地转换整个UIImageView。 - varender singh
这应该是被接受的答案!像魔法一样有效 :) - Ely Dantas
在 Swift 3 中:rotatedImage = UIImage(cgImage:image.cgImage!, scale: 1, orientation:UIImageOrientation.right) - Ansal Antony
这将适用于旋转您的视图以进行显示。 但是,如果您需要上传您的 UIImage 到某个地方使用 Alamofire 或其他工具,它将无法正常工作。 对于上传,请使用 @Aqua 的答案 fixImageOrientation(_:) - Nomad Developer

26

我将这里Anomie的答案中的代码(由suvish valsan复制并粘贴在上面)转换为Swift

func fixOrientation() -> UIImage {
    if self.imageOrientation == UIImageOrientation.Up {
        return self
    }

    var transform = CGAffineTransformIdentity

    switch self.imageOrientation {
    case .Down, .DownMirrored:
        transform = CGAffineTransformTranslate(transform, self.size.width, self.size.height)
        transform = CGAffineTransformRotate(transform, CGFloat(M_PI));

    case .Left, .LeftMirrored:
        transform = CGAffineTransformTranslate(transform, self.size.width, 0);
        transform = CGAffineTransformRotate(transform, CGFloat(M_PI_2));

    case .Right, .RightMirrored:
        transform = CGAffineTransformTranslate(transform, 0, self.size.height);
        transform = CGAffineTransformRotate(transform, CGFloat(-M_PI_2));

    case .Up, .UpMirrored:
        break
    }

    switch self.imageOrientation {

    case .UpMirrored, .DownMirrored:
        transform = CGAffineTransformTranslate(transform, self.size.width, 0)
        transform = CGAffineTransformScale(transform, -1, 1)

    case .LeftMirrored, .RightMirrored:
        transform = CGAffineTransformTranslate(transform, self.size.height, 0)
        transform = CGAffineTransformScale(transform, -1, 1);

    default:
        break;
    }

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

    CGContextConcatCTM(ctx, transform);

    switch self.imageOrientation {
    case .Left, .LeftMirrored, .Right, .RightMirrored:
        // Grr...
        CGContextDrawImage(ctx, CGRectMake(0, 0, self.size.height,self.size.width), self.CGImage);

    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
    let cgimg = CGBitmapContextCreateImage(ctx)

    let img = UIImage(CGImage: cgimg!)

    return img;
}

我将参数image的所有出现替换为self,因为我的代码是基于UIImage的扩展。


编辑: Swift 3 版本。

该方法返回一个可选值,因为许多中间调用可能会失败,我不喜欢使用!

func fixOrientation() -> UIImage? {

    guard let cgImage = self.cgImage else {
        return nil
    }

    if self.imageOrientation == UIImageOrientation.up {
        return self
    }

    let width  = self.size.width
    let height = self.size.height

    var transform = CGAffineTransform.identity

    switch self.imageOrientation {
    case .down, .downMirrored:
        transform = transform.translatedBy(x: width, y: height)
        transform = transform.rotated(by: CGFloat.pi)

    case .left, .leftMirrored:
        transform = transform.translatedBy(x: width, y: 0)
        transform = transform.rotated(by: 0.5*CGFloat.pi)

    case .right, .rightMirrored:
        transform = transform.translatedBy(x: 0, y: height)
        transform = transform.rotated(by: -0.5*CGFloat.pi)

    case .up, .upMirrored:
        break
    }

    switch self.imageOrientation {
    case .upMirrored, .downMirrored:
        transform = transform.translatedBy(x: width, y: 0)
        transform = transform.scaledBy(x: -1, y: 1)

    case .leftMirrored, .rightMirrored:
        transform = transform.translatedBy(x: height, y: 0)
        transform = transform.scaledBy(x: -1, y: 1)

    default:
        break;
    }

    // Now we draw the underlying CGImage into a new context, applying the transform
    // calculated above.
    guard let colorSpace = cgImage.colorSpace else {
        return nil
    }

    guard let context = CGContext(
        data: nil,
        width: Int(width),
        height: Int(height),
        bitsPerComponent: cgImage.bitsPerComponent,
        bytesPerRow: 0,
        space: colorSpace,
        bitmapInfo: UInt32(cgImage.bitmapInfo.rawValue)
        ) else {
            return nil
    }

    context.concatenate(transform);

    switch self.imageOrientation {

    case .left, .leftMirrored, .right, .rightMirrored:
        // Grr...
        context.draw(cgImage, in: CGRect(x: 0, y: 0, width: height, height: width))

    default:
        context.draw(cgImage, in: CGRect(x: 0, y: 0, width: width, height: height))
    }

    // And now we just create a new UIImage from the drawing context
    guard let newCGImg = context.makeImage() else {
        return nil
    }

    let img = UIImage(cgImage: newCGImg)

    return img;
}

(注意:Swift 3版本代码可以在Xcode 8.1下编译,但实际上尚未测试其是否有效。可能会存在错别字、宽度/高度混淆等问题。欢迎指出/修复任何错误。)


我已经将上述代码转换为Swift 3并使用了它。如果有人需要Swift 3版本,请查看我的答案。 - Waseem05
图片方向没有改变。非常抱歉,我是iOS开发的新手。我所做的是,在UIImage的扩展函数中使用该函数,然后在func viewDidLoad里面以编程方式设置它。这张照片来自三星手机https://photos.google.com/share/AF1QipN4DE_xAT-RyGYM3zPxiJyj5gPNgQiDHyp1M2JSD4CZSky43evQTjPKnMsiq4E_6g?key=YV92QnVaY1pyTTEybVFvbmdIS3R5c3A0ajJqaVJn并且有270度顺时针方向的exif数据。以下是我的代码:`let background: UIImage? = UIImage(named: "background_image")?.fixOrientation() backgroundImage.image = background`。 - HendraWD
有人在使用@Nicolas时遇到了内存泄漏问题吗?看起来CGImages没有被释放。我的应用程序是一个大规模的照片管理应用程序,处理许多照片。每次运行此函数似乎都会泄漏内存。有什么快速解决方法吗? - Michael Reilly
@MichaelReilly 嗯,我还没有仔细研究过,但是在Swift中,CoreGraphics调用应该可以与ARC一起使用。 - Nicolas Miari
@MichaelReilly 我找到了这个答案,似乎证实了我的观察:https://dev59.com/_18e5IYBdhLWcg3wZprw#25790214 - Nicolas Miari
@MichaelReilly,你能分享一下你的内存泄漏问题的更多细节吗?我自己已经有一段时间没用过这个函数了,而且我也不能够再访问我使用这段代码的实际项目。 - Nicolas Miari

10

Swift中的UIImage扩展。你完全不需要进行所有这些翻转操作。Objective-C的原地址在这里,但是我添加了一个尊重原始图像alpha通道的魔法代码(虽然粗略,但它可以区分不透明图像和透明图像)。

// from https://github.com/mbcharbonneau/UIImage-Categories/blob/master/UIImage%2BAlpha.m
// Returns true if the image has an alpha layer
    private func hasAlpha() -> Bool {
        guard let cg = self.cgImage else { return false }
        let alpha = cg.alphaInfo
        let retVal = (alpha == .first || alpha == .last || alpha == .premultipliedFirst || alpha == .premultipliedLast)
        return retVal
    }

    func normalizedImage() -> UIImage? {
        if self.imageOrientation == .up {
            return self
        }
        UIGraphicsBeginImageContextWithOptions(self.size, !self.hasAlpha(), self.scale)
        var rect = CGRect.zero
        rect.size = self.size
        self.draw(in: rect)
        let retVal = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return retVal
    }

8

我将@Nicolas Miari的代码转换成Swift 3,以防有需要的人

func fixOrientation() -> UIImage
{

    if self.imageOrientation == UIImageOrientation.up {
        return self
    }

    var transform = CGAffineTransform.identity

    switch self.imageOrientation {
    case .down, .downMirrored:
        transform = transform.translatedBy(x: self.size.width, y: self.size.height)
        transform = transform.rotated(by: CGFloat(M_PI));

    case .left, .leftMirrored:
        transform = transform.translatedBy(x: self.size.width, y: 0);
        transform = transform.rotated(by: CGFloat(M_PI_2));

    case .right, .rightMirrored:
        transform = transform.translatedBy(x: 0, y: self.size.height);
        transform = transform.rotated(by: CGFloat(-M_PI_2));

    case .up, .upMirrored:
        break
    }


    switch self.imageOrientation {

    case .upMirrored, .downMirrored:
        transform = transform.translatedBy(x: self.size.width, y: 0)
        transform = transform.scaledBy(x: -1, y: 1)

    case .leftMirrored, .rightMirrored:
        transform = transform.translatedBy(x: self.size.height, y: 0)
        transform = transform.scaledBy(x: -1, y: 1);

    default:
        break;
    }

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



    ctx!.concatenate(transform);

    switch self.imageOrientation {

    case .left, .leftMirrored, .right, .rightMirrored:
        // Grr...
        ctx?.draw(self.cgImage!, in: CGRect(x:0 ,y: 0 ,width: self.size.height ,height:self.size.width))

    default:
        ctx?.draw(self.cgImage!, in: CGRect(x:0 ,y: 0 ,width: self.size.width ,height:self.size.height))
        break;
    }

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

    let img = UIImage(cgImage: cgimg!)

    return img;

}

在Delegate方法中捕获图像后,图像总是正确方向的,但此方法返回了错误的图像 :(imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any] let imageCaptured:UIImage = info[UIImagePickerControllerOriginalImage] as! UIImage - Sumeet Mourya
是的,图像方向始终为.right,但在UIImageView上显示正确。苹果有一些内部数据用于正确显示。 - Waseem05

8

这里是一份可行的示例代码,考虑到图像方向:

#define rad(angle) ((angle) / 180.0 * M_PI)
- (CGAffineTransform)orientationTransformedRectOfImage:(UIImage *)img
{
    CGAffineTransform rectTransform;
    switch (img.imageOrientation)
    {
        case UIImageOrientationLeft:
            rectTransform = CGAffineTransformTranslate(CGAffineTransformMakeRotation(rad(90)), 0, -img.size.height);
            break;
        case UIImageOrientationRight:
            rectTransform = CGAffineTransformTranslate(CGAffineTransformMakeRotation(rad(-90)), -img.size.width, 0);
            break;
        case UIImageOrientationDown:
            rectTransform = CGAffineTransformTranslate(CGAffineTransformMakeRotation(rad(-180)), -img.size.width, -img.size.height);
            break;
        default:
            rectTransform = CGAffineTransformIdentity;
    };

    return CGAffineTransformScale(rectTransform, img.scale, img.scale);
}


- (UIImage *)croppedImage:(UIImage*)orignialImage InRect:(CGRect)visibleRect{
    //transform visible rect to image orientation
    CGAffineTransform rectTransform = [self orientationTransformedRectOfImage:orignialImage];
    visibleRect = CGRectApplyAffineTransform(visibleRect, rectTransform);

    //crop image
    CGImageRef imageRef = CGImageCreateWithImageInRect([orignialImage CGImage], visibleRect);
    UIImage *result = [UIImage imageWithCGImage:imageRef scale:orignialImage.scale orientation:orignialImage.imageOrientation];
    CGImageRelease(imageRef);
    return result;
}

5
如果您需要旋转和修复图像方向,下面的扩展程序将非常有用。
extension UIImage {

    public func imageRotatedByDegrees(degrees: CGFloat) -> UIImage {
        //Calculate the size of the rotated view's containing box for our drawing space
        let rotatedViewBox: UIView = UIView(frame: CGRect(x: 0, y: 0, width: self.size.width, height: self.size.height))
        let t: CGAffineTransform = CGAffineTransform(rotationAngle: degrees * CGFloat.pi / 180)
        rotatedViewBox.transform = t
        let rotatedSize: CGSize = rotatedViewBox.frame.size
        //Create the bitmap context
        UIGraphicsBeginImageContext(rotatedSize)
        let bitmap: CGContext = UIGraphicsGetCurrentContext()!
        //Move the origin to the middle of the image so we will rotate and scale around the center.
        bitmap.translateBy(x: rotatedSize.width / 2, y: rotatedSize.height / 2)
        //Rotate the image context
        bitmap.rotate(by: (degrees * CGFloat.pi / 180))
        //Now, draw the rotated/scaled image into the context
        bitmap.scaleBy(x: 1.0, y: -1.0)
        bitmap.draw(self.cgImage!, in: CGRect(x: -self.size.width / 2, y: -self.size.height / 2, width: self.size.width, height: self.size.height))
        let newImage: UIImage = UIGraphicsGetImageFromCurrentImageContext()!
        UIGraphicsEndImageContext()
        return newImage
    }


    public func fixedOrientation() -> UIImage {
        if imageOrientation == UIImageOrientation.up {
            return self
        }

        var transform: CGAffineTransform = CGAffineTransform.identity

        switch imageOrientation {
        case UIImageOrientation.down, UIImageOrientation.downMirrored:
            transform = transform.translatedBy(x: size.width, y: size.height)
            transform = transform.rotated(by: CGFloat.pi)
            break
        case UIImageOrientation.left, UIImageOrientation.leftMirrored:
            transform = transform.translatedBy(x: size.width, y: 0)
            transform = transform.rotated(by: CGFloat.pi/2)
            break
        case UIImageOrientation.right, UIImageOrientation.rightMirrored:
            transform = transform.translatedBy(x: 0, y: size.height)
            transform = transform.rotated(by: -CGFloat.pi/2)
            break
        case UIImageOrientation.up, UIImageOrientation.upMirrored:
            break
        }

        switch imageOrientation {
        case UIImageOrientation.upMirrored, UIImageOrientation.downMirrored:
            transform.translatedBy(x: size.width, y: 0)
            transform.scaledBy(x: -1, y: 1)
            break
        case UIImageOrientation.leftMirrored, UIImageOrientation.rightMirrored:
            transform.translatedBy(x: size.height, y: 0)
            transform.scaledBy(x: -1, y: 1)
        case UIImageOrientation.up, UIImageOrientation.down, UIImageOrientation.left, UIImageOrientation.right:
            break
        }

        let ctx: CGContext = CGContext(data: nil,
                                       width: Int(size.width),
                                       height: Int(size.height),
                                       bitsPerComponent: self.cgImage!.bitsPerComponent,
                                       bytesPerRow: 0,
                                       space: self.cgImage!.colorSpace!,
                                       bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue)!

        ctx.concatenate(transform)

        switch imageOrientation {
        case UIImageOrientation.left, UIImageOrientation.leftMirrored, UIImageOrientation.right, UIImageOrientation.rightMirrored:
            ctx.draw(self.cgImage!, in: CGRect(x: 0, y: 0, width: size.height, height: size.width))
        default:
            ctx.draw(self.cgImage!, in: CGRect(x: 0, y: 0, width: size.width, height: size.height))
            break
        }

        let cgImage: CGImage = ctx.makeImage()!

        return UIImage(cgImage: cgImage)
    }
}

4
感谢Waseem05提供的Swift 3翻译,但他的方法只有在我将它包装到UIImage的扩展中并将其放置在父类之外/下方时才能正常工作,就像这样:
extension UIImage {

        func fixOrientation() -> UIImage
        {

            if self.imageOrientation == UIImageOrientation.up {
            return self
        }

        var transform = CGAffineTransform.identity

        switch self.imageOrientation {
        case .down, .downMirrored:
            transform = transform.translatedBy(x: self.size.width, y: self.size.height)
            transform = transform.rotated(by: CGFloat(M_PI));

        case .left, .leftMirrored:
            transform = transform.translatedBy(x: self.size.width, y: 0);
            transform = transform.rotated(by: CGFloat(M_PI_2));

        case .right, .rightMirrored:
            transform = transform.translatedBy(x: 0, y: self.size.height);
            transform = transform.rotated(by: CGFloat(-M_PI_2));

        case .up, .upMirrored:
            break
        }


        switch self.imageOrientation {

        case .upMirrored, .downMirrored:
            transform = transform.translatedBy(x: self.size.width, y: 0)
            transform = transform.scaledBy(x: -1, y: 1)

        case .leftMirrored, .rightMirrored:
            transform = transform.translatedBy(x: self.size.height, y: 0)
            transform = transform.scaledBy(x: -1, y: 1);

        default:
            break;
        }

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



        ctx!.concatenate(transform);

        switch self.imageOrientation {

        case .left, .leftMirrored, .right, .rightMirrored:
            // Grr...
            ctx?.draw(self.cgImage!, in: CGRect(x:0 ,y: 0 ,width: self.size.height ,height:self.size.width))

        default:
            ctx?.draw(self.cgImage!, in: CGRect(x:0 ,y: 0 ,width: self.size.width ,height:self.size.height))
            break;
        }

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

        let img = UIImage(cgImage: cgimg!)

        return img;

    }
}

然后使用以下方式调用:
let correctedImage:UIImage = wonkyImage.fixOrientation()

一切都很好!苹果应该让我们更轻松地放弃方向,当我们不需要前/后摄像头和上/下/左/右设备方向元数据时。


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