mirror of
https://github.com/storytold/lightcraft.git
synced 2026-11-01 22:17:53 -06:00
Photo Merge decoded each raw itself and wrote the reference frame's own colour tags into the merged DNG. A CR2 (and NEF, ARW and the other raws whose colour is fitted from the camera JPEG) has none, so an HDR merge of CR2s rendered with no camera colour: green and about 16 L* dark. `lightcraft_merge::load_frame_with` takes the raw's colour tags from the caller, and the engine passes `starting_colour_tags`: the colour the raw opens with (`starting_colour`) as DNG tags. That is one forward matrix with a colour matrix and AsShotNeutral that agree with it at the as-shot white, the look's hue/sat table as ProfileHueSatMap and its tone curve as ProfileToneCurve. Raws with their own matrices (DNG) keep their tags. The look's chroma curve has no DNG tag and is left out. On two CC0 CR2s (EOS 550D, 40D) an HDR merge of three copies against the photo alone: mean CIE76 dE 26.15 -> 1.07 and 22.89 -> 0.47. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
60 lines
2.2 KiB
Rust
60 lines
2.2 KiB
Rust
//! LightCraft Photo Merge: HDR merge and panorama stitching (layer L3, no UI dependencies).
|
|
//!
|
|
//! - [`frame`]: inputs — scene-linear frames from raw files (camera RGB) or standard images.
|
|
//! - [`hdr`]: exposure brackets → one scene-linear radiance image (auto-align, exposure
|
|
//! normalisation, saturation-aware weighting, deghosting with an overlay).
|
|
//! - [`pano`]: overlapping photos → one panorama (features, matching, RANSAC, rotation bundle
|
|
//! adjustment, spherical/cylindrical/perspective projection, exposure compensation, seams,
|
|
//! multi-band blending, boundary warp, auto crop, fill edges).
|
|
//! - [`output`]: results as linear DNGs (16-bit float for HDR) with the source's colour tags.
|
|
//!
|
|
//! Every algorithm here is our own implementation from published papers (cited in each module);
|
|
//! no code from GPL projects was consulted.
|
|
#![forbid(unsafe_code)]
|
|
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]
|
|
|
|
pub mod align;
|
|
pub mod features;
|
|
pub mod frame;
|
|
pub mod hdr;
|
|
pub mod linalg;
|
|
pub mod output;
|
|
pub mod pano;
|
|
pub mod ransac;
|
|
pub mod synth;
|
|
|
|
pub use frame::{Frame, FrameColor, RawColour, load_frame, load_frame_with};
|
|
pub use hdr::{Deghost, HdrOptions, HdrResult, merge_hdr};
|
|
pub use output::{DngSamples, write_linear_dng};
|
|
pub use pano::{PanoOptions, PanoResult, Projection, stitch};
|
|
|
|
#[derive(Debug, Clone, PartialEq, thiserror::Error)]
|
|
pub enum MergeError {
|
|
#[error("{0} photo(s) selected; at least {1} are needed")]
|
|
TooFew(usize, usize),
|
|
#[error("{0}")]
|
|
Mismatch(String),
|
|
#[error("could not read a photo: {0}")]
|
|
Decode(String),
|
|
#[error("{0}")]
|
|
NoOverlap(String),
|
|
#[error("cancelled")]
|
|
Cancelled,
|
|
#[error("could not write the result: {0}")]
|
|
Output(String),
|
|
}
|
|
|
|
pub type Result<T> = std::result::Result<T, MergeError>;
|
|
|
|
/// Progress callback: `(fraction 0..1, stage)`; return `false` to cancel.
|
|
pub type Progress<'a> = dyn Fn(f32, &str) -> bool + Sync + 'a;
|
|
|
|
/// A progress callback that ignores progress and never cancels.
|
|
pub fn no_progress(_: f32, _: &str) -> bool {
|
|
true
|
|
}
|
|
|
|
pub(crate) fn check(p: &Progress, f: f32, stage: &str) -> Result<()> {
|
|
if p(f.clamp(0.0, 1.0), stage) { Ok(()) } else { Err(MergeError::Cancelled) }
|
|
}
|