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

问题描述 投票:0回答:8

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

 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
时损坏已经完成。

这是一个快速的问题,所以请快速回答,因为在 Objective-C 中解决方案可能不同。

ios swift uiimage cgimage
8个回答
22
投票

SWIFT 3:

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

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

来源@David Berry 回答


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

13
投票

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

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

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

那是一篇很长的文章,但主要思想是比较 CGImage 的高度和 UIImage 的宽度,如果它们不同,则期望原点反转。


10
投票

这就是答案,归功于@awolf(裁剪 UIImage)。完美处理比例和方向。只需在要裁剪的图像上调用此方法,然后传入裁剪

CGRect
,而无需担心比例或方向。随意检查
cgImage
是否为零,而不是像我在这里做的那样强制展开它。

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

另一个注意事项:如果您使用嵌入在

imageView
中的
scrollView
,还有一个额外的步骤,您必须考虑缩放因子。假设您的
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)

5
投票

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

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

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


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?
和 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
}

0
投票

对于 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

} }

© www.soinside.com 2019 - 2024. All rights reserved.