The 2020s: High-Resolution Color
High-resolution colors from the 2020s have three floating point coordinates and explicit color spaces:
ColorSpace
enumerates supported color spaces.Color
combines a color space and three floating point coordinates into a precise color representation.
Much of prettypretty’s functionality is accessible through Color
’s methods.
They include:
- Access to color space and coordinates
space
,as_ref
- Testing for achromatic colors
is_achromatic
,is_achromatic_threshold
- Conversion between color spaces
to
- Gamut testing
in_gamut
, clippingclip
, and mappingto_gamut
- Lightening
lighten
and darkeningdarken
- Perceptual contrast
contrast_against
,use_black_text
,use_black_background
- Color difference
distance
,find_closest_ok
,find_closest
- Interpolation
interpolate
- Projection onto 2D plane
hue_chroma
,uv_prime_chromaticity
,xy_chromaticity
Using Color
and Color Spaces
The example below illustrates how to use Color
. First, it instantiates a
color in the Oklch color space, which is the cylindrical version of the
perceptually uniform Oklab color
space. The three coordinates are L,
C, and h—lightness, chroma, and hue. The latter is in degrees, which explains
why it is two orders of magnitude larger than the other coordinates. Oklab/Oklch
and their improved versions,
Oklrab/Oklrch,
feature prominently in prettypretty because their perceptual uniformity makes
them excellent predictors for actual colors. I call all four, tongue firmly in
cheek, the Oklab variations.
After creating the color in Oklch, the example code converts it to Display P3 and tests whether the color is in gamut—it is. “Gamut” is lingo for all the colors that belong to a color space. It is of critical importance for physical devices and processes because it determines the range of reproducible colors. If a color is out of gamut, it simply can’t be reproduced. Display P3 is the larger of two RGB color spaces commonly supported by contemporary displays.
The smaller color space is called sRGB. It has been the default color space for the web for the longest time. The code example converts the color to sRGB as well. It again tests whether the result is in gamut—it is not. As a final step, the example code “gamut maps” the color to sRGB. That again is lingo and refers to any sophisticated means, i.e., algorithm, for finding an in-gamut color that still resembles the original. Meanwhile “clipping” or “clamping” is the crude means for producing in-gamut colors: It simply forces the coordinates into range, resetting them to the minimum or maximum if not.
#![allow(unused)] fn main() { extern crate prettypretty; use prettypretty::{Color, ColorSpace, assert_same_color}; let oklch = Color::oklch(0.716, 0.349, 335.0); let p3 = oklch.to(ColorSpace::DisplayP3); assert!(p3.in_gamut()); let not_srgb = oklch.to(ColorSpace::Srgb); assert!(!not_srgb.in_gamut()); let srgb = not_srgb.to_gamut(); assert_same_color!(srgb, Color::srgb(1.0, 0.15942348587138203, 0.9222706101768445)); }
Different Color Spaces for Different Folks
Color::to_gamut
implements the CSS Color 4
algorithm for gamut mapping. One noteworthy aspect of the algorithm is its
simultaneous use of three differrent color spaces. It generates candidate colors
in Oklch by adjusting the chroma of the original color (and leaving lightness
and hue unchanged). It produces in-gamut colors by clipping the candidates in
the target color space, here sRGB. And it determines whether the clipped
candidates fall within the just noticeable difference (JND), i.e., are good
enough, by calculating their distance from the candidates in Oklab. In other
words, there is no ideal color space, and different color spaces excel at
different tasks.
I Haz Color Swatches
Since the numeric coordinates in the code examples aren’t very colorful but supposedly do represent colors—independent of whether they are beautiful, garish, subdued, saturated, or what have you—each code block has its own color swatch that shows the colors mentioned in the code. Here is the one for the code block above:
Since the second color is in-gamut for sRGB and sRGB is widely supported, your screen and my screen are probably showing the same color for the second square of the color swatch. If your screen, like mine, also supports Display P3, then the same should hold for the first square and it should show a brighter, purer magenta than the second one. However, if your screen only supports sRGB, then the first square should show the same color as the second square. That’s because CSS Color 4 requires gamut mapping out-of-gamut colors and prettypretty implements the CSS Color 4 algorithm. But for some reason, the developers for all major browsers are having second thoughts about gamut mapping and instead just clip colors.
Revisiting the Example Code in Python
With Python being a first-tier runtime target for prettypretty, this guide tries to feature all example code for both languages. Here then is the Python version of the above code. It doesn’t look all that different.
from prettypretty.color import Color, ColorSpace
oklch = Color.oklch(0.716, 0.349, 335.0)
p3 = oklch.to(ColorSpace.DisplayP3)
assert p3.in_gamut()
not_srgb = oklch.to(ColorSpace.Srgb)
assert not not_srgb.in_gamut()
srgb = not_srgb.to_gamut()
assert srgb == Color.srgb(1.0, 0.15942348587138203, 0.9222706101768445)