From 5a975d0d755d3b3c80947c01a895f8c4bd244779 Mon Sep 17 00:00:00 2001 From: radialmonster Date: Sat, 10 Oct 2026 17:20:31 -0400 Subject: [PATCH] LR-IMP-FORMATS: CR3 CRX quantization 44 (QP 168) decodes, continuing the step sequence Canon EOS R50 C-RAW files reach QP 168 (quant 44, step 102, measured about 100). The quantization step and the adaptive QP map range accept it; anything above stays rejected. Bumps RENDER_CACHE_VERSION since such files change from the embedded preview to a raw decode. --- crates/engine/src/media.rs | 2 +- crates/raw/src/vendor/crx.rs | 18 +++++++++++++----- docs/cr3.md | 2 +- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/crates/engine/src/media.rs b/crates/engine/src/media.rs index 6d5b548f1..d863060ec 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 = 42; +pub const RENDER_CACHE_VERSION: u64 = 43; /// 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/crx.rs b/crates/raw/src/vendor/crx.rs index 2d15627fa..64ca37071 100644 --- a/crates/raw/src/vendor/crx.rs +++ b/crates/raw/src/vendor/crx.rs @@ -330,8 +330,10 @@ fn tile_qp(sample: &[u8], tile: &Tile) -> Result> { fn quant_step(value: u8) -> Result { // Measured independently with an external decoder after modifying only the - // quantValue field: all forty values 4..43 follow this integer sequence. - if !(4..=43).contains(&value) { + // quantValue field: all forty values 4..43 follow this integer sequence. Value 44 (QP 168) + // continues it (step 102); it was checked against a measured step of about 100 on an EOS R50 + // C-RAW file. Nothing above 44 has been seen, so it stays rejected. + if !(4..=44).contains(&value) { return Err(RawError::Unsupported(format!("Canon CRX quantization value {value}"))); } let scale = [40i32, 45, 51, 57, 64, 72].get(usize::from(value % 6)).copied().ok_or_else(|| corrupt("quantization table index"))?; @@ -754,7 +756,7 @@ fn qp_map(src: &[u8], width: usize, height: usize) -> Result { let right = previous.get(x + 1).copied().unwrap_or(above); let symbol = bits.qp_rice(k)?; let value = median(left, above, corner).checked_add(signed(symbol)).ok_or_else(|| corrupt("QP prediction overflow"))?; - if !(131..=167).contains(&value) { + if !(131..=168).contains(&value) { return Err(RawError::Unsupported(format!("Canon CRX adaptive QP value {value}"))); } *current.get_mut(x).ok_or_else(|| corrupt("QP outside row"))? = value; @@ -968,11 +970,11 @@ mod tests { #[test] fn measured_quantization_and_allocation_limits() { - for (quant, expected) in [(4, 1), (9, 1), (10, 2), (16, 4), (22, 8), (26, 12), (32, 25), (36, 40), (41, 72), (42, 80), (43, 90)] { + for (quant, expected) in [(4, 1), (9, 1), (10, 2), (16, 4), (22, 8), (26, 12), (32, 25), (36, 40), (41, 72), (42, 80), (43, 90), (44, 102)] { assert_eq!(quant_step(quant).unwrap(), expected); } assert!(matches!(quant_step(3), Err(RawError::Unsupported(_)))); - assert!(matches!(quant_step(44), Err(RawError::Unsupported(_)))); + assert!(matches!(quant_step(45), Err(RawError::Unsupported(_)))); for (width, height) in [(20_000, 20_000), (65_538, 2), (2, 65_538)] { let mut config = coding(); config.width = width; @@ -1003,6 +1005,12 @@ mod tests { assert!(matches!(qp_map(&[0, 0, 1, 4, 0x94], 1, 4), Err(RawError::Unsupported(_)))); } + #[test] + fn qp_168_is_accepted_and_169_is_not() { + assert_eq!(qp_map(&[0, 0, 1, 0x50, 0x94], 1, 4).unwrap().data, [168; 4]); + assert!(matches!(qp_map(&[0, 0, 1, 0x52, 0x94], 1, 4), Err(RawError::Unsupported(_)))); + } + #[test] fn odd_height_qp_map_uses_its_last_row_alone() { // EOS R7 C-RAW: 4732 sensor rows give a 1183-row QP map, so the last diff --git a/docs/cr3.md b/docs/cr3.md index ac13d3fda..9b0cc4c72 100644 --- a/docs/cr3.md +++ b/docs/cr3.md @@ -26,7 +26,7 @@ decoded. CRX version `0x100`, four Bayer planes and `ff01`/`ff02`/`ff03` marker headers support lossless RAW and three-level C-RAW with horizontal tiles. Uniform -quantization values 4–43 are independently verified. Version `0x200` C-RAW +quantization values 4–43 are independently verified; 44 continues the sequence (step 102, checked against a measured step on an EOS R50 C-RAW file). Version `0x200` C-RAW supports the `ff11`/`ff12`/`ff13` marker family, a single 14-bit Bayer tile and its adaptive quantization map. C-RAW is quantized, so next to clipped highlights its reconstruction can overshoot the sensor range by a few quantization steps