From 57ed51c1c50f0ed6be44fb9b28dfde15afd4e169 Mon Sep 17 00:00:00 2001 From: Duncan Tourolle Date: Sat, 19 Sep 2026 15:23:40 +0200 Subject: [PATCH] Projection chips redraw the preview; auto-crop as the DNG default crop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Picking a chip stored the choice for the merge and changed nothing on screen — the chip did not even highlight, since the selected property was never written back. Now the pick is reflected, and the job, waiting for its decision, takes a Preview request, draws the alignment on the chosen surface at proxy cost and reports again; the drain puts the new picture and its size up. Auto is the surface the field of view suggests. Also: The largest rectangle inside the frames' coverage is found a row at a time — a histogram of consecutive covered rows and a stack pass per row — so the composite is never held to be measured (FR-MRG-11). It is written as DefaultCropOrigin/DefaultCropSize (FR-MRG-4): the file opens on the picture, the border is still in it, and resetting the crop shows it. rawler reports the crop as the picture, which the test checks. FR-MRG-4 records the question raised the same day — fill the border rather than crop it — as open: a non-generative fill through the heal, or a generative inpainter with its licence and weights. Neither decided. --- core/dr-export/src/dng.rs | 81 +++++++++++++--- core/dr-export/src/inscribed.rs | 161 ++++++++++++++++++++++++++++++++ core/dr-export/src/lib.rs | 2 + docs/requirements.md | 7 ++ docs/traceability.md | 16 ++-- ui/dr-ui/src/merge.rs | 54 +++++++---- ui/dr-ui/src/merge_ui.rs | 27 ++++-- 7 files changed, 301 insertions(+), 47 deletions(-) create mode 100644 core/dr-export/src/inscribed.rs diff --git a/core/dr-export/src/dng.rs b/core/dr-export/src/dng.rs index 2951c55..ce2df92 100644 --- a/core/dr-export/src/dng.rs +++ b/core/dr-export/src/dng.rs @@ -60,7 +60,12 @@ pub struct DngProfile { /// /// `PhotometricInterpretation = LinearRaw`, `DNGVersion 1.4`, uncompressed, /// `Orientation = 1` — the composite is written upright (panorama.md §8). -pub fn write_linear_dng( +/// +/// `crop` is asked once every strip is in, and its answer — the largest +/// rectangle the frames covered, found while the strips went by +/// (`Inscribed`) — becomes `DefaultCropOrigin`/`DefaultCropSize` +/// (FR-MRG-4): the file opens on the picture, and the border is still in it. +pub fn write_linear_dng( out: W, width: u32, height: u32, @@ -68,10 +73,12 @@ pub fn write_linear_dng( profile: &DngProfile, source: Option<&SourceMetadata>, mut strips: F, + crop: C, ) -> Result<(), ExportError> where W: Write + Seek, F: FnMut(usize, &mut Vec) -> Result<(), ExportError>, + C: FnOnce() -> Option, { let enc = |e: tiff::TiffError| ExportError::Encode(e.to_string()); let mut encoder = TiffEncoder::new(out).map_err(enc)?; @@ -99,6 +106,22 @@ where } image.write_strip(&buf).map_err(enc)?; } + if let Some(r) = crop().filter(|r| r.width > 0 && r.height > 0) { + let r = crate::Rect { + x: r.x.min(width - 1), + y: r.y.min(height - 1), + width: r.width.min(width - r.x.min(width - 1)), + height: r.height.min(height - r.y.min(height - 1)), + }; + image + .encoder() + .write_tag(Tag::Unknown(tag::DEFAULT_CROP_ORIGIN), &[r.x, r.y][..]) + .map_err(enc)?; + image + .encoder() + .write_tag(Tag::Unknown(tag::DEFAULT_CROP_SIZE), &[r.width, r.height][..]) + .map_err(enc)?; + } image.finish().map_err(enc) } @@ -154,6 +177,8 @@ mod tag { pub const UNIQUE_CAMERA_MODEL: u16 = 50708; pub const BLACK_LEVEL: u16 = 50714; pub const WHITE_LEVEL: u16 = 50717; + pub const DEFAULT_CROP_ORIGIN: u16 = 50719; + pub const DEFAULT_CROP_SIZE: u16 = 50720; pub const COLOR_MATRIX_1: u16 = 50721; pub const COLOR_MATRIX_2: u16 = 50722; pub const AS_SHOT_NEUTRAL: u16 = 50728; @@ -205,16 +230,32 @@ mod tests { model: Some("Canon EOS 6D".into()), ..Default::default() }; - write_linear_dng(&mut bytes, width, height, rows, &profile(), Some(&source), |k, buf| { - let first = k as u32 * rows; - let n = rows.min(height - first); - for y in first..first + n { - for x in 0..width { - buf.extend([(x + y * width) as u16, 1000, 2000]); + write_linear_dng( + &mut bytes, + width, + height, + rows, + &profile(), + Some(&source), + |k, buf| { + let first = k as u32 * rows; + let n = rows.min(height - first); + for y in first..first + n { + for x in 0..width { + buf.extend([(x + y * width) as u16, 1000, 2000]); + } } - } - Ok(()) - }) + Ok(()) + }, + || { + Some(crate::Rect { + x: 2, + y: 1, + width: 15, + height: 10, + }) + }, + ) .expect("written"); bytes.into_inner() } @@ -242,15 +283,27 @@ mod tests { // The profile came through as the camera's. assert!(image.camera.color_matrix.len() >= 1); assert_eq!(image.model, "Canon EOS 6D"); + // The default crop is what the decoder reports as the picture. + let crop = image.crop_area.expect("a crop"); + assert_eq!((crop.p.x, crop.p.y, crop.d.w, crop.d.h), (2, 1, 15, 10)); } #[test] fn a_strip_of_the_wrong_length_is_refused() { let mut bytes = std::io::Cursor::new(Vec::new()); - let err = write_linear_dng(&mut bytes, 8, 8, 8, &profile(), None, |_, buf| { - buf.extend([0u16; 10]); - Ok(()) - }) + let err = write_linear_dng( + &mut bytes, + 8, + 8, + 8, + &profile(), + None, + |_, buf| { + buf.extend([0u16; 10]); + Ok(()) + }, + || None, + ) .unwrap_err(); assert!(matches!(err, ExportError::Encode(_))); } diff --git a/core/dr-export/src/inscribed.rs b/core/dr-export/src/inscribed.rs new file mode 100644 index 0000000..8f43129 --- /dev/null +++ b/core/dr-export/src/inscribed.rs @@ -0,0 +1,161 @@ +//! TRACES: FR-MRG-4 +//! The largest rectangle inside a coverage mask, found a row at a time. +//! +//! A merged panorama has ragged edges: the frames' footprints under a +//! cylinder or a sphere are not rectangles, and the composite carries a +//! black border where none of them reached. FR-MRG-4 asks for an auto-crop +//! to the largest inscribed rectangle. This finds it as the bands are +//! produced, so the composite is never held to be measured (FR-MRG-11): +//! each row extends a running histogram of consecutive covered rows above +//! it, and the largest rectangle ending on that row is the largest +//! rectangle under the histogram — a stack pass, linear in the width. +//! +//! The crop is written as the DNG's `DefaultCropOrigin`/`DefaultCropSize`, +//! which every reader honours and which discards nothing: the pixels +//! outside it are still in the file for a photographer who wants them. + +/// The rectangle so far, in pixels from the top left. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub struct Rect { + pub x: u32, + pub y: u32, + pub width: u32, + pub height: u32, +} + +impl Rect { + pub fn area(&self) -> u64 { + u64::from(self.width) * u64::from(self.height) + } +} + +/// Feed rows top to bottom; ask for the best at any point. +#[derive(Debug, Clone)] +pub struct Inscribed { + width: usize, + /// How many consecutive covered rows end at the last row fed, per column. + heights: Vec, + rows: u32, + best: Rect, +} + +impl Inscribed { + pub fn new(width: u32) -> Self { + Inscribed { + width: width as usize, + heights: vec![0; width as usize], + rows: 0, + best: Rect::default(), + } + } + + /// One more row of coverage, `width` long. + pub fn push_row(&mut self, covered: &[bool]) { + debug_assert_eq!(covered.len(), self.width); + for (h, &c) in self.heights.iter_mut().zip(covered) { + *h = if c { *h + 1 } else { 0 }; + } + self.rows += 1; + // Largest rectangle under the histogram, with a sentinel column of + // height 0 at the end so every bar is popped. + let mut stack: Vec = Vec::new(); + for i in 0..=self.width { + let h = if i < self.width { self.heights[i] } else { 0 }; + while let Some(&top) = stack.last() { + if self.heights[top] <= h { + break; + } + stack.pop(); + let height = self.heights[top]; + let left = stack.last().map_or(0, |&l| l + 1); + let width = (i - left) as u32; + let area = u64::from(width) * u64::from(height); + if area > self.best.area() { + self.best = Rect { + x: left as u32, + y: self.rows - height, + width, + height, + }; + } + } + stack.push(i); + } + } + + /// Several rows at once, as a band hands them over. + pub fn push_rows(&mut self, covered: &[bool], rows: u32) { + for r in 0..rows as usize { + self.push_row(&covered[r * self.width..(r + 1) * self.width]); + } + } + + pub fn best(&self) -> Rect { + self.best + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn from_art(art: &[&str]) -> Rect { + let mut ins = Inscribed::new(art[0].len() as u32); + for row in art { + let covered: Vec = row.chars().map(|c| c == '#').collect(); + ins.push_row(&covered); + } + ins.best() + } + + #[test] + fn a_full_mask_is_its_own_rectangle() { + let r = from_art(&["####", "####", "####"]); + assert_eq!( + r, + Rect { + x: 0, + y: 0, + width: 4, + height: 3 + } + ); + } + + #[test] + fn ragged_edges_are_cut_off() { + // A cylinder's footprint: narrower at top and bottom. + let r = from_art(&[ + "..####..", + ".######.", + "########", + "########", + ".######.", + "..####..", + ]); + // 6 wide × 4 tall = 24 beats 8 × 2 = 16 and 4 × 6 = 24 ties; the + // first found wins a tie, which is the wider one here. + assert_eq!(r.area(), 24); + assert!(r.width == 6 && r.height == 4 || r.width == 4 && r.height == 6); + } + + #[test] + fn a_hole_is_avoided() { + let r = from_art(&["#####", "##.##", "#####", "#####"]); + // Left of the hole: 2 × 4 = 8; right: 2 × 4 = 8; below: 5 × 2 = 10. + assert_eq!( + r, + Rect { + x: 0, + y: 2, + width: 5, + height: 2 + } + ); + } + + #[test] + fn nothing_covered_is_nothing() { + assert_eq!(from_art(&["....", "...."]).area(), 0); + } +} diff --git a/core/dr-export/src/lib.rs b/core/dr-export/src/lib.rs index 6a21c08..95056d5 100644 --- a/core/dr-export/src/lib.rs +++ b/core/dr-export/src/lib.rs @@ -26,6 +26,7 @@ use dr_types::{ColourSpace, ExportFormat, ExportSettings}; mod dng; mod encode; +mod inscribed; mod error; mod exif; pub mod icc; @@ -35,6 +36,7 @@ mod sharpen; mod size; pub use dng::{write_linear_dng, DngProfile}; +pub use inscribed::{Inscribed, Rect}; pub use error::ExportError; pub use metadata::SourceMetadata; pub use name::{resolve_name, NameContext}; diff --git a/docs/requirements.md b/docs/requirements.md index 2ec5388..8d325f0 100644 --- a/docs/requirements.md +++ b/docs/requirements.md @@ -1732,6 +1732,13 @@ field of view and overridable; the horizon levelled from the estimated rotations drag; auto-crop to the largest inscribed rectangle, overridable. No boundary fill: painting pixels that were never captured is the pixel editing §1.3 excludes. +*The auto-crop is non-destructive* (built 2026-09-19): it is the composite's default crop, not a +cut — the whole merge including its border is in the file, and resetting the crop shows it. +*Boundary fill is an open question, raised the same day:* a fill of the border from the picture's +own edge through FR-DEV-8's heal would be non-generative and honest about what it is; a generative +inpainter is a model licence, hundreds of megabytes of weights and an inference path the +application does not have. Neither is decided; the clause stands as written until one is. + **FR-MRG-5 — Honesty of failure.** *(general to any merge)* A frame that cannot be aligned is named, with why — too few matches, no overlap with any other frame, a residual above the stated bound — and the merge stops. Never a silent drop, never a best-effort composite with a frame diff --git a/docs/traceability.md b/docs/traceability.md index c498588..a190e30 100644 --- a/docs/traceability.md +++ b/docs/traceability.md @@ -9,8 +9,8 @@ Denominators are parsed from [`requirements.md`](requirements.md) at run time, n | Metric | Value | |---|---| -| Source files scanned | 375 | -| TRACES tags found | 1537 | +| Source files scanned | 376 | +| TRACES tags found | 1538 | | Requirements defined | 184 | | Requirements deferred (post-v1) | 24 | | Requirements covered | 152 | @@ -90,22 +90,22 @@ _None._ | FR-DSP-7 | [`core/dr-gpu/src/histogram.rs:147`](../core/dr-gpu/src/histogram.rs#L147), [`core/dr-gpu/src/histogram.rs:1`](../core/dr-gpu/src/histogram.rs#L1), [`core/dr-gpu/src/histogram.rs:281`](../core/dr-gpu/src/histogram.rs#L281), [`core/dr-gpu/src/histogram.rs:50`](../core/dr-gpu/src/histogram.rs#L50), [`core/dr-gpu/src/shaders/histogram.wgsl:1`](../core/dr-gpu/src/shaders/histogram.wgsl#L1), [`ui/dr-ui/src/develop.rs:4105`](../ui/dr-ui/src/develop.rs#L4105), [`ui/dr-ui/src/develop.rs:804`](../ui/dr-ui/src/develop.rs#L804), [`ui/dr-ui/src/develop.rs:8641`](../ui/dr-ui/src/develop.rs#L8641), [`ui/dr-ui/src/develop.rs:8673`](../ui/dr-ui/src/develop.rs#L8673), [`ui/dr-ui/src/histogram.rs:1`](../ui/dr-ui/src/histogram.rs#L1), [`ui/dr-ui/src/lib.rs:2078`](../ui/dr-ui/src/lib.rs#L2078), [`ui/dr-ui/src/lib.rs:426`](../ui/dr-ui/src/lib.rs#L426), [`ui/dr-ui/ui/histogram.slint:184`](../ui/dr-ui/ui/histogram.slint#L184), [`ui/dr-ui/ui/histogram.slint:1`](../ui/dr-ui/ui/histogram.slint#L1) | | FR-DSP-8 | [`platform/dr-plat/src/display.rs:1`](../platform/dr-plat/src/display.rs#L1), [`platform/dr-plat/src/display/icc.rs:1`](../platform/dr-plat/src/display/icc.rs#L1), [`platform/dr-plat/src/display/wayland.rs:1`](../platform/dr-plat/src/display/wayland.rs#L1), [`platform/dr-plat/src/display/x11.rs:1`](../platform/dr-plat/src/display/x11.rs#L1), [`ui/dr-ui/src/develop.rs:3997`](../ui/dr-ui/src/develop.rs#L3997), [`ui/dr-ui/src/develop.rs:4051`](../ui/dr-ui/src/develop.rs#L4051), [`ui/dr-ui/src/develop.rs:8967`](../ui/dr-ui/src/develop.rs#L8967), [`ui/dr-ui/src/develop.rs:9013`](../ui/dr-ui/src/develop.rs#L9013), [`ui/dr-ui/src/develop.rs:9032`](../ui/dr-ui/src/develop.rs#L9032), [`ui/dr-ui/src/develop.rs:957`](../ui/dr-ui/src/develop.rs#L957), [`ui/dr-ui/src/display_ui.rs:192`](../ui/dr-ui/src/display_ui.rs#L192), [`ui/dr-ui/src/display_ui.rs:1`](../ui/dr-ui/src/display_ui.rs#L1), [`ui/dr-ui/src/display_ui.rs:325`](../ui/dr-ui/src/display_ui.rs#L325), [`ui/dr-ui/src/display_ui.rs:346`](../ui/dr-ui/src/display_ui.rs#L346), [`ui/dr-ui/src/display_ui.rs:379`](../ui/dr-ui/src/display_ui.rs#L379), [`ui/dr-ui/src/lib.rs:1880`](../ui/dr-ui/src/lib.rs#L1880), [`ui/dr-ui/src/lib.rs:1944`](../ui/dr-ui/src/lib.rs#L1944), [`ui/dr-ui/src/lib.rs:3634`](../ui/dr-ui/src/lib.rs#L3634), [`ui/dr-ui/src/lib.rs:3727`](../ui/dr-ui/src/lib.rs#L3727), [`ui/dr-ui/ui/app.slint:1939`](../ui/dr-ui/ui/app.slint#L1939), [`ui/dr-ui/ui/app.slint:62`](../ui/dr-ui/ui/app.slint#L62), [`ui/dr-ui/ui/settings.slint:155`](../ui/dr-ui/ui/settings.slint#L155), [`ui/dr-ui/ui/settings.slint:895`](../ui/dr-ui/ui/settings.slint#L895) | | FR-EXP-1 | [`core/dr-export/src/encode.rs:1`](../core/dr-export/src/encode.rs#L1), [`core/dr-export/src/lib.rs:1`](../core/dr-export/src/lib.rs#L1), [`core/dr-types/src/settings.rs:1`](../core/dr-types/src/settings.rs#L1), [`ui/dr-ui/src/settings_ui.rs:1`](../ui/dr-ui/src/settings_ui.rs#L1) | -| FR-EXP-2 | [`core/dr-export/src/encode.rs:1`](../core/dr-export/src/encode.rs#L1), [`core/dr-export/src/error.rs:26`](../core/dr-export/src/error.rs#L26), [`core/dr-export/src/icc.rs:1`](../core/dr-export/src/icc.rs#L1), [`core/dr-export/src/lib.rs:155`](../core/dr-export/src/lib.rs#L155), [`core/dr-export/src/lib.rs:1`](../core/dr-export/src/lib.rs#L1), [`core/dr-export/src/lib.rs:55`](../core/dr-export/src/lib.rs#L55), [`core/dr-gpu/src/adjust.rs:2676`](../core/dr-gpu/src/adjust.rs#L2676), [`core/dr-pipeline/src/graph.rs:835`](../core/dr-pipeline/src/graph.rs#L835), [`core/dr-pipeline/src/graph.rs:933`](../core/dr-pipeline/src/graph.rs#L933), [`core/dr-pipeline/src/operation.rs:512`](../core/dr-pipeline/src/operation.rs#L512), [`core/dr-types/src/colour.rs:1`](../core/dr-types/src/colour.rs#L1), [`core/dr-types/src/settings.rs:1`](../core/dr-types/src/settings.rs#L1), [`core/dr-types/src/settings.rs:934`](../core/dr-types/src/settings.rs#L934), [`ui/dr-ui/src/develop.rs:9032`](../ui/dr-ui/src/develop.rs#L9032), [`ui/dr-ui/src/settings_ui.rs:1`](../ui/dr-ui/src/settings_ui.rs#L1) | -| FR-EXP-3 | [`core/dr-export/src/lib.rs:184`](../core/dr-export/src/lib.rs#L184), [`core/dr-export/src/lib.rs:1`](../core/dr-export/src/lib.rs#L1), [`core/dr-export/src/size.rs:136`](../core/dr-export/src/size.rs#L136), [`core/dr-export/src/size.rs:1`](../core/dr-export/src/size.rs#L1), [`core/dr-export/src/size.rs:25`](../core/dr-export/src/size.rs#L25), [`core/dr-export/src/size.rs:84`](../core/dr-export/src/size.rs#L84), [`core/dr-types/src/settings.rs:1003`](../core/dr-types/src/settings.rs#L1003), [`core/dr-types/src/settings.rs:1072`](../core/dr-types/src/settings.rs#L1072), [`core/dr-types/src/settings.rs:1100`](../core/dr-types/src/settings.rs#L1100), [`core/dr-types/src/settings.rs:1111`](../core/dr-types/src/settings.rs#L1111), [`core/dr-types/src/settings.rs:1117`](../core/dr-types/src/settings.rs#L1117), [`core/dr-types/src/settings.rs:1`](../core/dr-types/src/settings.rs#L1), [`core/dr-types/src/settings.rs:979`](../core/dr-types/src/settings.rs#L979), [`core/dr-types/src/settings.rs:987`](../core/dr-types/src/settings.rs#L987), [`ui/dr-ui/src/settings_ui.rs:1`](../ui/dr-ui/src/settings_ui.rs#L1), [`ui/dr-ui/src/settings_ui.rs:274`](../ui/dr-ui/src/settings_ui.rs#L274), [`ui/dr-ui/src/settings_ui.rs:287`](../ui/dr-ui/src/settings_ui.rs#L287), [`ui/dr-ui/src/settings_ui.rs:302`](../ui/dr-ui/src/settings_ui.rs#L302), [`ui/dr-ui/src/settings_ui.rs:701`](../ui/dr-ui/src/settings_ui.rs#L701), [`ui/dr-ui/ui/settings.slint:798`](../ui/dr-ui/ui/settings.slint#L798), [`ui/dr-ui/ui/settings.slint:808`](../ui/dr-ui/ui/settings.slint#L808) | +| FR-EXP-2 | [`core/dr-export/src/encode.rs:1`](../core/dr-export/src/encode.rs#L1), [`core/dr-export/src/error.rs:26`](../core/dr-export/src/error.rs#L26), [`core/dr-export/src/icc.rs:1`](../core/dr-export/src/icc.rs#L1), [`core/dr-export/src/lib.rs:157`](../core/dr-export/src/lib.rs#L157), [`core/dr-export/src/lib.rs:1`](../core/dr-export/src/lib.rs#L1), [`core/dr-export/src/lib.rs:57`](../core/dr-export/src/lib.rs#L57), [`core/dr-gpu/src/adjust.rs:2676`](../core/dr-gpu/src/adjust.rs#L2676), [`core/dr-pipeline/src/graph.rs:835`](../core/dr-pipeline/src/graph.rs#L835), [`core/dr-pipeline/src/graph.rs:933`](../core/dr-pipeline/src/graph.rs#L933), [`core/dr-pipeline/src/operation.rs:512`](../core/dr-pipeline/src/operation.rs#L512), [`core/dr-types/src/colour.rs:1`](../core/dr-types/src/colour.rs#L1), [`core/dr-types/src/settings.rs:1`](../core/dr-types/src/settings.rs#L1), [`core/dr-types/src/settings.rs:934`](../core/dr-types/src/settings.rs#L934), [`ui/dr-ui/src/develop.rs:9032`](../ui/dr-ui/src/develop.rs#L9032), [`ui/dr-ui/src/settings_ui.rs:1`](../ui/dr-ui/src/settings_ui.rs#L1) | +| FR-EXP-3 | [`core/dr-export/src/lib.rs:186`](../core/dr-export/src/lib.rs#L186), [`core/dr-export/src/lib.rs:1`](../core/dr-export/src/lib.rs#L1), [`core/dr-export/src/size.rs:136`](../core/dr-export/src/size.rs#L136), [`core/dr-export/src/size.rs:1`](../core/dr-export/src/size.rs#L1), [`core/dr-export/src/size.rs:25`](../core/dr-export/src/size.rs#L25), [`core/dr-export/src/size.rs:84`](../core/dr-export/src/size.rs#L84), [`core/dr-types/src/settings.rs:1003`](../core/dr-types/src/settings.rs#L1003), [`core/dr-types/src/settings.rs:1072`](../core/dr-types/src/settings.rs#L1072), [`core/dr-types/src/settings.rs:1100`](../core/dr-types/src/settings.rs#L1100), [`core/dr-types/src/settings.rs:1111`](../core/dr-types/src/settings.rs#L1111), [`core/dr-types/src/settings.rs:1117`](../core/dr-types/src/settings.rs#L1117), [`core/dr-types/src/settings.rs:1`](../core/dr-types/src/settings.rs#L1), [`core/dr-types/src/settings.rs:979`](../core/dr-types/src/settings.rs#L979), [`core/dr-types/src/settings.rs:987`](../core/dr-types/src/settings.rs#L987), [`ui/dr-ui/src/settings_ui.rs:1`](../ui/dr-ui/src/settings_ui.rs#L1), [`ui/dr-ui/src/settings_ui.rs:274`](../ui/dr-ui/src/settings_ui.rs#L274), [`ui/dr-ui/src/settings_ui.rs:287`](../ui/dr-ui/src/settings_ui.rs#L287), [`ui/dr-ui/src/settings_ui.rs:302`](../ui/dr-ui/src/settings_ui.rs#L302), [`ui/dr-ui/src/settings_ui.rs:701`](../ui/dr-ui/src/settings_ui.rs#L701), [`ui/dr-ui/ui/settings.slint:798`](../ui/dr-ui/ui/settings.slint#L798), [`ui/dr-ui/ui/settings.slint:808`](../ui/dr-ui/ui/settings.slint#L808) | | FR-EXP-4 | [`core/dr-export/src/lib.rs:1`](../core/dr-export/src/lib.rs#L1), [`core/dr-export/src/sharpen.rs:1`](../core/dr-export/src/sharpen.rs#L1), [`core/dr-export/src/size.rs:1`](../core/dr-export/src/size.rs#L1), [`ui/dr-ui/src/settings_ui.rs:1`](../ui/dr-ui/src/settings_ui.rs#L1) | | FR-EXP-5 | [`ui/dr-ui/src/settings_store.rs:1`](../ui/dr-ui/src/settings_store.rs#L1) | | FR-EXP-6 | [`core/dr-export/src/lib.rs:1`](../core/dr-export/src/lib.rs#L1), [`core/dr-export/src/name.rs:1`](../core/dr-export/src/name.rs#L1), [`core/dr-types/src/settings.rs:1`](../core/dr-types/src/settings.rs#L1), [`ui/dr-ui/src/export.rs:1`](../ui/dr-ui/src/export.rs#L1), [`ui/dr-ui/src/lib.rs:603`](../ui/dr-ui/src/lib.rs#L603), [`ui/dr-ui/src/settings_ui.rs:1`](../ui/dr-ui/src/settings_ui.rs#L1), [`ui/dr-ui/src/settings_ui.rs:49`](../ui/dr-ui/src/settings_ui.rs#L49), [`ui/dr-ui/src/settings_ui.rs:815`](../ui/dr-ui/src/settings_ui.rs#L815) | | FR-EXP-7 | [`ui/dr-ui/src/activity.rs:83`](../ui/dr-ui/src/activity.rs#L83), [`ui/dr-ui/src/export.rs:1005`](../ui/dr-ui/src/export.rs#L1005), [`ui/dr-ui/src/export.rs:1`](../ui/dr-ui/src/export.rs#L1), [`ui/dr-ui/src/lib.rs:264`](../ui/dr-ui/src/lib.rs#L264), [`ui/dr-ui/src/lib.rs:2783`](../ui/dr-ui/src/lib.rs#L2783), [`ui/dr-ui/src/lib.rs:603`](../ui/dr-ui/src/lib.rs#L603), [`ui/dr-ui/src/lib.rs:649`](../ui/dr-ui/src/lib.rs#L649), [`ui/dr-ui/src/lib.rs:676`](../ui/dr-ui/src/lib.rs#L676), [`ui/dr-ui/src/library_ui.rs:4275`](../ui/dr-ui/src/library_ui.rs#L4275), [`ui/dr-ui/src/library_ui.rs:719`](../ui/dr-ui/src/library_ui.rs#L719), [`ui/dr-ui/src/library_ui.rs:8026`](../ui/dr-ui/src/library_ui.rs#L8026), [`ui/dr-ui/src/library_ui.rs:8103`](../ui/dr-ui/src/library_ui.rs#L8103), [`ui/dr-ui/src/library_ui.rs:8115`](../ui/dr-ui/src/library_ui.rs#L8115), [`ui/dr-ui/src/library_ui.rs:819`](../ui/dr-ui/src/library_ui.rs#L819), [`ui/dr-ui/src/library_ui.rs:876`](../ui/dr-ui/src/library_ui.rs#L876), [`ui/dr-ui/ui/app.slint:1116`](../ui/dr-ui/ui/app.slint#L1116), [`ui/dr-ui/ui/app.slint:1676`](../ui/dr-ui/ui/app.slint#L1676), [`ui/dr-ui/ui/library.slint:1583`](../ui/dr-ui/ui/library.slint#L1583), [`ui/dr-ui/ui/library.slint:4291`](../ui/dr-ui/ui/library.slint#L4291) | -| FR-EXP-8 | [`core/dr-decode/src/lib.rs:346`](../core/dr-decode/src/lib.rs#L346), [`core/dr-decode/src/lib.rs:370`](../core/dr-decode/src/lib.rs#L370), [`core/dr-decode/src/lib.rs:384`](../core/dr-decode/src/lib.rs#L384), [`core/dr-decode/src/lib.rs:71`](../core/dr-decode/src/lib.rs#L71), [`core/dr-decode/src/lib.rs:79`](../core/dr-decode/src/lib.rs#L79), [`core/dr-decode/src/lib.rs:82`](../core/dr-decode/src/lib.rs#L82), [`core/dr-decode/src/locate.rs:1164`](../core/dr-decode/src/locate.rs#L1164), [`core/dr-decode/src/locate.rs:1223`](../core/dr-decode/src/locate.rs#L1223), [`core/dr-decode/src/locate.rs:316`](../core/dr-decode/src/locate.rs#L316), [`core/dr-decode/src/locate.rs:487`](../core/dr-decode/src/locate.rs#L487), [`core/dr-decode/src/locate.rs:571`](../core/dr-decode/src/locate.rs#L571), [`core/dr-decode/src/locate.rs:584`](../core/dr-decode/src/locate.rs#L584), [`core/dr-decode/src/locate.rs:667`](../core/dr-decode/src/locate.rs#L667), [`core/dr-export/examples/export.rs:99`](../core/dr-export/examples/export.rs#L99), [`core/dr-export/src/encode.rs:117`](../core/dr-export/src/encode.rs#L117), [`core/dr-export/src/encode.rs:161`](../core/dr-export/src/encode.rs#L161), [`core/dr-export/src/encode.rs:1`](../core/dr-export/src/encode.rs#L1), [`core/dr-export/src/encode.rs:206`](../core/dr-export/src/encode.rs#L206), [`core/dr-export/src/encode.rs:235`](../core/dr-export/src/encode.rs#L235), [`core/dr-export/src/encode.rs:311`](../core/dr-export/src/encode.rs#L311), [`core/dr-export/src/encode.rs:325`](../core/dr-export/src/encode.rs#L325), [`core/dr-export/src/encode.rs:408`](../core/dr-export/src/encode.rs#L408), [`core/dr-export/src/encode.rs:456`](../core/dr-export/src/encode.rs#L456), [`core/dr-export/src/encode.rs:70`](../core/dr-export/src/encode.rs#L70), [`core/dr-export/src/encode.rs:795`](../core/dr-export/src/encode.rs#L795), [`core/dr-export/src/encode.rs:809`](../core/dr-export/src/encode.rs#L809), [`core/dr-export/src/encode.rs:850`](../core/dr-export/src/encode.rs#L850), [`core/dr-export/src/encode.rs:898`](../core/dr-export/src/encode.rs#L898), [`core/dr-export/src/exif.rs:1`](../core/dr-export/src/exif.rs#L1), [`core/dr-export/src/lib.rs:138`](../core/dr-export/src/lib.rs#L138), [`core/dr-export/src/metadata.rs:1`](../core/dr-export/src/metadata.rs#L1), [`core/dr-export/src/metadata.rs:41`](../core/dr-export/src/metadata.rs#L41), [`core/dr-export/src/metadata.rs:74`](../core/dr-export/src/metadata.rs#L74), [`core/dr-types/src/lib.rs:655`](../core/dr-types/src/lib.rs#L655), [`core/dr-types/src/settings.rs:647`](../core/dr-types/src/settings.rs#L647), [`ui/dr-ui/src/develop.rs:4539`](../ui/dr-ui/src/develop.rs#L4539), [`ui/dr-ui/src/develop.rs:4651`](../ui/dr-ui/src/develop.rs#L4651), [`ui/dr-ui/src/develop.rs:729`](../ui/dr-ui/src/develop.rs#L729), [`ui/dr-ui/src/export.rs:474`](../ui/dr-ui/src/export.rs#L474), [`ui/dr-ui/src/export.rs:641`](../ui/dr-ui/src/export.rs#L641), [`ui/dr-ui/src/export.rs:687`](../ui/dr-ui/src/export.rs#L687), [`ui/dr-ui/src/export.rs:715`](../ui/dr-ui/src/export.rs#L715), [`ui/dr-ui/src/export.rs:839`](../ui/dr-ui/src/export.rs#L839), [`ui/dr-ui/src/export.rs:857`](../ui/dr-ui/src/export.rs#L857), [`ui/dr-ui/src/lib.rs:264`](../ui/dr-ui/src/lib.rs#L264), [`ui/dr-ui/src/lib.rs:287`](../ui/dr-ui/src/lib.rs#L287), [`ui/dr-ui/src/lib.rs:635`](../ui/dr-ui/src/lib.rs#L635), [`ui/dr-ui/src/settings_ui.rs:1`](../ui/dr-ui/src/settings_ui.rs#L1) | -| FR-EXP-9 | [`core/dr-decode/src/lib.rs:526`](../core/dr-decode/src/lib.rs#L526), [`core/dr-export/src/lib.rs:130`](../core/dr-export/src/lib.rs#L130), [`core/dr-export/src/lib.rs:1`](../core/dr-export/src/lib.rs#L1), [`core/dr-gpu/src/adjust.rs:1134`](../core/dr-gpu/src/adjust.rs#L1134), [`ui/dr-ui/examples/face_native.rs:1`](../ui/dr-ui/examples/face_native.rs#L1), [`ui/dr-ui/src/develop.rs:4672`](../ui/dr-ui/src/develop.rs#L4672), [`ui/dr-ui/src/develop.rs:4710`](../ui/dr-ui/src/develop.rs#L4710), [`ui/dr-ui/src/develop.rs:6878`](../ui/dr-ui/src/develop.rs#L6878), [`ui/dr-ui/src/lib.rs:603`](../ui/dr-ui/src/lib.rs#L603), [`ui/dr-ui/src/library.rs:4052`](../ui/dr-ui/src/library.rs#L4052), [`ui/dr-ui/src/library.rs:4522`](../ui/dr-ui/src/library.rs#L4522), [`ui/dr-ui/src/library.rs:4558`](../ui/dr-ui/src/library.rs#L4558), [`ui/dr-ui/tests/export_ignores_the_viewport.rs:1`](../ui/dr-ui/tests/export_ignores_the_viewport.rs#L1) | +| FR-EXP-8 | [`core/dr-decode/src/lib.rs:346`](../core/dr-decode/src/lib.rs#L346), [`core/dr-decode/src/lib.rs:370`](../core/dr-decode/src/lib.rs#L370), [`core/dr-decode/src/lib.rs:384`](../core/dr-decode/src/lib.rs#L384), [`core/dr-decode/src/lib.rs:71`](../core/dr-decode/src/lib.rs#L71), [`core/dr-decode/src/lib.rs:79`](../core/dr-decode/src/lib.rs#L79), [`core/dr-decode/src/lib.rs:82`](../core/dr-decode/src/lib.rs#L82), [`core/dr-decode/src/locate.rs:1164`](../core/dr-decode/src/locate.rs#L1164), [`core/dr-decode/src/locate.rs:1223`](../core/dr-decode/src/locate.rs#L1223), [`core/dr-decode/src/locate.rs:316`](../core/dr-decode/src/locate.rs#L316), [`core/dr-decode/src/locate.rs:487`](../core/dr-decode/src/locate.rs#L487), [`core/dr-decode/src/locate.rs:571`](../core/dr-decode/src/locate.rs#L571), [`core/dr-decode/src/locate.rs:584`](../core/dr-decode/src/locate.rs#L584), [`core/dr-decode/src/locate.rs:667`](../core/dr-decode/src/locate.rs#L667), [`core/dr-export/examples/export.rs:99`](../core/dr-export/examples/export.rs#L99), [`core/dr-export/src/encode.rs:117`](../core/dr-export/src/encode.rs#L117), [`core/dr-export/src/encode.rs:161`](../core/dr-export/src/encode.rs#L161), [`core/dr-export/src/encode.rs:1`](../core/dr-export/src/encode.rs#L1), [`core/dr-export/src/encode.rs:206`](../core/dr-export/src/encode.rs#L206), [`core/dr-export/src/encode.rs:235`](../core/dr-export/src/encode.rs#L235), [`core/dr-export/src/encode.rs:311`](../core/dr-export/src/encode.rs#L311), [`core/dr-export/src/encode.rs:325`](../core/dr-export/src/encode.rs#L325), [`core/dr-export/src/encode.rs:408`](../core/dr-export/src/encode.rs#L408), [`core/dr-export/src/encode.rs:456`](../core/dr-export/src/encode.rs#L456), [`core/dr-export/src/encode.rs:70`](../core/dr-export/src/encode.rs#L70), [`core/dr-export/src/encode.rs:795`](../core/dr-export/src/encode.rs#L795), [`core/dr-export/src/encode.rs:809`](../core/dr-export/src/encode.rs#L809), [`core/dr-export/src/encode.rs:850`](../core/dr-export/src/encode.rs#L850), [`core/dr-export/src/encode.rs:898`](../core/dr-export/src/encode.rs#L898), [`core/dr-export/src/exif.rs:1`](../core/dr-export/src/exif.rs#L1), [`core/dr-export/src/lib.rs:140`](../core/dr-export/src/lib.rs#L140), [`core/dr-export/src/metadata.rs:1`](../core/dr-export/src/metadata.rs#L1), [`core/dr-export/src/metadata.rs:41`](../core/dr-export/src/metadata.rs#L41), [`core/dr-export/src/metadata.rs:74`](../core/dr-export/src/metadata.rs#L74), [`core/dr-types/src/lib.rs:655`](../core/dr-types/src/lib.rs#L655), [`core/dr-types/src/settings.rs:647`](../core/dr-types/src/settings.rs#L647), [`ui/dr-ui/src/develop.rs:4539`](../ui/dr-ui/src/develop.rs#L4539), [`ui/dr-ui/src/develop.rs:4651`](../ui/dr-ui/src/develop.rs#L4651), [`ui/dr-ui/src/develop.rs:729`](../ui/dr-ui/src/develop.rs#L729), [`ui/dr-ui/src/export.rs:474`](../ui/dr-ui/src/export.rs#L474), [`ui/dr-ui/src/export.rs:641`](../ui/dr-ui/src/export.rs#L641), [`ui/dr-ui/src/export.rs:687`](../ui/dr-ui/src/export.rs#L687), [`ui/dr-ui/src/export.rs:715`](../ui/dr-ui/src/export.rs#L715), [`ui/dr-ui/src/export.rs:839`](../ui/dr-ui/src/export.rs#L839), [`ui/dr-ui/src/export.rs:857`](../ui/dr-ui/src/export.rs#L857), [`ui/dr-ui/src/lib.rs:264`](../ui/dr-ui/src/lib.rs#L264), [`ui/dr-ui/src/lib.rs:287`](../ui/dr-ui/src/lib.rs#L287), [`ui/dr-ui/src/lib.rs:635`](../ui/dr-ui/src/lib.rs#L635), [`ui/dr-ui/src/settings_ui.rs:1`](../ui/dr-ui/src/settings_ui.rs#L1) | +| FR-EXP-9 | [`core/dr-decode/src/lib.rs:526`](../core/dr-decode/src/lib.rs#L526), [`core/dr-export/src/lib.rs:132`](../core/dr-export/src/lib.rs#L132), [`core/dr-export/src/lib.rs:1`](../core/dr-export/src/lib.rs#L1), [`core/dr-gpu/src/adjust.rs:1134`](../core/dr-gpu/src/adjust.rs#L1134), [`ui/dr-ui/examples/face_native.rs:1`](../ui/dr-ui/examples/face_native.rs#L1), [`ui/dr-ui/src/develop.rs:4672`](../ui/dr-ui/src/develop.rs#L4672), [`ui/dr-ui/src/develop.rs:4710`](../ui/dr-ui/src/develop.rs#L4710), [`ui/dr-ui/src/develop.rs:6878`](../ui/dr-ui/src/develop.rs#L6878), [`ui/dr-ui/src/lib.rs:603`](../ui/dr-ui/src/lib.rs#L603), [`ui/dr-ui/src/library.rs:4052`](../ui/dr-ui/src/library.rs#L4052), [`ui/dr-ui/src/library.rs:4522`](../ui/dr-ui/src/library.rs#L4522), [`ui/dr-ui/src/library.rs:4558`](../ui/dr-ui/src/library.rs#L4558), [`ui/dr-ui/tests/export_ignores_the_viewport.rs:1`](../ui/dr-ui/tests/export_ignores_the_viewport.rs#L1) | | FR-MRG-1 | [`core/dr-pano/src/align.rs:1`](../core/dr-pano/src/align.rs#L1), [`core/dr-pano/src/lib.rs:1`](../core/dr-pano/src/lib.rs#L1), [`ui/dr-ui/src/lib.rs:1546`](../ui/dr-ui/src/lib.rs#L1546), [`ui/dr-ui/src/merge.rs:1`](../ui/dr-ui/src/merge.rs#L1), [`ui/dr-ui/src/merge_ui.rs:1`](../ui/dr-ui/src/merge_ui.rs#L1), [`ui/dr-ui/ui/app.slint:1681`](../ui/dr-ui/ui/app.slint#L1681), [`ui/dr-ui/ui/app.slint:820`](../ui/dr-ui/ui/app.slint#L820), [`ui/dr-ui/ui/library.slint:1591`](../ui/dr-ui/ui/library.slint#L1591), [`ui/dr-ui/ui/library.slint:4236`](../ui/dr-ui/ui/library.slint#L4236), [`ui/dr-ui/ui/merge.slint:1`](../ui/dr-ui/ui/merge.slint#L1) | | FR-MRG-10 | [`core/dr-gpu/src/merge.rs:1`](../core/dr-gpu/src/merge.rs#L1), [`core/dr-gpu/src/shaders/merge.wgsl:1`](../core/dr-gpu/src/shaders/merge.wgsl#L1), [`core/dr-pano/src/lib.rs:1`](../core/dr-pano/src/lib.rs#L1) | | FR-MRG-11 | [`core/dr-gpu/src/merge.rs:1`](../core/dr-gpu/src/merge.rs#L1), [`core/dr-gpu/src/shaders/merge.wgsl:1`](../core/dr-gpu/src/shaders/merge.wgsl#L1) | | FR-MRG-2 | [`core/dr-gpu/src/adjust.rs:104`](../core/dr-gpu/src/adjust.rs#L104), [`core/dr-gpu/src/adjust.rs:1157`](../core/dr-gpu/src/adjust.rs#L1157), [`core/dr-gpu/src/adjust.rs:1276`](../core/dr-gpu/src/adjust.rs#L1276), [`core/dr-gpu/src/adjust.rs:316`](../core/dr-gpu/src/adjust.rs#L316), [`core/dr-pipeline/src/graph.rs:852`](../core/dr-pipeline/src/graph.rs#L852), [`core/dr-pipeline/src/operation.rs:427`](../core/dr-pipeline/src/operation.rs#L427), [`core/dr-pipeline/src/operation.rs:595`](../core/dr-pipeline/src/operation.rs#L595), [`ui/dr-ui/src/merge.rs:1`](../ui/dr-ui/src/merge.rs#L1) | | FR-MRG-3 | [`core/dr-decode/examples/linear_dng.rs:1`](../core/dr-decode/examples/linear_dng.rs#L1), [`core/dr-decode/src/lib.rs:137`](../core/dr-decode/src/lib.rs#L137), [`core/dr-decode/src/lib.rs:144`](../core/dr-decode/src/lib.rs#L144), [`core/dr-decode/src/lib.rs:578`](../core/dr-decode/src/lib.rs#L578), [`core/dr-decode/src/profile.rs:395`](../core/dr-decode/src/profile.rs#L395), [`core/dr-export/src/dng.rs:1`](../core/dr-export/src/dng.rs#L1), [`core/dr-gpu/src/demosaic.rs:254`](../core/dr-gpu/src/demosaic.rs#L254), [`ui/dr-ui/src/merge.rs:1`](../ui/dr-ui/src/merge.rs#L1) | -| FR-MRG-4 | [`core/dr-pano/src/projection.rs:1`](../core/dr-pano/src/projection.rs#L1), [`ui/dr-ui/src/merge_ui.rs:1`](../ui/dr-ui/src/merge_ui.rs#L1), [`ui/dr-ui/ui/merge.slint:1`](../ui/dr-ui/ui/merge.slint#L1) | +| FR-MRG-4 | [`core/dr-export/src/inscribed.rs:1`](../core/dr-export/src/inscribed.rs#L1), [`core/dr-pano/src/projection.rs:1`](../core/dr-pano/src/projection.rs#L1), [`ui/dr-ui/src/merge_ui.rs:1`](../ui/dr-ui/src/merge_ui.rs#L1), [`ui/dr-ui/ui/merge.slint:1`](../ui/dr-ui/ui/merge.slint#L1) | | FR-MRG-5 | [`core/dr-pano/src/align.rs:1`](../core/dr-pano/src/align.rs#L1), [`ui/dr-ui/src/merge.rs:1`](../ui/dr-ui/src/merge.rs#L1), [`ui/dr-ui/src/merge_ui.rs:1`](../ui/dr-ui/src/merge_ui.rs#L1), [`ui/dr-ui/ui/merge.slint:1`](../ui/dr-ui/ui/merge.slint#L1) | -| FR-MRG-6 | [`core/dr-pipeline/src/sidecar.rs:1033`](../core/dr-pipeline/src/sidecar.rs#L1033), [`core/dr-pipeline/src/sidecar.rs:113`](../core/dr-pipeline/src/sidecar.rs#L113), [`core/dr-pipeline/src/sidecar.rs:123`](../core/dr-pipeline/src/sidecar.rs#L123), [`core/dr-pipeline/src/sidecar.rs:2241`](../core/dr-pipeline/src/sidecar.rs#L2241), [`core/dr-pipeline/src/sidecar.rs:841`](../core/dr-pipeline/src/sidecar.rs#L841), [`ui/dr-ui/src/merge.rs:464`](../ui/dr-ui/src/merge.rs#L464) | +| FR-MRG-6 | [`core/dr-pipeline/src/sidecar.rs:1033`](../core/dr-pipeline/src/sidecar.rs#L1033), [`core/dr-pipeline/src/sidecar.rs:113`](../core/dr-pipeline/src/sidecar.rs#L113), [`core/dr-pipeline/src/sidecar.rs:123`](../core/dr-pipeline/src/sidecar.rs#L123), [`core/dr-pipeline/src/sidecar.rs:2241`](../core/dr-pipeline/src/sidecar.rs#L2241), [`core/dr-pipeline/src/sidecar.rs:841`](../core/dr-pipeline/src/sidecar.rs#L841), [`ui/dr-ui/src/merge.rs:480`](../ui/dr-ui/src/merge.rs#L480) | | FR-MRG-7 | [`ui/dr-ui/src/merge.rs:1`](../ui/dr-ui/src/merge.rs#L1), [`ui/dr-ui/src/merge_ui.rs:1`](../ui/dr-ui/src/merge_ui.rs#L1), [`ui/dr-ui/ui/app.slint:820`](../ui/dr-ui/ui/app.slint#L820), [`ui/dr-ui/ui/merge.slint:1`](../ui/dr-ui/ui/merge.slint#L1) | | FR-MRG-8 | [`core/dr-pano/src/xfeat.rs:1`](../core/dr-pano/src/xfeat.rs#L1), [`core/dr-segment/examples/onnx_probe.rs:1`](../core/dr-segment/examples/onnx_probe.rs#L1) | | FR-NC-1 | [`core/dr-sync-nextcloud/src/auth.rs:132`](../core/dr-sync-nextcloud/src/auth.rs#L132), [`core/dr-sync-nextcloud/src/auth.rs:44`](../core/dr-sync-nextcloud/src/auth.rs#L44), [`core/dr-sync-nextcloud/src/provider.rs:1`](../core/dr-sync-nextcloud/src/provider.rs#L1), [`core/dr-sync/src/account.rs:361`](../core/dr-sync/src/account.rs#L361), [`ui/dr-ui/src/launch.rs:277`](../ui/dr-ui/src/launch.rs#L277), [`ui/dr-ui/src/launch.rs:61`](../ui/dr-ui/src/launch.rs#L61), [`ui/dr-ui/src/launch_ui.rs:417`](../ui/dr-ui/src/launch_ui.rs#L417) | diff --git a/ui/dr-ui/src/merge.rs b/ui/dr-ui/src/merge.rs index 1f9fe0c..67653a3 100644 --- a/ui/dr-ui/src/merge.rs +++ b/ui/dr-ui/src/merge.rs @@ -110,6 +110,10 @@ impl MergeRequest { #[derive(Debug, Clone, Copy, PartialEq)] pub enum Decision { Merge { projection: Option }, + /// Draw the alignment again on another surface, and report it again. + /// The page's projection chips: what a choice looks like before it is + /// confirmed, at proxy cost rather than the merge's. + Preview { projection: Option }, Abandon, } @@ -279,8 +283,7 @@ fn run_inner( let projection = request .projection .unwrap_or_else(|| Projection::suggest(hfov, vfov)); - let bounds = projection::bounds(projection, focal_full, &cameras, frame_size) - .ok_or("the frames project nowhere")?; + let suggested = projection; // 4. Gains, before the report so the preview shows them. let gains = if alignment.is_complete() { @@ -288,23 +291,25 @@ fn run_inner( } else { vec![1.0; frames.len()] }; - let preview = if alignment.is_complete() { - progress("Drawing the preview", 0, 1); - Some(preview(&colour, &proxies, &alignment, &gains, projection, &frames[0].raw)) - } else { - None + let report = |projection: Projection| -> Result { + let bounds = projection::bounds(projection, focal_full, &cameras, frame_size) + .ok_or("the frames project nowhere")?; + let preview = alignment + .is_complete() + .then(|| preview(&colour, &proxies, &alignment, &gains, projection, &frames[0].raw)); + Ok(AlignmentReport { + focal_mm: focal_full * 36.0 / full_long, + rms_px: alignment.rms_px, + projection, + width: bounds.width().ceil() as u32, + height: bounds.height().ceil() as u32, + frames: describe(&alignment), + links: alignment.links.len(), + preview, + }) }; - let report = AlignmentReport { - focal_mm: focal_full * 36.0 / full_long, - rms_px: alignment.rms_px, - projection, - width: bounds.width().ceil() as u32, - height: bounds.height().ceil() as u32, - frames: describe(&alignment), - links: alignment.links.len(), - preview, - }; - let _ = events.send(MergeEvent::Aligned(report)); + progress("Drawing the preview", 0, 1); + let _ = events.send(MergeEvent::Aligned(report(suggested)?)); if !alignment.is_complete() { let names: Vec = alignment .unaligned @@ -322,7 +327,10 @@ fn run_inner( return Ok(None); } match decision.recv_timeout(std::time::Duration::from_millis(100)) { - Ok(Decision::Merge { projection: p }) => break p.unwrap_or(projection), + Ok(Decision::Merge { projection: p }) => break p.unwrap_or(suggested), + Ok(Decision::Preview { projection: p }) => { + let _ = events.send(MergeEvent::Aligned(report(p.unwrap_or(suggested))?)); + } Ok(Decision::Abandon) => return Ok(None), Err(std::sync::mpsc::RecvTimeoutError::Timeout) => continue, Err(std::sync::mpsc::RecvTimeoutError::Disconnected) => return Ok(None), @@ -388,6 +396,10 @@ fn run_inner( // other's pace more than one band. let (band_tx, band_rx) = std::sync::mpsc::sync_channel::>(1); let rows_per_strip = request.chunk.1.max(1); + // The crop (FR-MRG-4), found as the bands go by; the writer reads it + // once the last strip is in, by which time every band has been seen. + let inscribed = Arc::new(std::sync::Mutex::new(dr_export::Inscribed::new(out_w))); + let inscribed_for_writer = inscribed.clone(); let file = std::fs::File::create(&out_path).map_err(|e| format!("{}: {e}", out_path.display()))?; let writer = std::thread::spawn(move || -> Result<(), String> { let mut file = std::io::BufWriter::new(file); @@ -405,6 +417,7 @@ fn run_inner( buf.extend_from_slice(&band); Ok(()) }, + || inscribed_for_writer.lock().ok().map(|i| i.best()), ) .map_err(|e| e.to_string()) }); @@ -435,6 +448,9 @@ fn run_inner( |band| { bands_done += 1; progress("Merging", bands_done, total_bands); + if let Ok(mut i) = inscribed.lock() { + i.push_rows(band.covered, band.rows); + } band_tx .send(band.rgb.to_vec()) .map_err(|_| dr_gpu::GpuError::Readback("the writer stopped".into())) diff --git a/ui/dr-ui/src/merge_ui.rs b/ui/dr-ui/src/merge_ui.rs index 71ea577..9c3243f 100644 --- a/ui/dr-ui/src/merge_ui.rs +++ b/ui/dr-ui/src/merge_ui.rs @@ -224,12 +224,7 @@ pub fn wire( let ctl = ctl.clone(); window.on_merge_confirm(move || { let Some(w) = weak.upgrade() else { return }; - let projection = match ctl.projection.get() { - 1 => Some(dr_pano::Projection::Perspective), - 2 => Some(dr_pano::Projection::Cylindrical), - 3 => Some(dr_pano::Projection::Spherical), - _ => None, - }; + let projection = chip_projection(ctl.projection.get()); if let Some(job) = ctl.job.borrow().as_ref() { let _ = job.decide.send(Decision::Merge { projection }); job.activity.detail("merging"); @@ -240,9 +235,19 @@ pub fn wire( }); } { + let weak = window.as_weak(); let ctl = ctl.clone(); window.on_merge_projection_picked(move |i| { + let Some(w) = weak.upgrade() else { return }; ctl.projection.set(i); + w.set_merge_projection_selected(i); + // Show it: the job redraws the preview on that surface and + // reports again, and the drain puts the new picture up. + if let Some(job) = ctl.job.borrow().as_ref() { + let _ = job.decide.send(Decision::Preview { + projection: chip_projection(i), + }); + } }); } @@ -348,6 +353,16 @@ fn start( *ctl.timer.borrow_mut() = Some(timer); } +/// The projection a chip index names; 0 is "Auto". +fn chip_projection(i: i32) -> Option { + match i { + 1 => Some(dr_pano::Projection::Perspective), + 2 => Some(dr_pano::Projection::Cylindrical), + 3 => Some(dr_pano::Projection::Spherical), + _ => None, + } +} + /// Take everything the job has said and reflect it on the page. fn drain(window: &AppWindow, ctl: &Rc, on_done: &Rc) { let events = {