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, /// Minimum number of bytes to represent a row of image data of the requested content. pub row_stride: Option, /// Number of planes in the image. pub planes: usize, /// Number of bytes used to represent one plane. pub plane_stride: Option, /// 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 { 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, ) -> TiffResult { 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(&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 { /// 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, current_ifd_pointer: Option, directory: Directory, /// The IFDs we visited already in this chain of IFDs. ifd_offsets: Vec, /// Map from the ifd into the `ifd_offsets` ordered list. seen_ifds: cycles::IfdCycles, image: Option, /// 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, } /// 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 { reader: EndianReader, 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(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(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(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` 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, ) { 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(mut r: R) -> TiffResult { 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(&self, r: R) -> Decoder { 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(), } } } /// Methods to manipulate the directory state impl Decoder { /// 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> { 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> { 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 { 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, &mut Vec)> { 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(&mut self, reader: S) -> Decoder { 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 { 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, next_ifd: Option, ) -> 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>> { 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 } } /// Methods that interact with the underlying reader. impl Decoder { /// 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 { 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 { 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 { self.value_reader.reader.read_u16() } /// Reads a TIFF sshort value #[inline] pub fn read_sshort(&mut self) -> Result { self.value_reader.reader.read_i16() } /// Reads a TIFF long value #[inline] pub fn read_long(&mut self) -> Result { self.value_reader.reader.read_u32() } /// Reads a TIFF slong value #[inline] pub fn read_slong(&mut self) -> Result { self.value_reader.reader.read_i32() } /// Reads a TIFF float value #[inline] pub fn read_float(&mut self) -> Result { self.value_reader.reader.read_f32() } /// Reads a TIFF double value #[inline] pub fn read_double(&mut self) -> Result { self.value_reader.reader.read_f64() } #[inline] pub fn read_long8(&mut self) -> Result { self.value_reader.reader.read_u64() } #[inline] pub fn read_slong8(&mut self) -> Result { self.value_reader.reader.read_i64() } /// Reads a string #[inline] pub fn read_string(&mut self, length: usize) -> TiffResult { 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> 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) } } /// Methods that interact with the current directory as an image. impl Decoder { /// 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 Decoder { /// 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> { 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>(&mut self, tag: Tag) -> TiffResult> { 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>( &mut self, tag: Tag, ) -> TiffResult>> { 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>(&mut self, tag: Tag) -> TiffResult { 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 { 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 { 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 { 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 { 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 { 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> { 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> { 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> { 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> { 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> { 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> { 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 { self.get_tag(tag)?.into_string() } #[deprecated = "Replace with `self.current_ifd().tag_iter`"] pub fn tag_iter(&mut self) -> impl Iterator> + '_ { self.current_ifd().tag_iter() } } impl ValueReader { pub(crate) fn read_directory(&mut self, ptr: IfdPointer) -> Result { 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, bigtiff: bool, ) -> TiffResult> { 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, bigtiff: bool, ifd_location: IfdPointer, ) -> TiffResult { 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 { 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> { 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> { 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> { 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>(&mut self, tag: Tag) -> TiffResult> { 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>( &mut self, tag: Tag, ) -> TiffResult>> { 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>(&mut self, tag: Tag) -> TiffResult { 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 { 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 { self.get_tag(tag)?.into_u32() } pub fn get_tag_u64(&mut self, tag: Tag) -> TiffResult { 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 { 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 { 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> { self.get_tag(tag)?.into_u32_vec() } pub fn get_tag_u16_vec(&mut self, tag: Tag) -> TiffResult> { self.get_tag(tag)?.into_u16_vec() } pub fn get_tag_u64_vec(&mut self, tag: Tag) -> TiffResult> { 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> { 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> { 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> { 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 { 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> + '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::()); 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::(1, 1, &[0]) .unwrap(); encoder.image_directory().unwrap().finish().unwrap(); encoder .write_image::(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::(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()); } }