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

如何用Core Image复刻Photoshop的阴影/中间调/高光色彩平衡?

嘿,我完全懂你现在的困扰——用Core Image复刻Photoshop的色彩平衡,尤其是分阴影、中间调、高光单独调色这块,确实没那么直接。你之前试的CIColorMatrix是全局调整,CIHighlightShadowAdjust又只能调明暗没法碰色彩,肯定达不到PS的效果。

其实核心思路是把图像按亮度分成三个区域,分别给每个区域做色彩偏移,再把它们合成回去,这和Photoshop内部的逻辑是一致的。我给你两种可行的方案,你可以根据需求选:


方案一:用现有Core Image滤镜组合实现(无需自定义Shader)

这个方案全靠系统自带滤镜拼出效果,适合快速上手:

步骤1:生成三个区域的蒙版

首先得把阴影、中间调、高光的区域提取出来做蒙版,这样才能只调整对应区域的色彩。这里用亮度阈值划分区域,还加了模糊让过渡更自然(避免生硬的边界):

func createBrightnessMask(from image: CIImage, threshold: CGFloat, isInverted: Bool = false) -> CIImage {
    // 转灰度亮度图
    let grayscaleFilter = CIFilter(name: "CIColorControls")!
    grayscaleFilter.setValue(image, forKey: kCIInputImageKey)
    grayscaleFilter.setValue(0, forKey: kCIInputSaturationKey)
    let grayscale = grayscaleFilter.outputImage!
    
    // 阈值提取蒙版
    let thresholdFilter = CIFilter(name: "CIThresholdAdjust")!
    thresholdFilter.setValue(grayscale, forKey: kCIInputImageKey)
    thresholdFilter.setValue(threshold, forKey: kCIInputThresholdKey)
    var mask = thresholdFilter.outputImage!
    
    // 按需反相蒙版
    if isInverted {
        let invertFilter = CIFilter(name: "CIColorInvert")!
        invertFilter.setValue(mask, forKey: kCIInputImageKey)
        mask = invertFilter.outputImage!
    }
    
    // 模糊蒙版让过渡柔和
    let blurFilter = CIFilter(name: "CIGaussianBlur")!
    blurFilter.setValue(mask, forKey: kCIInputImageKey)
    blurFilter.setValue(2, forKey: kCIInputRadiusKey)
    return blurFilter.outputImage!
}

// 生成三个蒙版:阴影(亮度<0.3)、高光(亮度>0.7)、中间调(剩下的区域)
let inputImage = gammaOutput // 你的输入图像
let shadowMask = createBrightnessMask(from: inputImage, threshold: 0.3)
let highlightMask = createBrightnessMask(from: inputImage, threshold: 0.7, isInverted: true)
let midtonesMask = CIFilter(name: "CIAdditionCompositing")!
midtonesMask.setValue(shadowMask, forKey: kCIInputImageKey)
midtonesMask.setValue(highlightMask, forKey: kCIInputBackgroundImageKey)
let invertedCombined = midtonesMask.outputImage!.applyingFilter("CIColorInvert")
let midtonesMaskFinal = invertedCombined.applyingFilter("CIGaussianBlur", parameters: [kCIInputRadiusKey: 2])

步骤2:给每个区域做色彩平衡调整

PS的色彩平衡是基于互补色滑块(红-青、黄-蓝、品红-绿),我们用CIColorMatrix的inputBiasVector实现色彩偏移,把PS的滑块值(-100到+100)转成合适的偏移范围:

func applyColorBalance(to image: CIImage, redCyan: CGFloat, yellowBlue: CGFloat, magentaGreen: CGFloat) -> CIImage {
    let colorMatrix = CIFilter(name: "CIColorMatrix")!
    colorMatrix.setValue(image, forKey: kCIInputImageKey)
    
    // 转换PS滑块值到Core Image的bias范围(这里用-0.5到+0.5,可根据效果调整比例)
    let redBias = redCyan / 200
    // 品红是减绿,绿是加绿,所以取反
    let greenBias = magentaGreen / -200
    // 黄是减蓝,蓝是加蓝,取反
    let blueBias = yellowBlue / -200
    
    colorMatrix.setValue(CIVector(x: 1, y: 0, z: 0, w: 0), forKey: "inputRVector")
    colorMatrix.setValue(CIVector(x: 0, y: 1, z: 0, w: 0), forKey: "inputGVector")
    colorMatrix.setValue(CIVector(x: 0, y: 0, z: 1, w: 0), forKey: "inputBVector")
    colorMatrix.setValue(CIVector(x: redBias, y: greenBias, z: blueBias, w: 0), forKey: "inputBiasVector")
    
    return colorMatrix.outputImage!
}

// 分别调整三个区域的色彩(参数对应PS滑块数值)
let shadowAdjusted = applyColorBalance(to: inputImage, redCyan: 50, yellowBlue: -30, magentaGreen: 20)
let midtonesAdjusted = applyColorBalance(to: inputImage, redCyan: 20, yellowBlue: 10, magentaGreen: -10)
let highlightAdjusted = applyColorBalance(to: inputImage, redCyan: -20, yellowBlue: 40, magentaGreen: 0)

步骤3:合成三个调整后的图层

用蒙版把每个调整后的区域叠加回原图,保证只有对应区域的色彩被修改:

// 合成调整层到基础图的通用函数
func compositeAdjustedLayer(base: CIImage, adjusted: CIImage, mask: CIImage) -> CIImage {
    let composite = CIFilter(name: "CISourceAtopCompositing")!
    composite.setValue(adjusted, forKey: kCIInputImageKey)
    composite.setValue(mask, forKey: kCIInputMaskImageKey)
    composite.setValue(base, forKey: kCIInputBackgroundImageKey)
    return composite.outputImage!
}

