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 } }
代码说明
- 坐标系转换:通过
transformForOrientation方法,根据图片的orientation,将基于UIImage视觉尺寸的裁剪rect转换为CGImage原始像素坐标系的rect,解决了竖屏拍摄时宽高颠倒的问题。 - 保留原方向:创建裁剪后的UIImage时,使用原图片的
imageOrientation,而非硬编码.leftMirrored,适配不同拍摄场景的图片方向。 - 结果验证:转换后的裁剪rect会匹配CGImage的实际像素尺寸,裁剪得到的CGImage再通过UIImage的orientation旋转显示后,会呈现1080×1350的尺寸,符合4:5的预期比例。
内容的提问来源于stack exchange,提问作者Ryan Sam
相关产品推荐
相关产品推荐

