如何进行颜色序列的插值?

3

我需要逐渐插值或更改一系列颜色,使其从颜色A到颜色B到颜色C到颜色D,然后回到颜色A,这需要基于以毫秒为单位经过的时间,任何帮助都将不胜感激(算法、伪代码将非常有用)。

请注意,我正在使用RGB,它可以是0-255或0.0-1.0范围。

这是我目前拥有的内容,我需要在每个“timePeriod”更改颜色,然后计算经过的时间百分比并更改颜色,这段代码的问题是当它从A到B再到B到C等时会跳跃。

int millisNow = ofGetElapsedTimeMillis();
int millisSinceLastCheck = millisNow - lastTimeCheck;
if ( millisSinceLastCheck > timePeriod ) {
    lastTimeCheck = millisNow;
    millisSinceLastCheck = 0;
    colorsIndex++;
    if ( colorsIndex == colors.size()-1 ) colorsIndex = 0;
    cout << "color indes: " << colorsIndex << endl;
    cout << "color indes: " << colorsIndex + 1 << endl;
}
timeFraction = (float)(millisSinceLastCheck) / (float)(timePeriod);
float p = timeFraction;
colorT.r = colors[colorsIndex].r * p + ( colors[colorsIndex+1].r * ( 1.0 - p ) );
colorT.g = colors[colorsIndex].g * p + ( colors[colorsIndex+1].g * ( 1.0 - p ) );
colorT.b = colors[colorsIndex].b * p + ( colors[colorsIndex+1].b * ( 1.0 - p ) );
colorT.normalize();

提前感谢你的帮助。


所以您希望颜色能够旋转呈现出效果? - Skurmedel
可能是重复问题:https://dev59.com/_UjSa4cB1Zd3GeqPIMNs - Robᵩ
6个回答

11
你的代码基本正确,但是你颠倒了插值的顺序:即你在进行B -> A,然后C -> B,然后D -> C等插值。这会导致在切换颜色时出现不连续性。
你应该将其替换为:
colorT.r = colors[colorsIndex].r * p + ( colors[colorsIndex+1].r * ( 1.0 - p ) );

使用:

colorT.r = colors[colorsIndex].r * (1.0 - p) + ( colors[colorsIndex+1].r * p );

对于其他行也是同样的操作。

此外,正如其他人所说,使用不同于RGB的颜色空间可以提供更好的外观结果。


哎呀,你比我快。+1,这是目前唯一一个真正回答问题而不是标题的答案。 - Anomie
很酷的非常容易的解决方案,我唯一需要做的就是处理从C-D跳转到D-A,但我认为我可以自己弄清楚,非常感谢!我也不确定标题怎么样 :) 英语不是我的母语。 - Ricardo Sanchez
如果你想让颜色变浅,只需将整个值乘以你选择的数值。 - August

9
有两种处理插值颜色的方法。一种方法快而简单(你正在使用的方法),另一种方法略慢但在某些情况下可能看起来更好。
第一种是显而易见的简单方法,即 (x * s) + (y * (1-s)),这是纯线性插值,就像名称所示的那样。然而,在某些颜色对(比如绿色和橙色)上,你会得到一些不好看的颜色(脏褐色)。这是因为你正在lerping每个组件(R、G和B),并且有些组合是不舒服的。如果你只需要最基本的lerp,那么这就是你想要的方法,你的代码大致正确。
如果你想要一个效果更好但略慢的效果,你需要在HSL颜色空间中进行插值。由于色相、饱和度和亮度都被插值了,你得到的颜色就是你期望它们之间的颜色,并且可以避免大多数丑陋的颜色。由于颜色通常以某种轮廓绘制,这种方法是知道的(而基本的RGB lerp则像是在处理三条离散的线)。
要使用HSL插值,您需要转换RGB值,对结果进行插值,然后再转换回去。此页面提供了一些公式,可能对此有所帮助,这个页面有处理它的PHP代码。

1
请注意,HSL并不是一个特别好的选择。HSL和HSB易于可视化,并且往往比直接使用RGB得到更好的结果 - 但仅此而已。Lab*并不完全对应于颜色感知,但它比HSL或HSB更接近。 - Jerry Coffin
@Jerry:我自己对LAB不是很熟悉(虽然会去了解一下),HSL可能不是最好的选择,但它比RGB更高级,而且并不太复杂。这取决于这里需要什么,我想。 - ssube
是的,与直接使用RGB相比,这绝对是一个更高级的步骤。它也是一种相对简单的转换,因此不会引起太多额外的计算量,这对于实时转换非常重要。 - Jerry Coffin

1

你已经做得很好了,但我会简化一下数学部分:

int millisNow = ofGetElapsedTimeMillis();
int millisSinceLastCheck = millisNow % timerPeriod;
int colorsIndex = (millisNow / timerPerod) % (colors.size() - 1);


float p = (float)(millisSinceLastCheck) / (float)(timePeriod);
colorT.r = colors[colorsIndex+1].r * p + ( colors[colorsIndex].r * ( 1.0 - p ) );
colorT.g = colors[colorsIndex+1].g * p + ( colors[colorsIndex].g * ( 1.0 - p ) );
colorT.b = colors[colorsIndex+1].b * p + ( colors[colorsIndex].b * ( 1.0 - p ) );
colorT.normalize();

1

对 R、G 和 B 分量进行插值将产生可工作的代码。唯一的缺点是,即使它们在数学上相等,你生成的步骤不一定会看起来相同。

如果这让你感到困扰,你可以将 RGB 值转换为类似 L*a*b* 的值(它被设计得更接近于人类感知),对这些值进行插值,然后将每个插值后的值转换回 RGB 进行显示。


你能提供一下 Lab* 的参考吗?(RGB 色彩空间与人类感知相差较远。将其正确转换为 HSL 可以改善这种情况,但转换必须尊重人类感知的异常,而这通常并不是这样。) - James Kanze
@James Kanze: Steven Schevell的《颜色科学》非常好,虽然它是关于颜色一般性的,而不是特定于Lab*。一个免费在线可用的是http://www.brucelindbloom.com/(它也涵盖了UP颜色空间,这至少从理论上来说应该是更好的选择)。我应该补充说,布鲁斯有很多对于我们正在讨论的东西可能过于详细了(虽然,公平地说,CIELAB也可能如此...) - Jerry Coffin

0
我们正在一个我目前正在工作的项目中这样做。我们只是独立地处理R、G、B值,并根据它们之间有多少“步骤”从color1过渡到color2。我们有离散值,所以我们采用查找表方法,但你也可以使用浮点数来动态计算RGB值。
如果你还有问题,我可以发布一些Java代码。

-1

将三个组件(RBG)分开,并分别使用经典插值算法进行插值。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接