Replace chunked scratch-buffer approach with direct indexed writes
into the pre-allocated output. Simpler and faster in practice.
Co-Authored-By: Claude <noreply@anthropic.com>
Replace single-byte pushes with chunk-based processing: pre-allocate
zeroed output, shuffle 4 values at a time into a stack buffer, then
copy_from_slice each byte lane into the output.
Co-Authored-By: Claude <noreply@anthropic.com>
Process the difference step in reverse 16-byte chunks, reading both
curr and prev into local arrays before writing. This makes all 16
subtractions independent, enabling autovectorization (confirmed with
SSE2: movdqu + psubb).
Co-Authored-By: Claude <noreply@anthropic.com>
Replace the two near-identical floating-point predictor encoding
functions with a single fp_predict! macro parameterized on the float
type, using core::mem::size_of to derive the byte size.
Co-Authored-By: Claude <noreply@anthropic.com>
Add support for encoding TIFF images with the floating-point predictor,
which improves compression ratios for floating-point image data.
The floating-point predictor works by:
1. Converting floats to big-endian bytes
2. Shuffling bytes so all first bytes are together, all second bytes, etc.
3. Applying byte-level horizontal differencing across the buffer
This complements the existing decoding support for Predictor 3.
Supported types: Gray32Float, Gray64Float, RGB32Float, RGB64Float,
RGBA32Float, RGBA64Float, CMYK32Float, CMYK64Float.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
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.
This complements the entry API of the encoder with the byte slice based
method. Note we don't provide it for writing raw data, here a
`std::io::Write` would be more appropriate and we should not attempt to
make it `write_all`, nor compress. You'd get all the benefits of the
other API through `[u8]: TiffValue` since that interface is otherwise
type agnostic and `u8` specifically is endian agnostic.
We have not released this yet, let's align these names before it is too
late. The decoder adopted the `_buf` suffix and I find it more
convincing than the more generic `_value` (that also confusingly shares
it with `TiffValue`).
Implement the encoder's `TiffValue` trait for the tags commonly defined
and represented as separate types in our own code. We don't tie the
tag for an entry of these to the value type (yet). This lets us keep the
trait exactly as-is and we avoid unnecessary churn. We can always add a
separate trait on top.
Seems they were introduced with BigTiff but since the enumeration of the
decoder had no obligation of introducing discriminant values the list
was not cross-referenced to libtiff. Hence, some rational types exist
with no basis in decoding.
We have a fundamental choice here: We could add more proxy
implementations for `Vec` and `Box` to follow `&'_ T`, for some types
that indirections is clear. Vec and arrays are also both very natural
ways of expressing a repeated value. However, passing an allocated value
to the interface forces us to drop it inside. We might be able to
utilize the allocation for an in-place byteswap in a future increment
but not with the current design.
So, overall, I think that inherently `Drop`-y types should be avoided in
these implementations. Instead, we can use the amazing diagnostic tools
in the compiler to just help the users navigate the type system. We can
still provide the impl for arrays.
For context, see: https://users.rust-lang.org/t/what-type-is-u16/137395
Now they see:
|
38 | .write_tag(tiff::tags::Tag::GeoKeyDirectoryTag, &geokey);
| ^^^^^^ the trait `TiffValue` is not implemented for `&Vec<u16>`
|
= note: the trait is implemented for primitive types (`u8`, `i16`, `f32`, etc.)
= note: the trait is implemented for shared references to values
= note: the trait is implemented for slices, pass them by reference (e.g. `&[u8]`)
= note: the trait is implemented for arrays if it is implemented for a slice
= note: values in a `Vec` or `Box` should be dereferenced, e.g. `&vec[..]`
= help: the following other types implement trait `TiffValue`:
Previously the compiler would both strip the `&` in its first message
(due to our forwarding impl) and not list any way of getting a
reference-to-slice.
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.
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.
We previously handed it a wrapper that was supposedly type-safe but
really internally cast the buffer to bytes. That was rather pointless as
the `_bytes` method could always be used instead. We replace the
semantics by a utility which allocates an appropriate buffer with the
internal limits, wrapping the exact sequence identified previously in
the example as a pattern.
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.