From 7e1c33ebed50da80f01c748b30c441bcde2cdf83 Mon Sep 17 00:00:00 2001 From: Duncan Tourolle Date: Thu, 27 Aug 2026 09:46:48 +0200 Subject: [PATCH] Draw the subjects on the photograph, not on the sensor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "Find subjects" would recognise a person on a portrait frame and then paint the outline into the hillside behind them. The detection was right and the mask was right; what was wrong was the picture drawn to show them. Instance masks live in sensor space, and correctly so — the generated shader samples them at `uv_src`, after the framing map, which is what keeps a mask on its subject through a zoom, a pan and a crop. The overlay is the one consumer that is *not* sampled by that shader. It is a flat image handed to the compositor to lay over a photograph that has already been through the framing map, so it has to arrive in the same space that photograph is in, and it did not. On a frame from a camera held sideways the outlines were drawn a quarter turn away from the subjects they described. `overlay_clip` had the same fault one layer down, and it is the more insidious of the two because it looks right. The crop and the viewport are fractions of the photograph as the user sees it — the prologue maps an output pixel through `crop_rect` *before* it unturns the frame — and they were being measured against the sensor's width and height. Two numbers, correct type, wrong axis. Both now go through `Orientation::into_shown`, so the overlay and its clip are in the photograph's space and the turn is the same one the render and the thumbnails make. Neither was noticeable until this week, and the reason is worth writing down: before the detector was given an upright frame it found almost nothing on a portrait photograph, so there was rarely an outline to be in the wrong place. Fixing the detector is what made this visible. Landscape frames were never affected, which is most of them, and is why an overlay that ignored orientation entirely survived this long. Verified on `_MG_9080.CR2`, a portrait frame of two people and a dog: the overlay was a 1599x1066 image drawn onto a 1066x1599 canvas, with the colour sitting in the mountainside above the subjects. It is now 1066x1599, and each outline is on the thing it names. Co-Authored-By: Claude Opus 5 (1M context) --- ui/dr-ui/src/develop.rs | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/ui/dr-ui/src/develop.rs b/ui/dr-ui/src/develop.rs index 9319963..2961566 100644 --- a/ui/dr-ui/src/develop.rs +++ b/ui/dr-ui/src/develop.rs @@ -1829,7 +1829,20 @@ impl DevelopSession { let Some(seg) = self.segmentation.as_ref() else { return (0, 0, 0, 0); }; - let (w, h) = seg.proxy_size(); + // **Shown pixels, matching `overlay_image`.** The crop and the + // viewport are fractions of the photograph as the user sees it — the + // prologue maps an output pixel through `crop_rect` *before* it + // unturns the frame — so measuring them against the sensor's width + // and height puts the clip on the wrong axis the moment the two + // differ. That is the same confusion as the overlay itself had, one + // layer down, and it is silent for exactly the images where it is + // wrong: a landscape frame has nothing to notice. + let (sw, sh) = seg.proxy_size(); + let (w, h) = self + .graph + .framing() + .effective_orientation() + .oriented_size(sw as u32, sh as u32); let rect = self.graph.framing().visible_rect(); // Rounded outward, so half a pixel of rounding never shows as a strip @@ -1858,6 +1871,26 @@ impl DevelopSession { return None; } let (rgba, w, h) = self.segmentation.as_ref()?.overlay_rgba(); + + // TRACES: FR-DEV-3h + // **Turned the right way up before it is drawn.** Instance masks live + // in sensor space, because the generated shader samples them after + // the framing map (`uv_src`) — but this is not sampled by that shader. + // It is a flat image handed to the compositor to lay over a + // photograph that *has* been through the framing map, so it has to + // arrive in the same space the photograph is in. + // + // Without this the outlines are drawn in the sensor's orientation over + // an upright picture: on a portrait frame the colour sits nowhere near + // the subject, which reads as the detector having failed rather than + // as the overlay being turned. Nothing announces it, and it is + // invisible on landscape frames, which is most of them. + let (rgba, w, h) = self + .graph + .framing() + .effective_orientation() + .into_shown(&rgba, w, h, 4); + let buffer = slint::SharedPixelBuffer::::clone_from_slice(&rgba, w, h); Some(slint::Image::from_rgba8(buffer)) }