|
3 | 3 | > **A research-grade color laboratory for the web.** |
4 | 4 | > **Every color space, every illuminant — digit-identical in JS, GLSL, and WGSL.** |
5 | 5 |
|
6 | | -Every conversion is generated from one table of cited constants. The matrix that |
7 | | -runs in your JavaScript is the matrix that runs in your shader — and CI verifies |
8 | | -it to the last digit. Stop hand-porting color matrices. |
9 | | - |
10 | | -## Pillars |
11 | | - |
12 | | -| Pillar | Target | 110% | |
13 | | -|---|---|---| |
14 | | -| **Coverage** | All 14 CSS Color 4 spaces + OKHSL/OKHSV; full CIE illuminant table + arbitrary white points; gamut checking *and* gamut mapping | Illuminants from correlated color temperature (`illuminantFromCCT`); CAT02/CAT16/von Kries alongside Bradford | |
15 | | -| **Verified precision** | Every conversion tested against derivational invariants and spec fixtures; round-trip bounds CI-enforced; every constant carries a citation | Differential testing against culori, colorjs.io, and @texel/color as oracles; precision-bounds table in the README, generated by CI | |
16 | | -| **Everywhere** | Core imports clean in Node / SSR / workers; GLSL + WGSL + JS emitted from a single descriptor pipeline over single-sourced constants | GPU parity tests in CI; shader-side gamut mapping | |
17 | | -| **Speed** | Zero-allocation out-param paths; precomposed routes; honest published benchmarks | SOTA: nothing maintained is faster, on conversion or gamut mapping — measured with @texel/color's own suite | |
| 6 | +What this library is for, and the rules it holds itself to. Every conversion is |
| 7 | +generated from one table of cited constants — the matrix that runs in your |
| 8 | +JavaScript is the matrix that runs in your shader, verified in CI to the last |
| 9 | +digit. That one idea drives everything below. |
| 10 | + |
| 11 | +## What it commits to |
| 12 | + |
| 13 | +- **Coverage.** Every CSS Color 4 space plus OKHSL/OKHSV; the CIE illuminants and |
| 14 | + arbitrary white points; chromatic adaptation across multiple transforms |
| 15 | + (Bradford, CAT02, CAT16, von Kries); illuminants from correlated color |
| 16 | + temperature; gamut *checking* and gamut *mapping*; and a spectral layer (color |
| 17 | + matching functions, Planck's law, daylight, emission). |
| 18 | +- **Verified precision.** Conversions are tested against derivational invariants, |
| 19 | + spec fixtures, and the other major libraries as oracles (culori, colorjs.io, |
| 20 | + @texel/color). Round-trip error bounds are CI-enforced and published — measured, |
| 21 | + never implied. |
| 22 | +- **Portability.** The core imports cleanly in Node, SSR, and workers, and one |
| 23 | + pipeline emits JS, GLSL, and WGSL from the same constants — with CPU/GPU parity |
| 24 | + checked in CI, so a color is identical on both. |
| 25 | +- **Speed.** Zero-allocation paths and precomposed routes, with benchmarks that |
| 26 | + are public and reproducible (`npm run bench`). |
18 | 27 |
|
19 | 28 | ## Principles |
20 | 29 |
|
21 | 30 | 1. **Derive, don't transcribe.** RGB↔XYZ matrices are computed at module load |
22 | | - from cited primaries and white points. A hardcoded matrix is a transcription |
23 | | - error waiting to happen; a derived one is checked against published values in CI. |
24 | | -2. **One source of truth.** Constants live in one table. JS functions, GLSL |
25 | | - strings, and WGSL strings are all views of that table. |
26 | | -3. **Precision is a contract.** No rounding inside conversions. Bytes exist only |
| 31 | + from cited primaries and white points, then checked against published values |
| 32 | + in CI. A hardcoded matrix is a transcription error waiting to happen. |
| 33 | +2. **One source of truth.** Constants live in one table; the JS functions, GLSL |
| 34 | + strings, and WGSL strings are all views of it. |
| 35 | +3. **Precision is a contract.** No rounding inside conversions — bytes exist only |
27 | 36 | at the explicit `toBytes`/`fromBytes` boundary. Error bounds are measured, |
28 | | - published, and enforced — never implied. |
29 | | -4. **The math is honest about itself.** Hue is undefined at zero chroma; CSS |
30 | | - white points differ from CIE 15 values in the fifth digit; gamut mapping is |
31 | | - lossy. We document these instead of papering over them. |
32 | | - |
33 | | -## Scope rule |
34 | | - |
35 | | -**If a frozen spec defines it on coordinates, implement it exactly; strings |
36 | | -on the way out only; constants still in motion wait.** |
37 | | - |
38 | | -## Anti-goals |
39 | | - |
40 | | -- No color wrapper class — plain arrays in, plain arrays out |
41 | | -- ~~No CSS string parsing, ever~~ — **amended at v0.8**: purity is an |
42 | | - instrument, not an identity. Strings are how colors arrive in the world, |
43 | | - so the frozen Level 4 grammar is in scope — behind a fenced boundary |
44 | | - (src/parse): the conversion core never sees a string, `none` becomes NaN |
45 | | - under the numerical policy, and still-moving grammar (Level 5 relative |
46 | | - color syntax) waits like every other unfrozen spec |
47 | | -- Alpha is not a color coordinate — the conversion core stays 3-channel; |
48 | | - spec-defined alpha interactions (premultiplied mixing, serialization) are |
49 | | - separate, consistently 4-channel entry points |
50 | | -- No palettes, harmonies, color naming, or gradient objects — those belong |
51 | | - to applications (spec-defined interpolation itself is in scope) |
52 | | -- No unfrozen perceptual models (APCA waits for WCAG 3 to freeze) |
| 37 | + published, and enforced. |
| 38 | +4. **The math is honest about itself.** Hue is undefined at zero chroma; the CSS |
| 39 | + white points differ from the CIE 15 values in the fifth digit; gamut mapping |
| 40 | + is lossy. The library documents these rather than papering over them. |
| 41 | + |
| 42 | +## Scope |
| 43 | + |
| 44 | +One rule decides what belongs here: **if a frozen spec defines it on |
| 45 | +coordinates, implement it exactly; parse strings only at a fenced boundary; |
| 46 | +anything still in motion waits.** |
| 47 | + |
| 48 | +Out of scope, on purpose: |
| 49 | + |
| 50 | +- **No color wrapper class** — plain arrays in, plain arrays out. |
| 51 | +- **Strings only at the edge.** Parsing covers the frozen CSS Color 4 grammar |
| 52 | + behind a fenced module the conversion core never sees; CSS Level 5 |
| 53 | + relative-color syntax and calc() wait until the spec freezes. |
| 54 | +- **Alpha is not a color coordinate** — the conversion core stays 3-channel; |
| 55 | + alpha-aware operations (premultiplied mixing, serialization) are separate, |
| 56 | + 4-channel entry points. |
| 57 | +- **No palettes, harmonies, color naming, or gradient objects** — those belong |
| 58 | + to applications. (Spec-defined interpolation itself is in scope.) |
| 59 | +- **No unfrozen perceptual models** — APCA returns when WCAG 3 freezes. |
0 commit comments