mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-29 20:48:33 -06:00
LR-EDIT-COLOR-TEMP: Kelvin white balance for the Nikon Z5II from built-in presets
The Z5II has no colour matrices and its NEFs carry no preset table, so its Temp slider showed the relative -100..+100 scale. A small per-model table (crates/raw/src/kelvin_builtin.rs) gives five (Kelvin, multipliers) points that feed the existing KelvinPresets curve: the NEF decoder attaches them to ColorData::kelvin_presets and kelvin_presets::covers accepts the model. Points: as-shot WB_RBLevels of the body's own shots at Incandescent, Direct sunlight, Kelvin 5500 K, Cloudy and Shade, placed at the temperature the camera itself writes for each preset (maker note ColorTemperatureAuto: 3000, 5210, 5500, 5990, 8000 K). Fluorescent and flash are left out. Natural Auto (camera: 5030 K) reads back at 5036 K. Sources: own design on the existing kelvin_presets model; the camera's own stated temperatures; ExifTool Nikon tag-name documentation (prose), exiftool used as a black-box tool. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
f34c7a8a89
commit
b03f1dd396
@@ -0,0 +1,92 @@
|
||||
//! Built-in white-balance presets for cameras whose raws carry neither colour matrices nor a preset table, feeding
|
||||
//! the Kelvin scale of [`crate::kelvin_presets`].
|
||||
//!
|
||||
//! Nikon NEFs store only the as-shot multipliers (`WB_RBLevels`) and, in the maker note, the colour temperature the
|
||||
//! camera states for the white-balance setting it shot with (`ColorTemperatureAuto`, `0x0045`: tag name from the
|
||||
//! ExifTool Nikon tag documentation, read with exiftool run as a black-box tool on the files). A body's points are
|
||||
//! the as-shot multipliers of its own photos shot at one preset each, placed at the temperature the camera itself
|
||||
//! wrote for that preset. Presets off the daylight locus (fluorescent, flash) and shots with a fine-tune shift are
|
||||
//! left out.
|
||||
//!
|
||||
//! Sources: own photos of each body listed (a reporter's samples, never committed), the camera's own stated
|
||||
//! temperatures, and the ExifTool Nikon tag-name documentation (prose). No matrices or third-party data.
|
||||
use crate::kelvin_presets::KelvinPresets;
|
||||
|
||||
/// One body: its make's first word, its Exif model without the make, and five (Kelvin, R/G, B/G) points.
|
||||
struct Builtin {
|
||||
make: &'static str,
|
||||
model: &'static str,
|
||||
points: [(f32, f32, f32); 5],
|
||||
}
|
||||
|
||||
const BUILTINS: &[Builtin] = &[
|
||||
// Nikon Z5II (issue emmertex/lightcraft-classic#157): Incandescent 3000 K, Direct sunlight 5210 K, the Kelvin
|
||||
// setting at 5500 K, Cloudy 5990 K, Shade 8000 K (the temperatures the camera writes in `ColorTemperatureAuto`).
|
||||
// Natural Auto (the camera wrote 5030 K) reads back at 5036 K; Flash (5410 K nominal) at about 6500 K.
|
||||
Builtin {
|
||||
make: "NIKON",
|
||||
model: "Z5_2",
|
||||
points: [
|
||||
(3000.0, 1.138_671_9, 2.083_984_4),
|
||||
(5210.0, 1.726_562_5, 1.335_937_5),
|
||||
(5500.0, 1.771_484_4, 1.281_25),
|
||||
(5990.0, 1.839_843_8, 1.212_890_6),
|
||||
(8000.0, 2.097_656_3, 1.089_843_8),
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
fn find_builtin(make: Option<&str>, model: &str) -> Option<&'static Builtin> {
|
||||
let model = model.trim();
|
||||
BUILTINS.iter().find(|b| {
|
||||
let n = b.make.len();
|
||||
let bare = match (model.get(..n), model.get(n..)) {
|
||||
(Some(prefix), Some(rest)) if prefix.eq_ignore_ascii_case(b.make) => rest.trim_start(),
|
||||
_ => model,
|
||||
};
|
||||
make.is_none_or(|m| m.split_whitespace().next().is_some_and(|w| w.eq_ignore_ascii_case(b.make))) && bare.eq_ignore_ascii_case(b.model)
|
||||
})
|
||||
}
|
||||
|
||||
/// Whether this Exif make and model has built-in presets.
|
||||
pub fn covers(make: Option<&str>, model: &str) -> bool {
|
||||
find_builtin(make, model).is_some()
|
||||
}
|
||||
|
||||
/// The built-in presets of this Exif make and model, if any.
|
||||
pub fn find(make: Option<&str>, model: &str) -> Option<KelvinPresets> {
|
||||
let b = find_builtin(make, model)?;
|
||||
KelvinPresets::new(b.points.map(|(k, r, b)| (k, [r, 1.0, b])))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn every_builtin_makes_a_curve() {
|
||||
for b in BUILTINS {
|
||||
assert!(find(Some(b.make), b.model).is_some(), "{} {}", b.make, b.model);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn z5ii_reads_its_own_shots_near_the_camera_temperatures() {
|
||||
let p = find(Some("NIKON CORPORATION"), "NIKON Z5_2").unwrap();
|
||||
// Natural Auto, not on the curve: the camera wrote 5030 K for it
|
||||
let (t, tint) = p.temp_tint([1.695_312_5, 1.0, 1.373_046_9]).unwrap();
|
||||
assert!((1e6 / t - 1e6 / 5030.0).abs() < 10.0 && tint.abs() < 5.0, "{t} {tint}");
|
||||
// Flash (nominally 5410 K) sits near the curve on the daylight side
|
||||
let (t, tint) = p.temp_tint([1.916_015_6, 1.0, 1.164_062_5]).unwrap();
|
||||
assert!((5000.0..7500.0).contains(&t) && tint.abs() < 10.0, "{t} {tint}");
|
||||
// the 5990 K Kelvin shot with a green-magenta fine-tune shift: daylight, clearly tinted
|
||||
let (t, tint) = p.temp_tint([1.531_25, 1.0, 1.074_218_8]).unwrap();
|
||||
assert!((4000.0..7500.0).contains(&t) && tint.abs() > 20.0, "{t} {tint}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn matching() {
|
||||
assert!(covers(Some("NIKON CORPORATION"), "NIKON Z5_2") && covers(None, "Z5_2") && covers(Some("Nikon"), "nikon z5_2"));
|
||||
assert!(!covers(Some("NIKON CORPORATION"), "NIKON Z5") && !covers(Some("SONY"), "Z5_2") && !covers(None, "") && !covers(None, "NIK"));
|
||||
}
|
||||
}
|
||||
@@ -41,8 +41,12 @@ const MAX_TINT: f64 = 150.0;
|
||||
const MODELS: &[&str] = &["DSC-RX10", "ILCA-77M2", "ILCA-99M2", "ILCE-3500", "ILCE-7S", "SLT-A33"];
|
||||
|
||||
/// Whether a raw of this Exif make and model is edited in Kelvin through its presets (when it has no colour
|
||||
/// matrices of its own or measured: see [`crate::spectral::find`], which comes first).
|
||||
/// matrices of its own or measured: see [`crate::spectral::find`], which comes first), from the file's presets or
|
||||
/// built-in ones ([`crate::kelvin_builtin`]).
|
||||
pub fn covers(make: Option<&str>, model: &str) -> bool {
|
||||
if crate::kelvin_builtin::covers(make, model) {
|
||||
return true;
|
||||
}
|
||||
let model = model.trim();
|
||||
let model = model.strip_prefix("SONY ").or_else(|| model.strip_prefix("Sony ")).unwrap_or(model);
|
||||
make.is_none_or(|m| m.split_whitespace().next().is_some_and(|b| b.eq_ignore_ascii_case("SONY")))
|
||||
|
||||
@@ -34,6 +34,7 @@ pub mod gaintable;
|
||||
pub mod highlight;
|
||||
#[cfg(feature = "jxl")]
|
||||
mod jxl;
|
||||
pub mod kelvin_builtin;
|
||||
pub mod kelvin_presets;
|
||||
pub mod lens_metadata;
|
||||
pub mod ljpeg;
|
||||
|
||||
Vendored
+1
-1
@@ -298,7 +298,7 @@ pub(crate) fn decode(bytes: &[u8], mode: Mode) -> Result<RawImage> {
|
||||
active_area: Rect::new(0, 0, active_w, h),
|
||||
crop,
|
||||
orientation: Orientation::from_exif(ifd0.u16(t::ORIENTATION).unwrap_or(1)),
|
||||
color: ColorData::default(),
|
||||
color: ColorData { kelvin_presets: crate::kelvin_builtin::find(Some(&make), &model), ..Default::default() },
|
||||
wb_multipliers: wb,
|
||||
linearized: false,
|
||||
pixel_aspect,
|
||||
|
||||
@@ -193,7 +193,7 @@ differences and missing bindings are in [ui-parity.md](ui-parity.md#shortcuts).
|
||||
| LR-EDIT-LIGHT-CURVE-TAT | Drag-on-image curve adjust | P1 | ✅ | `cmd:develop.targeted` (`target: curve`) | |
|
||||
| LR-EDIT-COLOR-WB-PRESET | White-balance presets | P0 | ✅ | `cmd:develop.wb` | |
|
||||
| LR-EDIT-COLOR-WB-PICKER | White-balance eyedropper | P0 | ✅ | `cmd:tool.wbPicker`, `cmd:develop.wbPick`, `crates/ui-egui/src/tests_wb_selector.rs` | the Edit panel's selector button does what W does (from Compare, Survey or the grid it opens the photo in Detail); no magnified loupe while picking |
|
||||
| LR-EDIT-COLOR-TEMP | Temperature | P0 | ✅ | `ctl:wb.temp`, `crates/pipeline/src/local.rs` (`wb_matrix_for`, `legacy_white`), `crates/raw/src/color.rs` (`rebalance`), `crates/raw/src/spectral.rs` (`find_camera`), `crates/catalog/src/model.rs` (`relative_wb_camera`), `crates/develop/src/controls.rs` (`ControlSpec::position`), `crates/raw/src/kelvin_presets.rs` | Kelvin (2000–50000 K, As Shot read from the file) on raws with colour matrices: in the file (DNG: re-developed for the chosen white through the camera's own matrices, camera-space white balance as Lightroom; the eyedropper / Auto invert the same model) or measured for the camera model (`lightcraft_raw::spectral`, 52 bodies, issue #730: the matrices turn the as-shot multipliers into Kelvin and the slider back, the look stays the file's fit, and the picture is adapted from the as-shot white, Bradford), or read from the camera's own white-balance presets (Sony ARW `SR2SubIFD`, Sony ILCA-99M2, ILCA-77M2, SLT-A33, DSC-RX10, ILCE-3500, ILCE-7S: Sony's Kelvin dial reads back within 0.3 % on 5 settings of 4 bodies); raws without matrices anywhere and rendered files use the relative −100..+100 scale (6500 K / 0 = as shot). The Kelvin slider's track is Lightroom's: 7000 K in the middle, 2000–7000 K evenly on the left half, logarithmic up to 50000 K on the right (within 2 % of positions measured in Lightroom Classic), for drawing, clicks and drags; wheel / arrow-key nudges and Quick Develop still step a fixed 50 / 100 / 500 K; the relative slider stays linear. Settings written on the relative scale before a camera's matrices reached it keep their look (`WbScale`). Not verified against Lightroom's Kelvin: on the ILCE-7M3 the matrices read 7–10 % below Sony's dial (issue #730), and whether the matrices or the dial are off is open; raws without matrices or presets anywhere and rendered files use the relative −100..+100 scale (6500 K / 0 = as shot). Settings written on the relative scale before a camera's matrices reached it keep their look (`WbScale`). Not verified against Lightroom's Kelvin: on the ILCE-7M3 the matrices read 7–10 % below Sony's dial (issue #730), and whether the matrices or the dial are off is open |
|
||||
| LR-EDIT-COLOR-TEMP | Temperature | P0 | ✅ | `ctl:wb.temp`, `crates/pipeline/src/local.rs` (`wb_matrix_for`, `legacy_white`), `crates/raw/src/color.rs` (`rebalance`), `crates/raw/src/spectral.rs` (`find_camera`), `crates/catalog/src/model.rs` (`relative_wb_camera`), `crates/develop/src/controls.rs` (`ControlSpec::position`), `crates/raw/src/kelvin_presets.rs` | Kelvin (2000–50000 K, As Shot read from the file) on raws with colour matrices: in the file (DNG: re-developed for the chosen white through the camera's own matrices, camera-space white balance as Lightroom; the eyedropper / Auto invert the same model) or measured for the camera model (`lightcraft_raw::spectral`, 52 bodies, issue #730: the matrices turn the as-shot multipliers into Kelvin and the slider back, the look stays the file's fit, and the picture is adapted from the as-shot white, Bradford), or read from the camera's own white-balance presets (Sony ARW `SR2SubIFD`, Sony ILCA-99M2, ILCA-77M2, SLT-A33, DSC-RX10, ILCE-3500, ILCE-7S: Sony's Kelvin dial reads back within 0.3 % on 5 settings of 4 bodies; or built-in presets from the camera's own stated temperatures, `crates/raw/src/kelvin_builtin.rs`: Nikon Z5II, whose Natural Auto shot reads back at 5036 K against the 5030 K the camera wrote); raws without matrices anywhere and rendered files use the relative −100..+100 scale (6500 K / 0 = as shot). The Kelvin slider's track is Lightroom's: 7000 K in the middle, 2000–7000 K evenly on the left half, logarithmic up to 50000 K on the right (within 2 % of positions measured in Lightroom Classic), for drawing, clicks and drags; wheel / arrow-key nudges and Quick Develop still step a fixed 50 / 100 / 500 K; the relative slider stays linear. Settings written on the relative scale before a camera's matrices reached it keep their look (`WbScale`). Not verified against Lightroom's Kelvin: on the ILCE-7M3 the matrices read 7–10 % below Sony's dial (issue #730), and whether the matrices or the dial are off is open; raws without matrices or presets anywhere and rendered files use the relative −100..+100 scale (6500 K / 0 = as shot). Settings written on the relative scale before a camera's matrices reached it keep their look (`WbScale`). Not verified against Lightroom's Kelvin: on the ILCE-7M3 the matrices read 7–10 % below Sony's dial (issue #730), and whether the matrices or the dial are off is open |
|
||||
| LR-EDIT-COLOR-TINT | Tint | P0 | ✅ | `ctl:wb.tint`, `docs/tint-direction.md` | DNG / Lightroom sign: negative (left) adds green, positive (right) magenta; XMP `crs:Tint` values render as in Lightroom (issue #188); CPU/GPU regression tests |
|
||||
| LR-EDIT-COLOR-VIBRANCE | Vibrance | P0 | ✅ | `ctl:color.vibrance` | |
|
||||
| LR-EDIT-COLOR-SATURATION | Saturation | P0 | ✅ | `ctl:color.saturation` | |
|
||||
|
||||
Reference in New Issue
Block a user