Skip to content

Dodge & burn as print exposure; density-driven chemical toning#403

Merged
marcinz606 merged 7 commits into
mainfrom
feat/exposure-domain-dodge-burn
Jul 5, 2026
Merged

Dodge & burn as print exposure; density-driven chemical toning#403
marcinz606 merged 7 commits into
mainfrom
feat/exposure-domain-dodge-burn

Conversation

@marcinz606

Copy link
Copy Markdown
Owner

Summary

Two physical-realism upgrades to the darkroom print model:

Dodge & burn in the print-exposure domain

  • Masks no longer multiply the finished positive (2^strength, which clipped to flat black/white); they now offset per-pixel print exposure ahead of the H&D curve, in the same domain as WB filtration. Burns roll into paper black through the toe, dodges lift toward paper white through the shoulder — like holding back light under the enlarger.
  • The separate local stage/shader is gone: the EV map (compute_local_ev_map) feeds the exposure pass on CPU and GPU, tiled export slices it per tile, and the exposure cache stage is keyed on (exposure, local).
  • Dodge & Burn UI section moved to the Exposure tab; tutorial step moved and reworded.
  • Fixed en route: exposure UBO binding size (240 → 256) for the grown uniform struct.

Density-driven selenium & sepia (B&W)

  • Chemical toners act on the print's silver density (D = −log10(t)) instead of luma-masked RGB tints: selenium converts the densest silver first (Dmax boost — visibly deeper blacks — and cool eggplant shadows); sepia bleach-redevelops the thinnest silver first (warm highlights, shadows hold; partial strength = classic split-sepia). Paper white stays white.
  • Runs on the linear buffer, no OETF bracket; CPU and WGSL share TONING_CONSTANTS. Conversion caps at all-silver-toned so the 0–2 sliders behave as a longer bath.
  • Also gates GPU chemical toning to B&W, matching the CPU (it was silently active in colour modes).

Look change: existing edits using dodge/burn masks or selenium/sepia will render differently (noted in CHANGELOG under 0.34.0).

Test plan

  • make all green (lint, ty, full pytest).
  • GPU parity suite: 30/30 including dodge/burn through the new exposure path and new B&W chemical-toning parity cases (first B&W coverage in the suite).
  • New unit tests: EV-map math (additivity, feather, geometry), burn/dodge roll-off vs clipping, toner density behaviour (densest/thinnest-first, paper-white identity, slider saturation).
  • Verified live: drove the real app under Xvfb (.claude/skills/verify/SKILL.md has the recipe) — selenium deepens/cools shadows, sepia warms highlights only, −3 EV burn keeps tonal separation, Dodge & Burn section on the Exposure tab.

Masks now offset per-pixel print exposure before the H&D curve
(same domain as WB filtration) instead of multiplying the finished
positive: burns roll into paper black through the toe, dodges lift
through the shoulder — no clipped flat patches. Local stage removed;
the EV map feeds the exposure pass on CPU and GPU (bit-exact parity),
tiled export slices it per tile. Dodge & Burn UI moves to the
Exposure tab. Existing dodge/burn edits render slightly differently.
Chemical toners act on the print's silver density instead of
luma-masked RGB tints: selenium converts the densest silver first
(Dmax boost, cool eggplant shadows), sepia bleach-redevelops the
thinnest first (warm highlights, shadows hold, split-sepia at
partial strength). Paper white stays white. Runs on the linear
buffer — no OETF bracket; CPU and WGSL share TONING_CONSTANTS.
GPU chemical toning now gated to B&W like the CPU. Existing toned
edits render differently.
@marcinz606 marcinz606 merged commit 86c807d into main Jul 5, 2026
1 check passed
@marcinz606 marcinz606 deleted the feat/exposure-domain-dodge-burn branch July 5, 2026 07:46
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