iOS中CILightenBlendMode为何改变亮色?双色调效果异常排查
问题分析与解决方案
问题出在哪?
你使用的CILightenBlendMode是逐通道取最大值的混合模式,它不会只针对黑色生效。你的目标紫色(#290a59)蓝色通道值很高,而经过Multiply后的黄色背景蓝色通道值极低,混合时蓝色通道会被紫色的高值覆盖,导致原本的黄色偏色。
举个数值例子:
- 黄色#efa403的RGB为(239,164,3),蓝色通道仅为3
- 紫色#290a59的RGB为(41,10,89),蓝色通道为89
Lighten混合时会取通道最大值,所以黄色的蓝色部分被替换,颜色自然变了。
另外你的流程逻辑也不符合双色调的标准逻辑:双色调应该是暗部绑定深色、亮部绑定浅色,而你先给灰度图叠加黄色,再用Lighten加紫,会让所有比紫色亮的区域都被影响,完全偏离预期。
正确的双色调实现方式
方法一:用CIColorMap精准映射(最推荐)
CIColorMap可以直接将灰度图的黑-白范围映射为你指定的两种颜色,完美实现双色调效果:
import CoreImage func applyDuotoneEffect(to image: UIImage) -> UIImage? { guard let ciImage = CIImage(image: image) else { return nil } let context = CIContext(options: nil) // 转灰度(用CIColorMonochrome比CIPhotoEffectMono更可控) let grayscaleFilter = CIFilter(name: "CIColorMonochrome", parameters: [ kCIInputImageKey: ciImage, kCIInputColorKey: CIColor(white: 1, alpha: 1), kCIInputIntensityKey: 1.0 ]) guard let grayscaleImage = grayscaleFilter?.outputImage else { return nil } // 定义双色调颜色 let darkColor = CIColor(cgColor: UIColor(hex: "290a59")?.cgColor ?? UIColor.black.cgColor) let lightColor = CIColor(cgColor: UIColor(hex: "efa403")?.cgColor ?? UIColor.red.cgColor) // 生成1px宽的黑白渐变映射底图 let gradientFilter = CIFilter(name: "CILinearGradient", parameters: [ kCIInputPoint0Key: CIVector(x: 0, y: 0), kCIInputPoint1Key: CIVector(x: 1, y: 0), kCIInputColor0Key: CIColor(white: 0, alpha: 1), kCIInputColor1Key: CIColor(white: 1, alpha: 1) ]) guard let gradientImage = gradientFilter?.outputImage?.cropped(to: CGRect(x: 0, y: 0, width: 256, height: 1)) else { return nil } // 将渐变底图替换为我们的双色调颜色 let colorMappedGradient = gradientImage .applyingFilter("CIColorMonochrome", parameters: [ kCIInputColorKey: darkColor, kCIInputIntensityKey: 1.0 ]) .applyingFilter("CILightenBlendMode", parameters: [ kCIInputBackgroundImageKey: gradientImage.applyingFilter("CIColorMonochrome", parameters: [ kCIInputColorKey: lightColor, kCIInputIntensityKey: 1.0 ]) ]) // 应用颜色映射到灰度图 let colorMapFilter = CIFilter(name: "CIColorMap", parameters: [ kCIInputImageKey: grayscaleImage, kCIInputGradientImageKey: colorMappedGradient ]) guard let outputImage = colorMapFilter?.outputImage, let cgImage = context.createCGImage(outputImage, from: outputImage.extent) else { return nil } return UIImage(cgImage: cgImage) } // 简化的UIColor Hex扩展 extension UIColor { convenience init?(hex: String) { var cString = hex.trimmingCharacters(in: .whitespacesAndNewlines).uppercased() if cString.hasPrefix("#") { cString.remove(at: cString.startIndex) } guard cString.count == 6 else { return nil } var rgbValue: UInt64 = 0 Scanner(string: cString).scanHexInt64(&rgbValue) self.init( red: CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0, green: CGFloat((rgbValue & 0x00FF00) >> 8) / 255.0, blue: CGFloat(rgbValue & 0x0000FF) / 255.0, alpha: 1.0 ) } }
方法二:用Multiply+Screen组合实现
通过两种混合模式分别控制暗部和亮部,逻辑更直观:
func applyDuotoneSimple(to image: UIImage) -> UIImage? { guard let ciImage = CIImage(image: image) else { return nil } let context = CIContext(options: nil) // 转灰度 let grayscale = CIFilter(name: "CIColorMonochrome", parameters: [ kCIInputImageKey: ciImage, kCIInputColorKey: CIColor(white: 1, alpha: 1), kCIInputIntensityKey: 1.0 ])?.outputImage ?? ciImage // 暗部应用紫色:Multiply混合只影响暗部,亮部几乎不变 let darkColor = CIImage(color: CIColor(cgColor: UIColor(hex: "290a59")!.cgColor)).cropped(to: grayscale.extent) let darkApplied = CIFilter(name: "CIMultiplyBlendMode", parameters: [ kCIInputImageKey: grayscale, kCIInputBackgroundImageKey: darkColor ])?.outputImage ?? grayscale // 亮部应用黄色:Screen混合只影响亮部,暗部几乎不变 let lightColor = CIImage(color: CIColor(cgColor: UIColor(hex: "efa403")!.cgColor)).cropped(to: grayscale.extent) let lightApplied = CIFilter(name: "CIScreenBlendMode", parameters: [ kCIInputImageKey: darkApplied, kCIInputBackgroundImageKey: lightColor ])?.outputImage ?? darkApplied guard let cgImage = context.createCGImage(lightApplied, from: lightApplied.extent) else { return nil } return UIImage(cgImage: cgImage) }
为什么这两种方法可行?
CIColorMap直接将灰度值(0=黑,1=白)映射到你指定的颜色区间,完全符合双色调的需求,不会出现混合模式的误伤。- 第二种方法中,
Multiply仅让暗部染上深色,Screen仅让亮部染上浅色,两者组合正好实现暗部紫、亮部黄的预期效果。
内容的提问来源于stack exchange,提问作者sudoExclamationExclamation
相关产品推荐
相关产品推荐

