将图像转换为CGImage时丢失图像方向

19

当我从矩形原始图像中裁剪出一个正方形的一部分时,我遇到了图像方向问题。如果图像是横向的话,一切正常。但是当它是纵向时,似乎图像方向没有被保留,这会导致图像方向错误且裁剪不良:

 func cropImage(cropRectangleCoordinates: CGRect) {

        let croppedImage = originalImage

        let finalCroppedImage : CGImageRef = CGImageCreateWithImageInRect(croppedImage.CGImage, cropRectangleCoordinates)

        finalImage =  UIImage(CGImage: finalCroppedImage)!


    }

我认为问题出在 croppedImage.CGImage 上。在这里,图像被转换为 CGImage,但似乎没有保留方向信息。 使用 UIImage 就可以轻松地保留方向,但是为了进行裁剪,图像需要被暂时地转换成 CGImage,这就是问题所在。即使在将其转换回 UIImage 时重新调整图像方向,它可能处于正确的方向,但在裁剪 CGImage 时已经造成了损坏。

这是一个Swift问题,因此请用Swift回答,因为在Objective-C中解决方案可能会有所不同。

8个回答

22

SWIFT 3:

使用此方法将旋转的cgImage转换为UIImage:

UIImage(cgImage:croppedCGImage, scale:originalImage.scale, orientation:originalImage.imageOrientation)

来源于 @David Berry 的回答


1
谢谢 :) 这比我在其他答案中看到的100行旋转代码好多了 :D - Matthew Cawley

17

这是我在查看其他人编写的几个旧代码后编写的UIImage扩展。它使用Swift 3编写,并使用iOS方向属性加上CGAffineTransform以正确方向重新绘制图像。

SWIFT 3:

public extension UIImage {

    /// Extension to fix orientation of an UIImage without EXIF
    func fixOrientation() -> UIImage {

        guard let cgImage = cgImage else { return self }

        if imageOrientation == .up { return self }

        var transform = CGAffineTransform.identity

        switch imageOrientation {

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

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

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

        case .up, .upMirrored:
            break
        }

        switch imageOrientation {

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

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

        case .up, .down, .left, .right:
            break
        }

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

            ctx.concatenate(transform)

            switch imageOrientation {

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

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

            if let finalImage = ctx.makeImage() {
                return (UIImage(cgImage: finalImage))
            }
        }

        // something failed -- return original
        return self
    }
}

2
在转换为调用image.cgImage之前调用此函数。 - Cjay
我知道这是老的,但是 case .upMirrored, .downMirrored:case .leftMirrored, .rightMirrored 是不正确的。你没有对 translatedByscaledBy 的结果做任何处理。你需要一个像上面 .down, .left.right 那样的模式(将结果保存回 transform 属性)。 - Rob

14

我找到了一个解决方案……时间会证明它是否足够稳定,但它似乎在所有情况下都起作用。这是一个非常难以解决的恶性bug。

问题在于,在某些情况下,UIImage在转换为CGImage时会失去其方向。这影响那些自动放置在横向模式下的肖像图像,所以默认情况下为横向的图像不受影响。 但这个 bug 的棘手之处在于,并不是所有的肖像图像都受到影响!!而且对于一些图像,imageorientation 值也没有帮助。 这些有问题的图像是用户从电子邮件、信息或网络上保存到库中的图像,因此不是通过相机拍摄的。这些图像可能没有方向信息,因此在某些情况下,将图像从纵向转换为横向后,纵向的图像仍然保持纵向。我真的卡在这个问题上,直到意识到设备库中的一些图像是从信息或电子邮件中保存下来的。

因此,我发现唯一可靠的方法是创建给定图像的两个版本:UIImage 和 CGImage,并比较它们的高度值。如果它们相等,则 CGImage 版本不会旋转,你可以像预期的那样处理它。 但是,如果它们的高度不同,你可以确定 CGImageCreateWithImageInRect 转换会将图像转为横向。 仅在这种情况下,我交换了原点的 x/y 坐标,并将其作为矩形坐标传递给裁剪函数,以便正确处理这些特殊的图像。

虽然这篇文章很长,但主要思想是比较 CGImage 的高度和 UIImage 的宽度,如果它们不同,则预计原点会被反转。


2
但是,如果图像是正方形的,那将成为您解决方案的一个大问题:[] - Husam
只需像以下视图一样放置图像路径并上传图像:UIImageView *imgViewTemp = [[UIImageView alloc]init]; [imgViewTemp setImage:[UIImage imageWithCGImage:[rep fullScreenImage]]]; UIImage *compressedImage = [mYGlobal funResizing:imgViewTemp.image];我也遇到了同样的问题,但是通过以上方法解决了。 - Chetu
这么多错误的答案,这个是正确的,谢谢! - las

11

这就是答案,感谢@awolf(Cropping an UIImage)。它完美地处理了比例和方向。只需在要裁剪的图像上调用此方法,并传递裁剪CGRect,无需担心比例或方向。随意检查cgImage是否为nil,而不像我在这里强制解包它。

