LR-EDIT-CALIB: Calibration — shadows tint and R/G/B primary hue/saturation

White-preserving primaries matrix (OkLCh hue rotation / chroma scale of each Rec.2020
primary) plus a luminance-preserving green/magenta shadows tint, applied in scene-linear
light before the tone map. CPU (colorops::calibrate) and GPU (finish.wgsl) with an
equivalence case; seven control specs (calibration.*), a settings group, crs: import
(ShadowTint, Red/Green/BlueHue/Saturation) and a Calibration section at the bottom of the
Edit panel.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Brandon Thomas
2026-10-01 10:01:48 -04:00
co-authored by Claude Opus 5.5
parent b13274f711
commit 48efc6df52
13 changed files with 238 additions and 4 deletions
+9
View File
@@ -22,6 +22,7 @@ pub enum Section {
Optics,
Geometry,
Profile,
Calibration,
}
impl Section {
@@ -40,6 +41,7 @@ impl Section {
Section::Optics => "Optics",
Section::Geometry => "Geometry",
Section::Profile => "Profile",
Section::Calibration => "Calibration",
}
}
}
@@ -227,6 +229,13 @@ controls! {
"geometry.scale" => geometry.scale, "Scale", Geometry, 50, 150, 100, 1, 0, Plain;
"geometry.offsetX" => geometry.offset_x, "Offset X", Geometry, -100, 100, 0, 0.1, 1, Centered;
"geometry.offsetY" => geometry.offset_y, "Offset Y", Geometry, -100, 100, 0, 0.1, 1, Centered;
"calibration.shadowsTint" => calibration.shadows_tint, "Shadows Tint", Calibration, -100, 100, 0, 1, 0, Tint;
"calibration.redHue" => calibration.red_hue, "Red Hue", Calibration, -100, 100, 0, 1, 0, Gradient { from: "#e0306a", to: "#e08a30" };
"calibration.redSat" => calibration.red_sat, "Red Saturation", Calibration, -100, 100, 0, 1, 0, Gradient { from: "#7a7a7a", to: "#e03a3a" };
"calibration.greenHue" => calibration.green_hue, "Green Hue", Calibration, -100, 100, 0, 1, 0, Gradient { from: "#b0c030", to: "#30c08a" };
"calibration.greenSat" => calibration.green_sat, "Green Saturation", Calibration, -100, 100, 0, 1, 0, Gradient { from: "#7a7a7a", to: "#40b050" };
"calibration.blueHue" => calibration.blue_hue, "Blue Hue", Calibration, -100, 100, 0, 1, 0, Gradient { from: "#30a0d0", to: "#8040d0" };
"calibration.blueSat" => calibration.blue_sat, "Blue Saturation", Calibration, -100, 100, 0, 1, 0, Gradient { from: "#7a7a7a", to: "#3a60e0" };
"crop.angle" => crop.geometry.angle, "Straighten", Geometry, -45, 45, 0, 0.01, 2, Centered;
}
+5 -1
View File
@@ -32,10 +32,11 @@ pub enum SettingsGroup {
Spots,
RedEye,
LensBlur,
Calibration,
}
impl SettingsGroup {
pub const ALL: [SettingsGroup; 19] = [
pub const ALL: [SettingsGroup; 20] = [
SettingsGroup::Profile,
SettingsGroup::Treatment,
SettingsGroup::WhiteBalance,
@@ -55,6 +56,7 @@ impl SettingsGroup {
SettingsGroup::Spots,
SettingsGroup::RedEye,
SettingsGroup::LensBlur,
SettingsGroup::Calibration,
];
/// Groups included by default when copying (Lightroom excludes crop, masks and spots by default).
@@ -86,6 +88,7 @@ impl SettingsGroup {
SettingsGroup::Spots => "Remove",
SettingsGroup::RedEye => "Red Eye",
SettingsGroup::LensBlur => "Lens Blur",
SettingsGroup::Calibration => "Calibration",
}
}
@@ -111,6 +114,7 @@ impl SettingsGroup {
SettingsGroup::Spots => &["spots"],
SettingsGroup::RedEye => &["red_eye"],
SettingsGroup::LensBlur => &["lens_blur"],
SettingsGroup::Calibration => &["calibration"],
}
}
}
+28
View File
@@ -32,6 +32,8 @@ pub struct DevelopSettings {
pub red_eye: Vec<RedEye>,
pub lens_blur: LensBlur,
pub enhance: Enhance,
/// Camera calibration: shadows tint and primary hue/saturation (applied before tone mapping).
pub calibration: Calibration,
/// Section on/off toggles (the "eye" buttons on panel headers): section id → enabled.
pub disabled_sections: Vec<String>,
}
@@ -62,6 +64,7 @@ impl Default for DevelopSettings {
red_eye: Vec::new(),
lens_blur: LensBlur::default(),
enhance: Enhance::default(),
calibration: Calibration::default(),
disabled_sections: Vec::new(),
}
}
@@ -319,6 +322,31 @@ impl ColorGrading {
}
}
/// Camera calibration (Lightroom Classic's Calibration panel): a green/magenta tint of the shadows,
/// and a hue rotation / saturation scale of each working-space primary (−100..100), i.e. a
/// white-preserving 3×3 matrix applied in scene-linear light before tone mapping.
#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
#[serde(default)]
pub struct Calibration {
pub shadows_tint: f64,
pub red_hue: f64,
pub red_sat: f64,
pub green_hue: f64,
pub green_sat: f64,
pub blue_hue: f64,
pub blue_sat: f64,
}
impl Calibration {
/// (hue, saturation) of the red, green and blue primaries.
pub fn primaries(&self) -> [(f64, f64); 3] {
[(self.red_hue, self.red_sat), (self.green_hue, self.green_sat), (self.blue_hue, self.blue_sat)]
}
pub fn is_neutral(&self) -> bool {
*self == Calibration::default()
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
#[serde(default)]
pub struct Effects {