我正在尝试在HSV颜色空间中的两种颜色之间进行插值,以产生平滑的颜色渐变。
我正在使用线性插值,例如:
h = (1 - p) * h1 + p * h2
s = (1 - p) * s1 + p * s2
v = (1 - p) * v1 + p * v2
(其中p是百分比,h1,h2,s1,s2,v1,v2是两种颜色的色调,饱和度和值分量)
这对s和v产生了良好的结果,但对于h则没有。由于色调分量是一个角度,计算需要计算出h1和h2之间的最短距离,然后沿正确的方向(顺时针或逆时针)进行插值。
我应该使用哪种公式或算法?
编辑:按照杰克的建议,我修改了我的JavaScript渐变功能,效果很好。对于任何有兴趣的人,这是我最终的结果:
// create gradient from yellow to red to black with 100 steps
var gradient = hsbGradient(100, [{h:0.14, s:0.5, b:1}, {h:0, s:1, b:1}, {h:0, s:1, b:0}]);
function hsbGradient(steps, colours) {
var parts = colours.length - 1;
var gradient = new Array(steps);
var gradientIndex = 0;
var partSteps = Math.floor(steps / parts);
var remainder = steps - (partSteps * parts);
for (var col = 0; col < parts; col++) {
// get colours
var c1 = colours[col],
c2 = colours[col + 1];
// determine clockwise and counter-clockwise distance between hues
var distCCW = (c1.h >= c2.h) ? c1.h - c2.h : 1 + c1.h - c2.h;
distCW = (c1.h >= c2.h) ? 1 + c2.h - c1.h : c2.h - c1.h;
// ensure we get the right number of steps by adding remainder to final part
if (col == parts - 1) partSteps += remainder;
// make gradient for this part
for (var step = 0; step < partSteps; step ++) {
var p = step / partSteps;
// interpolate h, s, b
var h = (distCW <= distCCW) ? c1.h + (distCW * p) : c1.h - (distCCW * p);
if (h < 0) h = 1 + h;
if (h > 1) h = h - 1;
var s = (1 - p) * c1.s + p * c2.s;
var b = (1 - p) * c1.b + p * c2.b;
// add to gradient array
gradient[gradientIndex] = {h:h, s:s, b:b};
gradientIndex ++;
}
}
return gradient;
}
答案 0 :(得分:11)
你应该只需要找出从开始色调到结束色调的最短路径。这可以很容易地完成,因为色调值的范围是0到255.
您可以先从较高的色调中减去较低的色调,然后在较低的色调中加256,再次检查交换操作数的差异。
int maxCCW = higherHue - lowerHue;
int maxCW = (lowerHue+256) - higherHue;
所以你将获得两个值,较大的一个决定你应该顺时针还是逆时针。然后你必须找到一种方法使插值在色调模256上运行,所以如果你从246
插值到20
,如果系数是>= 0.5f
,你应该重置色调为0(因为它在任何情况下达到256和hue = hue%256
)。
实际上,如果你在0上插值时不关心色调,但只是在计算新色调后应用模运算符它应该可以工作。
答案 1 :(得分:3)
虽然这个答案很晚,但是在声明色调应该在[0,255]之内时,接受的答案是错误的;通过更清晰的解释和代码,也可以做更多的正义。
Hue是区间[0,360]中的角度值;一个完整的圆圈,其中0 = 360.HSV颜色空间更容易可视化,并且对于人类而言比RGB更直观。 HSV形成一个圆柱体,在这个圆柱体中,切片显示在许多颜色选择器中,而RGB实际上是一个立方体,对于颜色选择器来说不是一个好的选择;大多数使用它的人都必须使用比HSV选择器所需更多的滑块。
插入色调时的要求是选择较小的弧从一个色调到另一个色调。因此,给定两个色调值,有四种可能性,给出以下示例角度:
Δ | ≤ 180 | > 180
--|---------|---------
+ | 40, 60 | 310, 10
− | 60, 40 | 10, 310
if Δ = 180 then both +/− rotation are valid options
让我们以逆时针方向+
和顺时针方向转动−
。如果绝对值的差值超过180,则将其标准化±360,以确保幅度在180以内;这也正确地扭转了方向。
var d = h2 - h1;
var delta = d + ((Math.abs(d) > 180) ? ((d < 0) ? 360 : -360) : 0);
现在只需将delta
除以所需的步数,即可在插值过程中将每个循环迭代的权重加到起始角度。
var new_angle = start + (i * delta);
从以下完整代码摘录的相关功能:
function interpolate(h1, h2, steps) {
var d = h2 - h1;
var delta = (d + ((Math.abs(d) > 180) ? ((d < 0) ? 360 : -360) : 0)) / (steps + 1.0);
var turns = [];
for (var i = 1; d && i <= steps; ++i)
turns.push(((h1 + (delta * i)) + 360) % 360);
return turns;
}
"use strict";
function interpolate(h1, h2, steps) {
var d = h2 - h1;
var delta = (d + ((Math.abs(d) > 180) ? ((d < 0) ? 360 : -360) : 0)) / (steps + 1.0);
var turns = [];
for (var i = 1; d && i <= steps; ++i)
turns.push(((h1 + (delta * i)) + 360) % 360);
return turns;
}
function get_results(h1, h2, steps) {
h1 = norm_angle(h1);
h2 = norm_angle(h2);
var r = "Start: " + h1 + "<br />";
var turns = interpolate(h1, h2, steps);
r += turns.length ? "Turn: " : "";
r += turns.join("<br />Turn: ");
r += (turns.length ? "<br />" : "") + "Stop: " + h2;
return r;
}
function run() {
var h1 = get_angle(document.getElementById('h1').value);
var h2 = get_angle(document.getElementById('h2').value);
var steps = get_num(document.getElementById('steps').value);
var result = get_results(h1, h2, steps);
document.getElementById('res').innerHTML = result;
}
function get_num(s) {
var n = parseFloat(s);
return (isNaN(n) || !isFinite(n)) ? 0 : n;
}
function get_angle(s) {
return get_num(s) % 360;
}
function norm_angle(a) {
a %= 360;
a += (a < 0) ? 360 : 0;
return a;
}
<h1 id="title">Hue Interpolation</h1>
Angle 1
<input type="text" id="h1" />
<br />Angle 2
<input type="text" id="h2" />
<br />
<br />Intermediate steps
<input type="text" id="steps" value="5" />
<br />
<br/>
<input type="submit" value="Run" onclick="run()" />
<p id="res"></p>