diff --git a/crates/engine/src/media.rs b/crates/engine/src/media.rs index 6c917b2314..c54cd8d746 100644 --- a/crates/engine/src/media.rs +++ b/crates/engine/src/media.rs @@ -62,7 +62,7 @@ impl SettingsHashes { } /// Bump when the pipeline's output changes, to invalidate cached thumbnails. -pub const RENDER_CACHE_VERSION: u64 = 61; +pub const RENDER_CACHE_VERSION: u64 = 62; /// Thumbnails render at one of these long edges (so window/cell size changes reuse the cache). pub const THUMB_SIZES: [usize; 4] = [128, 256, 384, 512]; diff --git a/crates/raw/src/dng.rs b/crates/raw/src/dng.rs index b7c2b07105..033fd28d3b 100644 --- a/crates/raw/src/dng.rs +++ b/crates/raw/src/dng.rs @@ -300,7 +300,9 @@ pub(crate) fn decode_as(bytes: &[u8], mode: Mode, format: RawFormat) -> Result Result> { if let Some(cfa) = &raw.cfa { ifd.set(t::CFA_REPEAT_PATTERN_DIM, Value::Short(vec![cfa.height as u16, cfa.width as u16])); - ifd.set(t::CFA_PATTERN_EP, Value::Byte(cfa.pattern.clone())); + // the tag is anchored at the ActiveArea's top-left, `raw.cfa` at the sensor origin + let a = raw.active_area; + ifd.set(t::CFA_PATTERN_EP, Value::Byte(cfa.shifted(a.x, a.y).pattern)); ifd.set(t::CFA_PLANE_COLOR, Value::Byte(vec![0, 1, 2])); ifd.set(t::CFA_LAYOUT, Value::Short(vec![1])); } diff --git a/crates/raw/tests/dng.rs b/crates/raw/tests/dng.rs index 130f3d229e..9876e349eb 100644 --- a/crates/raw/tests/dng.rs +++ b/crates/raw/tests/dng.rs @@ -1026,3 +1026,24 @@ fn baseline_sharpness_is_read_when_plausible() { assert_eq!(decode(&bytes).unwrap().color.baseline_sharpness, None); assert_eq!(decode(&profile_dng(ByteOrder::Big, |_| {})).unwrap().color.baseline_sharpness, None); } + +/// The DNG CFAPattern is anchored at the top-left of the ActiveArea: a DNG whose area starts on an odd row or +/// column still demosaics with the pattern the tag states (#1086), and the writer states it back the same way. +#[test] +fn cfa_pattern_is_relative_to_the_active_area() { + let (w, h) = (20, 12); + for (top, left, sensor_name) in [(0u32, 0u32, "GRBG"), (1, 0, "BGGR"), (0, 1, "RGGB"), (1, 1, "GBRG"), (3, 5, "GBRG"), (2, 4, "GRBG")] { + 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![top, left, h as u32 - 1, w as u32 - 1])); + let img = decode(&dng(raw, ByteOrder::Little)).unwrap(); + assert_eq!(img.cfa.as_ref().unwrap().name(), sensor_name, "sensor anchor for area at ({left}, {top})"); + assert_eq!(img.normalized().unwrap().cfa.unwrap().name(), "GRBG", "tag pattern for area at ({left}, {top})"); + let back = + decode(&write_dng(&img, &DngWriteOptions { compression: DngCompression::Uncompressed, order: ByteOrder::Little, xmp: None }).unwrap()) + .unwrap(); + assert_eq!(back.cfa.as_ref().unwrap().name(), sensor_name); + assert_eq!(back.normalized().unwrap().cfa.unwrap().name(), "GRBG"); + } +}