HEX, RGB, HSL i nowsze przestrzenie barw

Jak HEX, RGB, HSL, LCH i OKLCH mają się do siebie, dlaczego paleta zbudowana w HSL brudzi się w tonach środkowych oraz co obcinanie do gamutu i komponowanie z kanałem alfa robią z kolorem, który ostatecznie dostajesz.

Kolor w CSS to trzy albo cztery liczby plus reguła ich odczytu, czyli przestrzeń barw. Palety, które na ekranie wyglądają nierówno, są zwykle stopniowane w przestrzeni, która nie odpowiada temu, jak działa wzrok.

HEX i RGB to te same liczby

#3B82F6 to trzy bajty w systemie szesnastkowym: 3B to 59, 82 to 130, F6 to 246, więc rgb(59, 130, 246) to ten sam kolor w systemie dziesiętnym, a konwerter kolorów przechodzi między nimi bezstratnie.

Zapis skrócony do trzech cyfr powiela każdą cyfrę, zamiast uzupełniać ją zerami, więc #F80 rozwija się do #FF8800, a nie do #F08000; postać skróconą ma tylko 4096 kolorów. #RGBA dokłada w ten sam sposób cyfrę kanału alfa, a #RRGGBBAA wypisuje wszystkie cztery bajty, przy czym bajt alfa biegnie od 0 do 255 (1A to 10%, 80 to 50%, CC to 80%), podczas gdy każdy inny kanał alfa w CSS biegnie od 0 do 1. Nowoczesna składnia przyjmuje też składowe rozdzielone spacjami, z kanałem alfa po ukośniku: rgb(59 130 246 / 50%).

Te liczby nie są ilościami światła. sRGB koduje każdy kanał krzywą przenoszenia o wykładniku mniej więcej 2,2, więc 128 emituje około 21,6% światła emitowanego przez 255, a nie połowę, i każde mieszanie liczone na surowych wartościach dziedziczy tę krzywą.

HSL, HSV i jasność, która jasnością nie jest

HSL przestawia ten sam sześcian RGB w walec: odcień to kąt (0 czerwień, 120 zieleń, 240 błękit), nasycenie to odległość od centralnej osi szarości, a jasność biegnie od 0 przez 50%, czyli czysty odcień, do 100%, czyli bieli. HSV zamienia jasność na wartość, której szczytem jest czysty odcień, a nie biel, więc rozjaśnienia powstają przez obniżanie nasycenia. Żaden z tych modeli nie wie nic o percepcji.

KolorHSLLuminancja względnaJasność w OKLCH
#FFFF00hsl(60 100% 50%)0.9280.97
#0000FFhsl(240 100% 50%)0.0720.45

Oba deklarują jasność 50%; jeden emituje mniej więcej trzynaście razy tyle światła co drugi. Wiersz tokenów, w którym każdy odcień jest przypięty do 50%, czyta się jako mieszaninę różnych ciężarów.

Stopniowanie jasności w HSL przy stałym odcieniu i nasyceniu zawodzi z tego samego powodu, a do tego z jeszcze jednego: nasycenie mierzy się względem miejsca dostępnego przy danej jasności, więc stopień 50% jest najbardziej kolorowy, a wszystko po obu jego stronach blaknie. Kąt odcienia trzyma się przy tym stały, podczas gdy postrzegana chroma się zapada, i właśnie to sprawia, że środkowe stopnie wyglądają na brudne. Nie są one przyciemnionym kolorem bazowym, tylko kolorem bazowym poszarzonym o wartość, której nikt nie wybrał.

Najgorzej widać to na żółci. Obniżanie jasności w HSL prowadzi żółć wprost w oliwkę, ponieważ ciemna żółć przy tym kącie odcienia po prostu jest oliwką, a utrzymanie żółci wymaga obracania odcienia w stronę pomarańczy w miarę schodzenia w dół. Błękit ma odwrotny kłopot: leży tak nisko pod względem luminancji, że cała jego użyteczna zmienność mieszka powyżej 50%.

LCH i OKLCH

LCH to biegunowa postać CIE Lab, zbudowanego na eksperymentach z dopasowywaniem barw: jasność od 0 do 100, chroma (kolorowość, bez ustalonego maksimum) i kąt odcienia. OKLCH ma ten sam kształt, tylko nad przestrzenią Oklab, która koryguje znane przesunięcia odcienia w Lab, najbardziej widoczne przy błękitach dryfujących w fiolet podczas rozjaśniania.

.a { color: oklch(0.62 0.21 260); }        /* L 0 to 1, C about 0 to 0.37, H in degrees */
.b { color: oklch(62% 0.21 260 / 50%); }   /* percentages and alpha both allowed */
.c { color: lch(52% 82 285); }             /* L runs 0 to 100 here */

