平滑的颜色过渡算法

Posted

技术标签:

【中文标题】平滑的颜色过渡算法【英文标题】:Smooth color transition algorithm 【发布时间】:2014-03-17 03:24:37 【问题描述】:

我正在寻找一种在两种颜色之间平滑过渡的通用算法。

例如,这张图片取自Wikipedia,显示了从橙色到蓝色的过渡。

当我尝试使用我的代码 (C++) 做同样的事情时,首先想到的是使用 HSV 颜色空间,但会出现令人讨厌的中间颜色。

实现这一目标的好方法是什么?似乎与降低对比度或使用不同的色彩空间有关?

【问题讨论】:

请注意,这种从橙色到蓝色的特殊过渡是一种非常特殊的情况,而不是一般算法的应用。特别是 Planckian Locus - 黑体发射器的可见颜色,因为它从暗红色的光芒加热到炽热。 您是否尝试过在 YUV 颜色空间而不是 HSV 中进行线性插值? 【参考方案1】:

我过去做过很多这样的事情。可以通过许多不同的方式执行平滑,但他们可能在这里执行的方式是简单的线性方法。也就是说,对于每个 R、G 和 B 分量,他们只需计算出连接两点的“y = m*x + b”方程,并以此计算出它们之间的分量。

m[RED]   = (ColorRight[RED]   - ColorLeft[RED])   / PixelsWidthAttemptingToFillIn
m[GREEN] = (ColorRight[GREEN] - ColorLeft[GREEN]) / PixelsWidthAttemptingToFillIn
m[BLUE]  = (ColorRight[BLUE]  - ColorLeft[BLUE])  / PixelsWidthAttemptingToFillIn

b[RED]   = ColorLeft[RED]
b[GREEN] = ColorLeft[GREEN]
b[BLUE]  = ColorLeft[BLUE]

中间的任何新颜色现在都是:

NewCol[pixelXFromLeft][RED]   = m[RED]   * pixelXFromLeft + ColorLeft[RED]    
NewCol[pixelXFromLeft][GREEN] = m[GREEN] * pixelXFromLeft + ColorLeft[GREEN]
NewCol[pixelXFromLeft][BLUE]  = m[BLUE]  * pixelXFromLeft + ColorLeft[BLUE]

有许多数学方法可以创建过渡,我们真正想做的是了解您真正希望看到的过渡。如果您想从上面的图像中看到确切的过渡,则值得查看该图像的颜色值。我及时编写了一个程序来查看这些图像并以图形方式输出值。这是我的程序对上述伪彩色比例的输出。

根据查看图表,它比我上面所说的线性更复杂。蓝色部分看起来大部分是线性的,红色可以模拟为线性,但是绿色看起来有更圆的形状。我们可以对果岭进行数学分析,以更好地理解它的数学函数,并使用它来代替。您可能会发现,在 0 到 ~70 像素之间增加斜率并在 70 像素之后线性减小斜率的线性插值就足够了。

如果您查看屏幕底部,该程序会给出每个颜色分量的一些统计测量值,例如最小值、最大值和平均值,以及读取的图像宽度的像素数。

【讨论】:

谢谢,看来我把事情复杂化了,显然是需要的! @trumpetlicks :只是一个非常简短的评论。有没有可能上面的图像毕竟有线性方法,而是在 CMYK 空间而不是 RGB 中? @phil13131 - 你问这个很有趣,因为我正在为 mac 制作这个分析程序的一个版本,它将能够运行(即预转换)到颜色空间在绘图之前,以便可以在任何颜色空间中进行分析。目标是还能够在图形顶部设置近似函数,并为许多不同语言的颜色 SCALE 生成代码。如果你有 Mac,我会非常愿意分享 :-) 我怀疑线性插值和图中显示的曲线之间的差异是由于伽马校正造成的。如果您的坐标空间是线性的,则线性插值是有意义的。这里的实际算法可能是将您的 sRGB(或伽马校正 RGB)转换为线性 RGB,用这些计算线性插值,然后将插值转换回您的非线性显示色彩空间。【参考方案2】:

HSV 色彩空间不是用于平滑过渡的非常好的色彩空间。这是因为h 值,色调,只是用来在“色轮”周围任意定义不同的颜色。这意味着如果您在方向盘上相距很远的两种颜色之间切换,您将不得不通过一堆其他颜色。一点都不流畅。

使用 RGB(或 CMYK)会更有意义。这些“分量”颜色空间可以更好地定义以实现平滑过渡,因为它们代表了颜色需要的每个“分量”的多少。

每个组件值 R、G 和 B 的线性过渡(请参阅 @trumpetlicks 答案)应该看起来“相当不错”。任何超过“相当好”的东西都需要一个实际的人来调整这些值,因为我们的眼睛如何感知不同颜色组中的颜色值存在差异和不对称性,这些颜色值在 RBG 或 CMYK(或任何标准)中都没有表示.

