Javascript 如何在HSV颜色空间中插值色调值?

Javascript 如何在HSV颜色空间中插值色调值?,javascript,colors,interpolation,gradient,hsv,Javascript,Colors,Interpolation,Gradient,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之间的最短距离,然后按正确的方向(顺时针或逆时针)进行插值 我应该

我试图在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;
}
//用100步创建从黄色到红色到黑色的渐变
var梯度=hsbGradient(100,[{h:0.14,s:0.5,b:1},{h:0,s:1,b:1},{h:0,s:1,b:0}]);
功能hsbGradient(步骤、颜色){
变量部分=颜色。长度-1;
var梯度=新阵列(步数);
var梯度指数=0;
var partSteps=数学地板(步骤/零件);
var剩余=步骤-(零件步骤*零件);
对于(变量col=0;col=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;
//通过将剩余部分添加到最终零件,确保获得正确的步骤数
如果(col==parts-1)partSteps+=余数;
//为这部分做渐变
对于(var step=0;step
您只需找出从开始色调到结束色调的最短路径。这很容易做到,因为色调值的范围从0到255

您可以先从较高的色调中减去较低的色调,然后将256添加到较低的色调中,以再次检查与交换的操作数之间的差异

int maxCCW = higherHue - lowerHue;
int maxCW = (lowerHue+256) - higherHue;
所以你会得到两个值,较大的一个决定你是顺时针还是逆时针。然后,您必须找到一种方法使插值在色调的模256上运行,因此,如果您从
246
插值到
20
,如果系数
=0.5f
,您应该将色调重置为0(因为它在任何情况下都达到256并且
hue=hue%256


实际上,如果在0上插值时不关心色调,但在计算新色调后仅应用模运算,则无论如何都应该有效。

虽然这个答案很晚,但公认的答案在说明色调应在[0255]范围内时是不正确的;此外,通过更清晰的解释和代码,可以实现更多的公正

色调是间隔中的角度值[0,360);一个0=360的完整圆圈。HSV颜色空间比RGB更容易可视化,对人类更直观。HSV形成一个圆柱体,从中可以在许多颜色选择器中显示一个切片,而RGB实际上是一个立方体,对于颜色选择器来说并不是一个好的选择;大多数使用它的人必须使用比需要更多的滑块d为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);
从以下完整代码中摘录的相关函数:

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;
}
函数插值(h1,h2,步数){
var d=h2-h1;
var delta=(d+((数学绝对值(d)>180)?((d<0)?360:-360):0))/(步长+1.0);
风险值=[];
对于(VARI=1;d&i 180)?((d<0)?360:-360):0))/(步长+1.0);
风险值=[];

对于(var i=1;d&&i这是家庭作业吗?哈哈!不,不是,尽管我也读了那个练习,它确实帮助我表达了这个问题:)实际上,我正在用javascript和SVG绘制图表,我需要一种方法来在任何给定的hsv颜色列表之间生成漂亮的渐变。除了色调插值之外,我的所有功能都正常。谢谢Jack,这是一个真正的帮助。我设法使我的代码正常工作。我将编辑我的问题,以包括我最终得到的代码。请随时提供建议为了改进。需要注意的是,在颜色模型中,我使用的色调值都在0到1之间,所以我没有像你建议的那样使用模。
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;
}