semmixkolor i percepcja

Przekształcenie HLS i RGB

HLS i HSL to ten sam model (zob. modele HLS i HSI). Tu algorytm w obie strony, z przykładem i kodem, oraz przypadki, na których implementacje najczęściej się mylą.

RGB na HLS

max = max(R, G, B), min = min(R, G, B), d = max - min L = (max + min) / 2 d = 0 → S = 0, H nieokreślony (szarość) d > 0 → S = d / (1 - |2L - 1|) H: wzór sześciokątny, jak w HSV (zob. RGB na kąt barwy)

Starsze opisy podają nasycenie dwoma wzorami zależnie od L. To ten sam wynik zapisany inaczej:

L ≤ 0,5 → S = d / (max + min) L > 0,5 → S = d / (2 - max - min)

HLS na RGB

C = (1 - |2L - 1|) · S (chroma) X = C · (1 - |(H / 60°) mod 2 - 1|) m = L - C / 2 0° ≤ H < 60° → (C, X, 0) 60° ≤ H < 120° → (X, C, 0) 120° ≤ H < 180° → (0, C, X) 180° ≤ H < 240° → (0, X, C) 240° ≤ H < 300° → (X, 0, C) 300° ≤ H < 360° → (C, 0, X) (R, G, B) = wynik z tabeli + m

Różnica względem HSV jest tylko w dwóch miejscach: C liczy się z L, a nie z V, a przesunięcie m to L - C/2, a nie V - C. Tabela sektorów jest identyczna.

Przykład w obie strony

RGB (200, 80, 40), #c85028:

R = 0,7843 G = 0,3137 B = 0,1569 max = 0,7843 min = 0,1569 d = 0,6275 L = (0,7843 + 0,1569) / 2 = 0,4706 S = 0,6275 / (1 - |0,9412 - 1|) = 0,6275 / 0,9412 = 0,6667 (albo: L ≤ 0,5, więc S = 0,6275 / 0,9412, ten sam wynik) H = 60° · (0,3137 - 0,1569) / 0,6275 = 15° HLS = (15°, 47,1%, 66,7%) z powrotem: C = 0,9412 · 0,6667 = 0,6275 X = 0,6275 · (1 - |0,25 - 1|) = 0,1569 m = 0,4706 - 0,3137 = 0,1569 sektor 0°..60° → (0,6275; 0,1569; 0) + 0,1569 = (0,7843; 0,3137; 0,1569) RGB = (200, 80, 40)

Przypadki brzegowe

Kod

// r, g, b, s, l w zakresie 0..1, h w stopniach
function rgbToHsl(r, g, b) {
  const max = Math.max(r, g, b), min = Math.min(r, g, b), d = max - min;
  const l = (max + min) / 2;
  if (d < 1e-9) return { h: 0, s: 0, l };
  const s = d / (1 - Math.abs(2 * l - 1));
  let h = max === r ? (g - b) / d : max === g ? (b - r) / d + 2 : (r - g) / d + 4;
  h *= 60;
  return { h: h < 0 ? h + 360 : h, s, l };
}

function hslToRgb(h, s, l) {
  h = ((h % 360) + 360) % 360;
  const c = (1 - Math.abs(2 * l - 1)) * s;
  const x = c * (1 - Math.abs(((h / 60) % 2) - 1));
  const m = l - c / 2;
  const [r, g, b] =
    h < 60 ? [c, x, 0] : h < 120 ? [x, c, 0] : h < 180 ? [0, c, x] :
    h < 240 ? [0, x, c] : h < 300 ? [x, 0, c] : [c, 0, x];
  return { r: r + m, g: g + m, b: b + m };
}

Inna, równie częsta wersja hslToRgb, z funkcją hue2rgb, jest na stronie funkcje pomocnicze.