@@ -3172,7 +3172,7 @@ Device-independent Colors: CIE Lab and LCH, OKLab and OKLCH</h2>
31723172 The hue angles between CIE LCH and OKLCH are broadly similar,
31733173 but not identical.
31743174
3175- <figure>
3175+ <figure id="CIELCH-blue-hueshift" >
31763176 <img src="images/CIELCH-blue-slice.png" width="541" height="411"
31773177 alt="diagram showing purpling in CIE LCH">
31783178 <figcaption> A constant CIE LCH hue slice,
@@ -3181,7 +3181,7 @@ Device-independent Colors: CIE Lab and LCH, OKLab and OKLCH</h2>
31813181 </figcaption>
31823182 </figure>
31833183
3184- <figure>
3184+ <figure id="OKLCH-blue-no-hueshift" >
31853185 <img src="images/OKLCH-blue-slice.png" width="552" height="405"
31863186 alt="diagram showing hue constancy in OKLCH">
31873187 <figcaption> A constant OKLCH hue slice,
@@ -4642,9 +4642,188 @@ Hue interpolation</h3>
46424642
46434643 Issue(4928):
46444644
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.
46454703
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>
46464709
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.
46474714
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+ -->
46484827
46494828<!--
46504829 ██████ ████████ ██ ██ ██ ████████ ████████ ██ ██ ██ ████████ ██████
0 commit comments