Skip to content

Gamut Mapping

Many color spaces are designed in such a way that they represent a specific range of colors. This is often done to target specific display types or mediums. The monitor you are reading this on can likely display millions of colors, but there are still colors it is not capable of displaying. So color spaces are often designed to represent such mediums, to make it easy for authors and artists to know exactly where those color boundaries are. This range of colors that a color space is designed for is called a color gamut.

There are some color spaces that are theoretically unbounded, and even some color spaces that are bounded but can actually still give meaningful data if extended, but often, when it comes time to display a color, paint a product, or print a book, the actual colors are limited to what that device or process can handle.

The sRGB and Display P3 color spaces are both RGB color spaces, but they actually can represent a different amount of colors. Display P3 has a wider gamut and allows for greener greens and redder reds, etc. In the image below, we show four different RGB color spaces, each with varying different gamut sizes. Display P3 contains all the colors in sRGB and extends it even further. Rec. 2020, another RGB color space, is even wider. ProPhoto is so wide that it contains colors that the human eye can't even see.

Gamut Comparison

In order to visually represent a color from a wider gamut color space, such as Display P3, in a more narrow color space, such as sRGB, a suitable color within the more narrow color space must must be selected and be shown in its place. This selecting of a suitable replacement is called gamut mapping.

ColorAide defines a couple methods to help identify when a color is outside the gamut bounds of a color space and to help find a suitable, alternative color that is within the gamut.

Checking Gamut

When dealing with colors, it can be important to know whether a color is within its own gamut. Let's say we are working with colors in Display P3, but we want to output to an sRGB display. Let's say the color of interest is color(display-p3 1 0 0). If we plot the color as shown below, we can see that it is in Display P3, the faint transparent shell, but it is outside the sRGB gamut, the color solid in the middle. We'd like to detect these cases and make an adjustment to ensure we don't get unexpected behavior.

Out of Gamut

The in_gamut function allows for comparing the current color's specified values against the target color space's gamut.

Let's assume we have a color rgb(30% 105% 0%). The color is out of gamut due to the green channel exceeding the channel's limit of 100%. When we execute in_gamut, we can see that the color is not in its own gamut.

>>> Color("rgb(30% 105% 0%)").in_gamut()
False

On the other hand, some color spaces do not have a limit. CIELab and Oklab are such color spaces and can be represented in any gamut that'd you'd like.

Out of Gamut

Sometimes limits will be placed on the color space channels (as done above) for practicality, but theoretically, there are no bounds.

When we check a CIELab color, we will find that it is always considered in gamut as it has no gamut.

>>> Color("lab(200% -20 40 / 1)").in_gamut()
True

While checking CIELab's own gamut isn't very useful, we can test it against a different color space's gamut. By simply passing in the name of a different color space, the current color will be converted to the provided space and then will run in_gamut on the new color. You could do this manually, but using in_gamut in this manner can be very convenient. In the example below, we can see that the CIELab color of lab(200% -20 40 / 1) is outside the narrow gamut of sRGB.

>>> Color("lab(200% -20 40 / 1)").in_gamut('srgb')
False

Tolerance

Generally, ColorAide does not round off values in order to guarantee the best possible values for round tripping, but due to limitations of floating-point arithmetic and precision of conversion algorithms, there can be edge cases where colors don't round trip perfectly. By default, in_gamut allows for a tolerance of 0.000075 to account for such cases where a color is "close enough". If desired, this "tolerance" can be adjusted.

Let's consider the oRGB color model. When converting from sRGB to oRGB, both of which share the same gamut, we can see that the conversion back is very, very close to being correct, but still technically out of gamut with one channel value falling below zero, but only slightly. This is due to the perils of floating point arithmetic.

>>> Color('red').convert('orgb')[:]
[0.299, 1.905283832848159e-05, 0.9999779995764854, 1.0]
>>> Color('red').convert('orgb').convert('srgb')[:]
[1.0, 1.1102230246251565e-16, -1.3877787807814457e-16, 1.0]

When testing with a tolerance, the color is considered in gamut, but when testing with a tolerance of zero, the color is considered out of gamut. Depending on what you are doing, this may not be an issue up until you are ready to finalize the color as very close to in gamut is usually good enough, so sometimes it may be desirable to have some tolerance, and other times not.

>>> Color('red').convert('orgb').convert('srgb')[:]
[1.0, 1.1102230246251565e-16, -1.3877787807814457e-16, 1.0]
>>> Color('red').convert('orgb').convert('srgb').in_gamut()
True
>>> Color('red').convert('orgb').convert('srgb').in_gamut(tolerance=0)
False

Let's consider some color models that handle out of gamut colors in a less subtle way. HSL, HSV, and HWB are color models designed to represent an RGB color space in a cylindrical format, traditionally sRGB. Each of these spaces isolate different attributes of a color: saturation, whiteness, lightness, etc. Because these models are just representing the color space in a different way, they share the same gamut as the reference RGB color space. So it stands to reason that simply using the sRGB gamut check for them should be sufficient, and if we are using strict tolerance, this would be true.

>>> Color('rgb(255 255 255)').in_gamut('srgb', tolerance=0)
True
>>> Color('hsl(0 0% 100%)').in_gamut('srgb', tolerance=0)
True
>>> Color('color(--hsv 0 0% 100%)').in_gamut('srgb', tolerance=0)
True
>>> Color('rgb(255.05 255 255)').in_gamut('srgb', tolerance=0)
False
>>> Color('hsl(0 0% 100.05%)').in_gamut('srgb', tolerance=0)
False
>>> Color('color(--hsv 0 0% 100.05%)').in_gamut('srgb', tolerance=0)
False

But when we are using a tolerance, and we check one of these models only using the sRGB gamut, there are some cases where these cylindrical colors can exhibit coordinates wildly outside of the model's range but still very close to the sRGB gamut. This is isn't an error or a bug, but simply how the color model behaves with out of gamut colors. These values can still convert right back to the original color, but this might not always be the case with all color models.

In this example, we have an sRGB color that is extremely close to being in gamut, but when we convert it to HSL, we can see wildly large saturation. But since it round trips back to sRGB just fine, it can exhibit extreme saturation, but can still be considered in the sRGB gamut.

>>> hsl = Color('color(srgb 0.999999 1.000002 0.999999)').convert('hsl')
>>> hsl
color(--hsl 300 3 1 / 1)
>>> hsl.in_gamut('srgb')
True

This happens because these cylindrical color models do not represent out of gamut colors in a very sane way. When lightness exceeds the SDR range of 0 - 1 (or 0 - 100% as people generally associate HSL), they can return extremely high saturation. So even a slightly out of gamut sRGB color could translate to a value way outside the cylindrical color model's boundaries.

For this reason, gamut checks in the HSL, HSV, and HWB models apply tolerance checks on the color's coordinates in the sRGB color space and the respective cylindrical model ensuring we have coordinates that are close to the color's actual gamut and reasonably close to the cylindrical model's constraints as well. But if we specifically request srgb, we will see that only srgb is referenced.

>>> hsl = Color('color(srgb 0.999999 1.000002 0.999999)').convert('hsl')
>>> hsl
color(--hsl 300 3 1 / 1)
>>> hsl.in_gamut()
False
>>> hsl.in_gamut('hsl')
False
>>> hsl.in_gamut('srgb')
True

In short, ColorAide will figure out what best to test unless you explicitly tell it to use something else. If the Cartesian check is the only desired check, and the strange cylindrical values that are returned are not a problem, srgb can always be specified. tolerance=0 can also be used to constrain the check to values exactly in the gamut.

HSL has a very tight conversion to and from sRGB, so when an sRGB color is precisely in gamut, it will remain in gamut throughout the conversion to and from HSL, both forwards and backwards. On the other hand, there may be color models that have a looser conversion algorithm. There may even be cases where it may be beneficial to increase the threshold.

Gamut Mapping Colors

Gamut mapping is the process of taking a color that is out of gamut and adjusting it in such a way that it fits within the gamut. Essentially, gamut mapping takes a color that is out of gamut and maps it to some place on the target gamut that makes sense.

Gamut Mapping

There are various ways to map or compress values of an out of bound color to an in bound color, each with their own pros and cons. ColorAide offers a couple of methods related to gamut mapping: clip() and fit(). clip() is a dedicated function that performs the speedy, yet naive, approach of simply truncating a color channel's value to fit within the specified gamut, and fit() is a method that allows you to do more advanced gamut mapping approaches that, while often not as performant as simple, naive clipping, generally yield much better results.

While clipping won't always yield the best results, clipping is still very important and can be used to trim channel noise after certain mathematical operations or even used in other gamut mapping algorithms if used carefully. For this reason, clip has its own dedicated method for quick access: clip().

>>> Color('rgb(270 30 120)').clip()
color(srgb 1 0.11765 0.47059 / 1)

The fit() method, is the generic gamut mapping method that exposes access to all the different gamut mapping methods available. By default, fit() uses a more advanced method of gamut mapping that tries to preserve hue and lightness, hue being the attribute the human eye is most sensitive to. If desired, a user can also specify any currently registered gamut mapping algorithm via the method parameter.

>>> Color('rgb(270 30 120)').fit()
color(srgb 1 0.18296 0.47421 / 1)
>>> Color('rgb(270 30 120)').fit(method='clip')
color(srgb 1 0.11765 0.47059 / 1)

Gamut mapping can also be used to indirectly fit colors in another gamut. For instance, fitting a Display P3 color into an sRGB gamut.

>>> c1 = Color('color(display-p3 1 1 0)')
>>> c1.in_gamut('srgb')
False
>>> c1.fit('srgb')
color(display-p3 0.9986 0.99232 0.32855 / 1)
>>> c1.in_gamut()
True

This can also be done with clip().

>>> Color('color(display-p3 1 1 0)').clip('srgb')
color(display-p3 1 1 0.3309 / 1)

Indirectly Gamut Mapping a Color Space

When indirectly gamut mapping in another color space, results may vary depending on what color space you are in and what color space you are using to fit the color. The operation may not get the color precisely in gamut. This is because we must convert the color to the gamut mapping space, apply the gamut mapping, and then convert it back to the original color. The process will be subject to any errors that occur in the round trip to and from the targeted space. This is mainly mentioned as fitting in one color space and round tripping back may not give exact results and, in some cases, exceed "in gamut" thresholds.

There are actually many different ways to gamut map a color. Some are computationally expensive, some are quite simple, and many do really good in some cases and not so well in others. There is probably no perfect gamut mapping method, but some are better than others.

Clip

The clip gamut mapping is registered in Color by default and cannot be unregistered

Clipping is a simple and naive approach to gamut mapping. If the color space is bounded by a gamut, clip will compare each channel's value against the bounds for that channel set the value to the limit it exceeds.

Clip can be performed via fit by using the method name clip or by using the clip() method.

>>> c = Color('srgb', [2, 1, 1.5])
>>> c.fit(method='clip')
color(srgb 1 1 1 / 1)
>>> c = Color('srgb', [2, 1, 1.5])
>>> c.clip()
color(srgb 1 1 1 / 1)

Clipping is unique to all other clipping methods in that it has its own dedicated method clip() method and that its method name clip is reserved. While not always the best approach for gamut mapping in general, clip is very important to some other gamut mapping and has specific cases where its speed and simplicity are of great value.

While clipping may not preserve perceptual colors very well, at times it can be useful, and preferred over other approaches.

MINDE Chroma Reduction

MINDE chroma reduction is an approach that reduces the chroma in a perceptual color space until the color is within the gamut of a targeted color space. As the exact point at which the color will be in gamut is unknown, the chroma is reduced using bisection. The color is compared periodically to the the clipped version of the current iteration to see if the ∆E distance between them is below the "just noticeable difference" (JND) defined for the color space. If the color is close enough to the clipped version, the clipped version is returned.

Preserving lightness in this way is useful when creating tones or mixing and interpolating colors, but desiring to preserve their lightness for contrast. It also does a fairly accurate job at approaching the gamut surface. If the JND is reduced, the lightness and hue will be held even more constant and will often approach the gamut surface even closer and more accurately.

Computationally, MINDE Chroma Reduction is slower to compute than clipping due to how many iterations it must perform to get close enough to the gamut surface, but it generally provides good results, far surpassing naive clipping.

It should be noted that most color spaces that have a defined gamut are tied to specific RGB gamuts. And when they are gamut mapped, they are done so in those RGB spaces. For instance HSL, which represents the sRGB gamut in a cylindrical form will be gamut mapped in sRGB (though simple clipping may be done directly in HSL).

There are a few color spaces/models that do not have a clearly defined gamuts. One such case is HPLuv, which is only defined as a cylindrical color space that represent only a subset of the sRGB color space. Additionally Okhsl and Okhsv are two cylindrical color spaces based on the perceptual Oklab color space that are meant to target the sRGB gamut, but are only a loose approximation which actually can slightly clip the sRGB gamut while simultaneously containing a few colors that exceed the sRGB gamut. ColorAide will not automatically associate these color spaces with an RGB gamut. In the case of HPLuv, there is no specifically defined RGB gamut, and in the case of Okhsl and Okhsv, sRGB is the closest, but does not precisely represent the colors in Okhsl and Okhsv.

Gamut mapping in HPLuv usually provides fine results, but you may find that gamut mapping Okhsl may not provide the intended results. When gamut mapping such spaces, you may want to use the closest RGB gamut.

