@@ -3172,7 +3172,7 @@ Device-independent Colors: CIE Lab and LCH, OKLab and OKLCH</h2>
3172
3172
The hue angles between CIE LCH and OKLCH are broadly similar,
3173
3173
but not identical.
3174
3174
3175
- <figure>
3175
+ <figure id="CIELCH-blue-hueshift" >
3176
3176
<img src="images/CIELCH-blue-slice.png" width="541" height="411"
3177
3177
alt="diagram showing purpling in CIE LCH">
3178
3178
<figcaption> A constant CIE LCH hue slice,
@@ -3181,7 +3181,7 @@ Device-independent Colors: CIE Lab and LCH, OKLab and OKLCH</h2>
3181
3181
</figcaption>
3182
3182
</figure>
3183
3183
3184
- <figure>
3184
+ <figure id="OKLCH-blue-no-hueshift" >
3185
3185
<img src="images/OKLCH-blue-slice.png" width="552" height="405"
3186
3186
alt="diagram showing hue constancy in OKLCH">
3187
3187
<figcaption> A constant OKLCH hue slice,
@@ -4642,9 +4642,188 @@ Hue interpolation</h3>
4642
4642
4643
4643
Issue(4928):
4644
4644
4645
+ <h2 id="gamut-mapping">
4646
+ Gamut Mapping
4647
+ </h2>
4648
+
4649
+ <h3 id="gamut-mapping-intro">An Introduction to Gamut Mapping</h3>
4650
+
4651
+ <p><i> This section is non-normative</i></p>
4652
+
4653
+ When a color in an origin colorspace
4654
+ is converted to another, destination colorspace
4655
+ which has a smaller gamut,
4656
+ some colors will be outside the destination gamut.
4657
+
4658
+ For intermediate color calculations,
4659
+ these out of gamut values are preserved.
4660
+ However, if the destination is the display device
4661
+ (a screen, or a printer)
4662
+ then out of gamut values ust be converted to
4663
+ an in-gamut color.
4664
+
4665
+ Gamut mapping is the process of finding an in-gamut color
4666
+ with the least objectionable change in visual apprearance.
4667
+
4668
+ The simplest and least acceptable method
4669
+ is simply to clip the component values
4670
+ to the displayable range.
4671
+ This changes the proportions of (for an RGB display)
4672
+ the three primary colors,
4673
+ resulting in a hue shift.
4674
+
4675
+ A better method is to mapped colors
4676
+ in a perceptually uniform colorspace,
4677
+ by finding the closest in-gamut color
4678
+ (so-called minimum ΔE or <dfn export>MINDE</dfn> ).
4679
+ Clearly, the sucess of this technique
4680
+ depends on
4681
+ the degree of uniformity of the gamut mapping colorspace
4682
+ and the predictive accuracy of the deltaE function used.
4683
+
4684
+ However, when doing gamut mapping
4685
+ (and we are really talking about gamut reduction, here),
4686
+ changes in Hue are <em> particularly</em> objectionable;
4687
+ changes in Chroma are more tolerable,
4688
+ and
4689
+ small changes in Lightness can also be acceptable
4690
+ especially if the alternative is a larger Chroma reduction.
4691
+ MINDE weights changes in each dimension equally,
4692
+ and thus gives suboptimal results.
4693
+
4694
+ To improve on MINDE algorithms,
4695
+ colors are mapped in a perceptually uniform, <em> polar</em> colorspace
4696
+ by holding the hue constant,
4697
+ and reducing the chroma until the color falls in gamut.
4698
+
4699
+ <div class="example" id="ex-gamutmap-p3-yellow-to-srgb">
4700
+ In this example, Display P3 primary yellow
4701
+ is being mapped to an sRGB display.
4702
+ The gamut mapping colorspace is OKLCH.
4645
4703
4704
+ <pre class="lang-css">
4705
+ color(display-p3 1 1 0) is
4706
+ color(srgb 1 1 -0.3463) which is
4707
+ color(oklch 0.96476 <b> 0.24503</b> 110.23).
4708
+ </pre>
4646
4709
4710
+ By progressively reducing the chroma component
4711
+ until the resulting color falls inside the sRGB gamut
4712
+ (has no components negative, or greater than one)
4713
+ a gamut mapped color is obtained.
4647
4714
4715
+ <pre class="lang-css">
4716
+ <span class="swatch" style="--color: rgb(99.116% 99.733% 0.001%)"></span> color(oklch 0.96476 <b> 0.21094</b> 110.23) which is
4717
+ <span class="swatch" style="--color: rgb(99.116% 99.733% 0.001%)"></span> color(srgb 0.99116 0.99733 0.00001)
4718
+ </pre>
4719
+ <figure id="gamutmap-p3-yellow">
4720
+ <figcaption> A constant-hue slice of OKLCH colorspace.
4721
+ The vertical axis represents lightness,
4722
+ the horizontal axis is chroma.
4723
+ The color to be mapped,
4724
+ shown as a yellow circle,
4725
+ has the chroma reduced
4726
+ while keeping hue and lightness constant.
4727
+ The color therefore moves along the maroon line in the diagram,
4728
+ towards the neutral axis on the left.
4729
+ The gamut boundary of sRGB
4730
+ is shown in green.
4731
+ </figcaption>
4732
+ <img src="./images/slice-ok-110.23.svg" alt="">
4733
+ </figure>
4734
+ </div>
4735
+
4736
+ A practical implementation will converge more quickly than a linear reduction;
4737
+ either by binary search,
4738
+ or by computing an analytical intersection
4739
+ of the line of constant hue and lightness with the gamut boundary.
4740
+
4741
+ Also, this simple approach will give sub-optimal results
4742
+ for certain colors, principally very light colors
4743
+ like yellow and cyan,
4744
+ if the upper edge of the gamut boundary is shallow,
4745
+ or even slightly concave.
4746
+ The line of constant lightness can skim just above the gamut boundary,
4747
+ resulting in an excessively low chroma in those cases.
4748
+
4749
+ The choice of colorspace will affect the acceptability of the gamut mapped colors.
4750
+
4751
+ Note: Using the CIE LCH colorspace
4752
+ and deltaE2000 distance metric,
4753
+ is known to give suboptimal results
4754
+ with <a href="#CIELCH-blue-hueshift">significant hue shifts</a> ,
4755
+ for colors in the hue range
4756
+ 270° to 330°.
4757
+ Using OKLCH colorspace
4758
+ and deltaEOK distance metric
4759
+ <a href="OKLCH-blue-no-hueshift">avoids this issue</a> .
4760
+
4761
+
4762
+ <h3 id="css-gamut-mapping">
4763
+ CSS Gamut Mapping to an RGB destination
4764
+ </h3>
4765
+
4766
+ This <dfn export>CSS gamut mapping algorithm</dfn>
4767
+ applies to individual,
4768
+ Standard Dynamic Range (SDR) CSS colors
4769
+ which are out of gamut
4770
+ of an RGB display
4771
+ and thus require gamut mapping.
4772
+ It implements a relative colorimetric intent,
4773
+ and colors inside the destination gamut are unchanged.
4774
+
4775
+ Note: other situations,
4776
+ in particular mapping to printer gamuts
4777
+ where the maximum black level is significantly above zero,
4778
+ will require different algorithms
4779
+ which align the respective black and white points,
4780
+ which will result in lightness changes
4781
+ for very light and very dark colors
4782
+ as chroma is reduced..
4783
+
4784
+ Note: this algorithm is for individual, distinct colors;
4785
+ for color images,
4786
+ where relationships between neighboring pixels are important
4787
+ and detail and texture must be preserved,
4788
+ a perceptual rendering intent is more appropriate
4789
+ and in that case,
4790
+ colors inside the destinatin gamut
4791
+ may well be changed.
4792
+
4793
+ CSS gamut mapping occurs in the OKLCH colorspace,
4794
+ and the color difference formula used is deltaEOK.
4795
+
4796
+ The skimming/concavity problem is substantialy improved
4797
+ by a two-stage in-gamut check.
4798
+ For the binary search implementation,
4799
+ at each step,
4800
+ the deltaEOK is computed between the current mapped color
4801
+ and a clipped version of that color.
4802
+ If the current color is outside the gamut boundary,
4803
+ but the deltaEOK between it and the clipped version
4804
+ is below a threshold for a <em> just noticeable difference</em> (JND),
4805
+ the clipped version of the color is returned as the mapped result.
4806
+ Effectively, this is doing a MINDE mapping at each stage,
4807
+ but constrained so the hue and lightness changes
4808
+ are very small,
4809
+ and thus are not noticeable.
4810
+
4811
+ For the analytical implementation,
4812
+ having found the exact intersection,
4813
+ project outwards (higher chroma) along the line of constant lightness
4814
+ until the deltaEOK between the projected point
4815
+ and a clipped version of that point
4816
+ exceeds one JND.
4817
+
4818
+ For the OKLCH colorspace,
4819
+ one JND is is an OKLCH difference of 0.02.
4820
+
4821
+
4822
+ <!-- color(display-p3 1 1 0)
4823
+ color(oklch 0.96476 0.24503 110.23)
4824
+ mapped
4825
+ color(oklch 0.9651 0.20983 109.359)
4826
+ -->
4648
4827
4649
4828
<!--
4650
4829
██████ ████████ ██ ██ ██ ████████ ████████ ██ ██ ██ ████████ ██████
0 commit comments