From d6eac6685c837194a5049bf786eea10f1c369ec2 Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Sun, 4 Oct 2026 01:21:33 -0400 Subject: [PATCH] M3.7: header probe agrees with decode on subsampled lossless JPEG; lossless ARW corpus samples - tiffraw: Mode::Header parses the first LJ92 frame header (Compression 7), so probe_info reports Unsupported for subsampled frames (Sony lossless M/S) as the full decode does, instead of claiming the file decodes. - corpus: CC0 ILCE-7M4 lossless L/M/S samples from raw.pixls.us in `cargo xtask corpus`; M/S listed as known unsupported. Co-Authored-By: Claude Opus 5.5 --- crates/raw/src/tiffraw.rs | 7 +++++++ crates/raw/tests/corpus.rs | 2 ++ xtask/src/main.rs | 4 ++++ 3 files changed, 13 insertions(+) diff --git a/crates/raw/src/tiffraw.rs b/crates/raw/src/tiffraw.rs index 870a6164..200dd391 100644 --- a/crates/raw/src/tiffraw.rs +++ b/crates/raw/src/tiffraw.rs @@ -60,6 +60,13 @@ pub(crate) fn read_image_in(mode: crate::Mode, data: &[u8], info: &ImageInfo, or crate::Mode::Full => read_image(data, info, order, packing), crate::Mode::Header => { check_image(data, info)?; + // a lossless-JPEG layout the decoder rejects (subsampled components, e.g. Sony's + // lossless M/S sizes) must fail here too, as the full decode will + if info.compression == 7 + && let Some(src) = info.chunks(data.len() as u64).first().and_then(|c| chunk_bytes(data, c)) + { + ljpeg::frame_info(src)?; + } Ok(if info.sample_format == 3 { RawData::F32(Vec::new()) } else { RawData::U16(Vec::new()) }) } } diff --git a/crates/raw/tests/corpus.rs b/crates/raw/tests/corpus.rs index b8a5535c..2db239ed 100644 --- a/crates/raw/tests/corpus.rs +++ b/crates/raw/tests/corpus.rs @@ -16,6 +16,8 @@ fn corpus_root() -> PathBuf { /// Variants known not to decode yet (see the crate docs): matched against the lower-case file name. const KNOWN_UNSUPPORTED: &[&str] = &[ "cr3-", // CR3 / CRX (M11.1) + "arw-sony-a7m4-lossless-m", // Sony lossless compressed M/S: subsampled (YCbCr) lossless JPEG + "arw-sony-a7m4-lossless-s", // " "raf-fuji-xt20-compressed", // Fujifilm compressed RAF "rw2-panasonic-gh5.", // Panasonic raw format 4 (quantised) "rw2-panasonic-gx80", // " diff --git a/xtask/src/main.rs b/xtask/src/main.rs index a98c3c81..b91e6b86 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -230,6 +230,10 @@ const RAW_SAMPLES: &[(&str, &str)] = &[ "https://raw.pixls.us/getfile.php/2418/nice/Sony%20-%20ILCE-7M3%20-%2014bit%2014bit%20uncompressed%20%283:2%29.ARW", ), ("arw-sony-a7m4-14bit.arw", "https://raw.pixls.us/getfile.php/6936/nice/Sony%20-%20ILCE-7M4%20-%2014bit%20%283:2%29.ARW"), + // lossless compressed (Compression 7): L is 2×2 CFA cells per LJ92 sample; M and S are subsampled + ("arw-sony-a7m4-lossless-l.arw", "https://raw.pixls.us/data/Sony/ILCE-7M4/ILCE-7M4_DSC06674_FullFrame-LossLess-Compressed-Large.ARW"), + ("arw-sony-a7m4-lossless-m.arw", "https://raw.pixls.us/data/Sony/ILCE-7M4/ILCE-7M4_DSC06675_FullFrame-LossLess-Compressed-Medium.ARW"), + ("arw-sony-a7m4-lossless-s.arw", "https://raw.pixls.us/data/Sony/ILCE-7M4/ILCE-7M4_DSC06676_FullFrame-LossLess-Compressed-Small.ARW"), ("cr2-canon-5d3-sraw2.cr2", "https://raw.pixls.us/getfile.php/773/nice/Canon%20-%20EOS%205D%20Mark%20III%20-%20sRAW2%20%28sRAW%29.CR2"), ("cr2-canon-5d3.cr2", "https://raw.pixls.us/getfile.php/771/nice/Canon%20-%20EOS%205D%20Mark%20III.CR2"), ("cr2-canon-80d.cr2", "https://raw.pixls.us/getfile.php/1294/nice/Canon%20-%20EOS%2080D%20-%20RAW%20%283:2%29.CR2"),