如何在Swift(UIKit)中实现UIImage的反向遮罩效果?
实现自定义UIImage像素混合的最佳方案
核心思路
你的需求本质是以overhead的透明度作为遮罩:当overhead像素的alpha值≠0时,最终像素设为透明;当alpha=0时,保留base对应位置的像素。结合UIGraphicsImageRenderer在生成base的流程中直接处理,是最高效且符合你需求的实现方式。
方案一:在UIGraphicsImageRenderer中直接生成结果(推荐)
该方案无需单独保存base图像,直接在绘制base的上下文内完成遮罩逻辑,步骤如下:
- 先执行base的绘制逻辑;
- 生成overhead的反向遮罩(将overhead非透明区域转为遮罩范围);
- 通过混合模式将遮罩应用到base图像上,直接导出结果。
代码示例:
func generateResultImage(baseDrawingBlock: @escaping (CGContext) -> Void, overhead: UIImage) -> UIImage? { guard let _ = overhead.cgImage else { return nil } let size = overhead.size let renderer = UIGraphicsImageRenderer(size: size) return renderer.image { context in // 1. 执行base的绘制逻辑 baseDrawingBlock(context.cgContext) // 2. 生成反向遮罩:overhead非透明区域会"挖空"白色背景,形成遮罩 let maskImage = UIGraphicsImageRenderer(size: size).image { maskCtx in UIColor.white.setFill() maskCtx.fill(CGRect(origin: .zero, size: size)) overhead.draw(at: .zero, blendMode: .destinationOut, alpha: 1.0) } // 3. 应用遮罩:只保留base中对应overhead全透明的区域 maskImage.draw(at: .zero, blendMode: .destinationIn, alpha: 1.0) } }
调用示例(假设base是通过绘制图形生成):
let overheadImage = UIImage(named: "overhead")! let resultImage = generateResultImage(baseDrawingBlock: { context in // 这里编写base的绘制逻辑,比如: UIColor.systemBlue.setFill() context.fill(CGRect(x: 0, y: 0, width: 300, height: 300)) // 其他绘制操作... }, overhead: overheadImage)
方案二:直接操作像素数据(适合复杂自定义逻辑)
如果需要更精细的像素控制,可以直接读取两张图的像素缓冲区,逐个判断alpha值生成结果:
func generateResultImage(base: UIImage, overhead: UIImage) -> UIImage? { guard let baseCG = base.cgImage, let overheadCG = overhead.cgImage, base.size == overhead.size else { return nil } let width = baseCG.width let height = baseCG.height let bytesPerPixel = 4 let bytesPerRow = bytesPerPixel * width var pixelData = [UInt8](repeating: 0, count: bytesPerRow * height) // 绘制base到像素缓冲区 guard let context = CGContext(data: &pixelData, width: width, height: height, bitsPerComponent: 8, bytesPerRow: bytesPerRow, space: CGColorSpaceCreateDeviceRGB(), bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue) else { return nil } context.draw(baseCG, in: CGRect(x: 0, y: 0, width: width, height: height)) // 读取overhead的像素数据 var overheadPixelData = [UInt8](repeating: 0, count: bytesPerRow * height) let overheadCtx = CGContext(data: &overheadPixelData, width: width, height: height, bitsPerComponent: 8, bytesPerRow: bytesPerRow, space: CGColorSpaceCreateDeviceRGB(), bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue) overheadCtx?.draw(overheadCG, in: CGRect(x: 0, y: 0, width: width, height: height)) // 逐个像素处理:overhead非透明时将结果设为全透明 for i in stride(from: 0, to: pixelData.count, by: bytesPerPixel) { let overheadAlpha = overheadPixelData[i + 3] if overheadAlpha != 0 { pixelData[i...i+3] = [0, 0, 0, 0] } } guard let resultCG = context.makeImage() else { return nil } return UIImage(cgImage: resultCG) }
方案对比
- 方案一:依赖Core Graphics混合模式,代码简洁高效,完美支持在生成base的流程中直接处理,无需额外存储base,适合绝大多数场景。
- 方案二:直接操作像素,灵活性拉满,但代码繁琐、性能略低,仅适合需要对像素做额外自定义处理的场景。
内容的提问来源于stack exchange,提问作者Afinainflowers
相关产品推荐
相关产品推荐

