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:

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)