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`.
Fix two CI failures on i686 and powerpc:
1. chunk_col_stride overflow: RowsPerStrip = u32::MAX causes
data_row_bytes * chunk_height to exceed 32-bit usize. Clamp
the chunk height to the image height before computing stride
values, since a chunk never contributes more rows than the
image contains. Applied to both readout_for_size() and
set_row_stride().
2. off_luv32.tif hash mismatch: the LogLuv decoder produces
platform-dependent output (different hashes on x86_64, i686,
and powerpc), so demote to smoke test.
Add YCbCrUpsamplingReader that converts chroma-subsampled YCbCr block
data to full-resolution 3-bytes-per-pixel output. Each block of h×v
pixels stores h*v Y samples plus one shared Cb and Cr sample; the
reader expands these into per-pixel (Y, Cb, Cr) triples.
This enables decoding YCbCr TIFFs with any supported compression
method (uncompressed, LZW, Deflate, etc.), not just JPEG. Two more
libtiffpic test images now decode with verified hashes:
- dscf0013.tif (uncompressed YCbCr 2,1 subsampling)
- ycbcr-cat.tif (LZW-compressed YCbCr 2,2 subsampling)
The remaining two YCbCr test images (smallliz.tif, zackthecat.tif)
use Old JPEG compression (type 6) which is a separate unsupported
feature.
Add Group3Reader for decoding CCITT Group 3 (T.4) 1D compressed TIFF
images. The fax crate's Group3Decoder has a bug where new() verifies
but doesn't consume the initial EOL marker, causing its lookup tables
to destructively consume bits from the EOL code. We work around this
by using the crate's lower-level white::decode / black::decode
functions and handling EOL markers ourselves.
Also adds FillOrder tag (266) support: FillOrder=2 (LSB-to-MSB,
common in fax images) is handled by reversing bits in each byte
before decoding. This applies to both Group 3 and Group 4 readers.
Both libtiffpic fax test images (fax2d.tif, g3test.tif) now decode
correctly with verified CRC32 hashes.
Previously, sub-byte bit depths (1-7 bits per sample) were only
accepted for Gray and Multiband color types. This extends the
validation to accept all color types (RGB, RGBA, CMYK, CMYKA, YCbCr)
at sub-byte depths.
The data format is unchanged: sub-byte samples remain packed MSB-first
in the output buffer, exactly as for grayscale. Predictors are still
rejected for sub-byte data.
Tested via libtiffpic flower-rgb-contig-02/04 and flower-rgb-planar-02/04.
TIFF files can store samples at non-standard bit depths (e.g. 10, 12,
14, 24 bits) packed tightly in a continuous bit stream. Previously
these were rejected as unsupported color types.
This adds:
- MSB-first bit extraction from packed byte streams (unpack_bits,
extract_bits_msb) to expand N-bit samples to standard-width output
(9-16 bit → u16, 17-32 bit → u32)
- Output buffer sizing using the promoted sample width instead of the
packed bit depth (output_bits_per_sample field in ReadoutLayout)
- A new code path in expand_chunk that reads packed rows, unpacks to
standard-width samples, then applies predictor and color inversion
- Horizontal predictor support for extended depths
Tested with 12-bit grayscale and RGB images (existing test suite),
plus all libtiffpic depth test images (10/12/14/24-bit in gray, RGB
contig, and RGB planar configurations).
Read the ColorMap tag (320) for RGBPalette images and return raw
palette indices as the decoded data with ColorType::Palette(n).
Changes:
- Read and validate ColorMap tag during IFD parsing (3 * 2^bps entries)
- Return ColorType::Palette from colortype() for RGBPalette images
- Accept Palette in expand_chunk validation (standard + sub-byte depths)
- Expose color_map() method on Decoder for palette lookup
- Add tests for 1-bit, 4-bit, 8-bit palette images with checksums
- Add test verifying ColorMap accessibility and structure
The decoder returns raw palette indices; consumers apply the lookup
using the color map returned by Decoder::color_map().