Commit Graph
102 Commits
Author SHA1 Message Date
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 4ff374990e feat: accept sub-byte bit depths for all color types
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.
2026-03-10 06:02:19 -06:00
Lilith River 3d3c16e26f feat: support packed extended bit depths (9-15, 17-31 bits per sample)
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).
2026-03-10 06:01:48 -06:00
Lilith River 3b11e2f577 feat: support palette (ColorMap) image decoding
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().
2026-03-09 21:03:30 -06:00
A. Molzer 58e93fb8a3 Align tile detection to libtiff 2026-03-08 03:00:15 +01:00
A. Molzer 647e9b4a81 Add TiffHeader to open a file without seeking 2026-03-06 02:59:45 +01:00
A. Molzer 14ebf376f3 Move Directory from image into decoder proper
This prepares allowing to iterate over a TIFF file with invalid images
while `Image` maintains invariants. For instance we precalculate the
amount of buffer space and verify that the count of strips makes sense
in the constructor—so we do not have to redo that for every other entry
point.
2026-03-04 03:37:42 +01:00
A. Molzer c81d0a9c94 Skip endianess swap for SGI compression 2026-02-14 18:04:07 +01:00
A. Molzer 983c5baea0 Introduce LogL(uv) encodings 2026-02-14 18:04:07 +01:00
A. Molzer abf3cc617d Bump MSRV to 1.88 2026-02-14 18:04:07 +01:00
A. Molzer 45cd3c3b63 Improve unexpected tile count error
Do we even want to error early here? This isn't exactly fatal to reading
the image it is a only nuisance. It becomes crucial only when we are at
the point of reading all the tiles themselves.
2026-02-14 06:55:29 +01:00
Jonathan BehrensandA. Molzer a0191ed1a7 Support decoding WebP compressed TIFFs (#336)
Co-authored-by: A. Molzer <5550310+197g@users.noreply.github.com>
2026-01-29 10:53:00 -08:00
A. Molzer bd64e438a4 Add note about subsampling support to decoder 2026-01-05 21:46:59 +01:00
A. Molzer fa225e820b Exempt JPEG compression from upsampling restriction
I didn't realize we support this in a slightly weird manner where the
tag is completely ignored and zune-jpeg performs the upsampling as
indicated in the JPEG stream. This is a recurring problem with the
compression scheme and tags in TIFF. In the end, decoder can only handle
the data as indicated by the JPEG steam and while the specification
requires the tags to be consistent with the underlying data, sometimes
they are not. The useful fallback is of course to ignore the tag.

Ignoring the tag of course buts us in a weird spot for zero-copy
decoding since the buffer indication to the caller will be incorrect.
For now, maybe the best way of treating subsampling is to upsample all
the cases when we add support.
2026-01-05 21:38:32 +01:00
A. Molzer 23d1867f75 Recognize YCbCr/Chomra subsampling tags
In particular we avoid mistaking the absence of this tag for an absence
of subsampling. The specification instead requires it to mean half the
samples in both directions—a mode that we do not support yet. Instead of
returning erroneous samples or an error in the data stream (which would
appear too short) we return an error when asked to layout the read
buffer for these images.
2026-01-05 21:14:40 +01:00
A. Molzer 1732481b01 Add ICC Lab interpretation
The CIE Lab interpretation uses heterogeneous samples, and the ITU Lab
encoding requires the Tag Decode (433, 0x01B1) which has default values
depending on bit depth and convering values into floating points, probably.

See also Photometric Interpretation: <https://web.archive.org/web/20050309053132/http://www.awaresystems.be/imaging/tiff/tifftags/photometricinterpretation.html>
See also Tag Decode: <https://web.archive.org/web/20050309072856/http://www.awaresystems.be/imaging/tiff/tifftags/decode.html>
2025-12-30 06:51:20 +01:00
A. Molzer 6d56d014a1 Sample types in layouts, resize DecoderResult
By adding the type interpretation to `BufferLayoutPreference` (simply
computed from the same fields that are trivial to fetch in
`ReadoutLayout` with the rest) we can allocate an appropriate
`DecoderResult` without any `Decoder` instance. We still want a Limits
instance though. This lets a caller read _typed_ data from the `_bytes`
methods which perform less allocations. It also lets them read multiple
planes in a more convenient interface. See, `read_image` will keep its
planar treatment bug for a little bit so you don't accidentally break
during an upgrade.

This also implies less code is being monomorphized, just a bonus.
2025-12-28 21:06:59 +01:00
A. Molzer b1c74495ce Allow extraneous extra samples beyond alpha 2025-12-28 15:09:42 +01:00
A. Molzer 1f02ff8ca1 Add method to retrieve planes of a tile
This is well-defined even if we add sub-sampling support later on. We
can adjust the layout type accordingly with factors per each plane. Note
that the layout, in theory, does not depend on the index of the unit if
we describe all planes for a multi-planar layout. The specification
requires that the subsampling factors divide the tile sizes. However we
have to handle non-planar images here and this leaves us open to other
potential parameters.
2025-12-28 15:06:10 +01:00
A. Molzer c3882b252c Cleanup documentation 2025-11-27 23:56:57 +01:00
A. Molzer 6fe21da979 Simplify layout proxy methods 2025-11-27 23:56:57 +01:00
A. Molzer 6439880e3d Simplify layout structs 2025-11-27 23:56:13 +01:00
A. Molzer dce1c40bd5 Move layout responsibility into ReadoutLayout 2025-11-27 23:56:08 +01:00
A. Molzer 5d3a5242ea Add plane selection to allocating result_extent 2025-11-26 19:18:27 +01:00
A. Molzer 95f2d475fa Consolidate layout information type
We currently have one type in decoder/image.rs and another in
decoder/mod.rs which each contributes part of the computation for laying
out the output buffer. Confusingly though they rely on shared
implementation details such as determining the number of channels that
are written and their bit depth. We should have one shared method and
resulting struct instead.
2025-11-23 19:51:26 +01:00
A. Molzer f9eaf9632a Expand PreferredLayout for planar information 2025-11-17 21:11:28 +01:00
A. Molzer 65662c4834 Expand requested plane when reading chunk 2025-11-12 23:42:49 +01:00
A. Molzer 8517bb81db Decode with ExtraSamples
These samples are normal bits in the encoded (e.g. compressed) stream of
each tile or row of the image. However, when considering the photometric
interpretation of an image these samples should be discarded. They are
only relevant for multiband.

For the most part we previously rejected these images due to a mismatch
between sample count and the color as indicated by the interpretation.
Only, for alpha we never really verified if the extra channel is indeed
alpha or something unrelated. We don't have a perfect solution as
GeoTiff motivates us having a MultiBand pseudo-color output
configuration that has no related photometric interpretation and borrows
the usual gray tone instead. We can only really distinguish this from a
gray-alpha image through looking at the values of the ExtraSamples
vector. Then again we do not yet support GrayAlpha anyways.

As a simplification for the discarding process we only support full-byte
extra samples and require all samples to be the same bit-depth, not only
those which we return. This is blessed by Tiff's recommendations for
baseline decoding as well as libtiff.
2025-11-12 01:23:12 +01:00
Your Name 714e470595 Fix decoder panic with malformed (empty) sample format tag
I've included a minimal reproducer in
tests/fuzz_images/oor_panic/sample-format-empty-vec.bin
2025-10-27 15:56:37 +01:00
Jacob Adam W. 4adba46b9f Update zune-jpeg to 0.5 (#304) 2025-10-25 13:53:04 -07:00
Aurelia Molzer bc71086a96 Verify WhiteIsZero invert, add Gray(1) color type
This was found through the fax4 decoding that is always bilevel.

No longer silently ignore an unknown color type when a photometric
interpretation requires an inversion. DICOM requires that WhiteIsZero
and BlackIsZero are only used for samples-per-pixel=1 but we do not
strictly enforce that early. Previously for the result Multiband color
we just ignored the tag which is also not right.
2025-09-01 00:25:48 +02:00
Stephen Judkins 80ce77190e Refactor Group4 reader into stream module 2025-08-31 01:25:43 +02:00
Stephen Judkins c75aa98ce2 bit-addressable output (doesn't work yet) 2025-08-31 01:25:42 +02:00
Stephen Judkins c1087f6f08 Reader instance for group 4-decoded images, that buffer one line at a time 2025-08-31 01:24:21 +02:00
Stephen Judkins 9fcc2a3b65 review changes: pass in width/height, unwrap reader bytes 2025-08-30 23:24:30 +02:00
Stephen Judkins 666f871d30 CCITT group 4 (Fax4) decoding support 2025-08-30 23:20:41 +02:00
Aurelia Molzer 36a16db659 Merge pull request #285 from image-rs/read-into-buffer
Read into byte buffer
2025-07-25 19:16:09 +00:00
A. Molzer 48d52bed9b Return buffer layout as a struct
Prepares extensibility for the eventual case of supporting multi-planar
image layouts that are to be written into a multi-planar buffer as
indicated by the caller in some way (possibly not via read_*_bytes but a
method taking a wrapper around the output buffer instead). Through the
struct we can later add an indicator that can be used by the caller to
decide between methods of fetching the pixel data as appropriate.
2025-07-19 17:46:37 +02:00
A. Molzer 23672b2490 Switch to div_ceil for rounding implementation 2025-07-14 19:25:12 +02:00
A. Molzer ca74c03520 Fix straightforward cast/borrow lints 2025-07-14 19:25:12 +02:00
A. Molzer e62cdfbf42 Merge TagReader and exposed IfdReader internals
Having the value reader itself take care of the bigtiff and limit
semantics is a little cleaner, with the TagReader being responsible
for the interface of grabbing specific types of IFD values.
2025-07-11 15:09:31 +02:00
A. Molzer 98cd3dc2d2 Refactor directory reading
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.
2025-07-11 14:12:34 +02:00
Aurelia Molzer df4e42eecc Add a dedicated Directory type (#277) 2025-07-07 19:59:37 -07:00
Jonathan Behrens 7648317643 Stop doing color conversion when decoding JPEG compressed images (#274) 2025-06-22 16:19:36 -07:00
Emma 29b71ff597 add CMYKA (#208) 2025-06-15 16:38:22 -07:00
Jonathan Behrens 83e6d64751 Create feature flags for more compression algorithms (#270) 2025-06-15 15:50:14 -07:00