feat(gamut): second-profile compare and click-inspect (#147) #153

Merged
gronod merged 1 commits from feat/147-gamut-compare into milestone/m10-studio 2026-09-13 10:24:24 +01:00
Owner

Refs #147

What

  • Layered gamut model (NamedGamut): bundled sRGB reference + profile A + a single compare slot (profile B). Picking a third profile replaces B; remove-compare leaves sRGB + A.
  • GamutGeometry (ICCeryCore): Möller–Trumbore ray-cast containment over native .gam faces with off-axis retries on edge hits (in/out/? when a mesh has no faces), plus signed-tetrahedron volume from the vertex centroid. Status line prints per-layer Nv / Nf faces clauses and vol N% of sRGB only when both volumes are finite and positive.
  • Compare loading: Open .gam… via new FileDialogService.selectGamutFile; Open profile… reuses selectProfileFile, uses a sibling .gam when present, else runs bundled iccgamut -v -d 10 through ArgyllRunner.runIccgamut. Failure keeps existing layers and posts an in-sheet gamutNoticeText (#24 — never fatal).
  • Inspect panel (always visible, fixed min height): click a mesh (SceneKit hit-test in the coordinator, x→a*, y→L*, z→b*), or type Lab*; per-layer containment + swatch. ApproximateLab fixed-matrix sRGB8→Lab D50 path is labelled approx. Lab, not ColorSync — no CMM.
  • Sample TIFF…: host-side PNG preview via TiffPreview.previewPNG (#58), click a pixel → approximate Lab + swatch; sheet gamutTiffPreview / btnCloseGamutTiffPreview.
  • Compare B renders at ~30 % opacity; sRGB keeps the faint fill + edge lines look; profile A stays solid vertex-coloured. Layer toggles hide nodes, never unload or reset the camera; R/btnResetGamutCamera preserved; no-GPU fallback shows gamutViewerUnavailable without respawn loops.
  • Existing identifiers untouched: btnViewGamut, gamutView, gamutStatusText, btnResetGamutCamera.

Tests

Targeted gate (xcodebuild test -scheme ICCery, ARCHS=x86_64, ad-hoc signed):

  • ICCeryCoreTests: GamutContainmentTests 5, ApproximateLabTests 4, GamutViewModelTests 6, GamutMeshParserTests 8, GamutGeometryBuilderTests, IccgamutArgsTests 1 — all pass.
  • ICCeryUITests: Milestone6GamutUITests 1, Milestone10GamutCompareUITests 7, AboutHelpUITests — 10 UI tests, all pass.

Full suite (same destination): 400 ICCeryCoreTests + 45 ICCeryUITests = 445 tests, 0 failures. UI tests ran for real (no attach-skip).

Notes

  • Face-less meshes report containment unknown rather than inventing a hull (no QuickHull fallback in the Swift parser — documented in docs/18 §13).
  • docs/18-gamut-viewer.md and docs/21-ui-reference.md updated with the new identifier roster.
Refs #147 ## What - Layered gamut model (`NamedGamut`): bundled sRGB reference + profile A + a single compare slot (profile B). Picking a third profile replaces B; remove-compare leaves sRGB + A. - `GamutGeometry` (ICCeryCore): Möller–Trumbore ray-cast containment over native `.gam` faces with off-axis retries on edge hits (`in`/`out`/`?` when a mesh has no faces), plus signed-tetrahedron volume from the vertex centroid. Status line prints per-layer `Nv / Nf faces` clauses and `vol N% of sRGB` only when both volumes are finite and positive. - Compare loading: `Open .gam…` via new `FileDialogService.selectGamutFile`; `Open profile…` reuses `selectProfileFile`, uses a sibling `.gam` when present, else runs bundled `iccgamut -v -d 10` through `ArgyllRunner.runIccgamut`. Failure keeps existing layers and posts an in-sheet `gamutNoticeText` (#24 — never fatal). - Inspect panel (always visible, fixed min height): click a mesh (SceneKit hit-test in the coordinator, x→a*, y→L*, z→b*), or type L*a*b*; per-layer containment + swatch. `ApproximateLab` fixed-matrix sRGB8→Lab D50 path is labelled `approx. Lab, not ColorSync` — no CMM. - `Sample TIFF…`: host-side PNG preview via `TiffPreview.previewPNG` (#58), click a pixel → approximate Lab + swatch; sheet `gamutTiffPreview` / `btnCloseGamutTiffPreview`. - Compare B renders at ~30 % opacity; sRGB keeps the faint fill + edge lines look; profile A stays solid vertex-coloured. Layer toggles hide nodes, never unload or reset the camera; `R`/`btnResetGamutCamera` preserved; no-GPU fallback shows `gamutViewerUnavailable` without respawn loops. - Existing identifiers untouched: `btnViewGamut`, `gamutView`, `gamutStatusText`, `btnResetGamutCamera`. ## Tests Targeted gate (xcodebuild test -scheme ICCery, ARCHS=x86_64, ad-hoc signed): - ICCeryCoreTests: GamutContainmentTests 5, ApproximateLabTests 4, GamutViewModelTests 6, GamutMeshParserTests 8, GamutGeometryBuilderTests, IccgamutArgsTests 1 — all pass. - ICCeryUITests: Milestone6GamutUITests 1, Milestone10GamutCompareUITests 7, AboutHelpUITests — 10 UI tests, all pass. Full suite (same destination): 400 ICCeryCoreTests + 45 ICCeryUITests = 445 tests, 0 failures. UI tests ran for real (no attach-skip). ## Notes - Face-less meshes report containment `unknown` rather than inventing a hull (no QuickHull fallback in the Swift parser — documented in docs/18 §13). - `docs/18-gamut-viewer.md` and `docs/21-ui-reference.md` updated with the new identifier roster.
gronod added 1 commit 2026-09-13 10:24:10 +01:00
Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
gronod self-assigned this 2026-09-13 10:24:15 +01:00
gronod added this to the M10 — Studio workflow (media library, gamut compare, spot-read, projects) milestone 2026-09-13 10:24:15 +01:00
gronod merged commit 99c0d7e83d into milestone/m10-studio 2026-09-13 10:24:24 +01:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: gronod/iccery-v2-mac#153