Keep only where two selections agree, as a third way to join a mask part

A layer's parts could be added to the mask or taken out of it, and nothing
else. The selections that need composing most are the ones that are
neither: the sky that is also bright, the subject that is also skin. With
union and subtract alone, "this and that" had to be spelled as "this minus
everything that is not that", which needs a second part that selects the
complement and rarely exists.

Join gains Intersect, stored as "intersect" in the part block of a sidecar.
It is the product of the two coverages, dst * src, which is one more
fixed-function blend state beside union's max and subtract's
dst * (1 - src) (mask-editing.md 5.2): the same scratch texture, the same
three vertices, no shader arithmetic. The product equals the minimum
wherever either side is fully in or out, and is the softer reading where
two soft edges overlap. Join::apply spells the three operations on the CPU
so the GPU tests can be held to one definition.

A layer that intersects with a part covering nothing now reports that it
covers nothing, so it is not rasterised as an empty slice. Old sidecars
never contain the word, so they read as before; a build from before this
reads "intersect" as a union, the existing unknown-join fallback, which
keeps the part visible rather than dropping it. Join::ALL keeps union and
subtract at indices 0 and 1 so a stored panel index still means the same
join.
This commit is contained in:
2026-09-24 21:25:48 -04:00
parent 229def0afc
commit 8cdad3863d
7 changed files with 286 additions and 14 deletions
+13 -4
View File
@@ -5,7 +5,7 @@
Every entry here is extracted from the comment beside the code that implements it, so this file cannot describe a gesture the application does not have. Add one by writing a `GESTURE:` block next to the implementation; there is nowhere else to write it.
47 gestures, in 4 places.
48 gestures, in 4 places.
## Develop
@@ -189,7 +189,7 @@ The history is forgotten with the sitting, on purpose; a snapshot is the photogr
Disabling a layer is the before-and-after a local edit constantly wants, so it is one press away rather than inside the row. It is an edit and does take a
<sub>`ui/dr-ui/ui/masks.slint:212`</sub>
<sub>`ui/dr-ui/ui/masks.slint:213`</sub>
### Show or hide one mask on the photograph
@@ -198,7 +198,16 @@ Disabling a layer is the before-and-after a local edit constantly wants, so it i
A mask is judged by seeing where it falls, and two are judged by seeing where they meet — so each row has its own eye rather than the panel having one, and the eye is drawn in the colour the mask shows in, so the row says which shape on the picture is its. Nothing about the edit changes: this is how the photograph is looked at, and takes no history step.
<sub>`ui/dr-ui/ui/masks.slint:279`</sub>
<sub>`ui/dr-ui/ui/masks.slint:280`</sub>
### Change how a part joins its mask
- **Touch** — Tap the + / − / ∩ chip on the part's row
- **Pointer** — Click the + / − / ∩ chip on the part's row
A chip that cycles rather than a menu, because a photographer flips a join while looking at the picture, not at the panel: add, take away, keep only where both agree, and round.
<sub>`ui/dr-ui/ui/masks.slint:366`</sub>
### Leave one part out of a mask, and put it back
@@ -207,7 +216,7 @@ A mask is judged by seeing where it falls, and two are judged by seeing where th
The question a correction raises is whether it did what it was for — whether the stroke filled the shoulder, whether the subtracted gradient took only the sky. Removing it answers that and loses it. The same ring the layer wears, one row down, because it is the same question about a smaller thing.
<sub>`ui/dr-ui/ui/masks.slint:394`</sub>
<sub>`ui/dr-ui/ui/masks.slint:405`</sub>
## Collections sidebar