extension UIImage {
    func croppedInRect(rect: CGRect) -> UIImage {
        func rad(_ degree: Double) -> CGFloat {
            return CGFloat(degree / 180.0 * .pi)
        }

        var rectTransform: CGAffineTransform
        switch imageOrientation {
        case .left:
            rectTransform = CGAffineTransform(rotationAngle: rad(90)).translatedBy(x: 0, y: -self.size.height)
        case .right:
            rectTransform = CGAffineTransform(rotationAngle: rad(-90)).translatedBy(x: -self.size.width, y: 0)
        case .down:
            rectTransform = CGAffineTransform(rotationAngle: rad(-180)).translatedBy(x: -self.size.width, y: -self.size.height)
        default:
            rectTransform = .identity
        }
        rectTransform = rectTransform.scaledBy(x: self.scale, y: self.scale)

        let imageRef = self.cgImage!.cropping(to: rect.applying(rectTransform))
        let result = UIImage(cgImage: imageRef!, scale: self.scale, orientation: self.imageOrientation)
        return result
    }
}

另外需要注意的是,如果你正在处理嵌入到scrollView中的imageView,则还需要额外考虑缩放因素。假设你的imageView跨越了整个scrollView的内容视图,并且你使用scrollView的边界作为裁剪框架,则可以获取裁剪后的图像:

let ratio = imageView.image!.size.height / scrollView.contentSize.height
let origin = CGPoint(x: scrollView.contentOffset.x * ratio, y: scrollView.contentOffset.y * ratio)
let size = CGSize(width: scrollView.bounds.size.width * ratio, let height: scrollView.bounds.size.height * ratio)
let cropFrame = CGRect(origin: origin, size: size)
let croppedImage = imageView.image!.croppedInRect(rect: cropFrame)

非常感谢,我使用了您的扩展和一些自己的代码,现在它完美地运行了。 - Gertjan Brouwer
好答案,伙计! - Artem Kirillov
非常感谢。我快要发疯了,几天来一直在排查我的作物设置有什么问题,但结果只是方向失调!现在它运行得非常好。 - Alexnnd

5

将您的UIImage创建调用更改为:

finalImage = UIImage(CGImage:finalCroppedImage, scale:originalImage.scale, orientation:originalImage.orientation)

保持图像的原始方向(和比例)。


我尝试过了,但问题更加棘手:finalimage已经被裁剪了,在较早的阶段就发生了方向丢失的损失:croppedImage.CGImage。 你所建议的可以正确旋转图像,但是在损坏发生后。 - Robert Brax
非常感谢。这对我很有帮助。 - Eugene Trapeznikov

5

在 SWIFT 5 中,我将以下内容添加为 UIImage 的扩展。思路是强制使 UIImage 中的图像与 UIImage 方向匹配(方向只影响图像的显示)。重新绘制 UIImage “容器”中的实际图像数据将使相应的 CGImage 具有相同的方向。

func forceSameOrientation() -> UIImage {
    UIGraphicsBeginImageContext(self.size)
    self.draw(in: CGRect(origin: CGPoint.zero, size: self.size))
    guard let image = UIGraphicsGetImageFromCurrentImageContext() else {
        UIGraphicsEndImageContext()
        return self
    }
    UIGraphicsEndImageContext()
    return image
}

2

@JGuo提供的答案是唯一有效的。我稍微更新了一下,返回一个可选的UIImage?和更Swifty的语法。我不喜欢隐式解包。

extension UIImage {

    func crop(to rect: CGRect) -> UIImage? {
        func rad(_ degree: Double) -> CGFloat {
            return CGFloat(degree / 180.0 * .pi)
        }

        var rectTransform: CGAffineTransform
        switch imageOrientation {
        case .left:
            rectTransform = CGAffineTransform(rotationAngle: rad(90)).translatedBy(x: 0, y: -self.size.height)
        case .right:
            rectTransform = CGAffineTransform(rotationAngle: rad(-90)).translatedBy(x: -self.size.width, y: 0)
        case .down:
            rectTransform = CGAffineTransform(rotationAngle: rad(-180)).translatedBy(x: -self.size.width, y: -self.size.height)
        default:
            rectTransform = .identity
        }
        rectTransform = rectTransform.scaledBy(x: self.scale, y: self.scale)

        guard let imageRef = self.cgImage?.cropping(to: rect.applying(rectTransform)) else { return nil }
        let result = UIImage(cgImage: imageRef, scale: self.scale, orientation: self.imageOrientation)
        return result
    }
}

这是在我的ViewController中作为计算属性实现的。
var croppedImage: UIImage? {
    guard let image = self.image else { return nil }

    let ratio = image.size.height / self.contentSize.height
    let origin = CGPoint(x: self.contentOffset.x * ratio, y: self.contentOffset.y * ratio)
    let size = CGSize(width: self.bounds.size.width * ratio, height: self.bounds.size.height * ratio)
    let cropFrame = CGRect(origin: origin, size: size)
    let croppedImage = image.crop(to: cropFrame)

    return croppedImage
}

1

对于Swift 5,您可以使用:

public extension UIImage {
    func cropped(rect: CGRect) -> UIImage? {
        if let image = self.cgImage?.cropping(to: rect) {
            return UIImage(cgImage: image, scale:self.scale, orientation:self.imageOrientation)
        }
        return nil

} }


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