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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 00:55:12