LR-IMP-FORMATS: EOS R6 Mark III as-shot white balance (ColorData version 66)

Version 66 keeps version 64's as-shot white balance and black level
offsets; its word 661 is below where the sensor clips, so the white
level stays measured from the data. Checked on 33 R6 Mark III files
from 12 shoots: all now open with their as-shot white balance, black
levels match the masked columns.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017JKdR4oZ3jRSPPwMLLTa2U
This commit is contained in:
Bobby Jose
2026-10-11 14:13:06 -04:00
co-authored by Claude
parent 76b5377868
commit 73098bf5a3
3 changed files with 29 additions and 7 deletions
+18 -5
View File
@@ -135,13 +135,21 @@ struct CameraLevels {
/// Canon's versioned word arrays. Offsets are word indices from the published tag tables, not
/// byte offsets. The four as-shot WB values are R, G, G, B, independent of the CFA layout.
///
/// Version 66 (EOS R6 Mark III) keeps version 64's as-shot WB and black level offsets, measured on
/// 33 files from 12 shoots: the as-shot quadruple is followed by its colour temperature and then
/// the fixed preset table (Daylight 5200 K, Shade 7000 K, ...), and the black levels equal the
/// masked-column levels in every file. Its word 661 (14351 or 13995) is not the clipping point:
/// highlights pile up at 16383 and run smoothly through the range between, so the white level is
/// measured from the data instead.
fn camera_levels(maker: Option<&Ifd>, bits: u8) -> CameraLevels {
let Some(values) = maker.and_then(|m| m.u64s(COLOR_DATA)) else { return CameraLevels::default() };
let offsets = match values.first().copied() {
Some(16..=19) => (71, 329, 797),
Some(32 | 33) => (85, 343, 811),
Some(34 | 48) => (105, 363, 641),
Some(64 | 65) => (105, 383, 661),
Some(16..=19) => (71, 329, Some(797)),
Some(32 | 33) => (85, 343, Some(811)),
Some(34 | 48) => (105, 363, Some(641)),
Some(64 | 65) => (105, 383, Some(661)),
Some(66) => (105, 383, None),
_ => return CameraLevels::default(),
};
let full = ((1u32 << u32::from(bits.clamp(1, 16))) - 1) as f32;
@@ -160,7 +168,7 @@ fn camera_levels(maker: Option<&Ifd>, bits: u8) -> CameraLevels {
});
let black = quadruple(offsets.1).filter(|v| v.iter().any(|&b| b > 0.0) && v.iter().all(|&b| b >= 0.0 && b < full * 0.5));
let max_black = black.map(|v| v.into_iter().fold(0.0f32, f32::max)).unwrap_or(0.0);
let white = values.get(offsets.2).map(|&v| v as f32).filter(|&v| v > max_black && v >= full * 0.5 && v <= full);
let white = offsets.2.and_then(|at| values.get(at)).map(|&v| v as f32).filter(|&v| v > max_black && v >= full * 0.5 && v <= full);
CameraLevels { wb, black, white }
}
@@ -526,6 +534,11 @@ mod tests {
assert_eq!(levels.black, Some([100.0, 101.0, 102.0, 103.0]));
assert_eq!(levels.white, Some(15000.0));
}
// Version 66 (EOS R6 Mark III): as-shot WB and black from the table, white from the data.
let levels = camera_levels(Some(&maker(66, 105, 383, 661)), 14);
assert_eq!(levels.wb, Some([2.0, 1.0, 1.5]));
assert_eq!(levels.black, Some([100.0, 101.0, 102.0, 103.0]));
assert_eq!(levels.white, None);
assert!(camera_levels(Some(&maker(255, 71, 329, 797)), 14).wb.is_none());
assert!(camera_levels(None, 14).black.is_none());
}
+8
View File
@@ -5,6 +5,14 @@ The CR3 adapter reads the CRX Bayer track, `CMP1` compression descriptor,
black/white levels and the default crop come from the file. A Dual Pixel delta
track is never substituted for the primary Bayer track.
The EOS R6 Mark III writes `ColorData` version 66, which keeps version 64's as-shot white balance (word 105) and
black level (word 383) offsets. Measured on 33 files from 12 shoots (ISO 100–12800, 1/1250 s–25 s): the as-shot
R, G, G, B quadruple is followed by its colour temperature (3271–7424 K across the shoots) and then the fixed
preset table (Daylight 5200 K, Shade 7000 K, Cloudy 6000 K, Tungsten 3200 K, ...), and the black levels equal the
masked-column levels in every file (512 at low ISO, 2047–2048 above). Word 661 holds 14351 or 13995, but it is not
the clipping point: highlights pile up at 16383 and run smoothly through the range above 14351, so version 66
takes its white level from the data.
The in-camera aspect ratio (3:2, 4:3, 16:9, 1:1) is not applied to the sensor data. The maker note's
`AspectInfo` (tag `0x009a`: aspect code, cropped width, height, left, top) gives the shot's rectangle inside the
recommended crop, and the decoder uses it as the default crop, so the developed image has the shape the photographer
File diff suppressed because one or more lines are too long