Functional Weave
Code in Rust

charts.interpolate-color@1.0.0

README.md

1,760 bytes · view raw

# charts.interpolate-color

The colour a fraction `t` of the way along a ramp: `interpolateColor(["#000000",
"#ffffff"], 0.5)` is `"#636363"`. Use it for heatmaps, choropleths and any
sequential (light to dark) or diverging (colour, neutral, colour) scale. The
stops are evenly spaced: with three stops, t = 0.5 is exactly the middle one.

**Why OKLab.** Mixing the stored sRGB numbers, which is what a naive lerp of
hex digits does, gives a grey `#808080` halfway from blue to yellow and bunches
the visible change at one end of a ramp. Mixing in linear RGB is better for
light but still not perceptually even. OKLab (Björn Ottosson, 2020) is built so
that equal distances look like equal differences, so each stop is converted
to OKLab (sRGB → linear → LMS → cube root → Lab), the three coordinates are
interpolated linearly, and the result is converted back, clipped to the sRGB
gamut channel by channel, and rounded to 8 bits. Clipping matters in the middle
of red to green, which OKLab places slightly outside sRGB.

Hex parsing, the sRGB transfer function and the 8-bit rounding come from
`charts.color`; cube roots from `math.pow`. No platform `pow` or `cbrt` is
used, so TypeScript, Python and Rust return the same hex. The matrices are the
published ones, to Ottosson's ten decimal places; the endpoints come back
exactly (t = 0 gives the first stop).

`t` must be in 0..1 and the ramp needs at least two stops; anything else is an
error. Hex in either `#rgb` or `#rrggbb` form, either case; out is lowercase
`#rrggbb`.

Sources: B. Ottosson, "A perceptual color space for image processing" (2020),
https://bottosson.github.io/posts/oklab/ (the M1 and M2 matrices and their
inverses, for linear sRGB); IEC 61966-2-1 (sRGB transfer function).