Keeps upstream's ColorData 66 handling (white level from the data); recounts coverage (259 verified, 919 unverified, 232 preview); drops the duplicate raw-parity rows; notes high-QP R6 Mark II C-RAW and unverified R7 C-RAW. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
13 KiB
Canon CR3 decoding
The CR3 adapter reads the CRX Bayer track, CMP1 compression descriptor,
CDI1/IAD1 sensor geometry and timed CTMD maker notes. White balance,
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
framed (PowerShot SX70 HS, EOS 250D, PowerShot G5 X Mark II, EOS M6 Mark II and EOS M50 samples). A rectangle that
is missing or does not fit inside the crop is ignored. The embedded JPEG still shows the whole frame.
The EOS R5 Mark II's APS-C crop-mode files (5376 x 3574) state an active area that starts in dark columns (132..~256 sit at the black level) and stops ~130 columns short of the image, while their recommended crop (5088 x 3392, the camera JPEG's size) lies on the image. When the recommended crop doesn't fit inside the stated active area, it is used as the valid area: on raw.pixls.us 7883/7884 the render matches the camera JPEG (high-pass correlation 0.99, was -0.13 with a black band on the left). No other of the 110 CC0 CR3s changes.
The decoder is independent Rust code shared by desktop, CLI and WebAssembly;
it has no operating-system imaging dependency or external decoder. Its sources
are the public prose descriptions and patents cited in
crates/raw/src/vendor/crx.rs. Unsupported coding variants retain the existing
embedded-JPEG fallback and its visible warning when a JPEG is present. So does
any other CR3 decode error (a corrupt file, or a body whose CRX stream the
decoder misreads): every CR3 opened from its embedded JPEG before this decoder,
and not every body's variants are known, so a CR3 never fails to import or load
where a preview exists. HEVC-only preview tracks are recognized but are not
decoded.
Verified sensor decoding
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; 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
(up to 36 codes above 16383 in 17 CC0 files); those samples are clamped to the
CMP1 bit depth. A lossless sample outside the bit depth is still corrupt data.
Other unverified coding variants return explicit unsupported errors. Header probing validates the same coding variants and
tile/plane layout as full decoding.
crates/raw/tests/cr3_corpus.rs checks every full-sensor sample against a
black-box reference, before cropping or colour processing. Public CC0 files
are downloaded by cargo xtask corpus --download and kept outside version
control. The exact inputs and reference hashes are:
| File | Input SHA-256 | Sensor FNV-1a (u16 little-endian) |
|---|---|---|
| Canon M50 RAW, raw.pixls.us 4657 | 1ba9ad6b51b315b88820eb0fe5fd7f52bc136ba37b1a5ca76825ebfe25b18058 |
62261f0ba81cfcd2 |
| Canon M50 C-RAW, raw.pixls.us 2663 | 15384b775867ec4c42b11882837f1e368cedc0561832ffab271221e6bb80be4c |
9aacbfe66f505e1b |
| Canon R100 RAW, raw.pixls.us 7896 | 6d83217d58a5e6d2dabcf470430e91a16676b60531c025b81fa7453fc74b0521 |
cbe5299ab9c52630 |
| Canon R100 C-RAW, raw.pixls.us 7897 | 0b66842b2fe00329ebc05fe8ab9357ddbbe1a0a2d2e69a2893102de7fdf5c942 |
341c706b37c38bbf |
| Canon R8 RAW, raw.pixls.us 6585 | 7d5c6dbb11ff7e6ee58715d90a20f5d801e8b103f4711ff6ea329ceafcef254c |
63c1c6d8d1eb2312 |
| Canon R8 C-RAW, raw.pixls.us 6587 | df33cf394573645ce03dea1b2e9f0b5cc2b7734e9e3391ee7114151414cf2812 |
f836a663a795a375 |
The EOS R5 Mark II files (raw.pixls.us 7881–7884, CC0) are pinned to LightCraft's own decode; no external decoder was run on them:
| File | Input SHA-256 | Sensor FNV-1a (u16 little-endian) |
|---|---|---|
| Canon R5 Mark II RAW, raw.pixls.us 7881 | d6e6aa3b493ea8936dff0efb0cc17c87471437e7317bce37c8c82ba08b9cab7b |
ff716fb30248496f |
| Canon R5 Mark II C-RAW, raw.pixls.us 7882 | b3c08a5c7a97299dfd58896a12b939e7b43ec78682761812524825e6e5d087be |
af32b45357b534e5 |
| Canon R5 Mark II crop-mode RAW, raw.pixls.us 7883 | 9f88c0ff012924193b7f4497559d133dfe626766d600b5d67689156721739ead |
31cf630afe868958 |
| Canon R5 Mark II crop-mode C-RAW, raw.pixls.us 7884 | 6a597497776a6abbfab8fcf12d0086ac7a3f8afea2879d61accf6bbf48d357d7 |
4721605b20339332 |
Reference pixels were observed from the DNGLab 0.8.0 macOS arm64 release
binary; no decoder source was read or copied, and it is not shipped or linked.
The release ZIP SHA-256 was
ee70805cb60f18d5ed62548c4e595f6cee0a33407e15358eb1766a46afc1c16e.
All six sensor comparisons pass. R8 HEVC preview tracks are distinguished from
the primary Bayer RAW; the HEVC preview itself is not decoded. Missing corpus
files are reported as skipped by the tests.
The version 0x200 QP predictor and entropy framing were checked independently.
Controlled input mutations establish floor averaging of paired QP rows before
the nonlinear integer table, then multiplication, division by eight, addition
of the subband base and a minimum step of one. QP values 131–167 are verified;
unmeasured values are rejected.
The QP map is coded as follows (established on the EOS R5 Mark II). Its first symbol is a 32-bit
big-endian literal, mapped like every other symbol and added to a prediction of zero. Every later
symbol is Rice coded with a limited-length escape: a quotient of 23 or more is written as 23 zeros,
the terminating 1 and the symbol in 8 bits. This is the escape layout of ITU-T T.87 for a 32-bit
code-length limit and 8-bit symbols, without that standard's minus-one offset. In the R8 maps only
the first symbol needs more than 15 leading zeros, and its bits read the same whether taken as a
32-bit literal or as an escape after 16 zeros with a 16-bit literal (the rule LightCraft used before).
The R7 maps decoded to their exact ends under that rule, which a later long prefix would have broken;
no R7 file was at hand to recheck them. R5 Mark II maps (QP 150–162) also jump by up to 12 where the Rice parameter is 0. That
gives 18- to 22-zero prefixes, which the 16-zero rule misread as escapes ("Canon CRX adaptive QP value
−21087" and other out-of-range values; the file then opened as a preview). It also gives real escapes,
symbols 23 and 24. Escapes were seen only where the Rice parameter is 0 (in 100 of 612 maps; all 351 inspected, up to 10 per map); at a larger
parameter they would use the same layout, which is unobserved. With this coding the QP maps of all
33,665 R5 Mark II C-RAW files in a private library (version 0x200, 8480 × 5650) decode to the exact
end of their entropy streams, with values 150–162. So do the reduced-size tracks of 306 of those
files, the two CC0 R5 Mark II C-RAW files above and the R8 C-RAW fixture; all six fixture hashes are
unchanged. Under the 16-zero rule, 174 of those 306 files (57%) failed and opened as previews, all
with out-of-range values (none at QP 168). The two CC0 C-RAW files are low-ISO shots whose maps need no
long prefix: they decoded under the old rule too, so the escape is covered by synthetic unit tests in
crx.rs rather than by a public file. Render cache version 43 redraws thumbnails of files whose decode
changed; photos imported as preview-only switch over with Photo ▸ Reload from Disk.
The R5 Mark II sensor has 5650 rows (3574 in crop mode), so its Bayer planes have odd heights (2825, 1787). Lossless planes decode as before. In C-RAW every wavelet level has an odd height (2825 → 1413 → 707), and each subband's entropy stream ends exactly where the subband geometry says. The synthesis uses the same boundary rules that are verified on the R8's odd lengths. Decoded rows show no step down to the last sensor row, and the developed images match the camera's embedded JPEGs at 100 %.
An odd QP-map height (EOS R7: 4732 sensor rows give 1183 QP rows) leaves the last row of the second-level high-frequency bands without a partner row; that row's QP is used on its own. The odd-height map decodes to the exact end of its entropy stream. This rule is not yet checked against a black-box reference: it was checked on paired R7 RAW and C-RAW captures of the same scene (ISO 100 and 6400), whose developed images match visually and whose sensor means agree within 0.1% at ISO 6400. Files with even-height maps never reach this rule, so the six verified fixtures are unchanged. Partial-subband and rounded-plane coding, vertical C-RAW tiling and adaptive low-frequency gains remain unsupported. The decoder bounds image dimensions, sample count, tile count, marker headers and temporary allocations. The full sensor buffer is allocated only after the first plane decodes, and plane buffers grow row by row, so a small crafted file fails before reserving memory for the image it claims.
Further bodies: one CC0 file each
All 110 CR3 files on raw.pixls.us (2026-10-12, every one CC0, 31 folders) decode from sensor data; none
falls back to the preview, so none of them uses a CRX variant the decoder refuses (vertical C-RAW tiles,
partial-subband or rounded-plane coding, multi-tile 0x200, adaptive low-frequency gains) or a CR3
sRAW / mRAW. HEIF stills are separate .HIF files, not CR3. One file per body is pinned in
canon_cr3_bodies_decode_like_the_camera_jpeg (crates/raw/tests/cr3_corpus.rs; URLs and SHA-256 in
RAW_SAMPLES, xtask/src/main.rs): EOS-1D X Mark III, 90D, 250D, 850D, M200, M50 Mark II, M6 Mark II,
R, R3, R5, R6, R6 Mark II, R6 Mark III, R7, R10, RP, R50, R50 V and PowerShot G5 X Mark II, G7 X Mark III,
SX70 HS and V1, C-RAW where the body has a CC0 C-RAW sample. No external decoder was run: the sensor
hashes pin LightCraft's own decode, and the check is black-box against the camera:
- the green sites of the active area, on a 24 × 16 grid, follow the camera's own embedded JPEG (gamma-encoded Pearson r 0.93–0.99; 0.83 for the M6 Mark II and 0.71 for the G7 X Mark III, whose JPEGs are strongly distortion-corrected; a misread stream gives r near 0);
- each render was inspected at 100 % for tile seams, band or quantiser artefacts (none);
- where raw.pixls.us has RAW and C-RAW shots of one scene, their black-subtracted sensor means correlate on 16 × 16 blocks at 0.99–0.995 (R, R6, 250D, R50, G5 X Mark II, R5 crop mode) and 0.97 (RP); the two R5 full-frame shots correlate at only 0.83, though each follows its own camera JPEG at r 0.98, so the shots differ, not the decodes. The shots differ in exposure, so the means are not compared.
The EOS R6 Mark III (ColorData version 66) reads its as-shot white balance and black level as described at
the top of this page; its white level comes from the data.
Not every C-RAW file of a verified body decodes: the decoder accepts adaptive QP values 131–168 only, and a user reports 3 of 40 EOS R6 Mark II C-RAW files with QP 172 or 175 ("Canon CRX adaptive QP value 172"), which open as their preview. EOS R7 C-RAW (the odd-height QP map above) has only been checked on six paired RAW / C-RAW captures; the pinned R7 file is lossless, so R7 C-RAW still has no black-box reference.
Colour limits
CR3 carries as-shot white balance but no measured camera matrix is imported
here. The existing guarded embedded-JPEG fitting path now includes CR3, with
both proxies oriented identically before gathering training pairs. A bounded
crop/translation registration of the JPEG is confirmed on held-out edge
directions before fitting colour; the existing held-out colour acceptance gates
are unchanged. No lens distortion correction is applied (LightCraft has no lens
profiles of its own yet), so lenses whose camera JPEG is strongly
distortion-corrected may fail the gates and keep the neutral matrix. Render
cache version 13 invalidates earlier thumbnails. All developed pixels still come
from the RAW sensor; a rejected colour fit retains the documented neutral
matrix fallback. See camera-preview-colour.md for the limits of this estimate.