Slides)
A native Color object for the Web Platform (Use cases and motivation
- A format for APIs to expose colors to the developer. Some of the APIs in need for this are:
- Canvas API (see Canvas High Dynamic Range)
- CSS OM
- SVG DOM
- HTML
<input type="color">
or its successor (see Open UI) - Eyedropper API (see TAG review)
- WebGPU
- Lossless color space conversion (e.g. LCH β P3) by default, optional gamut mapping.
- Color manipulation (e.g. making a color darker by reducing its LCH lightness) with choice of manipulation color space
- Difference between two colors (ΞE)
- String parsing (e.g. what color is
rebeccapurple
?) - WCAG 2.1 (or it's successor) color contrast (for any color space, not just sRGB)
- Prototyping new functionality for incubation, before standardization
Use cases postponed to L2
- Interpolation (e.g. mixing two colors, compositing, generating color scales) with choice of interpolation color space
- Compositing and blending
- Parsing and serialization of custom formats
Audience
Web developers with varying levels of Color Science knowledge. Usable without error by those with little, powerful for those with much.
Goals
- Usability as a priority
- Common things should be easy, complex things should be possible
- Learnability: don't require a ton of color science knowledge to use
- Handle linearization, chromatic adaptation automatically when needed
- Consistent API shape independent of input syntax
- Efficiency: Avoid verbosity, have sensible defaults
- Safety: Avoid error conditions if possible
- Liberal in what is accepted (for arguments)
- Color-space agnostic
- API should make no assumptions about number, names, or ranges of components
- Ok to only support color spaces with numeric components
- Should not privilege certain color spaces over others, whenever possible
- Authors should be able to register new color spaces,
either via a JS version of
@color-profile
or by directly providing conversion code to and from a supported color space. - Should be able to support HDR color spaces, and SDR β HDR conversion
- No hidden gamut mapping or clipping
- API should make no assumptions about number, names, or ranges of components
- D65 relative CIE XYZ connection space for SDR
- (extended rec2020-linear, as used in Canvas HDR will give same result)
- Configurable media white level for HDR (203cd/mΒ² default for absolute)
- Extensibility
- sufficient power to allow polyfilling and experimentation
- introspection would be good
Predefined color spaces
This set covers the union of spaces from CSS Color 4 and Canvas HDR. All RGB spaces are defined over the extended range.
SDR
srgb
(Web legacy compatibility)srgb-linear
(as used in Canvas HDR, some GPU operations, native APIs)display-p3
(new Web)a98-rgb
(?? needed, nowadays?)prophoto-rgb
(from raw digital photos)rec2020
(streaming and broadcast)rec2020-linear
(canvas uses as connection space)xyz
(relative, D65) (for linear-light calculations)lab
(D50) (perceptual calculations)lch
(D50) (perceptual, chroma-preserving)oklab
(D65) (perceptual calculations)oklch
(D65) (perceptual, chroma-preserving)
HDR
rec2100-pq
(Netflix, Canvas HDR)rec2100-hlg
(BBC, Canvas HDR)
API sketch
Sample WebIDL and algorithms moved to the draft spec.
Example usage
Reading coordinates
For ease of use and widest applicability, coordinates are plain JavaScript number (for a single coordinate), or an array of number (for all coordinates in a given colorspace).
let color = new Color("rebeccapurple");
// Get individual coord in other color space
color.get("lch", "l"); // 32.4
// Get individual coord in current color space
color.get("r"); // 0.4
// Get all coords in another color space
color.to("lch").coords; // [32.4, 61.2, 309]
Parsing color and converting to a specific color space
Converting in place:
let color = new Color("rebeccapurple");
color.colorSpace; // "srgb";
color.coords; // [0.4, 0.2, 0.6]
color.colorSpace = "lch";
color.coords; // [32.4, 61.2, 309]
By creating a new object:
let color = new Color("rebeccapurple");
color.colorSpace; // "srgb";
color.coords; // [0.4, 0.2, 0.6]
let lchColor = color.to("lch");
lchColor.coords; // [32.4, 61.2, 309]
Lightening a color without changing its color space
color.set("lch", "l", color.get("lch", "l") * 1.2);
Another possibility for relative manipulations:
color.set("lch", "l", l => l * 1.2);
Calculating WCAG 2.1 contrast
This is straightforward, but could also be built-in as a contrast method.
let contrast;
let fg = new Color("display-p3" [1, 1, 0]); // P3 yellow
let bg = new Color("sienna"); // sRGB named color
let l1 = fg.get("xyz", "y");
let l2 = bg.get("xyz", "y");
if (l1 > l2) {
[l1, l2] = [l2, l1];
}
contrast = (l2 + 0.05) / (l1 + 0.05);
Defining new color spaces
// TBD
Color spaces from ICC profiles
let cmyk = ColorSpace.fromICCProfile("./cmyk-profile.icc", {
name: "fogra-coated",
coords: {
c: { min: 0, max: 100 },
m: { min: 0, max: 100 },
y: { min: 0, max: 100 },
k: { min: 0, max: 100 },
}
});
let magenta = new Color(cmyk, [0, 100, 0, 0]);
let lightMagenta = magenta.set("oklch", "l", l => l * 1.2);
Decisions
Can a color space become unregistered?
No, and this is by design. It complicates implementation if color spaces can "stop" being supported. What happens with all existing colors created?
Define colors over an extended range
Ths simplifies use of HDR, especially on platforms like WebGPU or WebGL which are not inherently color managed (all operatons happen in a single color space)
ICC Profiles
An earlier version of this draft had iccProfile
as a property of ColorSpace
objects.
However, that would require the entire API to be async, which significantly complicates use cases.
Therefore, it was deemed better to have an async ColorSpace.fromICCProfile()
method that returns a regular ColorSpace
object.