diff --git a/ui/dr-ui/src/merge.rs b/ui/dr-ui/src/merge.rs index 8babb40..1f9fe0c 100644 --- a/ui/dr-ui/src/merge.rs +++ b/ui/dr-ui/src/merge.rs @@ -603,6 +603,41 @@ fn preview( out[o + 3] = 255; } } + + // Each frame's outline, so the page shows *frames* and not one picture: + // a sweep of frames overlapping by more than half reads as a single + // photograph otherwise, which is what a photographer looking for a + // misplaced frame needs to see through. Walked along each border and + // drawn where it lands, two pixels wide, in a colour the scene does not + // have. + let steps = 600; + for k in 0..proxies.len() { + for s in 0..steps { + let t = s as f64 / steps as f64; + for p in [ + (-fw / 2.0 + fw * t, -fh / 2.0), + (-fw / 2.0 + fw * t, fh / 2.0 - 1.0), + (-fw / 2.0, -fh / 2.0 + fh * t), + (fw / 2.0 - 1.0, -fh / 2.0 + fh * t), + ] { + let d = cameras.bearing(k, p); + let Some((u, v)) = projection.from_direction(scale, d) else { continue }; + let x = ((u - bounds.min_u) / px) as i64; + let y = ((v - bounds.min_v) / px) as i64; + for (dx, dy) in [(0, 0), (1, 0), (0, 1), (1, 1)] { + let (xx, yy) = (x + dx, y + dy); + if xx < 0 || yy < 0 || xx >= out_w as i64 || yy >= out_h as i64 { + continue; + } + let o = (yy as usize * out_w + xx as usize) * 4; + out[o] = 255; + out[o + 1] = 176; + out[o + 2] = 0; + out[o + 3] = 255; + } + } + } + } (out_w as u32, out_h as u32, out) } diff --git a/ui/dr-ui/ui/merge.slint b/ui/dr-ui/ui/merge.slint index ecbcec0..158d54e 100644 --- a/ui/dr-ui/ui/merge.slint +++ b/ui/dr-ui/ui/merge.slint @@ -139,8 +139,13 @@ export component MergePage inherits Rectangle { // was found at: ghosting here is the alignment's error, // and what it looks like is what the photographer is // confirming. + // As tall as the picture needs up to most of the page, + // and as wide as it is at that height: a four-frame + // perspective merge is nearly square, a twelve-frame + // cylinder is a strip, and either should fill what it + // can rather than sit in a letterbox. if root.preview.width > 0: Rectangle { - height: min(320px, parent.width * root.preview.height / max(1, root.preview.width)); + height: min(root.height * 0.55, parent.width * root.preview.height / max(1, root.preview.width)); background: Theme.rule; Image { source: root.preview; @@ -151,6 +156,10 @@ export component MergePage inherits Rectangle { } Caption { text: root.summary; wrap: word-wrap; } + Caption { + text: "Each frame is outlined in amber where it lands. Ghosting in an overlap is the alignment's error."; + wrap: word-wrap; + } Segmented { label: "Projection";