@@ -114,6 +114,18 @@ spec: css-ui-4; type: property; text: accent-color
114114 "title": "ITU-R BT.1886 Reference electro-optical transfer function for flat panel displays used in HDTV studio production",
115115 "publisher": "ITU",
116116 "date": "2011-03"
117+ },
118+ "Coloraide-Ray-Trace": {
119+ "title": "Ray Tracing Chroma Reduction",
120+ "date": "2024",
121+ "authors": ["Isaac Muse"] ,
122+ "href": "https://facelessuser.github.io/coloraide/gamut/#ray-tracing-chroma-reduction"
123+ },
124+ "colorjs-EdgeSeeker": {
125+ "title": "EdgeSeeker Gamut Mapping algorithm",
126+ "date": "2023",
127+ "authors": ["James Stuckey Weber"] ,
128+ "href": "https://github.com/color-js/apps/tree/main/gamut-mapping/edge-seeker"
117129 }
118130 }
119131</pre>
@@ -5755,20 +5767,31 @@ CSS Gamut Mapping to an RGB Destination</h3>
57555767
57565768<wpt title="Actual values of color are not exposed to script, making this hard to test in an automated manner."></wpt>
57575769
5758- The <dfn export>CSS gamut mapping algorithm </dfn>
5759- applies to individual,
5770+ The three <dfn export>CSS gamut mapping algorithms </dfn>
5771+ apply to individual,
57605772 Standard Dynamic Range (SDR) CSS colors
57615773 which are out of gamut
57625774 of an RGB display
57635775 and thus require to be <dfn export>css gamut mapped</dfn> .
57645776
5777+ Implementations my choose any of the three algorithms
5778+ based on their quality and runtime efficiency tradeoffs,
5779+ and must use their chosen algorithm
5780+ wherever CSS mandates that gamut mapping be performed.
5781+
5782+ <ul>
5783+ <li> <a href="#GMA-Binary-local-MINDE">Binary Search Gamut Mapping with Local MINDE</a> </li>
5784+ <li> <a href="#GMA-EdgeSeeker">EdgeSeeker Gamut Mapping</a> </li>
5785+ <li> <a href="#GMA-Raytrace">Ray Trace Gamut Mapping</a> </li>
5786+ </ul>
5787+
57655788 <!-- unbounded hsl
57665789 It also applies to colors being converted
57675790 to HSL or HWB, which cannot express
57685791 colors outside the sRGB gamut. -->
57695792
5770- It implements a relative colorimetric intent,
5771- and colors inside the destination gamut are unchanged.
5793+ They all implement a relative colorimetric intent,
5794+ thus colors inside the destination gamut are unchanged.
57725795
57735796 Note: other situations,
57745797 in particular mapping to printer gamuts
@@ -5779,7 +5802,7 @@ CSS Gamut Mapping to an RGB Destination</h3>
57795802 for very light and very dark colors
57805803 as chroma is reduced..
57815804
5782- Note: this algorithm is for individual, distinct colors;
5805+ Note: these algorithms are for individual, distinct colors;
57835806 for color images,
57845807 where relationships between neighboring pixels are important
57855808 and the aim is to preserve detail and texture,
@@ -5788,26 +5811,34 @@ CSS Gamut Mapping to an RGB Destination</h3>
57885811 colors inside the destination gamut
57895812 could be changed.
57905813
5791- CSS gamut mapping occurs in the <a href="#ok-lab">OkLCh color space</a> ,
5792- and the color difference formula used is <a href="#color-difference-OK">deltaEOK</a> .
5793- The <a href="#GM-chroma-local-MINDE">local-MINDE</a> improvement is used.
5814+ CSS gamut mapping occurs in the <a href="#ok-lab">OkLCh color space</a> .
57945815
57955816 For colors which are out of range on the Lightness axis,
57965817 white is returned in the destination color space
57975818 if the Lightness is greater than or equal to 1.0,
57985819 while black is returned in the destination color space
57995820 if the Lightness is less than or equal to 0.0.
58005821
5801- For the binary search implementation,
5802- at each step in the search,
5822+ <h4 id="GMA-Binary-local-MINDE">
5823+ Binary Search Gamut Mapping with Local MINDE
5824+ </h4>
5825+
5826+ For this binary search algorithm,
5827+ the color difference formula used is <a href="#color-difference-OK">deltaEOK</a> .
5828+ The <a href="#GM-chroma-local-MINDE">local-MINDE</a> improvement is used.
5829+ At each step in the search,
58035830 the deltaEOK is computed between the current mapped color
58045831 and a clipped version of that color.
5832+
58055833 If the current color is <em> outside</em> the gamut boundary,
58065834 but the deltaEOK between it and the clipped version
58075835 is below a threshold for a <em> just noticeable difference</em> (JND),
58085836 the clipped version of the color is returned as the mapped result.
5837+ This gives good results with non-notivceable hue shifts,
5838+ and avoids excessive chroma reduction near concave gamut surfaces,
5839+ but can be computationally intensive.
58095840
5810-
5841+ <!--
58115842 For the geometric implementation,
58125843 having found the exact intersection,
58135844 project outwards (towards higher chroma) along the line of constant lightness
@@ -5821,6 +5852,8 @@ CSS Gamut Mapping to an RGB Destination</h3>
58215852
58225853 Then return the clipped version of the color as the mapped result.
58235854
5855+ -->
5856+
58245857 For the OkLCh color space,
58255858 one JND is is an OkLCh difference of 0.02.
58265859
@@ -5833,11 +5866,10 @@ CSS Gamut Mapping to an RGB Destination</h3>
58335866 using deltaEOK,
58345867 one JND is 100 times smaller.
58355868
5869+ <h4 id="pseudo-binsearch">
5870+ Sample Pseudocode for the Binary Search Gamut Mapping with Local MINDE</h4>
58365871
5837- <h4 id="binsearch">
5838- Sample Pseudocode for the Binary Search Gamut Mapping Algorithm with Local MINDE</h4>
5839-
5840- <div algorithm="to CSS gamut map a color">
5872+ <div algorithm="to gamut map a color by binary search with local MINDE">
58415873 To <dfn export>CSS gamut map</dfn> a color |origin|
58425874 in color space |origin color space|
58435875 to be in gamut of a destination color space |destination|:
@@ -5910,6 +5942,97 @@ Sample Pseudocode for the Binary Search Gamut Mapping Algorithm with Local MINDE
59105942 <li> return |clipped| as the gamut mapped color</li>
59115943</ol>
59125944
5945+ <h4 id="GMA-EdgeSeeker">
5946+ The EdgeSeeker Gamut Mapping
5947+ </h4>
5948+
5949+ The EdgeSeeker algorithm is a geometric and lookup-table-based approach,
5950+ originally developed by James Stuckey Weber
5951+ for the color.js library [[colorjs-EdgeSeeker]] .
5952+
5953+ For any given hue, the gamut boundary slice
5954+ is represented as a curved top section
5955+ and a linear bottom section,
5956+ joning at the highest chroma point for that hue.
5957+
5958+ To initialize this algorithm,
5959+ for a given target RGB space,
5960+ a lookup table (LUT) is constructed
5961+ containing the highest-chroma Oklch color on each hue slice.
5962+ Linear interpolation between closest LUT values
5963+ is used to then estimate the highest-chroma color
5964+ for the exact hue of each color to be gamut mapped.
5965+
5966+ Intersection of the constant-lightness ray
5967+ with the gamut boundary is then calculated,
5968+ which is fast for the lower (linear) part of the boundary
5969+ and still fairly fast for the upper (curved) portion.
5970+
5971+ This gives good results, at the expense of memory for the LUT.
5972+
5973+ <h4 id="pseudo-edgeseeker">
5974+ Sample Pseudocode for the EdgeSeeker Gamut Mapping</h4>
5975+
5976+ Issue: add pseudocode for EdgeSeeker GMA
5977+
5978+ <h4 id="GMA-Raytrace">
5979+ The Ray Trace Gamut Mapping
5980+ </h4>
5981+
5982+ The Ray Trace algorithm is a geometric approach
5983+ to RGB gamut mapping,
5984+ for fast chroma reduction with constant lightness.
5985+ It was originally developed by Isaac Muse
5986+ for the Coloraide Python Library [[Coloraide-Ray-Trace]] .
5987+
5988+ The color to be mapped is first converted to Oklch,
5989+ and then the achromatic version of that color is generated,
5990+ which will be the neutrax axis anchor.
5991+ These two colors are then converted to
5992+ the linear-light version of the target RGB space.
5993+
5994+ A ray is cast from the inside of the RGB cube,
5995+ from the anchor point to the current color.
5996+ The intersection along this path
5997+ with the RGB gamut surface is then found;
5998+ this is the first approximation to the gamut mapped color.
5999+
6000+ As RGB spaces are not perceptually uniform,
6001+ a constant hue, constant lightness ray
6002+ is actually a curved path in RGB space.
6003+
6004+ The first approximation is converted back to Oklch
6005+ to correct the color in the perceptual color space
6006+ by projecting the point back onto the chroma reduction path,
6007+ correcting the color's hue and lightness.
6008+ The corrected color becomes the new current color
6009+ and should be a much closer color on the reduced chroma line.
6010+
6011+ This process a maximum of three times,
6012+ each time finding a better, closer color on the path.
6013+ Finally, simple clipping is used to account for floating point math errors.
6014+
6015+ The results are comparable to binary search with local MINDE
6016+ using a low JND,
6017+ but resolves much faster
6018+ and within more predictable, consistent time.
6019+
6020+ <figure>
6021+ <img src="./images/raytrace-gma.png" alt="">
6022+ <figcaption> Ray Trace gamut mpping to the sRGB gamut,
6023+ showing the curved path of chroma reduction
6024+ as approximated over a maximum
6025+ of four iterations.<br>
6026+ Image copyright Isaac Muse.
6027+ </figcaption>
6028+ </figure>
6029+
6030+
6031+ <h4 id="pseudo-raytrace">
6032+ Sample Pseudocode for the Ray Trace Gamut Mapping</h4>
6033+
6034+ Issue: add pseudocode for Ray Trace GMA
6035+
59136036</div>
59146037
59156038 <!-- unbounded hsl
0 commit comments