>>> Steps([c.fit('okhsl', method='oklch-chroma') for c in Color.steps(['oklch(90% 0.4 0)', 'oklch(90% 0.4 360)'], steps=100, space='oklch', hue='longer')])
[color(--oklch 0.9 0.03085 0 / 1), color(--oklch 0.9 0.03372 3.6364 / 1), color(--oklch 0.9 0.03428 7.2727 / 1), color(--oklch 0.9 0.03333 10.909 / 1), color(--oklch 0.9 0.03093 14.545 / 1), color(--oklch 0.9 0.02652 18.182 / 1), color(--oklch 0.9 0.01818 21.818 / 1), color(--oklch 0.9 0.00125 205.45 / 1), color(--oklch 0.9 0.08837 209.09 / 1), color(--oklch 0.9 0.00249 32.727 / 1), color(--oklch 0.9 0.01967 36.364 / 1), color(--oklch 0.9 0.02693 40 / 1), color(--oklch 0.9 0.03129 43.636 / 1), color(--oklch 0.9 0.03452 47.273 / 1), color(--oklch 0.9 0.03733 50.909 / 1), color(--oklch 0.9 0.04004 54.545 / 1), color(--oklch 0.9 0.04282 58.182 / 1), color(--oklch 0.9 0.04576 61.818 / 1), color(--oklch 0.9 0.04888 65.455 / 1), color(--oklch 0.9 0.05211 69.091 / 1), color(--oklch 0.9 0.05531 72.727 / 1), color(--oklch 0.9 0.0581 76.364 / 1), color(--oklch 0.9 0.05966 80 / 1), color(--oklch 0.9 0.05812 83.636 / 1), color(--oklch 0.9 0.04843 87.273 / 1), color(--oklch 0.9 0.01209 90.909 / 1), color(--oklch 0.9 0.15515 94.545 / 1), color(--oklch 0.9 0.1861 98.182 / 1), color(--oklch 0.9 0.18832 101.82 / 1), color(--oklch 0.9 0.19138 105.45 / 1), color(--oklch 0.9 0.01909 289.09 / 1), color(--oklch 0.9 0.03162 292.73 / 1), color(--oklch 0.9 0.02113 296.36 / 1), color(--oklch 0.9 0.03591 300 / 1), color(--oklch 0.9 0.22247 123.64 / 1), color(--oklch 0.9 0.23299 127.27 / 1), color(--oklch 0.9 0.2456 130.91 / 1), color(--oklch 0.9 0.24384 134.55 / 1), color(--oklch 0.9 0.22238 138.18 / 1), color(--oklch 0.9 0.20619 141.82 / 1), color(--oklch 0.9 0.19362 145.45 / 1), color(--oklch 0.9 0.03067 329.09 / 1), color(--oklch 0.9 0.00536 152.73 / 1), color(--oklch 0.9 0.0202 156.36 / 1), color(--oklch 0.9 0.02441 160 / 1), color(--oklch 0.9 0.02118 163.64 / 1), color(--oklch 0.9 0.01041 167.27 / 1), color(--oklch 0.9 0.01147 350.91 / 1), color(--oklch 0.9 0.05689 354.55 / 1), color(--oklch 0.9 0.15497 178.18 / 1), color(--oklch 0.9 0.1556 181.82 / 1), color(--oklch 0.9 0.15708 185.45 / 1), color(--oklch 0.9 0.15589 189.09 / 1), color(--oklch 0.9 0.15425 192.73 / 1), color(--oklch 0.9 0.15008 196.36 / 1), color(--oklch 0.9 0.12658 200 / 1), color(--oklch 0.9 0.10983 203.64 / 1), color(--oklch 0.9 0.01686 27.273 / 1), color(--oklch 0.9 0.0287 210.91 / 1), color(--oklch 0.9 0.04084 214.55 / 1), color(--oklch 0.9 0.04427 218.18 / 1), color(--oklch 0.9 0.04441 221.82 / 1), color(--oklch 0.9 0.0431 225.45 / 1), color(--oklch 0.9 0.04112 229.09 / 1), color(--oklch 0.9 0.03883 232.73 / 1), color(--oklch 0.9 0.03641 236.36 / 1), color(--oklch 0.9 0.03393 240 / 1), color(--oklch 0.9 0.03138 243.64 / 1), color(--oklch 0.9 0.02866 247.27 / 1), color(--oklch 0.9 0.02552 250.91 / 1), color(--oklch 0.9 0.0212 254.55 / 1), color(--oklch 0.9 0.01317 258.18 / 1), color(--oklch 0.9 0.01887 81.818 / 1), color(--oklch 0.9 0.04813 265.45 / 1), color(--oklch 0.9 0.04811 269.09 / 1), color(--oklch 0.9 0.0831 92.727 / 1), color(--oklch 0.9 0.00036 276.36 / 1), color(--oklch 0.9 0.01827 280 / 1), color(--oklch 0.9 0.02615 283.64 / 1), color(--oklch 0.9 0.03053 287.27 / 1), color(--oklch 0.9 0.0331 290.91 / 1), color(--oklch 0.9 0.03429 294.55 / 1), color(--oklch 0.9 0.03388 298.18 / 1), color(--oklch 0.9 0.03076 301.82 / 1), color(--oklch 0.9 0.02106 305.45 / 1), color(--oklch 0.9 0.01717 129.09 / 1), color(--oklch 0.9 0.06811 312.73 / 1), color(--oklch 0.9 0.07289 316.36 / 1), color(--oklch 0.9 0.07874 320 / 1), color(--oklch 0.9 0.08599 323.64 / 1), color(--oklch 0.9 0.09033 327.27 / 1), color(--oklch 0.9 0.08316 330.91 / 1), color(--oklch 0.9 0.07729 334.55 / 1), color(--oklch 0.9 0.07245 338.18 / 1), color(--oklch 0.9 0.06842 341.82 / 1), color(--oklch 0.9 0.06506 345.45 / 1), color(--oklch 0.9 0.0235 169.09 / 1), color(--oklch 0.9 0.00322 172.73 / 1), color(--oklch 0.9 0.02296 356.36 / 1), color(--oklch 0.9 0.03085 0 / 1)]
>>> Steps([c.fit('srgb', method='oklch-chroma') for c in Color.steps(['oklch(90% 0.4 0)', 'oklch(90% 0.4 360)'], steps=100, space='oklch', hue='longer')])
[color(--oklch 0.88717 0.06673 355.33 / 1), color(--oklch 0.88718 0.06441 359.67 / 1), color(--oklch 0.88719 0.06255 4.0928 / 1), color(--oklch 0.88718 0.06113 8.5912 / 1), color(--oklch 0.88719 0.06011 13.152 / 1), color(--oklch 0.88715 0.05951 17.75 / 1), color(--oklch 0.88717 0.05925 22.369 / 1), color(--oklch 0.88713 0.05941 26.987 / 1), color(--oklch 0.88715 0.05991 31.574 / 1), color(--oklch 0.88713 0.06083 36.121 / 1), color(--oklch 0.88716 0.0621 40.588 / 1), color(--oklch 0.88717 0.06379 44.978 / 1), color(--oklch 0.88716 0.06593 49.278 / 1), color(--oklch 0.88715 0.06854 53.473 / 1), color(--oklch 0.88712 0.07167 57.565 / 1), color(--oklch 0.88715 0.07531 61.519 / 1), color(--oklch 0.88713 0.07964 65.378 / 1), color(--oklch 0.88712 0.08471 69.122 / 1), color(--oklch 0.88712 0.09063 72.748 / 1), color(--oklch 0.88713 0.09758 76.267 / 1), color(--oklch 0.88713 0.10584 79.694 / 1), color(--oklch 0.88717 0.11564 83.019 / 1), color(--oklch 0.88715 0.12763 86.283 / 1), color(--oklch 0.88719 0.14226 89.462 / 1), color(--oklch 0.88719 0.16071 92.59 / 1), color(--oklch 0.88726 0.18232 95.438 / 1), color(--oklch 0.89218 0.18372 96.494 / 1), color(--oklch 0.8987 0.18567 97.858 / 1), color(--oklch 0.90192 0.18793 100.67 / 1), color(--oklch 0.90192 0.19107 104.69 / 1), color(--oklch 0.90193 0.19529 108.7 / 1), color(--oklch 0.90192 0.20068 112.7 / 1), color(--oklch 0.90192 0.20737 116.68 / 1), color(--oklch 0.90193 0.21554 120.62 / 1), color(--oklch 0.90194 0.22539 124.54 / 1), color(--oklch 0.90194 0.23722 128.42 / 1), color(--oklch 0.90194 0.25142 132.26 / 1), color(--oklch 0.89219 0.26266 135.44 / 1), color(--oklch 0.88263 0.27371 138.18 / 1), color(--oklch 0.88175 0.25494 141.76 / 1), color(--oklch 0.88189 0.23689 145.66 / 1), color(--oklch 0.88209 0.22286 149.58 / 1), color(--oklch 0.88221 0.21214 153.5 / 1), color(--oklch 0.88229 0.20391 157.42 / 1), color(--oklch 0.88313 0.19644 161.07 / 1), color(--oklch 0.88558 0.18764 164.42 / 1), color(--oklch 0.88805 0.17994 167.95 / 1), color(--oklch 0.8905 0.17342 171.57 / 1), color(--oklch 0.89291 0.168 175.25 / 1), color(--oklch 0.8953 0.1636 178.96 / 1), color(--oklch 0.89767 0.16015 182.69 / 1), color(--oklch 0.90003 0.15756 186.42 / 1), color(--oklch 0.90238 0.15577 190.11 / 1), color(--oklch 0.90473 0.15472 193.75 / 1), color(--oklch 0.9054 0.15455 194.77 / 1), color(--oklch 0.90357 0.15414 195.17 / 1), color(--oklch 0.89711 0.15276 196.59 / 1), color(--oklch 0.89505 0.13708 199.31 / 1), color(--oklch 0.89504 0.12221 202.11 / 1), color(--oklch 0.89502 0.11028 204.96 / 1), color(--oklch 0.89502 0.10034 207.93 / 1), color(--oklch 0.895 0.09216 210.95 / 1), color(--oklch 0.89499 0.08517 214.1 / 1), color(--oklch 0.89499 0.07918 217.37 / 1), color(--oklch 0.89499 0.07401 220.78 / 1), color(--oklch 0.89499 0.06955 224.33 / 1), color(--oklch 0.89499 0.06569 228.03 / 1), color(--oklch 0.89498 0.06237 231.88 / 1), color(--oklch 0.89499 0.05949 235.92 / 1), color(--oklch 0.89497 0.05709 240.08 / 1), color(--oklch 0.89498 0.05504 244.48 / 1), color(--oklch 0.89498 0.05341 249.01 / 1), color(--oklch 0.89497 0.05215 253.7 / 1), color(--oklch 0.89497 0.05126 258.51 / 1), color(--oklch 0.89496 0.05075 263.44 / 1), color(--oklch 0.89495 0.05063 268.45 / 1), color(--oklch 0.89496 0.05088 273.51 / 1), color(--oklch 0.89495 0.05156 278.57 / 1), color(--oklch 0.89494 0.05266 283.61 / 1), color(--oklch 0.89494 0.0542 288.57 / 1), color(--oklch 0.89494 0.05623 293.44 / 1), color(--oklch 0.89493 0.0588 298.19 / 1), color(--oklch 0.89493 0.06192 302.78 / 1), color(--oklch 0.89492 0.0657 307.21 / 1), color(--oklch 0.89491 0.07022 311.49 / 1), color(--oklch 0.89492 0.07558 315.56 / 1), color(--oklch 0.8949 0.08201 319.49 / 1), color(--oklch 0.8949 0.0897 323.25 / 1), color(--oklch 0.89413 0.09833 326.36 / 1), color(--oklch 0.88972 0.10274 326.4 / 1), color(--oklch 0.88709 0.10539 326.42 / 1), color(--oklch 0.8866 0.10588 326.42 / 1), color(--oklch 0.8873 0.10102 328.09 / 1), color(--oklch 0.88727 0.09341 331.65 / 1), color(--oklch 0.88723 0.087 335.3 / 1), color(--oklch 0.88726 0.08147 339.1 / 1), color(--oklch 0.88723 0.07682 342.98 / 1), color(--oklch 0.88724 0.07284 347 / 1), color(--oklch 0.88718 0.06954 351.1 / 1), color(--oklch 0.88717 0.06673 355.33 / 1)]

Lastly, all MINDE Chroma Reduction methods allow the setting of the JND. The default is usually specific to the perceptual space being used, but it should be noted that while a lower JND will give you a theoretically better value, some color spaces have quirks due to their unique geometric shape. Consider the color color(display-p3 1 1 0). If we were to gamut map it in LCh with a very low JND, we can see that the odd shape of LCh can cause us to get a very desaturated color. By using the default JND of 2 for LCh, the fuzziness of the MINDE will catch the more saturated yellow. This isn't a problem in OkLCh, but it has its own quirks as well.

JND 0

JND 2

LCh Chroma

The lch-chroma gamut mapping is registered in Color by default

LCh Chroma applies MINDE Chroma Reduction within the CIELCh color space. This is the default in ColorAide.

Note

As most colors in ColorAide use a D65 white point by default, LCh D65 is used as the gamut mapping color space.

CIELCh, is not necessarily the best perceptual color space available, but it is a generally well understood color space that has been available a long time. It does suffer from a purple shift when dealing with blue colors, but can generally handle colors in very wide gamuts reasonably due to its fairly consistent shape well past the spectral locus.

CSS originally proposed MINDE Chroma Reduction with CIELCh, but has later changed to OkLCh. It is possible that the entire choice in algorithms could change as well in the future. We do offer an OkLCh variant, but we currently still use CIELCh due to its consistency even with colors far outside the gamut. If you are working within reasonable gamuts, OkLCh may be a better choice.

LCh Chroma is the default gamut mapping algorithm unless otherwise changed, and can be performed by simply calling fit() or by calling fit(method='lch-chroma').

>>> c = Color('srgb', [2, -1, 0])
>>> c.fit(method='lch-chroma')
color(srgb 1 0.39658 0.38511 / 1)

Additionally, the JND target can be controlled for tighter or looser gamut mapping via the jnd option. The default is 2.

>>> c = Color('srgb', [2, -1, 0])
>>> c.fit(method='lch-chroma', jnd=0.2)
color(srgb 1 0.4342 0.41183 / 1)

OkLCh Chroma

The lch-chroma gamut mapping is registered in Color by default

The CSS Color Level 4 specification currently recommends using the perceptually uniform OkLCh color space with the MINDE Chroma Reduction approach.

OkLCh does a much better job holding hues constant. When combined with gamut mapping, it generally does a better job than CIELCh, but it does have limitations. When colors get near the edge of the visible spectrum, the shape of the color space distorts, and gamut mapping will not be as good. But if you are working within reasonable gamuts, it may be an excellent option.

>>> c = Color('srgb', [2, -1, 0])
>>> c.fit(method='oklch-chroma')
color(srgb 1 0.60354 0.66617 / 1)

Additionally, the JND target can be controlled for tighter or looser gamut mapping via the jnd option. The default is 0.02.

>>> c = Color('srgb', [2, -1, 0])
>>> c.fit(method='oklch-chroma', jnd=0.002)
color(srgb 1 0.63219 0.68048 / 1)

HCT Chroma

The hct-chroma gamut mapping is not registered in Color by default

Warning

This approach was specifically added to help produce tonal palettes, but with the recent addition of the ray trace approach to chroma reduction in any perceptual space, it is recommended that users apply that approach as it performs a tight chroma reduction much quicker, and it doesn't require a special ∆E method.

On occasions, MINDE approach can be slightly more accurate very close to white due to the way ray trace handles HCT's atypical achromatic response, but differences should be imperceptible to the eye at such lightness levels making the the improved performance of the ray trace approach much more desirable.

>>> c = Color('hct', [325, 24, 50])
>>> tones = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 95, 100]
>>> Steps([c.clone().set('tone', tone).convert('srgb').to_string(hex=True, fit={'method': 'raytrace', 'pspace': 'hct'}) for tone in tones])
['#000000', '#29132e', '#3f2844', '#573e5b', '#705574', '#8a6d8d', '#a587a8', '#c1a1c3', '#debcdf', '#fbd7fc', '#ffebfd', '#ffffff']

Much like the other LCh chroma reduction algorithms, HCT Chroma performs gamut mapping exactly like LCh Chroma with the exception that it uses the HCT color space as the working LCh color space.

Google's Material Design uses a new color space called HCT. It uses the hue and chroma from CAM16 space and the tone/lightness from the CIELab space. HCT takes advantage of the good hue preservation of CAM16 and has the better lightness predictability of CIELab. Using these characteristics, the color space is adept at generating tonal palettes with predictable lightness. This makes it easier to construct UIs with decent contrast. But to do this well, you must work in HCT and gamut map in HCT. For this reason, the HCT Chroma gamut mapping method was added.

HCT Chroma is computationally the most expensive gamut mapping method that is offered. Since the color space used is based on the already computationally expensive CAM16 color space, and is made more expensive by blending that color space with CIELab, it is not the most performant approach, but when used in conjunction with the HCT color space, it can allow creating good tonal palettes:

>>> c = Color('hct', [325, 24, 50])
>>> tones = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 95, 100]
>>> Steps([c.clone().set('tone', tone).convert('srgb').to_string(hex=True, fit={'method': 'hct-chroma', 'jnd': 0.0}) for tone in tones])
['#000000', '#29132e', '#3f2844', '#573e5b', '#705574', '#8a6d8d', '#a587a8', '#c1a1c3', '#debcdf', '#fbd7fc', '#ffebfd', '#ffffff']

As shown above, the JND target can be controlled for tighter or looser gamut mapping via the jnd option. The default is 2, but to get tonal palette results comparable to Google Material, we are using 0.0.

To HCT Chroma plugin is not registered by default, but can be added by subclassing Color. You must register the ∆Ehct distancing algorithm and the HCT color space as well.

from coloraide import Color as Base
from coloraide.gamut.fit_hct_chroma import HCTChroma
from coloraide.distance.delta_e_hct import DEHCT
from coloraide.spaces.hct import HCT

class Color(Base): ...

Color.register([HCT(), DEHCT(), HCTChroma()])

Ray Tracing Chroma Reduction

Experimental Gamut Mapping

ColorAide has developed an experimental chroma reduction technique that employs ray tracing. This approach specifically targets RGB gamuts, or spaces that can be represented with RGB gamuts. Additionally, if ColorAide can detect a linear version of the targeted RGB gamut, that version will be used automatically for best results. Currently ColorAide can gamut map all officially supported color spaces as they either have an RGB gamut or can be coerced into one.

The way this approach works is it takes a given color and converts it to a perceptual LCh like color space. Then the achromatic version of the color (chroma set to zero) is calculated. If the achromatic color exceeds the maximum or minimum lightness of the gamut, the respective maximum or minimum achromatic color is returned.

Assuming the lightness is within bounds, a ray is cast from the inside of the cube, from the achromatic point to the current color. The intersection along this path with the RGB gamut surface is then found.

Ray Trace Algorithm

Ray trace algorithm is based on the slab method. The intersection that is selected is the first one encountered when following the ray from the origin point in the direction of the specified end point.

The intersection of the line and the gamut surface represents an approximation of the the most saturated color for that lightness and hue, but because the RGB space is not perceptual, the initial approximation is likely to be off because decreasing chroma and holding lightness and hue constant in a perceptual space will create a twisting path through the RGB space. In order to converge on a point as close as possible to the actual most saturated color with the given hue and lightness, we must refine our result with a few additional iterations.

In order to converge on the actual chroma reduced color we seek, we can take the first intersection we find and correct the color in the perceptual color space by setting the hue and lightness back to the original color's hue and lightness. The corrected color becomes our new current color and should be much closer color on the reduced chroma line. We can repeat this process a few more times, each time finding a better, closer color on the path. After about three additional iterations (a combined total of four for the entire process), we will be close enough where we can stop. Finally, we can then clip off floating point math errors. With this, we will now have an accurate approximation of the color we seek.

Ray Trace Gamut Mapping Example

Note

For accuracy, iterations could be increased further which would reduce a potential ∆h shift even more, but ColorAide has opted to keep iterations at 4 which can gamut map colors to sRGB with ∆h shift of less than 1, and when gamut mapping to a larger space like Rec. 2020, still below a ∆h of 2. It can be noted that the further away the gamut boundary is from the achromatic line, the less accurate gamut mapping can be, but with a shift still below ∆h of 2 for a large space like Rec. 2020, the accuracy is plenty sufficient.

