如何在 HSV 颜色空间中插入色调值?
我试图在 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之间的最短距离,然后在正确的方向(顺时针或逆时针)进行插值。
我应该使用什么公式或算法?
编辑:按照 Jack 的建议,我修改了 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;
}
I'm trying to interpolate between two colours in HSV colour space to produce a smooth colour gradient.
I'm using a linear interpolation, eg:
h = (1 - p) * h1 + p * h2
s = (1 - p) * s1 + p * s2
v = (1 - p) * v1 + p * v2
(where p is the percentage, and h1, h2, s1, s2, v1, v2 are the hue, saturation and value components of the two colours)
This produces a good result for s and v but not for h. As the hue component is an angle, the calculation needs to work out the shortest distance between h1 and h2 and then do the interpolation in the right direction (either clockwise or anti-clockwise).
What formula or algorithm should I use?
EDIT: By following Jack's suggestions I modified my JavaScript gradient function and it works well. For anyone interested, here's what I ended up with:
// 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;
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
您应该只需要找出从起始色调到结束色调的最短路径。这很容易完成,因为色调值的范围是 0 到 255。
您可以先用较高的色调减去较低的色调,然后将较低的色调加上 256,以再次检查交换操作数的差异。
因此,您将获得两个值,较大的值决定您应该顺时针还是逆时针旋转。然后,您必须找到一种方法,使插值对色调的模 256 进行运算,因此,如果您从
246
插值到20
,且系数为>= 0.5f
您应该将色调重置为 0(因为在任何情况下它都会达到 256 并且hue = Hue%256
)。实际上,如果您在对 0 进行插值时不关心色调,而只是在计算新色调后应用模运算符,那么它应该可以工作。
You should just need to find out which is the shortest path from starting hue to ending hue. This can be done easily since hue values range from 0 to 255.
You can first subtract the lower hue from the higher one, then add 256 to the lower one to check again the difference with swapped operands.
So you'll obtain two values, the greater one decides if you should go clockwise or counterclockwise. Then you'll have to find a way to make the interpolation operate on modulo 256 of the hue, so if you are interpolating from
246
to20
if the coefficient is>= 0.5f
you should reset hue to 0 (since it reaches 256 andhue = hue%256
in any case).Actually if you don't care about hue while interpolating over the 0 but just apply modulo operator after calculating the new hue it should work anyway.
尽管这个答案迟到了,但公认的答案是错误的,即色调应该在 [0, 255] 范围内;通过更清晰的解释和代码也可以实现更多正义。
色调是区间 [0, 360) 内的角度值;一个完整的圆,其中 0 = 360。HSV 颜色空间比 RGB 更容易可视化,并且对人类来说更直观。 HSV 形成一个圆柱体,在许多颜色选择器中显示一个切片,而 RGB 实际上是一个立方体,对于颜色选择器来说并不是一个好的选择;大多数使用它的人都必须使用比 HSV 选择器所需的更多的滑块。
插值色调时的要求是选择较小的弧线从一种色调到达另一种色调。因此,给定两个色调值,有四种可能性,以下面的示例角度给出:
让我们将
+
视为逆时针旋转,将−
视为顺时针旋转。如果绝对值的差值超过180,则将其归一化±360,以确保幅度在180以内;这也正确地扭转了方向。现在只需将 delta 除以所需的步数即可获得每次循环迭代的权重,以便在插值期间添加到起始角度。
相关函数摘录完整代码如下:
Although this answer is late, the accepted one is incorrect in stating that hue should be within [0, 255]; also more justice can be done with clearer explanation and code.
Hue is an angular value in the interval [0, 360); a full circle where 0 = 360. The HSV colour space is easier to visualize and is more intuitive to humans then RGB. HSV forms a cylinder from which a slice is shown in many colour pickers, while RGB is really a cube and isn't really a good choice for a colour picker; most ones which do use it would have to employ more sliders than required for a HSV picker.
The requirement when interpolating hue is that the smaller arc is chosen to reach from one hue to another. So given two hue values, there are four possibilities, given with example angles below:
Lets take
+
as counter-clockwise and−
as clockwise rotation. If the difference in absolute value exceeds 180 then normalize it by ± 360 to make sure the magnitude is within 180; this also reverses the direction, rightly.Now just divide
delta
by the required number of steps to get the weight of each loop iteration to add to the start angle during interpolating.Relevant function excerpted from the complete code that follows: