Skip to content

feat(gamut-cmm): LUT pipelines and per-intent tag selection - #370

Open
justin13888 wants to merge 3 commits into
feat/327-cmm-matrix-shaperfrom
feat/328-cmm-lut-pipelines
Open

feat(gamut-cmm): LUT pipelines and per-intent tag selection#370
justin13888 wants to merge 3 commits into
feat/327-cmm-matrix-shaperfrom
feat/328-cmm-lut-pipelines

Conversation

@justin13888

Copy link
Copy Markdown
Collaborator

Summary

PR 7/9 of the gamut-cmm stack (epic #323), stacked on #369. Turns LUT-based profiles — lut8, lut16, lutAToB, lutBToA — into runnable pipelines, with per-rendering-intent tag selection:

  • Each tag assembles in its native encoded [0,1] domain with the PCS seam decoded at the end (exact-dyadic constants derived in comments: PCSXYZ ×65535/32768; v4 Lab L=v·100, a/b=v·255−128; v2 Lab L=v·100.390625, a/b=v·255.99609375−128).
  • The classic trap is policy + a dedicated regression test: lut16Type decodes PCSLAB with the v2 encoding even inside a v4 profile — keyed on the tag's true type, never the header version (proven by a version-downgraded twin profile) — while lut8 and mAB/mBA use v4.
  • Intent selection uses lcms2's verbatim tables (A2B0/1/2/1, B2A0/1/2/1 — absolute maps to the relative tag, pinned bit-equal); missing tag → perceptual tag → matrix shaper → typed error, each transition tested (the fallback is proven equal to lcms2 on identically modified re-serialized bytes).
  • Lab-indexed PCS→device CLUTs select multilinear (lcms2's trilinear switch) — proven load-bearing: tetrahedral mode diverges 10× from the lcms2 agreement.
  • lut8/16's embedded 3×3 matrix applies for 3-channel non-identity tags (lcms2's rule, not XYZ-gated — documented divergence from the spec's letter). Lenient stage combinations per gamut-icc's parse. The Lab-PCS RGB shaper refusal from gamut-cmm: matrix/TRC pipelines and chad / white-point conventions #327 lifts via the new XyzToLab/LabToXyz stages.
  • Found + documented for gamut-cmm: rendering intents (absolute colorimetric) and black point compensation #329: lcms2 force-enables BPC for v4 profiles under perceptual/saturation — differentials here neutralize it via a v2 Lab endpoint; gamut-cmm: rendering intents (absolute colorimetric) and black point compensation #329 implements it.

Validation

  • 162 tests green (18+11 new LUT unit/differential tests). Differential bounds measured and documented per assert (A2B ≤4.2e-3 Lab on grid-9 profiles; B2A ≤2.4e-5).
  • mise run mutants-crate gamut-cmm: 634 mutants — 602 caught, 32 unviable, 0 missed, no new exclusions. Coverage 99.13% lines; clippy -D warnings, fmt-check, cargo doc clean.

Stack

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

Closes #328. Part of #323.

Assembles runnable pipelines from all four LUT tag types, each in the
tag's native encoded [0,1] domain with the PCS seam decoded at the
end: lut8/lut16 as matrix (3-channel non-identity only - lcms2's
rule, not XYZ-gated as the spec's letter suggests; documented
divergence) into input tables, CLUT, and output tables; mAB/mBA in
their s10.12/s10.13 element orders with optional stages omitted
leniently. The classic trap is pinned as policy: lut16 decodes PCSLAB
with the v2 encoding even inside a v4 profile - keyed on the tag's
true type, never the header version - while lut8 and mAB/mBA use v4.
Lab-indexed PCS-to-device CLUTs select multilinear interpolation,
matching lcms2's trilinear switch.

Intent selection uses lcms2's verbatim tables (absolute maps to the
relative tag; a missing intent tag falls back to perceptual, then the
matrix shaper, then a typed error). The Lab-PCS RGB shaper refusal
lifts via the new XyzToLab/LabToXyz stages, whose eval arms land in
this same diff per the wildcard-free growth rule.

Part of #328.
…-CMS

Stage-order unit pins per tag type and direction, the dedicated
lut16-v2-Lab-in-a-v4-profile regression the issue demands (with lut8
pinned to v4 scaling alongside), per-intent distinctness on the CMYK
prtr corpus with absolute bit-equal to relative, the perceptual
fallback proven equal to lcms2 on identically modified re-serialized
bytes, and the trilinear rule proven load-bearing (tetrahedral mode
diverges 10x from the lcms2 agreement). Differentials neutralize
lcms2's v4 forced-BPC perceptual behaviour via a v2 Lab endpoint and
a version-downgraded twin profile - which doubles as proof both sides
key the v2 encoding on tag type, not header version; measured bounds
documented at each assert.

Part of #328.
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