- X-S20 lens distortion and vignetting scale fixes
- X-S20 / X-S10 / X-M5 spectral sensor mappings
- Add Samuel Li to contributors/people.toml
Co-Authored-By: Antigravity <noreply@google.com>
- address review concerns
- update RENDER_CACHE_VERSION value in docs/camera-preview-colour.md
- made sure diagonal scales are exact match
The maker note's key (0xa020) can be LONG8, so a value past i64::MAX
made `-1 * key` overflow: a panic in debug and test builds. Use a
wrapping multiply; only the low 32 bits are kept, so real files decode
the same.
The TIFF reader accepts LONG8 (type 16) StripByteCounts in classic
TIFF, so a file can declare strips of u64::MAX and 1 bytes. The ORF,
SRW, ARW and PEF decoders added the strip lengths with
Iterator::sum::<u64>(), which panics on overflow in debug builds. They
now add them with a saturating fold, as raw/src/lib.rs already does,
so such a file is refused or read as far as its data go.
The tests rewrite a synthetic file of each decoder into two strips
with LONG8 counts [u64::MAX, 1] (vendor::tests::huge_strip_counts);
PEF is checked for the Huffman-coded and the uncompressed path.
The Fujifilm entropy reader read each unary prefix one bit at a time and refilled
its accumulator a byte at a time. It now keeps up to 64 bits buffered and counts a
prefix's zeros with leading_zeros, failing exactly where reading bit by bit would
(escape limit, end of stream). A property test checks it against reading bit by bit.
Sensor arrays are unchanged bit for bit: the 17 reference arrays of
corpus_fujifilm_compressed_samples, plus decode checksums of 20 X-Trans CMOS 4 files.
Decode on an Apple M2: X-S10 lossless 265 -> 226 ms, X-H2 448 -> 378 ms,
GFX 100 762 -> 644 ms (docs/raf-compression.md).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
X-T20, X-T2, X-Pro2, X-E3, X-H1 and X100F have no measured colour matrix, so
As Shot showed on the relative scale. Their RAFs store the camera's own
neutral multipliers for Illuminant A and D65 (tag 0xf00c). Two points
give a line in (ln R/G, ln B/G); mired is linear along it, isotherms are
perpendicular, tint is scaled by the CIE locus spacing. Only the file's pair
and CIE colorimetry are used. Bodies with matrices are unchanged. The look
stays the per-file JPEG fit.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014ti8KemZbjE2ZgHgV1q1BM
On wasm32 a PRVW/THMB box or JPEG length near u32::MAX wrapped j + len and
start + size, so both bounds checks passed and the slice panicked (abort in
the web build). Use checked_add and bytes.get, and add a regression test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014ti8KemZbjE2ZgHgV1q1BM
The default camera look is fitted to the raw's embedded JPEG, decoded in the
colour space embedded_preview_color_space reports. That function parsed the
file as TIFF, which fails on a CR3 (ISO BMFF), so a CR3 shot in Adobe RGB was
read as sRGB and the look was fitted to a desaturated target. A CR2 shot in
Adobe RGB parses, but its EXIF says ColorSpace Uncalibrated with InteropIndex
"R98" rather than Adobe RGB's "R03", so it was read as sRGB too.
Read the Canon maker-note ColorSpace (0x00b4: 1 = sRGB, 2 = Adobe RGB): for a
CR3 from its CMT3 block, else an EXIF ColorSpace of sRGB from CMT2; for a CR2
from the EXIF maker note, before the EXIF fallback.
LOOK_VERSION (13) and RENDER_CACHE_VERSION (71) are bumped so cached
smart previews and thumbnails are rebuilt with the new look. Smart previews of
preview-only raws now follow LOOK_VERSION too, since their proxy is the
embedded JPEG read in the declared colour space.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014ti8KemZbjE2ZgHgV1q1BM
The existing synthetic lossy RGB fixture tiles are re-declared grey by a
header edit (colour space RGB to Grey, dropping the 6-bit primaries field
a grey encoding does not carry, per the ColourEncoding bundle). Their XYB
frame data is unchanged, so they decode as one grey plane whose rendered
channels differ. The test checks decode against the gradient's BT.2100
luminance in the linear and sRGB curves.
Refs #275
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014ti8KemZbjE2ZgHgV1q1BM
Lightroom Classic's HDR and panorama merges of monochrome raws store a
one-plane LinearRaw image as lossy (XYB) JPEG XL tiles whose codestream
declares a grey colour encoding (16-bit float with a linear curve, or
16-bit integer with the sRGB curve). Those were refused as "lossy JPEG XL
tile with a single colour plane", so the files opened preview-only.
A lossy grey tile is still coded as three XYB channels. It is now
restored like the RGB case: jxl-oxide renders linear sRGB (never the
declared encoding, which would clip), the single plane is the luminance
(CIE Y, D65) of that, then the declared transfer curve is applied.
One plane must be declared grey and three RGB; HDR curves, other white
points and intensity targets above 255 stay unsupported.
RENDER_CACHE_VERSION 70 -> 71: files that showed their embedded preview
before render from raw data now, under the same content key.
Refs #275
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014ti8KemZbjE2ZgHgV1q1BM
- RawImage::develop demosaics only the default crop with AHD when there
is no OpcodeList3 (ahd_window), instead of demosaicing the margin too
and then copying the crop out of the full picture on one thread.
- Gpu::upload writes through queue.write_buffer_with and copies into
the staging buffer in parallel parts. create_buffer_init zeroed a
staging buffer and then copied over it, two single-threaded passes
over a full-size photo's ~290 MB.
- finish_and_read copies the mapped readback out in parallel parts (one
thread read the 91 MB of a 24 MP export at ~2 GB/s, ~45 ms).
Output is byte-identical (ahd_window_is_the_crop_of_the_whole,
uploads_read_back_exactly).
Export of a 24 MP ARW to full-size JPEG (examples/export_bench, 75
interleaved runs each on a shared 16-thread Ryzen):
main 802 ms median / 614 ms min -> this branch 647 / 525 (-19.3% / -14.5%).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An oversized CR3 candidate is skipped without ending the search, repeated child IFD references count once toward the pending cap, deflated chunks lay their rows out as the unpacker reads them (16-bit words, 32-bit word alignment), the tile grid's nominal size is checked against the image before anything is decoded, chunk grouping saturates, and the XMP flattened-key budget is document-wide. The JPEG-header test now parses a real header.
XMP flattening, sparse-focus EXIF decode (plus the maker-note and TIFF value
budgets), lossy-DNG chunk staging, panorama stitch frames, the reference ONNX
executor's tensors and the denoise product reader all now reject or cap what a
malformed file can make them allocate: each result is checked before the
memory behind it is reserved, every regression carrying a test that fails
without its bound.
Sparse walkers, truncated MEF/lossy-JPEG strips, embedded JPEG sizes, the
CR3 preview marker walk, OpcodeList3 full-image passes and caller-supplied
Deflate tile edges each let a small file or one bad argument multiply
allocations or CPU work thousands of times. Each path now is bounded,
every fix with a regression test that failed (or blew up) before it, plus
a tiff::image single_chunk helper so preview extraction never builds a
descriptor vector for a hostile multi-strip layout.
The APS-C knot spacing comes from the default crop, so only the measured 3:2 shape gets a spacing
(review: CodeRabbit on #2732).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The ARW one-byte and packed 12-bit strips and the SRW word and packed
12-bit strips sized the frame from the declared StripByteCounts, while
chunk_bytes clamps the strip to the file. A file of a few hundred bytes that
declares a large frame made the full decode allocate it (up to 2 GiB) and
unpack zeros. Both now check the bytes present against the declared strip
first and return Corrupt, as the ORF block path and RAF already did.