The merge job and its page: a selection to a panorama DNG, confirmed first
dr_ui::merge is the orchestration with no interface in it: decode each frame to sensor data and build its graph as a session would (orientation, lens profile); render each through the camera-space tap at proxy size and detect keypoints there, so the alignment is measured in the undistorted frame the tiles are rendered in; align; solve one gain per frame from the proxies' overlaps; draw the aligned set in colour for the page; then wait. Nothing is written until a Decision arrives (FR-MRG-1). The merge writes a linear DNG through the outbox with a destination record, so the drain puts it beside its sources on a folder library and a server alike, and the library rescans (FR-MRG-3). merge.slint is the page, on the import page's model: the alignment table with a failed frame named on its row and the button held off (FR-MRG-5), the preview, the projection choice, Stop and Back. A "Merge to panorama" button joins the grid's selection bar at two frames. Headless, the example produces the fixture's 22 993 x 5 980 DNG in 45 s on the reference desktop, exposures balanced across the stop of drift.
This commit is contained in:
+20
-7
@@ -230,13 +230,7 @@ fn stage(encoded: &Encoded, remote_dir: &str, outbox: &Path) -> Result<PathBuf,
|
||||
std::fs::write(&candidate, &encoded.bytes)
|
||||
.map_err(|e| format!("{}: {e}", candidate.display()))?;
|
||||
|
||||
let record = candidate.with_extension(format!(
|
||||
"{}.dest",
|
||||
candidate
|
||||
.extension()
|
||||
.map(|s| s.to_string_lossy().into_owned())
|
||||
.unwrap_or_default()
|
||||
));
|
||||
let record = destination_record(&candidate);
|
||||
// The remote folder and the intended name, one per line. Not JSON: two
|
||||
// strings do not need a parser, and a format a human can repair by hand
|
||||
// is worth something for a queue holding the only copy of someone's work.
|
||||
@@ -246,6 +240,19 @@ fn stage(encoded: &Encoded, remote_dir: &str, outbox: &Path) -> Result<PathBuf,
|
||||
Ok(candidate)
|
||||
}
|
||||
|
||||
/// The record that names where a staged payload goes: `name.ext.dest`
|
||||
/// beside it. One function so the merge, which stages its own composite,
|
||||
/// and the drain agree on the name.
|
||||
pub(crate) fn destination_record(payload: &Path) -> PathBuf {
|
||||
payload.with_extension(format!(
|
||||
"{}.dest",
|
||||
payload
|
||||
.extension()
|
||||
.map(|s| s.to_string_lossy().into_owned())
|
||||
.unwrap_or_default()
|
||||
))
|
||||
}
|
||||
|
||||
/// Everything currently waiting in the outbox.
|
||||
///
|
||||
/// A payload with no record is skipped rather than guessed at — see the write
|
||||
@@ -691,6 +698,12 @@ fn export_one(
|
||||
/// `{date}` is the capture date, not today's: a template naming exports by
|
||||
/// when the shutter fired is the reason the token exists. A file that recorded
|
||||
/// no capture time gets an empty string, never a substitute.
|
||||
/// The date and the carried record for one file's header — for the batch,
|
||||
/// and for a merge writing its composite's header from its first source.
|
||||
pub(crate) fn header_for_file(meta: &dr_decode::Metadata) -> (String, dr_export::SourceMetadata) {
|
||||
from_header(meta)
|
||||
}
|
||||
|
||||
fn from_header(meta: &dr_decode::Metadata) -> (String, dr_export::SourceMetadata) {
|
||||
let date = meta
|
||||
.captured_at
|
||||
|
||||
Reference in New Issue
Block a user