Compare commits

..
Author SHA1 Message Date
A. Molzer 97d357d446 Prepare ImageDecoder split, avoid as_mut|unwrap 2026-04-04 10:20:10 +02:00
A. Molzer 61867b46f9 Make decoder's Image an option
Instead all methods with mutable self will initialize it on-demand.
There are only few methods that take `&self` and can not do that. For
those that have no error/option return we panic but both of these could
panic previously already. This should be fine by documentation alone. In
particular, the image size can be queried safely.
2026-04-04 10:20:08 +02:00
Aurelia Molzer 0f1f075abb Merge pull request #372 from image-rs/deprecate-and-hide-tag-reading
Deprecate and hide direct tag reading
2026-04-04 08:13:45 +00:00
Aurelia Molzer 5576417abe Merge pull request #374 from image-rs/value-read-provider
Add a `Read`-er for entry values
2026-03-31 16:47:14 +00:00
A. Molzer 52b9cc7a19 Rename EntryBytesReader module 2026-03-31 18:35:19 +02:00
A. Molzer 006f0b0fa4 Directly implement Seek::stream_position 2026-03-31 18:34:16 +02:00
A. Molzer 5b8a2832c3 Add design note 2026-03-31 18:34:15 +02:00
A. Molzer f93a6380e6 Extra utils for reading tags 2026-03-31 18:34:14 +02:00
A. Molzer 8a1b65fe68 Expand testing 2026-03-31 14:01:39 +02:00
A. Molzer a2996ab34e Basic test for EntryByteReader 2026-03-31 09:16:33 +02:00
A. Molzer f890d22657 Add EntryBytesReader: impl Read + Seek 2026-03-31 09:16:33 +02:00
A. Molzer 6b04bff6b2 Relax Decoder, new interface documentation
The bounds `Read + Seek` are not representational so we may remove them
from the struct itself. This also allows us to use more precise bounds
for direct access to the inner stream, especially were that does not
seek.

Groups operations according to their use case.
2026-03-31 09:05:43 +02:00
A. Molzer bd0dccef3a Deprecate Decoder::*tag methods
Funnel users towards the `IfdDecoder` instead. That type does not have
a generic over the underlying reader. It also works with a directory of
your own choosing. So the duplicates that should go are those directly
on the decoder.
2026-03-31 05:47:04 +02:00
11 changed files with 734 additions and 223 deletions

No files matched your search

