Skip to content

Commit 5d806c1

Browse files
committed
[css-color-4] Update gamut mapping algorithm, fixes w3c#9715
1 parent 28c9e9e commit 5d806c1

File tree

1 file changed

+65
-48
lines changed

1 file changed

+65
-48
lines changed

css-color-4/Overview.bs

Lines changed: 65 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -5467,63 +5467,80 @@ CSS Gamut Mapping to an RGB Destination</h3>
54675467
<h4 id="binsearch">
54685468
Sample Pseudocode for the Binary Search Gamut Mapping Algorithm with Local MINDE</h4>
54695469

5470-
<div algorithm="to CSS gamut map a color">
5471-
To <dfn export>CSS gamut map</dfn> a color |origin|
5472-
in color space |origin color space|
5473-
to be in gamut of a destination color space |destination|:
5470+
<div algorithm="to CSS gamut map a color">
5471+
To <dfn export>CSS gamut map</dfn> a color |origin|
5472+
in color space |origin color space|
5473+
to be in gamut of a destination color space |destination|:
54745474

5475+
<ol>
5476+
<!-- check if we need gamut mapping at all -->
5477+
<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
5478+
<!-- we do, so convert to Oklch -->
5479+
<li>let |origin_Oklch| be |origin| converted
5480+
from |origin color space| to the Oklch color space</li>
5481+
<!-- constrain to SDR lightness range, which gamut maps to black or white -->
5482+
<li>if the Lightness of |origin_Oklch| is greater than or equal to 100%,
5483+
convert `oklab(1 0 0 / origin.alpha)` to |destination| and return it as the gamut mapped color</li>
5484+
<li>if the Lightness of |origin_Oklch| is less than than or equal to 0%,
5485+
convert `oklab(0 0 0 / origin.alpha)` to |destination| and return it as the gamut mapped color</li>
5486+
<li>let inGamut(|color|) be a function which returns true if, when passed a color,
5487+
that color is inside the gamut of |destination|.
5488+
For HSL and HWB, it returns true if the color is inside the gamut of sRGB.
5489+
</li>
5490+
<!-- are we already in gamut? -->
5491+
<li>if inGamut(|origin_Oklch|) is true, convert |origin_Oklch| to |destination| and return it as the gamut mapped color</li>
5492+
<!-- now start to gamut map -->
5493+
<li>otherwise, let delta(|one|, |two|) be a function which returns the deltaEOK of color |one| compared to color |two|</li>
5494+
<li>let |JND| be 0.02</li>
5495+
<li>let |epsilon| be 0.0001</li>
5496+
<!-- we already excluded spaces with no gamut limits in the first step, so this is fine -->
5497+
<li>let clip(|color|) be a function which converts |color| to |destination|,
5498+
clamps each component to the bounds of the reference range for that component
5499+
and returns the result</li>
5500+
5501+
<!-- is clipped already indistinguishable from origin, and in gamut? -->
5502+
<li>set |current| to |origin_Oklch|</li>
5503+
<li>set |clipped| to clip(|current|)</li>
5504+
<li>set |E| to delta(|clipped|, |current|)</li>
5505+
<li>if |E| < |JND|
54755506
<ol>
5476-
<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
5477-
<li>let |origin_Oklch| be |origin| converted
5478-
from |origin color space| to the Oklch color space</li>
5479-
<li>if the Lightness of |origin_Oklch| is greater than or equal to 100%,
5480-
return { 1 1 1 origin.alpha } in |destination|</li>
5481-
<li>if the Lightness of |origin_Oklch| is less than than or equal to 0%,
5482-
return { 0 0 0 origin.alpha } in |destination|</li>
5483-
<li>let inGamut(|color|) be a function which returns true if, when passed a color,
5484-
that color is inside the gamut of |destination|.
5485-
For HSL and HWB, it returns true if the color is inside the gamut of sRGB.
5486-
</li>
5487-
<li>if inGamut(|origin_Oklch|) is true, convert |origin_Oklch| to |destination| and return it as the gamut mapped color</li>
5488-
<li>otherwise, let delta(|one|, |two|) be a function which returns the deltaEOK of color |one| compared to color |two|</li>
5489-
<li>let |JND| be 0.02</li>
5490-
<li>let |epsilon| be 0.0001</li>
5491-
<!-- we already excluded spaces with no gamut limits in the first step, so this is fine -->
5492-
<li>let clip(|color|) be a function which converts |color| to |destination|,
5493-
converts all negative components to zero,
5494-
converts all components greater that one to one,
5495-
and returns the result
5496-
</li>
5497-
<li>set |min| to zero</li>
5498-
<li>set |max| to the Oklch chroma of |origin_Oklch|</li>
5499-
<li> let |min_inGamut| be a boolean that represents when |min| is still in gamut, and set it to true
5500-
<li>while (|max| - |min| is greater than |epsilon|) repeat the following steps
5507+
<li>return |clipped| as the gamut mapped color</li>
5508+
</ol>
5509+
</li>
5510+
5511+
<!-- reduce chroma -->
5512+
<li>set |min| to zero</li>
5513+
<li>set |max| to the Oklch chroma of |origin_Oklch|</li>
5514+
<li> let |min_inGamut| be a boolean that represents when |min| is still in gamut, and set it to true
5515+
<li>while (|max| - |min| is greater than |epsilon|) repeat the following steps
5516+
<ol>
5517+
<li>set |chroma| to (|min| + |max|) /2</li>
5518+
<li>set the chroma component of |current| to |chroma|</li>
5519+
<li>if |min_inGamut| is true and also if inGamut(|current|) is true, set |min| to |chroma| and continue to repeat these steps</li>
5520+
<li>otherwise, if inGamut(|current|) is false carry out these steps:
55015521
<ol>
5502-
<li>set |chroma| to (|min| + |max|) /2</li>
5503-
<li>set |current| to |origin_Oklch| and then set the chroma component to |chroma|</li>
5504-
<li>if |min_inGamut| is true and also if inGamut(|current|) is true, set |min| to |chroma| and continue to repeat these steps</li>
5505-
<li>otherwise, if inGamut(|current|) is false carry out these steps:
5522+
<li>set |clipped| to clip(|current|)</li>
5523+
<li>set |E| to delta(|clipped|, |current|)</li>
5524+
<li>if |E| < |JND|
5525+
<ol>
5526+
<li>if (|JND| - |E| < |epsilon|) return |clipped| as the gamut mapped color</li>
5527+
<li>otherwise,
55065528
<ol>
5507-
<li>set |clipped| to clip(|current|)</li>
5508-
<li>set |E| to delta(|clipped|, |current|)</li>
5509-
<li>if |E| < |JND|
5510-
<ol>
5511-
<li>if (|JND| - |E| < |epsilon|) return |clipped| as the gamut mapped color</li>
5512-
<li>otherwise,
5513-
<ol>
5514-
<li>set |min_inGamut| to false</li>
5515-
<li>set |min| to |chroma|</li>
5516-
</ol>
5517-
</li>
5518-
</ol>
5519-
</li>
5520-
<li>otherwise, set |max| to |chroma| and continue to repeat these steps</li>
5529+
<li>set |min_inGamut| to false</li>
5530+
<li>set |min| to |chroma|</li>
55215531
</ol>
5532+
</li>
5533+
</ol>
55225534
</li>
5535+
<li>otherwise, set |max| to |chroma| and continue to repeat these steps</li>
55235536
</ol>
55245537
</li>
5525-
<li>return |clipped| as the gamut mapped color</li>
55265538
</ol>
5539+
</li>
5540+
<li>return |clipped| as the gamut mapped color</li>
5541+
</ol>
5542+
5543+
</div>
55275544

55285545
<!-- unbounded hsl
55295546
<wpt ignore>

0 commit comments

Comments
 (0)