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.
Currently, when getting the LZWStatus::NoProgress from the underlying
weezl library, tiff checks that no input bytes were read and no
output bytes were written. Although this is according to what the
weezl library mentions in their doc-comment, it's not what the real
result always looks like.
Our fuzzer found input that triggered exactly this case, where the LZW
reader is able to write some output byte before hitting the NoProgress
status.
This change removes the assert, as the resulting action is anyways to return
an error that can be handled by the caller.
This allows us to recombine the reader and other file data in a Decoder
with any IFD, whether that of an image or an extension IFD as indicated
by an IFD pointer such as Exif and GPS.
This assumed that a `read` would succeed for the full length, thus
interpreting in a subsequent call some data bits as a header.
For instance, consider 77 bytes of literal were left and a buffer of
1024 bytes was provided. Then the `read` call might return 60 which was
ignored and assumed to be 77. Thus the next 17 bytes of literal data
were instead interpreted as a header due to setting `self.count` to 0.
- Switch to streaming decompression via `flate2`. Aside from performance improvements and lower RAM consumption, this fixes a bug where `max_uncompressed_length` was precalculated for a single tile but then used as a hard limit on the whole data, failing to decompress any tiled images.
- Deprecate `InflateError` - this was used for wrapping `miniz_oxide` raw status, but `flate2` already reports them as `io::Error` instead. This makes errors from both Lzw and Deflate modes more consistent - they're all now just `io::Error`.
- Add support for new `Deflate` tag in addition to `OldDeflate` - format-wise it's same Zlib, and on practice decodes successfully with the same underlying code.
- Slightly simplify partial reads from the `Reader` by using the `std::io::copy` helper instead of doing it manually.
Fixes#131.