The results are comparable to MINDE using a low JND, but resolves much faster and within more predictable, consistent time.

>>> Color('oklch(90% 0.8 270)').fit('srgb', method='lch-raytrace')
color(--oklch 0.76773 0.15856 309.38 / 1)

As noted earlier, this method specifically targets RGB gamuts. This is because the ray tracing is performed on a simple RGB cube which is easy to calculate. ColorAide maps almost all colors to an RGB gamut, if they have one. And those gamuts are often associated with a linear RGB counterpart which is preferred when gamut mapping, but there are a few color spaces/models that do not map to an obvious RGB gamut.

HPLuv, which is only defined as a cylindrical color space that represent only a subset of the sRGB color space, has no defined RGB gamut on which to operate on. Additionally Okhsl and Okhsv are two cylindrical color spaces, based on the perceptual Oklab color space, that are meant to target the sRGB gamut, but are only a loose approximation which actually can slightly clip the sRGB gamut while simultaneously containing a few colors that exceed the sRGB gamut. ColorAide will not automatically associate these color spaces with an RGB gamut as their is not one that precisely represent the colors in Okhsl and Okhsv.

With that said, ColorAide will translate these spaces into a cube shape to apply gamut mapping on them if they are specifically used. In the case of HPLuv, results are usually fine, but you may find that gamut mapping Okhsl may not provide the intended results. It should be noted that the currently suggested CSS gamut mapping algorithm (oklch-chroma) does not do much better, so, for Okhsl and Okhsv, it is better to use the closest RGB gamut.

>>> Steps([c.fit('okhsl', method='oklch-raytrace') for c in Color.steps(['oklch(90% 0.4 0)', 'oklch(90% 0.4 360)'], steps=100, space='oklch', hue='longer')])
[color(--oklch 0.9 0.03085 0 / 1), color(--oklch 0.9 0.03372 3.6364 / 1), color(--oklch 0.9 0.03428 7.2727 / 1), color(--oklch 0.9 0.03333 10.909 / 1), color(--oklch 0.9 0.03093 14.545 / 1), color(--oklch 0.9 0.02652 18.182 / 1), color(--oklch 0.9 0.01818 21.818 / 1), color(--oklch 0.9 0.00125 205.45 / 1), color(--oklch 0.9 0.08837 209.09 / 1), color(--oklch 0.9 0.00249 32.727 / 1), color(--oklch 0.9 0.01967 36.364 / 1), color(--oklch 0.9 0.02693 40 / 1), color(--oklch 0.9 0.03129 43.636 / 1), color(--oklch 0.9 0.03452 47.273 / 1), color(--oklch 0.9 0.03733 50.909 / 1), color(--oklch 0.9 0.04004 54.545 / 1), color(--oklch 0.9 0.04282 58.182 / 1), color(--oklch 0.9 0.04576 61.818 / 1), color(--oklch 0.9 0.04888 65.455 / 1), color(--oklch 0.9 0.05211 69.091 / 1), color(--oklch 0.9 0.05531 72.727 / 1), color(--oklch 0.9 0.0581 76.364 / 1), color(--oklch 0.9 0.05966 80 / 1), color(--oklch 0.9 0.05812 83.636 / 1), color(--oklch 0.9 0.04843 87.273 / 1), color(--oklch 0.9 0.01209 90.909 / 1), color(--oklch 0.9 0.15515 94.545 / 1), color(--oklch 0.9 0.1861 98.182 / 1), color(--oklch 0.9 0.18832 101.82 / 1), color(--oklch 0.9 0.19138 105.45 / 1), color(--oklch 0.9 0.01909 289.09 / 1), color(--oklch 0.9 0.03162 292.73 / 1), color(--oklch 0.9 0.02113 296.36 / 1), color(--oklch 0.9 0.03591 300 / 1), color(--oklch 0.9 0.22247 123.64 / 1), color(--oklch 0.9 0.23299 127.27 / 1), color(--oklch 0.9 0.2456 130.91 / 1), color(--oklch 0.9 0.24384 134.55 / 1), color(--oklch 0.9 0.22238 138.18 / 1), color(--oklch 0.9 0.20619 141.82 / 1), color(--oklch 0.9 0.19362 145.45 / 1), color(--oklch 0.9 0.03067 329.09 / 1), color(--oklch 0.9 0.00536 152.73 / 1), color(--oklch 0.9 0.0202 156.36 / 1), color(--oklch 0.9 0.02441 160 / 1), color(--oklch 0.9 0.02118 163.64 / 1), color(--oklch 0.9 0.01041 167.27 / 1), color(--oklch 0.9 0.01147 350.91 / 1), color(--oklch 0.9 0.05689 354.55 / 1), color(--oklch 0.9 0.15497 178.18 / 1), color(--oklch 0.9 0.1556 181.82 / 1), color(--oklch 0.9 0.15708 185.45 / 1), color(--oklch 0.9 0.15589 189.09 / 1), color(--oklch 0.9 0.15425 192.73 / 1), color(--oklch 0.9 0.15008 196.36 / 1), color(--oklch 0.9 0.12658 200 / 1), color(--oklch 0.9 0.10983 203.64 / 1), color(--oklch 0.9 0.01686 27.273 / 1), color(--oklch 0.9 0.0287 210.91 / 1), color(--oklch 0.9 0.04084 214.55 / 1), color(--oklch 0.9 0.04427 218.18 / 1), color(--oklch 0.9 0.04441 221.82 / 1), color(--oklch 0.9 0.0431 225.45 / 1), color(--oklch 0.9 0.04112 229.09 / 1), color(--oklch 0.9 0.03883 232.73 / 1), color(--oklch 0.9 0.03641 236.36 / 1), color(--oklch 0.9 0.03393 240 / 1), color(--oklch 0.9 0.03138 243.64 / 1), color(--oklch 0.9 0.02866 247.27 / 1), color(--oklch 0.9 0.02552 250.91 / 1), color(--oklch 0.9 0.0212 254.55 / 1), color(--oklch 0.9 0.01317 258.18 / 1), color(--oklch 0.9 0.01887 81.818 / 1), color(--oklch 0.9 0.04813 265.45 / 1), color(--oklch 0.9 0.04811 269.09 / 1), color(--oklch 0.9 0.0831 92.727 / 1), color(--oklch 0.9 0.00036 276.36 / 1), color(--oklch 0.9 0.01827 280 / 1), color(--oklch 0.9 0.02615 283.64 / 1), color(--oklch 0.9 0.03053 287.27 / 1), color(--oklch 0.9 0.0331 290.91 / 1), color(--oklch 0.9 0.03429 294.55 / 1), color(--oklch 0.9 0.03388 298.18 / 1), color(--oklch 0.9 0.03076 301.82 / 1), color(--oklch 0.9 0.02106 305.45 / 1), color(--oklch 0.9 0.01717 129.09 / 1), color(--oklch 0.9 0.06811 312.73 / 1), color(--oklch 0.9 0.07289 316.36 / 1), color(--oklch 0.9 0.07874 320 / 1), color(--oklch 0.9 0.08599 323.64 / 1), color(--oklch 0.9 0.09033 327.27 / 1), color(--oklch 0.9 0.08316 330.91 / 1), color(--oklch 0.9 0.07729 334.55 / 1), color(--oklch 0.9 0.07245 338.18 / 1), color(--oklch 0.9 0.06842 341.82 / 1), color(--oklch 0.9 0.06506 345.45 / 1), color(--oklch 0.9 0.0235 169.09 / 1), color(--oklch 0.9 0.00322 172.73 / 1), color(--oklch 0.9 0.02296 356.36 / 1), color(--oklch 0.9 0.03085 0 / 1)]
>>> Steps([c.fit('okhsl', method='oklch-chroma') for c in Color.steps(['oklch(90% 0.4 0)', 'oklch(90% 0.4 360)'], steps=100, space='oklch', hue='longer')])
[color(--oklch 0.9 0.03085 0 / 1), color(--oklch 0.9 0.03372 3.6364 / 1), color(--oklch 0.9 0.03428 7.2727 / 1), color(--oklch 0.9 0.03333 10.909 / 1), color(--oklch 0.9 0.03093 14.545 / 1), color(--oklch 0.9 0.02652 18.182 / 1), color(--oklch 0.9 0.01818 21.818 / 1), color(--oklch 0.9 0.00125 205.45 / 1), color(--oklch 0.9 0.08837 209.09 / 1), color(--oklch 0.9 0.00249 32.727 / 1), color(--oklch 0.9 0.01967 36.364 / 1), color(--oklch 0.9 0.02693 40 / 1), color(--oklch 0.9 0.03129 43.636 / 1), color(--oklch 0.9 0.03452 47.273 / 1), color(--oklch 0.9 0.03733 50.909 / 1), color(--oklch 0.9 0.04004 54.545 / 1), color(--oklch 0.9 0.04282 58.182 / 1), color(--oklch 0.9 0.04576 61.818 / 1), color(--oklch 0.9 0.04888 65.455 / 1), color(--oklch 0.9 0.05211 69.091 / 1), color(--oklch 0.9 0.05531 72.727 / 1), color(--oklch 0.9 0.0581 76.364 / 1), color(--oklch 0.9 0.05966 80 / 1), color(--oklch 0.9 0.05812 83.636 / 1), color(--oklch 0.9 0.04843 87.273 / 1), color(--oklch 0.9 0.01209 90.909 / 1), color(--oklch 0.9 0.15515 94.545 / 1), color(--oklch 0.9 0.1861 98.182 / 1), color(--oklch 0.9 0.18832 101.82 / 1), color(--oklch 0.9 0.19138 105.45 / 1), color(--oklch 0.9 0.01909 289.09 / 1), color(--oklch 0.9 0.03162 292.73 / 1), color(--oklch 0.9 0.02113 296.36 / 1), color(--oklch 0.9 0.03591 300 / 1), color(--oklch 0.9 0.22247 123.64 / 1), color(--oklch 0.9 0.23299 127.27 / 1), color(--oklch 0.9 0.2456 130.91 / 1), color(--oklch 0.9 0.24384 134.55 / 1), color(--oklch 0.9 0.22238 138.18 / 1), color(--oklch 0.9 0.20619 141.82 / 1), color(--oklch 0.9 0.19362 145.45 / 1), color(--oklch 0.9 0.03067 329.09 / 1), color(--oklch 0.9 0.00536 152.73 / 1), color(--oklch 0.9 0.0202 156.36 / 1), color(--oklch 0.9 0.02441 160 / 1), color(--oklch 0.9 0.02118 163.64 / 1), color(--oklch 0.9 0.01041 167.27 / 1), color(--oklch 0.9 0.01147 350.91 / 1), color(--oklch 0.9 0.05689 354.55 / 1), color(--oklch 0.9 0.15497 178.18 / 1), color(--oklch 0.9 0.1556 181.82 / 1), color(--oklch 0.9 0.15708 185.45 / 1), color(--oklch 0.9 0.15589 189.09 / 1), color(--oklch 0.9 0.15425 192.73 / 1), color(--oklch 0.9 0.15008 196.36 / 1), color(--oklch 0.9 0.12658 200 / 1), color(--oklch 0.9 0.10983 203.64 / 1), color(--oklch 0.9 0.01686 27.273 / 1), color(--oklch 0.9 0.0287 210.91 / 1), color(--oklch 0.9 0.04084 214.55 / 1), color(--oklch 0.9 0.04427 218.18 / 1), color(--oklch 0.9 0.04441 221.82 / 1), color(--oklch 0.9 0.0431 225.45 / 1), color(--oklch 0.9 0.04112 229.09 / 1), color(--oklch 0.9 0.03883 232.73 / 1), color(--oklch 0.9 0.03641 236.36 / 1), color(--oklch 0.9 0.03393 240 / 1), color(--oklch 0.9 0.03138 243.64 / 1), color(--oklch 0.9 0.02866 247.27 / 1), color(--oklch 0.9 0.02552 250.91 / 1), color(--oklch 0.9 0.0212 254.55 / 1), color(--oklch 0.9 0.01317 258.18 / 1), color(--oklch 0.9 0.01887 81.818 / 1), color(--oklch 0.9 0.04813 265.45 / 1), color(--oklch 0.9 0.04811 269.09 / 1), color(--oklch 0.9 0.0831 92.727 / 1), color(--oklch 0.9 0.00036 276.36 / 1), color(--oklch 0.9 0.01827 280 / 1), color(--oklch 0.9 0.02615 283.64 / 1), color(--oklch 0.9 0.03053 287.27 / 1), color(--oklch 0.9 0.0331 290.91 / 1), color(--oklch 0.9 0.03429 294.55 / 1), color(--oklch 0.9 0.03388 298.18 / 1), color(--oklch 0.9 0.03076 301.82 / 1), color(--oklch 0.9 0.02106 305.45 / 1), color(--oklch 0.9 0.01717 129.09 / 1), color(--oklch 0.9 0.06811 312.73 / 1), color(--oklch 0.9 0.07289 316.36 / 1), color(--oklch 0.9 0.07874 320 / 1), color(--oklch 0.9 0.08599 323.64 / 1), color(--oklch 0.9 0.09033 327.27 / 1), color(--oklch 0.9 0.08316 330.91 / 1), color(--oklch 0.9 0.07729 334.55 / 1), color(--oklch 0.9 0.07245 338.18 / 1), color(--oklch 0.9 0.06842 341.82 / 1), color(--oklch 0.9 0.06506 345.45 / 1), color(--oklch 0.9 0.0235 169.09 / 1), color(--oklch 0.9 0.00322 172.73 / 1), color(--oklch 0.9 0.02296 356.36 / 1), color(--oklch 0.9 0.03085 0 / 1)]
>>> Steps([c.fit('srgb', method='oklch-raytrace') for c in Color.steps(['oklch(90% 0.4 0)', 'oklch(90% 0.4 360)'], steps=100, space='oklch', hue='longer')])
[color(--oklch 0.9 0.05626 360 / 1), color(--oklch 0.9 0.05492 3.6364 / 1), color(--oklch 0.9 0.05385 7.2727 / 1), color(--oklch 0.9 0.05303 10.909 / 1), color(--oklch 0.9 0.05244 14.545 / 1), color(--oklch 0.9 0.05207 18.182 / 1), color(--oklch 0.9 0.0519 21.818 / 1), color(--oklch 0.9 0.05195 25.455 / 1), color(--oklch 0.9 0.0522 29.091 / 1), color(--oklch 0.9 0.05266 32.727 / 1), color(--oklch 0.9 0.05334 36.364 / 1), color(--oklch 0.9 0.05426 40 / 1), color(--oklch 0.9 0.05543 43.637 / 1), color(--oklch 0.9 0.05688 47.273 / 1), color(--oklch 0.9 0.05865 50.91 / 1), color(--oklch 0.9 0.06078 54.548 / 1), color(--oklch 0.9 0.06333 58.188 / 1), color(--oklch 0.9 0.06637 61.829 / 1), color(--oklch 0.9 0.07002 65.474 / 1), color(--oklch 0.9 0.07439 69.125 / 1), color(--oklch 0.9 0.07969 72.785 / 1), color(--oklch 0.9 0.08617 76.458 / 1), color(--oklch 0.9 0.09424 80.154 / 1), color(--oklch 0.9 0.1045 83.887 / 1), color(--oklch 0.9 0.11796 87.682 / 1), color(--oklch 0.9 0.1365 91.593 / 1), color(--oklch 0.9 0.16207 95.465 / 1), color(--oklch 0.9 0.1861 98.19 / 1), color(--oklch 0.9 0.18832 101.82 / 1), color(--oklch 0.9 0.19138 105.45 / 1), color(--oklch 0.9 0.19534 109.09 / 1), color(--oklch 0.9 0.20029 112.73 / 1), color(--oklch 0.9 0.20635 116.36 / 1), color(--oklch 0.9 0.21367 120 / 1), color(--oklch 0.9 0.22247 123.64 / 1), color(--oklch 0.9 0.23299 127.27 / 1), color(--oklch 0.9 0.2456 130.91 / 1), color(--oklch 0.9 0.24385 134.54 / 1), color(--oklch 0.9 0.22238 138.18 / 1), color(--oklch 0.9 0.20619 141.82 / 1), color(--oklch 0.9 0.19362 145.45 / 1), color(--oklch 0.9 0.1837 149.09 / 1), color(--oklch 0.9 0.17581 152.73 / 1), color(--oklch 0.9 0.16954 156.36 / 1), color(--oklch 0.9 0.16461 160 / 1), color(--oklch 0.9 0.16083 163.64 / 1), color(--oklch 0.9 0.15804 167.27 / 1), color(--oklch 0.9 0.15617 170.91 / 1), color(--oklch 0.9 0.15516 174.55 / 1), color(--oklch 0.9 0.15497 178.18 / 1), color(--oklch 0.9 0.1556 181.82 / 1), color(--oklch 0.9 0.15708 185.45 / 1), color(--oklch 0.9 0.15589 189.09 / 1), color(--oklch 0.9 0.15425 192.73 / 1), color(--oklch 0.89999 0.15015 196.35 / 1), color(--oklch 0.89999 0.12667 199.98 / 1), color(--oklch 0.9 0.10989 203.62 / 1), color(--oklch 0.9 0.09739 207.26 / 1), color(--oklch 0.9 0.08776 210.9 / 1), color(--oklch 0.9 0.08015 214.53 / 1), color(--oklch 0.9 0.07403 218.17 / 1), color(--oklch 0.9 0.06904 221.81 / 1), color(--oklch 0.9 0.06493 225.45 / 1), color(--oklch 0.9 0.06151 229.09 / 1), color(--oklch 0.9 0.05866 232.72 / 1), color(--oklch 0.9 0.05628 236.36 / 1), color(--oklch 0.9 0.0543 240 / 1), color(--oklch 0.9 0.05265 243.64 / 1), color(--oklch 0.9 0.0513 247.27 / 1), color(--oklch 0.9 0.05022 250.91 / 1), color(--oklch 0.9 0.04938 254.55 / 1), color(--oklch 0.9 0.04876 258.18 / 1), color(--oklch 0.9 0.04834 261.82 / 1), color(--oklch 0.9 0.04813 265.45 / 1), color(--oklch 0.9 0.04811 269.09 / 1), color(--oklch 0.9 0.04828 272.73 / 1), color(--oklch 0.9 0.04865 276.36 / 1), color(--oklch 0.9 0.04923 280 / 1), color(--oklch 0.9 0.05003 283.64 / 1), color(--oklch 0.9 0.05106 287.27 / 1), color(--oklch 0.9 0.05235 290.91 / 1), color(--oklch 0.9 0.05392 294.55 / 1), color(--oklch 0.9 0.05583 298.18 / 1), color(--oklch 0.9 0.05813 301.82 / 1), color(--oklch 0.9 0.06087 305.45 / 1), color(--oklch 0.9 0.06416 309.09 / 1), color(--oklch 0.9 0.06811 312.73 / 1), color(--oklch 0.9 0.07289 316.36 / 1), color(--oklch 0.9 0.07874 320 / 1), color(--oklch 0.9 0.08599 323.64 / 1), color(--oklch 0.9 0.09033 327.27 / 1), color(--oklch 0.9 0.08316 330.91 / 1), color(--oklch 0.9 0.07729 334.55 / 1), color(--oklch 0.9 0.07245 338.18 / 1), color(--oklch 0.9 0.06842 341.82 / 1), color(--oklch 0.9 0.06506 345.45 / 1), color(--oklch 0.9 0.06223 349.09 / 1), color(--oklch 0.9 0.05987 352.73 / 1), color(--oklch 0.9 0.05789 356.36 / 1), color(--oklch 0.9 0.05626 360 / 1)]

