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:
@@ -264,6 +264,12 @@ pub(crate) fn sync(window: &AppWindow, session: &Rc<RefCell<Option<DevelopSessio
|
||||
};
|
||||
masking.set_parts(ModelRc::new(VecModel::from(parts)));
|
||||
|
||||
// TRACES: FR-DEV-19c
|
||||
// Pushed rather than assumed, because the session is where it lives and
|
||||
// because it can change without the panel having asked: selecting a
|
||||
// second layer takes the reveal away, since a mask is one thing.
|
||||
masking.set_mask_view(s.mask_view() as i32);
|
||||
|
||||
let (radius, hardness, flow) = s.brush();
|
||||
masking.set_brush_radius(radius);
|
||||
masking.set_brush_hardness(hardness);
|
||||
@@ -279,7 +285,16 @@ pub(crate) fn sync(window: &AppWindow, session: &Rc<RefCell<Option<DevelopSessio
|
||||
// proxy-sized RGBA buffer — a megabyte or so — and rebuilding it on every
|
||||
// slider event would be a memcpy per frame for a picture that changes only
|
||||
// when the level does.
|
||||
match s.overlay_image() {
|
||||
//
|
||||
// TRACES: FR-DEV-19c
|
||||
// **Stood down while a mask is being shown.** The two overlays answer
|
||||
// different questions — this one is what the model *detected*, the reveal
|
||||
// is what a layer resolves to — and both at once is a false-coloured
|
||||
// picture over a tinted one, through which neither can be read. The one
|
||||
// describing the layer being worked on wins, because by the time a mask
|
||||
// has been chosen the detections are what the photographer is choosing
|
||||
// *between* rather than what they are looking at.
|
||||
match s.overlay_image().filter(|_| s.mask_view() == 0) {
|
||||
Some(image) => {
|
||||
window.set_region_overlay(image);
|
||||
window.set_overlay_on(true);
|
||||
@@ -558,6 +573,7 @@ pub(crate) fn wire(
|
||||
{
|
||||
let weak = window.as_weak();
|
||||
let session = session.clone();
|
||||
let redraw = redraw.clone();
|
||||
let rows = rows.clone();
|
||||
window
|
||||
.global::<Masking>()
|
||||
@@ -587,6 +603,11 @@ pub(crate) fn wire(
|
||||
// The scope changed, so the adjust panel below is now describing a
|
||||
// different chain.
|
||||
sync_rows(&w, &rows, &session);
|
||||
// TRACES: FR-DEV-19c
|
||||
// And so has the picture, when a mask is being shown: the
|
||||
// reveal follows the selection, so choosing another layer
|
||||
// draws another mask.
|
||||
redraw(&w);
|
||||
});
|
||||
}
|
||||
{
|
||||
@@ -841,13 +862,66 @@ pub(crate) fn wire(
|
||||
{
|
||||
let weak = window.as_weak();
|
||||
let session = session.clone();
|
||||
window.global::<Masking>().on_tool_picked(move |_tool| {
|
||||
// The tool itself lives in the interface — it arms a gesture and
|
||||
// changes no pixel — so nothing is set here. What this does is
|
||||
// re-sync, because arming the brush is what makes the parts of a
|
||||
// mask worth showing.
|
||||
let redraw = redraw.clone();
|
||||
window.global::<Masking>().on_tool_picked(move |tool| {
|
||||
// **The tool is written back here, and that is the whole of this
|
||||
// handler.** `Masking.tool` is an `in` property: the panel reads
|
||||
// it to light the right chip and `app.slint` reads it to decide
|
||||
// whether a drag on the photograph paints, but only Rust may write
|
||||
// it. So a version of this that recorded nothing left the strip
|
||||
// reporting "Select" however many times "Paint" was pressed, and
|
||||
// the paint area was never armed — the brush, the parts, the whole
|
||||
// of FR-DEV-19b, reachable from no control in the application.
|
||||
//
|
||||
// It still changes no pixel, which is what the previous note was
|
||||
// getting at: the tool arms a gesture and belongs to the
|
||||
// interface, not to the edit, so it takes no history step and is
|
||||
// not stored.
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
let tool = tool.clamp(0, 2);
|
||||
w.global::<Masking>().set_tool(tool);
|
||||
|
||||
// TRACES: FR-DEV-19c
|
||||
// Arming a brush shows the mask, if nothing was showing it. The
|
||||
// same nudge `on_part_added` makes and on the same argument: a
|
||||
// photographer about to correct an edge by hand needs to see the
|
||||
// edge, and "the tool did nothing" is what a stroke into an
|
||||
// invisible mask looks like.
|
||||
//
|
||||
// A nudge on an explicit action, never a standing rule. Turning
|
||||
// the view off and then picking the eraser leaves it off — the
|
||||
// trap `overlay-hidden` documents is that an automatic reveal
|
||||
// which re-arms a switch somebody turned off is worse than no
|
||||
// automatic reveal at all.
|
||||
if tool > 0 {
|
||||
if let Some(s) = session.borrow_mut().as_mut() {
|
||||
if s.mask_view() == 0 {
|
||||
s.set_mask_view(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Re-synced because arming the brush is what makes the parts of a
|
||||
// mask worth showing.
|
||||
sync(&w, &session);
|
||||
redraw(&w);
|
||||
});
|
||||
}
|
||||
// TRACES: FR-DEV-19c
|
||||
{
|
||||
let weak = window.as_weak();
|
||||
let session = session.clone();
|
||||
let redraw = redraw.clone();
|
||||
window.global::<Masking>().on_mask_view_picked(move |view| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
if let Some(s) = session.borrow_mut().as_mut() {
|
||||
s.set_mask_view(view.max(0) as usize);
|
||||
}
|
||||
// A redraw and not only a sync: the reveal is in the composed
|
||||
// shader, so what changed is the picture rather than the
|
||||
// panel.
|
||||
sync(&w, &session);
|
||||
redraw(&w);
|
||||
});
|
||||
}
|
||||
// TRACES: FR-DEV-19a
|
||||
@@ -1127,6 +1201,12 @@ pub(crate) fn reset(window: &AppWindow) {
|
||||
masking.set_segmenting(false);
|
||||
masking.set_refining(false);
|
||||
masking.set_segmented(false);
|
||||
// TRACES: FR-DEV-19b | FR-DEV-19c
|
||||
// The tool and the mask view are both about one selected layer, and the
|
||||
// next photograph has none. Left standing, they would arm a brush over a
|
||||
// photograph with nothing to paint into and claim a mask was being shown.
|
||||
masking.set_tool(0);
|
||||
masking.set_mask_view(0);
|
||||
masking.set_masks(ModelRc::new(VecModel::<MaskRow>::default()));
|
||||
masking.set_subjects(ModelRc::new(VecModel::<SubjectRow>::default()));
|
||||
masking.set_categories(ModelRc::new(VecModel::<CategoryRow>::default()));
|
||||
|
||||
Reference in New Issue
Block a user