You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

CGImage的.cropping(to:)方法为何无法按正确比例裁剪?

解决CGImage.cropping(to:)裁剪比例不符的问题

问题根源

UIImage的size是考虑了显示方向的视觉尺寸,但CGImage存储的是摄像头捕获的原始像素物理尺寸,两者的宽高可能因为图片方向(orientation)颠倒。

你用前置竖屏拍摄的图片,UIImage显示为1080×1920,但CGImage的实际像素尺寸是1920×1080(竖屏拍摄时,原始像素是横向的,UIImage通过orientation属性旋转后显示为竖屏)。当你基于UIImage的size计算出1080×1350的裁剪rect,直接应用到CGImage上时,CGImage的实际高度只有1080,裁剪高度超过了像素范围,所以只能裁到最大可用高度1080,最终导致比例错误。

解决方案

需要根据UIImage的orientation,将基于视觉尺寸的裁剪rect转换为CGImage原始像素坐标系下的rect,再执行裁剪。

修正后的代码

let aspectRatio = CGFloat(4.0 / 5.0)
print("aspect ratio: \(aspectRatio)")
                            
let rect = CGRect(
    x: 0,
    y: 0,
    width: capture.size.width,
    height: CGFloat(capture.size.width / aspectRatio)
)

image = crop(
    image: capture,
    toRect: rect
)

private func crop(image: UIImage, toRect rect: CGRect) -> UIImage? {
    print("org image width: \(image.size.width)")
    print("org image height: \(image.size.height)")
    print("crop width: \(rect.size.width)")
    print("crop height: \(rect.size.height)")
    
    guard let cgImage = image.cgImage else { return nil }
    
    // 将视觉坐标系的裁剪rect转换为CGImage原始像素坐标系的rect
    let cgImageSize = CGSize(width: cgImage.width, height: cgImage.height)
    let transformedRect = rect.applying(image.transformForOrientation(from: image.size, to: cgImageSize))
    
    print("img scale: \(image.scale)")
    print("transformed rect x: \(transformedRect.origin.x)")
    print("transformed rect y: \(transformedRect.origin.y)")
    print("transformed rect width: \(transformedRect.size.width)")
    print("transformed rect height: \(transformedRect.size.height)")
    
    guard let croppedCgImage = cgImage.cropping(to: transformedRect) else { return nil }
    print("cropped cgimage width: \(croppedCgImage.width)")
    print("cropped cgimage height: \(croppedCgImage.height)")
    
    // 保留原图片的方向,避免硬编码导致适配问题
    let croppedImage = UIImage(cgImage: croppedCgImage, scale: image.scale, orientation: image.imageOrientation)
    print("cropped image width: \(croppedImage.size.width)")
    print("cropped image height: \(croppedImage.size.height)")
    
    return croppedImage
}

// 扩展UIImage,实现坐标系转换逻辑
extension UIImage {
    func transformForOrientation(from imageSize: CGSize, to cgImageSize: CGSize) -> CGAffineTransform {
        var transform = CGAffineTransform.identity
        
        // 处理旋转
        switch imageOrientation {
        case .left, .leftMirrored, .right, .rightMirrored:
            transform = transform.translatedBy(x: imageSize.width, y: 0)
            transform = transform.rotated(by: .pi/2)
        case .down, .downMirrored:
            transform = transform.translatedBy(x: imageSize.width, y: imageSize.height)
            transform = transform.rotated(by: .pi)
        case .up, .upMirrored:
            break
        @unknown default:
            break
        }
        
        // 处理镜像翻转
        switch imageOrientation {
        case .leftMirrored, .rightMirrored, .upMirrored, .downMirrored:
            transform = transform.translatedBy(x: cgImageSize.width, y: 0)
            transform = transform.scaledBy(x: -1, y: 1)
        default:
            break
        }
        
        // 缩放匹配CGImage实际尺寸
        transform = transform.scaledBy(x: cgImageSize.width / imageSize.width, y: cgImageSize.height / imageSize.height)
        
        return transform
    }
}

代码说明

  1. 坐标系转换:通过transformForOrientation方法,根据图片的orientation,将基于UIImage视觉尺寸的裁剪rect转换为CGImage原始像素坐标系的rect,解决了竖屏拍摄时宽高颠倒的问题。
  2. 保留原方向:创建裁剪后的UIImage时,使用原图片的imageOrientation,而非硬编码.leftMirrored,适配不同拍摄场景的图片方向。
  3. 结果验证:转换后的裁剪rect会匹配CGImage的实际像素尺寸,裁剪得到的CGImage再通过UIImage的orientation旋转显示后,会呈现1080×1350的尺寸,符合4:5的预期比例。

内容的提问来源于stack exchange,提问作者Ryan Sam

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 18:23:14