LCh Ray Tracing Chroma Reduction

The lch-raytrace gamut mapping is registered in Color by default.

This is a ray tracing approach to chroma reduction using CIELCh D65. This can be a faster approach to gamut mapping.

>>> Color('oklch(90% 0.8 270)').fit('srgb', method='lch-raytrace')
color(--oklch 0.76773 0.15856 309.38 / 1)
>>> Color('oklch(90% 0.8 270)').fit('hsl', method='lch-raytrace')
color(--oklch 0.76773 0.15856 309.38 / 1)

OkLCh Ray Tracing Chroma Reduction

The oklch-raytrace gamut mapping is registered in Color by default.

This is a ray tracing approach to chroma reduction using OkLCh. This can be a faster approach to gamut mapping.

>>> Color('oklch(90% 0.8 270)').fit('srgb', method='oklch-raytrace')
color(--oklch 0.9 0.04813 270 / 1)
>>> Color('oklch(90% 0.8 270)').fit('hsl', method='oklch-raytrace')
color(--oklch 0.9 0.04813 270 / 1)

Ray Tracing Chroma Reduction in Any Perceptual Space

The raytrace gamut mapping is registered in Color by default.

This is a generic ray tracing approach that allows a user to specify any perceptual LCh-ish or Lab-ish color space for use in gamut mapping with ray tracing. Any perceptual space in the LCh-ish or Lab-ish form can be specified via the pspace parameter.

>>> Color('oklch(50% 0.4 270)').fit('srgb', method='raytrace', pspace='cam16-jmh')
color(--oklch 0.48631 0.29467 277.07 / 1)
>>> Color('oklch(50% 0.4 270)').fit('srgb', method='raytrace', pspace='luv')
color(--oklch 0.47398 0.29406 276.64 / 1)

Deprecated 3.3

The lch parameter in the raytrace method has been deprecated in 3.3 in favor of the pspace parameter that allows for perceptual spaces in both LCh and Lab form.

It should be noted that gamut mapping will be limited by the capabilities of the perceptual space being used. Some color spaces can swing to varying degrees outside the visible spectrum and some perceptual models can tolerate this more than others, and this can affect gamut mapping results, this does not mean the gamut mapping approach does not work, only that some color models may work better under more constraints than others.

Consider the example below. We take a very saturated yellow in Display P3 (color(display-p3 1 1 0)) and then we interpolate it's whiteness between 0, masking off chroma so that we are only interpolating lightness. We do this interpolation in CIELCh, which is known to have chroma that can swing very far outside the visible spectrum when interpolating hues at more extreme lightness. Finally, we gamut map in various LCh models. What we can observe is some models will struggle to map some of these colors as the hue preservation can break down at extreme limits. In the cases below, this specifically happens due to negative XYZ values that are produced due to high chroma in lower lightness. Some models can tolerate this more than others.

>>> yellow = Color('color(display-p3 1 1 0)')
>>> lightness_mask = Color('lch(0% none none)')
>>> Steps([c.fit('srgb', method='raytrace', pspace='oklch') for c in Color.steps([yellow, lightness_mask], steps=20, space='lch')])
[color(--lch 97.252 94.474 99.968 / 1), color(--lch 92.116 90.244 100.33 / 1), color(--lch 86.981 86.019 100.72 / 1), color(--lch 81.848 81.8 101.15 / 1), color(--lch 76.716 77.587 101.63 / 1), color(--lch 71.583 73.375 102.14 / 1), color(--lch 66.446 69.159 102.67 / 1), color(--lch 61.303 64.934 103.2 / 1), color(--lch 56.155 60.694 103.7 / 1), color(--lch 50.999 56.432 104.17 / 1), color(--lch 45.834 52.143 104.56 / 1), color(--lch 40.663 47.836 104.91 / 1), color(--lch 35.631 43.818 106.43 / 1), color(--lch 30.421 39.374 106.27 / 1), color(--lch 25.124 34.439 105.77 / 1), color(--lch 19.731 28.372 105.2 / 1), color(--lch 14.159 21.103 104.22 / 1), color(--lch 8.1264 12.265 101.36 / 1), color(--lch 1.9671 2.9842 79.847 / 1), color(--lch 0 0 none / 1)]
>>> Steps([c.fit('srgb', method='raytrace', pspace='lch99o') for c in Color.steps([yellow, lightness_mask], steps=20, space='lch')])
[color(--lch 97.038 22.96 96.153 / 1), color(--lch 92.214 89.989 97.362 / 1), color(--lch 87.091 85.717 97.183 / 1), color(--lch 81.969 81.446 96.949 / 1), color(--lch 76.847 77.184 96.878 / 1), color(--lch 71.724 72.924 96.861 / 1), color(--lch 66.6 68.664 96.843 / 1), color(--lch 61.473 64.401 96.827 / 1), color(--lch 56.343 60.137 96.818 / 1), color(--lch 51.21 55.869 96.823 / 1), color(--lch 46.071 51.599 96.852 / 1), color(--lch 40.924 47.323 96.92 / 1), color(--lch 35.767 43.039 97.053 / 1), color(--lch 30.594 38.745 97.292 / 1), color(--lch 25.396 34.033 97.641 / 1), color(--lch 20.157 28.339 98.228 / 1), color(--lch 14.844 21.718 99.385 / 1), color(--lch 9.3945 14.051 98.579 / 1), color(--lch 4.0615 6.0794 98.13 / 1), color(--lch 0 0 none / 1)]
>>> Steps([c.fit('srgb', method='raytrace', pspace='hct') for c in Color.steps([yellow, lightness_mask], steps=20, space='lch')])
[color(--lch 97.316 94.474 99.598 / 1), color(--lch 92.189 90.239 99.889 / 1), color(--lch 87.062 86.008 100.21 / 1), color(--lch 81.937 81.782 100.56 / 1), color(--lch 76.813 77.56 100.95 / 1), color(--lch 71.688 73.334 101.34 / 1), color(--lch 66.563 69.099 101.69 / 1), color(--lch 61.437 64.832 101.85 / 1), color(--lch 56.31 60.536 101.82 / 1), color(--lch 51.178 56.255 101.94 / 1), color(--lch 46.042 51.962 102.01 / 1), color(--lch 40.898 47.651 101.99 / 1), color(--lch 35.745 43.318 101.81 / 1), color(--lch 30.577 38.956 101.39 / 1), color(--lch 25.384 34.191 100.69 / 1), color(--lch 20.152 28.407 99.637 / 1), color(--lch 14.849 21.647 97.546 / 1), color(--lch 9.4049 13.959 92.117 / 1), color(--lch 4.0759 6.2258 78.058 / 1), color(--lch 0 0 none / 1)]
>>> Steps([c.fit('srgb', method='raytrace', pspace='jzczhz') for c in Color.steps([yellow, lightness_mask], steps=20, space='lch')])
[color(--lch 97.139 94.455 100.44 / 1), color(--lch 91.987 90.221 100.84 / 1), color(--lch 86.837 85.991 101.27 / 1), color(--lch 81.689 81.766 101.73 / 1), color(--lch 76.544 77.547 102.23 / 1), color(--lch 71.395 73.327 102.76 / 1), color(--lch 66.237 69.101 103.33 / 1), color(--lch 61.067 64.865 103.92 / 1), color(--lch 55.883 60.613 104.53 / 1), color(--lch 50.68 56.34 105.17 / 1), color(--lch 45.456 52.052 105.87 / 1), color(--lch 40.235 47.847 107.09 / 1), color(--lch 25.052 77.611 313.27 / 1), color(--lch 20.684 69.351 313.27 / 1), color(--lch 16.234 60.938 313.27 / 1), color(--lch 11.635 52.242 313.27 / 1), color(--lch 6.7735 42.632 313.1 / 1), color(--lch 2.6403 23.628 305.32 / 1), color(--lch 0 0 none / 1), color(--lch 0 0 none / 1)]
>>> Steps([c.fit('srgb', method='raytrace', pspace='lchuv') for c in Color.steps([yellow, lightness_mask], steps=20, space='lch')])
[color(--lch 97.317 94.47 99.572 / 1), color(--lch 92.189 90.24 99.895 / 1), color(--lch 87.062 86.016 100.26 / 1), color(--lch 81.936 81.799 100.68 / 1), color(--lch 76.811 77.59 101.16 / 1), color(--lch 71.686 73.383 101.67 / 1), color(--lch 66.559 69.173 102.18 / 1), color(--lch 61.431 64.955 102.68 / 1), color(--lch 56.302 60.723 103.14 / 1), color(--lch 51.169 56.469 103.51 / 1), color(--lch 46.032 52.183 103.74 / 1), color(--lch 40.889 47.854 103.71 / 1), color(--lch 35.738 43.476 103.34 / 1), color(--lch 30.572 39.052 102.52 / 1), color(--lch 25.382 34.234 101.29 / 1), color(--lch 20.151 28.423 99.916 / 1), color(--lch 14.846 21.683 98.546 / 1), color(--lch 8.7143 33.663 285.14 / 1), color(--lch 3.7511 22.378 280.24 / 1), color(--lch 0 0 none / 1)]

Almost any perceptual model, if pushed far enough, can start to break down. Converting to and from these spaces before reducing chroma can introduce such disparities. Every color space has limitations, some spaces just have more agreeable ones.

If you are working within reasonable gamuts, most will work just fine. And if you want to do something like above, holding chroma really high for all lightness values, you will often find that it works best when you do it directly in the color space that is doing the gamut mapping as you will have to "fit" the color before converting to another color space.

>>> yellow = Color('color(display-p3 1 1 0)')
>>> lightness_mask = Color('oklch(0% none none)')
>>> Steps([c.fit('srgb', method='raytrace', pspace='oklch') for c in Color.steps([yellow, lightness_mask], steps=20, space='oklch')])
[color(--oklch 0.96476 0.21094 110.23 / 1), color(--oklch 0.91399 0.19984 110.23 / 1), color(--oklch 0.86321 0.18874 110.23 / 1), color(--oklch 0.81243 0.17763 110.23 / 1), color(--oklch 0.76166 0.16653 110.23 / 1), color(--oklch 0.71088 0.15543 110.23 / 1), color(--oklch 0.6601 0.14433 110.23 / 1), color(--oklch 0.60932 0.13323 110.23 / 1), color(--oklch 0.55855 0.12212 110.23 / 1), color(--oklch 0.50777 0.11102 110.23 / 1), color(--oklch 0.45699 0.09992 110.23 / 1), color(--oklch 0.40622 0.08882 110.23 / 1), color(--oklch 0.35544 0.07772 110.23 / 1), color(--oklch 0.30466 0.06661 110.23 / 1), color(--oklch 0.25389 0.05551 110.23 / 1), color(--oklch 0.20311 0.04441 110.23 / 1), color(--oklch 0.15233 0.03331 110.23 / 1), color(--oklch 0.10155 0.0222 110.23 / 1), color(--oklch 0.05078 0.0111 110.23 / 1), color(--oklch 0 0 none / 1)]
>>> yellow = Color('color(display-p3 1 1 0)')
>>> lightness_mask = Color('color(--lch99o 0% none none)')
>>> Steps([c.fit('srgb', method='raytrace', pspace='lch99o') for c in Color.steps([yellow, lightness_mask], steps=20, space='lch99o')])
[color(--lch99o 97.304 20.716 98.051 / 1), color(--lch99o 92.182 43.933 97.877 / 1), color(--lch99o 87.061 42.844 97.877 / 1), color(--lch99o 81.94 41.72 97.877 / 1), color(--lch99o 76.819 40.558 97.877 / 1), color(--lch99o 71.697 39.356 97.877 / 1), color(--lch99o 66.576 38.109 97.877 / 1), color(--lch99o 61.455 36.813 97.877 / 1), color(--lch99o 56.334 35.464 97.877 / 1), color(--lch99o 51.212 34.055 97.877 / 1), color(--lch99o 46.091 32.58 97.877 / 1), color(--lch99o 40.97 31.031 97.877 / 1), color(--lch99o 35.849 29.399 97.877 / 1), color(--lch99o 30.727 27.581 97.877 / 1), color(--lch99o 25.606 25.272 97.877 / 1), color(--lch99o 20.485 22.368 97.877 / 1), color(--lch99o 15.364 18.721 97.877 / 1), color(--lch99o 10.242 14.071 97.877 / 1), color(--lch99o 5.1212 8.0424 97.877 / 1), color(--lch99o 0 0 none / 1)]
>>> yellow = Color('color(display-p3 1 1 0)')
>>> lightness_mask = Color('color(--hct none none 0%)')
>>> Steps([c.fit('srgb', method='raytrace', pspace='hct') for c in Color.steps([yellow, lightness_mask], steps=20, space='hct')])
[color(--hct 111.07 75.366 96.849 / 1), color(--hct 111.07 72.594 91.752 / 1), color(--hct 111.07 69.788 86.655 / 1), color(--hct 111.07 66.946 81.557 / 1), color(--hct 111.07 64.066 76.46 / 1), color(--hct 111.07 61.147 71.363 / 1), color(--hct 111.07 58.186 66.265 / 1), color(--hct 111.07 55.18 61.168 / 1), color(--hct 111.07 52.127 56.071 / 1), color(--hct 111.07 49.024 50.973 / 1), color(--hct 111.07 45.866 45.876 / 1), color(--hct 111.07 42.649 40.779 / 1), color(--hct 111.07 39.368 35.681 / 1), color(--hct 111.07 36.015 30.584 / 1), color(--hct 111.07 32.581 25.487 / 1), color(--hct 111.07 29.056 20.389 / 1), color(--hct 111.07 25.424 15.292 / 1), color(--hct 111.07 21.666 10.195 / 1), color(--hct 111.07 17.402 5.0973 / 1), color(--hct none 0 0 / 1)]
>>> yellow = Color('color(display-p3 1 1 0)')
>>> lightness_mask = Color('color(jzczhz 0% none none)')
>>> Steps([c.fit('srgb', method='raytrace', pspace='jzczhz') for c in Color.steps([yellow, lightness_mask], steps=20, space='jzczhz')])
[color(jzczhz 0.2083 0.13765 102.74 / 1), color(jzczhz 0.19734 0.13381 102.74 / 1), color(jzczhz 0.18637 0.12977 102.74 / 1), color(jzczhz 0.17541 0.12552 102.74 / 1), color(jzczhz 0.16445 0.12103 102.74 / 1), color(jzczhz 0.15348 0.1163 102.74 / 1), color(jzczhz 0.14252 0.1113 102.74 / 1), color(jzczhz 0.13156 0.10601 102.74 / 1), color(jzczhz 0.12059 0.10039 102.74 / 1), color(jzczhz 0.10963 0.09443 102.74 / 1), color(jzczhz 0.09867 0.08808 102.74 / 1), color(jzczhz 0.08771 0.0813 102.74 / 1), color(jzczhz 0.07674 0.07405 102.74 / 1), color(jzczhz 0.06578 0.06625 102.74 / 1), color(jzczhz 0.05482 0.05785 102.74 / 1), color(jzczhz 0.04385 0.04873 102.74 / 1), color(jzczhz 0.03289 0.03876 102.74 / 1), color(jzczhz 0.02193 0.02772 102.74 / 1), color(jzczhz 0.01096 0.01523 102.74 / 1), color(jzczhz 0 0 none / 1)]
>>> yellow = Color('color(display-p3 1 1 0)')
>>> lightness_mask = Color('color(--lchuv 0% none none)')
>>> Steps([c.fit('srgb', method='raytrace', pspace='lchuv') for c in Color.steps([yellow, lightness_mask], steps=20, space='lchuv')])
[color(--lchuv 96.849 106.77 85.874 / 1), color(--lchuv 91.752 101.15 85.874 / 1), color(--lchuv 86.655 95.528 85.874 / 1), color(--lchuv 81.557 89.909 85.874 / 1), color(--lchuv 76.46 84.29 85.874 / 1), color(--lchuv 71.363 78.67 85.874 / 1), color(--lchuv 66.265 73.051 85.874 / 1), color(--lchuv 61.168 67.432 85.874 / 1), color(--lchuv 56.071 61.812 85.874 / 1), color(--lchuv 50.973 56.193 85.874 / 1), color(--lchuv 45.876 50.574 85.874 / 1), color(--lchuv 40.779 44.954 85.874 / 1), color(--lchuv 35.681 39.335 85.874 / 1), color(--lchuv 30.584 33.716 85.874 / 1), color(--lchuv 25.487 28.097 85.874 / 1), color(--lchuv 20.389 22.477 85.874 / 1), color(--lchuv 15.292 16.858 85.874 / 1), color(--lchuv 10.195 11.239 85.874 / 1), color(--lchuv 5.0973 5.6193 85.874 / 1), color(--lchuv 0 0 none / 1)]

