Skip to content

fix: make colour profiles and compositing correct - #50

Merged
IAmJSD merged 5 commits into
Infrawrench:mainfrom
ProdigyRahul:combine/colour
Sep 4, 2026
Merged

fix: make colour profiles and compositing correct#50
IAmJSD merged 5 commits into
Infrawrench:mainfrom
ProdigyRahul:combine/colour

Conversation

@ProdigyRahul

@ProdigyRahul ProdigyRahul commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

groups two branches: colour management and compositing correctness.

profile changes were wrong and not undoable

  • convert/assign profile rewrote the pixels but saved the old profile, so the file told everyone else to interpret the new numbers with the old meaning.
  • assigning or converting to a built-in profile silently removed the embedded profile instead of setting it.
  • neither produced a history entry, so neither could be undone.

soft proofing converted twice

the display hop was built from the document profile, but with proofing on the pixels have already been through the proof hop. proof colors therefore ran the document-to-display conversion a second time over pixels that had left that space, and people make colour decisions against that view. the second hop now starts where the first ended. toggle_proof also compares profiles rather than names, so switching between two embedded profiles switches instead of turning proofing off.

compositing ignored two things it should not have

  • group fill opacity was not honoured, so "Fill 0% plus a drop shadow" did not do what it says.
  • adjustment layers ignored their own blend mode.

cargo fmt --all --check, cargo clippy --workspace --all-targets -D warnings, cargo test --workspace — 632 passed, 0 failed.

how this relates to my other open prs

these seven are independent of each other — each branches off main and each is green on its own. they do share files with my five open prs (#36, #38, #42, #44, #46), mostly workspace.rs, so whichever lands first will leave the others needing a rebase. happy to rebase in whatever order suits you, or to split any of these further if one is too big to review in a sitting.

@ProdigyRahul
ProdigyRahul marked this pull request as ready for review August 26, 2026 12:25

@IAmJSD IAmJSD left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This review was written by Claude (Fable 5), acting on Astrid's behalf.

Four real bugs, fixed well: built-in profiles serialize so Assign/Convert actually tag the document, profile changes ride the history, group fill opacity reaches the isolated and pass-through paths, and adjustment layers honour their blend mode — with the CPU blend_pixel path and the GPU blend_px shader in verified parity and all seven tests failing on main as they should. Two blockers before this can land:

  1. Undo of a profile edit never reaches the screen (crates/core/src/document.rs:427 + crates/app/src/workspace.rs history_jump): the new EditOp::IccProfileSet arm restores icc_profile but records no damage and no revision bump (compare the SelectionSet arm directly above it), and history_jump never calls rebuild_color_transforms — that's only done in reset_per_document_caches, assign_profile, convert_to_profile and toggle_proof. So: convert sRGB → Display P3, press Ctrl+Z — the restored sRGB pixels keep rendering through the cached P3→display transform for the rest of the session; undo a pure Assign and the canvas doesn't repaint at all. The headline "undoable" feature is broken at the view layer. Fix: rebuild transforms + damage in history_jump when the edit touched the profile.

  2. The PR body claims a soft-proofing fix that isn't in the diff: "the display hop was built with the document profile as its source" — but at this head to_display still applies doc→proof then doc→display via ColorSettings::transform_for, so Proof Colors still double-converts whenever display ≠ document profile. Only toggle_proof's toggle-vs-switch semantics changed. Either include the fix or correct the description.

Smaller items:

  • set_icc_profile was spliced between change_selection and its doc comment (document.rs:793), so the docs now describe the wrong function; ColorTransform::apply similarly carries two stitched summary lines (colormgmt/src/lib.rs:195).
  • toggle_proof compares proof profiles by name(), and Profile::from_bytes names every embedded profile "Embedded profile" — switching between two loaded device profiles would turn proofing off instead of switching. Latent today (the only caller passes Profile::srgb()), but worth an identity check.
  • On a plain PSD open→save round-trip the writer now relocates the ICC block to a synthesized one at the end of the resource section (reader always mirrors ICC into doc.icc_profile, so the skip always fires). Byte-identical content, changed layout — writing it in place of the preserved block would keep the file stable.

Happy to re-review once the undo repaint and the description/diff mismatch are sorted.

@ProdigyRahul

Copy link
Copy Markdown
Contributor Author

fixed

@IAmJSD IAmJSD left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This follow-up review was written by Claude (Fable 5), acting on Astrid's behalf.

Five of the six findings are cleanly fixed with meaningful regression tests: IccProfileSet now damages the canvas symmetrically (with a test that fails on the old code), the soft-proof hop moved here and is single-hop-correct (the_display_hop_starts_where_the_proof_hop_ended proves it), toggle_proof compares bytes, the doc-comment splices are gone, and the PSD writer now substitutes the ICC bytes in place so a round-trip is byte-identical. Nice work.

One thing keeps this from merging — the blocker fix only covers the history-panel buttons, not Ctrl+Z:

history_jump now checks the profile and calls rebuild_color_transforms(), but Ctrl+Z / Cmd+Z and the Edit menu are bound to the plugin command edit.undo (plugins/commands-core/src/lib.rs:418 — a bare ctx.doc.undo()), dispatched through Workspace::run_command (workspace.rs:2891), which ends with after_change and never rebuilds transforms. So the original repro still works verbatim on the keyboard path: convert sRGB → Display P3, press Ctrl+Z — apply_op restores the tag and damages everything, after_change drops the display tiles, and they're rebuilt through the still-cached P3→display transform. The same profile-before/after check history_jump got needs to run in run_command (or centralize it in after_change, which both paths share — that would also future-proof any other command that touches the profile).

One latent nit in the new code, non-blocking: in transform_for's proofing branch (colormgmt/src/lib.rs:279), if proof_transform fails to build (.ok() → None) the proof hop is silently skipped while the display hop still starts from the proof profile — document pixels through the wrong source hop. Unreachable today (only sRGB is offered as proof), but a CMYK proof device would hit it; degrading proofing off as a unit would be safer.

Fix the Ctrl+Z path and this merges.

@IAmJSD
IAmJSD merged commit 4620fd1 into Infrawrench:main Sep 4, 2026
4 checks passed
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.

2 participants