Commit Graph
586 Commits
Author SHA1 Message Date
Aurelia Molzer e27f6fe00d Merge pull request #401 from jacovdbergh/fix-tiled-border-extra-samples
Fix: clip border tile rows when discarding extra samples
2026-08-10 18:46:22 +00:00
aarshivv c27fd94e1d feat: decode old-style (pre-TIFF-6.0) LZW streams 2026-07-25 12:45:40 +05:30
aarshivv 3436a2222b feat: support 16-bit CIELab decoding 2026-07-24 13:40:56 +05:30
aarshivv d0c9198016 Merge remote-tracking branch 'origin/main' into palette-cielab-transparency-lzw 2026-07-23 00:52:37 +05:30
aarshivv 8f83f0fa82 chore: address review feedback on CIELab/TransparencyMask PR 2026-07-23 00:39:19 +05:30
Sergey "Shnatsel" Davidoff f3f9ff1244 Merge pull request #398 from Shnatsel/safe-rust-zstd-2
Add safe Rust zstd decoding backend
2026-07-20 00:33:05 +01:00
Lilith River 35c14f1e3c decoder: reconstruct extended-depth samples modulo declared bit depth
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.
2026-07-18 16:04:42 -06:00
florianandClaude Fable 5 6276defac6 Fix clippy 1.97 useless_borrows_in_formatting lint
Rust 1.97's new clippy lint flags the redundant `&` on `self.offset`
in Entry's Debug impl. CI runs clippy with -D warnings in every job,
so this single pre-existing lint currently fails all PR pipelines.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 16:29:05 +02:00
jacovdbergh@gmail.com dee0c2fb11 fix: clip border tile rows when discarding extra samples
Tiled images with a width that is not a multiple of the tile width
wrote the border tiles' horizontal padding into the next output row.
Fixes #400.
2026-07-08 08:16:27 +02:00
陈启 4a9e1d152b fix: resolve comments 2026-07-03 18:49:16 +08:00
陈启 1c6001ea5d fix: resolve comments 2026-07-03 17:19:48 +08:00
Sergey "Shnatsel" Davidoff c0043c1a84 Use zrip-decode crate instead of ruzstd 2026-06-27 12:47:51 +01:00
陈启 c7a1176381 fix: eliminate clippy errors 2026-06-22 14:13:53 +08:00
陈启 0309349e1b fix: add lenient mode for tiff image decoder 2026-06-22 10:59:36 +08:00
Dustin Thomson f11a1c5393 Add PhotometricInterpretation unknown tag support
Provides a mechanism to support private or extension
photometric interpretations (such as CFA)
2026-05-28 08:48:12 -06:00
A. Molzer 5947d3b72e Make newer clippy happy 2026-05-16 00:46:02 +02:00
aarshivv 30e04f3381 chore: drop palette and LZW, add tests for CIELab and TransparencyMask 2026-04-11 20:48:40 +05:30
aarshivv dfd0191403 support palette expansion, CIE L*a*b*, transparency mask, and lzw end marker tolerance
Add expand_palette_to_rgb() for converting palette indices to RGB using the color map (1/2/4/8/16-bit indices, row-aligned sub-byte).

Accept CIE L*a*b* (photometric=8) as Lab(8) for 8-bit 3-channel images. Re-bias a/b from signed to unsigned during expand_chunk so callers handle CIELab and ICCLab the same way.

Treat TransparencyMask as grayscale instead of erroring.

