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`.
log16_to_y() uses f32::exp() which is not required to be correctly
rounded by IEEE 754. Apple libm on arm64 differs by 1 ULP from glibc
on x86_64, producing different CRC32 checksums for the decoded output.
Same issue as off_luv32.tif which was already demoted in commit 617dc6e.
A deterministic LUT-based approach could fix both in a follow-up.
Three 16x16 bilevel fax3 test images covering the key Group 3 variants:
- seq-1c-1b-fax3: basic G3, no fill bits, MSB-first fill order
- seq-1c-1b-fax3-fillbits: G3 with T4Options fill bits (byte-aligned EOLs)
- seq-1c-1b-fax3-lsb: G3 with FillOrder=2 (LSB-first)
All use MinIsWhite photometric with inverted sequential pattern, producing
the same decoded CRC32 hash (71f6a21a) as the uncompressed 1-bit images.
Cross-validated: libtiff writes the images, both libtiff and image-tiff
read them back and agree on the decoded data.
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.
Upgrade the libtiffpic test harness from decode-only smoke tests to
full output verification using CRC32 reference hashes. 50 of 61 images
now have hash-verified decoded output; the remaining 11 use unsupported
formats (YCbCr subsampling, Fax3, etc.) and remain as decode-only tests.
Uses read_image_to_buffer (the non-deprecated API) with ByteOrder::convert
for cross-platform LE normalization.
Remove the 13 individual depth tests from decode_images.rs — they tested
the same libtiffpic flower images that are now hash-verified here.
Address PR review feedback:
- Remove benches/decode.rs which referenced non-existent gen_bench_tiff.c
- Simplify compute_tiff_hash: replace 66-line match block with
as_buffer().as_bytes() + ByteOrder::convert for LE normalization
- Use TIFF Type repr(u16) as hash discriminant instead of ad-hoc u8
- Rename all 74 seq test images to match updated hashes
Measures decode throughput for 1024x1024 gray images at various bit
depths (3-24 bit). Requires pre-generated images in /tmp/bench-tiff/
(see stderr instructions for generation).
- compute_tiff_hash now panics on unhandled ColorType variants instead
of silently using a fallback discriminant
- print_seq_hashes correctly identifies existing 8-char hex hash suffix
vs non-hash filename segments
Move 74 seq-*.tiff test images to tests/images/seq/ and embed a CRC32
hash of the full decoded output (dimensions, color type, sample data)
as the last filename segment. Replace 75 individual test functions with
one generic verify_all_seq_images test that decodes each file and
validates its hash against the filename.
This catches byte-order, bit-alignment, stride, padding, and value
errors that sum-based tests would miss (e.g., swapped pixels preserve
the sum but change the hash). Adding new test images only requires
dropping a correctly-named file in seq/ — no test code changes needed.
Replace Python-generated test TIFFs with libtiff-generated ones to
eliminate circular validation. The C program (gen_seq_tiff.c) uses
libtiff's TIFFWriteScanline with MSB-first packed bit streams.
Cross-validated: tifffile independently confirms all 27 images match
the original Python-generated versions byte-for-byte. tiffinfo validates
all IFD structures. All 27 image-tiff decode tests pass unchanged.