let withShadow = compositeAdjustedLayer(base: inputImage, adjusted: shadowAdjusted, mask: shadowMask)
let withMidtones = compositeAdjustedLayer(base: withShadow, adjusted: midtonesAdjusted, mask: midtonesMaskFinal)
let finalImage = compositeAdjustedLayer(base: withMidtones, adjusted: highlightAdjusted, mask: highlightMask)

方案二:自定义Metal Shader实现(更高效更可控)

如果你的应用对性能要求高,或者想要更平滑的区域过渡,自定义Shader是更好的选择——它能在一次渲染中完成所有逻辑,还能直接实现PS的“保持明度”功能:

步骤1:编写Metal Kernel代码

这个kernel会计算每个像素的亮度,判断属于哪个区域,然后应用对应的色彩偏移,同时保持原图像的明度:

#include <CoreImage/CoreImage.h>
using namespace metal;

kernel void colorBalanceWithRanges(
    sampler inputImage,
    constant float &shadowThreshold,
    constant float &highlightThreshold,
    constant float3 &shadowOffset,
    constant float3 &midtonesOffset,
    constant float3 &highlightOffset,
    destination dest
) {
    float2 coords = dest.coord();
    float4 color = inputImage.sample(coords);
    
    // 用BT.709标准计算亮度(和PS的明度计算一致)
    float luminance = 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b;
    
    float3 offset;
    // 平滑过渡阴影、中间调、高光的偏移
    if (luminance <= shadowThreshold) {
        offset = shadowOffset;
    } else if (luminance >= highlightThreshold) {
        offset = highlightOffset;
    } else {
        float midPoint = (shadowThreshold + highlightThreshold) / 2.0;
        if (luminance < midPoint) {
            float t = smoothstep(shadowThreshold, midPoint, luminance);
            offset = mix(shadowOffset, midtonesOffset, t);
        } else {
            float t = smoothstep(midPoint, highlightThreshold, luminance);
            offset = mix(midtonesOffset, highlightOffset, t);
        }
    }
    
    // 应用色彩偏移
    float4 newColor = color + float4(offset, 0.0);
    
    // 保持明度:把新图像的亮度调整回原亮度
    float newLuminance = 0.2126 * newColor.r + 0.7152 * newColor.g + 0.0722 * newColor.b;
    float luminanceScale = luminance / max(newLuminance, 0.001); // 避免除以0
    newColor.rgb *= luminanceScale;
    
    dest.write(newColor);
}

步骤2:在Swift中注册自定义滤镜

把上面的Metal代码包装成Core Image滤镜,方便调用:

class PSColorBalanceFilter: CIFilter {
    private let kernel: CIKernel?
    
    // 输入参数
    @objc var inputImage: CIImage?
    @objc var shadowThreshold: CGFloat = 0.3 // 阴影亮度阈值
    @objc var highlightThreshold: CGFloat = 0.7 // 高光亮阈值
    @objc var shadowOffset: CIVector = CIVector(x: 0, y: 0, z: 0) // 阴影的RGB偏移(对应PS滑块)
    @objc var midtonesOffset: CIVector = CIVector(x: 0, y: 0, z: 0) // 中间调偏移
    @objc var highlightOffset: CIVector = CIVector(x: 0, y: 0, z: 0) // 高光偏移
    
    override init() {
        let source = """
            // 上面的Metal Kernel代码
            """
        kernel = CIKernel(source: source)
        super.init()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override var outputImage: CIImage? {
        guard let inputImage = inputImage, let kernel = kernel else { return nil }
        let args: [Any] = [
            shadowThreshold,
            highlightThreshold,
            SIMD3<Float>(x: Float(shadowOffset.x), y: Float(shadowOffset.y), z: Float(shadowOffset.z)),
            SIMD3<Float>(x: Float(midtonesOffset.x), y: Float(midtonesOffset.y), z: Float(midtonesOffset.z)),
            SIMD3<Float>(x: Float(highlightOffset.x), y: Float(highlightOffset.y), z: Float(highlightOffset.z))
        ]
        return kernel.apply(
            extent: inputImage.extent,
            roiCallback: { _, rect in rect },
            arguments: args,
            inputImage: inputImage
        )
    }
}

使用自定义滤镜

let colorBalanceFilter = PSColorBalanceFilter()
colorBalanceFilter.inputImage = gammaOutput
// 设置参数,比如阴影加红(0.2)、减蓝(-0.1)
colorBalanceFilter.shadowOffset = CIVector(x: 0.2, y: 0, z: -0.1)
// 中间调加黄(0.1即加绿减蓝)
colorBalanceFilter.midtonesOffset = CIVector(x: 0, y: 0.1, z: -0.1)
// 高光加蓝(0.2)
colorBalanceFilter.highlightOffset = CIVector(x: 0, y: 0, z: 0.2)
let finalImage = colorBalanceFilter.outputImage!

关键说明

  • 蒙版的阈值(0.3和0.7)可以根据需求调整,对应PS色彩平衡里的阴影、中间调、高光范围。
  • 如果要完全匹配PS的“保持明度”功能,方案二的自定义Shader已经实现,方案一可以额外把原图的亮度通道合成回去。
  • 色彩偏移的比例可以根据你的滑块范围调整,比如PS滑块是-100到+100,你可以把偏移值设置为sliderValue / 200,这样范围是-0.5到+0.5,效果更接近PS。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 06:54:19