Swift如何实现类似Snapchat/PS魔术棒的自定义轮廓裁剪替代CGRect裁剪?
实现自定义轮廓裁剪的方案
原来基于CGRect的裁剪只能实现矩形裁剪,你可以通过路径蒙版的方式实现任意轮廓的裁剪,具体实现逻辑如下:
核心实现思路
- 先获取目标物体的轮廓路径:可通过用户手绘交互生成
UIBezierPath对象,或调用系统能力自动识别主体轮廓,路径需要和显示图片的视图坐标对齐,和你原有逻辑一致做等比缩放匹配原图尺寸 - 替换原有Core Graphics裁剪逻辑:将轮廓路径设置为上下文裁剪蒙版,仅保留路径范围内的图像内容,可支持保留路径外透明效果
完整代码实现
func cropImage(_ inputImage: UIImage, toPath cropPath: UIBezierPath, viewWidth: CGFloat, viewHeight: CGFloat) -> UIImage? { let imageViewScale = max(inputImage.size.width / viewWidth, inputImage.size.height / viewHeight) // 缩放绘制路径匹配原图尺寸 let scaledPath = cropPath.copy() as! UIBezierPath scaledPath.apply(CGAffineTransform(scaleX: imageViewScale, y: imageViewScale)) UIGraphicsBeginImageContextWithOptions(inputImage.size, false, inputImage.scale) guard let context = UIGraphicsGetCurrentContext() else { UIGraphicsEndImageContext() return nil } // 适配Core Graphics与UIKitY轴翻转的坐标差异 context.translateBy(x: 0, y: inputImage.size.height) context.scaleBy(x: 1.0, y: -1.0) // 将轮廓路径设置为裁剪区域 scaledPath.addClip() // 绘制原图,仅保留路径范围内内容 context.draw(inputImage.cgImage!, in: CGRect(origin: .zero, size: inputImage.size)) let croppedImage = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() // 可选:自动裁剪掉透明空白边缘,输出最小尺寸的结果图 guard let finalCgImage = croppedImage?.cgImage?.trimmingTransparentPixels() else { return croppedImage } return UIImage(cgImage: finalCgImage, scale: inputImage.scale, orientation: inputImage.imageOrientation) } // 辅助扩展:裁剪CGImage的透明边缘 extension CGImage { func trimmingTransparentPixels() -> CGImage? { let width = self.width let height = self.height let colorSpace = CGColorSpaceCreateDeviceRGB() let bytesPerPixel = 4 let bytesPerRow = bytesPerPixel * width let bitsPerComponent = 8 let alphaInfo = CGImageAlphaInfo.premultipliedLast.rawValue guard let context = CGContext(data: nil, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: alphaInfo) else { return nil } context.draw(self, in: CGRect(x: 0, y: 0, width: width, height: height)) guard let pixelData = context.data else { return nil } let data = pixelData.assumingMemoryBound(to: UInt8.self) var minX = width, maxX = 0, minY = height, maxY = 0 for y in 0..<height { for x in 0..<width { let alpha = data[y * bytesPerRow + x * bytesPerPixel + 3] if alpha > 0 { minX = min(minX, x) maxX = max(maxX, x) minY = min(minY, y) maxY = max(maxY, y) } } } guard minX <= maxX, minY <= maxY else { return nil } let cropRect = CGRect(x: minX, y: minY, width: maxX - minX + 1, height: maxY - minY + 1) return self.cropping(to: cropRect) } }
注意事项
- 如果需要实现类似魔术棒的自动识别轮廓能力,不需要用户手动绘制,可以直接用iOS自带的Vision框架的
VNGenerateForegroundInstanceMaskRequest接口,自动识别图片中的主体生成对应轮廓路径 - 路径缩放逻辑要和你原有
CGRect缩放逻辑完全一致,避免裁剪位置和用户绘制的轮廓出现偏移 - 如果需要裁剪区域外为不透明背景,只要把
UIGraphicsBeginImageContextWithOptions的第二个参数设置为true,再提前绘制一层你需要的背景色即可
内容的提问来源于stack exchange,提问作者Lloyd English
相关产品推荐
相关产品推荐

