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

Swift如何实现类似Snapchat/PS魔术棒的自定义轮廓裁剪替代CGRect裁剪?

实现自定义轮廓裁剪的方案

原来基于CGRect的裁剪只能实现矩形裁剪,你可以通过路径蒙版的方式实现任意轮廓的裁剪,具体实现逻辑如下:

核心实现思路

  1. 先获取目标物体的轮廓路径:可通过用户手绘交互生成UIBezierPath对象,或调用系统能力自动识别主体轮廓,路径需要和显示图片的视图坐标对齐,和你原有逻辑一致做等比缩放匹配原图尺寸
  2. 替换原有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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.30 10:06:04