Skip to content

feat(gamut-cmm): N-D multilinear and tetrahedral CLUT interpolation - #368

Open
justin13888 wants to merge 4 commits into
feat/325-cmm-tone-curvesfrom
feat/326-cmm-clut-interp
Open

feat(gamut-cmm): N-D multilinear and tetrahedral CLUT interpolation#368
justin13888 wants to merge 4 commits into
feat/325-cmm-tone-curvesfrom
feat/326-cmm-clut-interp

Conversation

@justin13888

Copy link
Copy Markdown
Collaborator

Summary

PR 5/9 of the gamut-cmm stack (epic #323), stacked on #364. Implements CLUT interpolation — the stage that makes LUT-based profiles evaluable:

  • ClutTable with two per-table modes: tetrahedral replicating lcms2's TetrahedralInterpFloat exactly (fclamp, exact-1.0 upper-index rule, the six-branch >= cascade in source order — tie order is observable and pinned), with ≥4-D recursing outer-axis slices down to the 3-D tetrahedral base (lcms2's Eval4InputsFloat shape, so 4-D CMYK differentials compare like-for-like); multilinear as the pure 2ᴺ corner-weight form for any of 1..=15 inputs.
  • Defaults match lcms2 (tetrahedral for ≥3 inputs); the with_interpolation hook exists because gamut-cmm: LUT-based pipelines and per-intent tag selection #328 must select multilinear for Lab-indexed B2A/devicelink CLUTs (ChangeInterpolationToTrilinear).
  • Grid addressing per ICC §10.12.5 pinned by hand-built tables independent of any interpolator; samples normalized once by ClutPrecision::full_scale(). Documented divergence: 1-node axes evaluate as constants where lcms2 reads out of bounds.
  • Oracle additions (build.rs untouched): clut_probe_profile (arbitrary CLUT in a minimal devicelink mAB wrapper, with a PreOptimize-survival guard), Transform::devicelink, and ClutPipeline (cmsPipelineEvalFloat — lcms2's float interpolators without 16-bit quantization).
  • references/cmm/ now transcribes the tetrahedral decomposition (cited to cmsintrp.c lines; Kasson et al. 1995 is paywalled, per the vendoring convention).

Validation

  • 97 tests green. Float-path differentials vs lcms2's own interpolators: 1-D–4-D measured ≤1.9e-7, asserted 1e-6; profile-route ≤6.3e-5, asserted 5e-4 (bound set by lcms2 evaluating profile-borne 16-bit CLUTs in fixed point — documented). Per-branch bitwise tetrahedral probes; naive corner-weight cross-check at 1e-12; tetra-vs-trilinear divergence bounded and asserted nonzero.
  • cargo mutants -p gamut-cmm: 325 mutants, all viable caught (2 initial misses killed by test hardening, not exclusions). Coverage 98.8% regions; clippy/fmt/doc clean.

Stack

#321 (#351) → #322 (#358) → #324 (#362) → #325 (#364) → #326 (this)#327#328#329#330.

Closes #326. Part of #323.

ClutTable turns gamut-icc's data-only Clut into an evaluable stage:
samples normalized once by the table's full scale, grid addressing per
ICC s10.12.5 (last input channel fastest, outputs interleaved), and
two interpolation modes carried per table. Tetrahedral replicates
lcms2's TetrahedralInterpFloat exactly - the fclamp input rule, the
exact-1.0 upper-index rule, and the six-branch >= cascade in source
order (tie order is observable) - with >=4 inputs recursing
outermost-axis slices down to the 3-D tetrahedral base, exactly
lcms2's Eval4InputsFloat shape, so 4-D CMYK differentials compare
like-for-like. Multilinear is the pure corner-weight form at any
dimension. Defaults match lcms2 (tetrahedral for >=3 inputs); the
mode hook exists because Lab-indexed B2A/devicelink CLUTs must select
multilinear (#328). One documented divergence: 1-node axes evaluate
as constants where lcms2 reads out of bounds.

Stage::Clut joins the pipeline enum with its eval arm in the same
diff, per the wildcard-free growth rule.

Part of #326.
clut_probe_profile wraps caller-supplied CLUT node data in a minimal
devicelink-class mAB profile (identity curves around a granular
16-bit CLUT, with a node-reproduction guard proving the identity
stages survive lcms2's PreOptimize), Transform::devicelink drives it,
and ClutPipeline exposes cmsPipelineEvalFloat so stage-level
differentials can hit lcms2's actual float interpolators without
16-bit quantization. build.rs unchanged.

Part of #326.
Float-path differentials against lcms2's own interpolators (1-D to
4-D within 1e-6, sweeps covering corners, per-axis nodes, near-tie
fractions, NaN, and out-of-range) plus profile-route transforms at
the documented 5e-4 bound set by lcms2's fixed-point evaluation of
profile-borne 16-bit CLUTs. Grid addressing is pinned by hand-built
tables independent of any interpolator; each tetrahedral branch has a
bitwise hand-derived probe; tetrahedral-vs-trilinear divergence is
bounded and asserted nonzero; an independent naive corner-weight
implementation cross-checks multilinear at 1e-12.

Part of #326.
references/cmm now carries the six-branch cell decomposition and
selection rules cited to cmsintrp.c and Kasson et al. (1995, paywalled
- transcribed per the workspace convention); STATUS records the P3
settled decisions.

Part of #326.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant