Replace decode_image with decode_comprehensive, which exercises
per-IFD via a probe_directory helper:
- Tag iteration and value parsing for every tag
- SubIFD, EXIF, and GPS directory traversal via read_directory
- Image metadata (dimensions, colortype, color_map, chunk info)
- Layout queries (image_buffer_layout, image_chunk_buffer_layout)
- Per-chunk reads at first and last indices
- Full image decode via read_image
Three IFD traversal strategies:
- Linear iteration via next_directory (tolerates invalid
intermediate IFDs)
- Random-access via seek_to_directory (find_nth_ifd code path)
- Image-aware random-access via seek_to_image
Dictionary covers classic TIFF and BigTIFF byte order marks, and
common IFD tags in both LE and BE (image geometry, strips, tiles,
SubIFD, EXIF, GPS, ColorMap).
Strict resource limits (512 KB decode/intermediate, 64 KB IFD).
Bounded iteration (max 8 linear, max 4 seek).
`1usize << bits_per_sample` panics when bits_per_sample >= 64.
Use checked_shl to return a LimitsExceeded error instead.
Found by fuzzing decode_comprehensive with a BE TIFF containing
PhotometricInterpretation=RGBPalette and BitsPerSample=83.
Format-specific dictionary with LE/BE byte order marks, TIFF magic
number, and common IFD tag numbers as 2-byte LE and BE pairs.
Also add coverage to fuzz/.gitignore.
Fuzz testing found that crafted TIFF files with corrupt Group3 fax data
could cause a u16 addition overflow when accumulating run lengths. Use
checked_add and break on overflow (treating as corrupt data) instead of
panicking.
Change predict_f32/f16/f64 to take &mut [[u8; N]] output instead of
&mut [u8], making sample alignment a compile-time guarantee rather
than a silent runtime truncation. The caller uses as_chunks_mut at the
boundary, with a debug_assert that the remainder is empty.
This follows 197g's suggestion in #376 to move alignment validation
to the caller via typed slices, keeping the predictor functions clean
for future SIMD dispatch.
Two input validation fixes:
- vec_with_capacity now checks raw IFD value bytes against
ifd_value_size before allocating. Previously only the in-memory
Value representation was bounded (by decoding_buffer_size), so a
crafted IFD with a large count of small-typed values could allocate
based on raw byte count without limit checking. Also fix ASCII value
parsing to check ifd_value_size instead of decoding_buffer_size.
- LogLuv PackBits decoder now verifies that the expected number of
pixels was actually decoded per plane, returning UnexpectedEof if
the RLE stream ends short.
Horizontal predictor (one_byte_predict, all integer TIFFs with
Predictor=2): rewrite with explicit [u8; N] accumulator so LLVM unrolls
and keeps each channel in a register. For N=3 (RGB8), this replaces a
scalar 1-byte-per-iteration loop with a 2x-unrolled 3-register loop.
Float predictor (Predictor=3, f16/f32/f64): split deinterleave into
per-format functions with plane slicing up front; f32 batches 4 samples
through a u128 wide store to eliminate per-sample bounds checks. Thread
a reusable scratch buffer through expand_chunk to eliminate per-row
heap allocation.
Benchmarked on real 21MP photos and GeoTIFF DEMs (vs upstream main):
Strip RGB8 deflate+hpred: 253ms -> 224ms (-11%)
Strip RGB8 LZW: 405ms -> 370ms (-9%)
Tiled RGB8 deflate+hpred: 241ms -> 222ms (-8%)
Strip RGB f32 deflate+fpred: 726ms -> 527ms (-27%)
Gray f32 tiled 512: 290ms -> 166ms (-43%)
Copernicus DEM GeoTIFF: 239ms -> 152ms (-37%)
USGS DEM GeoTIFF: 284ms -> 198ms (-30%)
Instead all methods with mutable self will initialize it on-demand.
There are only few methods that take `&self` and can not do that. For
those that have no error/option return we panic but both of these could
panic previously already. This should be fine by documentation alone. In
particular, the image size can be queried safely.
The bounds `Read + Seek` are not representational so we may remove them
from the struct itself. This also allows us to use more precise bounds
for direct access to the inner stream, especially were that does not
seek.
Groups operations according to their use case.
Funnel users towards the `IfdDecoder` instead. That type does not have
a generic over the underlying reader. It also works with a directory of
your own choosing. So the duplicates that should go are those directly
on the decoder.
Most of this is slightly gratuitous for now, we have no need internally,
but especially since the enums are non-exhaustive it will probably be
appreciated that you can interact with the buffer at a byte-level.
Additionally we make the extra promise that all sample types correspond
to some supported SampleType tag value. This prepares using the same
buffer for a runtime version of `Encoder::write_image`.