导读:本期聚焦于小伙伴创作的《如何用Core Image在iOS端实现灰度图到彩色渐变的映射与多色色调分离?》,敬请观看详情。把一张普通灰度图映射成带有艺术感的彩色渐变,是iOS图像处理的常见需求。Core Image提供了CIColorCube和自定义滤镜链路,可以精确控制每个亮度区间对应的输出颜色。双色映射只需定义黑到白的线性颜色插值,多色渐变则要构造包含多个控制点的渐变查找表。很多初学者直接操作像素导致性能极差,而利用Core Image的离线查找表能在大图上保持六十帧流畅。本文从查找表原理讲起,给出双色与三色映射的完整实现,并对比使用CIColorMatrix的局限性,帮助你在相册滤镜、实时相机预览中稳定落地渐变映射效果。

在iOS图像开发中,将灰度图像按照亮度映射为彩色渐变,或者把画面压缩成有限的几种色调以实现色调分离,都可以借助Core Image框架完成。Core Image本身并没有一个名为“渐变映射”的独立滤镜,但我们可以通过构建颜色查找表(LUT)并配合CIColorCube滤镜,或者使用多个CIColorMatrix组合来逼近效果。理解亮度到颜色的映射关系,是写出稳定滤镜代码的前提。

如何用Core Image在iOS端实现灰度图到彩色渐变的映射与多色色调分离?

渐变映射的底层原理与查找表构造

渐变映射的本质,是把输入图像的灰度值(或者亮度分量)作为索引,到一张预设的“颜色条”里取出对应的颜色。假设图像某像素的红色、绿色、蓝色分量经过灰度公式计算后得到亮度L(范围0到1),我们忽略原色,直接用L去渐变表中取色:L为0时取起始色,L为1时取结束色,中间按线性或曲线插值。这种方式能让整张图呈现出从冷到暖、从暗到亮的统一色调。

在Core Image里最规范的做法是构造一个立体颜色查找表,也就是CIColorCube所需的立方体数据。立方体边长通常取32或64,代表把0到1的区间等分成若干份。我们将每一份对应的输入RGB(这里输入RGB相等,因为是灰度)映射成目标RGB,写进浮点数组,再包装成NSData交给滤镜。这样GPU在渲染时只是一次纹理采样,性能远高于CPU遍历像素。

如果不想自己算立方体,也可以先用CIColorMonochrome把图变成纯灰度,再用CILinearGradient生成一张渐变图,最后用CIBlendWithMask把两者以亮度为遮罩混合。不过这种混合方式在色调分离时不够精确,且多色控制点多了之后链路复杂,因此生产环境更推荐查找表方案。

双色与多色渐变的代码实现

下面示例展示如何用Swift生成一张三色渐变查找表并应用CIColorCube。我们以黑、紫、橙三色为例,亮度0到0.5从黑到紫,0.5到1从紫到橙。代码中的cubeSize为32,代表每个通道32级。

import CoreImage

func makeGradientCubeData() -> NSData {
    let cubeSize = 32
    let count = cubeSize * cubeSize * cubeSize * 4
    var data = [Float](repeating: 0, count: count)
    let black: (Float, Float, Float) = (0, 0, 0)
    let purple: (Float, Float, Float) = (0.5, 0, 0.5)
    let orange: (Float, Float, Float) = (1.0, 0.5, 0)
    for b in 0..<cubeSize {
        for g in 0..<cubeSize {
            for r in 0..<cubeSize {
                // 输入为灰度,r=g=b,取r作为亮度
                let lum = Float(r) / Float(cubeSize - 1)
                var outR: Float = 0, outG: Float = 0, outB: Float = 0
                if lum < 0.5 {
                    let t = lum / 0.5
                    outR = black.0 + (purple.0 - black.0) * t
                    outG = black.1 + (purple.1 - black.1) * t
                    outB = black.2 + (purple.2 - black.2) * t
                } else {
                    let t = (lum - 0.5) / 0.5
                    outR = purple.0 + (orange.0 - purple.0) * t
                    outG = purple.1 + (orange.1 - purple.1) * t
                    outB = purple.2 + (orange.2 - purple.2) * t
                }
                let offset = (b * cubeSize * cubeSize + g * cubeSize + r) * 4
                data[offset] = outR
                data[offset + 1] = outG
                data[offset + 2] = outB
                data[offset + 3] = 1.0
            }
        }
    }
    return NSData(bytes: data, length: count * MemoryLayout<Float>.size)
}

let cube = CIFilter(name: "CIColorCube")!
cube.setValue(32, forKey: "inputCubeSize")
cube.setValue(makeGradientCubeData(), forKey: "inputCubeData")
cube.setValue(inputImage, forKey: kCIInputImageKey)
let output = cube.outputImage

上述代码先把三色关系写成插值逻辑,再填进立方体。因为输入图像可能是彩色,我们在映射时仅使用了红色通道近似亮度;若希望更准确,应在之前插入CIColorMonochrome或者自己用CIColorMatrix做亮度提取。双色映射只需把中间purple段删掉,直接从black到orange插值即可,代码量减少但结构完全一致。

多色渐变(如五色)则可以把颜色数组化,用循环找到lum所在的区间再做局部插值。需要注意立方体边长越大,内存和初始化时间越长,32通常在视觉和性能间平衡较好。如果要在相机实时预览里用,建议把makeGradientCubeData的结果缓存起来,避免每帧重建。

色调分离与渐变映射的结合及方案对比

色调分离(Posterize)是指把连续亮度切成有限个台阶。我们可以在查找表生成阶段,故意把lum量化成几段,例如只保留0、0.33、0.66、1四档,再对每档映射颜色,这样就同时实现了“多色渐变”和“色调分离”。相比单独使用CIColorPosterize后再叠渐变,这种一步到位的方式不会出现边界色偏,也少一次滤镜调用。

另一种常见思路是用CIColorMatrix把RGB乘以系数并四舍五入,但CIColorMatrix只支持线性变换,无法表达“亮度0.2和0.8映射到不同颜色”的非单调映射,因此面对复杂渐变就无能为力。而CIColorCube因为是查表,可以表达任意曲线甚至离散跳变,灵活性最高。

从工程落地看,若你的App只需要双色且不需要分离,用CILinearGradient加混合也能凑合,但一旦设计稿出现多色或台阶感,尽早切到查找表方案最省心。测试时建议拿一张纯灰度渐变条图像验证,确认暗部不死黑、亮部不过曝,再接到真实业务图上。

Core_Image渐变映射色调分离修改时间:2026-08-13 11:55:03

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。