2245 lines
84 KiB
Rust
2245 lines
84 KiB
Rust
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>,
|
||
}
|
||
|
||
/// 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>,
|
||
}
|
||
|
||
fn rev_hpredict_nsamp(buf: &mut [u8], bit_depth: u8, samples: u16) {
|
||
fn one_byte_predict<const N: usize>(buf: &mut [u8]) {
|
||
for i in N..buf.len() {
|
||
buf[i] = buf[i].wrapping_add(buf[i - N]);
|
||
}
|
||
}
|
||
|
||
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.")
|
||
}
|
||
}
|
||
}
|
||
|
||
fn predict_f32(input: &mut [u8], output: &mut [u8], samples: u16) {
|
||
let samples = usize::from(samples);
|
||
|
||
for i in samples..input.len() {
|
||
input[i] = input[i].wrapping_add(input[i - samples]);
|
||
}
|
||
|
||
for (i, chunk) in output.chunks_mut(4).enumerate() {
|
||
chunk.copy_from_slice(&u32::to_ne_bytes(u32::from_be_bytes([
|
||
input[i],
|
||
input[input.len() / 4 + i],
|
||
input[input.len() / 4 * 2 + i],
|
||
input[input.len() / 4 * 3 + i],
|
||
])));
|
||
}
|
||
}
|
||
|
||
fn predict_f16(input: &mut [u8], output: &mut [u8], samples: u16) {
|
||
let samples = usize::from(samples);
|
||
|
||
for i in samples..input.len() {
|
||
input[i] = input[i].wrapping_add(input[i - samples]);
|
||
}
|
||
|
||
for (i, chunk) in output.chunks_mut(2).enumerate() {
|
||
chunk.copy_from_slice(&u16::to_ne_bytes(u16::from_be_bytes([
|
||
input[i],
|
||
input[input.len() / 2 + i],
|
||
])));
|
||
}
|
||
}
|
||
|
||
fn predict_f64(input: &mut [u8], output: &mut [u8], samples: u16) {
|
||
let samples = usize::from(samples);
|
||
|
||
for i in samples..input.len() {
|
||
input[i] = input[i].wrapping_add(input[i - samples]);
|
||
}
|
||
|
||
for (i, chunk) in output.chunks_mut(8).enumerate() {
|
||
chunk.copy_from_slice(&u64::to_ne_bytes(u64::from_be_bytes([
|
||
input[i],
|
||
input[input.len() / 8 + i],
|
||
input[input.len() / 8 * 2 + i],
|
||
input[input.len() / 8 * 3 + i],
|
||
input[input.len() / 8 * 4 + i],
|
||
input[input.len() / 8 * 5 + i],
|
||
input[input.len() / 8 * 6 + i],
|
||
input[input.len() / 8 * 7 + i],
|
||
])));
|
||
}
|
||
}
|
||
|
||
fn fix_endianness_and_predict(
|
||
buf: &mut [u8],
|
||
bit_depth: u8,
|
||
samples: u16,
|
||
byte_order: ByteOrder,
|
||
predictor: Predictor,
|
||
) {
|
||
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 => {
|
||
let mut buffer_copy = buf.to_vec();
|
||
match bit_depth {
|
||
16 => predict_f16(&mut buffer_copy, buf, samples),
|
||
32 => predict_f32(&mut buffer_copy, buf, samples),
|
||
64 => predict_f64(&mut buffer_copy, 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,
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <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>)> {
|
||
if self.image.is_some() {
|
||
return Ok((self.image.as_mut().unwrap(), &mut self.value_reader));
|
||
}
|
||
|
||
let image = Image::from_reader(&mut self.value_reader, &self.directory)?;
|
||
Ok((self.image.insert(image), &mut self.value_reader))
|
||
}
|
||
|
||
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),
|
||
}
|
||
}
|
||
|
||
/// 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) = 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)?;
|
||
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) = 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)?;
|
||
}
|
||
|
||
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) = 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,
|
||
)?;
|
||
}
|
||
}
|
||
|
||
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());
|
||
}
|
||
}
|