CMYK → RGB via LUT — emulation mode
Real-world use case: your app has CMYK images (proofs, prepress previews, archival scans) and needs to display them as RGB on screen. In production you don’t want to ship ICC profiles or run a full ICC pipeline per pixel. You want one pre-baked LUT and a fast kernel.
This page builds two CMYK→RGB LUTs at load time, serialises them
to JSON (the portable handshake format), then rebuilds Transforms from
the JSON and converts a CMYK master image three ways. The
jsCE LUT column shows what production looks like
— no ICC profile loaded, just a JSON file and the engine.
The lcms LUT column shows
emulation: jsCE’s WASM-SIMD kernels with lcms’s colour
math sampled into the grid.
Important: Once you build and save the LUT from Lcms or jsCE, you no
longer need to load lcms or the profile, only the LUT and jsCE’s Transform.fromJSON() to load it.
Profile: GRACoL2006_Coated1v2.icc ·
Intent: relative colorimetric + BPC (lcms’s perceptual disables BPC) ·
Grid: 174 (4D) ·
Source: view source ·
API guide: lutbuilder.md
LUT build — one-time, at page load
Idle.
CMYK image → three RGB conversions
(a) jsCE live transform
(b) jsCE LUT — from JSON
(c) lcms LUT — emulation
| pair | mean ΔP | p95 ΔP | max ΔP | note |
|---|---|---|---|---|
| Run a conversion to populate. | ||||
ΔP = per-pixel Euclidean distance on RGB in 0–255 scale (per-channel difference, not perceptual ΔE). ΔP < 1 means “sub-LSB at 8-bit, indistinguishable on screen”. Mean and p95 are the meaningful numbers — the typical pixel. Max is usually a single pixel at a profile “kink”: K-generation transitions, gamut-boundary crossings, or TAC clamp regions where the CMYK profile is non-smooth and 17-pt linear interpolation diverges from the f64 curve. Going to a 33-pt grid (4× the cells, 4× the JSON size) shrinks the max substantially. For most production work the 17-pt budget is fine.
What’s actually in the JSON?
The portable handshake format. CLUT bytes are elided
("<data>") so you can see the chain, grid shape,
and signature. This is what JSON.stringify(transform)
produces and what Transform.fromJSON() consumes.
Build LUTs first.