Files
image-tiff/src/decoder/mod.rs
T
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

2379 lines
90 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
use std::collections::BTreeMap;
use std::io::{self, Read, Seek};
use std::num::NonZeroUsize;
use crate::tags::{
ByteOrder, IfdPointer, PlanarConfiguration, Predictor, SampleFormat, Tag, TiffVariant, Type,
ValueBuffer,
};
use crate::{
ColorType, Directory, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, UsageError,
};
use self::image::Image;
use self::stream::EndianReader;
mod buffer;
mod cycles;
mod entry_reader;
pub mod ifd;
mod image;
mod logluv;
mod stream;
mod tag_reader;
pub use self::buffer::{DecodingSampleBuffer, DecodingSampleSlice, DecodingSampleType};
pub use self::entry_reader::EntryBytesReader;
#[deprecated = "Renamed to `DecodingSampleBuffer`"]
pub type DecodingResult = DecodingSampleBuffer;
#[deprecated = "Renamed to `DecodingSampleSlice`"]
pub type DecodingBuffer<'a> = DecodingSampleSlice<'a>;
use self::buffer::DecodingExtent;
/// An index referring to a (rectangular) region of an image.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct TiffCodingUnit(pub u32);
/// Information on the byte buffer that should be supplied to the decoder.
///
/// This is relevant for [`Decoder::read_image_bytes`] and [`Decoder::read_chunk_bytes`] where the
/// caller provided buffer must fit the expectations of the decoder to be filled with data from the
/// current image.
#[non_exhaustive]
#[derive(Debug, Clone)]
pub struct BufferLayoutPreference {
/// Minimum byte size of the buffer to read image data.
pub len: usize,
/// The interpretation of each sample in the image.
///
/// We only support a uniform sample layout. Detailed information for mixed colors may be added
/// in the future and will become available by explicit query. The same goes for the bit-depth
/// of samples that must also be uniform.
pub sample_format: SampleFormat,
/// The type representation for each sample. Only available for depths and formats which the
/// library can describe.
pub sample_type: Option<DecodingSampleType>,
/// Minimum number of bytes to represent a row of image data of the requested content.
pub row_stride: Option<NonZeroUsize>,
/// Number of planes in the image.
pub planes: usize,
/// Number of bytes used to represent one plane.
pub plane_stride: Option<NonZeroUsize>,
/// Number of bytes of data when reading all planes.
pub complete_len: usize,
}
impl BufferLayoutPreference {
fn from_planes(layout: &image::PlaneLayout) -> Self {
BufferLayoutPreference {
len: layout.readout.plane_stride,
row_stride: core::num::NonZeroUsize::new(layout.readout.row_stride),
planes: layout.plane_offsets.len(),
plane_stride: core::num::NonZeroUsize::new(layout.readout.plane_stride),
complete_len: layout.total_bytes,
sample_format: layout.readout.sample_format,
sample_type: Self::sample_type(layout.readout.sample_format, layout.readout.color),
}
}
fn sample_type(sample_format: SampleFormat, color: ColorType) -> Option<DecodingSampleType> {
Some(match sample_format {
SampleFormat::Uint => match color.bit_depth() {
n if n <= 8 => DecodingSampleType::U8,
n if n <= 16 => DecodingSampleType::U16,
n if n <= 32 => DecodingSampleType::U32,
n if n <= 64 => DecodingSampleType::U64,
_ => return None,
},
SampleFormat::IEEEFP => match color.bit_depth() {
16 => DecodingSampleType::F16,
32 => DecodingSampleType::F32,
64 => DecodingSampleType::F64,
_ => return None,
},
SampleFormat::Int => match color.bit_depth() {
n if n <= 8 => DecodingSampleType::I8,
n if n <= 16 => DecodingSampleType::I16,
n if n <= 32 => DecodingSampleType::I32,
n if n <= 64 => DecodingSampleType::I64,
_ => return None,
},
_other => {
return None;
}
})
}
}
impl image::ReadoutLayout {
// FIXME: when planes are not homogenous (i.e. subsampled or differing depths) then the
// `readout_for_size` or `to_plane_layout` needs a parameter to determine the planes being
// read instead of assuming a constant repeated size for them.
fn result_extent_for_planes(
self: &image::ReadoutLayout,
planes: core::ops::Range<u16>,
) -> TiffResult<DecodingExtent> {
let buffer = self.to_plane_layout()?;
// The layout is for all planes. So restrict ourselves to the planes that were requested.
let offset = match buffer.plane_offsets.get(usize::from(planes.start)) {
Some(n) => *n,
None => {
return Err(TiffError::UsageError(UsageError::InvalidPlaneIndex(
planes.start,
)))
}
};
let end = match buffer.plane_offsets.get(usize::from(planes.end)) {
Some(n) => *n,
None => buffer.total_bytes,
};
let buffer_bytes = end - offset;
let bits_per_sample = self.color.bit_depth();
let Some(sample_type) = BufferLayoutPreference::sample_type(self.sample_format, self.color)
else {
if matches!(
self.sample_format,
SampleFormat::Uint | SampleFormat::Int | SampleFormat::IEEEFP
) {
return Err(TiffError::UnsupportedError(
TiffUnsupportedError::UnsupportedSampleDepth(bits_per_sample),
));
} else {
return Err(TiffError::UnsupportedError(
TiffUnsupportedError::UnsupportedSampleFormat(vec![self.sample_format]),
));
}
};
Ok(sample_type.extent_for_bytes(buffer_bytes))
}
#[inline(always)]
fn assert_min_layout<T>(&self, buffer: &[T]) -> TiffResult<()> {
if core::mem::size_of_val(buffer) < self.plane_stride {
Err(TiffError::UsageError(
UsageError::InsufficientOutputBufferSize {
needed: self.plane_stride,
provided: buffer.len(),
},
))
} else {
Ok(())
}
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
/// Chunk type of the internal representation
pub enum ChunkType {
Strip,
Tile,
}
/// Decoding limits
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct Limits {
/// The maximum size of any `DecodingResult` in bytes, the default is
/// 256MiB. If the entire image is decoded at once, then this will
/// be the maximum size of the image. If it is decoded one strip at a
/// time, this will be the maximum size of a strip.
pub decoding_buffer_size: usize,
/// The maximum size of any ifd value in bytes, the default is
/// 1MiB.
pub ifd_value_size: usize,
/// Maximum size for intermediate buffer which may be used to limit the amount of data read per
/// segment even if the entire image is decoded at once.
pub intermediate_buffer_size: usize,
}
impl Limits {
/// A configuration that does not impose any limits.
///
/// This is a good start if the caller only wants to impose selective limits, contrary to the
/// default limits which allows selectively disabling limits.
///
/// Note that this configuration is likely to crash on excessively large images since,
/// naturally, the machine running the program does not have infinite memory.
pub fn unlimited() -> Limits {
Limits {
decoding_buffer_size: usize::MAX,
ifd_value_size: usize::MAX,
intermediate_buffer_size: usize::MAX,
}
}
}
impl Default for Limits {
fn default() -> Limits {
Limits {
decoding_buffer_size: 256 * 1024 * 1024,
intermediate_buffer_size: 128 * 1024 * 1024,
ifd_value_size: 1024 * 1024,
}
}
}
/// Describe the header of TIFF file, including where to find the first image directory.
#[derive(Debug)]
pub struct TiffHeader {
pub byte_order: ByteOrder,
pub variant: TiffVariant,
pub first_ifd: IfdPointer,
}
/// The representation of a TIFF decoder
///
/// There are three groups of operations:
///
/// - [Manipulate the directory state](#header-manipulate-directory) to which the decoder currently
/// refers in the file.
/// - [Interact with the underlying stream](#header-stream-access) instead of entries in an IFD.
/// - [Access the encoded image](#header-image).
///
/// ## Limitations
///
/// Currently does not support decoding of interlaced images. It also does not support images with
/// downsampling in any direction; except if they are also JPEG or WebP compressed.
#[derive(Debug)]
pub struct Decoder<R> {
/// There are grouped for borrow checker reasons. This allows us to implement methods that
/// borrow the stream access and the other fields mutably at the same time.
value_reader: ValueReader<R>,
current_ifd_pointer: Option<IfdPointer>,
directory: Directory,
/// The IFDs we visited already in this chain of IFDs.
ifd_offsets: Vec<IfdPointer>,
/// Map from the ifd into the `ifd_offsets` ordered list.
seen_ifds: cycles::IfdCycles,
image: Option<Image>,
/// Reusable scratch buffer for the floating-point predictor. Stored here so that the
/// allocation persists across strips/tiles and images, avoiding per-row heap allocation.
scratch: Vec<u8>,
}
/// All the information needed to read and interpret byte slices from the underlying file, i.e. to
/// turn an entry of a tag into an `ifd::Value` or otherwise fetch arrays of similar types. Used
/// only as the type of the field [`Decoder::value_reader`] and passed to submodules.
#[derive(Debug)]
struct ValueReader<R> {
reader: EndianReader<R>,
bigtiff: bool,
limits: Limits,
}
/// Reads a directory's tag values from an underlying stream.
///
/// Created by [`Decoder::current_ifd`] and [`Decoder::read_directory_tags`].
pub struct IfdDecoder<'lt> {
inner: tag_reader::TagReader<'lt, dyn tag_reader::EntryDecoder + 'lt>,
}
/// Byte-level prefix sum with compile-time stride. Each element is the wrapping sum of itself and
/// the element N positions earlier. For N >= 2, the N interleaved channels are independent streams.
///
/// For N=3 (RGB) and N=4 (RGBA), we use an explicit `[u8; N]` accumulator so LLVM sees each
/// channel as an independent scalar chain and can unroll effectively. Without this, LLVM emits a
/// scalar byte-at-a-time loop for N=3 because 3 doesn't map cleanly to SIMD widths.
fn one_byte_predict<const N: usize>(buf: &mut [u8]) {
if N == 0 || buf.len() <= N {
return;
}
// Copy the first N bytes as the initial accumulator state.
let mut acc = [0u8; N];
acc.copy_from_slice(&buf[..N]);
// Process N bytes per iteration. Each channel's prefix sum is independent.
let mut chunks = buf[N..].chunks_exact_mut(N);
for chunk in &mut chunks {
for c in 0..N {
acc[c] = chunk[c].wrapping_add(acc[c]);
chunk[c] = acc[c];
}
}
// Handle trailing bytes (when buf.len() - N is not a multiple of N).
for (c, byte) in chunks.into_remainder().iter_mut().enumerate() {
*byte = byte.wrapping_add(acc[c]);
}
}
fn rev_hpredict_nsamp(buf: &mut [u8], bit_depth: u8, samples: u16) {
fn two_bytes_predict<const N: usize>(buf: &mut [u8]) {
for i in (2 * N..buf.len()).step_by(2) {
let v = u16::from_ne_bytes(buf[i..][..2].try_into().unwrap());
let p = u16::from_ne_bytes(buf[i - 2 * N..][..2].try_into().unwrap());
buf[i..][..2].copy_from_slice(&(v.wrapping_add(p)).to_ne_bytes());
}
}
fn four_bytes_predict<const N: usize>(buf: &mut [u8]) {
for i in (N * 4..buf.len()).step_by(4) {
let v = u32::from_ne_bytes(buf[i..][..4].try_into().unwrap());
let p = u32::from_ne_bytes(buf[i - 4 * N..][..4].try_into().unwrap());
buf[i..][..4].copy_from_slice(&(v.wrapping_add(p)).to_ne_bytes());
}
}
let samples = usize::from(samples);
match (bit_depth, samples) {
// Note we can't use `windows` or so due to the overlap between each iteration. We split
// the cases by the samples / lookback constant so that each is optimized individually.
// This is more code generated but each loop can then have a different vectorization
// strategy.
(0..=8, 1) => one_byte_predict::<1>(buf),
(0..=8, 2) => one_byte_predict::<2>(buf),
(0..=8, 3) => one_byte_predict::<3>(buf),
(0..=8, 4) => one_byte_predict::<4>(buf),
// The generic, sub-optimal case for the above.
(0..=8, _) => {
for i in samples..buf.len() {
buf[i] = buf[i].wrapping_add(buf[i - samples]);
}
}
(9..=16, 1) => {
two_bytes_predict::<1>(buf);
}
(9..=16, 2) => {
two_bytes_predict::<2>(buf);
}
(9..=16, 3) => {
two_bytes_predict::<3>(buf);
}
(9..=16, 4) => {
two_bytes_predict::<4>(buf);
}
(9..=16, _) => {
for i in (samples * 2..buf.len()).step_by(2) {
let v = u16::from_ne_bytes(buf[i..][..2].try_into().unwrap());
let p = u16::from_ne_bytes(buf[i - 2 * samples..][..2].try_into().unwrap());
buf[i..][..2].copy_from_slice(&(v.wrapping_add(p)).to_ne_bytes());
}
}
(17..=32, 1) => {
four_bytes_predict::<1>(buf);
}
(17..=32, 2) => {
four_bytes_predict::<2>(buf);
}
(17..=32, 3) => {
four_bytes_predict::<3>(buf);
}
(17..=32, 4) => {
four_bytes_predict::<4>(buf);
}
(17..=32, _) => {
for i in (samples * 4..buf.len()).step_by(4) {
let v = u32::from_ne_bytes(buf[i..][..4].try_into().unwrap());
let p = u32::from_ne_bytes(buf[i - 4 * samples..][..4].try_into().unwrap());
buf[i..][..4].copy_from_slice(&(v.wrapping_add(p)).to_ne_bytes());
}
}
(33..=64, _) => {
for i in (samples * 8..buf.len()).step_by(8) {
let v = u64::from_ne_bytes(buf[i..][..8].try_into().unwrap());
let p = u64::from_ne_bytes(buf[i - 8 * samples..][..8].try_into().unwrap());
buf[i..][..8].copy_from_slice(&(v.wrapping_add(p)).to_ne_bytes());
}
}
_ => {
unreachable!("Caller should have validated arguments. Please file a bug.")
}
}
}
/// Undo the floating-point predictor: byte-level prefix sum, then de-interleave byte planes back
/// into native-endian samples.
///
/// The TIFF float predictor splits each N-byte sample into N separate byte planes within the row,
/// then delta-encodes each plane. Decoding reverses this: prefix-sum to undo the deltas, then
/// transpose the N planes back into packed samples.
fn predict_f32(input: &mut [u8], output: &mut [u8], samples: u16) {
one_byte_predict_generic(input, usize::from(samples));
deinterleave_f32_planes(input, output);
}
fn predict_f16(input: &mut [u8], output: &mut [u8], samples: u16) {
one_byte_predict_generic(input, usize::from(samples));
deinterleave_f16_planes(input, output);
}
fn predict_f64(input: &mut [u8], output: &mut [u8], samples: u16) {
one_byte_predict_generic(input, usize::from(samples));
deinterleave_f64_planes(input, output);
}
/// Byte-level prefix sum: `buf[i] += buf[i - stride]` for all `i >= stride`.
///
/// Same operation as `one_byte_predict<N>` but dispatched at runtime. The most common float
/// channel counts (1, 3, 4) get dedicated loops so LLVM can see the independent channel streams
/// and vectorize each one separately.
fn one_byte_predict_generic(buf: &mut [u8], stride: usize) {
match stride {
1 => one_byte_predict::<1>(buf),
2 => one_byte_predict::<2>(buf),
3 => one_byte_predict::<3>(buf),
4 => one_byte_predict::<4>(buf),
_ => {
for i in stride..buf.len() {
buf[i] = buf[i].wrapping_add(buf[i - stride]);
}
}
}
}
/// Transpose byte-planes back into packed f16/f32/f64 samples with big-endian to native conversion.
///
/// Input layout: `[plane0: n bytes][plane1: n bytes]...[planeP: n bytes]` where each plane holds
/// one byte position of each sample. Output: packed native-endian samples.
///
/// Each function splits the input into equal-sized plane slices, then gathers one byte per plane
/// per sample. Using `from_be_bytes` for endian conversion lets LLVM recognise the byte-swap idiom
/// and emit efficient code (bswap or movbe).
fn deinterleave_f32_planes(input: &[u8], output: &mut [u8]) {
let n = output.len() / 4;
if n == 0 || input.len() < n * 4 {
return;
}
let (p0, rest) = input.split_at(n);
let (p1, rest) = rest.split_at(n);
let (p2, p3) = rest.split_at(n);
// Batch 4 samples (16 output bytes) per iteration. Fixed-size array slicing eliminates
// bounds checks in the inner loop.
let (out_chunks, out_rest) = output.as_chunks_mut::<16>();
let (p0_chunks, p0_rest) = p0.as_chunks::<4>();
let (p1_chunks, p1_rest) = p1.as_chunks::<4>();
let (p2_chunks, p2_rest) = p2.as_chunks::<4>();
let (p3_chunks, p3_rest) = p3.as_chunks::<4>();
for ((((out, b0), b1), b2), b3) in out_chunks
.iter_mut()
.zip(p0_chunks)
.zip(p1_chunks)
.zip(p2_chunks)
.zip(p3_chunks)
{
let s0 = u32::from_be_bytes([b0[0], b1[0], b2[0], b3[0]]).to_ne_bytes();
let s1 = u32::from_be_bytes([b0[1], b1[1], b2[1], b3[1]]).to_ne_bytes();
let s2 = u32::from_be_bytes([b0[2], b1[2], b2[2], b3[2]]).to_ne_bytes();
let s3 = u32::from_be_bytes([b0[3], b1[3], b2[3], b3[3]]).to_ne_bytes();
*out = [
s0[0], s0[1], s0[2], s0[3], s1[0], s1[1], s1[2], s1[3], s2[0], s2[1], s2[2], s2[3],
s3[0], s3[1], s3[2], s3[3],
];
}
// Remaining 0–3 samples.
for (j, out) in out_rest.chunks_exact_mut(4).enumerate() {
out.copy_from_slice(
&u32::from_be_bytes([p0_rest[j], p1_rest[j], p2_rest[j], p3_rest[j]]).to_ne_bytes(),
);
}
}
fn deinterleave_f16_planes(input: &[u8], output: &mut [u8]) {
let n = output.len() / 2;
if n == 0 || input.len() < n * 2 {
return;
}
let (p0, p1) = input.split_at(n);
for (out, (b0, b1)) in output.chunks_exact_mut(2).zip(p0.iter().zip(p1)) {
out.copy_from_slice(&u16::from_be_bytes([*b0, *b1]).to_ne_bytes());
}
}
fn deinterleave_f64_planes(input: &[u8], output: &mut [u8]) {
let n = output.len() / 8;
if n == 0 || input.len() < n * 8 {
return;
}
let (p0, rest) = input.split_at(n);
let (p1, rest) = rest.split_at(n);
let (p2, rest) = rest.split_at(n);
let (p3, rest) = rest.split_at(n);
let (p4, rest) = rest.split_at(n);
let (p5, rest) = rest.split_at(n);
let (p6, p7) = rest.split_at(n);
// Zip all 8 planes with the output to avoid indexed access and bounds checks.
let iter = output.chunks_exact_mut(8).zip(
p0.iter()
.zip(p1)
.zip(p2)
.zip(p3)
.zip(p4)
.zip(p5)
.zip(p6)
.zip(p7),
);
for (out, (((((((b0, b1), b2), b3), b4), b5), b6), b7)) in iter {
out.copy_from_slice(
&u64::from_be_bytes([*b0, *b1, *b2, *b3, *b4, *b5, *b6, *b7]).to_ne_bytes(),
);
}
}
/// Fix byte order and apply the inverse predictor for one row of decoded data.
///
/// `scratch` is a reusable buffer for the floating-point predictor (which needs a working copy of
/// the input). Callers should pass the same buffer across rows to avoid per-row heap allocation.
fn fix_endianness_and_predict(
buf: &mut [u8],
bit_depth: u8,
samples: u16,
byte_order: ByteOrder,
predictor: Predictor,
scratch: &mut Vec<u8>,
) {
match predictor {
Predictor::None => {
fix_endianness(buf, byte_order, bit_depth);
}
Predictor::Horizontal => {
fix_endianness(buf, byte_order, bit_depth);
rev_hpredict_nsamp(buf, bit_depth, samples);
}
Predictor::FloatingPoint => {
// The float predictor reads from scattered byte planes, so it needs a separate input
// buffer.
scratch.resize(buf.len(), 0);
scratch.copy_from_slice(buf);
match bit_depth {
16 => predict_f16(scratch, buf, samples),
32 => predict_f32(scratch, buf, samples),
64 => predict_f64(scratch, buf, samples),
_ => unreachable!("Caller should have validated arguments. Please file a bug."),
}
}
}
}
fn invert_colors(
buf: &mut [u8],
color_type: ColorType,
sample_format: SampleFormat,
) -> TiffResult<()> {
match (color_type, sample_format) {
(ColorType::Gray(1 | 2 | 4 | 8 | 16 | 32 | 64), SampleFormat::Uint) => {
// It's guaranteed that pixels do not cross a byte boundary.
// To invert a single N-bit pixel, we can calculate:
// (2^N - 1) - x = (2^N - 1) + !x + 1 (because -x = !x + 1)
// = 2^n + !x
// = !x (because modular arithmetic)
//
// Further, since !x operates on bits independently, negating bytes
// also works for multi-byte pixels.
for x in buf {
*x = !*x;
}
}
(ColorType::Gray(n @ (3 | 5 | 6 | 7)), SampleFormat::Uint) => {
// Non-standard sub-byte depths where samples may cross byte boundaries.
// Byte-wise NOT would corrupt samples spanning two bytes, so we invert
// each N-bit sample individually within the packed stream.
let max = (1u8 << n) - 1;
let total_bits = buf.len() * 8;
let mut bit_offset = 0;
while bit_offset + n as usize <= total_bits {
let byte_idx = bit_offset / 8;
let bit_in_byte = bit_offset % 8;
if bit_in_byte + n as usize <= 8 {
// Sample fits within one byte
let shift = 8 - bit_in_byte - n as usize;
let val = (buf[byte_idx] >> shift) & max;
let mask = max << shift;
buf[byte_idx] = (buf[byte_idx] & !mask) | ((max - val) << shift);
} else {
// Sample spans two bytes
let combined = ((buf[byte_idx] as u16) << 8)
| buf.get(byte_idx + 1).copied().unwrap_or(0) as u16;
let shift = 16 - bit_in_byte - n as usize;
let max16 = max as u16;
let val = ((combined >> shift) & max16) as u8;
let new_combined =
(combined & !(max16 << shift)) | ((max - val) as u16) << shift;
buf[byte_idx] = (new_combined >> 8) as u8;
if byte_idx + 1 < buf.len() {
buf[byte_idx + 1] = new_combined as u8;
}
}
bit_offset += n as usize;
}
}
(ColorType::Gray(n @ 9..=15), SampleFormat::Uint) => {
// Extended depths promoted to u16: invert within the valid bit range.
// Byte-wise NOT would flip the upper padding bits, so we use (max - v).
let max = (1u16 << n) - 1;
for chunk in buf.as_chunks_mut::<2>().0 {
let v = u16::from_ne_bytes(*chunk);
*chunk = (max - v).to_ne_bytes();
}
}
(ColorType::Gray(n @ 17..=31), SampleFormat::Uint) => {
// Extended depths promoted to u32: invert within the valid bit range.
let max = (1u32 << n) - 1;
for chunk in buf.as_chunks_mut::<4>().0 {
let v = u32::from_ne_bytes(*chunk);
*chunk = (max - v).to_ne_bytes();
}
}
(ColorType::Gray(32), SampleFormat::IEEEFP) => {
for x in buf.as_chunks_mut::<4>().0 {
let v = f32::from_ne_bytes(*x);
*x = (1.0 - v).to_ne_bytes();
}
}
(ColorType::Gray(64), SampleFormat::IEEEFP) => {
for x in buf.as_chunks_mut::<8>().0 {
let v = f64::from_ne_bytes(*x);
*x = (1.0 - v).to_ne_bytes();
}
}
_ => {
return Err(TiffError::UnsupportedError(
TiffUnsupportedError::UnknownInterpretation,
))
}
}
Ok(())
}
/// Fix endianness. If `byte_order` matches the host, then conversion is a no-op.
fn fix_endianness(buf: &mut [u8], byte_order: ByteOrder, bit_depth: u8) {
let host = ByteOrder::native();
let class = match bit_depth {
0..=8 => crate::tags::EndianBytes::One,
9..=16 => crate::tags::EndianBytes::Two,
17..=32 => crate::tags::EndianBytes::Four,
_ => crate::tags::EndianBytes::Eight,
};
host.convert_endian_bytes(class, buf, byte_order);
}
impl TiffHeader {
pub fn parse<R: Read + Seek>(mut r: R) -> TiffResult<Self> {
let mut endianess = Vec::with_capacity(2);
(&mut r).take(2).read_to_end(&mut endianess)?;
let byte_order = match &*endianess {
b"II" => ByteOrder::LittleEndian,
b"MM" => ByteOrder::BigEndian,
_ => {
return Err(TiffError::FormatError(
TiffFormatError::TiffSignatureNotFound,
))
}
};
let mut reader = EndianReader::new(r, byte_order);
let bigtiff = match reader.read_u16()? {
42 => false,
43 => {
// Read bytesize of offsets (in bigtiff it's alway 8 but provide a way to move to 16 some day)
if reader.read_u16()? != 8 {
return Err(TiffError::FormatError(
TiffFormatError::TiffSignatureNotFound,
));
}
// This constant should always be 0
if reader.read_u16()? != 0 {
return Err(TiffError::FormatError(
TiffFormatError::TiffSignatureNotFound,
));
}
true
}
_ => {
return Err(TiffError::FormatError(
TiffFormatError::TiffSignatureInvalid,
))
}
};
let next_ifd = IfdPointer(if bigtiff {
reader.read_u64()?
} else {
u64::from(reader.read_u32()?)
});
Ok(TiffHeader {
byte_order,
variant: if bigtiff {
TiffVariant::BigTiff
} else {
TiffVariant::Standard
},
first_ifd: next_ifd,
})
}
/// Open a file, without seeking to any IFD yet.
pub fn open<R: Read + Seek>(&self, r: R) -> Decoder<R> {
let reader = EndianReader::new(r, self.byte_order);
Decoder {
value_reader: ValueReader {
reader,
bigtiff: matches!(self.variant, TiffVariant::BigTiff),
limits: Default::default(),
},
ifd_offsets: vec![],
current_ifd_pointer: None,
directory: {
let mut dir = Directory::empty();
dir.set_next(Some(self.first_ifd));
dir
},
seen_ifds: cycles::IfdCycles::new(),
image: None,
scratch: Vec::new(),
}
}
}
/// <span id=header-manipulate-directory>Methods to manipulate the directory state</span>
impl<R: Read + Seek> Decoder<R> {
/// Parse the image header from the underlying file and create a decoder positioned before its
/// first directory.
///
/// This method opens the file in a state _before_ the first (image) directory. You need to
/// call one of [`Self::next_image`], [`Self::next_directory`] or [`Self::seek_to_directory`]
/// to choose a directory before interacting with the tag methods.
///
/// # Examples
///
/// ```
/// use tiff::decoder::{Decoder, DecodingResult};
///
/// let buffered_file = /* */
/// # std::io::Cursor::new(include_bytes!(concat!(
/// # env!("CARGO_MANIFEST_DIR"), "/tests/images/tiled-gray-i1.tif"
/// # )));
///
/// let mut decoder = Decoder::open(buffered_file)?;
/// let mut img_buffer = DecodingResult::U8(vec![]);
///
/// while decoder.more_images() {
/// decoder.next_image()?;
/// decoder.read_image_to_buffer(&mut img_buffer)?;
/// }
///
/// # Ok::<_, tiff::TiffError>(())
/// ```
///
/// # Relations
///
/// The [`TiffHeader`] type allows opening a file with a custom offset.
pub fn open(mut r: R) -> TiffResult<Decoder<R>> {
let header = TiffHeader::parse(&mut r)?;
Ok(header.open(r))
}
/// Create a decoder and attempt to parse the first image directory.
///
/// To open a file without parsing any directory yet (for instance to retain a [`Decoder`] on
/// errors in the directory), use [`TiffHeader::parse`] and [`TiffHeader::open`] instead.
///
/// This method is equivalent to [`Decoder::open`] except it provides no decoder when the first
/// IFD errors due to not being an image or an IO problem. It can be replaced precisely with
/// this sequence:
///
/// ```no_run
/// # use tiff::decoder::Decoder;
/// # let r = std::io::BufReader::new(std::io::empty());
/// let decoder = {
/// let mut tmp = Decoder::open(r)?;
/// tmp.next_image()?;
/// tmp
/// };
///
/// # Ok::<_, tiff::TiffError>(())
/// ```
///
#[deprecated = "Prefer `Decoder::open` followed by an explicit `next_image` call. These methods will become aliases in a future major release."]
pub fn new(mut r: R) -> TiffResult<Decoder<R>> {
let header = TiffHeader::parse(&mut r)?;
let mut decoder = header.open(r);
decoder.next_image()?;
Ok(decoder)
}
/// Configure allocation limits for all subsequent operations.
///
/// Limits apply to allocated return values such as from [`Decoder::read_image`] and
/// reallocations on [`ValueBuffer`] and [`DecodingResult`] arguments when they are passed as
/// mutable references to methods such as [`Decoder::read_image_to_buffer`]. They carry over to
/// the decoder returned form [`Self::current_ifd`].
pub fn with_limits(mut self, limits: Limits) -> Decoder<R> {
self.value_reader.limits = limits;
self
}
// FIXME: this tuple would be a good candidate for a proxy, like IfdDecoder. That has been
// requested in some form or another in a number of issues.
#[expect(clippy::unnecessary_unwrap)] // Lifetimes would overlap due to return.
fn ensure_image_and_reader(
&mut self,
) -> TiffResult<(&mut Image, &mut ValueReader<R>, &mut Vec<u8>)> {
if self.image.is_some() {
return Ok((
self.image.as_mut().unwrap(),
&mut self.value_reader,
&mut self.scratch,
));
}
let image = Image::from_reader(&mut self.value_reader, &self.directory)?;
Ok((
self.image.insert(image),
&mut self.value_reader,
&mut self.scratch,
))
}
fn ensure_image(&mut self) -> TiffResult<&mut Image> {
Ok(self.ensure_image_and_reader()?.0)
}
/// Change to another reader in a new decoder, taking with it parsed data and limits.
///
/// The reader will keep the IFD state but resources and in particular the parsed image are
/// transferred to the new decoder. This has two motivations. Firstly, it can not silently
/// duplicate any limits and exhaust more memory than expected. Secondly, this is compatible
/// with additional resources that can not be cloned at all.
///
/// This method assumes that the alternate reader contains the same data as the current reader
/// (but not necessarily the same stream).
///
/// The limits used by `self` get replaced with default limits. To keep using the reader that
/// is the receiver of this method you likely want to reinitialize it with
/// [`Self::with_limits`].
///
/// # Examples
///
/// This method can also be used to 'fork' the reader into multiple independent iterators over
/// the file, provided you use access to [`Self::inner`] to clone the underlying stream. Since
/// the first call takes the resources, keep that object separate if you want each worker to
/// get the same decoder (each with default configured limits).
///
/// Here we show the functionality by opening cursors over data in-memory.
///
/// ```
/// use std::io::Cursor;
/// use tiff::{decoder::Decoder, TiffError};
///
/// let data: &[u8] = /* */
/// # &include_bytes!(concat!(
/// # env!("CARGO_MANIFEST_DIR"), "/tests/images/tiled-gray-i1.tif"
/// # ))[..];
/// # const N: usize = 1;
/// let decoder = Decoder::open(Cursor::new(data))?;
///
/// let mut workers = vec![decoder];
///
/// for _ in 0..N {
/// let decoder = workers.last_mut().unwrap();
/// // Takes the resources with it, they are always in the last.
/// let dup = decoder.take_with_reader(Cursor::new(data));
/// workers.push(dup);
/// }
///
/// let with_resources = workers.pop().unwrap();
///
/// // Now all `workers` are at the same IFD but in a bare state.
///
/// Ok::<_, TiffError>(())
/// ```
///
/// Please note that you do **not** want to use [`File::try_clone`][`std::fs::File::open`], or
/// any other method where multiple readers share a single position variable, in combination
/// with concurrent use of decoders. When the independent decoders each seek the file
/// descriptor in parallel there is a race as to what position they then read from
/// (time-to-check-to-time-to-use). This of course applies to the original file passed to
/// [`Decoder::open`] already but may be more tempting here. Instead, use a file reader that
/// owns a position and uses [`read_at`] or equivalents.
///
/// [`read_at`]: `std::os::unix::fs::FileExt::read_at`
///
/// An *incorrect* use would be:
///
/// ```no_run
/// use std::fs::File;
/// use tiff::{decoder::Decoder, TiffError};
///
/// let file: File = /* */
/// # File::open(concat!(
/// # env!("CARGO_MANIFEST_DIR"), "/tests/images/tiled-gray-i1.tif"
/// # ))?;
/// let mut decoder = Decoder::open(&file)?;
///
/// let concurrent = decoder.inner().try_clone()?;
/// // ⚠️Do NOT attempt to use both decoder and racing_decoder in parallel!
/// // This will be defined behavior but likely cause both to fail.
/// let racing_decoder = decoder.take_with_reader(concurrent);
///
/// # Ok::<_, TiffError>(())
/// ```
///
/// In another use case you have a partially cached file backed by fast storage that you only
/// access from slow disk when any operation misses the cache. In that case you may use this
/// method to replace the cached reader with a file reader after a miss is detected, without
/// losing any of the state collected by the decoder.
///
/// ```
/// use tiff::{decoder::Decoder, TiffError};
///
/// let partial_file = /* */
/// # std::io::Cursor::new(include_bytes!(concat!(
/// # env!("CARGO_MANIFEST_DIR"), "/tests/images/tiled-gray-i1.tif"
/// # )));
/// let mut decoder = Decoder::open(partial_file)?;
///
/// # let backing_file = concat!(
/// # env!("CARGO_MANIFEST_DIR"), "/tests/images/tiled-gray-i1.tif"
/// # );
///
/// // Catch cache errors: we are out of pages we have for this cache..
/// if let Err(TiffError::IoError(e)) = decoder.next_image() {
/// if e.kind() == std::io::ErrorKind::OutOfMemory {
/// let file = std::fs::File::open(backing_file)?;
/// let mut slow_decoder = decoder.take_with_reader(file);
/// // Repeat the op, `next_image` is atomic on error.
/// slow_decoder.next_image()?;
/// // …
/// }
/// }
///
/// # Ok::<_, TiffError>(())
/// ```
pub fn take_with_reader<S: Read + Seek>(&mut self, reader: S) -> Decoder<S> {
Decoder {
value_reader: ValueReader {
reader: EndianReader::new(reader, self.value_reader.reader.byte_order),
bigtiff: self.value_reader.bigtiff,
limits: core::mem::take(&mut self.value_reader.limits),
},
ifd_offsets: self.ifd_offsets.clone(),
seen_ifds: self.seen_ifds.clone(),
directory: self.directory.clone(),
current_ifd_pointer: self.current_ifd_pointer,
image: core::mem::take(&mut self.image),
scratch: core::mem::take(&mut self.scratch),
}
}
/// The offset of the directory representing the current image.
pub fn ifd_pointer(&mut self) -> Option<IfdPointer> {
self.current_ifd_pointer
}
/// Get the IFD decoder for our current IFD.
///
/// This may be used with any directory, valid image or otherwise. When the decoder has not
/// read a directory yet (a state after [`TiffHeader::open`]) the decoder will represent an
/// empty directory with only the next pointer set.
pub fn current_ifd(&mut self) -> IfdDecoder<'_> {
IfdDecoder {
inner: tag_reader::TagReader {
decoder: &mut self.value_reader,
ifd: &self.directory,
},
}
}
/// Loads the IFD at the specified index in the list, if one exists
///
/// Error guarantee: the current directory and [`Self::ifd_pointer`] is not modified when an
/// error is returned.
///
/// The next directory image attributes are *not* parsed on a successful seek, i.e. it need not be
/// semantically valid as an image (incoherent sample depths, unsupported planar configuration,
/// missing required tags, etc.) but [`Self::read_image`] may not work. Calling methods
/// interacting with it as an image after this will first attempt to parse the directory
/// instead.
pub fn seek_to_directory(&mut self, ifd_index: usize) -> TiffResult<()> {
let (offset, directory) = self.find_nth_ifd(ifd_index)?;
self.image = None;
self.current_ifd_pointer = Some(offset);
self.directory = directory;
Ok(())
}
/// Loads the IFD at the specified index in the list, if one exists
///
/// The directory is then decoded as an image.
///
/// Error guarantee: the current directory and [`Self::ifd_pointer`] is not modified when an
/// error is returned.
pub fn seek_to_image(&mut self, ifd_index: usize) -> TiffResult<()> {
let (offset, directory) = self.find_nth_ifd(ifd_index)?;
self.image = Some(Image::from_reader(&mut self.value_reader, &directory)?);
self.current_ifd_pointer = Some(offset);
self.directory = directory;
Ok(())
}
/// Change the directory to the nth in the linked-list chain of IFDs.
fn find_nth_ifd(&mut self, ifd_index: usize) -> TiffResult<(IfdPointer, Directory)> {
// Check whether we have seen this IFD before, if so then the index will be less than the
// length of the list of ifd offsets If the index is within the list of ifds then we can
// load the selected image/IFD. Otherwise we iterate the chain until the selected index.
if let Some(&ifd_offset) = self.ifd_offsets.get(ifd_index) {
let directory = self.value_reader.read_directory(ifd_offset)?;
Ok((ifd_offset, directory))
} else {
// We do not know if we are at the last IFD in the chain if we previous did seek back.
// The `read_next_ifd_pointer` correctly handles this.
let mut chained_ifd = self.current_ifd_pointer;
let mut next_ifd = self.directory.next();
loop {
// Follow the list until we find the one we want, or we reach the end, whichever happens first
let (ifd_pointer, ifd) = self.read_chained_ifd(chained_ifd, next_ifd)?;
chained_ifd = Some(ifd_pointer);
next_ifd = ifd.next();
if ifd_index < self.ifd_offsets.len() {
// We only end up here if initially `ifd_index >= self.ifd_offsets.len()` and
// each loop iteration increases the length of `self.ifd_offsets` by (at most)
// one.
debug_assert!(ifd_index + 1 == self.ifd_offsets.len());
return Ok((ifd_pointer, ifd));
}
}
}
}
fn read_chained_ifd(
&mut self,
current_ifd_pointer: Option<IfdPointer>,
next_ifd: Option<IfdPointer>,
) -> TiffResult<(IfdPointer, Directory)> {
let Some(next_ifd) = next_ifd else {
return Err(TiffError::FormatError(
TiffFormatError::ImageFileDirectoryNotFound,
));
};
let ifd = self.value_reader.read_directory(next_ifd)?;
// Ensure this walk does not get us into a cycle.
self.seen_ifds.insert_next(next_ifd, ifd.next())?;
// Extend the list of known IFD offsets in this chain, if needed.
if self.ifd_offsets.last().copied() == current_ifd_pointer {
self.ifd_offsets.push(next_ifd);
}
Ok((next_ifd, ifd))
}
/// Reads the next IFD.
///
/// If there is no further IFD in the TIFF file a format error is returned.
/// To determine whether there are more images call [`Self::more_images`] instead.
///
/// The next directory image attributes are *not* parsed on a successful seek, i.e. it need not be
/// semantically valid as an image (incoherent sample depths, unsupported planar configuration,
/// missing required tags, etc.) but [`Self::read_image`] may not work. Calling methods
/// interacting with it as an image after this will first attempt to parse the directory
/// instead.
///
/// Error guarantee: the current directory and [`Self::ifd_pointer`] is not modified when an error is
/// returned.
pub fn next_directory(&mut self) -> TiffResult<()> {
let next_ifd = self.directory.next();
let (offset, directory) = self.read_chained_ifd(self.current_ifd_pointer, next_ifd)?;
self.image = None;
self.current_ifd_pointer = Some(offset);
self.directory = directory;
Ok(())
}
/// Reads in the next image.
///
/// If there is no further image in the TIFF file a format error is returned.
/// To determine whether there are more images call [`Self::more_images`] instead.
///
/// Error guarantee: the current directory and [`Self::ifd_pointer`] is not modified when an error is
/// returned.
pub fn next_image(&mut self) -> TiffResult<()> {
let next_ifd = self.directory.next();
let (offset, directory) = self.read_chained_ifd(self.current_ifd_pointer, next_ifd)?;
self.image = Some(Image::from_reader(&mut self.value_reader, &directory)?);
self.current_ifd_pointer = Some(offset);
self.directory = directory;
Ok(())
}
/// Prepare reading values for tags of a given directory.
///
/// # Examples
///
/// This method may be used to read the values of tags in directories that have been previously
/// read with [`Decoder::read_directory`].
///
/// ```no_run
/// use tiff::decoder::Decoder;
/// use tiff::tags::Tag;
///
/// # use std::io::Cursor;
/// # let mut data = Cursor::new(vec![0]);
/// let mut decoder = Decoder::open(&mut data)?;
///
/// decoder.next_directory()?;
/// let sub_ifds = decoder.get_tag(Tag::SubIfd)?.into_ifd_vec()?;
///
/// for ifd in sub_ifds {
/// let subdir = decoder.read_directory(ifd)?;
/// let subfile = decoder.read_directory_tags(&subdir).find_tag(Tag::SubfileType)?;
/// // omitted: handle the subfiles, e.g. thumbnails
/// }
///
/// # Ok::<_, tiff::TiffError>(())
/// ```
pub fn read_directory_tags<'ifd>(&'ifd mut self, ifd: &'ifd Directory) -> IfdDecoder<'ifd> {
IfdDecoder {
inner: tag_reader::TagReader {
decoder: &mut self.value_reader,
ifd,
},
}
}
/// Utility to create a reader like [`Self::read_entry`], from the current IFD.
///
/// Returns an `Ok(None)` if the tag is not found in the current directory. See
/// [`Self::read_entry´] of which this is a simple wrapper.
pub fn read_tag(&mut self, tag: Tag) -> TiffResult<Option<EntryBytesReader<'_, R>>> {
match self.current_ifd().find_entry(tag) {
None => Ok(None),
Some(entry) => self.read_entry(entry).map(Some),
}
}
/// Returns `true` if there is at least one more image available.
pub fn more_images(&self) -> bool {
self.directory.next().is_some()
}
/// Returns the byte_order of the file.
///
/// # Usage
///
/// This is only relevant to interpreting raw bytes read from tags. The image decoding methods
/// will correct to the host byte order automatically.
pub fn byte_order(&self) -> ByteOrder {
self.value_reader.reader.byte_order
}
}
/// <span id=header-stream-access>Methods that interact with the underlying reader.</span>
impl<R: Read> Decoder<R> {
/// Returns a mutable reference to the stream being decoded.
pub fn inner(&mut self) -> &mut R {
self.value_reader.reader.inner()
}
/// Check if this file is a BigTIFF or a standard TIFF.
pub fn variant(&self) -> TiffVariant {
if self.value_reader.bigtiff {
TiffVariant::BigTiff
} else {
TiffVariant::Standard
}
}
#[inline]
pub fn read_ifd_offset(&mut self) -> Result<u64, io::Error> {
if self.value_reader.bigtiff {
self.read_long8()
} else {
self.read_long().map(u64::from)
}
}
/// Reads a TIFF byte value
#[inline]
pub fn read_byte(&mut self) -> Result<u8, io::Error> {
let mut buf = [0; 1];
self.value_reader.reader.inner().read_exact(&mut buf)?;
Ok(buf[0])
}
/// Reads a TIFF short value
#[inline]
pub fn read_short(&mut self) -> Result<u16, io::Error> {
self.value_reader.reader.read_u16()
}
/// Reads a TIFF sshort value
#[inline]
pub fn read_sshort(&mut self) -> Result<i16, io::Error> {
self.value_reader.reader.read_i16()
}
/// Reads a TIFF long value
#[inline]
pub fn read_long(&mut self) -> Result<u32, io::Error> {
self.value_reader.reader.read_u32()
}
/// Reads a TIFF slong value
#[inline]
pub fn read_slong(&mut self) -> Result<i32, io::Error> {
self.value_reader.reader.read_i32()
}
/// Reads a TIFF float value
#[inline]
pub fn read_float(&mut self) -> Result<f32, io::Error> {
self.value_reader.reader.read_f32()
}
/// Reads a TIFF double value
#[inline]
pub fn read_double(&mut self) -> Result<f64, io::Error> {
self.value_reader.reader.read_f64()
}
#[inline]
pub fn read_long8(&mut self) -> Result<u64, io::Error> {
self.value_reader.reader.read_u64()
}
#[inline]
pub fn read_slong8(&mut self) -> Result<i64, io::Error> {
self.value_reader.reader.read_i64()
}
/// Reads a string
#[inline]
pub fn read_string(&mut self, length: usize) -> TiffResult<String> {
let mut out = vec![0; length];
self.value_reader.reader.inner().read_exact(&mut out)?;
// Strings may be null-terminated, so we trim anything downstream of the null byte
if let Some(first) = out.iter().position(|&b| b == 0) {
out.truncate(first);
}
Ok(String::from_utf8(out)?)
}
/// Reads a TIFF IFA offset/value field
#[inline]
pub fn read_offset(&mut self) -> TiffResult<[u8; 4]> {
if self.value_reader.bigtiff {
return Err(TiffError::FormatError(
TiffFormatError::InconsistentSizesEncountered,
));
}
let mut val = [0; 4];
self.value_reader.reader.inner().read_exact(&mut val)?;
Ok(val)
}
/// Create a byte reader for the value of an arbitrary IFD entry.
///
/// Small values are stored inline inside the offset field of the entry. The threshold for this
/// depends on the [`Self::variant`] of the file. Interpret entries that have not been
/// extracted from an IFD of the current decoder with care.
///
/// The reader returned form this method reads raw bytes. Values retrieved in this way are not
/// yet converted to the host endianess.
///
/// Returns an error if the entry count exceeds the possible offset values or if the offset
/// of the entry would overflow the offset in the underlying stream. Otherwise returns a reader
/// that yields the bytes of the value.
///
/// ## Design Notes
///
/// This method does not appear on [`IfdDecoder`] as we assume the reader type may be important
/// to you. It also allows forward compatibility to additional trait implementations without
/// demanding them from all types as `dyn` would. There is also no `M×N` instantiation problem
/// with this method: you get one type per reader type and monomorphic methods from the traits
/// it implements.
pub fn read_entry(&mut self, entry: ifd::Entry) -> TiffResult<EntryBytesReader<'_, R>>
where
R: Seek,
{
EntryBytesReader::from_entry(&mut self.value_reader, entry)
}
/// Reads a TIFF IFA offset/value field
#[inline]
pub fn read_offset_u64(&mut self) -> Result<[u8; 8], io::Error> {
let mut val = [0; 8];
self.value_reader.reader.inner().read_exact(&mut val)?;
Ok(val)
}
/// Moves the cursor to the specified offset
#[inline]
pub fn goto_offset(&mut self, offset: u32) -> io::Result<()>
where
R: Seek,
{
self.goto_offset_u64(offset.into())
}
#[inline]
pub fn goto_offset_u64(&mut self, offset: u64) -> io::Result<()>
where
R: Seek,
{
self.value_reader.reader.goto_offset(offset)
}
}
/// <span id=header-image>Methods that interact with the current directory as an image.</span>
impl<R: Read + Seek> Decoder<R> {
/// Return the current IFD's image size.
///
/// Returns an error if the current directory could not be parsed as an image. This can occur
/// if [`Self::next_directory`] or [`Self::seek_to_directory`] was used to advance the decoder
/// to the current directory which implies the IFD will be validated as an image on-demand by
/// this method.
///
/// [`Self::image_buffer_layout`] provides further information on reading the image data and
/// can be used alternatively to verify further that the image can be read.
pub fn dimensions(&mut self) -> TiffResult<(u32, u32)> {
let image = self.ensure_image()?;
Ok((image.width, image.height))
}
/// Return the color type resulting from reading the image (in the current configuration).
///
/// Returns an error if the current directory could not be parsed as an image. This can occur
/// if [`Self::next_directory`] or [`Self::seek_to_directory`] was used to advance the decoder
/// to the current directory which implies the IFD will be validated as an image on-demand by
/// this method.
pub fn colortype(&mut self) -> TiffResult<ColorType> {
let image = self.ensure_image()?;
image.colortype()
}
/// Returns the color map (palette) for the current image, if it uses
/// `PhotometricInterpretation::RGBPalette`.
///
/// The returned slice contains `3 * 2^BitsPerSample` entries as u16 values,
/// laid out as: all red values, then all green values, then all blue values.
/// Each value is in the range `0..=65535` regardless of the actual bit depth.
pub fn color_map(&self) -> Option<&[u16]> {
self.image.as_ref().and_then(|img| img.color_map.as_deref())
}
/// Read a tag-entry map from a known offset.
///
/// A TIFF [`Directory`], aka. image file directory aka. IFD, refers to a map from
/// tags–identified by a `u16`–to a typed vector of elements. It is encoded as a list
/// of ascending tag values with the offset and type of their corresponding values. The
/// semantic interpretations of a tag and its type requirements depend on the context of the
/// directory. The main image directories, those iterated over by the `Decoder` after
/// construction, are represented by [`Tag`] and [`ifd::Value`]. Other forms are EXIF and GPS
/// data as well as thumbnail Sub-IFD representations associated with each image file.
///
/// This method allows the decoding of a directory from an arbitrary offset in the image file
/// with no specific semantic interpretation. Such an offset is usually found as the value of
/// a tag, e.g. [`Tag::SubIfd`], [`Tag::ExifDirectory`], [`Tag::GpsDirectory`] and recovered
/// from the associated value by [`ifd::Value::into_ifd_pointer`].
///
/// The library will not verify whether the offset overlaps any other directory or would form a
/// cycle with any other directory when calling this method. This will modify the position of
/// the reader, i.e. continuing with direct reads at a later point will require going back with
/// [`Self::goto_offset`].
pub fn read_directory(&mut self, ptr: IfdPointer) -> TiffResult<Directory> {
self.value_reader.read_directory(ptr)
}
/// The chunk type (Strips / Tiles) of the image
///
/// # Panics
///
/// This method panics if the directory was read with [`Self::next_directory`] or
/// [`Self::seek_to_directory`] and [`Self::dimensions`] has not yet succeeded. (Other
/// image-related methods with signature (`fn(&mut self) -> TiffResult<_>`) can be substituted
/// but use that one by default).
pub fn get_chunk_type(&self) -> ChunkType {
self.image.as_ref().unwrap().chunk_type
}
/// Number of strips in image
pub fn strip_count(&mut self) -> TiffResult<u32> {
let image = self.ensure_image()?;
image.check_chunk_type(ChunkType::Strip)?;
let rows_per_strip = image.strip_decoder.as_ref().unwrap().rows_per_strip;
if rows_per_strip == 0 {
return Ok(0);
}
// rows_per_strip - 1 can never fail since we know it's at least 1
let height = match image.height.checked_add(rows_per_strip - 1) {
Some(h) => h,
None => return Err(TiffError::IntSizeError),
};
let strips = match image.planar_config {
PlanarConfiguration::Chunky => height / rows_per_strip,
PlanarConfiguration::Planar => height / rows_per_strip * image.samples as u32,
};
Ok(strips)
}
/// Number of tiles in image
pub fn tile_count(&mut self) -> TiffResult<u32> {
let image = self.ensure_image()?;
image.check_chunk_type(ChunkType::Tile)?;
Ok(u32::try_from(image.chunk_offsets.len())?)
}
fn read_chunk_to_bytes(
&mut self,
buffer: &mut [u8],
chunk_index: u32,
layout: &image::ReadoutLayout,
) -> TiffResult<()> {
let (image, value_reader, scratch) = self.ensure_image_and_reader()?;
let offset = image.chunk_file_range(chunk_index)?.0;
value_reader.reader.goto_offset(offset)?;
image.expand_chunk(value_reader, buffer, layout, chunk_index, scratch)?;
Ok(())
}
/// Returns the layout preferred to read the specified chunk with [`Self::read_chunk_bytes`].
///
/// Returns the layout without being specific as to the underlying type for forward
/// compatibility. Note that, in general, a TIFF may contain an almost arbitrary number of
/// channels of individual *bit* length and format each.
///
/// See [`Self::colortype`] to describe the sample types.
pub fn image_chunk_buffer_layout(
&mut self,
chunk_index: u32,
) -> TiffResult<BufferLayoutPreference> {
let image = self.ensure_image()?;
let data_dims = image.chunk_data_dimensions(chunk_index)?;
let readout = image.readout_for_size(data_dims.0, data_dims.1)?;
let extent = readout.result_extent_for_planes(0..1)?;
let sample_type = extent.sample_type();
let layout = extent.preferred_layout()?;
let row_stride = core::num::NonZeroUsize::new(readout.minimum_row_stride);
let plane_stride = core::num::NonZeroUsize::new(readout.plane_stride);
Ok(BufferLayoutPreference {
len: layout.size(),
row_stride,
planes: 1,
plane_stride,
complete_len: layout.size(),
sample_format: image.sample_format,
sample_type: Some(sample_type),
})
}
/// Return the layout preferred to read several planes corresponding to the specified region.
///
/// This is similar to [`Self::image_chunk_buffer_layout`] but can read chunks from all planes
/// at the corresponding coordinates of the image.
///
/// # Bugs
///
/// Sub-sampled images are not yet supported properly.
pub fn image_coding_unit_layout(
&mut self,
code_unit: TiffCodingUnit,
) -> TiffResult<BufferLayoutPreference> {
let image = self.ensure_image()?;
match image.planar_config {
PlanarConfiguration::Chunky => return self.image_chunk_buffer_layout(code_unit.0),
PlanarConfiguration::Planar => {}
}
let (width, height) = image.chunk_data_dimensions(code_unit.0)?;
let layout = image.readout_for_size(width, height)?.to_plane_layout()?;
if code_unit.0 >= layout.readout.chunks_per_plane {
return Err(TiffError::UsageError(UsageError::InvalidCodingUnit(
code_unit.0,
layout.readout.chunks_per_plane,
)));
}
Ok(BufferLayoutPreference::from_planes(&layout))
}
/// Read the specified chunk (at index `chunk_index`) and return the binary data as a Vector.
///
/// Note that for planar images each chunk contains only one sample of the underlying data.
pub fn read_chunk(&mut self, chunk_index: u32) -> TiffResult<DecodingSampleBuffer> {
let image = self.ensure_image()?;
let (width, height) = image.chunk_data_dimensions(chunk_index)?;
let readout = image.readout_for_size(width, height)?;
let mut result = readout
.result_extent_for_planes(0..1)?
.to_result_buffer(&self.value_reader.limits)?;
self.read_chunk_to_bytes(result.as_buffer(0).as_bytes_mut(), chunk_index, &readout)?;
Ok(result)
}
/// Read the specified chunk (at index `chunk_index`) into an allocated buffer.
///
/// Returns a [`TiffError::UsageError`] if the chunk is smaller than the size indicated with a
/// call to [`Self::image_chunk_buffer_layout`]. Note that the alignment may be arbitrary, but
/// an alignment smaller than the preferred alignment may perform worse.
///
/// Note that for planar images each chunk contains only one sample of the underlying data.
pub fn read_chunk_bytes(&mut self, chunk_index: u32, buffer: &mut [u8]) -> TiffResult<()> {
let image = self.ensure_image()?;
let (width, height) = image.chunk_data_dimensions(chunk_index)?;
let layout = image.readout_for_size(width, height)?;
layout.assert_min_layout(buffer)?;
self.read_chunk_to_bytes(buffer, chunk_index, &layout)?;
Ok(())
}
/// Read the specified chunk (at index `chunk_index`) into a provide buffer.
///
/// It will re-allocate the buffer into the correct type and size, within the decoder's
/// configured limits, and then pass it to the underlying method. This is essentially a
/// type-safe wrapper around the raw [`Self::read_chunk_bytes`] method.
///
/// Note that for planar images each chunk contains only one sample of the underlying data.
pub fn read_chunk_to_buffer(
&mut self,
buffer: &mut DecodingSampleBuffer,
chunk_index: u32,
output_width: usize,
) -> TiffResult<()> {
let image = self.ensure_image()?;
let (width, height) = image.chunk_data_dimensions(chunk_index)?;
let mut layout = image.readout_for_size(width, height)?;
layout.set_row_stride(output_width)?;
let extent = layout.result_extent_for_planes(0..1)?;
buffer.resize_to_extent(extent, &self.value_reader.limits)?;
self.read_chunk_to_bytes(buffer.as_buffer(0).as_bytes_mut(), chunk_index, &layout)?;
Ok(())
}
/// Read chunks corresponding to several planes of a region of pixels.
///
/// For non planar images this is equivalent to [`Self::read_chunk_bytes`] as there is only one
/// plane in the image. For planar images the planes are stored consecutively into the output
/// buffer. Returns an error if not enough space for at least one plane is provided. Otherwise
/// reads all planes that can be stored completely in the provided output buffer.
///
/// A region is a rectangular assortment of pixels in the image, depending on the chunk type
/// either strips or tiles. Borrowing terminology from JPEG we call the collection of all
/// chunks from all planes that encode samples from the same region a "coding unit".
///
/// # Bugs
///
/// Sub-sampled images are not yet supported properly.
pub fn read_coding_unit_bytes(
&mut self,
slice: TiffCodingUnit,
buffer: &mut [u8],
) -> TiffResult<()> {
let (image, value_reader, scratch) = self.ensure_image_and_reader()?;
let (width, height) = image.chunk_data_dimensions(slice.0)?;
let readout = image.readout_for_size(width, height)?;
let ref layout @ image::PlaneLayout {
ref plane_offsets,
// We assume that is correct, so really it can be ignored.
total_bytes: _,
ref readout,
} = readout.to_plane_layout()?;
if slice.0 >= readout.chunks_per_plane {
return Err(TiffError::UsageError(UsageError::InvalidCodingUnit(
slice.0,
readout.chunks_per_plane,
)));
}
// No subsamples planes support, for now.
let used_plane_offsets = usize::from(layout.used_planes(buffer)?);
debug_assert!(used_plane_offsets >= 1, "Should have errored");
for (idx, &plane_offset) in plane_offsets[..used_plane_offsets].iter().enumerate() {
let chunk = slice.0 + idx as u32 * readout.chunks_per_plane;
let offset = image.chunk_offsets[chunk as usize];
value_reader.reader.goto_offset(offset)?;
image.expand_chunk(
value_reader,
&mut buffer[plane_offset..],
readout,
chunk,
scratch,
)?;
}
Ok(())
}
/// Returns the default chunk size for the current image. Any given chunk in the image is at most as large as
/// the value returned here. For the size of the data (chunk minus padding), use `chunk_data_dimensions`.
///
/// # Panics
///
/// This method panics if the directory was read with [`Self::next_directory`] or
/// [`Self::seek_to_directory`] and [`Self::dimensions`] has not yet succeeded. (Other
/// image-related methods with signature (`fn(&mut self) -> TiffResult<_>`) can be substituted
/// but use that one by default).
pub fn chunk_dimensions(&self) -> (u32, u32) {
self.image.as_ref().unwrap().chunk_dimensions().unwrap()
}
/// Returns the size of the data in the chunk with the specified index. This is the default size of the chunk,
/// minus any padding.
///
/// # Panics
///
/// This method panics if the directory was read with [`Self::next_directory`] or
/// [`Self::seek_to_directory`] and [`Self::dimensions`] has not yet succeeded. (Other
/// image-related methods with signature (`fn(&mut self) -> TiffResult<_>`) can be substituted
/// but use that one to be sure).
///
/// This method panics if the index is out-of-range.
pub fn chunk_data_dimensions(&self, chunk_index: u32) -> (u32, u32) {
self.image
.as_ref()
.unwrap()
.chunk_data_dimensions(chunk_index)
.expect("invalid chunk_index")
}
/// Returns the preferred buffer required to read the whole image with [`Self::read_image_bytes`].
///
/// Returns the layout without being specific as to the underlying type for forward
/// compatibility. Note that, in general, a TIFF may contain an almost arbitrary number of
/// channels of individual *bit* length and format each.
///
/// See [`Self::colortype`] to describe the sample types.
///
/// # Bugs
///
/// When the image is stored as a planar configuration, this method will currently only
/// indicate the layout needed to read the first data plane. This will be fixed in a future
/// major version of `tiff`.
pub fn image_buffer_layout(&mut self) -> TiffResult<BufferLayoutPreference> {
let image = self.ensure_image()?;
let layout = image.readout_for_image()?.to_plane_layout()?;
Ok(BufferLayoutPreference::from_planes(&layout))
}
/// Decodes the entire image and return it as a Vector
///
/// # Examples
///
/// This method is deprecated. For replacement usage see `examples/decode.rs`.
///
/// # Bugs
///
/// When the image is stored as a planar configuration, this method will currently only read
/// the first sample's plane. This will be fixed in a future major version of `tiff`. To read
/// multiple planes, [`Self::read_image_to_buffer`] can be used instead.
///
/// # Intent to deprecate
///
/// Use [`Self::read_image_to_buffer`] or a combination of [`DecodingResult::resize_to`] and
/// [`Self::read_image_bytes`] instead where possible, preserving the buffer across multiple
/// calls. This old method will likely keep its bugged planar behavior until it is fully
/// replaced, to ensure that existing code will not run into unexpectedly large allocations
/// that will error on limits instead.
pub fn read_image(&mut self) -> TiffResult<DecodingSampleBuffer> {
let image = self.ensure_image()?;
let readout = image.readout_for_image()?;
let mut result = readout
.result_extent_for_planes(0..1)?
.to_result_buffer(&self.value_reader.limits)?;
self.read_image_bytes(result.as_buffer(0).as_bytes_mut())?;
Ok(result)
}
/// Decodes the entire image into a provided buffer.
///
/// It will re-allocate the buffer into the correct type and size, within the decoder's
/// configured limits, and then pass it to the underlying method. This is essentially a
/// type-safe wrapper around the raw [`Self::read_image_bytes`] method.
///
/// ## Planar behavior
///
/// If the image is stored as a planar configuration, an attempt is made to resize the buffer
/// to hold all planes. If that does not fit then only the first plane is read. Check the
/// buffer size against [`BufferLayoutPreference::complete_len`] to ensure that all planes were
/// read:
///
/// ```
/// use tiff::decoder::{Decoder, DecodingResult};
/// let mut result = DecodingResult::U8(vec![]);
///
/// let mut reader = /* */
/// # Decoder::open(std::io::Cursor::new(include_bytes!(concat!(
/// # env!("CARGO_MANIFEST_DIR"), "/tests/images/tiled-gray-i1.tif"
/// # )))).unwrap();
/// # reader.next_image().unwrap();
/// let layout = reader.read_image_to_buffer(&mut result)?;
///
/// if result.as_buffer(0).as_bytes().len() < layout.complete_len {
/// println!("Only the first plane was read");
/// }
///
/// # Ok::<_, tiff::TiffError>(())
/// ```
///
/// # Examples
///
/// ```
/// use tiff::decoder::{Decoder, DecodingResult, Limits};
///
/// let mut result = DecodingResult::I8(vec![]);
///
/// let mut reader = /* */
/// # Decoder::open(std::io::Cursor::new(include_bytes!(concat!(
/// # env!("CARGO_MANIFEST_DIR"), "/tests/images/tiled-gray-i1.tif"
/// # )))).unwrap();
/// # reader.next_image().unwrap();
/// reader.read_image_to_buffer(&mut result)?;
///
/// # Ok::<_, tiff::TiffError>(())
/// ```
pub fn read_image_to_buffer(
&mut self,
result: &mut DecodingSampleBuffer,
) -> TiffResult<BufferLayoutPreference> {
let readout = self.ensure_image()?.readout_for_image()?;
let planes = readout.to_plane_layout()?;
let num_planes = if planes.total_bytes <= self.value_reader.limits.decoding_buffer_size {
planes.plane_offsets.len() as u16
} else {
1
};
let layout = BufferLayoutPreference::from_planes(&planes);
let extent = readout.result_extent_for_planes(0..num_planes)?;
// Compatibility: if this extent is too large our configured limits we will fall back to
// reading only the first plane.
result.resize_to_extent(extent, &self.value_reader.limits)?;
self.read_image_bytes(result.as_buffer(0).as_bytes_mut())?;
Ok(layout)
}
/// Decodes the entire image into a provided buffer.
///
/// Returns a [`TiffError::UsageError`] if the chunk is smaller than the size indicated with a
/// call to [`Self::image_buffer_layout`]. Note that the alignment may be arbitrary, but an
/// alignment smaller than the preferred alignment may perform worse.
///
/// # Error
///
/// Returns an error if the buffer fits less than one plane. In particular, for non-planar
/// images returns an error if the buffer does not fit the required size.
pub fn read_image_bytes(&mut self, buffer: &mut [u8]) -> TiffResult<()> {
let (image, value_reader, scratch) = self.ensure_image_and_reader()?;
let readout = image.readout_for_image()?;
let ref layout @ image::PlaneLayout {
ref plane_offsets,
// We assume that is correct, so really it can be ignored.
total_bytes: _,
ref readout,
} = readout.to_plane_layout()?;
let used_plane_offsets = usize::from(layout.used_planes(buffer)?);
debug_assert!(used_plane_offsets >= 1, "Should have errored");
// For multi-band images, only the first band is read.
// Possible improvements:
// * pass requested band as parameter
// * collect bands to a RGB encoding result in case of RGB bands
for chunk in 0..readout.chunks_per_plane {
let x = (chunk % readout.chunks_across) as usize;
let y = (chunk / readout.chunks_across) as usize;
let buffer_offset = y * readout.chunk_col_stride + x * readout.chunk_row_stride;
for (idx, &plane_offset) in plane_offsets[..used_plane_offsets].iter().enumerate() {
let chunk = chunk + idx as u32 * readout.chunks_per_plane;
let offset = image.chunk_offsets[chunk as usize];
value_reader.reader.goto_offset(offset)?;
image.expand_chunk(
value_reader,
&mut buffer[plane_offset..][buffer_offset..],
readout,
chunk,
scratch,
)?;
}
}
Ok(())
}
}
#[doc(hidden)]
#[allow(deprecated)]
impl<R: Read + Seek> Decoder<R> {
/// Tries to retrieve a tag from the current image directory.
/// Return `Ok(None)` if the tag is not present.
#[deprecated = "Replace with `self.current_ifd().find_tag`"]
pub fn find_tag(&mut self, tag: Tag) -> TiffResult<Option<ifd::Value>> {
self.current_ifd().find_tag(tag)
}
/// Tries to retrieve a tag in the current image directory and convert it to the desired
/// unsigned type.
#[deprecated = "Replace with `self.current_ifd().find_tag_unsigned`"]
pub fn find_tag_unsigned<T: TryFrom<u64>>(&mut self, tag: Tag) -> TiffResult<Option<T>> {
self.current_ifd().find_tag_unsigned(tag)
}
/// Tries to retrieve a vector of all a tag's values and convert them to the desired unsigned
/// type.
#[deprecated = "Replace with `self.current_ifd().find_tag_unsigned_vec`"]
pub fn find_tag_unsigned_vec<T: TryFrom<u64>>(
&mut self,
tag: Tag,
) -> TiffResult<Option<Vec<T>>> {
self.current_ifd().find_tag_unsigned_vec(tag)
}
/// Tries to retrieve a tag from the current image directory and convert it to the desired
/// unsigned type. Returns an error if the tag is not present.
#[deprecated = "Replace with `self.current_ifd().get_tag_unsigned`"]
pub fn get_tag_unsigned<T: TryFrom<u64>>(&mut self, tag: Tag) -> TiffResult<T> {
self.current_ifd().get_tag_unsigned(tag)
}
/// Tries to retrieve a tag from the current image directory.
/// Returns an error if the tag is not present
#[deprecated = "Replace with `self.current_ifd().get_tag`"]
pub fn get_tag(&mut self, tag: Tag) -> TiffResult<ifd::Value> {
self.current_ifd().get_tag(tag)
}
#[deprecated = "Replace with `self.current_ifd().get_tag_u32`"]
pub fn get_tag_u32(&mut self, tag: Tag) -> TiffResult<u32> {
self.get_tag(tag)?.into_u32()
}
#[deprecated = "Replace with `self.current_ifd().get_tag_u64`"]
pub fn get_tag_u64(&mut self, tag: Tag) -> TiffResult<u64> {
self.get_tag(tag)?.into_u64()
}
/// Tries to retrieve a tag and convert it to the desired type.
#[deprecated = "Replace with `self.current_ifd().get_tag_f32`"]
pub fn get_tag_f32(&mut self, tag: Tag) -> TiffResult<f32> {
self.get_tag(tag)?.into_f32()
}
/// Tries to retrieve a tag and convert it to the desired type.
#[deprecated = "Replace with `self.current_ifd().get_tag_f64`"]
pub fn get_tag_f64(&mut self, tag: Tag) -> TiffResult<f64> {
self.get_tag(tag)?.into_f64()
}
/// Tries to retrieve a tag and convert it to the desired type.
#[deprecated = "Replace with `self.current_ifd().get_tag_u32_vec`"]
pub fn get_tag_u32_vec(&mut self, tag: Tag) -> TiffResult<Vec<u32>> {
self.get_tag(tag)?.into_u32_vec()
}
#[deprecated = "Replace with `self.current_ifd().get_tag_u16_vec`"]
pub fn get_tag_u16_vec(&mut self, tag: Tag) -> TiffResult<Vec<u16>> {
self.get_tag(tag)?.into_u16_vec()
}
#[deprecated = "Replace with `self.current_ifd().get_tag_u64_vec`"]
pub fn get_tag_u64_vec(&mut self, tag: Tag) -> TiffResult<Vec<u64>> {
self.get_tag(tag)?.into_u64_vec()
}
/// Tries to retrieve a tag and convert it to the desired type.
#[deprecated = "Replace with `self.current_ifd().get_tag_f32_vec`"]
pub fn get_tag_f32_vec(&mut self, tag: Tag) -> TiffResult<Vec<f32>> {
self.get_tag(tag)?.into_f32_vec()
}
/// Tries to retrieve a tag and convert it to the desired type.
#[deprecated = "Replace with `self.current_ifd().get_tag_f64_vec`"]
pub fn get_tag_f64_vec(&mut self, tag: Tag) -> TiffResult<Vec<f64>> {
self.get_tag(tag)?.into_f64_vec()
}
/// Tries to retrieve a tag and convert it to a 8bit vector.
#[deprecated = "Replace with `self.current_ifd().get_tag_u8_vec`"]
pub fn get_tag_u8_vec(&mut self, tag: Tag) -> TiffResult<Vec<u8>> {
self.get_tag(tag)?.into_u8_vec()
}
/// Tries to retrieve a tag and convert it to a ascii vector.
#[deprecated = "Replace with `self.current_ifd().get_tag_ascii_string`"]
pub fn get_tag_ascii_string(&mut self, tag: Tag) -> TiffResult<String> {
self.get_tag(tag)?.into_string()
}
#[deprecated = "Replace with `self.current_ifd().tag_iter`"]
pub fn tag_iter(&mut self) -> impl Iterator<Item = TiffResult<(Tag, ifd::Value)>> + '_ {
self.current_ifd().tag_iter()
}
}
impl<R: Seek + Read> ValueReader<R> {
pub(crate) fn read_directory(&mut self, ptr: IfdPointer) -> Result<Directory, TiffError> {
Self::read_ifd(&mut self.reader, self.bigtiff, ptr)
}
/// Reads a IFD entry.
// An IFD entry has four fields:
//
// Tag 2 bytes
// Type 2 bytes
// Count 4 bytes
// Value 4 bytes either a pointer the value itself
fn read_entry(
reader: &mut EndianReader<R>,
bigtiff: bool,
) -> TiffResult<Option<(Tag, ifd::Entry)>> {
let tag = Tag::from_u16_exhaustive(reader.read_u16()?);
let type_ = match Type::from_u16(reader.read_u16()?) {
Some(t) => t,
None => {
// Unknown type. Skip this entry according to spec.
reader.read_u32()?;
reader.read_u32()?;
return Ok(None);
}
};
let entry = if bigtiff {
let mut offset = [0; 8];
let count = reader.read_u64()?;
reader.inner().read_exact(&mut offset)?;
ifd::Entry::new_u64(type_, count, offset)
} else {
let mut offset = [0; 4];
let count = reader.read_u32()?;
reader.inner().read_exact(&mut offset)?;
ifd::Entry::new(type_, count, offset)
};
Ok(Some((tag, entry)))
}
/// Reads the IFD starting at the indicated location.
fn read_ifd(
reader: &mut EndianReader<R>,
bigtiff: bool,
ifd_location: IfdPointer,
) -> TiffResult<Directory> {
reader.goto_offset(ifd_location.0)?;
let mut entries: BTreeMap<_, _> = BTreeMap::new();
let num_tags = if bigtiff {
reader.read_u64()?
} else {
reader.read_u16()?.into()
};
for _ in 0..num_tags {
let (tag, entry) = match Self::read_entry(reader, bigtiff)? {
Some(val) => val,
None => {
continue;
} // Unknown data type in tag, skip
};
entries.insert(tag.to_u16(), entry);
}
let next_ifd = if bigtiff {
reader.read_u64()?
} else {
reader.read_u32()?.into()
};
let next_ifd = core::num::NonZeroU64::new(next_ifd);
Ok(Directory { entries, next_ifd })
}
}
impl IfdDecoder<'_> {
/// Retrieve the IFD entry for a given tag, if it exists.
///
/// The entry contains the metadata of the value, that is its type and count from which we can
/// calculate a total byte size.
pub fn find_entry(&self, tag: Tag) -> Option<ifd::Entry> {
self.inner.ifd.get(tag).cloned()
}
/// Tries to retrieve a tag.
/// Return `Ok(None)` if the tag is not present.
pub fn find_tag(&mut self, tag: Tag) -> TiffResult<Option<ifd::Value>> {
self.inner.find_tag(tag)
}
/// Retrieve a tag and reproduce its bytes into the provided buffer.
///
/// The buffer is unmodified if the tag is not present.
pub fn find_tag_buf(
&mut self,
tag: Tag,
buf: &mut ValueBuffer,
) -> TiffResult<Option<ifd::Entry>> {
self.inner.find_tag_buf(tag, buf)
}
/// Read bytes of a tag's value into a byte buffer.
pub fn find_tag_bytes(
&mut self,
tag: Tag,
buf: &mut [u8],
offset: u64,
) -> TiffResult<Option<usize>> {
self.inner.find_tag_raw(tag, buf, offset)
}
/// Tries to retrieve a tag and convert it to the desired unsigned type.
pub fn find_tag_unsigned<T: TryFrom<u64>>(&mut self, tag: Tag) -> TiffResult<Option<T>> {
self.find_tag(tag)?
.map(|v| v.into_u64())
.transpose()?
.map(|value| {
T::try_from(value).map_err(|_| TiffFormatError::InvalidTagValueType(tag).into())
})
.transpose()
}
/// Tries to retrieve a vector of all a tag's values and convert them to
/// the desired unsigned type.
pub fn find_tag_unsigned_vec<T: TryFrom<u64>>(
&mut self,
tag: Tag,
) -> TiffResult<Option<Vec<T>>> {
self.find_tag(tag)?
.map(|v| v.into_u64_vec())
.transpose()?
.map(|v| {
v.into_iter()
.map(|u| {
T::try_from(u).map_err(|_| TiffFormatError::InvalidTagValueType(tag).into())
})
.collect()
})
.transpose()
}
/// Tries to retrieve a tag and convert it to the desired unsigned type.
/// Returns an error if the tag is not present.
pub fn get_tag_unsigned<T: TryFrom<u64>>(&mut self, tag: Tag) -> TiffResult<T> {
self.find_tag_unsigned(tag)?
.ok_or_else(|| TiffFormatError::RequiredTagNotFound(tag).into())
}
/// Tries to retrieve a tag.
/// Returns an error if the tag is not present
pub fn get_tag(&mut self, tag: Tag) -> TiffResult<ifd::Value> {
match self.find_tag(tag)? {
Some(val) => Ok(val),
None => Err(TiffError::FormatError(
TiffFormatError::RequiredTagNotFound(tag),
)),
}
}
/// Tries to retrieve a tag and convert it to the desired type.
pub fn get_tag_u32(&mut self, tag: Tag) -> TiffResult<u32> {
self.get_tag(tag)?.into_u32()
}
pub fn get_tag_u64(&mut self, tag: Tag) -> TiffResult<u64> {
self.get_tag(tag)?.into_u64()
}
/// Tries to retrieve a tag and convert it to the desired type.
pub fn get_tag_f32(&mut self, tag: Tag) -> TiffResult<f32> {
self.get_tag(tag)?.into_f32()
}
/// Tries to retrieve a tag and convert it to the desired type.
pub fn get_tag_f64(&mut self, tag: Tag) -> TiffResult<f64> {
self.get_tag(tag)?.into_f64()
}
/// Tries to retrieve a tag and convert it to the desired type.
pub fn get_tag_u32_vec(&mut self, tag: Tag) -> TiffResult<Vec<u32>> {
self.get_tag(tag)?.into_u32_vec()
}
pub fn get_tag_u16_vec(&mut self, tag: Tag) -> TiffResult<Vec<u16>> {
self.get_tag(tag)?.into_u16_vec()
}
pub fn get_tag_u64_vec(&mut self, tag: Tag) -> TiffResult<Vec<u64>> {
self.get_tag(tag)?.into_u64_vec()
}
/// Tries to retrieve a tag and convert it to the desired type.
pub fn get_tag_f32_vec(&mut self, tag: Tag) -> TiffResult<Vec<f32>> {
self.get_tag(tag)?.into_f32_vec()
}
/// Tries to retrieve a tag and convert it to the desired type.
pub fn get_tag_f64_vec(&mut self, tag: Tag) -> TiffResult<Vec<f64>> {
self.get_tag(tag)?.into_f64_vec()
}
/// Tries to retrieve a tag and convert it to a 8bit vector.
pub fn get_tag_u8_vec(&mut self, tag: Tag) -> TiffResult<Vec<u8>> {
self.get_tag(tag)?.into_u8_vec()
}
/// Tries to retrieve a tag and convert it to a ascii vector.
pub fn get_tag_ascii_string(&mut self, tag: Tag) -> TiffResult<String> {
self.get_tag(tag)?.into_string()
}
/// Inspect the raw underlying directory.
pub fn directory(&self) -> &Directory {
self.inner.ifd
}
}
impl<'l> IfdDecoder<'l> {
/// Returns an iterator over all tags in the current image, along with their values.
pub fn tag_iter(self) -> impl Iterator<Item = TiffResult<(Tag, ifd::Value)>> + 'l {
self.inner
.ifd
.iter()
.map(|(tag, entry)| match self.inner.decoder.entry_val(entry) {
Ok(value) => Ok((tag, value)),
Err(err) => Err(err),
})
}
}
#[cfg(test)]
mod tests {
use super::Decoder;
use crate::{
bytecast, encoder,
tags::{ByteOrder, Tag, ValueBuffer},
};
#[test]
fn equivalence_of_tag_readers() {
let file = std::fs::File::open(concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/images/int8_rgb.tif"
))
.unwrap();
let mut decoder = Decoder::open(file).unwrap();
let file_bo = decoder.byte_order();
decoder.next_image().unwrap();
let mut ifd = decoder.current_ifd();
{
let value = ifd
.find_tag(Tag::BitsPerSample)
.unwrap()
.expect("must have BitsPerSample");
let samples = value.into_u16_vec().unwrap();
assert_eq!(samples.as_slice(), [8, 8, 8]);
}
{
let mut value = ValueBuffer::from_value(&[0u16; 4]);
let _entry = ifd
.find_tag_buf(Tag::BitsPerSample, &mut value)
.unwrap()
.expect("must have BitsPerSample");
value.set_byte_order(ByteOrder::native());
assert_eq!(value.as_bytes(), bytecast::u16_as_ne_bytes(&[8, 8, 8]));
}
{
let mut by_bytes = [0u16; 4];
let entry = ifd
.find_entry(Tag::BitsPerSample)
.expect("must have BitsPerSample");
let byte_len = ifd
.find_tag_bytes(
Tag::BitsPerSample,
bytecast::u16_as_ne_mut_bytes(&mut by_bytes),
0,
)
.unwrap()
.expect("must have BitsPerSample");
assert_eq!(byte_len, 3 * std::mem::size_of::<u16>());
file_bo.convert(
entry.field_type(),
bytecast::u16_as_ne_mut_bytes(&mut by_bytes[..3]),
ByteOrder::native(),
);
assert_eq!(&by_bytes[..3], &[8, 8, 8]);
}
}
#[test]
fn non_image_directory_between() {
let mut cursor = std::io::Cursor::new(vec![]);
{
let mut encoder = encoder::TiffEncoder::new(&mut cursor).unwrap();
encoder
.write_image::<encoder::colortype::Gray8>(1, 1, &[0])
.unwrap();
encoder.image_directory().unwrap().finish().unwrap();
encoder
.write_image::<encoder::colortype::Gray8>(1, 1, &[0])
.unwrap();
}
cursor.set_position(0);
let (first_ptr, second_ptr, third_ptr);
{
let mut decoder = Decoder::open(&mut cursor).unwrap();
decoder.next_image().unwrap();
first_ptr = decoder.ifd_pointer();
let first_ifd = decoder.current_ifd();
assert!(first_ifd.directory().get(Tag::ImageWidth).is_some());
assert!(decoder.more_images());
assert!(decoder.next_image().is_err());
let broken_ifd = decoder.current_ifd();
assert!(broken_ifd.directory().get(Tag::ImageWidth).is_some());
assert!(decoder.next_directory().is_ok());
second_ptr = decoder.ifd_pointer();
let second_ifd = decoder.current_ifd();
assert!(second_ifd.directory().get(Tag::ImageWidth).is_none());
assert!(decoder.more_images());
assert!(decoder.next_image().is_ok());
third_ptr = decoder.ifd_pointer();
let third_ifd = decoder.current_ifd();
assert!(third_ifd.directory().get(Tag::ImageWidth).is_some());
assert!(!decoder.more_images());
assert!(decoder.seek_to_image(1).is_err());
assert_eq!(decoder.ifd_pointer(), third_ptr);
assert!(decoder.seek_to_directory(1).is_ok());
assert_eq!(decoder.ifd_pointer(), second_ptr);
}
cursor.set_position(0);
{
let mut seek_immediately = Decoder::open(&mut cursor).unwrap();
assert!(seek_immediately.seek_to_image(0).is_ok());
assert_eq!(seek_immediately.ifd_pointer(), first_ptr);
assert!(seek_immediately.seek_to_image(1).is_err());
assert!(seek_immediately.seek_to_image(2).is_ok());
assert_eq!(seek_immediately.ifd_pointer(), third_ptr);
assert!(seek_immediately
.current_ifd()
.directory()
.get(Tag::ImageWidth)
.is_some());
}
cursor.set_position(0);
{
let mut seek_immediately = Decoder::open(&mut cursor).unwrap();
assert!(seek_immediately.seek_to_directory(1).is_ok());
assert_eq!(seek_immediately.ifd_pointer(), second_ptr);
assert!(seek_immediately
.current_ifd()
.directory()
.get(Tag::ImageWidth)
.is_none());
}
}
#[test]
fn non_image_directory_start() {
let mut cursor = std::io::Cursor::new(vec![]);
{
let mut encoder = encoder::TiffEncoder::new(&mut cursor).unwrap();
encoder.image_directory().unwrap().finish().unwrap();
encoder
.write_image::<encoder::colortype::Gray8>(1, 1, &[0])
.unwrap();
}
cursor.set_position(0);
let header = super::TiffHeader::parse(&mut cursor).unwrap();
let mut decoder = header.open(cursor);
let indicated_next = decoder.current_ifd().directory().next();
assert!(decoder.more_images());
assert_eq!(decoder.ifd_pointer(), None);
assert!(decoder.next_directory().is_ok());
let first_ifd = decoder.current_ifd();
assert!(first_ifd.directory().get(Tag::ImageWidth).is_none());
assert!(decoder.more_images());
assert_eq!(decoder.ifd_pointer(), indicated_next);
assert!(decoder.next_image().is_ok());
let second_ifd = decoder.current_ifd();
assert!(second_ifd.directory().get(Tag::ImageWidth).is_some());
assert!(!decoder.more_images());
}
}