Jak interpolować wartości barwy w przestrzeni barw HSV?

Próbuję interpolować pomiędzy dwoma kolorami w przestrzeni kolorów HSV, aby uzyskać gładki gradient kolorów.

Używam interpolacji liniowej, np:

h = (1 - p) * h1 + p * h2
s = (1 - p) * s1 + p * s2
v = (1 - p) * v1 + p * v2

(gdzie p jest wartością procentową, a h1, h2, s1, S2, v1, v2 są składnikami barwy, nasycenia i wartości obu kolorów)

Daje to dobry wynik dla s I v, ale nie dla h. ponieważ składową barwy jest kąt, obliczenia muszą obliczyć najkrótszą odległość między h1 i h2, a następnie wykonać interpolacja we właściwym kierunku (zgodnie z ruchem wskazówek zegara lub przeciwnie do ruchu wskazówek zegara).

Jakiej formuły lub algorytmu powinienem użyć?


EDIT: podążając za sugestiami Jacka zmodyfikowałem moją funkcję gradientu JavaScript i działa ona dobrze. Dla wszystkich zainteresowanych, oto co znalazłem:

// 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;
}
Author: nick, 2010-04-07

2 answers

Powinieneś tylko dowiedzieć się, która jest najkrótszą ścieżką od początkowego odcienia do końcowego odcienia. Można to zrobić łatwo, ponieważ wartości barwy wahają się od 0 do 255.

Możesz najpierw odjąć niższy od wyższego odcień, a następnie dodać 256 do dolnego, aby ponownie sprawdzić różnicę z zamienionymi operandami.

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

Więc otrzymasz dwie wartości, większa decyduje, czy powinieneś iść zgodnie z ruchem wskazówek zegara czy przeciwnie do ruchu wskazówek zegara. Wtedy będziesz musiał znaleźć sposób, aby interpolacja działała na modulo 256 odcienia, więc jeśli interpolujesz od 246 do 20 jeśli współczynnik wynosi >= 0.5f, powinieneś zresetować odcień do 0 (ponieważ w każdym przypadku osiąga 256 i hue = hue%256).

Właściwie, jeśli nie zależy ci na odcieniu podczas interpolacji nad 0, ale po prostu zastosuj operator modulo po obliczeniu nowej barwy, to i tak powinno działać.

 11
Author: Jack,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-04-07 16:13:02

Chociaż ta odpowiedź jest spóźniona, przyjęta jest błędna, stwierdzając, że barwa powinna być w granicach [0, 255]; również więcej sprawiedliwości można zrobić z jaśniejszym wyjaśnieniem i kodem.

Barwa jest wartością kątową w przedziale [0, 360); Pełne koło, gdzie 0 = 360. Przestrzeń kolorów HSV jest łatwiejsza do wizualizacji i bardziej intuicyjna dla ludzi niż RGB. HSV tworzy cylinder, z którego kawałek jest wyświetlany w wielu próbnikach kolorów, podczas gdy RGB jest naprawdę kostką i nie jest to dobry wybór dla selektor kolorów; większość tych, którzy go używają, musiałaby zatrudnić więcej suwaków niż jest to wymagane dla selektora HSV.

Wymaganiem podczas interpolacji odcienia jest to, aby mniejszy łuk był wybrany tak, aby sięgał z jednego odcienia do drugiego. Tak więc, biorąc pod uwagę dwie wartości barwy, istnieją cztery możliwości, podane z przykładowymi kątami poniżej:

Δ |  ≤ 180  |  > 180
--|---------|---------
+ |  40, 60 | 310, 10
− |  60, 40 | 10, 310

if Δ = 180 then both +/− rotation are valid options

Weźmy + jako obrót przeciwnie do ruchu wskazówek zegara i jako obrót zgodnie z ruchem wskazówek zegara. Jeśli różnica w wartości bezwzględnej przekracza 180, normalizuj ją o ± 360, aby upewnić się, że wielkość jest w granicach 180; to również odwraca kierunek, słusznie.

var d = h2 - h1;
var delta = d + ((Math.abs(d) > 180) ? ((d < 0) ? 360 : -360) : 0);

Teraz po prostu podziel delta przez wymaganą liczbę kroków, aby uzyskać wagę każdej iteracji pętli, aby dodać kąt początkowy podczas interpolacji.

var new_angle = start + (i * delta);

Odpowiednia funkcja]}

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>
 3
Author: legends2k,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-26 06:56:24