Issue #212: Raw: keep vendor white balance when writing DNG

NEF (and other vendor raws without a colour matrix) carry their as-shot
white balance as vendor multipliers, not as a DNG AsShotNeutral. The DNG
writer only copied AsShotNeutral/AsShotWhiteXY, so Convert to DNG and
Copy as DNG produced files with no white balance at all, which develop
dark and green. Write the reciprocal of the vendor multipliers as
AsShotNeutral when the source has no DNG white of its own.
This commit is contained in:
Gremiger
2026-10-07 21:43:23 +00:00
parent 7aab3fd97f
commit 855d3a7959
2 changed files with 19 additions and 0 deletions
+3
View File
@@ -177,6 +177,9 @@ pub fn write_dng(raw: &RawImage, opts: &DngWriteOptions) -> Result<Vec<u8>> {
ifd.set(t::AS_SHOT_NEUTRAL, rat_vec(&v));
} else if let Some(xy) = c.as_shot_white_xy {
ifd.set(t::AS_SHOT_WHITE_XY, rat_vec(&[xy.x, xy.y]));
} else if let Some(m) = raw.wb_multipliers.filter(|m| m.iter().all(|v| *v > 0.0 && v.is_finite())) {
// vendor raws (NEF, ARW…) carry white balance as multipliers: the DNG neutral is their reciprocal
ifd.set(t::AS_SHOT_NEUTRAL, rat_vec(&m.map(|v| 1.0 / v as f64)));
}
ifd.set(t::BASELINE_EXPOSURE, Value::SRational(vec![srational(c.baseline_exposure)]));
// the source's own profile look travels with its data (conversions, smart previews, merges)
+16
View File
@@ -453,6 +453,22 @@ fn colour_transform_from_decoded_dng() {
assert!(o.iter().all(|v| (v - 1.0).abs() < 1e-9));
}
/// Issue #212: a NEF converted to DNG came out dark and green. NEF white balance is vendor multipliers
/// (no colour matrix, no `AsShotNeutral`); the DNG must carry them as its as-shot neutral.
#[test]
fn vendor_white_balance_survives_dng_conversion() {
let mut raw = synthetic(16, 16, Some(Cfa::bayer("RGGB").unwrap()), 1);
raw.color = ColorData::default();
raw.wb_multipliers = Some([2.25, 1.0, 1.7421875]);
let before = lightcraft_raw::color::camera_transform(&raw, lightcraft_raw::color::as_shot_white_xy(&raw));
let back = decode(&write_dng(&raw, &DngWriteOptions::default()).unwrap()).unwrap();
let after = lightcraft_raw::color::camera_transform(&back, lightcraft_raw::color::as_shot_white_xy(&back));
for c in 0..3 {
assert!((before.wb[c] - after.wb[c]).abs() < 1e-3, "{:?} vs {:?}", before.wb, after.wb);
}
assert!((after.wb[0] - 2.25).abs() < 1e-3 && (after.wb[2] - 1.7421875).abs() < 1e-3, "{:?}", after.wb);
}
#[test]
fn rejects_unsupported_and_broken() {
let (w, h) = (8, 8);