+362
View File
@@ -0,0 +1,362 @@
use std::io::{self, Cursor, Read, Seek};
use crate::{
decoder::{ifd::Entry, stream::EndianReader, ValueReader},
tags::{ByteOrder, IfdPointer, Type},
TiffResult,
};
pub struct EntryBytesReader<'lt, R> {
inner: &'lt mut EndianReader<R>,
variant: Variant,
}
const OFFSET_BUFFER_LEN: usize = 8;
enum Variant {
Immediate {
/// An in-value reader if the offset field contains the data. Note that we rotate it to the
/// back of this buffer, not the front, so that `Cursor` already contains the right end.
/// We do not let you seek back beyond our start.
data: Cursor<[u8; OFFSET_BUFFER_LEN]>,
value_bytes: u8,
},
InFile {
// the length relative to the base so we can seek relative without querying the position.
value_bytes: u64,
remainder: u64,
base: u64,
},
}
impl<'lt, R> EntryBytesReader<'lt, R> {
pub(super) fn from_entry(inner: &'lt mut ValueReader<R>, entry: Entry) -> TiffResult<Self>
where
R: Read + Seek,
{
let offset: [_; OFFSET_BUFFER_LEN] = *entry.offset_raw();
let limit = if inner.bigtiff { 8 } else { 4 };
assert!(limit as usize <= OFFSET_BUFFER_LEN);
let value_bytes = entry.field_type().value_bytes(entry.count())?;
if value_bytes <= limit {
// We move the data to the back of the array whereas it is in the front right now.
let mut value = offset;
value.rotate_left(value_bytes as usize);
let mut cursor = Cursor::new(value);
cursor.set_position(OFFSET_BUFFER_LEN as u64 - value_bytes);
Ok(EntryBytesReader {
inner: &mut inner.reader,
variant: Variant::Immediate {
data: cursor,
value_bytes: value_bytes as u8,
},
})
} else {
// remainder > limit >= 0 so this is an unreachable panic.
let pointer = if inner.bigtiff {
let mut offset = offset;
inner
.reader
.byte_order
.convert(Type::IFD8, &mut offset[..], ByteOrder::native());
IfdPointer(u64::from_ne_bytes(offset))
} else {
let mut offset = *offset.first_chunk::<4>().unwrap();
inner
.reader
.byte_order
.convert(Type::IFD, &mut offset[..], ByteOrder::native());
let offset = u32::from_ne_bytes(offset);
IfdPointer(u64::from(offset))
};
inner.reader.goto_offset(pointer.0)?;
Ok(EntryBytesReader {
inner: &mut inner.reader,
variant: Variant::InFile {
value_bytes,
remainder: value_bytes,
base: pointer.0,
},
})
}
}
}
impl<R: Read> Read for EntryBytesReader<'_, R> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
match &mut self.variant {
Variant::Immediate { data, .. } => data.read(buf),
Variant::InFile { remainder, .. } => {
let bound = usize::try_from(*remainder).unwrap_or(usize::MAX);
let avail = buf.len().min(bound);
let read = self.inner.inner().read(&mut buf[..avail])?;
// On a well-behaved `Read`: read <= avail <= bound <=(mathematical) remainder
let consume = read as u64;
*remainder -= consume;
Ok(read)
}
}
}
fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> {
match &mut self.variant {
Variant::Immediate { data, .. } => data.read_exact(buf),
Variant::InFile { remainder, .. } => {
let bound = usize::try_from(*remainder).unwrap_or(usize::MAX);
// Fail early if we would be sure to over-read.
if buf.len() > bound {
return Err(io::Error::new(
io::ErrorKind::UnexpectedEof,
"Tiff value shorter than the requested buffer size",
));
}
// since buf.len() <= bound <=(mathematical) remainder
let consume = buf.len() as u64;
self.inner.inner().read_exact(buf)?;
*remainder -= consume;
Ok(())
}
}
}
}
impl<R: Read + Seek> Seek for EntryBytesReader<'_, R> {
fn seek(&mut self, offset: io::SeekFrom) -> io::Result<u64> {
let (value_bytes, remainder) = match &self.variant {
Variant::Immediate { data, value_bytes } => {
let remainder = data.get_ref().len() as u64 - data.position();
(*value_bytes as u64, remainder)
}
&Variant::InFile {
value_bytes,
remainder,
base: _,
} => (value_bytes, remainder),
};
// Normalize to an offset from the start
let offset_from_start = match offset {
io::SeekFrom::Start(n) => Some(n),
io::SeekFrom::Current(n) => {
let front = value_bytes - remainder;
front.checked_add_signed(n)
}
io::SeekFrom::End(n) => value_bytes.checked_add_signed(n),
};
let Some((offset, remainder)) = offset_from_start.and_then(|n| {
let remainder = value_bytes.checked_sub(n)?;
Some((n, remainder))
}) else {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"seek out of bounds of entry value",
));
};
match &mut self.variant {
Variant::Immediate { data, .. } => {
let start = OFFSET_BUFFER_LEN as u64 - value_bytes;
data.set_position(start + offset);
Ok(offset)
}
&mut Variant::InFile { base, .. } => {
// We checked this against overflow on construction. Ignore the offset we get.
self.inner
.inner()
.seek(io::SeekFrom::Start(base + offset))?;
self.variant = Variant::InFile {
value_bytes,
remainder,
base,
};
Ok(offset)
}
}
}
fn stream_position(&mut self) -> io::Result<u64> {
Ok(match &self.variant {
Variant::Immediate { data, value_bytes } => {
// Recall the value is at the end of the buffer.
let from_end = OFFSET_BUFFER_LEN as u64 - data.position();
u64::from(*value_bytes) - from_end
}
// Note: we might think about using the inner `stream_position` here. However that
// exposes something interesting: If the internal stream is modified concurrently from
// another instance (e.g. a shared `File`) then we may lose track of it anyways.
// Especially the `Read` implementation is not bounded on `Seek` so it could not
// acquire the position. And it should not since that is just a different form of race
// where we risk the position being modified by a different thread just as well.
//
// So in the interest of least surprise we just assume that we can track the position
// accurately here and that the inner reader uses for instance `read_at` consistently.
// We own the value (or mutable reference) it after all.
//
// Also this one MUST be compatible with `seek(SeekFrom::Current(0))` as per trait
// defintion—so at least keep those consistent if changing anything.
&Variant::InFile {
remainder,
value_bytes,
..
} => value_bytes - remainder,
})
}
}
#[cfg(test)]
mod tests {
use crate::{decoder::Decoder, encoder, tags};
use std::io::{Read as _, Seek as _, SeekFrom};
#[test]
fn can_seek_tag_bytes() -> Result<(), crate::error::TiffError> {
let file = std::fs::File::open(concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/images/int8_rgb.tif"
))
.unwrap();
let mut decoder = Decoder::open(file).unwrap();
decoder.next_directory().unwrap();
let mut buffer = crate::tags::ValueBuffer::default();
decoder
.current_ifd()
.find_tag_buf(tags::Tag::StripByteCounts, &mut buffer)
.expect("some strip byte count is present");
let bytes_of_value = buffer.as_bytes().len() as u64;
let mut reader = decoder
.read_tag(tags::Tag::StripByteCounts)?
.expect("some strip byte count is present");
let mut data = vec![];
reader.read_to_end(&mut data)?;
// `ValueBuffer` also stores its data in the file endianess until converted.
assert_eq!(reader.stream_position()?, bytes_of_value);
assert_eq!(buffer.as_bytes(), data);
reader.seek(SeekFrom::Start(0))?;
assert_eq!(reader.stream_position()?, 0);
reader.read_to_end(&mut data)?;
assert_eq!(reader.stream_position()?, bytes_of_value);
let (first, second) = data.split_at(bytes_of_value as usize);
assert_eq!(first, second);
Ok(())
}
#[test]
#[allow(clippy::seek_from_current)] // We want to test that functionality.
fn seek_from_end() -> Result<(), crate::error::TiffError> {
const ARTIST: &str = "99 little bugs in the code";
let data = {
let mut data = std::io::Cursor::new(vec![]);
let mut encoder = encoder::TiffEncoder::new(&mut data)?;
let mut dir = encoder.image_directory()?;
dir.write_tag(tags::Tag::Artist, ARTIST)?;
dir.finish()?;
data.into_inner()
};
let file = std::io::Cursor::new(data);
let mut decoder = Decoder::open(file).unwrap();
decoder.next_directory().unwrap();
let mut reader = decoder
.read_tag(tags::Tag::Artist)?
.expect("the artist is present?");
let mut value = vec![];
reader.read_to_end(&mut value)?;
let (&nul, value) = value.split_last().unwrap();
assert_eq!(value, ARTIST.as_bytes());
assert_eq!(nul, b'\0');
assert_eq!(reader.seek(SeekFrom::End(0))?, ARTIST.len() as u64 + 1);
assert!(reader.seek(SeekFrom::End(1)).is_err());
assert_eq!(reader.stream_position()?, ARTIST.len() as u64 + 1);
// Check relative seeks by comparing against the "code"
let code_offset = reader.seek(SeekFrom::End(-5))?;
let mut is_code = [0u8; 4];
reader.read_exact(&mut is_code)?;
assert_eq!(is_code, *value.last_chunk::<4>().unwrap());
assert_eq!(reader.seek(SeekFrom::Start(0))?, 0);
assert_eq!(reader.stream_position()?, 0);
assert_eq!(reader.seek(SeekFrom::Current(0))?, 0);
assert_eq!(reader.stream_position()?, 0);
assert!(reader.seek(SeekFrom::Current(-1)).is_err());
assert_eq!(reader.stream_position()?, 0);
reader.seek(SeekFrom::Start(code_offset))?;
reader.read_exact(&mut is_code)?;
assert_eq!(reader.stream_position()?, ARTIST.len() as u64);
assert_eq!(is_code, *value.last_chunk::<4>().unwrap());
Ok(())
}
/// Check that the reader from an `offset` field also works.
#[test]
fn read_from_offset() -> Result<(), crate::error::TiffError> {
let data = {
let mut data = std::io::Cursor::new(vec![]);
let mut encoder = encoder::TiffEncoder::new(&mut data)?;
let mut dir = encoder.image_directory()?;
dir.write_tag(
tags::Tag::PlanarConfiguration,
tags::PlanarConfiguration::Planar,
)?;
dir.finish()?;
data.into_inner()
};
let file = std::io::Cursor::new(data);
let mut decoder = Decoder::open(file).unwrap();
decoder.next_directory().unwrap();
let bo = decoder.byte_order();
let mut reader = decoder
.read_tag(tags::Tag::PlanarConfiguration)?
.expect("some artist is present");
let mut buf = [0; 2];
reader.read_exact(&mut buf)?;
bo.convert(tags::Type::SHORT, &mut buf, tags::ByteOrder::BigEndian);
assert_eq!(buf, [0, 2]);
// Check relative seeks, this item is only two bytes long.
assert!(reader.seek(SeekFrom::End(-3)).is_err());
Ok(())
}
}
+1 -1
View File
@@ -393,7 +393,7 @@ impl Entry {
self.count
}
pub(crate) fn offset(&self) -> &[u8] {
pub(crate) fn offset_raw(&self) -> &[u8; 8] {
&self.offset
}
+11 -26
View File
@@ -155,32 +155,6 @@ pub(crate) struct ReadoutLayout {
}
impl Image {
pub(crate) fn no_image() -> Image {
Image {
width: 0,
height: 0,
bits_per_sample: 1,
samples: 1,
extra_samples: vec![],
photometric_samples: 1,
sample_format: SampleFormat::Uint,
photometric_interpretation: PhotometricInterpretation::BlackIsZero,
compression_method: CompressionMethod::None,
jpeg_tables: None,
predictor: Predictor::None,
chunk_type: ChunkType::Strip,
planar_config: PlanarConfiguration::Chunky,
strip_decoder: None,
tile_attributes: None,
chunk_offsets: Vec::new(),
chunk_bytes: Vec::new(),
chroma_subsampling: (2, 2),
fill_order: 1,
color_map: None,
decompression_to_host_endian: false,
}
}
pub fn from_reader<R: Read + Seek>(
decoder: &mut ValueReader<R>,
ifd: &Directory,
@@ -528,6 +502,17 @@ impl Image {
})
}
pub(crate) fn check_chunk_type(&self, expected: ChunkType) -> TiffResult<()> {
if expected != self.chunk_type {
return Err(TiffError::UsageError(UsageError::InvalidChunkType(
expected,
self.chunk_type,
)));
}
Ok(())
}
pub(crate) fn colortype(&self) -> TiffResult<ColorType> {
let is_alpha_extra_samples = matches!(
self.extra_samples.as_slice(),
+292 -140
View File
@@ -15,6 +15,7 @@ use self::stream::EndianReader;
mod buffer;
mod cycles;
mod entry_reader;
pub mod ifd;
mod image;
mod logluv;
@@ -22,6 +23,7 @@ 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;
@@ -229,12 +231,19 @@ pub struct TiffHeader {
/// The representation of a TIFF decoder
///
/// Currently does not support decoding of interlaced images
/// 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>
where
R: Read + Seek,
{
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>,
@@ -244,7 +253,7 @@ where
ifd_offsets: Vec<IfdPointer>,
/// Map from the ifd into the `ifd_offsets` ordered list.
seen_ifds: cycles::IfdCycles,
image: Image,
image: Option<Image>,
}
/// All the information needed to read and interpret byte slices from the underlying file, i.e. to
@@ -617,11 +626,12 @@ impl TiffHeader {
dir
},
seen_ifds: cycles::IfdCycles::new(),
image: Image::no_image(),
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.
@@ -699,22 +709,20 @@ impl<R: Read + Seek> Decoder<R> {
self
}
pub fn dimensions(&mut self) -> TiffResult<(u32, u32)> {
Ok((self.image.width, self.image.height))
// 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));
}
pub fn colortype(&mut self) -> TiffResult<ColorType> {
self.image.colortype()
let image = Image::from_reader(&mut self.value_reader, &self.directory)?;
Ok((self.image.insert(image), &mut self.value_reader))
}
/// 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.color_map.as_deref()
fn ensure_image(&mut self) -> TiffResult<&mut Image> {
Ok(self.ensure_image_and_reader()?.0)
}
/// The offset of the directory representing the current image.
@@ -722,13 +730,33 @@ impl<R: Read + Seek> Decoder<R> {
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 = Image::no_image();
self.image = None;
self.current_ifd_pointer = Some(offset);
self.directory = directory;
Ok(())
@@ -742,7 +770,7 @@ impl<R: Read + Seek> Decoder<R> {
/// 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 = Image::from_reader(&mut self.value_reader, &directory)?;
self.image = Some(Image::from_reader(&mut self.value_reader, &directory)?);
self.current_ifd_pointer = Some(offset);
self.directory = directory;
Ok(())
@@ -805,15 +833,21 @@ impl<R: Read + Seek> Decoder<R> {
/// Reads the next IFD.
///
/// If there is no further image in the TIFF file a format error is returned.
/// To determine whether there are more images call `TIFFDecoder::more_images` instead.
/// 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 = Image::no_image();
self.image = None;
self.current_ifd_pointer = Some(offset);
self.directory = directory;
Ok(())
@@ -822,19 +856,65 @@ impl<R: Read + Seek> Decoder<R> {
/// 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 `TIFFDecoder::more_images` instead.
/// 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 = Image::from_reader(&mut self.value_reader, &directory)?;
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()
@@ -849,6 +929,23 @@ impl<R: Read + Seek> Decoder<R> {
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> {
@@ -859,11 +956,6 @@ impl<R: Read + Seek> Decoder<R> {
}
}
/// Returns a mutable reference to the stream being decoded.
pub fn inner(&mut self) -> &mut R {
self.value_reader.reader.inner()
}
/// Reads a TIFF byte value
#[inline]
pub fn read_byte(&mut self) -> Result<u8, io::Error> {
@@ -943,6 +1035,33 @@ impl<R: Read + Seek> Decoder<R> {
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> {
@@ -953,14 +1072,58 @@ impl<R: Read + Seek> Decoder<R> {
/// Moves the cursor to the specified offset
#[inline]
pub fn goto_offset(&mut self, offset: u32) -> io::Result<()> {
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<()> {
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.
///
@@ -985,40 +1148,37 @@ impl<R: Read + Seek> Decoder<R> {
self.value_reader.read_directory(ptr)
}
fn check_chunk_type(&self, expected: ChunkType) -> TiffResult<()> {
if expected != self.image.chunk_type {
return Err(TiffError::UsageError(UsageError::InvalidChunkType(
expected,
self.image.chunk_type,
)));
}
Ok(())
}
/// 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.chunk_type
self.image.as_ref().unwrap().chunk_type
}
/// Number of strips in image
pub fn strip_count(&mut self) -> TiffResult<u32> {
self.check_chunk_type(ChunkType::Strip)?;
let rows_per_strip = self.image.strip_decoder.as_ref().unwrap().rows_per_strip;
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 self.image.height.checked_add(rows_per_strip - 1) {
let height = match image.height.checked_add(rows_per_strip - 1) {
Some(h) => h,
None => return Err(TiffError::IntSizeError),
};
let strips = match self.image.planar_config {
let strips = match image.planar_config {
PlanarConfiguration::Chunky => height / rows_per_strip,
PlanarConfiguration::Planar => height / rows_per_strip * self.image.samples as u32,
PlanarConfiguration::Planar => height / rows_per_strip * image.samples as u32,
};
Ok(strips)
@@ -1026,8 +1186,9 @@ impl<R: Read + Seek> Decoder<R> {
/// Number of tiles in image
pub fn tile_count(&mut self) -> TiffResult<u32> {
self.check_chunk_type(ChunkType::Tile)?;
Ok(u32::try_from(self.image.chunk_offsets.len())?)
let image = self.ensure_image()?;
image.check_chunk_type(ChunkType::Tile)?;
Ok(u32::try_from(image.chunk_offsets.len())?)
}
fn read_chunk_to_bytes(
@@ -1036,12 +1197,10 @@ impl<R: Read + Seek> Decoder<R> {
chunk_index: u32,
layout: &image::ReadoutLayout,
) -> TiffResult<()> {
let offset = self.image.chunk_file_range(chunk_index)?.0;
self.goto_offset_u64(offset)?;
self.image
.expand_chunk(&mut self.value_reader, buffer, layout, chunk_index)?;
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(())
}
@@ -1056,8 +1215,9 @@ impl<R: Read + Seek> Decoder<R> {
&mut self,
chunk_index: u32,
) -> TiffResult<BufferLayoutPreference> {
let data_dims = self.image.chunk_data_dimensions(chunk_index)?;
let readout = self.image.readout_for_size(data_dims.0, data_dims.1)?;
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();
@@ -1072,7 +1232,7 @@ impl<R: Read + Seek> Decoder<R> {
planes: 1,
plane_stride,
complete_len: layout.size(),
sample_format: self.image.sample_format,
sample_format: image.sample_format,
sample_type: Some(sample_type),
})
}
@@ -1089,17 +1249,15 @@ impl<R: Read + Seek> Decoder<R> {
&mut self,
code_unit: TiffCodingUnit,
) -> TiffResult<BufferLayoutPreference> {
match self.image.planar_config {
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) = self.image.chunk_data_dimensions(code_unit.0)?;
let layout = self
.image
.readout_for_size(width, height)?
.to_plane_layout()?;
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(
@@ -1115,9 +1273,10 @@ impl<R: Read + Seek> Decoder<R> {
///
/// 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 (width, height) = self.image.chunk_data_dimensions(chunk_index)?;
let image = self.ensure_image()?;
let readout = self.image.readout_for_size(width, height)?;
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)?
@@ -1136,9 +1295,10 @@ impl<R: Read + Seek> Decoder<R> {
///
/// 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 (width, height) = self.image.chunk_data_dimensions(chunk_index)?;
let image = self.ensure_image()?;
let layout = self.image.readout_for_size(width, height)?;
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)?;
@@ -1159,9 +1319,10 @@ impl<R: Read + Seek> Decoder<R> {
chunk_index: u32,
output_width: usize,
) -> TiffResult<()> {
let (width, height) = self.image.chunk_data_dimensions(chunk_index)?;
let image = self.ensure_image()?;
let mut layout = self.image.readout_for_size(width, height)?;
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)?;
@@ -1191,8 +1352,10 @@ impl<R: Read + Seek> Decoder<R> {
slice: TiffCodingUnit,
buffer: &mut [u8],
) -> TiffResult<()> {
let (width, height) = self.image.chunk_data_dimensions(slice.0)?;
let readout = self.image.readout_for_size(width, height)?;
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,
@@ -1214,14 +1377,9 @@ impl<R: Read + Seek> Decoder<R> {
for (idx, &plane_offset) in plane_offsets[..used_plane_offsets].iter().enumerate() {
let chunk = slice.0 + idx as u32 * readout.chunks_per_plane;
self.goto_offset_u64(self.image.chunk_offsets[chunk as usize])?;
self.image.expand_chunk(
&mut self.value_reader,
&mut buffer[plane_offset..],
readout,
chunk,
)?;
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(())
@@ -1229,14 +1387,32 @@ impl<R: Read + Seek> Decoder<R> {
/// 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.chunk_dimensions().unwrap()
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")
}
@@ -1255,7 +1431,8 @@ impl<R: Read + Seek> Decoder<R> {
/// 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 layout = self.image.readout_for_image()?.to_plane_layout()?;
let image = self.ensure_image()?;
let layout = image.readout_for_image()?.to_plane_layout()?;
Ok(BufferLayoutPreference::from_planes(&layout))
}
@@ -1279,7 +1456,8 @@ impl<R: Read + Seek> Decoder<R> {
/// 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 readout = self.image.readout_for_image()?;
let image = self.ensure_image()?;
let readout = image.readout_for_image()?;
let mut result = readout
.result_extent_for_planes(0..1)?
@@ -1341,7 +1519,7 @@ impl<R: Read + Seek> Decoder<R> {
&mut self,
result: &mut DecodingSampleBuffer,
) -> TiffResult<BufferLayoutPreference> {
let readout = self.image.readout_for_image()?;
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 {
@@ -1372,7 +1550,8 @@ impl<R: Read + Seek> Decoder<R> {
/// 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 readout = self.image.readout_for_image()?;
let (image, value_reader) = self.ensure_image_and_reader()?;
let readout = image.readout_for_image()?;
let ref layout @ image::PlaneLayout {
ref plane_offsets,
@@ -1396,10 +1575,11 @@ impl<R: Read + Seek> Decoder<R> {
for (idx, &plane_offset) in plane_offsets[..used_plane_offsets].iter().enumerate() {
let chunk = chunk + idx as u32 * readout.chunks_per_plane;
self.goto_offset_u64(self.image.chunk_offsets[chunk as usize])?;
let offset = image.chunk_offsets[chunk as usize];
value_reader.reader.goto_offset(offset)?;
self.image.expand_chunk(
&mut self.value_reader,
image.expand_chunk(
value_reader,
&mut buffer[plane_offset..][buffer_offset..],
readout,
chunk,
@@ -1409,70 +1589,28 @@ impl<R: Read + Seek> Decoder<R> {
Ok(())
}
/// 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,
},
}
}
/// 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,
},
}
}
#[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,
@@ -1482,67 +1620,81 @@ impl<R: Read + Seek> Decoder<R> {
/// 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()
}
+2 -11
View File
@@ -28,8 +28,6 @@ use self::compression::Compression as Comp;
use self::compression::*;
use self::writer::*;
pub use self::writer::TiffWriter;
/// Type of prediction to prepare the image with.
///
/// Image data can be very unpredictable, and thus very hard to compress. Predictors are simple
@@ -187,14 +185,6 @@ impl<W: Write + Seek, K: TiffKind> TiffEncoder<W, K> {
self
}
/// Get a mutable reference to a writer for raw values.
///
/// This can be used to write custom data sections to the underlying writer that may or may not
/// be referenced in the IFDs of the tiff structure.
pub fn get_mut(&mut self) -> &mut TiffWriter<W> {
&mut self.writer
}
/// Create a [`DirectoryEncoder`] to encode an ifd directory.
#[deprecated = "`image_directory` replaced the old behavior and clarifies the intent"]
#[doc(hidden)]
@@ -498,7 +488,8 @@ impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> {
self.writer.write_u16(entry.field_type().to_u16())?;
let count = K::convert_offset(entry.count())?;
count.write(self.writer)?;
self.writer.write_bytes(&entry.offset()[..offset_bytes])?;
self.writer
.write_bytes(&entry.offset_raw()[..offset_bytes])?;
}
Ok(offset)
-16
View File
@@ -40,7 +40,6 @@ pub fn write_bigtiff_header<W: Write>(writer: &mut TiffWriter<W>) -> TiffResult<
Ok(())
}
/// A writer that tracks offset and can compress.
pub struct TiffWriter<W> {
writer: W,
offset: u64,
@@ -62,7 +61,6 @@ impl<W: Write> TiffWriter<W> {
self.compressor = compressor;
}
/// Unset the compression.
pub fn reset_compression(&mut self) {
self.compressor = Compressor::default();
}
@@ -75,7 +73,6 @@ impl<W: Write> TiffWriter<W> {
self.offset() - core::mem::size_of::<K::OffsetType>() as u64
}
/// Get the number of bytes written by the last write operation.
pub fn last_written(&self) -> u64 {
self.byte_count
}
@@ -186,19 +183,6 @@ impl<W: Write> TiffWriter<W> {
}
}
/// Write uncompressed bytes to the raw underlying file.
impl<W: Write> std::io::Write for TiffWriter<W> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let n = self.writer.write(buf)?;
self.offset += n as u64;
Ok(n)
}
fn flush(&mut self) -> io::Result<()> {
self.writer.flush()
}
}
impl<W: Seek> TiffWriter<W> {
pub fn goto_offset(&mut self, offset: u64) -> Result<(), io::Error> {
self.offset = offset;
+7
View File
@@ -567,6 +567,13 @@ impl ValueBuffer {
}
}
/// Create an empty value buffer with `Byte` type. This type choice is arbitrary.
impl Default for ValueBuffer {
fn default() -> Self {
ValueBuffer::empty(Type::BYTE)
}
}
/// Byte order of the TIFF file.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ByteOrder {
+5 -6
View File
@@ -26,21 +26,20 @@ fn test_big_tiff() {
decoder.colortype().expect("Cannot get colortype"),
ColorType::RGB(8)
);
let mut ifd = decoder.current_ifd();
assert_eq!(
decoder
.get_tag_u64(Tag::StripOffsets)
ifd.get_tag_u64(Tag::StripOffsets)
.expect("Cannot get StripOffsets"),
16
);
assert_eq!(
decoder
.get_tag_u64(Tag::RowsPerStrip)
ifd.get_tag_u64(Tag::RowsPerStrip)
.expect("Cannot get RowsPerStrip"),
64
);
assert_eq!(
decoder
.get_tag_u64(Tag::StripByteCounts)
ifd.get_tag_u64(Tag::StripByteCounts)
.expect("Cannot get StripByteCounts"),
12288
)
+6 -8
View File
@@ -30,27 +30,25 @@ fn test_geo_tiff() {
num_samples: 5
}
);
let mut ifd = decoder.current_ifd();
assert_eq!(
decoder
.get_tag_u64(Tag::StripOffsets)
ifd.get_tag_u64(Tag::StripOffsets)
.expect("Cannot get StripOffsets"),
418
);
assert_eq!(
decoder
.get_tag_u64(Tag::RowsPerStrip)
ifd.get_tag_u64(Tag::RowsPerStrip)
.expect("Cannot get RowsPerStrip"),
10
);
assert_eq!(
decoder
.get_tag_u64(Tag::StripByteCounts)
ifd.get_tag_u64(Tag::StripByteCounts)
.expect("Cannot get StripByteCounts"),
1000
);
assert_eq!(
decoder
.get_tag(Tag::ModelPixelScaleTag)
ifd.get_tag(Tag::ModelPixelScaleTag)
.expect("Cannot get pixel scale")
.into_f64_vec()
.expect("Cannot get pixel scale"),
+8 -4
View File
@@ -404,7 +404,10 @@ fn test_string_tags() {
let img_file = File::open(path).expect("Cannot find test image!");
let mut decoder = Decoder::open(img_file).expect("Cannot create decoder");
decoder.next_image().expect("Cannot read image IFD");
let software = decoder.get_tag(tiff::tags::Tag::Software).unwrap();
let software = decoder
.current_ifd()
.get_tag(tiff::tags::Tag::Software)
.unwrap();
match software {
ifd::Value::Ascii(s) => assert_eq!(
&s,
@@ -478,7 +481,8 @@ fn test_inner_access() {
decoder.next_image().expect("Cannot read image IFD");
assert_eq!(decoder.colortype().unwrap(), ColorType::RGB(8));
let c = decoder
let mut ifd = decoder.current_ifd();
let c = ifd
.get_tag(tiff::tags::Tag::Compression)
.unwrap()
.into_u16()
@@ -487,13 +491,13 @@ fn test_inner_access() {
// Because the image is uncompressed, reading the first tile directly with the inner reader
// should yield the same result as reading it with the decoder's read_chunk method.
let first_offset = decoder
let first_offset = ifd
.get_tag_u32_vec(tiff::tags::Tag::TileOffsets)
.unwrap()
.into_iter()
.next()
.unwrap();
let first_byte_count = decoder
let first_byte_count = ifd
.get_tag_u32_vec(tiff::tags::Tag::TileByteCounts)
.unwrap()
.into_iter()
+40 -11
View File
@@ -40,6 +40,7 @@ fn encode_decode() {
assert_eq!(decoder.dimensions().unwrap(), (100, 100));
let mut all_tags = decoder
.current_ifd()
.tag_iter()
.filter_map(Result::ok)
.collect::<Vec<_>>();
@@ -80,7 +81,7 @@ fn encode_decode() {
);
assert_eq!(
decoder.get_tag(Tag::Artist).unwrap(),
decoder.current_ifd().get_tag(Tag::Artist).unwrap(),
ifd::Value::Ascii("Image-tiff".into())
);
@@ -125,6 +126,7 @@ fn encode_decode_big() {
assert_eq!(decoder.dimensions().unwrap(), (100, 100));
let mut all_tags = decoder
.current_ifd()
.tag_iter()
.filter_map(Result::ok)
.collect::<Vec<_>>();
@@ -165,7 +167,7 @@ fn encode_decode_big() {
);
assert_eq!(
decoder.get_tag(Tag::Artist).unwrap(),
decoder.current_ifd().get_tag(Tag::Artist).unwrap(),
ifd::Value::Ascii("Image-tiff".into())
);
let mut buffer = tiff::decoder::DecodingSampleBuffer::U8(vec![]);
@@ -539,37 +541,57 @@ trait AssertDecode {
impl<R: std::io::Read + std::io::Seek> AssertDecode for Decoder<R> {
fn assert_tag_u32(&mut self, tag: u16) -> u32 {
self.get_tag(Tag::Unknown(tag)).unwrap().into_u32().unwrap()
self.current_ifd()
.get_tag(Tag::Unknown(tag))
.unwrap()
.into_u32()
.unwrap()
}
fn assert_tag_u32_vec(&mut self, tag: u16) -> Vec<u32> {
self.get_tag(Tag::Unknown(tag))
self.current_ifd()
.get_tag(Tag::Unknown(tag))
.unwrap()
.into_u32_vec()
.unwrap()
}
fn assert_tag_i32(&mut self, tag: u16) -> i32 {
self.get_tag(Tag::Unknown(tag)).unwrap().into_i32().unwrap()
self.current_ifd()
.get_tag(Tag::Unknown(tag))
.unwrap()
.into_i32()
.unwrap()
}
fn assert_tag_i32_vec(&mut self, tag: u16) -> Vec<i32> {
self.get_tag(Tag::Unknown(tag))
self.current_ifd()
.get_tag(Tag::Unknown(tag))
.unwrap()
.into_i32_vec()
.unwrap()
}
fn assert_tag_u64(&mut self, tag: u16) -> u64 {
self.get_tag(Tag::Unknown(tag)).unwrap().into_u64().unwrap()
self.current_ifd()
.get_tag(Tag::Unknown(tag))
.unwrap()
.into_u64()
.unwrap()
}
fn assert_tag_u64_vec(&mut self, tag: u16) -> Vec<u64> {
self.get_tag(Tag::Unknown(tag))
self.current_ifd()
.get_tag(Tag::Unknown(tag))
.unwrap()
.into_u64_vec()
.unwrap()
}
fn assert_tag_i64(&mut self, tag: u16) -> i64 {
self.get_tag(Tag::Unknown(tag)).unwrap().into_i64().unwrap()
self.current_ifd()
.get_tag(Tag::Unknown(tag))
.unwrap()
.into_i64()
.unwrap()
}
fn assert_tag_i64_vec(&mut self, tag: u16) -> Vec<i64> {
self.get_tag(Tag::Unknown(tag))
self.current_ifd()
.get_tag(Tag::Unknown(tag))
.unwrap()
.into_i64_vec()
.unwrap()
@@ -776,7 +798,13 @@ fn test_rows_per_strip() {
{
let mut decoder = Decoder::open(&mut file).unwrap();
decoder.next_image().unwrap();
assert_eq!(decoder.get_tag_u64(Tag::RowsPerStrip).unwrap(), 2);
assert_eq!(
decoder
.current_ifd()
.get_tag_u64(Tag::RowsPerStrip)
.unwrap(),
2
);
assert_eq!(decoder.strip_count().unwrap(), 50);
for i in 0..50 {
@@ -829,6 +857,7 @@ fn test_auxiliary_directory() {
let _ = decoder.read_image().unwrap();
let exif_location = decoder
.current_ifd()
.get_tag(Tag::ExifDirectory)
.expect("second directory missing Exif")
.into_ifd_pointer()