From cfca2999451e5514474ee4cbac1a4f301ebc192c Mon Sep 17 00:00:00 2001 From: radialmonster Date: Fri, 9 Oct 2026 05:29:23 -0400 Subject: [PATCH] LR-IMP-FORMATS: report Sony A200/A230/A350 packed ARW as unsupported (preview fallback) The one-strip Sony ARW layout was accepted when the raw strip was 1.00-1.25x width x height bytes. The DSLR-A200, A230 and A350 sample files carry strips of 1.06x, 1.23x and 1.04x, passed that test and decoded as colour noise. Every file that decodes correctly (108 in the test archive) has a strip of exactly width x height bytes, so the layout is now accepted only then; the rest report "Sony ARW version 1 / packed compressed variant" and open from the embedded preview like the A290/A380. Part of #461 (not a decoder). RENDER_CACHE_VERSION 16 -> 19. --- crates/engine/src/media.rs | 2 +- crates/raw/src/vendor/arw.rs | 41 +++++++++++++++++++++++++++++++++--- 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/crates/engine/src/media.rs b/crates/engine/src/media.rs index b0439390..5c885bd8 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 = 16; +pub const RENDER_CACHE_VERSION: u64 = 19; /// 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/vendor/arw.rs b/crates/raw/src/vendor/arw.rs index acaacf0b..0c079d99 100644 --- a/crates/raw/src/vendor/arw.rs +++ b/crates/raw/src/vendor/arw.rs @@ -454,13 +454,17 @@ pub(crate) fn decode(bytes: &[u8], mode: Mode) -> Result { let linear_rgb = info.compression == 7 && info.photometric == photometric::YCBCR; let chunks = info.chunks(bytes.len() as u64); let strip_len: u64 = chunks.iter().map(|c| c.len).sum(); + // The row-per-w-bytes (ARW2) layout stores exactly one byte per pixel in a single strip; all 108 such files in the test + // archive measure exactly width x height. A200/A230/A350 files carry a strip 4-23 % larger than that (and A290/A390 one + // smaller), which is a different packing, so they are refused rather than read with the wrong layout. + let one_byte_per_sample = chunks.len() == 1 && strip_len == (w * h) as u64; let (data, out_bits) = match info.compression { 7 if linear_rgb => (RawData::U16(read_ycbcr_tiles(bytes, &info, raw, mode)?), 14), - 32767 if chunks.len() == 1 && strip_len >= (w * h) as u64 && strip_len < (w * h) as u64 * 5 / 4 && mode == Mode::Header => { + 32767 if one_byte_per_sample && mode == Mode::Header => { chunk_bytes(bytes, &chunks[0]).ok_or_else(|| RawError::Corrupt("raw strip outside file".into()))?; (RawData::U16(Vec::new()), 14) } - 32767 if chunks.len() == 1 && strip_len >= (w * h) as u64 && strip_len < (w * h) as u64 * 5 / 4 => { + 32767 if one_byte_per_sample => { let src = chunk_bytes(bytes, &chunks[0]).ok_or_else(|| RawError::Corrupt("raw strip outside file".into()))?; let curve = code_curve(&raw.u64s(TONE_CURVE).unwrap_or_else(|| vec![8000, 10400, 12900, 14100])); let mut data = vec![0u16; w * h]; @@ -473,7 +477,7 @@ pub(crate) fn decode(bytes: &[u8], mode: Mode) -> Result { }); (RawData::U16(data), 14) } - 32767 => return Err(RawError::Unsupported("Sony ARW version 1 / packed compressed variant".into())), + 32767 => return Err(RawError::Unsupported("Sony ARW version 1 / packed compressed variant (raw strip is not one byte per pixel)".into())), 7 if is_quad_tiled(bytes, &info) => match mode { Mode::Full => (RawData::U16(read_quad_tiles(bytes, &info)?), bits), Mode::Header => { @@ -595,6 +599,37 @@ mod tests { assert_eq!((developed.width, developed.height), (4, 4)); } + /// A 32767-compressed ARW whose single strip is `strip` bytes for a 32 x 4 image. + fn arw_with_strip(strip: usize) -> Vec { + use lightcraft_tiff::{IfdBuilder, ImageData, TiffWriter, Value}; + let mut raw = IfdBuilder::new(); + raw.set(t::MAKE, Value::Ascii("SONY".into())); + raw.set(t::MODEL, Value::Ascii("DSLR-A200".into())); + raw.set(t::IMAGE_WIDTH, Value::Long(vec![32])); + raw.set(t::IMAGE_LENGTH, Value::Long(vec![4])); + raw.set(t::BITS_PER_SAMPLE, Value::Short(vec![12])); + raw.set(t::SAMPLES_PER_PIXEL, Value::Short(vec![1])); + raw.set(t::PHOTOMETRIC, Value::Short(vec![photometric::CFA])); + raw.set(t::COMPRESSION, Value::Short(vec![32767])); + raw.set(TONE_CURVE, Value::Short(vec![8000, 10400, 12900, 14100])); + raw.set_image(ImageData::Strips { rows_per_strip: 4, strips: vec![vec![0u8; strip]] }); + TiffWriter::default().write(&[raw]).unwrap() + } + + #[test] + fn strip_larger_or_smaller_than_one_byte_per_pixel_is_the_packed_variant() { + for strip in [32 * 4 + 8, 32 * 4 * 5 / 4 - 1, 32 * 4 - 1] { + for mode in [Mode::Header, Mode::Full] { + let err = decode(&arw_with_strip(strip), mode).unwrap_err(); + assert!(matches!(err, RawError::Unsupported(ref m) if m.contains("packed compressed variant")), "{strip} {mode:?}: {err:?}"); + } + } + for mode in [Mode::Header, Mode::Full] { + let r = decode(&arw_with_strip(32 * 4), mode); + assert!(!matches!(r, Err(RawError::Unsupported(ref m)) if m.contains("packed compressed variant")), "{r:?}"); + } + } + /// Encode one 16-value set as an ARW2 block (the paper's scheme, used here to test the decoder). pub(crate) fn encode_block(v: &[u16; 16]) -> [u8; 16] { let (imax, &max) = v.iter().enumerate().max_by_key(|(i, x)| (**x, usize::MAX - *i)).unwrap();