Show the photographer the mask they are shaping
Nobody can refine an edge they are not being shown. The only thing drawn on the canvas was the region overlay — a false-coloured picture of what the model *detected* — which knows nothing of a layer's feather, its falloff, its morphology, its invert or its opacity, and nothing at all about a gradient, a range or a stroke. Every control added for mask editing therefore acted on something invisible, which is why the whole feature reads as absent rather than as unfinished. A layer's finished mask now draws over the photograph in one of three styles: a tint for whether the right thing is selected, an alpha for where the edge is, an outline for whether that edge is registered against the detail the other two hide. The hard part is not the shader. A selection with no adjustment on it changes no pixel, so it is not active, so it holds no slice of the mask array and is never rasterised — and that is exactly the layer somebody wants to look at, for the whole of the time between choosing a subject and deciding what to do to it. So `MaskStack::rendered` is `active()` plus the layer being looked at, and the rasteriser, the composer and the distance-field builder all index by position in it. Which is also why the design's "two uniforms, no recompile" is not available: a uniform can select a slot, it cannot conjure one. The reveal is never on the graph. It reaches the pipeline as an argument to `compose_revealing`, and `compose_for` — which the exporter, the thumbnail and the neutral probe all call — has no way to ask for one. A flag on the graph would have been shorter, would have type-checked, and would have been one forgotten reset away from a red tint baked into an exported file. And the tools that shape a mask now arm. `Masking.tool` is an `in` property only Rust may write, and the handler wrote nothing back, so the strip reported "Select" however many times Paint was pressed and the paint area was never enabled — the brush, the parts and the whole of FR-DEV-19b reachable from no control in the application. The region overlay stands down while a mask is being shown, and its button now says what it hides: two overlays that look alike and mean different things is worse than either.
This commit is contained in:
+96
-19
@@ -36,12 +36,18 @@ the *whole* boundary. When the model's coverage stops two pixels inside the
|
||||
shoulder and leaks four pixels into the hair, no global number fixes both, and
|
||||
that is the ordinary case rather than a corner one.
|
||||
|
||||
**And you cannot see the mask.** The overlay on the canvas is
|
||||
[`overlay_rgba`](../ui/dr-ui/src/segmentation.rs) — a CPU-built false-colour
|
||||
picture of *what the model detected*, at proxy resolution. It is not the
|
||||
layer's alpha: it knows nothing of the layer's feather, its falloff, its
|
||||
morphology, its invert, or its opacity. Nobody can refine an edge they are not
|
||||
being shown.
|
||||
**And you cannot see the mask.** *(Built — see §6.)* The overlay on the canvas
|
||||
was [`overlay_rgba`](../ui/dr-ui/src/segmentation.rs) and nothing else — a
|
||||
CPU-built false-colour picture of *what the model detected*, at proxy
|
||||
resolution. It is not the layer's alpha: it knows nothing of the layer's
|
||||
feather, its falloff, its morphology, its invert, or its opacity. Nobody can
|
||||
refine an edge they are not being shown.
|
||||
|
||||
That one turned out to be load-bearing for the other three rather than the
|
||||
last of four. With no way to see a mask, choosing a category produced a layer
|
||||
whose extent was invisible and whose adjustment had not been touched yet — so
|
||||
the correct behaviour and the broken one look identical, and "the segmentation
|
||||
does not make masks" is what it reads as from the outside.
|
||||
|
||||
Those four are one feature, and this is its specification.
|
||||
|
||||
@@ -390,31 +396,89 @@ usable along hair.
|
||||
|
||||
## 6. Seeing the mask
|
||||
|
||||
The mask array is **already bound to the composed adjust shader** — this is
|
||||
almost free, and it is the first thing to build, because every other tool here
|
||||
**Status: built.** `MaskStack::rendered`, `RevealStyle`,
|
||||
`EditGraph::compose_revealing`, `MaskPass::render_revealing`, and the "Show
|
||||
mask" strip in the Local panel.
|
||||
|
||||
The mask array is **already bound to the composed adjust shader**, so this is
|
||||
almost free, and it is the first thing to build because every other tool here
|
||||
is unusable without it.
|
||||
|
||||
Two uniforms in the composed shader: which layer to reveal (−1 for none) and
|
||||
which style. The block is always emitted, guarded by the uniform, so switching
|
||||
the overlay on is a uniform write rather than a shader recompile.
|
||||
### 6.1 One correction to this section, found in the building
|
||||
|
||||
The draft said "two uniforms — which layer to reveal (−1 for none) and which
|
||||
style — always emitted and guarded by the uniform, so switching the overlay on
|
||||
is a uniform write rather than a shader recompile". The second half of that is
|
||||
not available, and the reason is the case the feature exists for.
|
||||
|
||||
A uniform can *select* a slot. It cannot conjure one. A layer with no
|
||||
adjustment on it changes no pixel, so it is not `is_active`, so it occupies no
|
||||
slice of the mask array and the rasteriser never draws it — and that is
|
||||
precisely the layer a photographer wants to look at, for the whole of the time
|
||||
between choosing a subject and deciding what to do to it. Revealing it means
|
||||
*rendering* it, which changes the sequence of layers, which changes the
|
||||
uniform block. The composition moves either way.
|
||||
|
||||
So the slot and the style are written into the source, and turning the reveal
|
||||
on, off, or onto another layer recompiles the fused shader. That is a button
|
||||
press rather than a frame, and the uniform would only have added a branch per
|
||||
pixel on top of a recomposition that was happening anyway.
|
||||
|
||||
`MaskStack::rendered(reveal)` is the one sequence this rests on: `active()`
|
||||
plus the layer being looked at. The rasteriser, the composer and the distance
|
||||
field builder all index by position in it, so all three must be given the same
|
||||
`reveal` — two of them disagreeing shows as an adjustment applied through
|
||||
another layer's mask, which is why they take it as an argument rather than
|
||||
reading a flag.
|
||||
|
||||
### 6.2 Where the block runs, and why not with the others
|
||||
|
||||
After the output transform, immediately before the clip and the encode — not
|
||||
among the layer blocks. Everything there runs on scene-referred colour in the
|
||||
working space, where a flat tint would be pushed through the base curve and
|
||||
the camera matrix and arrive as some other colour, and an alpha's white on
|
||||
black would arrive as neither.
|
||||
|
||||
### 6.3 Not on the graph
|
||||
|
||||
The reveal is an argument to `EditGraph::compose_revealing`, and
|
||||
`compose_for` — which the exporter, the thumbnail and the neutral probe all
|
||||
call — has no way to ask for one. A flag on the graph would have been fewer
|
||||
parameters, would have type-checked, and would have been one forgotten reset
|
||||
away from a red tint baked into an exported file.
|
||||
|
||||
Three styles, all read from the same alpha:
|
||||
|
||||
- **Tint** — the mask over the picture in a flat colour at ~50%. The default,
|
||||
and what every editor's photographers already expect. Red by default and
|
||||
configurable, since a red tint over a red dress shows nothing.
|
||||
and what every editor's photographers already expect. Red, and *not yet*
|
||||
configurable: the case for choosing the colour is a red tint over a red
|
||||
dress, and the answer to that is the Alpha style beside it until somebody
|
||||
finds a picture where neither works.
|
||||
- **Alpha** — the mask alone, white on black. For judging an edge, where a
|
||||
tint over a busy picture cannot be read.
|
||||
- **Edge** — the boundary outlined over the untouched picture. For checking
|
||||
registration against detail the other two hide, and the same reasoning the
|
||||
region overlay's white outline already carries.
|
||||
|
||||
**When it appears.** Automatically while a mask tool is armed, while a part
|
||||
row is selected, and for ~1s after a shaping slider is released; manually from
|
||||
a control in the Local panel. The existing `overlay-hidden` property is the
|
||||
precedent and the trap it documents applies unchanged: the photographer's
|
||||
switch and the automatic reveal are separate questions, and an automatic
|
||||
reveal must never silently re-arm a switch the photographer turned off.
|
||||
**When it appears.** From the "Show mask" strip in the Local panel, which sits
|
||||
beside the tool strip and under the same condition — both describe one
|
||||
selected mask. Off is the resting state, so entering local mode does not paint
|
||||
a photograph red.
|
||||
|
||||
Automatic in exactly one place: arming Paint or Erase turns the tint on if
|
||||
nothing was showing the mask, which is the same nudge `on_part_added` makes and
|
||||
on the same argument — a stroke into an invisible mask is indistinguishable
|
||||
from a tool that did nothing.
|
||||
|
||||
A nudge on an explicit action, never a standing rule. Turning the view off and
|
||||
then picking the eraser leaves it off. The existing `overlay-hidden` property
|
||||
is the precedent and the trap it documents applies unchanged: an automatic
|
||||
reveal that re-arms a switch somebody turned off is worse than no automatic
|
||||
reveal at all.
|
||||
|
||||
The two remaining triggers in the draft — while a part row is selected, and for
|
||||
~1s after a shaping slider is released — are not built. The second is the one
|
||||
worth returning to, and it is a timer rather than a decision.
|
||||
|
||||
The region overlay stays exactly what it is — a picture of what the model
|
||||
detected — and gains a name in the interface that says so, because two
|
||||
@@ -597,6 +661,19 @@ the mask pass beyond the blend variants; no change to distance fields, because
|
||||
a painted part needs none. *This is the whole of the user-visible ask except
|
||||
push and intersect,* and it is deliberately the milestone that stands alone.
|
||||
|
||||
Done: parts with `Union` and `Subtract`, painted parts, the tool strip, the
|
||||
canvas gesture, and the overlay (§6). Outstanding: the brush HUD and cursor —
|
||||
radius, hardness and flow are sliders with no ring drawn on the photograph,
|
||||
so the size of the brush is a number rather than a thing you can see — and
|
||||
the incremental raster of §5.4, without which a layer's whole stroke history
|
||||
is redrawn per dab.
|
||||
|
||||
One lesson from the order it was actually built in, since §6 said it and the
|
||||
build did not listen: the overlay is not the last quarter of M1, it is the
|
||||
first. Parts and painting shipped without it and the result was a feature
|
||||
nobody could tell was working — the panel listed a mask, the photograph showed
|
||||
nothing, and every report of it came back as "the masks do not work".
|
||||
|
||||
**M2 — Combine properly.** `Intersect`, model and gradient and range parts,
|
||||
per-part distance fields (§5.5), the part list with its join chips, folding
|
||||
two layers.
|
||||
|
||||
Reference in New Issue
Block a user