diff --git a/ROADMAP.md b/ROADMAP.md index d0ba2a29..6609e660 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -57,7 +57,7 @@ Priorities, in order. Each points at tracker rows in [`docs/parity.md`](docs/par checked for plausible colour; fix per-model bugs (#85). 4. **Render fidelity suite** (LR-BEHAV-RENDER-FIDELITY, P1): measure our output against Lightroom on the same CC0 raws (references stay in the local `plan/`), then tune against the numbers. -5. **Lens profiles of our own** (LR-EDIT-OPTICS-PROFILE, P1): DNG, Panasonic / Leica RW2 and Sony Bayer ARW +5. **Lens profiles of our own** (LR-EDIT-OPTICS-PROFILE, P1): DNG, Panasonic / Leica RW2 and Sony ILCE-7RM4A Bayer ARW embedded distortion are supported; broaden validation and decode remaining vignetting / lateral CA metadata. 6. **AI model strategy** (maintainer decision): which permissively licensed models (or our own training) for segmentation masks and denoise. Bayer denoise inference is implemented in pure Rust with an opt-in GPL model; release policy and independently licensed weights remain open. Unblocks M12 and Enhance. Face detection and recognition run on our own checked Rust engine. Object / Describe masks now diff --git a/crates/engine/src/media.rs b/crates/engine/src/media.rs index ce41bfaa..e4519137 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 = 22; +pub const RENDER_CACHE_VERSION: u64 = 23; /// 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 28943b62..05442ff6 100644 --- a/crates/raw/src/vendor/arw.rs +++ b/crates/raw/src/vendor/arw.rs @@ -412,11 +412,10 @@ fn sr2_black_levels(cipher: &[u8], order: lightcraft_tiff::ByteOrder) -> Option< (levels.len() == 4 && (64..=4096).contains(&lo) && hi - lo <= 64).then(|| levels.iter().map(|&v| f32::from(v)).sum::() / 4.0) } -/// The image area for files without Sony's crop tags (`0x74c7/0x74c8`, written since about 2017): the DNG-style -/// default crop when the raw IFD has one, else the maker note's `FullImageSize` (the camera JPEG's size) anchored -/// at the top-left. Older bodies store a few columns of padding at the right edge of the raw frame (constant -/// values, 8–32 columns on the samples we checked) inside a frame 16–48 pixels wider than `FullImageSize`, so -/// the anchored crop removes them while keeping the CFA phase. +/// The image area: prefer the standard `DefaultCropOrigin` / `DefaultCropSize`, then Sony's +/// crop tags (`0x74c7/0x74c8`), then a plausible maker-note `FullImageSize` anchored at the top-left, +/// otherwise the full raw frame. Older bodies store a few padding columns at the right edge; +/// the anchored fallback removes those while keeping the CFA phase. All crops are clipped to the frame. fn default_crop(raw: &Ifd, mn: Option<&makernote::MakerNote>, w: usize, h: usize) -> Rect { let full = Rect::new(0, 0, w, h); if let (Some([x, y]), Some([cw, ch])) = (raw.u64s(t::DEFAULT_CROP_ORIGIN).as_deref(), raw.u64s(t::DEFAULT_CROP_SIZE).as_deref()) @@ -563,7 +562,7 @@ pub(crate) fn decode(bytes: &[u8], mode: Mode) -> Result { wb_multipliers: if linear_rgb { Some([1.0; 3]) } else { wb }, linearized: false, opcodes: OpcodeLists { - list3: if linear_rgb { Vec::new() } else { super::arw_lens::distortion(raw, Rect::new(0, 0, w, h), crop).into_iter().collect() }, + list3: if linear_rgb { Vec::new() } else { super::arw_lens::distortion(&model, raw, Rect::new(0, 0, w, h), crop).into_iter().collect() }, ..Default::default() }, metadata, diff --git a/crates/raw/src/vendor/arw_lens.rs b/crates/raw/src/vendor/arw_lens.rs index 4841ea1b..9f0cc80d 100644 --- a/crates/raw/src/vendor/arw_lens.rs +++ b/crates/raw/src/vendor/arw_lens.rs @@ -11,9 +11,15 @@ const DISTORTION: u16 = 0x7037; const SAMPLES: usize = 1024; const MAX_ERROR: f64 = 0.0005; -/// The 16-entry layout only. Unknown layouts and implausible/non-smooth maps are left uncorrected. +/// Only independently validated camera models and the 16-entry layout. Unknown models/layouts +/// and implausible/non-smooth maps are left uncorrected. See docs/sony-lens-corrections.md. /// A camera setting of Off does not remove the lens data: the user can enable it in the developer. -pub(super) fn distortion(ifd: &Ifd, active: Rect, crop: Rect) -> Option { +pub(super) fn distortion(model: &str, ifd: &Ifd, active: Rect, crop: Rect) -> Option { + // A matching table shape does not establish its scale or radial normalization on another body. + // Keep this exact: ILCE-7RM4 and ILCE-7RM4A are distinct models, not interchangeable aliases. + if model != "ILCE-7RM4A" { + return None; + } let Value::SShort(values) = &ifd.get(DISTORTION)?.value else { return None }; let [16, table @ ..] = values.as_slice() else { return None }; let table: &[i16; 16] = table.try_into().ok()?; @@ -131,7 +137,7 @@ mod tests { #[test] fn matches_independent_sony_reference_geometry() { // On/off feature correspondences from Imaging Edge 4.1 at 600 mm, independently fitted. - let Opcode::WarpRectilinear { planes, center } = distortion(&table(&TELE), AREA, AREA).unwrap() else { panic!("warp") }; + let Opcode::WarpRectilinear { planes, center } = distortion("ILCE-7RM4A", &table(&TELE), AREA, AREA).unwrap() else { panic!("warp") }; assert_eq!(center, [0.5, 0.5]); let reference = [0.9760159044, 0.0254891644, -0.0005984764, -0.0010120638]; for i in 0..=100 { @@ -145,8 +151,8 @@ mod tests { fn barrel_framing_and_offset_crop_keep_the_same_geometry() { let crop = Rect::new(32, 20, 9504, 6336); let active = Rect::new(0, 0, 9600, 6376); - let Opcode::WarpRectilinear { planes: a, .. } = distortion(&table(&WIDE), AREA, AREA).unwrap() else { panic!("warp") }; - let Opcode::WarpRectilinear { planes: b, center } = distortion(&table(&WIDE), active, crop).unwrap() else { panic!("warp") }; + let Opcode::WarpRectilinear { planes: a, .. } = distortion("ILCE-7RM4A", &table(&WIDE), AREA, AREA).unwrap() else { panic!("warp") }; + let Opcode::WarpRectilinear { planes: b, center } = distortion("ILCE-7RM4A", &table(&WIDE), active, crop).unwrap() else { panic!("warp") }; assert!(a[0][0] > 1.02 && a[0][0] < 1.03, "barrel correction expands the middle"); assert!((center[0] * 9599.0 - 4783.5).abs() < 1e-10); assert!((center[1] * 6375.0 - 3187.5).abs() < 1e-10); @@ -169,41 +175,52 @@ mod tests { ifd(Value::Short(vec![16; 17])), ifd(Value::Double(vec![f64::NAN; 17])), ] { - assert!(distortion(&bad, AREA, AREA).is_none()); + assert!(distortion("ILCE-7RM4A", &bad, AREA, AREA).is_none()); } - assert!(distortion(&table(&TELE), Rect::new(0, 0, 1, 1), AREA).is_none()); - assert!(distortion(&table(&TELE), AREA, Rect::new(0, 0, 9504, 5346)).is_none()); - assert!(distortion(&table(&TELE), AREA, Rect::new(99999, 0, 10, 10)).is_none()); + assert!(distortion("ILCE-7RM4A", &table(&TELE), Rect::new(0, 0, 1, 1), AREA).is_none()); + assert!(distortion("ILCE-7RM4A", &table(&TELE), AREA, Rect::new(0, 0, 9504, 5346)).is_none()); + assert!(distortion("ILCE-7RM4A", &table(&TELE), AREA, Rect::new(99999, 0, 10, 10)).is_none()); } + #[test] + fn refuses_unvalidated_models_even_with_a_valid_table() { + let valid = table(&TELE); + assert!(distortion("ILCE-7RM4A", &valid, AREA, AREA).is_some()); + for model in ["", "ILCE-7M3", "ILCE-7M4", "ILCE-7RM4", "ILCE-7RM5", "ILCE-9M2", "DSC-RX100M3", "ILCE-7RM4A unknown"] { + assert!(distortion(model, &valid, AREA, AREA).is_none(), "{model}"); + } + } + #[test] fn header_and_full_decode_preserve_correction_through_dng() { use lightcraft_tiff::{ByteOrder, IfdBuilder, ImageData, TiffWriter, tags as t}; - for order in [ByteOrder::Little, ByteOrder::Big] { - let mut raw = IfdBuilder::new(); - raw.set(t::MAKE, Value::Ascii("SONY".into())); - raw.set(t::MODEL, Value::Ascii("synthetic correction test".into())); - raw.set(t::IMAGE_WIDTH, Value::Long(vec![32])); - raw.set(t::IMAGE_LENGTH, Value::Long(vec![24])); - raw.set(t::BITS_PER_SAMPLE, Value::Short(vec![16])); - raw.set(t::SAMPLES_PER_PIXEL, Value::Short(vec![1])); - raw.set(t::PHOTOMETRIC, Value::Short(vec![t::photometric::CFA])); - raw.set(t::COMPRESSION, Value::Short(vec![1])); - raw.set(t::WHITE_LEVEL, Value::Long(vec![16383])); - raw.set(0x74c7, Value::Long(vec![0, 2])); - raw.set(t::DEFAULT_CROP_ORIGIN, Value::Long(vec![2, 2])); - raw.set(t::DEFAULT_CROP_SIZE, Value::Long(vec![30, 20])); - raw.set(0x74c8, Value::Long(vec![30, 20])); - raw.set(DISTORTION, Value::SShort(std::iter::once(16).chain(TELE).collect())); - // Zero pixels have the same byte representation in both orders. - raw.set_image(ImageData::Strips { rows_per_strip: 24, strips: vec![vec![0; 32 * 24 * 2]] }); - let file = TiffWriter { order, ..Default::default() }.write(&[raw]).unwrap(); - let header = super::super::arw::decode(&file, crate::Mode::Header).unwrap(); - let full = super::super::arw::decode(&file, crate::Mode::Full).unwrap(); - assert_eq!(header.info(), full.info()); - assert_eq!(full.opcodes.list3.len(), 1); - assert_eq!(full.crop, Rect::new(2, 2, 30, 20)); - let dng = crate::write_dng(&full, &Default::default()).unwrap(); - assert_eq!(crate::decode(&dng).unwrap().opcodes.list3, full.opcodes.list3); + for (model, expected) in [("ILCE-7RM4A", 1), ("ILCE-7RM4", 0), ("ILCE-7M3", 0), ("", 0)] { + for order in [ByteOrder::Little, ByteOrder::Big] { + let mut raw = IfdBuilder::new(); + raw.set(t::MAKE, Value::Ascii("SONY".into())); + raw.set(t::MODEL, Value::Ascii(model.into())); + raw.set(t::IMAGE_WIDTH, Value::Long(vec![32])); + raw.set(t::IMAGE_LENGTH, Value::Long(vec![24])); + raw.set(t::BITS_PER_SAMPLE, Value::Short(vec![16])); + raw.set(t::SAMPLES_PER_PIXEL, Value::Short(vec![1])); + raw.set(t::PHOTOMETRIC, Value::Short(vec![t::photometric::CFA])); + raw.set(t::COMPRESSION, Value::Short(vec![1])); + raw.set(t::WHITE_LEVEL, Value::Long(vec![16383])); + raw.set(0x74c7, Value::Long(vec![0, 2])); + raw.set(t::DEFAULT_CROP_ORIGIN, Value::Long(vec![2, 2])); + raw.set(t::DEFAULT_CROP_SIZE, Value::Long(vec![30, 20])); + raw.set(0x74c8, Value::Long(vec![30, 20])); + raw.set(DISTORTION, Value::SShort(std::iter::once(16).chain(TELE).collect())); + // Zero pixels have the same byte representation in both orders. + raw.set_image(ImageData::Strips { rows_per_strip: 24, strips: vec![vec![0; 32 * 24 * 2]] }); + let file = TiffWriter { order, ..Default::default() }.write(&[raw]).unwrap(); + let header = super::super::arw::decode(&file, crate::Mode::Header).unwrap(); + let full = super::super::arw::decode(&file, crate::Mode::Full).unwrap(); + assert_eq!(header.info(), full.info()); + assert_eq!(full.opcodes.list3.len(), expected, "{model} {order:?}"); + assert_eq!(full.crop, Rect::new(2, 2, 30, 20)); + let dng = crate::write_dng(&full, &Default::default()).unwrap(); + assert_eq!(crate::decode(&dng).unwrap().opcodes.list3, full.opcodes.list3); + } } } } diff --git a/crates/raw/tests/corpus.rs b/crates/raw/tests/corpus.rs index 53193f1f..24a03f04 100644 --- a/crates/raw/tests/corpus.rs +++ b/crates/raw/tests/corpus.rs @@ -610,3 +610,38 @@ fn corpus_raws_keep_their_container_and_are_not_thumbnail_shells() { } eprintln!("{checked} corpus raws keep their container"); } + +/// Matching Sony table dimensions are not evidence of a shared correction model. Exercise the +/// actual corpus headers and decoded files as well as the synthetic model-boundary unit tests. +#[test] +fn sony_embedded_distortion_is_limited_to_validated_models() { + let root = corpus_root().join("raw"); + let mut checked = 0; + for (name, model, expected) in [ + ("arw-sony-a7rm4a-compressed.arw", "ILCE-7RM4A", 1), + ("arw-sony-a9m2-compressed.arw", "ILCE-9M2", 0), + ("arw-sony-a7m3-compressed.arw", "ILCE-7M3", 0), + ("arw-sony-a7m3-uncompressed.arw", "ILCE-7M3", 0), + ("arw-sony-a7m4-14bit.arw", "ILCE-7M4", 0), + ("arw-sony-a7m4-lossless-l.arw", "ILCE-7M4", 0), + ("arw-sony-a7m4-lossless-m.arw", "ILCE-7M4", 0), + ("arw-sony-a7m4-lossless-s.arw", "ILCE-7M4", 0), + ("arw-sony-a7rm2-12bit-uncompressed.arw", "ILCE-7RM2", 0), + ("arw-sony-rx100.arw", "DSC-RX100", 0), + ("arw-sony-rx100m3.arw", "DSC-RX100M3", 0), + ] { + let path = root.join(name); + if !path.exists() { + eprintln!("skip: {} absent", path.display()); + continue; + } + let bytes = std::fs::read(&path).unwrap(); + let full = decode(&bytes).unwrap_or_else(|e| panic!("{name}: {e}")); + assert_eq!(probe_info(&bytes).unwrap(), full.info(), "{name}"); + assert_eq!(full.metadata.model.as_deref(), Some(model), "{name}"); + assert_eq!(full.opcodes.list3.len(), expected, "{name}: only the validated Sony model gets a distortion warp"); + eprintln!("{name}: {expected} warp(s); header and full decode agree"); + checked += 1; + } + eprintln!("Sony distortion model boundary: {checked} corpus files checked"); +} diff --git a/docs/parity.md b/docs/parity.md index b92a7f20..76986a4e 100644 --- a/docs/parity.md +++ b/docs/parity.md @@ -75,7 +75,7 @@ Take the first one nobody is working on. 4b. **LR-VIEW-ZOOM** (P0): finish the sharp zoomed views of issue #323: windows for compare, overlays and soft proofing (Before/After has them); check sharpness and the wide-stage margin on real raw photos (only synthetic scenes so far). 5. **LR-EDIT-OPTICS-PROFILE** (P1): a lens-profile database of our own (corrections embedded in DNG files and the - distortion correction Panasonic / Leica cameras record in RW2 / RWL files work today; issue #256). Sony Bayer + distortion correction Panasonic / Leica cameras record in RW2 / RWL files work today; issue #256). Sony ILCE-7RM4A Bayer ARW 16-sample tables in native 3:2 framing now feed the same distortion path; broader coverage and Sony vignetting / lateral CA remain (see `docs/sony-lens-corrections.md`). 6. **AI masks and Enhance** (LR-MASK-SUBJECT / SKY / PEOPLE / OBJECTS, LR-EDIT-DETAIL-DENOISE, SUPERRES, LENSBLUR): @@ -274,7 +274,7 @@ under the UI command that wraps it (a no-op error); the UI command now wins. | LR-EDIT-DETAIL-SUPERRES | Super resolution | P2 | ⬜ | | | | LR-EDIT-DETAIL-AISHARPEN | AI sharpen | OOS | 🚫 | | | | LR-EDIT-OPTICS-CA | Remove chromatic aberration | P1 | ✅ | `crates/ui-egui/src/panels/edit.rs` (checkbox), `ctl:optics.caRed`, `ctl:optics.caBlue` | | -| LR-EDIT-OPTICS-PROFILE | Lens profile corrections | P1 | 🟡 | `ctl:optics.profileDistortion`, `ctl:optics.profileVignetting`, `crates/pipeline/src/optics.rs`, `crates/raw/src/vendor/rw2.rs`, `crates/engine/src/crs.rs` | uses corrections embedded in the file: DNG `WarpRectilinear` / `FixVignetteRadial`, and the camera's own distortion correction in Panasonic / Leica RW2 / RWL files (tag `0x0119`, read as the equivalent `WarpRectilinear`; on 79 photos from 13 bodies and 11 lenses the corrected render lines up with the camera JPEG within 0.75 px at 1440 px at the corners, from up to 13 % of the half diagonal uncorrected; issue #256); an imported preset's `LensProfileEnable=0` leaves them on, as Lightroom applies built-in corrections regardless (inferred); Sony Bayer ARW raw-IFD distortion tables (`0x7037`, 16-sample layout, native 3:2 framing) use the same warp path, validated on ILCE-7RM4A with FE 24–105 and FE 200–600 lenses (see `docs/sony-lens-corrections.md`); vignetting and lateral CA data of other raw formats (Olympus, Fujifilm, Sony) not read yet; no lens-profile database | +| LR-EDIT-OPTICS-PROFILE | Lens profile corrections | P1 | 🟡 | `ctl:optics.profileDistortion`, `ctl:optics.profileVignetting`, `crates/pipeline/src/optics.rs`, `crates/raw/src/vendor/rw2.rs`, `crates/engine/src/crs.rs` | uses corrections embedded in the file: DNG `WarpRectilinear` / `FixVignetteRadial`, and the camera's own distortion correction in Panasonic / Leica RW2 / RWL files (tag `0x0119`, read as the equivalent `WarpRectilinear`; on 79 photos from 13 bodies and 11 lenses the corrected render lines up with the camera JPEG within 0.75 px at 1440 px at the corners, from up to 13 % of the half diagonal uncorrected; issue #256); an imported preset's `LensProfileEnable=0` leaves them on, as Lightroom applies built-in corrections regardless (inferred); Sony ILCE-7RM4A Bayer ARW raw-IFD distortion tables (`0x7037`, 16-sample layout, native 3:2 framing) use the same warp path, enabled only for the independently validated ILCE-7RM4A with FE 24–105 and FE 200–600 lenses; other Sony models remain uncorrected (see `docs/sony-lens-corrections.md`); vignetting and lateral CA data of other raw formats (Olympus, Fujifilm, Sony) not read yet; no lens-profile database | ) | LR-EDIT-OPTICS-DEFRINGE | Defringe | P1 | ✅ | `ctl:optics.defringe*` | no fringe eyedropper | | LR-EDIT-OPTICS-MANUAL | Manual distortion / vignetting | P1 | ✅ | `ctl:optics.distortion`, `ctl:optics.vignetting`, `ctl:optics.vignettingMidpoint` | | diff --git a/docs/sony-lens-corrections.md b/docs/sony-lens-corrections.md index a2fa705d..05f2fdc5 100644 --- a/docs/sony-lens-corrections.md +++ b/docs/sony-lens-corrections.md @@ -1,11 +1,13 @@ # Sony embedded distortion corrections -LightCraft reads the signed 16-sample distortion table in the raw image IFD (`0x7037`) of Sony ARWs. +LightCraft reads the signed 16-sample distortion table in the raw image IFD (`0x7037`) of +Sony **ILCE-7RM4A (A7R IVA)** Bayer ARWs. Other camera models are deliberately left uncorrected, +even when they contain a table with the same layout. It converts that table into `OpcodeList3` / `WarpRectilinear`, so the existing profile correction control, CPU/GPU optics path, EXIF orientation handling, and DNG export use the same correction. This adds **distortion only**. Sony vignetting and lateral chromatic-aberration tables are not decoded. -The tested files are Bayer ARWs from the ILCE-7RM4A with the FE 24–105mm F4 G OSS and +The enabled and independently validated model is ILCE-7RM4A with the FE 24–105mm F4 G OSS and FE 200–600mm F5.6–6.3 G OSS. Linear YCbCr ARWs and non-3:2 camera crops are excluded pending validation. An aspect crop may retain the full-frame radial normalization; the decoder does not guess that relationship. Older files that carry only encrypted correction metadata, different table lengths, and rejected tables remain uncorrected. @@ -70,3 +72,42 @@ source has loaded, enable lens corrections if they were previously disabled. Tests cover signedness/length rejection, zero and malformed tables, independent 600 mm reference geometry, barrel framing, offset crop normalization, both TIFF byte orders, header/full consistency, and preservation through DNG export. No private photographs or reference TIFFs are committed. + +## Camera coverage and regression boundary + +The model check is exact: `ILCE-7RM4` (A7R IV without the A suffix), `ILCE-7M3`, unknown/missing +model names, and every other model remain unsupported by this distortion decoder. Matching tag +numbers and table lengths do not establish the scale, interpolation or crop normalization on another +body. Support can expand after independent geometry validation; do not add model-name prefix matching. + +The original nine Sony samples listed by `cargo xtask corpus --download` were downloaded from its +CC0 URLs. The manifest now also includes CC0 samples 4822 (A7R IVA / 24–105 mm) and 3989 +(A9 II / 200–600 mm), with their SHA-256 hashes recorded alongside the URLs. All eleven files +are checked with `sony_embedded_distortion_is_limited_to_validated_models`: + +| Camera | Corpus samples | Raw-IFD table observed | Expected correction | +|---|---|---|---| +| ILCE-7RM4A | Compressed (4822) | 16 samples | One distortion warp | +| ILCE-9M2 | Compressed (3989) | 16 samples | None | +| ILCE-7M3 | Compressed, uncompressed | 16 samples | None | +| ILCE-7M4 | 14-bit, lossless L/M/S | 11 samples in the 14-bit file; 16 in L/M/S | None | +| ILCE-7RM2 | 12-bit uncompressed | 16 samples | None | +| DSC-RX100 | One | No raw-IFD table | None | +| DSC-RX100M3 | One | 11 samples | None | + +These are **model-boundary regression checks, not new geometric validation of those models**. In particular, +refusing the A7 III tables resolves the unverified corner warp identified in review. The lossless M/S +files are linear YCbCr and remain excluded independently of the model check. Header and full decode +must agree for every sample. Synthetic tests also verify both TIFF byte orders, exact model matching, +standard crop precedence, and that DNG export preserves an accepted warp without adding one to rejected +models. The existing independent Sony geometry tests continue to cover the enabled A7R IVA path. +Re-exporting the 24, 200 and 600 mm reference files after the model restriction produced byte-identical +corrected PNGs to the earlier validated exports. Isolated headless app checks showed the A7R IVA profile +controls present and the A7 III profile controls absent, with no notices and completed RAW renders. + +Before enabling another body, compare corrected and uncorrected renders against independent references +across multiple lenses/focal lengths and photographs with features near the edges and corners. Record +residuals and spatial coverage, and check crop/aspect and compression variants. Embedded JPEGs are +useful when the camera actually applied distortion correction; an Off JPEG cannot certify an On warp. +Prefer same-renderer Sony On/Off exports when the camera setting or geometry is ambiguous. Table-fit +error alone only checks our polynomial approximation, not whether the table's interpretation is correct. diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 0db8f6b2..4c4a9484 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -308,6 +308,16 @@ fn cmd_ci() -> Result<(), String> { /// CC0 raw samples from raw.pixls.us (each verified CC0 on the site; files matched by their published SHA-256). /// One per format / compression variant we decode or deliberately report as unsupported (preview only). const RAW_SAMPLES: &[(&str, &str)] = &[ + // ILCE-7RM4A / CC0-1.0; SHA-256 690c774f1d7bc1db3fa8c2489762743d8e7586e50b6f65d0b6a61cb467972c67 + ( + "arw-sony-a7rm4a-compressed.arw", + "https://raw.pixls.us/getfile.php/4822/nice/Sony%20-%20ILCE-7RM4A%20-%2014bit%2014bit%20compressed%20%283:2%29.ARW", + ), + // ILCE-9M2 / CC0-1.0; SHA-256 161c2a9da2b5f1e50be6117a0b4da0ce249660b5d7de13568d301c4034716b97 + ( + "arw-sony-a9m2-compressed.arw", + "https://raw.pixls.us/getfile.php/3989/nice/Sony%20-%20ILCE-9M2%20-%2014bit%2014bit%20compressed%20%283:2%29.ARW", + ), ( "arw-sony-a7m3-compressed.arw", "https://raw.pixls.us/getfile.php/2414/nice/Sony%20-%20ILCE-7M3%20-%2014bit%2014bit%20compressed%20%283:2%29.ARW",