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

按CGPath遮罩UIImage后保存至相册失效问题求助

解决UIImage遮罩保存无效的问题

我来帮你排查下问题所在,你的预览代码能正常工作,但保存原图尺寸遮罩图片时失效,主要是这几个关键点没匹配上:


1. 裁剪规则不匹配

你在预览时给maskLayer设置了fillRule = .evenOdd,但Core Graphics默认的裁剪规则是非零缠绕规则(nonZero),这会导致裁剪效果和预览完全不一致,甚至看起来遮罩没生效。

2. 坐标系方向差异

UIKit的坐标系原点在左上角,但Core Graphics的原点在左下角。如果不做翻转处理,你的遮罩路径会上下颠倒,根本没覆盖在图片的正确位置上。

3. 图像上下文的分辨率问题

你用的UIGraphicsBeginImageContext不支持Retina屏幕的scale,不仅会导致保存的图片模糊,还可能因为scale不匹配让路径缩放计算出错。


修改后的完整代码

首先修正CGPath的缩放扩展(更准确的居中计算)

extension CGPath {
    func resized(to rect: CGRect) -> CGPath {
        let boundingBox = self.boundingBox
        let boxWidth = boundingBox.width
        let boxHeight = boundingBox.height
        
        guard boxWidth > 0, boxHeight > 0 else { return self }
        
        let boxAspect = boxWidth / boxHeight
        let rectAspect = rect.width / rect.height
        
        // 计算适配目标rect的缩放比例
        let scaleFactor: CGFloat = boxAspect > rectAspect 
            ? rect.width / boxWidth 
            : rect.height / boxHeight
        
        // 计算平移量,保证缩放后的路径在rect中居中
        let targetCenterX = rect.midX
        let targetCenterY = rect.midY
        let scaledBoxCenterX = boundingBox.midX * scaleFactor
        let scaledBoxCenterY = boundingBox.midY * scaleFactor
        
        // 因为先缩放再平移,所以平移量要除以缩放比例
        let tx = (targetCenterX - scaledBoxCenterX) / scaleFactor
        let ty = (targetCenterY - scaledBoxCenterY) / scaleFactor
        
        var transform = CGAffineTransform.identity
            .scaledBy(x: scaleFactor, y: scaleFactor)
            .translatedBy(x: tx, y: ty)
        
        return copy(using: &transform)!
    }
}

然后修改遮罩图片生成与保存函数

func getMaskedImage(path: CGPath) {
    guard let originalImage = UIImage(named: "1") else { 
        print("无法加载原图")
        return 
    }
    
    // 创建支持Retina的图像上下文,匹配原图的scale
    UIGraphicsBeginImageContextWithOptions(originalImage.size, false, originalImage.scale)
    defer { UIGraphicsEndImageContext() } // 确保上下文被正确释放
    
    guard let context = UIGraphicsGetCurrentContext() else { return }
    
    // 翻转坐标系,对齐UIKit的Y轴方向
    context.scaleBy(x: 1, y: -1)
    context.translateBy(x: 0, y: -originalImage.size.height)
    
    // 适配路径到原图尺寸
    let targetRect = CGRect(x: 0, y: 0, width: originalImage.size.width, height: originalImage.size.height)
    let maskedPath = path.resized(to: targetRect)
    
    // 应用evenOdd裁剪规则,和预览保持一致
    context.saveGState()
    context.addPath(maskedPath)
    context.setFillRule(.evenOdd)
    context.clip(using: .evenOdd)
    
    // 绘制原图到裁剪区域内
    originalImage.draw(at: .zero)
    context.restoreGState()
    
    // 获取处理后的图片并保存到相册
    if let maskedImage = UIGraphicsGetImageFromCurrentImageContext() {
        UIImageWriteToSavedPhotosAlbum(maskedImage, nil, nil, nil)
    }
}

额外注意事项

别忘了在Info.plist中添加相册写入权限描述,否则保存时会崩溃:

<key>NSPhotoLibraryAddUsageDescription</key>
<string>需要保存处理后的图片到你的相册</string>

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.30 17:12:33