Cut panorama overlaps along seams instead of averaging them

The merge weighted every overlap pixel by its distance from each frame's
edge, a 200 px linear cross-fade. Anything the frames disagreed on —
parallax in the near foreground, grass in the wind, a walker — came out
twice at half strength: a soft double edge at 1:1.

dr_pano::seam picks, per output texel at proxy resolution, which frame a
pixel comes from. Where a new frame overlaps the composite the cost is the
gain-corrected difference plus local detail plus nearness to either
frame's edge, taken as the worst over a small window, and the cut is a
dynamic-programming path across the overlap. merge.wgsl weights each frame
by its tent-filtered share of that map, a 64 px blend that follows the
seam, with the edge feather kept as the fallback. The page's preview uses
the same map, and examples/merge.rs takes --feather-only for comparison.
This commit is contained in:
2026-09-30 21:57:44 -04:00
parent 1d7115437b
commit 6f33517b35
8 changed files with 1012 additions and 32 deletions
+9
View File
@@ -6,6 +6,9 @@
//! cargo run -p dr-ui --example merge --release -- --leave-out 3 fixtures/pano/2025-08-05/*.CR2
//! ```
//!
//! `--feather-only` merges without seams — every overlap the feathered
//! average — for comparing the two on the same set.
//!
//! `--leave-out N` unticks frame `N` once the first alignment is in, as the
//! page's box would, so the job aligns again without it.
//!
@@ -25,6 +28,7 @@ fn main() {
let mut fill = false;
let mut wait_engines = false;
let mut leave_out: Option<usize> = None;
let mut seams = true;
loop {
match args.first().map(String::as_str) {
Some("--out") => {
@@ -39,6 +43,10 @@ fn main() {
args.remove(0);
leave_out = args.remove(0).parse().ok();
}
Some("--feather-only") => {
args.remove(0);
seams = false;
}
Some("--wait-engines") => {
args.remove(0);
wait_engines = true;
@@ -101,6 +109,7 @@ fn main() {
dr_ui::merge::MergeRequest::new(frames, dr_ui::merge::MergeDestination::Local(dir));
// The filler from the shared models directory, as the app finds it.
request.inpaint_model = dr_ui::merge::inpaint_model_path();
request.seams = seams;
let (tx, rx) = std::sync::mpsc::channel();
let (decide, decision) = std::sync::mpsc::channel();
let cancel = dr_ui::merge::Cancel::default();