Why Not Just Clip?

In the past, clipping has been the default way in which out of gamut colors have been handled in web browsers. It is fast, and has generally been fine as most browsers have been constrained to using sRGB. Additionally, it can perform better in some circumstances depending on what you are doing.

ColorAide currently only has clipping and chroma reduction approaches to gamut mapping. Clipping, which is often done in the RGB gamut, can often be preferred when colorfulness is found to be most important as it will keep colors vivid, though hue and lightness can change. For instance, in photos, if you have colors that are entirely in one gamut and that gamut is fairly close to a smaller target gamut, clipping can provide reasonable results. Clipping a Display P3 or even a Rec. 2020 photo may give visually, reasonable results.

When preserving chroma, the lightness and hue is prioritized. In the target gamut there may simply not be a color vivid enough with that hue and lightness to map to. When the gamut difference of the source and target are large enough, the results in some color regions can be less ideal.

Consider the example below that displays colors in a Rec. 2020 image. When comparing clipping to chroma reduction, we can see that the clipping results feel arguably more natural. Note that the original will be modified by the browser you are viewing it in to display it on your monitor.

Rec. 2020 Spectrum

Rec. 2020 Spectrum Clipped

Rec. 2020 Spectrum Clipped

This doesn't mean clipping or preserving color is simply superior as there are use cases where preserving lightness is more preferable.

Let's consider some common cases that arise in CSS, cases that often require placing text on colored backgrounds. If we wanted to generate tones using LCH, we can select a color and convert it to that space. Then we can just adjust the lightness. A gamut mapping approach such as chroma reduction will ensure the chroma is reasonable for each level of lightness making decent tones, while clipping will instead favor the colorfulness not retaining the lightness and sometimes even the hue.

>>> tones = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 95, 100]
>>> green = Color('green').convert('lch-d65')
>>> Steps([green.clone().set('l', tone).fit('srgb', method='raytrace', pspace='lch-d65') for tone in tones])
[color(--lch-d65 0 0 none / 1), color(--lch-d65 10 21.09 136.01 / 1), color(--lch-d65 20 39.453 136.01 / 1), color(--lch-d65 30 53.071 135.96 / 1), color(--lch-d65 40 64.634 135.99 / 1), color(--lch-d65 50 71.853 136.01 / 1), color(--lch-d65 60 71.853 136.01 / 1), color(--lch-d65 70 71.853 136.01 / 1), color(--lch-d65 80 71.853 136.01 / 1), color(--lch-d65 90 71.853 136.01 / 1), color(--lch-d65 95 40.562 136.01 / 1), color(--lch-d65 100 0 none / 1)]
>>> Steps([green.clone().set('l', tone).clip('srgb') for tone in tones])
[color(--lch-d65 6.9652 17.49 144.44 / 1), color(--lch-d65 13.159 30.696 141.99 / 1), color(--lch-d65 21.558 42.652 137.02 / 1), color(--lch-d65 31.138 54.429 136.01 / 1), color(--lch-d65 40.473 65.208 136.01 / 1), color(--lch-d65 50 71.853 136.01 / 1), color(--lch-d65 60 71.853 136.01 / 1), color(--lch-d65 70 71.853 136.01 / 1), color(--lch-d65 80 71.853 136.01 / 1), color(--lch-d65 90 71.853 136.01 / 1), color(--lch-d65 92.31 66.658 135.72 / 1), color(--lch-d65 93.221 58.371 135.01 / 1)]

If creating gradients that may have text overlaid on them, clipping doesn't keep consistent or expected lightness either. Colors of the same lightness don't always have the same maximum chroma within a certain color gamut.

In the example below, we interpolate between two colors, both of which have 75% lightness. What we would expect is that the colors will have a similar lightness close to 75%, but clipping will yield much darker, vivid colors with inconsistent lightness, while chroma reduction will have colors that are generally closer to the expected lightness.

