mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-27 03:27:14 -06:00
LR-IMP-FORMATS: DNG CFAPattern is anchored at the ActiveArea, not the sensor (odd active-area offsets rendered magenta, #1086)
The DNG CFAPattern describes the top-left of the ActiveArea, but RawImage::cfa is sensor-anchored (the crop to the active area shifts it by the area's offset). The DNG reader now moves the tag's pattern out to the sensor origin, and the DNG writer states the pattern back at the ActiveArea. A DNG whose area starts on an odd row or column (the Canon EOS 60D DNG of #1086, top 51) no longer reads RGGB as GBRG.
This commit is contained in:
@@ -290,7 +290,9 @@ pub(crate) fn decode_as(bytes: &[u8], mode: Mode, format: RawFormat) -> Result<R
|
||||
if cpp != 1 {
|
||||
return Err(RawError::Unsupported("CFA data with several samples per pixel".into()));
|
||||
}
|
||||
Some(Cfa { width: cols, height: rows, pattern })
|
||||
// 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))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
@@ -125,7 +125,9 @@ pub fn write_dng(raw: &RawImage, opts: &DngWriteOptions) -> Result<Vec<u8>> {
|
||||
|
||||
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]));
|
||||
}
|
||||
|
||||
@@ -989,3 +989,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");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user