From e8f68a92f8fbf537ab46fd7bc402bb8c5dcf4a5f Mon Sep 17 00:00:00 2001 From: Duncan Tourolle Date: Thu, 24 Sep 2026 21:00:09 -0400 Subject: [PATCH] Record intersection as built in the requirement and the mask plan FR-DEV-19a said a part is added to the mask or taken out of it, and mask-editing.md listed Intersect as an M2 item with nothing built. Both now say what shipped: the requirement names the third join and why it is a product rather than a minimum, and how old and new sidecars read across it; the plan marks the blend-table row built, names the tests that hold the GPU to the definition, and splits M2 into what is done and what is still outstanding (joining non-painted parts from the panel, per-part distance fields, folding two layers). --- docs/dev/mask-editing.md | 20 +++++++++++++++++++- docs/dev/requirements.md | 14 +++++++++++--- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/docs/dev/mask-editing.md b/docs/dev/mask-editing.md index 4158193..6cb0bfb 100644 --- a/docs/dev/mask-editing.md +++ b/docs/dev/mask-editing.md @@ -269,7 +269,7 @@ The set operations are already expressible in fixed-function blending over |------|-------------------------------|--------|-------| | Union | `One`, `One`, `Max` | `max(dst, src)` | M1 | | Subtract | `Zero`, `OneMinusSrc`, `Add` | `dst · (1 − src)` | M1 | -| Intersect | `Zero`, `Src`, `Add` | `dst · src` | M2 | +| Intersect | `Zero`, `Src`, `Add` | `dst · src` | M2 (built 2026-09-24) | The middle row is `brush_erase`, already constructed in [`MaskPass::new`](../../core/dr-gpu/src/mask.rs). The other two are the same @@ -292,6 +292,16 @@ rendering as it did. `an_erase_stroke_holes_its_own_part_and_not_the_mask` in [`local_adjustments.rs`](../../core/dr-gpu/tests/local_adjustments.rs) is the test that holds this in place. +Intersection, as built, is exactly the table's row: a third pipeline beside +the other two in `MaskPass::new`, `Join::apply` stating the three on the CPU, +and `a_part_unioned_subtracted_and_intersected_gives_the_three_fields` and +`the_joins_match_their_definition` in `local_adjustments.rs` holding the GPU +to it. It is a product, not a minimum: the two agree wherever either side is +fully in or out, and between two soft edges the product is the softer reading, +which is the right way for an overlap of two partial selections to be wrong. +Its blend is `Add`, not `Max`, so it adds nothing to the Android question +below. + `Max` blending on `r8unorm` is core WGPU and universally supported on the desktop backends; **verify it on the Android adapter before M2 lands**, since that is the platform where a blend mode is most likely to be quietly emulated @@ -689,6 +699,14 @@ nothing, and every report of it came back as "the masks do not work". per-part distance fields (§5.5), the part list with its join chips, folding two layers. +Done (2026-09-24): `Intersect` — the join, its blend state, the sidecar word +`intersect`, the part row's chip cycling + / − / ∩, and an "∩ Intersect" +button beside Add and Subtract (§7.1). The part list with its chips and a +per-part eye was already there from M1. Outstanding: joining a model, +gradient or range part from the panel (the buttons still join a painted +part, so an intersection is painted where the mask should survive), per-part +distance fields, and folding two layers. + **M3 — Push, and cling.** The warp pass (§5.3) and edge-aware deposit (§5.6). Both are refinements of a tool that already works, which is the right place for the two riskiest pieces. diff --git a/docs/dev/requirements.md b/docs/dev/requirements.md index 116af20..fe4d4d4 100644 --- a/docs/dev/requirements.md +++ b/docs/dev/requirements.md @@ -544,9 +544,17 @@ decade; a mask that cannot be corrected is the reason an edit leaves this applic editor. **FR-DEV-19a — Mask composition.** A layer's mask is an ordered list of parts, each naming a source -and how it joins the mask before it — added to it, or taken out of it. A layer of one part is -exactly the layer that existed before this, and reads and writes the same sidecar. The parts of a -layer merge under FR-NC-9 as the layer does, and each carries its own edge treatment. +and how it joins the mask before it — added to it, taken out of it, or intersected with it, keeping +only where both agree. A layer of one part is exactly the layer that existed before this, and reads +and writes the same sidecar. The parts of a layer merge under FR-NC-9 as the layer does, and each +carries its own edge treatment. + +*Intersection added 2026-09-24* (issue #9, `mask-editing.md` M2). Union and subtraction were built +first; the selections that most need composing — the sky that is also bright, the subject that is +also skin — are intersections, and spelling one as a subtraction needs a part that selects the +complement. Intersection is the product of the two coverages, which is the minimum wherever either +side is fully in or out. A sidecar written before it never names it; a build from before it reads +the word as a union, which keeps the part visible rather than dropping it. **FR-DEV-19b — Hand correction.** A part may be painted, with add and erase strokes, at a radius, hardness and flow the photographer sets. Strokes are stored as normalised source coordinates and