Promoted extended-depth grayscale (9-15 bits -> u16, 17-31 bits -> u32) can carry bits above the declared depth after the horizontal predictor, which reconstructs prev+delta in the promoted storage width rather than modulo 2^BitsPerSample. This underflowed `max - v` in invert_colors (the image-rs/image-tiff#396 panic in debug, wrong pixel in release) and left junk in the high bits of the non-inverted output (#396 review, @197g: "clamp the channel values in the non-inverted color case"). An n-bit predictor delta is stored in exactly n bits, i.e. modulo 2^n, so reconstruction is defined modulo 2^n as well. Since 2^n divides the u16/u32 storage modulus, masking each promoted sample with (1<<n)-1 once after the predictor yields exactly the mod-2^n value -- including legitimate wrap-arounds -- and also bounds any out-of-range sample from malformed input. invert_colors' `max - v` is then always in range, superseding the targeted saturating_sub from #396. mask_samples_to_bit_depth runs in every integer decode branch of expand_chunk; outside the extended-depth unpacking branch it is a statically provable no-op (promoted layouts, declared < storage, occur only there -- sub-byte depths keep byte-granular storage and reject the horizontal predictor, standard depths have declared == storage, and the float predictor requires IEEEFP samples that are never promoted), so the branches cannot diverge. Adds exact-pixel regression tests on hand-built TIFFs: 12/20-bit wrap-around (plain and WhiteIsZero) and a 9-31-bit encode/decode identity sweep, alongside the two fuzzer-minimized panic seeds. A saturating clamp corrupts the wrap-around cases -- 12-bit [4095, 0, 1] is stored as deltas [4095, 1, 1] and masks back to [4095, 0, 1], but saturating produced [4095, 4095, 4095] -- so the tests lock the modular semantics.
image-tiff
TIFF decoding and encoding library in pure Rust
Supported
Features
- Baseline spec (other than formats and tags listed below as not supported)
- Multipage
- BigTIFF
- Incremental decoding
Formats
This table lists photometric interpretations and sample formats which are supported for encoding and decoding. The entries are ColorType variants for which sample bit depths are supported. Only samples where all bit depths are equal are currently supported. For example, RGB(8) means that the bit depth [8, 8, 8] is supported and will be interpreted as an 8 bit per channel RGB color type.
PhotometricInterpretation |
UINT Format | IEEEFP Format |
|---|---|---|
WhiteIsZero |
Gray(8|16|32|64) | Gray(32|64) |
BlackIsZero |
Gray(8|16|32|64) | Gray(32|64) |
RGB |
RGB(8|16|32|64), RGBA(8|16|32|64) | RGB(32|64), RGBA(32|64) |
RGBPalette |
||
Mask |
||
CMYK |
CMYK(8|16|32|64) | CMYK(32|64) |
CMYKA |
CMYKA(8) | |
YCbCr |
||
CIELab |
Compressions
| Decoding | Encoding | |
|---|---|---|
| None | ✓ | ✓ |
| LZW | ✓ | ✓ |
| Deflate | ✓ | ✓ |
| PackBits | ✓ | ✓ |
| Fax4 | ✓ | not yet |
| JPEG | ✓ | not yet |
| ZSTD | ✓ | not yet |
Not yet supported
Formats and interpretations not listed above or with empty entries are unsupported.
- Baseline tags
ExtraSamples
- Extension tags
Fuzzing
This crate uses cargo-fuzz in order to test the image parser.
After installing it with cargo install cargo-fuzz on a nightly rustc, the
fuzzing harness can be run with recommended settings using
cargo fuzz run decode_comprehensive -snone -- -dict=fuzz/tiff.dict -timeout=5.