Jednorodność percepcyjna oznacza, że równa odległość liczbowa jest bliska równej odległości postrzeganej. Rampa w krokach po 0.08 jasności wygląda na równomierną, wiersz odcieni przypiętych do jednej jasności czyta się jako jeden wiersz, a zmiana jasności zostawia odcień tam, gdzie go ustawiłeś. Tej przestrzeni można też użyć do interpolacji, jak w linear-gradient(in oklch, #0000FF, #FFFF00). Jedno zastrzeżenie: jasność w OKLCH jest znacznie bliższa postrzeganej jasności niż w HSL, ale nie jest wzorem luminancji z WCAG, więc pary tekstu i tła nadal przechodzą przez sprawdzanie kontrastu.

Gamut i obcinanie

Gamut to zbiór kolorów, które dana przestrzeń potrafi przedstawić. sRGB jest od dawna ustawieniem domyślnym; Display P3 obejmuje mniej więcej o jedną czwartą więcej widzialnych barw, przy czym prawie cały zysk przypada na głębokie czerwienie i zielenie. Chroma w OKLCH nie ma górnej granicy, więc łatwo jest zapisać kolor, którego żaden ekran nie pokaże.

@supports (color: color(display-p3 1 1 1)) {
  .vivid { background: color(display-p3 0.9 0.1 0.14); }
}

Obcinanie kanał po kanale sprowadza kolor spoza gamutu z powrotem, przypinając każdy kanał do jego granicy niezależnie, co przesuwa odcień i spłaszcza sąsiadujące odcienie do jednej wartości, więc na gradientach pojawiają się pasma, a szczegóły znikają. CSS Color 4 zamiast tego odwzorowuje: utrzymuje jasność i odcień, a obniża chromę w OKLCH, aż kolor się zmieści.

Właśnie dlatego zrzut ekranu z żywym zdjęciem potrafi wyglądać płasko. Zdjęcie niesie profil P3 i wyświetlacz pokazuje je w pełni; eksport przeliczony do sRGB wciska te nasycone piksele w mniejszą objętość, a czerwienie i zielenie mają najdalszą drogę do przebycia.

Komponowanie z kanałem alfa

Kolor półprzezroczysty na tle daje trzeci, nieprzezroczysty kolor, kanał po kanale:

result = alpha * source + (1 - alpha) * backdrop

Czerń przy 50% na bieli daje (127.5, 127.5, 127.5), czyli #808080. Z tego zbiegu okoliczności bierze się przekonanie, że "czerń z kryciem 50% to szarość", a trzyma się ono na bieli i nigdzie indziej: ta sama czerń na #3B82F6 daje mniej więcej #1E417B, czyli ciemny błękit, a na fotografii daje inny kolor w każdym pikselu. Półprzezroczysta czerń jest operatorem przyciemniania, a nie szarością.

Komponowanie działa na wartościach zakodowanych, więc to #808080 ma luminancję względną mniej więcej 0,216, a nie 0,5. Mierz skomponowaną wartość szesnastkową, nigdy kolor zadeklarowany. Kanał alfa też nie sumuje się tak, jak się tego oczekuje: dwie warstwy po 50% dają pokrycie 75%, ponieważ druga działa wyłącznie na tym, co przepuściła pierwsza.

Rozjaśnienia, przyciemnienia, złamania i harmonie

TerminTradycyjny przepisW OKLCH
Rozjaśnieniebaza plus bielpodnieś L, lekko obniż C
Przyciemnieniebaza plus czerńobniż L
Złamaniebaza plus szarośćobniż C przy stałym L

Harmonie to przesunięcia odcienia. Wskazują akcent; nie budują systemu.

HarmoniaPrzesunięciaDobra do
Dopełniająca+180°jednego akcentu wobec dominującego odcienia, choć wibruje, gdy oba są nasycone
Analogiczna±30°spokojnych zestawień, teł, stanów wewnątrz jednej rodziny
Triadyczna+120°, +240°zbiorów kategorialnych, takich jak serie na wykresie

Przesunięcia na kole RGB nie są percepcyjnie równomierne, ponieważ to koło poświęca nieproporcjonalnie długi łuk zieleni, więc traktuj wyliczoną harmonię jak pierwszy szkic i popraw ją okiem. Generator palet budujący rozjaśnienia, przyciemnienia i harmonie z jednej bazy szybko pokazuje ten szkic na ekranie.

Rampa neutralna wymaga własnej decyzji. Czysta szarość, w której czerwień, zieleń i błękit są równe, ustawiona obok kolorowego interfejsu wygląda martwo i lekko brudno. Nadaj neutralnym niewielką chromę, mniej więcej od 0.005 do 0.02 w OKLCH, blisko odcienia marki: ciepłe szarości w okolicach 60° do 90° czyta się jak papier, chłodne szarości w okolicach 250° czyta się technicznie. Wygaszaj tę chromę w stronę najjaśniejszych stopni, żeby tła nie wyglądały na zabarwione.

Zaburzenia widzenia barw

Mniej więcej 1 na 12 mężczyzn i 1 na 200 kobiet ma zaburzenie widzenia barw. Prawie wszystko to typy czerwono-zielone, z ogromną przewagą deuteranomalii; typy żółto-niebieskie, czyli tritan, dotyczą mniej niż 1 na 10 000 osób.

Pomyłki, które mają znaczenie w interfejsach, to czerwień wobec zieleni przy zbliżonej jasności, zieleń wobec brązu i pomarańczy, fiolet wobec błękitu oraz róż wobec szarości. Widzenie protan dodatkowo przyciemnia czerwienie, więc czerwień wybrana dla podkreślenia pilności może się okazać najcichszą rzeczą na ekranie.

Kolor nie może więc nigdy być jedynym nośnikiem znaczenia. Każdemu stanowi towarzyszy etykieta, ikona, kształt albo pozycja, a stany zestawione parami powinny różnić się nie tylko odcieniem, lecz także jasnością, ponieważ czerwień i zieleń o tej samej jasności w OKLCH to najgorsze możliwe zestawienie. Przełączenie zrzutu ekranu na skalę szarości wyłapuje takie kolizje w kilka sekund.