>>> Steps(
...     [
...         c.fit('srgb', method='raytrace', pspace='lch-d65')
...         for c in Color.steps(['color(--lch-d65 75% 120 0)', 'color(--lch-d65 75% 120 360)'], space='lch-d65', steps=500, hue='longer')
...     ]
... )
[color(--lch-d65 75 41.031 0 / 1), color(--lch-d65 75 40.849 0.72144 / 1), color(--lch-d65 75 40.676 1.4429 / 1), color(--lch-d65 75 40.512 2.1643 / 1), color(--lch-d65 75 40.356 2.8858 / 1), color(--lch-d65 75 40.21 3.6072 / 1), color(--lch-d65 75 40.071 4.3287 / 1), color(--lch-d65 75 39.941 5.0501 / 1), color(--lch-d65 75 39.818 5.7715 / 1), color(--lch-d65 75 39.704 6.493 / 1), color(--lch-d65 75 39.598 7.2144 / 1), color(--lch-d65 75 39.499 7.9359 / 1), color(--lch-d65 75 39.407 8.6573 / 1), color(--lch-d65 75 39.324 9.3788 / 1), color(--lch-d65 75 39.247 10.1 / 1), color(--lch-d65 75 39.178 10.822 / 1), color(--lch-d65 75 39.117 11.543 / 1), color(--lch-d65 75 39.062 12.265 / 1), color(--lch-d65 75 39.015 12.986 / 1), color(--lch-d65 75 38.975 13.707 / 1), color(--lch-d65 75 38.941 14.429 / 1), color(--lch-d65 75 38.915 15.15 / 1), color(--lch-d65 75 38.896 15.872 / 1), color(--lch-d65 75 38.884 16.593 / 1), color(--lch-d65 75 38.879 17.315 / 1), color(--lch-d65 75 38.881 18.036 / 1), color(--lch-d65 75 38.889 18.758 / 1), color(--lch-d65 75 38.905 19.479 / 1), color(--lch-d65 75 38.928 20.2 / 1), color(--lch-d65 75 38.958 20.922 / 1), color(--lch-d65 75 38.994 21.643 / 1), color(--lch-d65 75 39.038 22.365 / 1), color(--lch-d65 75 39.089 23.086 / 1), color(--lch-d65 75 39.147 23.808 / 1), color(--lch-d65 75 39.213 24.529 / 1), color(--lch-d65 75 39.286 25.251 / 1), color(--lch-d65 75 39.366 25.972 / 1), color(--lch-d65 75 39.453 26.693 / 1), color(--lch-d65 75 39.548 27.415 / 1), color(--lch-d65 75 39.651 28.136 / 1), color(--lch-d65 75 39.762 28.858 / 1), color(--lch-d65 75 39.88 29.579 / 1), color(--lch-d65 75 40.007 30.301 / 1), color(--lch-d65 75 40.141 31.022 / 1), color(--lch-d65 75 40.284 31.744 / 1), color(--lch-d65 75 40.435 32.465 / 1), color(--lch-d65 75 40.595 33.187 / 1), color(--lch-d65 75 40.764 33.908 / 1), color(--lch-d65 75 40.941 34.63 / 1), color(--lch-d65 75 41.128 35.352 / 1), color(--lch-d65 75 41.325 36.073 / 1), color(--lch-d65 75 41.53 36.795 / 1), color(--lch-d65 75 41.746 37.517 / 1), color(--lch-d65 75 41.972 38.239 / 1), color(--lch-d65 75 42.208 38.961 / 1), color(--lch-d65 75 42.456 39.683 / 1), color(--lch-d65 75 42.714 40.405 / 1), color(--lch-d65 75 42.983 41.127 / 1), color(--lch-d65 75 43.265 41.849 / 1), color(--lch-d65 75 43.558 42.572 / 1), color(--lch-d65 75 43.865 43.295 / 1), color(--lch-d65 75 44.184 44.018 / 1), color(--lch-d65 75 44.517 44.741 / 1), color(--lch-d65 75 44.864 45.465 / 1), color(--lch-d65 75 45.225 46.189 / 1), color(--lch-d65 75 45.602 46.913 / 1), color(--lch-d65 75 45.995 47.638 / 1), color(--lch-d65 75 46.404 48.363 / 1), color(--lch-d65 75 46.83 49.088 / 1), color(--lch-d65 75 47.275 49.815 / 1), color(--lch-d65 75 47.739 50.542 / 1), color(--lch-d65 75 48.223 51.269 / 1), color(--lch-d65 75 48.727 51.998 / 1), color(--lch-d65 75 49.254 52.727 / 1), color(--lch-d65 75 49.805 53.457 / 1), color(--lch-d65 75 50.38 54.189 / 1), color(--lch-d65 75 50.981 54.922 / 1), color(--lch-d65 75 51.611 55.656 / 1), color(--lch-d65 75 52.27 56.391 / 1), color(--lch-d65 75 52.96 57.129 / 1), color(--lch-d65 75 53.685 57.868 / 1), color(--lch-d65 75 54.446 58.61 / 1), color(--lch-d65 75 55.246 59.354 / 1), color(--lch-d65 75 56.088 60.1 / 1), color(--lch-d65 75 56.976 60.85 / 1), color(--lch-d65 75 57.914 61.603 / 1), color(--lch-d65 75 58.907 62.36 / 1), color(--lch-d65 75 59.96 63.121 / 1), color(--lch-d65 75 61.078 63.888 / 1), color(--lch-d65 75 62.271 64.66 / 1), color(--lch-d65 75 63.545 65.439 / 1), color(--lch-d65 75 64.913 66.225 / 1), color(--lch-d65 75 66.387 67.021 / 1), color(--lch-d65 75 67.982 67.827 / 1), color(--lch-d65 75 69.72 68.646 / 1), color(--lch-d65 75 71.626 69.481 / 1), color(--lch-d65 75 73.736 70.335 / 1), color(--lch-d65 75 75.507 71.001 / 1), color(--lch-d65 75 77.073 71.554 / 1), color(--lch-d65 75 78.608 72.067 / 1), color(--lch-d65 75 80.056 72.526 / 1), color(--lch-d65 75 81.324 72.91 / 1), color(--lch-d65 75 82.303 73.594 / 1), color(--lch-d65 75 81.911 74.314 / 1), color(--lch-d65 75 81.537 75.033 / 1), color(--lch-d65 75 81.179 75.753 / 1), color(--lch-d65 75 80.837 76.474 / 1), color(--lch-d65 75 80.512 77.194 / 1), color(--lch-d65 75 80.202 77.916 / 1), color(--lch-d65 75 79.908 78.637 / 1), color(--lch-d65 75 79.629 79.359 / 1), color(--lch-d65 75 79.365 80.08 / 1), color(--lch-d65 75 79.116 80.802 / 1), color(--lch-d65 75 78.881 81.523 / 1), color(--lch-d65 75 78.661 82.244 / 1), color(--lch-d65 75 78.455 82.966 / 1), color(--lch-d65 75 78.264 83.687 / 1), color(--lch-d65 75 78.085 84.409 / 1), color(--lch-d65 75 77.921 85.13 / 1), color(--lch-d65 75 77.77 85.852 / 1), color(--lch-d65 75 77.633 86.573 / 1), color(--lch-d65 75 77.508 87.295 / 1), color(--lch-d65 75 77.397 88.016 / 1), color(--lch-d65 75 77.299 88.737 / 1), color(--lch-d65 75 77.214 89.459 / 1), color(--lch-d65 75 77.141 90.18 / 1), color(--lch-d65 75 77.082 90.902 / 1), color(--lch-d65 75 77.035 91.623 / 1), color(--lch-d65 75 77.001 92.345 / 1), color(--lch-d65 75 76.98 93.066 / 1), color(--lch-d65 75 76.971 93.788 / 1), color(--lch-d65 75 76.975 94.509 / 1), color(--lch-d65 75 76.992 95.23 / 1), color(--lch-d65 75 77.021 95.952 / 1), color(--lch-d65 75 77.063 96.673 / 1), color(--lch-d65 75 77.117 97.395 / 1), color(--lch-d65 75 77.185 98.116 / 1), color(--lch-d65 75 77.265 98.838 / 1), color(--lch-d65 75 77.358 99.559 / 1), color(--lch-d65 75 77.464 100.28 / 1), color(--lch-d65 75 77.583 101 / 1), color(--lch-d65 75 77.715 101.72 / 1), color(--lch-d65 75 77.861 102.44 / 1), color(--lch-d65 75 78.019 103.17 / 1), color(--lch-d65 75 78.192 103.89 / 1), color(--lch-d65 75 78.378 104.61 / 1), color(--lch-d65 75 78.577 105.33 / 1), color(--lch-d65 75 78.791 106.05 / 1), color(--lch-d65 75 79.019 106.77 / 1), color(--lch-d65 75 79.262 107.5 / 1), color(--lch-d65 75 79.519 108.22 / 1), color(--lch-d65 75 79.79 108.94 / 1), color(--lch-d65 75 80.077 109.66 / 1), color(--lch-d65 75 80.379 110.38 / 1), color(--lch-d65 75 80.697 111.1 / 1), color(--lch-d65 75 81.031 111.83 / 1), color(--lch-d65 75 81.381 112.55 / 1), color(--lch-d65 75 81.748 113.27 / 1), color(--lch-d65 75 82.131 113.99 / 1), color(--lch-d65 75 82.532 114.71 / 1), color(--lch-d65 75 82.951 115.43 / 1), color(--lch-d65 75 83.388 116.16 / 1), color(--lch-d65 75 83.843 116.88 / 1), color(--lch-d65 75 84.318 117.6 / 1), color(--lch-d65 75 84.811 118.32 / 1), color(--lch-d65 75 85.326 119.04 / 1), color(--lch-d65 75 85.86 119.77 / 1), color(--lch-d65 75 86.416 120.49 / 1), color(--lch-d65 75 86.994 121.21 / 1), color(--lch-d65 75 87.594 121.93 / 1), color(--lch-d65 75 88.217 122.66 / 1), color(--lch-d65 75 88.864 123.38 / 1), color(--lch-d65 75 89.536 124.1 / 1), color(--lch-d65 75 90.233 124.82 / 1), color(--lch-d65 75 90.956 125.55 / 1), color(--lch-d65 75 91.707 126.27 / 1), color(--lch-d65 75 92.485 126.99 / 1), color(--lch-d65 75 93.293 127.71 / 1), color(--lch-d65 75 94.13 128.43 / 1), color(--lch-d65 75 94.999 129.16 / 1), color(--lch-d65 75 95.9 129.88 / 1), color(--lch-d65 75 96.835 130.6 / 1), color(--lch-d65 75 97.804 131.32 / 1), color(--lch-d65 75 98.81 132.04 / 1), color(--lch-d65 75 99.854 132.76 / 1), color(--lch-d65 75 100.94 133.48 / 1), color(--lch-d65 75 102.04 134.19 / 1), color(--lch-d65 75 103.19 134.9 / 1), color(--lch-d65 75 104.37 135.61 / 1), color(--lch-d65 75 104.12 136.38 / 1), color(--lch-d65 75 102.91 136.85 / 1), color(--lch-d65 75 101.26 137.51 / 1), color(--lch-d65 75 99.307 138.31 / 1), color(--lch-d65 75 97.45 139.09 / 1), color(--lch-d65 75 95.675 139.85 / 1), color(--lch-d65 75 93.972 140.6 / 1), color(--lch-d65 75 92.332 141.35 / 1), color(--lch-d65 75 90.747 142.09 / 1), color(--lch-d65 75 89.212 142.82 / 1), color(--lch-d65 75 87.724 143.55 / 1), color(--lch-d65 75 86.279 144.28 / 1), color(--lch-d65 75 84.874 145 / 1), color(--lch-d65 75 83.509 145.73 / 1), color(--lch-d65 75 82.18 146.45 / 1), color(--lch-d65 75 80.888 147.17 / 1), color(--lch-d65 75 79.63 147.89 / 1), color(--lch-d65 75 78.406 148.62 / 1), color(--lch-d65 75 77.215 149.34 / 1), color(--lch-d65 75 76.056 150.06 / 1), color(--lch-d65 75 74.929 150.78 / 1), color(--lch-d65 75 73.832 151.5 / 1), color(--lch-d65 75 72.765 152.22 / 1), color(--lch-d65 75 71.726 152.95 / 1), color(--lch-d65 75 70.717 153.67 / 1), color(--lch-d65 75 69.734 154.39 / 1), color(--lch-d65 75 68.779 155.11 / 1), color(--lch-d65 75 67.851 155.83 / 1), color(--lch-d65 75 66.947 156.55 / 1), color(--lch-d65 75 66.069 157.27 / 1), color(--lch-d65 75 65.216 158 / 1), color(--lch-d65 75 64.386 158.72 / 1), color(--lch-d65 75 63.579 159.44 / 1), color(--lch-d65 75 62.795 160.16 / 1), color(--lch-d65 75 62.032 160.88 / 1), color(--lch-d65 75 61.291 161.6 / 1), color(--lch-d65 75 60.571 162.32 / 1), color(--lch-d65 75 59.871 163.05 / 1), color(--lch-d65 75 59.191 163.77 / 1), color(--lch-d65 75 58.53 164.49 / 1), color(--lch-d65 75 57.887 165.21 / 1), color(--lch-d65 75 57.263 165.93 / 1), color(--lch-d65 75 56.656 166.65 / 1), color(--lch-d65 75 56.067 167.37 / 1), color(--lch-d65 75 55.494 168.1 / 1), color(--lch-d65 75 54.938 168.82 / 1), color(--lch-d65 75 54.397 169.54 / 1), color(--lch-d65 75 53.872 170.26 / 1), color(--lch-d65 75 53.361 170.98 / 1), color(--lch-d65 75 52.866 171.7 / 1), color(--lch-d65 75 52.384 172.42 / 1), color(--lch-d65 75 51.917 173.15 / 1), color(--lch-d65 75 51.463 173.87 / 1), color(--lch-d65 75 51.022 174.59 / 1), color(--lch-d65 75 50.594 175.31 / 1), color(--lch-d65 75 50.178 176.03 / 1), color(--lch-d65 75 49.775 176.75 / 1), color(--lch-d65 75 49.384 177.47 / 1), color(--lch-d65 75 49.004 178.2 / 1), color(--lch-d65 75 48.635 178.92 / 1), color(--lch-d65 75 48.278 179.64 / 1), color(--lch-d65 75 47.931 180.36 / 1), color(--lch-d65 75 47.595 181.08 / 1), color(--lch-d65 75 47.269 181.8 / 1), color(--lch-d65 75 46.953 182.53 / 1), color(--lch-d65 75 46.647 183.25 / 1), color(--lch-d65 75 46.35 183.97 / 1), color(--lch-d65 75 46.063 184.69 / 1), color(--lch-d65 75 45.785 185.41 / 1), color(--lch-d65 75 45.516 186.13 / 1), color(--lch-d65 75 45.256 186.85 / 1), color(--lch-d65 75 45.004 187.58 / 1), color(--lch-d65 75 44.761 188.3 / 1), color(--lch-d65 75 44.527 189.02 / 1), color(--lch-d65 75 44.3 189.74 / 1), color(--lch-d65 75 44.082 190.46 / 1), color(--lch-d65 75 43.871 191.18 / 1), color(--lch-d65 75 43.668 191.9 / 1), color(--lch-d65 75 43.472 192.63 / 1), color(--lch-d65 75 43.284 193.35 / 1), color(--lch-d65 75 43.103 194.07 / 1), color(--lch-d65 75 42.93 194.79 / 1), color(--lch-d65 75 42.763 195.51 / 1), color(--lch-d65 75 42.604 196.23 / 1), color(--lch-d65 75 42.451 196.95 / 1), color(--lch-d65 75 42.305 197.68 / 1), color(--lch-d65 75 42.166 198.4 / 1), color(--lch-d65 75 42.034 199.12 / 1), color(--lch-d65 75 41.908 199.84 / 1), color(--lch-d65 75 41.788 200.56 / 1), color(--lch-d65 75 41.675 201.28 / 1), color(--lch-d65 75 41.568 202 / 1), color(--lch-d65 75 41.467 202.73 / 1), color(--lch-d65 75 41.373 203.45 / 1), color(--lch-d65 75 41.284 204.17 / 1), color(--lch-d65 75 41.202 204.89 / 1), color(--lch-d65 75 41.126 205.61 / 1), color(--lch-d65 75 41.055 206.33 / 1), color(--lch-d65 75 40.991 207.05 / 1), color(--lch-d65 75 40.932 207.78 / 1), color(--lch-d65 75 40.88 208.5 / 1), color(--lch-d65 75 40.833 209.22 / 1), color(--lch-d65 75 40.792 209.94 / 1), color(--lch-d65 75 40.756 210.66 / 1), color(--lch-d65 75 40.727 211.38 / 1), color(--lch-d65 75 40.703 212.1 / 1), color(--lch-d65 75 40.685 212.83 / 1), color(--lch-d65 75 40.672 213.55 / 1), color(--lch-d65 75 40.666 214.27 / 1), color(--lch-d65 75 40.665 214.99 / 1), color(--lch-d65 75 40.67 215.71 / 1), color(--lch-d65 75 40.68 216.43 / 1), color(--lch-d65 75 40.696 217.15 / 1), color(--lch-d65 75 40.718 217.88 / 1), color(--lch-d65 75 40.746 218.6 / 1), color(--lch-d65 75 40.78 219.32 / 1), color(--lch-d65 75 40.819 220.04 / 1), color(--lch-d65 75 40.864 220.76 / 1), color(--lch-d65 75 40.915 221.48 / 1), color(--lch-d65 75 40.972 222.2 / 1), color(--lch-d65 75 41.035 222.93 / 1), color(--lch-d65 75 41.104 223.65 / 1), color(--lch-d65 75 41.179 224.37 / 1), color(--lch-d65 75 41.26 225.09 / 1), color(--lch-d65 75 41.347 225.81 / 1), color(--lch-d65 75 41.441 226.53 / 1), color(--lch-d65 75 41.541 227.25 / 1), color(--lch-d65 75 41.647 227.98 / 1), color(--lch-d65 75 41.759 228.7 / 1), color(--lch-d65 75 41.878 229.42 / 1), color(--lch-d65 75 42.004 230.14 / 1), color(--lch-d65 75 42.136 230.86 / 1), color(--lch-d65 75 42.275 231.58 / 1), color(--lch-d65 75 42.421 232.3 / 1), color(--lch-d65 75 42.574 233.03 / 1), color(--lch-d65 75 42.734 233.75 / 1), color(--lch-d65 75 42.901 234.47 / 1), color(--lch-d65 75 43.076 235.19 / 1), color(--lch-d65 75 43.258 235.91 / 1), color(--lch-d65 75 43.448 236.63 / 1), color(--lch-d65 75 43.645 237.35 / 1), color(--lch-d65 75 43.85 238.08 / 1), color(--lch-d65 75 44.064 238.8 / 1), color(--lch-d65 75 44.286 239.52 / 1), color(--lch-d65 75 44.516 240.24 / 1), color(--lch-d65 75 44.278 240.96 / 1), color(--lch-d65 75 43.967 241.68 / 1), color(--lch-d65 75 43.666 242.4 / 1), color(--lch-d65 75 43.376 243.12 / 1), color(--lch-d65 75 43.097 243.85 / 1), color(--lch-d65 75 42.829 244.57 / 1), color(--lch-d65 75 42.57 245.29 / 1), color(--lch-d65 75 42.321 246.01 / 1), color(--lch-d65 75 42.082 246.73 / 1), color(--lch-d65 75 41.851 247.45 / 1), color(--lch-d65 75 41.63 248.18 / 1), color(--lch-d65 75 41.418 248.9 / 1), color(--lch-d65 75 41.215 249.62 / 1), color(--lch-d65 75 41.019 250.34 / 1), color(--lch-d65 75 40.833 251.06 / 1), color(--lch-d65 75 40.654 251.78 / 1), color(--lch-d65 75 40.483 252.5 / 1), color(--lch-d65 75 40.32 253.23 / 1), color(--lch-d65 75 40.165 253.95 / 1), color(--lch-d65 75 40.017 254.67 / 1), color(--lch-d65 75 39.876 255.39 / 1), color(--lch-d65 75 39.743 256.11 / 1), color(--lch-d65 75 39.617 256.83 / 1), color(--lch-d65 75 39.497 257.56 / 1), color(--lch-d65 75 39.385 258.28 / 1), color(--lch-d65 75 39.28 259 / 1), color(--lch-d65 75 39.181 259.72 / 1), color(--lch-d65 75 39.089 260.44 / 1), color(--lch-d65 75 39.004 261.16 / 1), color(--lch-d65 75 38.925 261.88 / 1), color(--lch-d65 75 38.852 262.61 / 1), color(--lch-d65 75 38.786 263.33 / 1), color(--lch-d65 75 38.727 264.05 / 1), color(--lch-d65 75 38.673 264.77 / 1), color(--lch-d65 75 38.626 265.49 / 1), color(--lch-d65 75 38.585 266.21 / 1), color(--lch-d65 75 38.55 266.93 / 1), color(--lch-d65 75 38.522 267.66 / 1), color(--lch-d65 75 38.499 268.38 / 1), color(--lch-d65 75 38.483 269.1 / 1), color(--lch-d65 75 38.472 269.82 / 1), color(--lch-d65 75 38.468 270.54 / 1), color(--lch-d65 75 38.47 271.26 / 1), color(--lch-d65 75 38.478 271.98 / 1), color(--lch-d65 75 38.492 272.71 / 1), color(--lch-d65 75 38.512 273.43 / 1), color(--lch-d65 75 38.538 274.15 / 1), color(--lch-d65 75 38.571 274.87 / 1), color(--lch-d65 75 38.609 275.59 / 1), color(--lch-d65 75 38.654 276.31 / 1), color(--lch-d65 75 38.705 277.03 / 1), color(--lch-d65 75 38.762 277.76 / 1), color(--lch-d65 75 38.826 278.48 / 1), color(--lch-d65 75 38.896 279.2 / 1), color(--lch-d65 75 38.972 279.92 / 1), color(--lch-d65 75 39.055 280.64 / 1), color(--lch-d65 75 39.145 281.36 / 1), color(--lch-d65 75 39.241 282.08 / 1), color(--lch-d65 75 39.344 282.81 / 1), color(--lch-d65 75 39.453 283.53 / 1), color(--lch-d65 75 39.57 284.25 / 1), color(--lch-d65 75 39.693 284.97 / 1), color(--lch-d65 75 39.824 285.69 / 1), color(--lch-d65 75 39.961 286.41 / 1), color(--lch-d65 75 40.106 287.13 / 1), color(--lch-d65 75 40.259 287.86 / 1), color(--lch-d65 75 40.419 288.58 / 1), color(--lch-d65 75 40.587 289.3 / 1), color(--lch-d65 75 40.762 290.02 / 1), color(--lch-d65 75 40.946 290.74 / 1), color(--lch-d65 75 41.138 291.46 / 1), color(--lch-d65 75 41.338 292.18 / 1), color(--lch-d65 75 41.547 292.91 / 1), color(--lch-d65 75 41.765 293.63 / 1), color(--lch-d65 75 41.991 294.35 / 1), color(--lch-d65 75 42.227 295.07 / 1), color(--lch-d65 75 42.472 295.79 / 1), color(--lch-d65 75 42.727 296.51 / 1), color(--lch-d65 75 42.991 297.23 / 1), color(--lch-d65 75 43.266 297.96 / 1), color(--lch-d65 75 43.552 298.68 / 1), color(--lch-d65 75 43.848 299.4 / 1), color(--lch-d65 75 44.155 300.12 / 1), color(--lch-d65 75 44.473 300.84 / 1), color(--lch-d65 75 44.804 301.56 / 1), color(--lch-d65 75 45.146 302.28 / 1), color(--lch-d65 75 45.501 303.01 / 1), color(--lch-d65 75 45.869 303.73 / 1), color(--lch-d65 75 46.25 304.45 / 1), color(--lch-d65 75 46.645 305.17 / 1), color(--lch-d65 75 47.054 305.89 / 1), color(--lch-d65 75 47.478 306.61 / 1), color(--lch-d65 75 47.918 307.34 / 1), color(--lch-d65 75 48.373 308.06 / 1), color(--lch-d65 75 48.845 308.78 / 1), color(--lch-d65 75 49.334 309.5 / 1), color(--lch-d65 75 49.841 310.22 / 1), color(--lch-d65 75 50.366 310.94 / 1), color(--lch-d65 75 50.911 311.66 / 1), color(--lch-d65 75 51.475 312.39 / 1), color(--lch-d65 75 52.061 313.11 / 1), color(--lch-d65 75 52.668 313.83 / 1), color(--lch-d65 75 53.299 314.55 / 1), color(--lch-d65 75 53.953 315.27 / 1), color(--lch-d65 75 54.632 315.99 / 1), color(--lch-d65 75 55.337 316.71 / 1), color(--lch-d65 75 56.069 317.44 / 1), color(--lch-d65 75 56.831 318.16 / 1), color(--lch-d65 75 57.622 318.88 / 1), color(--lch-d65 75 58.445 319.6 / 1), color(--lch-d65 75 59.301 320.32 / 1), color(--lch-d65 75 60.192 321.04 / 1), color(--lch-d65 75 61.12 321.76 / 1), color(--lch-d65 75 62.087 322.49 / 1), color(--lch-d65 75 63.094 323.21 / 1), color(--lch-d65 75 64.146 323.93 / 1), color(--lch-d65 75 65.243 324.65 / 1), color(--lch-d65 75 66.389 325.37 / 1), color(--lch-d65 75 67.586 326.09 / 1), color(--lch-d65 75 68.7 326.81 / 1), color(--lch-d65 75 67.221 327.54 / 1), color(--lch-d65 75 65.831 328.26 / 1), color(--lch-d65 75 64.52 328.98 / 1), color(--lch-d65 75 63.282 329.7 / 1), color(--lch-d65 75 62.111 330.42 / 1), color(--lch-d65 75 61.002 331.14 / 1), color(--lch-d65 75 59.95 331.86 / 1), color(--lch-d65 75 58.952 332.59 / 1), color(--lch-d65 75 58.002 333.31 / 1), color(--lch-d65 75 57.098 334.03 / 1), color(--lch-d65 75 56.236 334.75 / 1), color(--lch-d65 75 55.415 335.47 / 1), color(--lch-d65 75 54.63 336.19 / 1), color(--lch-d65 75 53.881 336.91 / 1), color(--lch-d65 75 53.165 337.64 / 1), color(--lch-d65 75 52.481 338.36 / 1), color(--lch-d65 75 51.825 339.08 / 1), color(--lch-d65 75 51.197 339.8 / 1), color(--lch-d65 75 50.596 340.52 / 1), color(--lch-d65 75 50.02 341.24 / 1), color(--lch-d65 75 49.467 341.96 / 1), color(--lch-d65 75 48.937 342.69 / 1), color(--lch-d65 75 48.429 343.41 / 1), color(--lch-d65 75 47.941 344.13 / 1), color(--lch-d65 75 47.473 344.85 / 1), color(--lch-d65 75 47.023 345.57 / 1), color(--lch-d65 75 46.591 346.29 / 1), color(--lch-d65 75 46.177 347.01 / 1), color(--lch-d65 75 45.778 347.74 / 1), color(--lch-d65 75 45.396 348.46 / 1), color(--lch-d65 75 45.029 349.18 / 1), color(--lch-d65 75 44.677 349.9 / 1), color(--lch-d65 75 44.338 350.62 / 1), color(--lch-d65 75 44.013 351.34 / 1), color(--lch-d65 75 43.702 352.06 / 1), color(--lch-d65 75 43.403 352.79 / 1), color(--lch-d65 75 43.116 353.51 / 1), color(--lch-d65 75 42.841 354.23 / 1), color(--lch-d65 75 42.578 354.95 / 1), color(--lch-d65 75 42.326 355.67 / 1), color(--lch-d65 75 42.085 356.39 / 1), color(--lch-d65 75 41.854 357.11 / 1), color(--lch-d65 75 41.633 357.84 / 1), color(--lch-d65 75 41.423 358.56 / 1), color(--lch-d65 75 41.222 359.28 / 1), color(--lch-d65 75 41.031 360 / 1)]
>>> Steps(
...     [
...         c.clip('srgb')
...         for c in Color.steps(['color(--lch-d65 75% 120 0)', 'color(--lch-d65 75% 120 360)'], space='lch-d65', steps=500, hue='longer')
...     ]
... )
[color(--lch-d65 57.051 94.74 342.08 / 1), color(--lch-d65 56.939 94.057 342.81 / 1), color(--lch-d65 56.829 93.393 343.56 / 1), color(--lch-d65 56.72 92.747 344.32 / 1), color(--lch-d65 56.614 92.121 345.1 / 1), color(--lch-d65 56.509 91.516 345.89 / 1), color(--lch-d65 56.406 90.931 346.7 / 1), color(--lch-d65 56.305 90.367 347.51 / 1), color(--lch-d65 56.205 89.826 348.35 / 1), color(--lch-d65 56.108 89.307 349.19 / 1), color(--lch-d65 56.012 88.811 350.05 / 1), color(--lch-d65 55.918 88.34 350.91 / 1), color(--lch-d65 55.826 87.892 351.79 / 1), color(--lch-d65 55.736 87.47 352.69 / 1), color(--lch-d65 55.648 87.072 353.59 / 1), color(--lch-d65 55.562 86.701 354.5 / 1), color(--lch-d65 55.477 86.356 355.43 / 1), color(--lch-d65 55.394 86.038 356.36 / 1), color(--lch-d65 55.313 85.747 357.3 / 1), color(--lch-d65 55.234 85.484 358.25 / 1), color(--lch-d65 55.157 85.249 359.21 / 1), color(--lch-d65 55.081 85.042 0.17041 / 1), color(--lch-d65 55.007 84.864 1.1407 / 1), color(--lch-d65 54.935 84.714 2.1166 / 1), color(--lch-d65 54.865 84.594 3.0974 / 1), color(--lch-d65 54.796 84.503 4.0824 / 1), color(--lch-d65 54.729 84.441 5.0708 / 1), color(--lch-d65 54.664 84.41 6.0621 / 1), color(--lch-d65 54.6 84.408 7.0554 / 1), color(--lch-d65 54.538 84.436 8.0502 / 1), color(--lch-d65 54.478 84.494 9.0456 / 1), color(--lch-d65 54.419 84.582 10.041 / 1), color(--lch-d65 54.362 84.7 11.036 / 1), color(--lch-d65 54.307 84.848 12.029 / 1), color(--lch-d65 54.253 85.026 13.021 / 1), color(--lch-d65 54.2 85.235 14.011 / 1), color(--lch-d65 54.149 85.473 14.997 / 1), color(--lch-d65 54.099 85.742 15.98 / 1), color(--lch-d65 54.051 86.041 16.959 / 1), color(--lch-d65 54.068 86.218 18.024 / 1), color(--lch-d65 54.305 85.925 19.398 / 1), color(--lch-d65 54.547 85.675 20.785 / 1), color(--lch-d65 54.796 85.47 22.183 / 1), color(--lch-d65 55.05 85.312 23.59 / 1), color(--lch-d65 55.31 85.2 25.004 / 1), color(--lch-d65 55.575 85.136 26.422 / 1), color(--lch-d65 55.844 85.12 27.843 / 1), color(--lch-d65 56.119 85.153 29.264 / 1), color(--lch-d65 56.398 85.235 30.684 / 1), color(--lch-d65 56.681 85.366 32.099 / 1), color(--lch-d65 56.968 85.547 33.508 / 1), color(--lch-d65 57.258 85.776 34.909 / 1), color(--lch-d65 57.552 86.052 36.299 / 1), color(--lch-d65 57.849 86.377 37.676 / 1), color(--lch-d65 58.149 86.747 39.039 / 1), color(--lch-d65 58.451 87.163 40.386 / 1), color(--lch-d65 58.756 87.623 41.715 / 1), color(--lch-d65 59.064 88.126 43.025 / 1), color(--lch-d65 59.373 88.67 44.314 / 1), color(--lch-d65 59.684 89.254 45.58 / 1), color(--lch-d65 59.997 89.875 46.823 / 1), color(--lch-d65 60.311 90.532 48.042 / 1), color(--lch-d65 60.627 91.223 49.236 / 1), color(--lch-d65 60.946 91.766 50.303 / 1), color(--lch-d65 61.28 91.334 50.802 / 1), color(--lch-d65 61.614 90.913 51.306 / 1), color(--lch-d65 61.947 90.503 51.812 / 1), color(--lch-d65 62.28 90.103 52.322 / 1), color(--lch-d65 62.613 89.714 52.834 / 1), color(--lch-d65 62.945 89.336 53.349 / 1), color(--lch-d65 63.277 88.969 53.866 / 1), color(--lch-d65 63.607 88.613 54.385 / 1), color(--lch-d65 63.937 88.268 54.906 / 1), color(--lch-d65 64.266 87.934 55.428 / 1), color(--lch-d65 64.594 87.612 55.951 / 1), color(--lch-d65 64.92 87.301 56.475 / 1), color(--lch-d65 65.246 87 56.999 / 1), color(--lch-d65 65.57 86.711 57.524 / 1), color(--lch-d65 65.893 86.433 58.049 / 1), color(--lch-d65 66.214 86.166 58.573 / 1), color(--lch-d65 66.534 85.909 59.097 / 1), color(--lch-d65 66.852 85.663 59.62 / 1), color(--lch-d65 67.168 85.428 60.142 / 1), color(--lch-d65 67.483 85.204 60.663 / 1), color(--lch-d65 67.796 84.989 61.182 / 1), color(--lch-d65 68.107 84.785 61.699 / 1), color(--lch-d65 68.417 84.592 62.215 / 1), color(--lch-d65 68.724 84.408 62.728 / 1), color(--lch-d65 69.029 84.234 63.239 / 1), color(--lch-d65 69.333 84.069 63.747 / 1), color(--lch-d65 69.634 83.914 64.252 / 1), color(--lch-d65 69.933 83.768 64.754 / 1), color(--lch-d65 70.23 83.631 65.254 / 1), color(--lch-d65 70.525 83.503 65.749 / 1), color(--lch-d65 70.817 83.384 66.242 / 1), color(--lch-d65 71.107 83.273 66.73 / 1), color(--lch-d65 71.395 83.171 67.215 / 1), color(--lch-d65 71.681 83.076 67.696 / 1), color(--lch-d65 71.964 82.99 68.172 / 1), color(--lch-d65 72.245 82.911 68.645 / 1), color(--lch-d65 72.523 82.84 69.113 / 1), color(--lch-d65 72.8 82.776 69.577 / 1), color(--lch-d65 73.073 82.719 70.036 / 1), color(--lch-d65 73.344 82.668 70.49 / 1), color(--lch-d65 73.613 82.625 70.94 / 1), color(--lch-d65 73.879 82.588 71.384 / 1), color(--lch-d65 74.142 82.557 71.824 / 1), color(--lch-d65 74.403 82.532 72.259 / 1), color(--lch-d65 74.662 82.513 72.689 / 1), color(--lch-d65 74.918 82.5 73.114 / 1), color(--lch-d65 75.171 82.492 73.533 / 1), color(--lch-d65 75.274 82.181 74.269 / 1), color(--lch-d65 75.276 81.684 75.23 / 1), color(--lch-d65 75.278 81.21 76.204 / 1), color(--lch-d65 75.28 80.761 77.192 / 1), color(--lch-d65 75.282 80.336 78.192 / 1), color(--lch-d65 75.284 79.936 79.204 / 1), color(--lch-d65 75.286 79.562 80.228 / 1), color(--lch-d65 75.288 79.214 81.262 / 1), color(--lch-d65 75.29 78.894 82.306 / 1), color(--lch-d65 75.292 78.6 83.36 / 1), color(--lch-d65 75.293 78.334 84.421 / 1), color(--lch-d65 75.295 78.096 85.491 / 1), color(--lch-d65 75.296 77.886 86.567 / 1), color(--lch-d65 75.298 77.705 87.649 / 1), color(--lch-d65 75.299 77.553 88.735 / 1), color(--lch-d65 75.3 77.43 89.825 / 1), color(--lch-d65 75.302 77.336 90.919 / 1), color(--lch-d65 75.303 77.271 92.014 / 1), color(--lch-d65 75.304 77.236 93.11 / 1), color(--lch-d65 75.305 77.229 94.205 / 1), color(--lch-d65 75.306 77.252 95.3 / 1), color(--lch-d65 75.306 77.304 96.392 / 1), color(--lch-d65 75.307 77.385 97.482 / 1), color(--lch-d65 75.308 77.495 98.567 / 1), color(--lch-d65 75.308 77.632 99.647 / 1), color(--lch-d65 75.309 77.798 100.72 / 1), color(--lch-d65 75.309 77.992 101.79 / 1), color(--lch-d65 75.31 78.213 102.85 / 1), color(--lch-d65 75.31 78.461 103.9 / 1), color(--lch-d65 75.31 78.735 104.94 / 1), color(--lch-d65 75.311 79.036 105.97 / 1), color(--lch-d65 75.311 79.361 106.99 / 1), color(--lch-d65 75.311 79.712 108 / 1), color(--lch-d65 75.311 80.086 109 / 1), color(--lch-d65 75.31 80.484 109.98 / 1), color(--lch-d65 75.31 80.905 110.95 / 1), color(--lch-d65 75.31 81.349 111.91 / 1), color(--lch-d65 75.31 81.814 112.86 / 1), color(--lch-d65 75.309 82.3 113.79 / 1), color(--lch-d65 75.308 82.806 114.7 / 1), color(--lch-d65 75.308 83.331 115.6 / 1), color(--lch-d65 75.307 83.876 116.49 / 1), color(--lch-d65 75.306 84.439 117.36 / 1), color(--lch-d65 75.305 85.019 118.21 / 1), color(--lch-d65 75.304 85.616 119.05 / 1), color(--lch-d65 75.302 86.229 119.88 / 1), color(--lch-d65 75.301 86.858 120.69 / 1), color(--lch-d65 75.299 87.501 121.48 / 1), color(--lch-d65 75.297 88.159 122.26 / 1), color(--lch-d65 75.295 88.83 123.02 / 1), color(--lch-d65 75.293 89.513 123.77 / 1), color(--lch-d65 75.291 90.209 124.5 / 1), color(--lch-d65 75.288 90.917 125.22 / 1), color(--lch-d65 75.286 91.635 125.93 / 1), color(--lch-d65 75.283 92.364 126.62 / 1), color(--lch-d65 75.28 93.103 127.29 / 1), color(--lch-d65 75.276 93.851 127.95 / 1), color(--lch-d65 75.273 94.608 128.6 / 1), color(--lch-d65 75.269 95.374 129.23 / 1), color(--lch-d65 75.265 96.147 129.85 / 1), color(--lch-d65 75.26 96.927 130.46 / 1), color(--lch-d65 75.256 97.715 131.05 / 1), color(--lch-d65 75.251 98.509 131.63 / 1), color(--lch-d65 75.246 99.31 132.2 / 1), color(--lch-d65 75.241 100.12 132.76 / 1), color(--lch-d65 75.235 100.93 133.31 / 1), color(--lch-d65 75.229 101.74 133.84 / 1), color(--lch-d65 75.222 102.57 134.36 / 1), color(--lch-d65 75.216 103.39 134.88 / 1), color(--lch-d65 75.209 104.22 135.38 / 1), color(--lch-d65 75.201 105.06 135.87 / 1), color(--lch-d65 75.279 105.4 136.01 / 1), color(--lch-d65 75.391 105.53 136.01 / 1), color(--lch-d65 75.501 105.65 136.01 / 1), color(--lch-d65 75.609 105.78 136.01 / 1), color(--lch-d65 75.715 105.9 136.01 / 1), color(--lch-d65 75.819 106.02 136.01 / 1), color(--lch-d65 75.921 106.14 136.01 / 1), color(--lch-d65 76.021 106.25 136.01 / 1), color(--lch-d65 76.119 106.37 136.01 / 1), color(--lch-d65 76.215 106.48 136.01 / 1), color(--lch-d65 76.309 106.59 136.01 / 1), color(--lch-d65 76.402 106.69 136.01 / 1), color(--lch-d65 76.493 106.8 136.01 / 1), color(--lch-d65 76.581 106.9 136.01 / 1), color(--lch-d65 76.669 107 136.01 / 1), color(--lch-d65 76.754 107.1 136.01 / 1), color(--lch-d65 76.838 107.2 136.01 / 1), color(--lch-d65 76.932 106.58 136.29 / 1), color(--lch-d65 77.029 105.73 136.65 / 1), color(--lch-d65 77.125 104.87 137.02 / 1), color(--lch-d65 77.22 104.01 137.4 / 1), color(--lch-d65 77.314 103.13 137.79 / 1), color(--lch-d65 77.408 102.25 138.18 / 1), color(--lch-d65 77.5 101.35 138.58 / 1), color(--lch-d65 77.592 100.45 138.99 / 1), color(--lch-d65 77.683 99.54 139.41 / 1), color(--lch-d65 77.773 98.622 139.84 / 1), color(--lch-d65 77.862 97.695 140.27 / 1), color(--lch-d65 77.951 96.761 140.72 / 1), color(--lch-d65 78.04 95.819 141.17 / 1), color(--lch-d65 78.127 94.87 141.63 / 1), color(--lch-d65 78.215 93.914 142.1 / 1), color(--lch-d65 78.301 92.951 142.58 / 1), color(--lch-d65 78.388 91.981 143.08 / 1), color(--lch-d65 78.474 91.006 143.58 / 1), color(--lch-d65 78.559 90.024 144.09 / 1), color(--lch-d65 78.644 89.037 144.62 / 1), color(--lch-d65 78.729 88.044 145.16 / 1), color(--lch-d65 78.814 87.047 145.71 / 1), color(--lch-d65 78.899 86.045 146.27 / 1), color(--lch-d65 78.983 85.039 146.84 / 1), color(--lch-d65 79.067 84.029 147.43 / 1), color(--lch-d65 79.151 83.015 148.03 / 1), color(--lch-d65 79.235 81.999 148.65 / 1), color(--lch-d65 79.319 80.98 149.28 / 1), color(--lch-d65 79.402 79.958 149.93 / 1), color(--lch-d65 79.486 78.935 150.59 / 1), color(--lch-d65 79.57 77.911 151.27 / 1), color(--lch-d65 79.654 76.887 151.96 / 1), color(--lch-d65 79.738 75.862 152.68 / 1), color(--lch-d65 79.822 74.837 153.41 / 1), color(--lch-d65 79.906 73.814 154.16 / 1), color(--lch-d65 79.99 72.793 154.94 / 1), color(--lch-d65 80.074 71.773 155.73 / 1), color(--lch-d65 80.159 70.757 156.54 / 1), color(--lch-d65 80.244 69.745 157.38 / 1), color(--lch-d65 80.329 68.736 158.24 / 1), color(--lch-d65 80.414 67.734 159.12 / 1), color(--lch-d65 80.499 66.737 160.03 / 1), color(--lch-d65 80.585 65.747 160.97 / 1), color(--lch-d65 80.671 64.765 161.93 / 1), color(--lch-d65 80.757 63.791 162.92 / 1), color(--lch-d65 80.844 62.828 163.93 / 1), color(--lch-d65 80.931 61.875 164.98 / 1), color(--lch-d65 81.018 60.933 166.06 / 1), color(--lch-d65 81.106 60.005 167.17 / 1), color(--lch-d65 81.193 59.09 168.31 / 1), color(--lch-d65 81.281 58.191 169.49 / 1), color(--lch-d65 81.37 57.307 170.7 / 1), color(--lch-d65 81.459 56.442 171.95 / 1), color(--lch-d65 81.548 55.595 173.23 / 1), color(--lch-d65 81.637 54.769 174.55 / 1), color(--lch-d65 81.727 53.964 175.91 / 1), color(--lch-d65 81.817 53.182 177.3 / 1), color(--lch-d65 81.907 52.425 178.74 / 1), color(--lch-d65 81.998 51.694 180.21 / 1), color(--lch-d65 82.089 50.99 181.72 / 1), color(--lch-d65 82.18 50.315 183.27 / 1), color(--lch-d65 82.272 49.671 184.86 / 1), color(--lch-d65 82.364 49.058 186.49 / 1), color(--lch-d65 82.456 48.479 188.16 / 1), color(--lch-d65 82.548 47.935 189.86 / 1), color(--lch-d65 82.64 47.428 191.59 / 1), color(--lch-d65 82.733 46.958 193.36 / 1), color(--lch-d65 82.826 46.527 195.16 / 1), color(--lch-d65 82.918 46.137 196.99 / 1), color(--lch-d65 83.011 45.787 198.85 / 1), color(--lch-d65 83.105 45.481 200.73 / 1), color(--lch-d65 83.198 45.217 202.63 / 1), color(--lch-d65 83.291 44.998 204.54 / 1), color(--lch-d65 83.384 44.824 206.47 / 1), color(--lch-d65 83.477 44.694 208.41 / 1), color(--lch-d65 83.57 44.61 210.36 / 1), color(--lch-d65 83.663 44.572 212.3 / 1), color(--lch-d65 83.756 44.579 214.25 / 1), color(--lch-d65 83.768 44.583 214.45 / 1), color(--lch-d65 83.77 44.584 214.45 / 1), color(--lch-d65 83.771 44.584 214.45 / 1), color(--lch-d65 83.771 44.584 214.45 / 1), color(--lch-d65 83.769 44.584 214.45 / 1), color(--lch-d65 83.767 44.583 214.46 / 1), color(--lch-d65 83.764 44.581 214.47 / 1), color(--lch-d65 83.76 44.579 214.48 / 1), color(--lch-d65 83.754 44.577 214.49 / 1), color(--lch-d65 83.748 44.574 214.51 / 1), color(--lch-d65 83.74 44.571 214.53 / 1), color(--lch-d65 83.732 44.567 214.55 / 1), color(--lch-d65 83.722 44.562 214.58 / 1), color(--lch-d65 83.711 44.557 214.61 / 1), color(--lch-d65 83.699 44.552 214.65 / 1), color(--lch-d65 83.685 44.546 214.68 / 1), color(--lch-d65 83.671 44.539 214.72 / 1), color(--lch-d65 83.655 44.532 214.77 / 1), color(--lch-d65 83.638 44.524 214.82 / 1), color(--lch-d65 83.619 44.516 214.87 / 1), color(--lch-d65 83.599 44.507 214.92 / 1), color(--lch-d65 83.578 44.498 214.98 / 1), color(--lch-d65 83.555 44.488 215.05 / 1), color(--lch-d65 83.531 44.478 215.11 / 1), color(--lch-d65 83.506 44.467 215.18 / 1), color(--lch-d65 83.479 44.455 215.26 / 1), color(--lch-d65 83.45 44.443 215.34 / 1), color(--lch-d65 83.42 44.43 215.42 / 1), color(--lch-d65 83.388 44.416 215.51 / 1), color(--lch-d65 83.355 44.402 215.61 / 1), color(--lch-d65 83.32 44.388 215.71 / 1), color(--lch-d65 83.283 44.373 215.81 / 1), color(--lch-d65 83.245 44.357 215.92 / 1), color(--lch-d65 83.205 44.341 216.03 / 1), color(--lch-d65 83.163 44.324 216.15 / 1), color(--lch-d65 83.119 44.307 216.27 / 1), color(--lch-d65 83.074 44.289 216.4 / 1), color(--lch-d65 83.026 44.27 216.53 / 1), color(--lch-d65 82.977 44.251 216.67 / 1), color(--lch-d65 82.926 44.232 216.82 / 1), color(--lch-d65 82.872 44.212 216.97 / 1), color(--lch-d65 82.817 44.191 217.13 / 1), color(--lch-d65 82.76 44.171 217.29 / 1), color(--lch-d65 82.7 44.149 217.46 / 1), color(--lch-d65 82.639 44.128 217.63 / 1), color(--lch-d65 82.575 44.106 217.82 / 1), color(--lch-d65 82.509 44.083 218.01 / 1), color(--lch-d65 82.441 44.061 218.2 / 1), color(--lch-d65 82.37 44.038 218.4 / 1), color(--lch-d65 82.298 44.014 218.61 / 1), color(--lch-d65 82.222 43.991 218.83 / 1), color(--lch-d65 82.145 43.967 219.05 / 1), color(--lch-d65 82.065 43.944 219.28 / 1), color(--lch-d65 81.983 43.92 219.52 / 1), color(--lch-d65 81.898 43.896 219.77 / 1), color(--lch-d65 81.81 43.873 220.02 / 1), color(--lch-d65 81.72 43.849 220.28 / 1), color(--lch-d65 81.628 43.826 220.55 / 1), color(--lch-d65 81.533 43.802 220.83 / 1), color(--lch-d65 81.435 43.78 221.11 / 1), color(--lch-d65 81.334 43.757 221.41 / 1), color(--lch-d65 81.231 43.735 221.71 / 1), color(--lch-d65 81.125 43.714 222.02 / 1), color(--lch-d65 81.016 43.693 222.34 / 1), color(--lch-d65 80.904 43.673 222.67 / 1), color(--lch-d65 80.789 43.654 223.01 / 1), color(--lch-d65 80.671 43.636 223.36 / 1), color(--lch-d65 80.55 43.618 223.71 / 1), color(--lch-d65 80.427 43.602 224.08 / 1), color(--lch-d65 80.3 43.588 224.46 / 1), color(--lch-d65 80.17 43.574 224.84 / 1), color(--lch-d65 80.037 43.562 225.24 / 1), color(--lch-d65 79.901 43.552 225.64 / 1), color(--lch-d65 79.762 43.544 226.06 / 1), color(--lch-d65 79.619 43.538 226.49 / 1), color(--lch-d65 79.473 43.534 226.92 / 1), color(--lch-d65 79.324 43.532 227.37 / 1), color(--lch-d65 79.171 43.532 227.83 / 1), color(--lch-d65 79.016 43.536 228.3 / 1), color(--lch-d65 78.856 43.542 228.77 / 1), color(--lch-d65 78.693 43.551 229.26 / 1), color(--lch-d65 78.527 43.563 229.76 / 1), color(--lch-d65 78.357 43.579 230.28 / 1), color(--lch-d65 78.184 43.598 230.8 / 1), color(--lch-d65 78.007 43.621 231.33 / 1), color(--lch-d65 77.827 43.649 231.88 / 1), color(--lch-d65 77.643 43.68 232.43 / 1), color(--lch-d65 77.455 43.716 233 / 1), color(--lch-d65 77.263 43.757 233.57 / 1), color(--lch-d65 77.068 43.803 234.16 / 1), color(--lch-d65 76.869 43.854 234.76 / 1), color(--lch-d65 76.666 43.911 235.37 / 1), color(--lch-d65 76.459 43.973 235.99 / 1), color(--lch-d65 76.249 44.042 236.63 / 1), color(--lch-d65 76.034 44.117 237.27 / 1), color(--lch-d65 75.816 44.199 237.92 / 1), color(--lch-d65 75.593 44.287 238.59 / 1), color(--lch-d65 75.367 44.383 239.26 / 1), color(--lch-d65 75.137 44.487 239.94 / 1), color(--lch-d65 74.902 44.598 240.64 / 1), color(--lch-d65 74.664 44.718 241.34 / 1), color(--lch-d65 74.421 44.846 242.06 / 1), color(--lch-d65 74.174 44.984 242.78 / 1), color(--lch-d65 73.923 45.13 243.51 / 1), color(--lch-d65 73.668 45.286 244.25 / 1), color(--lch-d65 73.409 45.452 245 / 1), color(--lch-d65 73.145 45.628 245.76 / 1), color(--lch-d65 72.878 45.814 246.53 / 1), color(--lch-d65 72.606 46.012 247.3 / 1), color(--lch-d65 72.329 46.221 248.08 / 1), color(--lch-d65 72.049 46.441 248.87 / 1), color(--lch-d65 71.764 46.673 249.67 / 1), color(--lch-d65 71.474 46.918 250.47 / 1), color(--lch-d65 71.18 47.175 251.28 / 1), color(--lch-d65 70.882 47.446 252.09 / 1), color(--lch-d65 70.58 47.729 252.91 / 1), color(--lch-d65 70.273 48.027 253.73 / 1), color(--lch-d65 69.961 48.338 254.56 / 1), color(--lch-d65 69.645 48.664 255.39 / 1), color(--lch-d65 69.324 49.004 256.22 / 1), color(--lch-d65 68.999 49.36 257.06 / 1), color(--lch-d65 68.67 49.73 257.9 / 1), color(--lch-d65 68.336 50.117 258.74 / 1), color(--lch-d65 67.997 50.519 259.58 / 1), color(--lch-d65 67.654 50.938 260.42 / 1), color(--lch-d65 67.306 51.373 261.26 / 1), color(--lch-d65 66.953 51.825 262.11 / 1), color(--lch-d65 66.596 52.295 262.95 / 1), color(--lch-d65 66.485 52.254 265.04 / 1), color(--lch-d65 66.582 51.902 268.16 / 1), color(--lch-d65 66.681 51.702 271.28 / 1), color(--lch-d65 66.783 51.648 274.37 / 1), color(--lch-d65 66.887 51.736 277.41 / 1), color(--lch-d65 66.992 51.959 280.4 / 1), color(--lch-d65 67.1 52.31 283.31 / 1), color(--lch-d65 67.21 52.78 286.14 / 1), color(--lch-d65 67.321 53.361 288.87 / 1), color(--lch-d65 67.435 54.045 291.5 / 1), color(--lch-d65 67.55 54.822 294.03 / 1), color(--lch-d65 67.667 55.684 296.45 / 1), color(--lch-d65 67.785 56.623 298.77 / 1), color(--lch-d65 67.905 57.63 300.97 / 1), color(--lch-d65 68.026 58.698 303.07 / 1), color(--lch-d65 68.148 59.819 305.06 / 1), color(--lch-d65 68.272 60.987 306.95 / 1), color(--lch-d65 68.397 62.196 308.74 / 1), color(--lch-d65 68.523 63.439 310.45 / 1), color(--lch-d65 68.651 64.712 312.06 / 1), color(--lch-d65 68.779 66.009 313.59 / 1), color(--lch-d65 68.908 67.326 315.04 / 1), color(--lch-d65 69.038 68.659 316.41 / 1), color(--lch-d65 69.168 70.004 317.72 / 1), color(--lch-d65 69.3 71.359 318.95 / 1), color(--lch-d65 69.432 72.719 320.13 / 1), color(--lch-d65 69.564 74.083 321.24 / 1), color(--lch-d65 69.697 75.447 322.3 / 1), color(--lch-d65 69.83 76.811 323.31 / 1), color(--lch-d65 69.964 78.17 324.27 / 1), color(--lch-d65 70.098 79.524 325.18 / 1), color(--lch-d65 70.232 80.872 326.05 / 1), color(--lch-d65 70.367 82.211 326.87 / 1), color(--lch-d65 70.248 83.356 327.25 / 1), color(--lch-d65 69.929 84.354 327.28 / 1), color(--lch-d65 69.61 85.355 327.31 / 1), color(--lch-d65 69.29 86.359 327.34 / 1), color(--lch-d65 68.971 87.365 327.37 / 1), color(--lch-d65 68.652 88.372 327.41 / 1), color(--lch-d65 68.334 89.38 327.44 / 1), color(--lch-d65 68.016 90.39 327.47 / 1), color(--lch-d65 67.698 91.4 327.5 / 1), color(--lch-d65 67.381 92.409 327.53 / 1), color(--lch-d65 67.066 93.418 327.56 / 1), color(--lch-d65 66.751 94.427 327.59 / 1), color(--lch-d65 66.437 95.433 327.63 / 1), color(--lch-d65 66.125 96.438 327.66 / 1), color(--lch-d65 65.815 97.44 327.69 / 1), color(--lch-d65 65.506 98.44 327.72 / 1), color(--lch-d65 65.198 99.435 327.75 / 1), color(--lch-d65 64.893 100.43 327.78 / 1), color(--lch-d65 64.59 101.41 327.81 / 1), color(--lch-d65 64.289 102.4 327.84 / 1), color(--lch-d65 63.991 103.37 327.87 / 1), color(--lch-d65 63.695 104.34 327.9 / 1), color(--lch-d65 63.402 105.3 327.93 / 1), color(--lch-d65 63.112 106.26 327.96 / 1), color(--lch-d65 62.825 107.2 327.99 / 1), color(--lch-d65 62.542 108.14 328.01 / 1), color(--lch-d65 62.262 109.07 328.04 / 1), color(--lch-d65 61.986 109.99 328.07 / 1), color(--lch-d65 61.713 110.89 328.1 / 1), color(--lch-d65 61.445 111.79 328.12 / 1), color(--lch-d65 61.181 112.67 328.15 / 1), color(--lch-d65 60.921 113.54 328.17 / 1), color(--lch-d65 60.666 114.39 328.2 / 1), color(--lch-d65 60.416 115.23 328.22 / 1), color(--lch-d65 60.323 115.55 328.23 / 1), color(--lch-d65 60.323 115.55 328.23 / 1), color(--lch-d65 60.323 115.55 328.23 / 1), color(--lch-d65 60.224 114.93 328.51 / 1), color(--lch-d65 60.076 114 328.93 / 1), color(--lch-d65 59.93 113.07 329.36 / 1), color(--lch-d65 59.784 112.14 329.81 / 1), color(--lch-d65 59.64 111.23 330.27 / 1), color(--lch-d65 59.496 110.31 330.73 / 1), color(--lch-d65 59.354 109.4 331.21 / 1), color(--lch-d65 59.213 108.5 331.7 / 1), color(--lch-d65 59.074 107.61 332.21 / 1), color(--lch-d65 58.936 106.72 332.72 / 1), color(--lch-d65 58.799 105.84 333.25 / 1), color(--lch-d65 58.664 104.97 333.8 / 1), color(--lch-d65 58.531 104.11 334.35 / 1), color(--lch-d65 58.398 103.26 334.92 / 1), color(--lch-d65 58.268 102.42 335.5 / 1), color(--lch-d65 58.139 101.59 336.09 / 1), color(--lch-d65 58.011 100.78 336.7 / 1), color(--lch-d65 57.885 99.973 337.33 / 1), color(--lch-d65 57.761 99.181 337.96 / 1), color(--lch-d65 57.638 98.403 338.61 / 1), color(--lch-d65 57.518 97.639 339.28 / 1), color(--lch-d65 57.398 96.89 339.95 / 1), color(--lch-d65 57.281 96.157 340.65 / 1), color(--lch-d65 57.165 95.44 341.35 / 1), color(--lch-d65 57.051 94.74 342.08 / 1)]

