# Conflicts:
#	crates/raw/tests/dng.rs
This commit is contained in:
Brandon Thomas
2026-10-14 01:38:59 -04:00
3 changed files with 28 additions and 2 deletions
+5 -1
View File
@@ -306,7 +306,11 @@ pub(crate) fn decode_as(bytes: &[u8], mode: Mode, format: RawFormat) -> Result<R
}
// the tag is anchored at the top-left of the ActiveArea (DNG spec); `RawImage::cfa` is anchored at the
// sensor origin (the crop to the active area shifts it back), so move the anchor out by the area's offset
Some(Cfa { width: cols, height: rows, pattern }.shifted(cols - active_area.x % cols, rows - active_area.y % rows))
// (LightCraft's own DNGs from before the writer followed the spec carry the sensor-origin pattern; they are marked by
// a bare "LightCraft" Software tag, the writer now says [`crate::dngwrite::SOFTWARE`], and keep their original reading)
let at_origin = Cfa { width: cols, height: rows, pattern };
let legacy_writer = [raw, ifd0].iter().any(|i| i.string(t::SOFTWARE).as_deref().map(str::trim) == Some("LightCraft"));
Some(if legacy_writer { at_origin } else { at_origin.shifted(cols - active_area.x % cols, rows - active_area.y % rows) })
} else {
None
};
+5 -1
View File
@@ -25,6 +25,10 @@ pub enum DngCompression {
},
}
/// Software tag of the DNGs this writer makes. A bare "LightCraft" marks older files, whose CFAPattern is anchored at
/// the sensor origin instead of the ActiveArea (the reader keeps their original reading).
pub const SOFTWARE: &str = "LightCraft DNG 2";
/// IEEE 754 binary32 → binary16 (round to nearest even; overflow → ±inf, NaN kept).
pub fn f32_to_f16(v: f32) -> u16 {
let b = v.to_bits();
@@ -119,7 +123,7 @@ pub fn write_dng(raw: &RawImage, opts: &DngWriteOptions) -> Result<Vec<u8>> {
let unique = format!("{make} {model}").trim().to_string();
ifd.set(t::UNIQUE_CAMERA_MODEL, Value::Ascii(if unique.is_empty() { "LightCraft".into() } else { unique }));
ifd.set(t::ORIENTATION, Value::Short(vec![raw.orientation.to_exif()]));
ifd.set(t::SOFTWARE, Value::Ascii("LightCraft".into()));
ifd.set(t::SOFTWARE, Value::Ascii(SOFTWARE.into()));
let xmp = opts.xmp.clone().unwrap_or_else(|| lightcraft_meta::write_xmp(&raw.metadata, None));
ifd.set(t::XMP, Value::Byte(xmp.into_bytes()));
+18
View File
@@ -1072,3 +1072,21 @@ fn two_samples_per_pixel_are_refused_not_a_panic() {
let n = lightcraft_raw::Normalized { width: 2, height: 1, cpp: 2, data: vec![0.1, 0.2, 0.3, 0.4], cfa: None };
assert_eq!(lightcraft_raw::demosaic(&n, Method::Ahd).data, vec![[0.1, 0.2, 0.2], [0.3, 0.4, 0.4]]);
}
/// DNGs LightCraft wrote before the writer anchored the pattern at the ActiveArea carry a bare "LightCraft" Software
/// tag and a sensor-origin pattern; they keep that reading, while the current writer marks its files differently.
#[test]
fn legacy_lightcraft_dng_keeps_the_sensor_anchored_pattern() {
let (w, h) = (20, 12);
let build = |software: &str| {
let mut raw = base_ifd(w, h, 16, 1, true);
raw.set_image(ImageData::Strips { rows_per_strip: h as u32, strips: vec![u16_bytes(&pattern(w, h, 1, 16), ByteOrder::Little)] });
raw.set(t::WHITE_LEVEL, Value::Short(vec![65535]));
raw.set(t::ACTIVE_AREA, Value::Long(vec![1, 0, h as u32 - 1, w as u32 - 1]));
raw.set(t::SOFTWARE, Value::Ascii(software.into()));
decode(&dng(raw, ByteOrder::Little)).unwrap()
};
assert_eq!(build("LightCraft").cfa.unwrap().name(), "GRBG");
assert_eq!(build("Adobe Photoshop Lightroom").cfa.unwrap().name(), "BGGR");
assert_ne!(lightcraft_raw::dngwrite::SOFTWARE, "LightCraft");
}