Skip to content

feat(gamut-cmm): matrix/TRC shaper pipelines and the chad convention - #369

Open
justin13888 wants to merge 3 commits into
feat/326-cmm-clut-interpfrom
feat/327-cmm-matrix-shaper
Open

feat(gamut-cmm): matrix/TRC shaper pipelines and the chad convention#369
justin13888 wants to merge 3 commits into
feat/326-cmm-clut-interpfrom
feat/327-cmm-matrix-shaper

Conversation

@justin13888

Copy link
Copy Markdown
Collaborator

Summary

PR 6/9 of the gamut-cmm stack (epic #323), stacked on #368 — the epic's flagged highest-risk phase: matrix/TRC ("shaper") pipelines and the v2/v4 chromatic-adaptation convention.

  • Device→PCS: TRC Curves into the colorant Matrix (column per primary); PCS→device: linalg-inverted matrix into analytically inverted curves (SingularMatrix on singular/non-finite colorants — exact-zero/non-finite determinant is the documented conditioning threshold). Gray kTRC via the new general-affine Stage::MatrixN (1↔3, decoded D50 scaling for XYZ PCS, L*/100 for Lab PCS).
  • The chad convention, settled and documented in STATUS.md with citations: colorants are taken as-is (v2 and v4 — ICC.1:2022 requires D50-adapted colorants; chad is informative), chad is never read on the relative path (lcms2's sole chad consumer is the absolute-intent branch, inert at default adaptation state — exhaustive consumer audit cited), wtpt is reserved for absolute colorimetric (gamut-cmm: rendering intents (absolute colorimetric) and black point compensation #329). Deliberate match of lcms2 over a strict v2 reading, per the issue's requirement to document the divergence explicitly.
  • LUT tags take precedence over the shaper (lcms2 rule); until gamut-cmm: LUT-based pipelines and per-intent tag selection #328 those pipelines refuse with UnsupportedProfile rather than guessing.

Validation

  • 126 tests green (9 new oracle differentials + ~15 new unit tests). Differentials reopen every profile from serialized bytes on both sides so they measure evaluation semantics, not tag quantization: device→PCS raw XYZ ≤8.8e-8 (asserted 1e-6, f32 oracle floor) for sRGB / Display P3 / wide-gamut γ2.2; chad three-way tested separately: v2±chad bitwise identical, v2 vs v4 exactly equal, each ≤7.6e-8 vs lcms2. Colorant-matrix orientation pinned against Lindbloom's published sRGB matrix (transposition mutants die). Error paths asserted by exact display string.
  • mise run mutants-crate gamut-cmm (full crate): 561 mutants — 546 caught, 15 unviable, 0 missed. Coverage 99.0% lines; clippy -D warnings, fmt-check, cargo doc clean.

Stack

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

Closes #327. Part of #323.

Builds runnable pipelines from v2 and v4 matrix/TRC profiles - the
shape carried by sRGB, Display P3, Adobe RGB, and every camera-tagged
JPEG - in both directions: TRC curves into the colorant matrix
(column per primary, as read) for device->PCS, and the linalg-inverted
matrix into analytically inverted curves for PCS->device (singular or
non-finite colorant matrices reject with SingularMatrix). Gray kTRC
profiles map through the new general-affine Stage::MatrixN (1<->3
shapes, decoded D50 scaling for XYZ PCS, L*/100 for Lab PCS).

The flagged v2/v4 risk is settled and documented: colorants are taken
as-is (spec-mandated D50-adapted), chad is never read on the relative
path - matching lcms2, whose only chad consumer is the absolute-intent
branch, inert at default adaptation state - and wtpt is reserved for
absolute colorimetric (#329). LUT tags take precedence over the
shaper; until #328 lands those pipelines they refuse with
UnsupportedProfile rather than guessing.

Part of #327.
Differentials reopen each synthesized profile from its serialized
bytes on both sides, so the comparison measures evaluation semantics
rather than tag quantization: device->PCS raw XYZ within 1e-6 (f32
oracle floor, measured 8.8e-8) for sRGB, Display P3, and a gamma-2.2
wide-gamut shaper; the chad three-way tested separately as the issue
demands - v2-with-chad and v2-without-chad produce bitwise-identical
pipelines, v2 and v4 of the same colorimetry agree exactly, and each
matches lcms2. Gray, round-trip, and PCS->device sweeps carry
measured, justified bounds (near-black looseness is gamma-slope
amplification, documented at the assert). Error paths are pinned by
exact display string: singular colorants, missing/mistyped tags, and
the LUT-tag refusal boundary.

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