No perceptual color space is perfect and no gamut mapping approach perfect for all situations either. There are other gamut mapping approaches that can even offer compromises providing better better hue preservation than clipping with less shifts in lightness.

In the future, it is possible that ColorAide can expose more approaches that provide compromises between preserving lightness and colorfulness, but it is good to know why some approaches may be more favorable at times than others.

Pointer's Gamut

New 2.4

The Pointer’s gamut is (an approximation of) the gamut of real surface colors as can be seen by the human eye, based on the research by Michael R. Pointer (1980). What this means is that every color that can be reflected by the surface of an object of any material should be is inside the Pointer’s gamut. This does not include, however, those that do not occur naturally, such as neon lights, etc.

Pointer's Gamut

While in the above image, it may appear that most of sRGB is in the gamut, it is important to note that the image is showing the maximum range of the gamut. The actual boundary will be different at different luminance levels.

Pointer's Gamut lightness Levels

The gamuts previously discussed are bound by a color space's limits, but the Pointer's gamut applies to colors more generally and was created from observed data via research. Because it doesn't quite fit with the color space gamut API, ColorAide exposes two special functions to test if a color is in the Pointer's gamut and to fit a color to the gamut.

To test if a color is within the gamut, simply call in_pointer_gamut():

>>> Color('red').in_pointer_gamut()
False
>>> Color('orange').in_pointer_gamut()
True

ColorAide also provides a way to fit a color to the Pointer's gamut. The original gamut's data is described in LCh using illuminant C. Using this color space, we can estimate the chroma limit for any color based on it's lightness and hue. We can then reduce the chroma, preserving the lightness and hue. The image below shows the out of Pointer's gamut color red (indicated by the x) which is clamped to the Pointer's gamut by reducing the chroma (indicated by the dot).

Pointer's Gamut Fitted

ColorAide provides the fit_pointer_gamut() method to perform this "fitting" of the color.

>>> color = Color('red')
>>> color
color(srgb 1 0 0 / 1)
>>> color.in_pointer_gamut()
False
>>> color.fit_pointer_gamut()
color(srgb 0.95687 0.18251 0.09074 / 1)
>>> color.in_pointer_gamut()
True

Tip

Much like in_gamut(), in_pointer_gamut() allows adjusting tolerance as well via the tolerance parameter.