v155 · canvas · color

PredefinedColorSpace srgb-linear and display-p3-linear

The 2D canvas has had colorSpace: "srgb" | "display-p3" since Chrome 94. Chrome 155 extends the PredefinedColorSpace enum with the two linear-light variants CSS already defines — "srgb-linear" and "display-p3-linear" — so a canvas backing store, and an ImageData, can hold pixel values that are proportional to light. That is the space where gradients stop going muddy in the middle and where adding two lights actually adds their photons.

concepts

  1. Context settings

    The canonical surface: pass each of the four enum values to getContext("2d", { colorSpace }), read the real answer back with getContextAttributes(), and see exactly what an unsupported value does — it throws, it is not ignored. Live matrix, verbatim results.

  2. ImageData round trip

    new ImageData(w, h, { colorSpace }) tags pixels with a space, and getImageData() converts between spaces on request. Pick a colour, push it through every space the browser has, and compare the raw bytes that come back.

  3. Linear-light gradients

    Why linear matters: the same two gradient stops interpolated in gamma-encoded sRGB versus linear light, side by side, with the midpoint pixel sampled from each canvas. The gamma version dips dark and grey in the middle; the linear one keeps its luminance.

  4. Additive spotlights

    Three coloured lights composited with globalCompositeOperation: "lighter". In a gamma-encoded canvas, "lighter" adds encoded bytes — physically meaningless. In a linear canvas the addition models real light. Move the lights and watch the overlaps disagree.

  5. Detection traps

    How to feature-detect this correctly — and the two wrong ways that look right. Unknown enum values throw TypeError; unknown dictionary keys are silently ignored; a probe that cannot tell those apart reports support that is not there.

why it shipped

Pixel values in an ordinary canvas are gamma-encoded: the byte 128 is not half the light of 255, it is about 22% of it. That encoding is perceptually efficient for storage, but every arithmetic operation the canvas performs — gradient interpolation, image smoothing, compositing, "lighter" — happens on the encoded values, which is mathematically the wrong domain for anything that models light. Games, renderers, anti-aliasing and image-resampling pipelines all work in linear light for exactly this reason, and until now the only way to do that on the web was to run your own conversion in JavaScript or move to WebGL/WebGPU.

CSS Color 4 already defines srgb-linear and display-p3-linear as predefined colour spaces; Chrome 155 lets a canvas backing store use them directly, so the browser does its blending in the right domain and converts to the display at the end — the same conversion an <img> with a matching colour profile gets.

Availability: this shipped to stable in Chrome 155. On earlier versions the runtime flag is ColorSpacePredefinedLinearSpaces (status: experimental) — launch with --enable-blink-features=ColorSpacePredefinedLinearSpaces or --enable-experimental-web-platform-features. There is no dedicated chrome://flags entry. Every demo here probes the exact enum values and shows the browser's real answer either way.

references