Skip to content

Commit

Permalink
[css-color-4] Update gamut mapping algorithm, fixes #9715
Browse files Browse the repository at this point in the history
  • Loading branch information
svgeesus committed Dec 21, 2023
1 parent 28c9e9e commit 5d806c1
Showing 1 changed file with 65 additions and 48 deletions.
113 changes: 65 additions & 48 deletions css-color-4/Overview.bs
Original file line number Diff line number Diff line change
Expand Up @@ -5467,63 +5467,80 @@ CSS Gamut Mapping to an RGB Destination</h3>
<h4 id="binsearch">
Sample Pseudocode for the Binary Search Gamut Mapping Algorithm with Local MINDE</h4>

<div algorithm="to CSS gamut map a color">
To <dfn export>CSS gamut map</dfn> a color |origin|
in color space |origin color space|
to be in gamut of a destination color space |destination|:
<div algorithm="to CSS gamut map a color">
To <dfn export>CSS gamut map</dfn> a color |origin|
in color space |origin color space|
to be in gamut of a destination color space |destination|:

<ol>
<!-- check if we need gamut mapping at all -->
<li>if |destination| has no gamut limits (XYZ-D65, XYZ-D50, Lab, LCH, Oklab, Oklch) convert |origin| to |destination| and return it as the gamut mapped color
<!-- we do, so convert to Oklch -->
<li>let |origin_Oklch| be |origin| converted
from |origin color space| to the Oklch color space</li>
<!-- constrain to SDR lightness range, which gamut maps to black or white -->
<li>if the Lightness of |origin_Oklch| is greater than or equal to 100%,
convert `oklab(1 0 0 / origin.alpha)` to |destination| and return it as the gamut mapped color</li>
<li>if the Lightness of |origin_Oklch| is less than than or equal to 0%,
convert `oklab(0 0 0 / origin.alpha)` to |destination| and return it as the gamut mapped color</li>
<li>let inGamut(|color|) be a function which returns true if, when passed a color,
that color is inside the gamut of |destination|.
For HSL and HWB, it returns true if the color is inside the gamut of sRGB.
</li>
<!-- are we already in gamut? -->
<li>if inGamut(|origin_Oklch|) is true, convert |origin_Oklch| to |destination| and return it as the gamut mapped color</li>
<!-- now start to gamut map -->
<li>otherwise, let delta(|one|, |two|) be a function which returns the deltaEOK of color |one| compared to color |two|</li>
<li>let |JND| be 0.02</li>
<li>let |epsilon| be 0.0001</li>
<!-- we already excluded spaces with no gamut limits in the first step, so this is fine -->
<li>let clip(|color|) be a function which converts |color| to |destination|,
clamps each component to the bounds of the reference range for that component
and returns the result</li>

<!-- is clipped already indistinguishable from origin, and in gamut? -->
<li>set |current| to |origin_Oklch|</li>
<li>set |clipped| to clip(|current|)</li>
<li>set |E| to delta(|clipped|, |current|)</li>
<li>if |E| < |JND|
<ol>
<li>if |destination| has no gamut limits (XYZ-D65, XYZ-D50, Lab, LCH, Oklab, Oklch) convert |origin| to |destination| and return it as the gamut mapped color
<li>let |origin_Oklch| be |origin| converted
from |origin color space| to the Oklch color space</li>
<li>if the Lightness of |origin_Oklch| is greater than or equal to 100%,
return { 1 1 1 origin.alpha } in |destination|</li>
<li>if the Lightness of |origin_Oklch| is less than than or equal to 0%,
return { 0 0 0 origin.alpha } in |destination|</li>
<li>let inGamut(|color|) be a function which returns true if, when passed a color,
that color is inside the gamut of |destination|.
For HSL and HWB, it returns true if the color is inside the gamut of sRGB.
</li>
<li>if inGamut(|origin_Oklch|) is true, convert |origin_Oklch| to |destination| and return it as the gamut mapped color</li>
<li>otherwise, let delta(|one|, |two|) be a function which returns the deltaEOK of color |one| compared to color |two|</li>
<li>let |JND| be 0.02</li>
<li>let |epsilon| be 0.0001</li>
<!-- we already excluded spaces with no gamut limits in the first step, so this is fine -->
<li>let clip(|color|) be a function which converts |color| to |destination|,
converts all negative components to zero,
converts all components greater that one to one,
and returns the result
</li>
<li>set |min| to zero</li>
<li>set |max| to the Oklch chroma of |origin_Oklch|</li>
<li> let |min_inGamut| be a boolean that represents when |min| is still in gamut, and set it to true
<li>while (|max| - |min| is greater than |epsilon|) repeat the following steps
<li>return |clipped| as the gamut mapped color</li>
</ol>
</li>

<!-- reduce chroma -->
<li>set |min| to zero</li>
<li>set |max| to the Oklch chroma of |origin_Oklch|</li>
<li> let |min_inGamut| be a boolean that represents when |min| is still in gamut, and set it to true
<li>while (|max| - |min| is greater than |epsilon|) repeat the following steps
<ol>
<li>set |chroma| to (|min| + |max|) /2</li>
<li>set the chroma component of |current| to |chroma|</li>
<li>if |min_inGamut| is true and also if inGamut(|current|) is true, set |min| to |chroma| and continue to repeat these steps</li>
<li>otherwise, if inGamut(|current|) is false carry out these steps:
<ol>
<li>set |chroma| to (|min| + |max|) /2</li>
<li>set |current| to |origin_Oklch| and then set the chroma component to |chroma|</li>
<li>if |min_inGamut| is true and also if inGamut(|current|) is true, set |min| to |chroma| and continue to repeat these steps</li>
<li>otherwise, if inGamut(|current|) is false carry out these steps:
<li>set |clipped| to clip(|current|)</li>
<li>set |E| to delta(|clipped|, |current|)</li>
<li>if |E| < |JND|
<ol>
<li>if (|JND| - |E| < |epsilon|) return |clipped| as the gamut mapped color</li>
<li>otherwise,
<ol>
<li>set |clipped| to clip(|current|)</li>
<li>set |E| to delta(|clipped|, |current|)</li>
<li>if |E| < |JND|
<ol>
<li>if (|JND| - |E| < |epsilon|) return |clipped| as the gamut mapped color</li>
<li>otherwise,
<ol>
<li>set |min_inGamut| to false</li>
<li>set |min| to |chroma|</li>
</ol>
</li>
</ol>
</li>
<li>otherwise, set |max| to |chroma| and continue to repeat these steps</li>
<li>set |min_inGamut| to false</li>
<li>set |min| to |chroma|</li>
</ol>
</li>
</ol>
</li>
<li>otherwise, set |max| to |chroma| and continue to repeat these steps</li>
</ol>
</li>
<li>return |clipped| as the gamut mapped color</li>
</ol>
</li>
<li>return |clipped| as the gamut mapped color</li>
</ol>

</div>

<!-- unbounded hsl
<wpt ignore>
Expand Down

0 comments on commit 5d806c1

Please sign in to comment.