***图像使用 Photoshop 使用的算法。不幸的是,该算法尚未公开。

【讨论】:

色调绝不是任意的。互补色的色调相差 180 度。这里的问题是橙色到蓝色的过渡是在互补色之间,相隔 180 度,并且保持 SV 恒定(仅改变 H)的路径会产生半个彩虹。 @Msalters 我理解,但是虽然“互补”颜色定义明确,但它们围绕色轮的顺序确实是任意的。有许多不同的方式可以定义***。 嗯,这也不是任意的。从 RGB 空间到 HSV 空间的变换是连续的;相邻的颜色保持相邻。如果色调是任意排序的,情况就不会如此。 @MSalters RBG 颜色空间是三维的,这意味着每种颜色有 6 种“相邻”颜色。色轮只选择其中两种相邻的颜色。有许多不同的方式可以设置色轮,以保持 RGB 到 HSV 是连续的约束。 “任意”这个词可能太强了,但不只有一种颜色排序是有意义的。有很多命令可以工作。传统的色轮只是其中之一。 @DamienBlack:HSV 也是三维的。 RGB 中的任意点与 HSV 中的对应点具有一样多的邻居。【参考方案3】:

对 R、G、B 值进行简单的线性插值即可。

trumpetlicks has shown您使用的图像不是纯线性插值。但我认为插值会给你你正在寻找的效果。下面我展示了一个顶部带有线性插值的图像,底部是您的原始图像。

这是生成它的 (Python) 代码:

for y in range(height/2):
    for x in range(width):
        p = x / float(width - 1)
        r = int((1.0-p) * r1 + p * r2 + 0.5)
        g = int((1.0-p) * g1 + p * g2 + 0.5)
        b = int((1.0-p) * b1 + p * b2 + 0.5)
        pix[x,y] = (r,g,b)

【讨论】:

有趣 - 取决于 OP 真正在寻找什么。原始比例(图像)似乎确实提供了更高的对比度。 不是真正的主题,但是在为给定的 x 计算颜色时执行 y 循环不是更有意义吗?循环数量相等,但计算量更少(我知道,小图像的微优化) @engineercoding 对于像这样的小例子,这真的没关系。即使它确实很重要,这也不是一个简单的决定 - 图像通常以较低的顺序排列 x,从而更有效地访问我的示例代码显示的方式。现代处理器可以在缓存未命中所需的时间内进行大量计算。 你能解释一下代码吗?什么是r1r2 ..etc @NullByte08 r1,g1,b1 是左侧的 RGB 值,r2,g2,b2 是右侧的 RGB。如果您查找“线性插值”意味着其余的应该是有意义的。【参考方案4】:

在我看来,使用 RGB 值创建渐变会更容易。您应该首先根据渐变的宽度计算每个值的颜色变化。需要对 R、G 和 B 值执行以下伪代码。

if (redValue1 == redValue2) 
    redDifference = 0
 else 
    redDifference = absoluteValue(redValue1 - redValue2) / widthOfGradient


if (redValue1 > redValue2) 
    redDifference *= -1

然后您可以使用这些值渲染每个像素,如下所示:

for (int i = 0; i < widthOfGradient; i++) 
    int r = round(redValue1 + (i * redDifference))
    // ...repeat for green and blue

    drawLine(i, r, g, b)

我知道您指定您使用的是 C++,但我创建了一个 JSFiddle,以使用您的第一个渐变为例:http://jsfiddle.net/eumf7/

【讨论】:

【参考方案5】:

我一直在研究这个以根据调色板构建algorithm that takes a grayscale image as input and colorises it artificially:

■■■■灰度输入■■■■输出■■■■■■■■■■■■■■■■

与许多其他解决方案一样,该算法使用线性插值来实现颜色之间的过渡。在您的示例中,应使用以下参数调用 smooth_color_transition()

QImage input("gradient.jpg");

QVector<QColor> colors;
colors.push_back(QColor(242, 177, 103)); // orange
colors.push_back(QColor(124, 162, 248)); // blue-ish

QImage output = smooth_color_transition(input, colors);    
output.save("output.jpg");

原始图像 VS 输出的比较如下:

(输出)

(原创)

可以在输出中观察到的视觉伪影已经存在于输入中(灰度)。输入图像在调整为 189x51 时出现这些伪像。

这是另一个使用更复杂的调色板创建的示例:

■■■■灰度输入■■■■输出■■■■■■■■■■■■■■■■

【讨论】:

以上是关于平滑的颜色过渡算法的主要内容,如果未能解决你的问题,请参考以下文章

在 PHP 中生成 RGB 渐变颜色的算法

如何平滑 CSS 渐变过渡?

为啥 ViewPager 向左滚动时颜色过渡动画错误?

VueJs - 使用过渡更改 div 颜色

CSS3单向过渡?

在具有多个部分的 tableView 中滚动时使用平滑动画更改视图背景渐变颜色