This commit is contained in:
Brandon Thomas
2026-10-09 20:34:54 -04:00
4 changed files with 61 additions and 0 deletions
+24
View File
@@ -1166,6 +1166,30 @@ mod tests {
assert!(e.contains("without an embedded preview"), "{e}");
}
/// A GoPro GPR (DNG with VC-5 coded data, no preview anywhere) still imports, described from its
/// headers; loading it says what is wrong instead of naming a bare TIFF compression number.
#[test]
fn gopro_vc5_dng_imports_and_fails_to_load_with_a_clear_reason() {
use lightcraft_tiff::{IfdBuilder, ImageData, TiffWriter, Value, tags as t};
let mut ifd = IfdBuilder::new();
ifd.set(t::NEW_SUBFILE_TYPE, Value::Long(vec![0]));
ifd.set(t::IMAGE_WIDTH, Value::Long(vec![32]));
ifd.set(t::IMAGE_LENGTH, Value::Long(vec![16]));
ifd.set(t::BITS_PER_SAMPLE, Value::Short(vec![16]));
ifd.set(t::SAMPLES_PER_PIXEL, Value::Short(vec![1]));
ifd.set(t::PHOTOMETRIC, Value::Short(vec![32803]));
ifd.set(t::COMPRESSION, Value::Short(vec![9]));
ifd.set(t::DNG_VERSION, Value::Byte(vec![1, 4, 0, 0]));
ifd.set(t::CFA_REPEAT_PATTERN_DIM, Value::Short(vec![2, 2]));
ifd.set(t::CFA_PATTERN_EP, Value::Byte(vec![0, 1, 1, 2]));
ifd.set_image(ImageData::Tiles { tile_width: 32, tile_height: 16, tiles: vec![vec![0x5au8; 400]] });
let f = TiffWriter::default().write(&[ifd]).unwrap();
let p = probe_bytes("GOPR0001.GPR", &f).unwrap();
assert_eq!((p.kind, p.preview_only, p.width, p.height), (MediaKind::Raw, None, 32, 16));
let e = load_bytes(&f, 16).unwrap_err();
assert!(e.contains("GoPro VC-5") && e.contains("not decoded yet"), "{e}");
}
/// Any failure of a recognised raw (not only an unsupported variant) can fall back to its preview;
/// CR3 keeps its own wording.
#[test]
+9
View File
@@ -125,6 +125,15 @@ pub(crate) fn decode_as(bytes: &[u8], mode: Mode, format: RawFormat) -> Result<R
let tiff = Tiff::parse(bytes)?;
let ifd0 = &tiff.ifds[0];
let raw = raw_ifd(&tiff).ok_or_else(|| RawError::Corrupt("DNG without a raw image IFD".into()))?;
// GoPro's GPR: a DNG whose raw data is VC-5 coded, which is not decoded (yet). Named here, before
// the generic "TIFF compression 9" of the chunk decoder. Header mode still describes the file
// (a GPR has no embedded preview, and a library keeps the photo with this reason shown, as it
// does for any file whose pixels fail to decode, rather than refusing to import it).
if mode == Mode::Full
&& let Some(c) = raw.u16(t::COMPRESSION).filter(|&c| c == t::compression::VC5)
{
return Err(RawError::Unsupported(format!("DNG compression {c} (GoPro VC-5) is not decoded yet")));
}
let info = raw.image()?;
let (w, h, cpp) = (info.width as usize, info.height as usize, info.samples_per_pixel as usize);
if !(1..=4).contains(&cpp) {
+26
View File
@@ -942,6 +942,32 @@ mod tests {
assert_eq!(probe(&g), None);
}
/// A GoPro GPR (a DNG with VC-5 coded raw data, Compression 9) is refused by the full decode with
/// a reason that names the format, whatever its tile bytes hold; the header probe still
/// describes it (so it imports).
#[test]
fn gopro_vc5_dng_is_unsupported_with_a_clear_reason() {
let mut ifd = IfdBuilder::new();
ifd.set(t::NEW_SUBFILE_TYPE, Value::Long(vec![0]));
ifd.set(t::IMAGE_WIDTH, Value::Long(vec![32]));
ifd.set(t::IMAGE_LENGTH, Value::Long(vec![16]));
ifd.set(t::BITS_PER_SAMPLE, Value::Short(vec![16]));
ifd.set(t::SAMPLES_PER_PIXEL, Value::Short(vec![1]));
ifd.set(t::PHOTOMETRIC, Value::Short(vec![32803]));
ifd.set(t::COMPRESSION, Value::Short(vec![9]));
ifd.set(t::DNG_VERSION, Value::Byte(vec![1, 4, 0, 0]));
ifd.set(t::CFA_REPEAT_PATTERN_DIM, Value::Short(vec![2, 2]));
ifd.set(t::CFA_PATTERN_EP, Value::Byte(vec![0, 1, 1, 2]));
ifd.set_image(ImageData::Tiles { tile_width: 32, tile_height: 16, tiles: vec![vec![0x5au8; 400]] });
let bytes = write(&[ifd]);
assert_eq!(probe(&bytes), Some(RawFormat::Dng));
let want = "DNG compression 9 (GoPro VC-5) is not decoded yet";
let Err(RawError::Unsupported(why)) = decode(&bytes) else { panic!("expected Unsupported from decode") };
assert_eq!(why, want);
let info = probe_info(&bytes).expect("header probe describes the file");
assert_eq!((info.width, info.height), (32, 16));
}
/// Compression 99 (not a registered TIFF value; Leaf MOS tiles) marks a raw even without a CFA tag.
#[test]
fn private_compression_99_is_a_raw() {
+2
View File
@@ -200,6 +200,8 @@ pub mod compression {
/// DNG lossy JPEG (baseline DCT, 8-bit).
pub const LOSSY_JPEG: u16 = 34892;
pub const JPEG_XL: u16 = 52546;
/// SMPTE ST 2073 (VC-5) as written by GoPro's GPR files (the value measured in them).
pub const VC5: u16 = 9;
/// Nikon NEF Huffman compression.
pub const NIKON: u16 = 34713;
/// Sony ARW compressed (the value used by ARW files).