mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-30 04:57:22 -06:00
M4.2: optics — manual distortion/vignetting, lateral CA (auto + manual), defringe, DNG-embedded profile corrections
- pipeline::optics::Warp: one inverse map (lens-corrected → source) inside the single geometry resample: manual radial distortion, embedded DNG WarpRectilinear (per plane), per-channel lateral CA scale, radial vignetting gain (manual + embedded FixVignetteRadial); all in normalized radii - automatic lateral CA estimate: radial edge profiles matched sub-pixel per plane, robust LSQ fit of displacement = α·r (cached per source) - defringe: purple/green hue ranges (OkLab hue) desaturated next to high-contrast edges, width relative to the image long edge - "Enable Lens Corrections" applies only lens data embedded in DNGs (opcodes are no longer baked at decode; stored on the photo record, on by default at import when present). No LCP data anywhere. - new controls: optics.caRed/caBlue, defringe hue ranges; Optics panel: Manual sub-section, Defringe flyout - tests: barrel grid straightened, CA removal (auto + manual), embedded corrections toggle/scale, preview vs export resolution independence, CA estimator accuracy, orientation of embedded lens data Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
55c5c6f0c6
commit
a0da99660d
@@ -213,7 +213,13 @@ controls! {
|
||||
"optics.profileDistortion" => optics.profile_distortion, "Profile Distortion", Optics, 0, 200, 100, 1, 0, Plain;
|
||||
"optics.profileVignetting" => optics.profile_vignetting, "Profile Vignetting", Optics, 0, 200, 100, 1, 0, Plain;
|
||||
"optics.defringePurple" => optics.defringe_purple_amount, "Purple Amount", Optics, 0, 20, 0, 1, 0, Gradient { from: "#7a7a7a", to: "#a040d0" };
|
||||
"optics.defringePurpleHueLo" => optics.defringe_purple_hue_lo, "Purple Hue Low", Optics, 0, 90, 30, 1, 0, Gradient { from: "#6040e0", to: "#d040b0" };
|
||||
"optics.defringePurpleHueHi" => optics.defringe_purple_hue_hi, "Purple Hue High", Optics, 10, 100, 70, 1, 0, Gradient { from: "#6040e0", to: "#d040b0" };
|
||||
"optics.defringeGreen" => optics.defringe_green_amount, "Green Amount", Optics, 0, 20, 0, 1, 0, Gradient { from: "#7a7a7a", to: "#40c040" };
|
||||
"optics.defringeGreenHueLo" => optics.defringe_green_hue_lo, "Green Hue Low", Optics, 0, 90, 40, 1, 0, Gradient { from: "#c0d030", to: "#30c0b0" };
|
||||
"optics.defringeGreenHueHi" => optics.defringe_green_hue_hi, "Green Hue High", Optics, 10, 100, 60, 1, 0, Gradient { from: "#c0d030", to: "#30c0b0" };
|
||||
"optics.caRed" => optics.ca_red, "Red/Cyan Fringe", Optics, -100, 100, 0, 1, 0, Gradient { from: "#30c0c0", to: "#e04040" };
|
||||
"optics.caBlue" => optics.ca_blue, "Blue/Yellow Fringe", Optics, -100, 100, 0, 1, 0, Gradient { from: "#d0c030", to: "#4060e0" };
|
||||
"geometry.vertical" => geometry.vertical, "Vertical", Geometry, -100, 100, 0, 1, 0, Centered;
|
||||
"geometry.horizontal" => geometry.horizontal, "Horizontal", Geometry, -100, 100, 0, 1, 0, Centered;
|
||||
"geometry.rotate" => geometry.rotate, "Rotate", Geometry, -10, 10, 0, 0.1, 1, Centered;
|
||||
|
||||
@@ -419,6 +419,10 @@ pub struct Optics {
|
||||
pub distortion: f64,
|
||||
pub vignetting: f64,
|
||||
pub vignetting_midpoint: f64,
|
||||
/// Manual lateral chromatic aberration (red/cyan, blue/yellow fringes), −100..100, added to the
|
||||
/// automatic estimate when `remove_ca` is on.
|
||||
pub ca_red: f64,
|
||||
pub ca_blue: f64,
|
||||
}
|
||||
|
||||
impl Default for Optics {
|
||||
@@ -437,10 +441,42 @@ impl Default for Optics {
|
||||
distortion: 0.0,
|
||||
vignetting: 0.0,
|
||||
vignetting_midpoint: 50.0,
|
||||
ca_red: 0.0,
|
||||
ca_blue: 0.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Lens corrections embedded in a DNG file (`OpcodeList3`: `WarpRectilinear`, `FixVignetteRadial`), converted
|
||||
/// to the oriented, default-cropped image. This is camera/file data (stored on the photo record, not in the develop
|
||||
/// settings); "Enable Profile Corrections" applies it, scaled by the profile distortion/vignetting amounts.
|
||||
/// LightCraft never uses Adobe LCP lens profiles.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct EmbeddedLens {
|
||||
pub warp: Option<EmbeddedWarp>,
|
||||
pub vignette: Option<EmbeddedVignette>,
|
||||
}
|
||||
|
||||
/// DNG `WarpRectilinear`: per plane (R, G, B) `[kr0, kr1, kr2, kr3, kt0, kt1]`. For an output (corrected) point at
|
||||
/// offset `d = (p − center) / radius`, the source point is `center + radius · (d·f(r²) + tangential(d))`.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
|
||||
pub struct EmbeddedWarp {
|
||||
pub planes: [[f64; 6]; 3],
|
||||
/// Optical centre, normalized to the oriented image (0..1).
|
||||
pub center: Point,
|
||||
/// Normalisation radius as a fraction of the oriented image's long edge.
|
||||
pub radius: f64,
|
||||
}
|
||||
|
||||
/// DNG `FixVignetteRadial`: gain `1 + k0 r² + k1 r⁴ + … + k4 r¹⁰` with `r = |p − center| / radius`.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
|
||||
pub struct EmbeddedVignette {
|
||||
pub k: [f64; 5],
|
||||
pub center: Point,
|
||||
pub radius: f64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum Upright {
|
||||
|
||||
Reference in New Issue
Block a user