feat(gamut-cmm): N-D multilinear and tetrahedral CLUT interpolation - #368
Open
justin13888 wants to merge 4 commits into
Open
feat(gamut-cmm): N-D multilinear and tetrahedral CLUT interpolation#368justin13888 wants to merge 4 commits into
justin13888 wants to merge 4 commits into
Conversation
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.
This was referenced Aug 15, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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:
ClutTablewith two per-table modes: tetrahedral replicating lcms2'sTetrahedralInterpFloatexactly (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'sEval4InputsFloatshape, so 4-D CMYK differentials compare like-for-like); multilinear as the pure 2ᴺ corner-weight form for any of 1..=15 inputs.with_interpolationhook exists because gamut-cmm: LUT-based pipelines and per-intent tag selection #328 must select multilinear for Lab-indexed B2A/devicelink CLUTs (ChangeInterpolationToTrilinear).ClutPrecision::full_scale(). Documented divergence: 1-node axes evaluate as constants where lcms2 reads out of bounds.clut_probe_profile(arbitrary CLUT in a minimal devicelink mAB wrapper, with a PreOptimize-survival guard),Transform::devicelink, andClutPipeline(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
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.