Handle missing LZW end-of-information code gracefully when output was already produced.
2026-04-10 23:02:15 +05:30
Lilith River 26b18c5184 fix: prevent shift overflow panic in ColorMap size validation
`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.
2026-04-09 04:46:05 -06:00
Lilith River d7961a2fcf fix: prevent u16 overflow panic in Group3 fax run-length decode
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.
2026-04-07 17:45:59 -06:00
Aurelia Molzer 2166fd591a Merge pull request #380 from lilith/fix/ifd-limits-and-packbits
fix: enforce ifd_value_size limits and detect PackBits under-decode
2026-04-07 18:13:40 +00:00
Lilith River 149492ca6e refactor: use typed slices for float predictor output
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.
2026-04-07 03:03:23 -06:00
Lilith River e4c3c1fcfd fix: enforce ifd_value_size limits and detect PackBits under-decode
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.
2026-04-07 02:53:43 -06:00
Lilith River fa8a911f79 perf: optimize horizontal and float predictor decode
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%)
2026-04-06 08:49:00 -06:00
Aurelia Molzer a42b16ab72 Merge pull request #377 from image-rs/change-reader-of-decoder
Transfer decoder to new reader
2026-04-05 08:05:41 +00:00
A. Molzer 399469f512 Cleanup dbg! left in error path 2026-04-04 23:13:07 +02:00
A. Molzer 740c5cdeea Transfer decoder to new reader 2026-04-04 15:45:31 +02:00
A. Molzer 97d357d446 Prepare ImageDecoder split, avoid as_mut|unwrap 2026-04-04 10:20:10 +02:00
A. Molzer 61867b46f9 Make decoder's Image an option
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.
2026-04-04 10:20:08 +02:00
Aurelia Molzer 0f1f075abb Merge pull request #372 from image-rs/deprecate-and-hide-tag-reading
Deprecate and hide direct tag reading
2026-04-04 08:13:45 +00:00
A. Molzer 52b9cc7a19 Rename EntryBytesReader module 2026-03-31 18:35:19 +02:00
A. Molzer 006f0b0fa4 Directly implement Seek::stream_position 2026-03-31 18:34:16 +02:00
A. Molzer 5b8a2832c3 Add design note 2026-03-31 18:34:15 +02:00
A. Molzer f93a6380e6 Extra utils for reading tags 2026-03-31 18:34:14 +02:00
A. Molzer 8a1b65fe68 Expand testing 2026-03-31 14:01:39 +02:00
A. Molzer a2996ab34e Basic test for EntryByteReader 2026-03-31 09:16:33 +02:00
A. Molzer f890d22657 Add EntryBytesReader: impl Read + Seek 2026-03-31 09:16:33 +02:00
A. Molzer 6b04bff6b2 Relax Decoder, new interface documentation
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.
2026-03-31 09:05:43 +02:00
A. Molzer 57d238fd9c Add ValueBuffer::set and some documentation 2026-03-31 08:47:10 +02:00
A. Molzer bd0dccef3a Deprecate Decoder::*tag methods
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.
2026-03-31 05:47:04 +02:00
A. Molzer 7491436306 Utils for DecodingBuffer et.al
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`.
2026-03-28 01:25:07 +01:00
A. Molzer 860052a8f4 Rename decoding buffers
New names align better with conventions. In particular, Buffer refers to
an owning type.
2026-03-27 19:58:59 +01:00
A. Molzer 5696b0c78b Move DecodingBuffer related types to module 2026-03-27 17:57:13 +01:00
Lilith River 85215e81e3 Revert "feat: add YCbCr chroma subsampling support for non-JPEG compression"
This reverts commit 83409cbdf3.
2026-03-11 00:39:37 -06:00
Lilith River 617dc6eed8 fix: 32-bit platform failures in chunk_col_stride and LogLuv hash
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.
2026-03-11 00:27:06 -06:00
Lilith River 83409cbdf3 feat: add YCbCr chroma subsampling support for non-JPEG compression
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.
2026-03-10 22:46:59 -06:00
Lilith River 26301fc3f1 feat: add CCITT Group 3 (Fax3) compression and FillOrder support
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.
2026-03-10 22:41:03 -06:00
Lilith River 3434d0220b perf: const-generic bit unpacking with wide u64 reads
Specialize extract_bits_msb and unpack_bits via const generics so the
compiler knows BPS at compile time. Replace the byte-by-byte accumulation
loop with a single u64::from_be_bytes read + shift + mask.

Benchmarks (1024x1024 gray, uncompressed):
  10-bit: 1.32ms → 886µs  (1.49x faster)
  12-bit: 1.34ms → 871µs  (1.54x faster)
  14-bit: 1.49ms → 963µs  (1.55x faster)
  24-bit: 1.84ms → 776µs  (2.37x faster)
2026-03-10 08:08:09 -06:00
Lilith River fb1afc81e9 fix: handle WhiteIsZero for non-standard sub-byte depths (3/5/6/7-bit)
For sub-byte depths where samples cross byte boundaries (3, 5, 6, 7-bit),
byte-wise NOT corrupts samples. Added per-sample inversion that extracts
each N-bit sample from the packed stream, computes (max - value), and
writes it back.

Tests cover:
- 3/5/7-bit BlackIsZero (verify basic decoding)
- 3-bit and 6-bit WhiteIsZero (verify per-sample inversion)
2026-03-10 06:02:19 -06:00
Lilith River 29e43d2314 fix: handle WhiteIsZero for extended bit depths, add comprehensive tests
Fix invert_colors to support extended bit depths (9-15 promoted to u16,
17-31 promoted to u32). Byte-wise NOT doesn't work for promoted samples
because it flips unused upper bits; use (max - value) instead.

Add regression tests covering the extended depth matrix:
- Gray 10/14/24-bit (contiguous, BlackIsZero)
- RGB 10/14/24-bit (contiguous)
- RGB 10/12/14/24-bit (planar)
- Gray 12-bit with WhiteIsZero (synthetic 4x2 image)
- Gray 12-bit with horizontal predictor (synthetic 4x2 image)
2026-03-10 06:02:19 -06:00