Swift4:如何用滤镜修改黑白图像颜色?优化逐像素遍历慢问题
Great question—逐像素遍历确实是图像处理里的性能大坑,尤其是图像尺寸大的时候,每次调用getPixel和setPixel都会带来额外的方法调用开销,慢得离谱。下面给你几个更高效的实现方案,以及Swift 4里用滤镜快速修改黑白图像颜色的方法:
高效替代逐像素遍历的实现方式
方案1:直接操作CGContext像素缓冲区
这种方式跳过了单个像素的get/set方法,直接操作内存中的像素数据,效率比原代码高很多,适合大多数场景:
import UIKit func convertBlackWhiteImage(_ image: UIImage, darkColor: UIColor, lightColor: UIColor) -> UIImage? { guard let cgImage = image.cgImage else { return nil } let width = cgImage.width let height = cgImage.height let bytesPerPixel = 4 let bytesPerRow = bytesPerPixel * width let bitsPerComponent = 8 // 创建位图上下文 guard let context = CGContext(data: nil, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: CGColorSpaceCreateDeviceRGB(), bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue) else { return nil } // 将原图像绘制到上下文 context.draw(cgImage, in: CGRect(x: 0, y: 0, width: width, height: height)) // 获取像素缓冲区指针 guard let pixelBuffer = context.data else { return nil } let pixels = pixelBuffer.bindMemory(to: UInt32.self, capacity: width * height) // 把目标颜色转换为RGBA格式的UInt32值 let darkRGBA = darkColor.rgbaValue let lightRGBA = lightColor.rgbaValue // 批量处理像素 for i in 0..<(width * height) { let pixel = pixels[i] // 提取红色通道(黑白图RGB通道值一致,取任意一个即可) let red = (pixel >> 16) & 0xFF pixels[i] = red > 200 ? darkRGBA : lightRGBA } // 生成新图像 guard let newCGImage = context.makeImage() else { return nil } return UIImage(cgImage: newCGImage) } // 给UIColor扩展,方便获取RGBA的UInt32值 extension UIColor { var rgbaValue: UInt32 { var red: CGFloat = 0, green: CGFloat = 0, blue: CGFloat = 0, alpha: CGFloat = 0 self.getRed(&red, green: &green, blue: &blue, alpha: &alpha) let r = UInt32(red * 255) << 16 let g = UInt32(green * 255) << 8 let b = UInt32(blue * 255) let a = UInt32(alpha * 255) << 24 return a | r | g | b } }
方案2:用Accelerate框架的vImage实现极致性能
如果处理的是超大尺寸图像(比如4K以上),可以用苹果的Accelerate框架,它会利用CPU的向量指令(比如NEON)做批量处理,速度比普通Swift循环快数倍:
import Accelerate import UIKit func convertBlackWhiteImageWithAccelerate(_ image: UIImage, darkColor: UIColor, lightColor: UIColor) -> UIImage? { guard let cgImage = image.cgImage else { return nil } // 定义vImage的图像格式 var format = vImage_CGImageFormat(bitsPerComponent: 8, bitsPerPixel: 32, colorSpace: nil, bitmapInfo: CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue), version: 0, decode: nil, renderingIntent: .defaultIntent) var sourceBuffer = vImage_Buffer() defer { sourceBuffer.data.deallocate() } // 将CGImage转换为vImage缓冲区 let error = vImageBuffer_InitWithCGImage(&sourceBuffer, &format, nil, cgImage, vImage_Flags(kvImageNoFlags)) guard error == kvImageNoError else { return nil } // 分配目标缓冲区 var destinationBuffer = vImage_Buffer() defer { destinationBuffer.data.deallocate() } guard vImageBuffer_Init(&destinationBuffer, sourceBuffer.height, sourceBuffer.width, format.bitsPerPixel, vImage_Flags(kvImageNoFlags)) == kvImageNoError else { return nil } // 准备目标颜色的组件值 guard let darkComponents = darkColor.cgColor.components, let lightComponents = lightColor.cgColor.components else { return nil } // 创建颜色查找表:每个灰度值对应目标颜色 var lookupTable = [Pixel_8x4](repeating: Pixel_8x4(a: 0, r: 0, g: 0, b: 0), count: 256) for i in 0..<256 { if i > 200 { lookupTable[i] = Pixel_8x4(a: UInt8(darkComponents[3] * 255), r: UInt8(darkComponents[0] * 255), g: UInt8(darkComponents[1] * 255), b: UInt8(darkComponents[2] * 255)) } else { lookupTable[i] = Pixel_8x4(a: UInt8(lightComponents[3] * 255), r: UInt8(lightComponents[0] * 255), g: UInt8(lightComponents[1] * 255), b: UInt8(lightComponents[2] * 255)) } } // 应用查找表,批量转换像素 vImageLookupTable_PlanarToARGB8888(&sourceBuffer, &destinationBuffer, &lookupTable, vImage_Flags(kvImageNoFlags)) // 将vImage缓冲区转换为CGImage guard let resultCGImage = vImageCreateCGImageFromBuffer(&destinationBuffer, &format, nil, nil, vImage_Flags(kvImageNoFlags), nil)?.takeRetainedValue() else { return nil } return UIImage(cgImage: resultCGImage) } // 匹配vImage的ARGB像素结构 struct Pixel_8x4 { var a: UInt8 var r: UInt8 var g: UInt8 var b: UInt8 }
Swift 4中用滤镜修改黑白图像颜色
Core Image框架提供了现成的滤镜,代码简洁且利用GPU加速,性能拉满,适合不需要自定义复杂逻辑的场景:
方法1:用CIFalseColor快速替换明暗颜色
这个滤镜专门用来把图像的暗部和亮部分别替换成指定颜色,完美匹配你的需求:
import CoreImage import UIKit func convertBlackWhiteImageWithFalseColor(_ image: UIImage, darkColor: UIColor, lightColor: UIColor) -> UIImage? { guard let ciImage = CIImage(image: image) else { return nil } // 初始化FalseColor滤镜 let falseColorFilter = CIFilter(name: "CIFalseColor")! falseColorFilter.setValue(ciImage, forKey: kCIInputImageKey) // 替换亮部颜色(对应你原代码中red>200的部分) falseColorFilter.setValue(CIColor(cgColor: darkColor.cgColor), forKey: kCIInputColor0Key) // 替换暗部颜色(对应原代码中red<=200的部分) falseColorFilter.setValue(CIColor(cgColor: lightColor.cgColor), forKey: kCIInputColor1Key) guard let outputCI = falseColorFilter.outputImage else { return nil } // 创建GPU加速的CI上下文 let context = CIContext(options: [.useSoftwareRenderer: false]) guard let outputCG = context.createCGImage(outputCI, from: outputCI.extent) else { return nil } return UIImage(cgImage: outputCG) }
方法2:结合阈值滤镜+颜色映射(适合非纯黑白的原图)
如果你的原图不是严格的黑白图,先通过阈值滤镜把它转成纯黑白,再用颜色映射替换:
import CoreImage import UIKit func convertBlackWhiteImageWithThresholdAndMap(_ image: UIImage, darkColor: UIColor, lightColor: UIColor) -> UIImage? { guard let ciImage = CIImage(image: image) else { return nil } // 第一步:用阈值滤镜把图像转成纯黑白 let thresholdFilter = CIFilter(name: "CIColorThreshold")! thresholdFilter.setValue(ciImage, forKey: kCIInputImageKey) thresholdFilter.setValue(200/255.0, forKey: kCIInputThresholdKey) // 对应原代码的red>200 guard let thresholdedImage = thresholdFilter.outputImage else { return nil } // 第二步:创建渐变映射图,用来替换颜色 let gradient = CGGradient(colorsSpace: CGColorSpaceCreateDeviceRGB(), colors: [lightColor.cgColor, darkColor.cgColor] as CFArray, locations: [0, 1])! let gradientImage = UIGraphicsImageRenderer(size: CGSize(width: 256, height: 1)).image { context in gradient.draw(in: context.cgContext, startPoint: CGPoint(x: 0, y: 0), endPoint: CGPoint(x: 256, y: 0)) } // 第三步:用颜色映射滤镜替换颜色 let colorMapFilter = CIFilter(name: "CIColorMap")! colorMapFilter.setValue(thresholdedImage, forKey: kCIInputImageKey) colorMapFilter.setValue(CIImage(image: gradientImage), forKey: kCIInputGradientImageKey) guard let outputCI = colorMapFilter.outputImage else { return nil } let context = CIContext(options: [.useSoftwareRenderer: false]) guard let outputCG = context.createCGImage(outputCI, from: outputCI.extent) else { return nil } return UIImage(cgImage: outputCG) }
总结
- 追求极致性能(超大图像):选Accelerate的vImage方案;
- 追求代码简洁+高性能:选Core Image滤镜方案;
- 自定义逻辑较多:选CGContext直接操作像素的方案。
内容的提问来源于stack exchange,提问作者Hafiz Shoaib Awan
相关产品推荐
相关产品推荐

