-
Notifications
You must be signed in to change notification settings - Fork 7
Expand file tree
/
Copy pathcolorgen.html
More file actions
2389 lines (1954 loc) · 171 KB
/
colorgen.html
File metadata and controls
2389 lines (1954 loc) · 171 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE html><html xmlns:dc="http://purl.org/dc/terms/" xmlns:og="http://ogp.me/ns#" ><head><meta http-equiv=Content-Type content="text/html; charset=utf-8"><title>Color Topics for Programmers</title><meta name="citation_pdf_url" content="https://peteroupc.github.io/colorgen.pdf"><meta name="citation_url" content="https://peteroupc.github.io/colorgen.html"><meta name="citation_title" content="Color Topics for Programmers"><meta name="dc.date" content="2026-03-16"><meta name="citation_date" content="2026/03/16"><meta name="citation_publication_date" content="2026/03/16"><meta name="citation_online_date" content="2026/03/16"><meta name="og:title" content="Color Topics for Programmers"><meta name="og:type" content="article"><meta name="og:url" content="https://peteroupc.github.io/colorgen.html"><meta name="og:site_name" content="peteroupc.github.io"><meta name="dc.format" content="text/html"><meta name="dc.language" content="en"><meta name="title" content="Color Topics for Programmers"><meta name="dc.title" content="Color Topics for Programmers"><meta name="twitter:title" content="Color Topics for Programmers"><meta name="dc.creator" content="Peter Occil"/><meta name="author" content="Peter Occil"/><meta name="citation_author" content="Peter Occil"/><meta name="viewport" content="width=device-width"><link rel=stylesheet type="text/css" href="/style.css">
<script type="text/x-mathjax-config"> MathJax.Hub.Config({"HTML-CSS": { availableFonts: ["STIX","TeX"], linebreaks: { automatic:true }, preferredFont: "TeX" },
tex2jax: { displayMath: [ ["$$","$$"], ["\\[", "\\]"] ], inlineMath: [ ["$", "$"], ["\\\\(","\\\\)"] ], processEscapes: true } });
</script><script type="text/javascript" async src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-AMS_HTML-full"></script></head><body> <div class="header">
<nav><p><a href="#navigation">Menu</a> - <a href="#top">Top</a> - <a href="/">Home</a></nav></div>
<div class="mainarea" id="top">
<h1 id="color-topics-for-programmers">Color Topics for Programmers</h1><p>This version of the document is dated 2026-03-16. <a href='https://github.com/peteroupc/peteroupc.github.io/issues'>Post an issue or comment on this document</a>.</p>
<p><a href="mailto:poccil14@gmail.com"><strong>Peter Occil</strong></a></p>
<p><a id="Introduction"></a></p>
<h2 id="introduction">Introduction</h2>
<p>This document presents an overview of many common color topics that are of general interest to programmers and that can be implemented in many different programming languages. <a href="https://peteroupc.github.io/colorutil.zip"><strong>Sample Python code</strong></a> <strong>that implements many of the methods in this document is available.</strong> <a href="https://peteroupc.github.io/suppcolor.html"><strong>Supplemental topics</strong></a> are listed in another open-source page.</p>
<p><strong>Topics this document covers include:</strong></p>
<ul>
<li>Red-green-blue (RGB) and other color models of practical interest.</li>
<li>How to generate colors with certain properties.</li>
<li>Color differences, color maps, and color mixing.</li>
<li>Dominant colors of an image.</li>
<li>Colors as spectral curves.</li>
</ul>
<p><strong>This document does not cover:</strong></p>
<ul>
<li>How to change or set colors used—
<ul>
<li>in text, foregrounds, or backgrounds of user-interface elements (such as buttons, text boxes, and windows),</li>
<li>in text or backgrounds of documents (such as HTML documents), or</li>
<li>when generating graphics (such as plots and charts).</li>
</ul>
</li>
<li>Determining which colors are used, or used by default, in user-interface elements, documents, plots, or charts.</li>
<li>Color pickers, including how to choose colors with them.</li>
<li>Specifics on setting and getting pixel, palette, and other colors in images (including screenshots) with the exception of finding dominant colors.</li>
<li>Colorization of command line outputs, or terminal or shell outputs. <a href="https://peteroupc.github.io/suppcolor.html#Terminal_Graphics"><strong>“ANSI” graphic codes</strong></a> are discussed elsewhere.</li>
<li>In general, topics that are specific to a programming language or application programming interface.</li>
</ul>
<p><a id="Contents"></a></p>
<h2 id="contents">Contents</h2>
<ul>
<li><a href="#Introduction"><strong>Introduction</strong></a></li>
<li><a href="#Contents"><strong>Contents</strong></a></li>
<li><a href="#Notation_and_Definitions"><strong>Notation and Definitions</strong></a></li>
<li><a href="#Overview_of_Color_Vision"><strong>Overview of Color Vision</strong></a>
<ul>
<li><a href="#Human_Color_Vision"><strong>Human Color Vision</strong></a></li>
<li><a href="#Defective_and_Animal_Color_Vision"><strong>Defective and Animal Color Vision</strong></a></li>
</ul>
</li>
<li><a href="#Specifying_Colors"><strong>Specifying Colors</strong></a></li>
<li><a href="#RGB_Color_Model"><strong>RGB Color Model</strong></a>
<ul>
<li><a href="#RGB_Color_Spaces"><strong>RGB Color Spaces</strong></a></li>
<li><a href="#sRGB"><strong>sRGB</strong></a></li>
<li><a href="#Representing_RGB_Colors"><strong>Representing RGB Colors</strong></a>
<ul>
<li><a href="#Binary_Formats"><strong>Binary Formats</strong></a></li>
<li><a href="#HTML_Format_and_Other_Text_Formats"><strong>HTML Format and Other Text Formats</strong></a></li>
</ul>
</li>
</ul>
</li>
<li><a href="#Transformations_of_RGB_Colors"><strong>Transformations of RGB Colors</strong></a>
<ul>
<li><a href="#HSV"><strong>HSV</strong></a></li>
<li><a href="#HSL"><strong>HSL</strong></a></li>
<li><a href="#HWB"><strong>HWB</strong></a></li>
<li><a href="#Y_prime_C_B_C_R__and_Other_Video_Color_Formats"><strong>Y′C<sub><em>B</em></sub>C<sub><em>R</em></sub> and Other Video Color Formats</strong></a></li>
</ul>
</li>
<li><a href="#Other_Color_Models"><strong>Other Color Models</strong></a>
<ul>
<li><a href="#CIE_XYZ"><strong>CIE XYZ</strong></a>
<ul>
<li><a href="#Encoding_XYZ_Through_RGB"><strong>Encoding XYZ Through RGB</strong></a></li>
<li><a href="#Conversion_Matrices_Between_XYZ_and_RGB"><strong>Conversion Matrices Between XYZ and RGB</strong></a></li>
<li><a href="#Chromaticity_Coordinates"><strong>Chromaticity Coordinates</strong></a></li>
</ul>
</li>
<li><a href="#CIELAB"><strong>CIELAB</strong></a></li>
<li><a href="#CIELUV"><strong>CIELUV</strong></a></li>
<li><a href="#Oklab"><strong>Oklab</strong></a></li>
<li><a href="#CMYK_and_Other_Ink_Mixture_Color_Models"><strong>CMYK and Other Ink-Mixture Color Models</strong></a></li>
</ul>
</li>
<li><a href="#Color_Operations"><strong>Color Operations</strong></a>
<ul>
<li><a href="#Luminance_Factor_Grayscale"><strong>Luminance Factor (Grayscale)</strong></a></li>
<li><a href="#Alpha_Blending"><strong>Alpha Blending</strong></a></li>
<li><a href="#Binarization"><strong>Binarization</strong></a></li>
<li><a href="#Color_Schemes_and_Harmonies"><strong>Color Schemes and Harmonies</strong></a></li>
<li><a href="#Contrast_Between_Two_Colors"><strong>Contrast Between Two Colors</strong></a></li>
<li><a href="#Porter_ndash_Duff_Formulas"><strong>Porter–Duff Formulas</strong></a></li>
<li><a href="#Raster_Operations"><strong>Raster Operations</strong></a></li>
<li><a href="#Blend_Modes"><strong>Blend Modes</strong></a></li>
<li><a href="#Color_Matrices"><strong>Color Matrices</strong></a></li>
<li><a href="#Lighten_Darken"><strong>Lighten/Darken</strong></a></li>
<li><a href="#Saturate_Desaturate"><strong>Saturate/Desaturate</strong></a></li>
<li><a href="#Miscellaneous"><strong>Miscellaneous</strong></a></li>
</ul>
</li>
<li><a href="#Color_Differences"><strong>Color Differences</strong></a>
<ul>
<li><a href="#Nearest_Colors"><strong>Nearest Colors</strong></a></li>
</ul>
</li>
<li><a href="#Dominant_Colors_of_an_Image"><strong>Dominant Colors of an Image</strong></a></li>
<li><a href="#Color_Maps"><strong>Color Maps</strong></a>
<ul>
<li><a href="#Kinds_of_Color_Maps"><strong>Kinds of Color Maps</strong></a></li>
<li><a href="#Color_Collections"><strong>Color Collections</strong></a></li>
<li><a href="#Visually_Distinct_Colors"><strong>Visually Distinct Colors</strong></a></li>
<li><a href="#Linear_Gradients"><strong>Linear Gradients</strong></a></li>
<li><a href="#Pseudocode"><strong>Pseudocode</strong></a></li>
</ul>
</li>
<li><a href="#Generating_a_Random_Color"><strong>Generating a Random Color</strong></a></li>
<li><a href="#Spectral_Color_Functions"><strong>Spectral Color Functions</strong></a>
<ul>
<li><a href="#Color_Temperature"><strong>Color Temperature</strong></a></li>
<li><a href="#Color_Mixture"><strong>Color Mixture</strong></a></li>
</ul>
</li>
<li><a href="#Conclusion"><strong>Conclusion</strong></a></li>
<li><a href="#Notes"><strong>Notes</strong></a></li>
<li><a href="#License"><strong>License</strong></a></li>
</ul>
<p><a id="Notation_and_Definitions"></a></p>
<h2 id="notation-and-definitions">Notation and Definitions</h2>
<ul>
<li>The <a href="https://peteroupc.github.io/pseudocode.html"><strong>pseudocode conventions</strong></a> apply to this document.</li>
<li><strong>bpc.</strong> Bits per color component, also known as bits per color channel.</li>
<li><strong>CIE.</strong> French initials for the International Commission on Illumination.</li>
<li><strong>Color model.</strong> Describes, in general terms, the relationship of colors in a theoretical space.</li>
<li><strong>Color space.</strong> A mapping from colors to numbers that follows a particular color model.</li>
<li><strong>D50 illuminant, D65 illuminant.</strong> CIE models of daylight at a correlated color temperature of about 5000 or 6500 kelvins respectively.<sup id="fnref:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup></li>
<li><strong>D50/2 white point.</strong> The white point determined by the D50 illuminant and the CIE 1931 standard observer.</li>
<li><strong>D65/2 white point.</strong> The white point determined by the D65 illuminant and the CIE 1931 standard observer.</li>
<li><strong>Image color list.</strong> Means either—
<ul>
<li>a list of colors (which can have duplicates), all of the same color space, or</li>
<li>the colors (which can have duplicates) used in a two-, three-, or more-dimensional image or geometric model, a digital video, or a digital document.</li>
</ul>
</li>
<li><strong>ISO.</strong> International Organization for Standardization.</li>
<li><strong>Image.</strong> Rectangular array of samples called <em>pixels</em>, where each pixel stores a color value or a reference to a color value. Also known as a <em>raster image</em> or <em>bitmap image</em>. More generally, an image is as defined in <a href="https://alvyray.com/Memos/CG/Microsoft/5_sprite.pdf"><strong>“A Sprite Theory of Image Computing”</strong></a>.</li>
<li><strong>Light source.</strong> Means a <a href="https://cie.co.at/eilvterm/17-27-002"><strong><em>primary light source</em></strong></a> or an <a href="https://cie.co.at/eilvterm/17-23-018"><strong><em>illuminant</em></strong></a>, as defined by the CIE. Roughly means an emitter of light, or radiation describing an emitter of light.</li>
<li><strong>RGB.</strong> Red-green-blue.</li>
</ul>
<p><a id="Overview_of_Color_Vision"></a></p>
<h2 id="overview-of-color-vision">Overview of Color Vision</h2>
<p>Color<sup id="fnref:2"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">2</a></sup> is possible only if three things exist, namely—</p>
<ul>
<li><em>light</em>,</li>
<li>an <em>object</em> receiving that light (a surface, for example), and</li>
<li>an <em>observer</em> viewing that object and interpreting the light received from it.</li>
</ul>
<p>Because of this, color does not exist in light, in objects receiving light, in light sources, or even in the signals generated by the eyes when they see things.<sup id="fnref:3"><a href="#fn:3" class="footnote" rel="footnote" role="doc-noteref">3</a></sup> In the Opticks, I. Newton said, “the Rays to speak properly are not coloured.”</p>
<p>Color appearance is subjective — since interpreting the light is required — and varies with the <em>light source</em> (sunlight, daylight, incandescent light, etc.), <em>object</em> (material), <em>observer</em>, viewing situation, or a combination of these.<sup id="fnref:4"><a href="#fn:4" class="footnote" rel="footnote" role="doc-noteref">4</a></sup></p>
<blockquote>
<p><strong>Note:</strong> The three things that together make color possible — <em>light</em>, <em>object</em>, and <em>observer</em> — can be modeled by curves that span the <em>visible spectrum</em> (the part of the electromagnetic spectrum in which light is “seen”), as described in the section “<a href="#Spectral_Color_Functions"><strong>Spectral Color Functions</strong></a>”.</p>
</blockquote>
<p><a id="Human_Color_Vision"></a></p>
<h3 id="human-color-vision">Human Color Vision</h3>
<p>When a person views an object, the light it reflects reaches that person’s eyes.</p>
<p>The human eye has an inner back lining (called the <em>retina</em>) filled with three kinds of <em>cones</em>, and each kind of cone is differently sensitive to light.</p>
<p>The human visual system compares the responses it receives from the cones and converts them to three kinds of signals, namely a light–dark signal and the two <em>opponent signals</em> red/green and blue/yellow. It’s these signals, and not the cone responses, that are passed to the brain.<sup id="fnref:5"><a href="#fn:5" class="footnote" rel="footnote" role="doc-noteref">5</a></sup></p>
<p>With these signals, the human visual system can judge color appearance, taking into account the visual situation. One process involved in this is called <em>adaptation</em>, in which the human visual system, roughly speaking, treats the brightest thing in the scene as “white” and mentally adjusts the rest of the colors it sees accordingly, to account for differences in lighting. Adaptation is thus similar to a digital camera’s “auto white balance”.</p>
<blockquote>
<p><strong>Notes:</strong></p>
<ol>
<li>The cone responses can be described by three overlapping “curves” that peak at different places in the visible spectrum — in fact, two of these curves span the entire visible spectrum. As a result, at least two of the three kinds of cones will react to light, not just one by itself.</li>
<li>Because there are three kinds of cones, three numbers are enough to uniquely identify a color humans can see — which is why many <a href="https://peteroupc.github.io/suppcolor.html#Kinds_of_Color_Spaces"><strong>color spaces</strong></a> are 3-dimensional, such as RGB or CIE XYZ spaces.</li>
</ol>
</blockquote>
<p><a id="Defective_and_Animal_Color_Vision"></a></p>
<h3 id="defective-and-animal-color-vision">Defective and Animal Color Vision</h3>
<p><a href="http://eilv.cie.co.at/term/287"><strong>Defective color vision</strong></a>, including so-called <a href="https://en.wikipedia.org/wiki/Color_blindness"><strong>“colorblindness”</strong></a>, can make certain kinds of light harder to distinguish than is the case with normal color vision.<sup id="fnref:6"><a href="#fn:6" class="footnote" rel="footnote" role="doc-noteref">6</a></sup></p>
<p>In addition to humans, many other animals possess color vision to a greater or lesser extent. As an extreme example, the <a href="https://en.wikipedia.org/wiki/Mantis_shrimp"><strong>mantis shrimp</strong></a> has at least twelve different cone types, making its color vision considerably sharper than humans’.</p>
<p><a id="Specifying_Colors"></a></p>
<h2 id="specifying-colors">Specifying Colors</h2>
<p>A color can be specified in one of two ways:</p>
<ul>
<li><strong>As a point in space</strong>, that is, as a small set of numbers (usually three numbers) showing where the color lies in a color space. This is the usual practice. Some color spaces include the following:
<ul>
<li><a href="#RGB_Color_Model"><strong>RGB</strong></a> color spaces describe proportions of “red”, “green”, and “blue” dots of light.</li>
<li><a href="#HSV"><strong>HSV</strong></a>, <a href="#HSL"><strong>HSL</strong></a>, and <a href="#HWB"><strong>HWB</strong></a> color spaces transform RGB colors to make their presentation more intuitive, but are not perception-based.</li>
<li><a href="#CIE_XYZ"><strong>XYZ</strong></a>, <a href="#CIELAB"><strong>CIELAB</strong></a>, and <a href="#CIELUV"><strong>CIELUV</strong></a> color spaces are based on human color perception.</li>
<li><a href="#CMYK_and_Other_Ink_Mixture_Color_Models"><strong>CMYK</strong></a> color spaces are especially used to describe proportions of four specific kinds of ink.</li>
<li><a href="#Y_prime_C_B_C_R_and_Other_Video_Color_Formats"><strong>Y′C<sub><em>B</em></sub>C<sub><em>R</em></sub></strong></a> is especially used in video encoding.</li>
</ul>
</li>
<li><strong>As a <em>spectral curve</em></strong>, which gives the behavior of light across the electromagnetic spectrum (see “<a href="#Spectral_Color_Functions"><strong>Spectral Color Functions</strong></a>”). Colors given as spectral curves, unlike colors in RGB or other color spaces, have the advantage that they are not specific to a lighting condition, whereas colors in a given color space assume a specific lighting, viewing, or printing condition.</li>
</ul>
<p><a id="RGB_Color_Model"></a></p>
<h2 id="rgb-color-model">RGB Color Model</h2>
<p>The <strong>red–green–blue (RGB) color model</strong> is the most commonly seen color model in mainstream computer programming. The RGB model is ideally based on the intensity that “red”, “green”, and “blue” dots of light should have in order to reproduce certain colors on display devices.<sup id="fnref:7"><a href="#fn:7" class="footnote" rel="footnote" role="doc-noteref">7</a></sup> The RGB model is a cube with one vertex set to “black”, the opposite vertex set to “white”, and the remaining vertices set to “red”, “green”, “blue”, “cyan”, “yellow”, and “magenta”.</p>
<p><strong>RGB colors.</strong> An RGB color consists of three components (<em>color components</em> or <em>color channels</em>) in the following order: “red”, “green”, “blue”.</p>
<p><strong>RGBA colors.</strong> Some RGB colors also contain a fourth component, called the <em>alpha component</em> or <em>alpha channel</em>,<sup id="fnref:8"><a href="#fn:8" class="footnote" rel="footnote" role="doc-noteref">8</a></sup> which ranges from fully transparent to fully opaque. Such RGB colors are called <em>RGBA colors</em> in this document. RGB colors without an alpha component are generally considered fully opaque.</p>
<p><strong>0-1 format.</strong> In this document, an RGB or RGBA color is in the <strong>0-1 format</strong> if all its components are 0 or greater and 1 or less. This document understands all RGB and RGBA colors to be in this format unless noted otherwise.</p>
<p><a id="RGB_Color_Spaces"></a></p>
<h3 id="rgb-color-spaces">RGB Color Spaces</h3>
<p>There are many <strong>RGB color spaces</strong>, not just one, and they generally differ in their red, green, blue, and white points and in their color component transfer functions (<em>“transfer functions”</em>):</p>
<ul>
<li>
<p><strong>Red, green, blue, and white points.</strong> These are what a given RGB color space considers “red”, “green”, “blue”, and “white”, that is, what that space associates with the RGB colors (1, 0, 0), (0, 1, 0), (0, 0, 1), and (1, 1, 1), respectively. (The first three points are commonly called “primaries”.) Each of these points need not be an actual color (this is illustrated by the <a href="http://www.oscars.org/science-technology/sci-tech-projects/aces"><strong>ACES2065-1 color space</strong></a>, for example). Examples of “primaries” are Rec. 601 (NTSC), Rec. 709, and DCI-P3. Examples of white points are the D50/2 and D65/2 white points.</p>
</li>
<li>
<p><strong>“Transfer function”.</strong> This is a function used to convert, component by component, a so-called <strong><em>linear RGB</em></strong> color to an <strong><em>encoded RGB</em> (<em>R′G′B′</em>)</strong> color in the same color space. Examples include the sRGB transfer function given <a href="#sRGB"><strong>later</strong></a>; <em>power-law</em> or <em>gamma</em> functions such as <em>c</em><sup>1/<em>γ</em></sup>, where <em>c</em> is the red, green, or blue component and <em>γ</em> is a positive number; and the PQ and HLG functions.</p>
</li>
</ul>
<p>In general, the same three numbers, such as (1, 0.5, 0.3), identify a different-appearing RGB color in different RGB color spaces. In this document, the only RGB color space described in detail is <a href="#sRGB"><strong>sRGB</strong></a>. (Lindbloom)<sup id="fnref:9"><a href="#fn:9" class="footnote" rel="footnote" role="doc-noteref">9</a></sup> contains further information on many RGB color spaces.</p>
<blockquote>
<p><strong>Notes:</strong></p>
<ol>
<li>In this document, all techniques involving RGB colors apply to such colors in linear or encoded form, unless noted otherwise.</li>
<li>The term <em>wide color gamut</em> (WCG) usually refers to a wider range of colors than sRGB. The term <a href="https://android-developers.googleblog.com/2025/08/what-is-hdr.html"><strong><em>high dynamic range</em></strong></a> (HDR) refers to a higher maximum <em>luminance</em> (see “CIE XYZ”, later) compared to what is called “standard dynamic range” or “SDR”<sup id="fnref:10"><a href="#fn:10" class="footnote" rel="footnote" role="doc-noteref">10</a></sup>, and does not necessarily mean a “darker” black level, “better” black detail, higher “contrast”, or more vivid colors. (Mano 2018)<sup id="fnref:11"><a href="#fn:11" class="footnote" rel="footnote" role="doc-noteref">11</a></sup> contains an introduction to WCG. See also Rep. 2390-4, a more advanced overview, from the International Telecommunication Union.</li>
<li>RGB colors encoded in images and video or specified in documents are usually 8-bpc or 10-bpc <em>encoded RGB</em> colors.</li>
</ol>
</blockquote>
<p><a id="sRGB"></a></p>
<h3 id="srgb">sRGB</h3>
<p>Among RGB color spaces, one of the most popular is the <em>sRGB color space</em>. In sRGB—</p>
<ul>
<li>the red, green, and blue points were chosen to cover the range of colors displayed by typical cathode-ray-tube displays (as in the high-definition standard <a href="https://en.wikipedia.org/wiki/Rec._709"><strong>Rec. 709</strong></a>),</li>
<li>the white point was chosen as the D65/2 white point, and</li>
<li>the color component transfer function (implemented as <code>SRGBFromLinear</code> below) was based on the power-law (gamma) encoding used for cathode-ray-tube monitors.</li>
</ul>
<p>For background, see the <a href="https://www.w3.org/Graphics/Color/sRGB"><strong>sRGB proposal</strong></a>, which recommends RGB image data in an unidentified RGB color space to be treated as sRGB.</p>
<p>The following methods convert colors between linear and encoded sRGB. (Note that the thresholds <code>0.0031308</code> and <code>0.4045</code> are those of IEC 61966-2-1, the official sRGB standard published by the International Electrotechnical Commission; the sRGB proposal has a different value for these thresholds.)</p>
<pre>// Convert a color component from encoded to linear sRGB
// NOTE: This is not gamma decoding; it's similar to, but
// not exactly, c^2.2. This function was designed "to
// allow for invertability in integer math", according to
// the sRGB proposal.
METHOD SRGBToLinear(c)
// NOTE: Threshold here would more properly be
// 12.92 * 0.0031308 = 0.040449936, but 0.04045
// is what the IEC standard uses
if c <= 0.04045: return c / 12.92
return pow((0.055 + c) / 1.055, 2.4)
END METHOD
// Convert a color component from linear to encoded sRGB
// NOTE: This is not gamma encoding; it's similar to, but
// not exactly, c^(1/2.2).
METHOD SRGBFromLinear(c)
if c <= 0.0031308: return 12.92 * c
return pow(c, 1.0 / 2.4) * 1.055 - 0.055
END METHOD
// Convert a color from encoded to linear sRGB
METHOD SRGBToLinear3(c)
return [SRGBToLinear(c[0]), SRGBToLinear(c[1]), SRGBToLinear(c[2])]
END METHOD
// Convert a color from linear to encoded sRGB
METHOD SRGBFromLinear3(c)
return [SRGBFromLinear(c[0]), SRGBFromLinear(c[1]), SRGBFromLinear(c[2])]
END METHOD
</pre>
<blockquote>
<p><strong>Note:</strong> IEC 61966-2-1 defines a reference display where, among other things, encoded sRGB colors’ components (<em>c</em>) are decoded using a power law of 2.2, that is, the decoding is <em>c</em><sup>2.2</sup>. Indeed, this power law, and not an inverse of the sRGB color component transfer function, is what is <a href="https://github.com/dylanraga/win11hdr-srgb-to-gamma2.2-icm"><strong>employed in practice</strong></a> by most computer displays today that can show, more or less, the range of colors supported by sRGB.</p>
</blockquote>
<p><a id="Representing_RGB_Colors"></a></p>
<h3 id="representing-rgb-colors">Representing RGB Colors</h3>
<p>The following shows how linear or encoded RGB colors can be represented as integers or as text.</p>
<p><a id="Binary_Formats"></a></p>
<h4 id="binary-formats">Binary Formats</h4>
<p>RGB and RGBA colors are often expressed by packing their components as binary integers, as follows:</p>
<ul>
<li><strong>RGB colors:</strong> With an RN-bit red component, a GN-bit green, and a BN-bit blue, resulting in an integer that’s (RN + GN + BN) bits long.</li>
<li><strong>RGBA colors:</strong> With an RN-bit red component, a GN-bit green, a BN-bit blue, and an AN-bit alpha, resulting in an integer that’s (RN + GN + BN + AN) bits long.</li>
</ul>
<p>For both kinds of colors, the lowest value of each component is 0, and its highest value is 2<sup>B</sup> - 1, where B is that component’s size in bits.</p>
<p>The following are examples of these formats:
- <strong>5/6/5 RGB colors:</strong> As 16-bit integers (5 bits each for red and blue, and 6 bits for green).
- <strong>5-bpc:</strong> As 15-bit integers (5 bpc [bits per color channel] RGB).
- <strong>8-bpc:</strong> As 24-bit integers (8 bpc RGB), or as 32-bit integers with an alpha component.
- <strong>10-bpc:</strong> As 30-bit integers (10 bpc RGB), or as 40-bit integers with an alpha component.
- <strong>16-bpc:</strong> As 48-bit integers (16 bpc RGB), or as 64-bit integers with an alpha component.</p>
<p>There are many ways to store RGB and RGBA colors in these formats as integers or as a sequence of 8-bit bytes. For example, the RGB color’s components can be in “little-endian” or “big-endian” 8-bit byte order, or the order in which the color’s components are packed into an integer can vary. This document does not seek to survey the RGB binary storage formats available.</p>
<p>The following pseudocode presents methods to convert RGB colors to and from different binary color formats (where RGB color integers are packed red/green/blue, in that order from lowest to highest bits):</p>
<pre>METHOD round(x):
if floor(x)<0.5: return floor(x)
else: return ceil(b)
END METHOD
// Converts 0-1 format to N/N/N format as an integer.
METHOD ToNNN(rgb, scale)
sm1 = scale - 1
return round(rgb[2]*sm1) * scale * scale + round(rgb[1]*sm1) * scale +
round(rgb[0]*sm1)
END METHOD
// Converts N/N/N integer format to 0-1 format
METHOD FromNNN(rgb, scale)
sm1 = scale - 1
r = rem(rgb, scale)
g = rem(floor(rgb / scale), scale)
b = rem(floor(rgb / (scale * scale)), scale)
return [ r / sm1, g / sm1, b / sm1]
END METHOD
METHOD To444(rgb): return ToNNN(rgb, 16)
METHOD To555(rgb): return ToNNN(rgb, 32)
METHOD To888(rgb): return ToNNN(rgb, 256)
METHOD To161616(rgb): return ToNNN(rgb, 65536)
METHOD From444(rgb): return FromNNN(rgb, 16)
METHOD From555(rgb): return FromNNN(rgb, 32)
METHOD From888(rgb): return FromNNN(rgb, 256)
METHOD From161616(rgb): return FromNNN(rgb, 65536)
METHOD To565(rgb)
return round(rgb[2] * 31) * 32 * 64 + round(rgb[1] * 63) * 32 +
round(rgb[0] * 31)
END METHOD
METHOD From565(rgb)
r = rem(rgb, 32)
g = rem(floor(rgb / 32.0), 64)
b = rem(floor(rgb / (32.0 * 64.0)), 32)
return [ r / 31.0, g / 63.0, b / 31.0]
END METHOD
</pre>
<p><a id="HTML_Format_and_Other_Text_Formats"></a></p>
<h4 id="html-format-and-other-text-formats">HTML Format and Other Text Formats</h4>
<p>A color string in the <strong>HTML color format</strong> (also known as “hex” format), which expresses 8-bpc RGB colors as text strings, consists of the character “#”, two base-16 (hexadecimal) digits<sup id="fnref:12"><a href="#fn:12" class="footnote" rel="footnote" role="doc-noteref">12</a></sup> for the red component, two for the green, and two for the blue, in that order.</p>
<p>For example, <code>#003F86</code> expresses the 8-bpc RGB color (0, 63, 134).</p>
<p>The following pseudocode presents methods to convert RGB colors to and from the HTML color format or the 3-digit variant described in note 1 to this section.</p>
<pre>METHOD NumToHex(x)
if hex < 0 or hex >= 16: return error
hexlist=["0", "1", "2", "3", "4", "5", "6",
"7", "8", "9", "A", "B", "C", "D", "E", "F"]
return hexlist[x]
END METHOD
METHOD HexToNum(x)
hexlist=["0", "1", "2", "3", "4", "5", "6",
"7", "8", "9", "A", "B", "C", "D", "E", "F"]
hexdown=["a", "b", "c", "d", "e", "f"]
i = 0
while i < 16
if hexlist[i] == x: return i
i = i + 1
end
i = 0
while i < 6
if hexdown[i] == x: return 10 + i
i = i + 1
end
return -1
END METHOD
METHOD ColorToHtml(rgb)
r = (rgb[0] * 255)
g = (rgb[1] * 255)
b = (rgb[2] * 255)
if floor(r)<0.5: r=floor(r)
else: r=ceil(r)
if floor(g)<0.5: g=floor(g)
else: g=ceil(g)
if floor(b)<0.5: b=floor(b)
else: b=ceil(b)
return ["#",
NumToHex(rem(floor(r/16),16)), NumToHex(rem(r, 16)),
NumToHex(rem(floor(g/16),16)), NumToHex(rem(g, 16)),
NumToHex(rem(floor(b/16),16)), NumToHex(rem(b, 16)),
]
END METHOD
METHOD HtmlToColor(colorString)
if string[0]!="#": return error
if size(colorString)==7
r1=HexToNum(colorString[1])
r2=HexToNum(colorString[2])
g1=HexToNum(colorString[3])
g2=HexToNum(colorString[4])
b1=HexToNum(colorString[5])
b2=HexToNum(colorString[6])
if r1<0 or r2<0 or g1<0 or g2<0 or
b1<0 or b2<0: return error
return [(r1*16+r2)/255.0,
(g1*16+g2)/255.0,
(b1*16+b2)/255.0]
end
if size(colorString)==4
r=HexToNum(colorString[1])
g=HexToNum(colorString[2])
b=HexToNum(colorString[3])
if r<0 or g<0 or b<0: return error
return [(r*16+r)/255.0,
(g*16+g)/255.0,
(b*16+b)/255.0]
end
return error
END METHOD
</pre>
<p>Other text-based color formats include the following<sup id="fnref:11:1"><a href="#fn:11" class="footnote" rel="footnote" role="doc-noteref">11</a></sup>:</p>
<ul>
<li>
<p>The <a href="https://www.w3.org/TR/css3-color/#rgb-color"><strong>CSS Color Module Level 3</strong></a>, which specifies this format, also mentions a <strong>3-digit variant</strong>, consisting of “#” followed by three base-16 digits, one each for the red, green, and blue components, in that order. Conversion to the 6-digit format involves replicating each base-16 component (for example, “#345” is the same as “#334455” in the 6-digit format).</p>
</li>
<li>
<p>An <strong>8-digit variant</strong> used in the Android operating system consists of “#” followed by eight base-16 digits, two each for the alpha, red, green, and blue components, in that order. This variant thus describes 8-bpc RGBA colors.</p>
</li>
<li>
<p>Additional formats are given in the <a href="https://peteroupc.github.io/suppcolor.html#Additional_Color_Formats"><strong>supplemental color topics</strong></a>.</p>
</li>
</ul>
<blockquote>
<p><strong>Note:</strong> As used in the <a href="http://www.w3.org/TR/css3-color/"><strong>CSS Color Module Level 3</strong></a>, for example, colors in the HTML color format or its 3-digit variant are in the <a href="#sRGB"><strong><em>sRGB color space</em></strong></a> (as encoded RGB colors).</p>
</blockquote>
<p><a id="Transformations_of_RGB_Colors"></a></p>
<h2 id="transformations-of-rgb-colors">Transformations of RGB Colors</h2>
<p>The following sections discuss popular color models for transforming RGB colors. The exact appearance
of colors in these models varies by <a href="#RGB_Color_Spaces"><strong>RGB color space</strong></a>.</p>
<p><a id="HSV"></a></p>
<h3 id="hsv">HSV</h3>
<p><a href="https://en.wikipedia.org/wiki/HSL_and_HSV"><strong>HSV</strong></a> (also known as HSB) is a color model that transforms RGB colors to make them easier to manipulate and reason with. An HSV color consists of three components, in the following order:</p>
<ul>
<li><em>Hue</em> is an angle from red at 0 to yellow to green to cyan to blue to magenta to red.<sup id="fnref:13"><a href="#fn:13" class="footnote" rel="footnote" role="doc-noteref">13</a></sup></li>
<li>A component called “saturation”, the distance of the color from gray and white (but not necessarily from black), is 0 or greater and 1 or less.</li>
<li>A component variously called “value” or “brightness” is the distance of the color from black and is 0 or greater and 1 or less.</li>
</ul>
<p>The following pseudocode converts colors between RGB and HSV. The transformation is independent of RGB color space, but should be done using <a href="#RGB_Color_Spaces"><strong><em>linear RGB</em> colors</strong></a>.</p>
<pre>METHOD RgbToHsv(rgb)
mx = max(max(rgb[0], rgb[1]), rgb[2])
mn = min(min(rgb[0], rgb[1]), rgb[2])
// NOTE: "Value" is the highest of the
// three components
if mx==mn: return [0,0,mx]
s=(mx-mn)/mx
h=0
if rgb[0]==mx
h=(rgb[1]-rgb[2])/(mx-mn)
else if rgb[1]==mx
h=2+(rgb[2]-rgb[0])/(mx-mn)
else
h=4+(rgb[0]-rgb[1])/(mx-mn)
end
if h < 0: h = 6 - rem(-h, 6)
if h >= 6: h = rem(h, 6)
return [h * (pi / 3), s, mx]
END METHOD
METHOD HsvToRgb(hsv)
hue=hsv[0]
sat=hsv[1]
val=hsv[2]
if hue < 0: hue = pi * 2 - rem(-hue, pi * 2)
if hue >= pi * 2: hue = rem(hue, pi * 2)
hue60 = hue * 3 / pi
hi = floor(hue60)
f = hue60 - hi
c = val * (1 - sat)
a = val * (1 - sat * f)
e = val * (1 - sat * (1 - f))
if hi == 0: return [val, e, c]
if hi == 1: return [a, val, c]
if hi == 2: return [c, val, e]
if hi == 3: return [c, a, val]
if hi == 4: return [e, c, val]
return [val, c, a]
END METHOD
</pre>
<blockquote>
<p><strong>Note:</strong> The HSV color model is not perception-based, as the HWB article acknowledges<sup id="fnref:14"><a href="#fn:14" class="footnote" rel="footnote" role="doc-noteref">14</a></sup>.</p>
</blockquote>
<p><a id="HSL"></a></p>
<h3 id="hsl">HSL</h3>
<p><a href="https://en.wikipedia.org/wiki/HSL_and_HSV"><strong>HSL</strong></a> (also known as HLS), like HSV, is a color model that transforms RGB colors to ease intuition. An HSL color consists of three components, in the following order:</p>
<ul>
<li><em>Hue</em> is the same for a given RGB color as in <a href="#HSV"><strong>HSV</strong></a>.</li>
<li>A component called “saturation” is the distance of the color from gray (but not necessarily from
black or white), which is 0 or greater and 1 or less.</li>
<li>A component variously called “lightness”, “luminance”, or “luminosity”, is roughly the amount
of black or white mixed with the color and is 0 or greater and 1 or less, where 0 is black, 1 is white, closer to 0 means closer to black, and closer to 1 means closer to white.</li>
</ul>
<p>The following pseudocode converts colors between RGB and HSL. The transformation is independent of RGB color space, but should be done using <a href="#RGB_Color_Spaces"><strong><em>linear RGB</em> colors</strong></a>.</p>
<pre>METHOD RgbToHsl(rgb)
vmax = max(max(rgb[0], rgb[1]), rgb[2])
vmin = min(min(rgb[0], rgb[1]), rgb[2])
vadd = vmax + vmin
// NOTE: "Lightness" is the midpoint between
// the greatest and least RGB component
lt = vadd / 2.0
if vmax==vmin: return [0, 0, lt]
vd = vmax - vmin
divisor = vadd
if lt > 0.5: divisor = 2.0 - vadd
s = vd / divisor
h = 0
hvd = vd / 2.0
deg60 = pi / 3
if rgb[0]==vmax
h=((vmax-rgb[2])*deg60 + hvd) / vd
h = h - ((vmax-rgb[1])*deg60+hvd) / vd
else if rgb[2]==vmax
h=pi * 4 / 3 + ((vmax-rgb[1])*deg60 + hvd) / vd
h = h - ((vmax-rgb[0])*deg60+hvd) / vd
else
h=pi * 2 / 3 + ((vmax-rgb[0])*deg60 + hvd) / vd
h = h - ((vmax-rgb[2])*deg60+hvd) / vd
end
if h < 0: h = pi * 2 - rem(-h, pi * 2)
if h >= pi * 2: h = rem(h, pi * 2)
return [h, s, lt]
END METHOD
METHOD HslToRgb(hsl)
if hsl[1]==0: return [hsl[2],hsl[2],hsl[2]]
lum = hsl[2]
sat = hsl[1]
bb = 0
if lum <= 0.5: bb = lum * (1.0 + sat)
if lum > 0.5: bb= lum + sat - (lum * sat)
a = lum * 2 - bb
hueval = hsl[0]
if hueval < 0: hueval = pi * 2 - rem(-hueval, pi * 2)
if hueval >= pi * 2: hueval = rem(hueval, pi * 2)
deg60 = pi / 3
deg240 = pi * 4 / 3
hue = hueval + pi * 2 / 3
hue2 = hueval - pi * 2 / 3
if hue >= pi * 2: hue = hue - pi * 2
if hue2 < 0: hue2 = hue2 + pi * 2
rgb = [a, a, a]
hues = [hue, hueval, hue2]
i = 0
while i < 3
if hues[i] < deg60: rgb[i] = a + (bb - a) * hues[i] / deg60
else if hues[i] < pi: rgb[i] = bb
else if hues[i] < deg240
rgb[i] = a + (bb - a) * (deg240 - hues[i]) / deg60
end
i = i + 1
end
return rgb
END METHOD
</pre>
<blockquote>
<p><strong>Notes:</strong></p>
<ul>
<li>In some applications and specifications, especially where this color model is called HLS, the HSL color’s “lightness” component comes before “saturation”. This is not the case in this document, though.</li>
<li>The HSL color model is not perception-based, as the HWB article acknowledges<sup id="fnref:14:1"><a href="#fn:14" class="footnote" rel="footnote" role="doc-noteref">14</a></sup>.</li>
</ul>
</blockquote>
<p><a id="HWB"></a></p>
<h3 id="hwb">HWB</h3>
<p>In 1996, the HWB model, which seeks to be more intuitive than HSV or HSL, was published<sup id="fnref:14:2"><a href="#fn:14" class="footnote" rel="footnote" role="doc-noteref">14</a></sup>. An HWB color consists of three components in the following order:
- <em>Hue</em> is the same for a given RGB color as in <a href="#HSV"><strong>HSV</strong></a>.
- <em>Whiteness</em>, the amount of white mixed to the color, is 0 or greater and 1 or less.
- <em>Blackness</em>, the amount of black mixed to the color, is 0 or greater and 1 or less.</p>
<p>The conversions given below are independent of RGB color space, but should be done using <a href="#RGB_Color_Spaces"><strong><em>linear RGB</em> colors</strong></a>.</p>
<ul>
<li>To convert an RGB color <code>color</code> to HWB, generate <code>[RgbToHsv(color)[0], min(min(color[0], color[1]), color[2]), 1 - max(max(color[0], color[1]), color[2])]</code>.</li>
<li>To convert an HWB color <code>hwb</code> to RGB, generate <code>HsvToRgb([hwb[0], 1 - hwb[1]/(1-hwb[2]), 1 - hwb[2]])</code> if <code>hwb[2] < 1</code>, or <code>[hwb[0], 0, 0]</code> otherwise.</li>
</ul>
<blockquote>
<p><strong>Note:</strong> The HWB color model is not perception-based, as the HWB article acknowledges<sup id="fnref:14:3"><a href="#fn:14" class="footnote" rel="footnote" role="doc-noteref">14</a></sup>.</p>
</blockquote>
<p><a id="Y_prime_C_B_C_R__and_Other_Video_Color_Formats"></a></p>
<h3 id="yprimecsubbsubcsubrsub-and-other-video-color-formats">Y′C<sub><em>B</em></sub>C<sub><em>R</em></sub> and Other Video Color Formats</h3>
<p>An RGB color can be transformed to a specialized form to improve image and video encoding.</p>
<p><a href="https://en.wikipedia.org/wiki/YCbCr"><strong>Y′C<sub><em>B</em></sub>C<sub><em>R</em></sub></strong></a> (also known as YCbCr, YCrCb, or Y′CrCb) is a family of color formats designed for this purpose. A Y′C<sub><em>B</em></sub>C<sub><em>R</em></sub> color consists of three components in the following order:</p>
<ul>
<li>Y′, or <em>luma</em>, expresses an approximate “brightness”.<sup id="fnref:15"><a href="#fn:15" class="footnote" rel="footnote" role="doc-noteref">15</a></sup></li>
<li>C<sub><em>B</em></sub>, or <em>blue chroma</em>, is based on the difference between blue and luma.</li>
<li>C<sub><em>R</em></sub>, or <em>red chroma</em>, is, based on the difference between red and luma.</li>
</ul>
<p>The following pseudocode is an approximate conversion between RGB and Y′C<sub><em>B</em></sub>C<sub><em>R</em></sub> (an approximation because the factors in the pseudocode are rounded off to a limited number of decimal places). There are three variants shown here, namely—</p>
<ul>
<li>the Rec. 601 variant (for standard-definition digital video), as the <code>YCbCrToRgb601</code> and <code>RgbToYCbCr601</code> methods,</li>
<li>the Rec. 709 variant (for high-definition video), as the <code>YCbCrToRgb709</code> and <code>RgbToYCbCr709</code> methods, and</li>
<li>the <a href="https://www.w3.org/Graphics/JPEG/jfif3.pdf"><strong>JPEG File Interchange Format</strong></a> variant, as the <code>YCbCrToRgbJpeg</code> and <code>RgbToYCbCrJpeg</code> methods.</li>
</ul>
<p>The Y′C<sub><em>B</em></sub>C<sub><em>R</em></sub> transformation is independent of RGB color space, but the three variants given earlier should use <a href="#RGB_Color_Spaces"><strong><em>encoded RGB</em> colors</strong></a> rather than <em>linear RGB</em> colors.</p>
<pre>// NOTE: Derived from scaled YPbPr using red/green/blue luminance factors
// in the NTSC color space
METHOD RgbToYCbCr601(rgb)
y = (16.0/255.0+rgb[0]*0.25678824+rgb[1]*0.50412941+rgb[2]*0.097905882)
cb = (128.0/255.0-rgb[0]*0.1482229-rgb[1]*0.29099279+rgb[2]*0.43921569)
cr = (128.0/255.0+rgb[0]*0.43921569-rgb[1]*0.36778831-rgb[2]*0.071427373)
return [y, cb, cr]
END METHOD
// NOTE: Derived from scaled YPbPr using red/green/blue Rec. 709 luminance factors
METHOD RgbToYCbCr709(rgb)
y = (0.06200706*rgb[2] + 0.6142306*rgb[1] + 0.1825859*rgb[0] + 16.0/255.0)
cb = (0.4392157*rgb[2] - 0.338572*rgb[1] - 0.1006437*rgb[0] + 128.0/255.0)
cr = (-0.04027352*rgb[2] - 0.3989422*rgb[1] + 0.4392157*rgb[0] + 128.0/255.0)
return [y, cb, cr]
END METHOD
// NOTE: Derived from unscaled YPbPr using red/green/blue luminance factors
// in the NTSC color space
METHOD RgbToYCbCrJpeg(rgb)
y = (0.299*rgb[0] + 0.587*rgb[1] + 0.114*rgb[2])
cb = (-0.1687359*rgb[0] - 0.3312641*rgb[1] + 0.5*rgb[2] + 128.0/255.0)
cr = (0.5*rgb[0] - 0.4186876*rgb[1] - 0.08131241*rgb[2] + 128.0/255.0)
return [y, cb, cr]
END METHOD
METHOD YCbCrToRgb601(yCbCr)
cb = yCbCr[1] - 128/255.0
cr = yCbCr[2] - 128/255.0
yp = 1.1643836 * (yCbCr[0] - 16/255.0)
r = yp + 1.5960268 * cr
g = yp - 0.39176229 * cb - 0.81296765 * cr
b = yp + 2.0172321 * cb
return [min(max(r,0),1),min(max(g,0),1),min(max(b,0),1)]
END METHOD
METHOD YCbCrToRgb709(yCbCr)
cb = yCbCr[1] - 128/255.0
cr = yCbCr[2] - 128/255.0
yp = 1.1643836 * (yCbCr[0] - 16/255.0)
r = yp + 1.7927411 * cr
g = yp - 0.21324861 * cb - 0.53290933 * cr
b = yp + 2.1124018 * cb
return [min(max(r,0),1),min(max(g,0),1),min(max(b,0),1)]
END METHOD
METHOD YCbCrToRgbJpeg(yCbCr)
cb = yCbCr[1] - 128/255.0
cr = yCbCr[2] - 128/255.0
yp = yCbCr[0]
r = yp + 1.402 * cr
g = yp - 0.34413629 * cb - 0.71413629 * cr
b = yp + 1.772 * cb
return [min(max(r,0),1),min(max(g,0),1),min(max(b,0),1)]
END METHOD
</pre>
<blockquote>
<p><strong>Notes:</strong></p>
<ol>
<li>This document does not seek to survey the various ways in which the point samples in images and videos are represented as Y′C<sub><em>B</em></sub>C<sub><em>R</em></sub> and similar colors. In general, such ways take into account the human eye’s normally greater spatial sensitivity to luminance (Y, as approximated, for example, by Y′, luma) than chromatic sensitivity (for example, C<sub><em>B</em></sub>, C<sub><em>R</em></sub>).</li>
<li>Other video color formats include “BT.2020 constant luminance”, in <a href="https://en.wikipedia.org/wiki/Rec._2020"><strong>Rec. 2020</strong></a>, and IC<sub><em>T</em></sub>C<sub><em>P</em></sub>, mentioned in Rep. 2390-4 and detailed in a <a href="https://www.dolby.com/us/en/technologies/dolby-vision/ICtCp-white-paper.pdf"><strong>Dolby white paper</strong></a>.</li>
</ol>
</blockquote>
<p><a id="Other_Color_Models"></a></p>
<h2 id="other-color-models">Other Color Models</h2>
<p>The following sections discuss other color models of practical interest.</p>
<p><a id="CIE_XYZ"></a></p>
<h3 id="cie-xyz">CIE XYZ</h3>
<p>The <a href="https://en.wikipedia.org/wiki/CIE_1931_color_space"><strong>CIE 1931 standard colorimetric system</strong></a> (called the <em>XYZ color model</em> in this document) describes a transformation of a spectral curve into a point in three-dimensional space, as further explained in “<a href="#Spectral_Color_Functions"><strong>Spectral Color Functions</strong></a>”. An XYZ color consists of three components, in the following order:</p>
<ul>
<li>X is a component without special meaning.</li>
<li>Y is related to the color’s <a href="http://6degreesoffreedom.co/luminance-vs-illuminance/"><strong><em>luminance</em></strong></a>.</li>
<li>Z is a component without special meaning.</li>
</ul>
<p>Conventions for XYZ colors include the following:</p>
<ul>
<li><strong>Absolute XYZ.</strong> In this convention, the Y component represents an absolute <em>luminance</em> in candelas per square meter (cd/m<sup>2</sup>).</li>
<li><strong>Relative XYZ.</strong> In this convention, the three components are divided by the luminance of a given white point. In this case, the Y component represents a <em>luminance factor</em>; the white point has a luminance factor of 1.<sup id="fnref:16"><a href="#fn:16" class="footnote" rel="footnote" role="doc-noteref">16</a></sup> (In sRGB, the white point’s luminance is 80 cd/m<sup>2</sup>.)</li>
</ul>
<p>The conversion between RGB and XYZ varies by <a href="#RGB_Color_Spaces"><strong>RGB color space</strong></a>. For example, the pseudocode below shows two methods that convert a color between <strong>encoded sRGB</strong> (<code>rgb</code>) and relative XYZ:</p>
<ul>
<li>For <code>XYZFromsRGB(rgb)</code> and <code>XYZTosRGB(xyz)</code>, the white point is the D65/2 white point.</li>
<li>For <code>XYZFromsRGBD50(rgb)</code> and <code>XYZTosRGBD50(xyz)</code>, the white point is the D50/2 white point<sup id="fnref:17"><a href="#fn:17" class="footnote" rel="footnote" role="doc-noteref">17</a></sup>.</li>
</ul>
<p>Both methods are approximate conversions because the factors in the pseudocode are rounded off to a limited number of decimal places.</p>
<p> </p>
<pre>// Applies a 3 &times; 3 matrix transformation
METHOD Apply3x3Matrix(xyz, xyzmatrix)
r=xyz[0]*xyzmatrix[0]+xyz[1]*xyzmatrix[1]+xyz[2]*xyzmatrix[2]
g=xyz[0]*xyzmatrix[3]+xyz[1]*xyzmatrix[4]+xyz[2]*xyzmatrix[5]
b=xyz[0]*xyzmatrix[6]+xyz[1]*xyzmatrix[7]+xyz[2]*xyzmatrix[8]
return [r,g,b]
END METHOD
METHOD XYZFromsRGBD50(rgb)
lin=SRGBToLinear3(rgb)
// D65/2 sRGB matrix adapted to D50/2
return Apply3x3Matrix(lin, [
0.436027535573195, 0.385097932872408, 0.143074531554397,
0.222478677613186, 0.716902127457834, 0.0606191949289806,
0.0139242392790820, 0.0970836931437703, 0.714092067577148])
END METHOD
METHOD XYZTosRGBD50(xyz)
// D65/2 sRGB matrix adapted to D50/2
rgb=Apply3x3Matrix(xyz, [
3.13424933163426, -1.61717292521282, -0.490692377104512,
-0.978746070339639, 1.91611436125945, 0.0334415219513205,
0.0719490494816283, -0.228969853236611, 1.40540126012171])
return SRGBFromLinear3(rgb)
END METHOD
METHOD XYZFromsRGB(rgb)
lin=SRGBToLinear3(rgb)
// D65/2 sRGB matrix
return Apply3x3Matrix(lin, [
0.4123907992659591, 0.35758433938387796, 0.18048078840183424
0.21263900587151016, 0.7151686787677559, 0.0721923153607337
0.01933081871559181, 0.11919477979462596, 0.9505321522496605])
END METHOD
METHOD XYZTosRGB(xyz)
// D65/2 sRGB matrix
rgb=Apply3x3Matrix(xyz, [
3.2409699419045235, -1.5373831775700944, -0.49861076029300355,
-0.9692436362808797, 1.8759675015077204, 0.0415550574071756,
0.05563007969699365, -0.20397695888897652, 1.0569715142428786])
return SRGBFromLinear3(rgb)
END METHOD
</pre>
<blockquote>
<p><strong>Notes:</strong></p>
<ol>
<li>In the pseudocode just given, 3 × 3 matrices are used to transform a linear RGB color to or from XYZ form (see <a href="#Conversion_Matrices_Between_XYZ_and_RGB"><strong>“Conversion Matrices Between XYZ and RGB”</strong></a>).</li>
<li><code>XYZTosRGB</code> and <code>XYZTosRGBD50</code> can return sRGB colors with components less than 0 or greater than 1, to make out-of-range XYZ colors easier to identify. If that is not desired, then the sRGB color can be converted to an in-range one. There are many such <em>gamut mapping</em> conversions; for example, one such conversion involves clamping each component of the sRGB color using the idiom <code>min(max(compo,0), 1)</code>, where <code>compo</code> is that component.</li>
<li>
<p>XYZ colors that have undergone <strong>black point compensation</strong> (see also ISO 18619) can be expressed as <code>Lerp3(wpoint, xyz, (1.0 - blackDest) / (1.0 - blackSrc))</code>, where—</p>
<ul>
<li><code>wpoint</code> is the white point as an absolute or relative XYZ color,</li>
<li><code>xyz</code> is a relative XYZ color (relative to <code>wpoint</code>), and</li>
<li><code>blackSrc</code> and <code>blackDest</code> are the luminance factors of the source and destination black points.</li>
</ul>
</li>
</ol>
</blockquote>
<p><a id="Encoding_XYZ_Through_RGB"></a></p>
<h4 id="encoding-xyz-through-rgb">Encoding XYZ Through RGB</h4>
<p>The following summarizes the transformations needed to convert a color from (relative) XYZ through RGB to an encoding form suitable for images or video.</p>
<ol>
<li>An XYZ-to-linear-RGB transform. This is usually a <a href="#Conversion_Matrices_Between_XYZ_and_RGB"><strong>matrix</strong></a> generated using the <a href="#RGB_Color_Spaces"><strong>RGB color space</strong></a>’s red, green, blue, and white points, but can also include a <a href="https://en.wikipedia.org/wiki/Chromatic_adaptation"><strong><em>chromatic adaptation transform</em></strong></a> if the XYZ and RGB color spaces use different white points (see the <code>XYZFromsRGBD50</code> and <code>XYZTosRGBD50</code> methods above)<sup id="fnref:18"><a href="#fn:18" class="footnote" rel="footnote" role="doc-noteref">18</a></sup>.</li>
<li>A linear-to-encoded-RGB transform. This is the RGB color space’s “transfer function”. This can be left out if linear RGB colors are desired.</li>
<li>A color encoding transform. This transforms the RGB color into <a href="#Y_prime_C_B_C_R_and_Other_Video_Color_Formats"><strong>Y′C<sub><em>B</em></sub>C<sub><em>R</em></sub></strong></a> or another form. This can be left out.</li>
<li>The final color form is serialized into a binary, text, or other representation (see also “<a href="#Representing_RGB_Colors"><strong>Representing RGB Colors</strong></a>”).</li>
</ol>
<p>The corresponding conversions to XYZ are then the inverse of the conversions just given.</p>
<p><a id="Conversion_Matrices_Between_XYZ_and_RGB"></a></p>
<h4 id="conversion-matrices-between-xyz-and-rgb">Conversion Matrices Between XYZ and RGB</h4>
<p>The following methods calculate a 3 × 3 matrix to convert from a linear RGB color to XYZ form (<code>RGBToXYZMatrix</code>) and back (<code>XYZToRGBMatrix</code>), given the RGB color space’s red, green, blue, and white points. Each point is expressed as a relative XYZ color with arbitrary X and Z components and a Y component of 1. For example, <code>xr</code> and <code>zr</code> are the red point’s X and Z components, respectively. See <a href="http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html"><strong>brucelindbloom.com</strong></a> for more information.</p>
<pre>METHOD RGBToXYZMatrix(xr,zr,xg,zg,xb,zb,xw,zw)
s1=(xb*zg - xb*zw - xg*zb + xg*zw + xw*zb - xw*zg)
s2=(xb*zg - xb*zr - xg*zb + xg*zr + xr*zb - xr*zg)
s3=(-xb*zr + xb*zw + xr*zb - xr*zw - xw*zb + xw*zr)
sz=(-xr*(zg - zr) + xw*(zg - zr) + zr*(xg - xr) -
zw*(xg - xr)) /
((xb - xr)*(zg - zr) - (xg - xr)*(zb - zr))
sx=s1/s2
sy=s3/s2
return [xr*sx,xg*sy,xb*sz,sx,sy,sz,zr*sx,zg*sy,zb*sz]
END METHOD
METHOD XYZToRGBMatrix(xr,zr,xg,zg,xb,zb,xw,zw)
// NOTE: Inverse of RGBToXYZMatrix
d1=(xb*zg - xb*zw - xg*zb + xg*zw + xw*zb - xw*zg)
d2=(xb*zr - xb*zw - xr*zb + xr*zw + xw*zb - xw*zr)
d3=(xg*zr - xg*zw - xr*zg + xr*zw + xw*zg - xw*zr)
return [(zb - zg)/d1,(xb*zg - xg*zb)/d1,
(-xb + xg)/d1, (zb - zr)/d2,
(xb*zr - xr*zb)/d2,(-xb + xr)/d2,
(zg - zr)/d3,(xg*zr - xr*zg)/d3,
(-xg + xr)/d3]
END METHOD
</pre>
<p><a id="Chromaticity_Coordinates"></a></p>
<h4 id="chromaticity-coordinates">Chromaticity Coordinates</h4>
<p>The chromaticity coordinates <em>x</em>, <em>y</em>, and <em>z</em> are each the ratios of the corresponding component of an XYZ color to the sum of those components; therefore, those three coordinates sum to 1.<sup id="fnref:19"><a href="#fn:19" class="footnote" rel="footnote" role="doc-noteref">19</a></sup> “xyY” form consists of <em>x</em> then <em>y</em> then the Y component of an XYZ color. “Yxy” form consists of the Y component then <em>x</em> then <em>y</em> of an XYZ color.</p>
<p>The CIE 1976 uniform chromaticity scale diagram is drawn using coordinates <em>u′</em> and <em>v′</em>.<sup id="fnref:20"><a href="#fn:20" class="footnote" rel="footnote" role="doc-noteref">20</a></sup> “u′v′Y” form consists of <em>u′</em> then <em>v′</em> then the Y component of an XYZ color. “Yu′v′” form consists of the Y component then <em>u′</em> then <em>v′</em> of an XYZ color.</p>
<p>In the following pseudocode, <code>XYZToxyY</code> and <code>XYZFromxyY</code> convert XYZ colors to and from their “xyY” form, respectively, and <code>XYZTouvY</code> and <code>XYZFromuvY</code> convert XYZ colors to and from their “u′v′Y” form, respectively.</p>
<pre> METHOD XYZToxyY(xyz)
sum=xyz[0]+xyz[1]+xyz[2]
if sum==0: return [0,0,0]
return [xyz[0]/sum, xyz[1]/sum, xyz[1]]
END METHOD
METHOD XYZFromxyY(xyy)
// NOTE: Results undefined if xyy[1]==0
return [xyy[0]*xyy[2]/xyy[1], xyy[2], xyy[2]*(1 - xyy[0] - xyy[1])/xyy[1]]
END METHOD
METHOD XYZTouvY(xyz)
sum=xyz[0]+xyz[1]*15.0+xyz[2]*3.0
if sum==0: return [0,0,0]
return [4.0*xyz[0]/sum,9.0*xyz[1]/sum,xyz[1]]
END METHOD
METHOD XYZFromuvY(uvy)
// NOTE: Results undefined if uvy[1]==0
su=uvy[2]/(uvy[1]/9.0)
x=u*su/4.0
z=(su/3.0)-(x/3.0)-5.0*uvy[2]
return [x,uvy[2],z]
END METHOD
</pre>
<p><a id="CIELAB"></a></p>
<h3 id="cielab">CIELAB</h3>
<p><a href="https://en.wikipedia.org/wiki/Lab_color_space"><strong>CIELAB</strong></a> (also known as CIE <em>L*a*b*</em> or CIE 1976 <em>L*a*b*</em>) is a three-dimensional color model designed for color comparisons.<sup id="fnref:21"><a href="#fn:21" class="footnote" rel="footnote" role="doc-noteref">21</a></sup> In general, CIELAB color spaces differ in their white points.</p>
<p>A color in CIELAB consists of three components, in the following order:</p>
<ul>
<li><em>L*</em>, or <em>lightness</em> of a color (how bright that color appears in comparison to white), is 0 or greater and 100 or less, where 0 is black and 100 is white.</li>
<li><em>a*</em> is a coordinate of the red/green axis (positive points to red, negative to green).</li>
<li><em>b*</em> is a coordinate of the yellow/blue axis (positive points to yellow, negative to blue).<sup id="fnref:22"><a href="#fn:22" class="footnote" rel="footnote" role="doc-noteref">22</a></sup></li>
</ul>
<p><em>L*C*h</em> form expresses CIELAB colors as cylindrical coordinates; the three components have the following order:</p>
<ul>
<li>Lightness (<em>L*</em>) remains unchanged.</li>
<li><em>Chroma</em> (<em>C*</em>) is the distance of the color from the “gray” line.<sup id="fnref:23"><a href="#fn:23" class="footnote" rel="footnote" role="doc-noteref">23</a></sup></li>
<li><em>Hue</em> (<em>h</em>, an angle)<sup id="fnref:13:1"><a href="#fn:13" class="footnote" rel="footnote" role="doc-noteref">13</a></sup> ranges from magenta at roughly 0 to red to yellow to green to cyan to blue to magenta.</li>
</ul>
<p>In the following pseudocode:</p>
<ul>
<li>
<p>The following methods convert an <strong>encoded sRGB</strong> color to and from CIELAB:</p>
<ul>
<li><code>SRGBToLab</code> and <code>SRGBFromLab</code> treat white as the D65/2 white point.</li>
<li><code>SRGBToLabD50</code> and <code>SRGBFromLabD50</code> treat white as the D50/2 white point.<sup id="fnref:17:1"><a href="#fn:17" class="footnote" rel="footnote" role="doc-noteref">17</a></sup></li>
</ul>
<p>Both methods are approximate conversions because the values in the pseudocode are rounded off to a limited number of decimal places.</p>
</li>
<li><code>XYZToLab(xyz, wpoint)</code> and <code>LabToXYZ(lab, wpoint)</code> convert an XYZ color to or from CIELAB, respectively, treating <code>wpoint</code> (an XYZ color) as the white point.</li>
<li><code>LabToChroma(lab)</code> and <code>LabToHue(lab)</code> find a CIELAB color’s <em>chroma</em> or <em>hue</em>, respectively.</li>
<li><code>LchToLab(lch)</code> finds a CIELAB color given a 3-item list of lightness, chroma, and hue (<em>L*C*h</em>), in that order.</li>
<li><code>LabHueDifference(lab1, lab2)</code> finds the <em>metric hue difference</em> (<em>ΔH*</em>) between two CIELAB colors. The return value can be positive or negative, but in some cases, the absolute value of that return value can be important.</li>
<li><code>LabChromaHueDifference(lab1, lab2)</code> finds the <em>chromaticness difference</em> (Δ<em>C</em><sub>h</sub>) between two CIELAB colors, as given, for example, in ISO 13655.</li>
</ul>
<p> </p>
<pre>METHOD XYZToLab(xyzval, wpoint)
xyz=[xyzval[0]/wpoint[0],xyzval[1]/wpoint[1],xyzval[2]/wpoint[2]]
i=0
while i < 3
if xyz[i] > 216.0 / 24389 // See BruceLindbloom.com
xyz[i]=pow(xyz[i], 1.0/3.0)
else
kappa=24389.0/27 // See BruceLindbloom.com
xyz[i]=(16.0 + kappa*xyz[i]) / 116
end
i=i+1
end
return [116.0*xyz[1] - 16,
500 * (xyz[0] - xyz[1]),
200 * (xyz[1] - xyz[2])]
END METHOD
METHOD LabToXYZ(lab,wpoint)
fy=(lab[0]+16)/116.0
fx=fy+lab[1]/500.0
fz=fy-lab[2]/200.0
fxcb=fx*fx*fx