Repository

lib-color

Kinogaki's color science and color management.

GitHub
kinogaki/lib-color (private)
Folder
libs/lib-color/
Crates
2
Rust lines
10,741 in src, 903 in tests
depends on (an edge implied by others shows when you point at a box)used by tests alone

Crates

CrateWhat it isFilesLinesTest lines
kinogaki-colorColor science and color management for Kinogaki.3110,328522
kinogaki-color-wgpukinogaki-color's processors on a wgpu device.1413381

README

lib-color

Kinogaki's color science and color management. It has no dependencies and forbids unsafe code. kinogaki-color-wgpu runs its processors on a wgpu device.

Crate / moduleWhat it holds
kinogaki-color matrix, xyz3×3 matrices; CIE XYZ, chromaticities, primaries (sRGB, Display P3, Adobe RGB, Rec. 2020, ACES AP0/AP1, ProPhoto), matrices derived from them, Bradford and CAT02 adaptation
transfersRGB, gamma 1.8/2.2/2.4, PQ, HLG
lab, oklab, hslCIELAB with ΔE76/CIEDE2000, Oklab with its sRGB gamut and chroma-reducing map, HSV/HSL for pickers
temperaturecorrelated color temperature and tint (Robertson, DNG tint scale)
spectralthe CIE 1931 observer (the CIE's table and Wyman et al.'s fit), XYZ under E, blackbodies, CIE daylight, the spectral upsampling of a color in any primaries (Jakob and Hanika)
acesthe ACES 2.0 Output Transform and its inverse
ocioOpenColorIO v1/v2 configs read by our own code: YAML, the config model (roles, scene and display color spaces, displays, shared views, view transforms, looks, named transforms, file and viewing rules), every transform type the ACES configs and common LUT-based configs use, builtins, LUT files (.cube, .spi1d, .spi3d, .spimtx, .clf, .ctf), CPU processors and WGSL; the ACES 2.0 CG config built in
kinogaki-color-wgpuImageDisplay (a scene-linear image drawn through a display/view into a render pass) and Evaluator (colors through a processor in a compute pass)

OpenColorIO is the oracle of the tests and never a dependency: tools/make_ocio_fixtures.py and tools/make_aces_fixtures.py write its answers into crates/color/tests/fixtures from a scratch venv. Results: docs/ARCHITECTURE.md.

Checks: cargo fmt --all -- --check, cargo clippy --workspace --all-targets --locked -- -D warnings, cargo test --workspace --locked; on a machine with a GPU, cargo test -p kinogaki-color-wgpu --release -- --ignored --skip hand_run_. In Kinogaki Studio the root's python3 tools/check_all.py runs them all.

Ported from luce-color (reference only). Not ported yet: icc (skcms), cam16, camera (camera sensitivities), the spectral Upsampler grid and white-balance illuminants; OCIO GradingPrimary/RGBCurve/Tone transforms, the ACES 1.x output transforms, the ACES 2.0 D60-sim builtins, CDL files (.cc/.ccc), half-domain CLF tables and the video ExposureContrast style.

Its documents

The repository's architecture document as written there (code blocks and links left out), and the opening of each other document.

lib-color architecture (libs/lib-color/docs/ARCHITECTURE.md)

lib-color architecture

Color science (kinogaki-color)

Everything is f64 on the CPU. Matrices are derived from chromaticities (xyz::rgb_to_xyz), never typed in, except where a standard publishes the matrix itself (ADX, Sony Venice, the blue-light LMT). spectral is the one home of the observer: Spectre's spectral data (kinogaki-spectra) and its upsampling use it.

The ACES 2.0 Output Transform (aces)

aces::OutputTransform::new(peak, limiting) builds the hue tables once (a few milliseconds; ocio::builtins shares them). cam is the Hellwig 2022 JMh model, tonescale the Daniele curve and chroma compression, tables the reach, hue and cusp tables, gamut the gamut compression and the upper hull's fit. apply/invert work on AP0, apply_ap1/invert_ap1 on AP1.

OpenColorIO (ocio)

  • yaml: the YAML subset configs use (block and flow collections, tags, block scalars, quotes, comments), bounded (64 MiB, 64 levels), errors with lines; malformed input never panics (mutation tests).
  • config: the model and lookups (names, aliases and roles case-insensitively), active displays/views, viewing rules, file rules (globs, regex (our own backtracking matcher with a step budget), path search, default), context variables, the search path.
  • transform: the transforms as written.
  • compile: transforms to ops::Ops with directions resolved. Color space conversions go source → its reference → (the default view transform when the references differ) → destination; data spaces pass through; views run looks, then the view transform from its reference to the display reference, then the display color space; named transforms stand in for color spaces.
  • ops: the primitive operations, each with its CPU evaluation and its inverse. processor merges matrices and drops identities.
  • builtins: OpenColorIO's builtin transforms from their definitions (curves in closed form in curves where OpenColorIO uses tables).
  • files, lut, xml: LUT formats; 1D tables invert by search, 3D tables by Newton steps per color (the same on the GPU).
  • wgsl: the ops as one WGSL function plus a storage buffer (the viewer's exposure and gamma at its start, then tables). The ACES 2.0 transform's WGSL (aces_helpers.wgsl, aces_transform.wgsl) mirrors aces in f32.

GPU (kinogaki-color-wgpu)

ImageDisplay::new(device, format, processor) compiles the processor into a fragment shader that samples a scene-linear texture. For an sRGB target it writes values whose hardware encoding is the display's code value, so any display encoding survives. set_dynamic rewrites exposure and gamma. bind and set_region choose the image and the part of it shown.

Evidence

Against OpenColorIO 2.6 (pip, scratch venv; tools/make_*_fixtures.py), |ours − theirs| / max(1, |theirs|):

FixtureCasesLargest
ACES 2.0 output transforms (SDR Rec.709, SDR P3, HDR 1000 P3, HDR 1000 Rec.2020), forward1,7881.5e-5 of peak
the same, inverse: scene (under 10× white) / shown again1,7884.5e-4 relative / 6.2e-4 of peak
every builtin we define (63), forward and inverse4,7162.0e-4 (ADX inverse 3.0e-4: OCIO's half-float table)
ACES 2.0 CG config: every color space both ways, every display/view from ACEScg, ACEScct and sRGB, the look5,9516.5e-5
ACES 2.0 studio config: the same (cameras included)10,1351.5e-4
one color space per transform type and LUT format (.cube 1D/3D, .spi1d, .spi3d, .clf), file rules7822.6e-5

OpenColorIO inverts 3D tables approximately (a baked grid); ours solves each color, held to the round trip instead (1e-6).

GPU against CPU (Apple M4 Max, Metal; 311 colors per processor, 267 processors: every view, conversion, builtin both ways and LUT space), error relative to the color's size: views at most 4.9e-5, everything 9.3e-4 (PQ decode in f32) except a backward 3D table solved outside the table (2.0e-3). An image drawn through ImageDisplay into Rgba8Unorm matches the CPU's code values within one step, before and after an exposure change.