Как интерполировать значения оттенков в цветовом пространстве HSV? - PullRequest
15 голосов
/ 07 апреля 2010

Я пытаюсь интерполировать два цвета в цветовом пространстве 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;
}

Ответы [ 2 ]

11 голосов
/ 07 апреля 2010

Вам просто нужно выяснить, какой кратчайший путь от начального оттенка до конечного оттенка. Это можно сделать легко, поскольку значения оттенков варьируются от 0 до 255.

Вы можете сначала вычесть более низкий оттенок из более высокого, а затем добавить 256 к более низкому, чтобы еще раз проверить разницу с замененными операндами.

int maxCCW = higherHue - lowerHue;
int maxCW = (lowerHue+256) - higherHue;

Таким образом, вы получите два значения, большее из них решает, следует ли вам идти по часовой стрелке или против часовой стрелки. Затем вам нужно будет найти способ заставить интерполяцию работать по модулю 256 оттенка, поэтому, если вы интерполируете от 246 до 20, если коэффициент равен >= 0.5f, вы должны сбросить оттенок на 0 (так как достигает 256 и hue = hue%256 в любом случае).

На самом деле, если вы не заботитесь о оттенке при интерполяции по 0, а просто применяете оператор по модулю после вычисления нового оттенка, он все равно должен работать.

3 голосов
/ 26 октября 2015

Хотя этот ответ запоздал, принятый неверно, утверждая, что оттенок должен быть в пределах [0, 255]; также можно добиться большей справедливости с помощью более четкого объяснения и кода.

Оттенок - угловое значение в интервале [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>
...