Załóżmy, że pracujesz z kolorami RGB: każdy kolor jest reprezentowany z trzema intensywnościami lub jasnościami. Masz do wyboru „linear RGB” i „sRGB”. Na razie uprościmy rzeczy, ignorując trzy różne intensywności i założymy, że masz tylko jedną intensywność: to znaczy, że masz do czynienia tylko z odcieniami szarości.
W liniowej przestrzeni kolorów związek między przechowywanymi liczbami a ich intensywnością jest liniowy. W praktyce oznacza to, że jeśli podwoisz liczbę, podwoisz intensywność (jasność szarości). Jeśli chcesz dodać dwie intensywności razem (ponieważ obliczasz intensywność na podstawie udziału dwóch źródeł światła lub ponieważ dodajesz przezroczysty obiekt na nieprzezroczystym obiekcie), możesz to zrobić, po prostu dodając dwie liczby razem. Jeśli wykonujesz jakiekolwiek mieszanie 2D lub cieniowanie 3D lub prawie dowolne przetwarzanie obrazu, chcesz, aby twoje intensywności były w liniowej przestrzeni kolorów, więc możesz po prostu dodawać, odejmować, mnożyć i dzielić liczby, aby mieć taki sam wpływ na intensywność. Większość algorytmów przetwarzania i renderowania kolorów daje poprawne wyniki tylko przy liniowym RGB, chyba że dodasz dodatkowe wagi do wszystkiego.
Brzmi to bardzo prosto, ale jest problem. Wrażliwość ludzkiego oka na światło jest mniejsza przy niskiej intensywności niż przy wysokiej intensywności. To znaczy, jeśli sporządzisz listę wszystkich intensywności, które możesz rozróżnić, będzie więcej ciemnych niż jasnych. Innymi słowy, ciemne odcienie szarości odróżniają się lepiej niż w przypadku jasnych odcieni szarości. W szczególności, jeśli używasz 8 bitów do reprezentowania swojej intensywności i robisz to w liniowej przestrzeni kolorów, otrzymasz zbyt wiele jasnych odcieni, a za mało ciemnych. W ciemnych obszarach pojawiają się pasy, podczas gdy w jasnych obszarach marnujesz kawałki na różne odcienie prawie bieli, których użytkownik nie może odróżnić.
Aby uniknąć tego problemu i jak najlepiej wykorzystać te 8 bitów, zwykle używamy sRGB . Standard sRGB określa krzywą, której należy użyć, aby kolory były nieliniowe. Krzywa jest płytsza na dole, więc możesz mieć więcej ciemnych szarości i bardziej stromą na górze, dzięki czemu masz mniej jasnych szarości. Jeśli podwoisz liczbę, zwiększysz intensywność ponad dwukrotnie. Oznacza to, że jeśli dodasz razem kolory sRGB, otrzymasz wynik jaśniejszy niż powinien. Obecnie większość monitorów interpretuje kolory wejściowe jako sRGB. Tak więc, kiedy umieszczasz kolor na ekranie lub przechowujesz go w teksturze 8-bitowej na kanał, zapisz go jako sRGB , aby jak najlepiej wykorzystać te 8 bitów.
Zauważysz, że mamy teraz problem: chcemy, aby nasze kolory były przetwarzane w przestrzeni liniowej, ale przechowywane w sRGB. Oznacza to, że w końcu wykonujesz konwersję sRGB do liniowej przy odczycie i konwersję do sRGB przy zapisie. Jak już powiedzieliśmy, liniowe 8-bitowe intensywności nie mają wystarczającej ilości ciemności, spowodowałoby to problemy, więc jest jeszcze jedna praktyczna zasada: nie używaj 8-bitowych liniowych kolorów, jeśli możesz tego uniknąć. Powszechnie przyjmuje się zasadę, że 8-bitowe kolory to zawsze sRGB, więc konwersję sRGB na liniową przeprowadza się w tym samym czasie, gdy zwiększa się intensywność z 8 do 16 bitów lub z liczb całkowitych na zmiennoprzecinkowe; podobnie, po zakończeniu przetwarzania zmiennoprzecinkowego zawężasz do 8 bitów w tym samym czasie, gdy konwertujesz do sRGB. Jeśli będziesz przestrzegać tych zasad,
Kiedy czytasz obraz sRGB i chcesz uzyskać liniowe intensywności, zastosuj następującą formułę do każdej intensywności:
float s = read_channel();
float linear;
if (s <= 0.04045) linear = s / 12.92;
else linear = pow((s + 0.055) / 1.055, 2.4);
W drugą stronę, jeśli chcesz zapisać obraz jako sRGB, zastosuj następującą formułę do każdego natężenia liniowego:
float linear = do_processing();
float s;
if (linear <= 0.0031308) s = linear * 12.92;
else s = 1.055 * pow(linear, 1.0/2.4) - 0.055; ( Edited: The previous version is -0.55 )
W obu przypadkach wartość zmiennoprzecinkowa wynosi od 0 do 1, więc jeśli czytasz 8-bitowe liczby całkowite, które chcesz najpierw podzielić przez 255, a jeśli piszesz 8-bitowe liczby całkowite, które chcesz pomnożyć przez 255 ostatni, w taki sam sposób, jak zwykle. To wszystko, co musisz wiedzieć, aby pracować z sRGB.
Do tej pory miałem do czynienia tylko z jedną intensywnością, ale są sprytniejsze rzeczy do zrobienia z kolorami. Ludzkie oko lepiej rozróżnia różne jasności niż różne odcienie (technicznie rzecz biorąc, ma lepszą rozdzielczość luminancji niż chrominancja), więc możesz jeszcze lepiej wykorzystać swoje 24 bity, przechowując jasność oddzielnie od odcienia. To właśnie próbują robić reprezentacje YUV, YCrCb itp. Kanał Y to ogólna jasność koloru i wykorzystuje więcej bitów (lub ma większą rozdzielczość przestrzenną) niż pozostałe dwa kanały. W ten sposób nie musisz (zawsze) stosować krzywej, tak jak w przypadku intensywności RGB. YUV to liniowa przestrzeń kolorów, więc jeśli podwoisz liczbę w kanale Y, podwoisz jasność koloru, ale nie możesz dodawać ani mnożyć kolorów YUV razem, tak jak w przypadku kolorów RGB, więc
Myślę, że to odpowiada na twoje pytanie, więc zakończę krótką notatką historyczną. Przed sRGB stare CRT miały wbudowaną nieliniowość. Jeśli podwoisz napięcie dla piksela, zwiększysz intensywność ponad dwukrotnie. O ile więcej było różne dla każdego monitora, a ten parametr nazywano gamma . To zachowanie było przydatne, ponieważ oznaczało, że możesz uzyskać więcej ciemności niż świateł, ale oznaczało również, że nie możesz powiedzieć, jak jasne będą twoje kolory na CRT użytkownika, chyba że najpierw go skalibrowałeś. Korekcja gammaoznacza przekształcenie kolorów, od których zaczynasz (prawdopodobnie liniowe) i przekształcenie ich dla gamma CRT użytkownika. OpenGL pochodzi z tej epoki, dlatego jego zachowanie w sRGB jest czasami trochę zagmatwane. Ale dostawcy GPU mają teraz tendencję do pracy z konwencją, którą opisałem powyżej: kiedy przechowujesz 8-bitową intensywność w teksturze lub buforze ramki, jest to sRGB, a kiedy przetwarzasz kolory, jest to liniowe. Na przykład w OpenGL ES 3.0, każdy bufor ramki i tekstura mają „flagę sRGB”, którą można włączyć, aby umożliwić automatyczną konwersję podczas czytania i pisania. Nie musisz w ogóle jawnie wykonywać konwersji sRGB ani korekcji gamma.