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

Losing image orientation while converting an image to CGImage

从矩形原始图像中裁剪出图像的正方形部分时,我遇到了图像方向问题。当图像处于横向时,它很好。但是当它是纵向时,图像方向似乎没有保留,这导致图像方向错误和裁剪错误:

 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 中的解决方案可能有所不同。

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

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

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

我找到了解决方案....时间会证明它是否足够强大,但它似乎适用于所有情况。这是一个需要修复的恶性错误。

所以问题是 UIImage,仅在某些情况下,在转换为 CGImage 时失去了它的方向。它会影响自动进入横向模式的肖像图像。所以默认情况下横向的图像不受影响。 但是这个错误的恶毒之处在于它不会影响所有肖像图像! imageorientation 值对某些图像也无济于事。 这些有问题的图像是用户在其库中从电子邮件、消息或从网络上保存的图像,而不是用相机拍摄的。这些图像可能没有方向信息,因此在某些情况下,图像是纵向的……当转换为 CGImage 时,仍然是纵向的。直到我意识到我设备库中的某些图像是从消息或电子邮件中保存的,我才真正坚持这一点。

所以我发现猜测哪个图像将被重定向的唯一可靠方法是创建给定图像的两个版本:UIImage 和 CGI​​mage,并比较它们的高度值。如果它们相等,则 CGImage 版本将不会旋转,您可以按预期使用它。 但是,如果它们的高度不同,您可以确定从 CGImageCreateWithImageInRect 进行的 CGImage 转换将美化图像。 仅在这种情况下,我交换原点的 x/y 坐标,我将其作为矩形坐标传递给裁剪并正确处理这些特殊图像。

那是一个很长的 post,但主要思想是比较 CGImage 的高度和 UIImage 的宽度,如果它们不同,则预计原点会反转。

这是我在查看其他人编写的几段较旧代码后编写的 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
    }
}

SWIFT 3:

通过此方法将旋转的 cgImage 转换为 UIImage

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

来自@David Berry 的回答

这就是答案,归功于@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
    }
}

另请注意:如果您使用嵌入 scrollViewimageView,还有一个额外的步骤,您必须考虑缩放系数。假设你的 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)

@JGuo 给出了唯一有效的答案。我只更新了一点点 return 一个可选的 UIImage? 和 swift-er 语法。我宁愿永远不要隐式解包。

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
}

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
}