Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions src/decoder/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,19 @@ impl Image {
decoder: &mut ValueReader<R>,
ifd: Directory,
) -> TiffResult<Image> {
let mut tag_reader = TagReader { decoder, ifd: &ifd };
let img = Self::from_ref(decoder, &ifd)?;

Ok(Image {
ifd: Some(ifd),
..img
})
}

pub(crate) fn from_ref<R: Read + Seek>(
decoder: &mut ValueReader<R>,
ifd: &Directory,
) -> TiffResult<Image> {
let mut tag_reader = TagReader { decoder, ifd };

let width = tag_reader.require_tag(Tag::ImageWidth)?.into_u32()?;
let height = tag_reader.require_tag(Tag::ImageLength)?.into_u32()?;
Expand Down Expand Up @@ -288,7 +300,7 @@ impl Image {
};

Ok(Image {
ifd: Some(ifd),
ifd: None,
width,
height,
bits_per_sample: bits_per_sample[0],
Expand Down
165 changes: 126 additions & 39 deletions src/decoder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ mod stream;
mod tag_reader;

/// Result of a decoding process
#[derive(Debug)]
#[derive(Debug, PartialEq)]
pub enum DecodingResult {
/// A vector of unsigned bytes
U8(Vec<u8>),
Expand Down Expand Up @@ -349,6 +349,10 @@ where
ifd_offsets: Vec<IfdPointer>,
/// Map from the ifd into the `ifd_offsets` ordered list.
seen_ifds: cycles::IfdCycles,
/// The directory, if we have not yet read it as an image.
/// This is prioritized _over_ the image. Hence it must _not_ be set if we are currently
/// reading a true image IFD that is instead stored in the `image` attribute.
non_image_ifd: Option<Directory>,
image: Image,
}

Expand Down Expand Up @@ -624,6 +628,7 @@ impl<R: Read + Seek> Decoder<R> {
chunk_offsets: Vec::new(),
chunk_bytes: Vec::new(),
},
non_image_ifd: None,
};
decoder.next_image()?;
Ok(decoder)
Expand Down Expand Up @@ -653,48 +658,85 @@ impl<R: Read + Seek> Decoder<R> {

/// Loads the IFD at the specified index in the list, if one exists
pub fn seek_to_image(&mut self, ifd_index: usize) -> TiffResult<()> {
// 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 ifd_index >= self.ifd_offsets.len() {
// We possibly need to load in the next IFD
if self.next_ifd.is_none() {
self.current_ifd = None;

return Err(TiffError::FormatError(
TiffFormatError::ImageFileDirectoryNotFound,
));
}
let ifd = self.seek_to_directory(ifd_index)?;
self.non_image_ifd = None;
self.image = Image::from_reader(&mut self.value_reader, ifd)?;
Ok(())
}

loop {
// Follow the list until we find the one we want, or we reach the end, whichever happens first
let ifd = self.next_ifd()?;
fn seek_to_directory(&mut self, ifd_index: usize) -> TiffResult<Directory> {
if ifd_index < self.ifd_offsets.len() {
// If the index is within the list of ifds then we can load the selected image/IFD
let ifd_offset = self.ifd_offsets[ifd_index];
let ifd = self.value_reader.read_directory(ifd_offset)?;

if ifd.next().is_none() {
break;
}
self.next_ifd = ifd.next();
self.current_ifd = Some(ifd_offset);

if ifd_index < self.ifd_offsets.len() {
break;
}
}
return Ok(ifd);
}

// If the index is within the list of ifds then we can load the selected image/IFD
if let Some(ifd_offset) = self.ifd_offsets.get(ifd_index) {
let ifd = self.value_reader.read_directory(*ifd_offset)?;
self.next_ifd = ifd.next();
self.current_ifd = Some(*ifd_offset);
self.image = Image::from_reader(&mut self.value_reader, ifd)?;
// Follow the list until we find the one we want, or we reach the end, whichever happens
// first. How many IFDs to read only for their `next` field?
let step_over = self.ifd_offsets.len() - ifd_index;

Ok(())
} else {
Err(TiffError::FormatError(
TiffFormatError::ImageFileDirectoryNotFound,
))
for _ in 0..step_over {
// FIXME: for optimization we only need to read the offset of this one, not its whole
// data. However on buffered files this should be a rather small difference unless
// you're traversing a lot of directories.

// Detecting an end-of-file is done by `next_ifd`. We ignore the IFD itself (but not
// via an ignore pattern to avoid silencing must_use accidentally).
self.next_ifd()?;
}

// self.next_ifd, self.current_ifd will be setup by `next_ifd`.
self.next_ifd()
}

/// Start the chain of image directories from a new location.
///
/// This enters a new chain of image file directories with the indicated offset as the new root
/// (similar to the file having its initial offset at the given IFD position). After this call,
/// [`Self::seek_to_image`] has the new root at index `0` and works relative to the new root.
///
/// This is not atomic with regards to errors. If the function returns an error, the decoder is
/// left in an intermediate state where it does not point to any image. A valid state can be
/// recovered by calling [`Self::restart_at_image`] with a valid offset and a successful seek.
pub fn restart_at_image(&mut self, offset: IfdPointer) -> TiffResult<()> {
let ifd = self.restart_at_offset(offset)?;
self.non_image_ifd = None;
self.image = Image::from_reader(&mut self.value_reader, ifd)?;
Ok(())
}

/// Start the chain of non-image directories from a new location.
///
/// See [`Self::restart_at_image`] for details, except this method does not attempt to
/// interpret the directory in the sequence as an image. Instead, it may be used to read a
/// sequence of auxiliary IFDs that are not necessarily images. For instance, a directory
/// referred to in the SubIfd tag may be a thumbnail
pub fn restart_at_directory(&mut self, offset: IfdPointer) -> TiffResult<()> {
let ifd = self.restart_at_offset(offset)?;
self.non_image_ifd = Some(ifd);
Ok(())
}

fn restart_at_offset(&mut self, offset: IfdPointer) -> TiffResult<Directory> {
self.ifd_offsets.clear();
self.ifd_offsets.push(offset);

self.next_ifd = Some(offset);
self.current_ifd = None;

self.next_ifd()
}

fn next_ifd(&mut self) -> TiffResult<Directory> {
let Some(next_ifd) = self.next_ifd.take() else {
self.current_ifd = None;
self.non_image_ifd = None;

return Err(TiffError::FormatError(
TiffFormatError::ImageFileDirectoryNotFound,
));
Expand All @@ -721,10 +763,44 @@ impl<R: Read + Seek> Decoder<R> {
/// To determine whether there are more images call `TIFFDecoder::more_images` instead.
pub fn next_image(&mut self) -> TiffResult<()> {
let ifd = self.next_ifd()?;
self.non_image_ifd = None;
self.image = Image::from_reader(&mut self.value_reader, ifd)?;
Ok(())
}

/// Read the next directory without interpreting it as an 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_directories` instead.
pub fn next_directory(&mut self) -> TiffResult<()> {
let ifd = self.next_ifd()?;
self.non_image_ifd = Some(ifd);
Ok(())
}

/// Interpret the current directory as an image.
///
/// This method is used after having called [`Self::restart_at`] or [`Self::next_directory`] to
/// iterate the sequence of image file directories, having read a directory without having read
/// its tags as image data.
pub fn current_directory_as_image(&mut self) -> TiffResult<()> {
let current_ifd = self.current_ifd.ok_or(TiffError::FormatError(
TiffFormatError::ImageFileDirectoryNotFound,
))?;

if let Some(ifd) = &self.non_image_ifd {
self.image = Image::from_ref(&mut self.value_reader, ifd)?;
// Definitely sets an IFD but this works without unwraps.
self.image.ifd = self.non_image_ifd.take();
} else {
// Probably re-reading an image but that's fine.
let ifd = self.read_directory(current_ifd)?;
self.image = Image::from_reader(&mut self.value_reader, ifd)?;
}

Ok(())
}

/// Returns `true` if there is at least one more image available.
pub fn more_images(&self) -> bool {
self.next_ifd.is_some()
Expand Down Expand Up @@ -1227,11 +1303,22 @@ impl<R: Read + Seek> Decoder<R> {
}

/// Get the IFD decoder for our current image IFD.
fn image_ifd(&mut self) -> IfdDecoder<'_> {
fn current_directory_ifd(&mut self) -> IfdDecoder<'_> {
// Special fallback. We do not want to error handle not having read a current directory, in
// particular as the behavior having a directory without tags will produce these errors
// anyways (most likely). Note that an empty directory is invalid in a TIFF.
static NO_IFD: Directory = Directory::empty();

let ifd = self
.non_image_ifd
.as_ref()
.or(self.image.ifd.as_ref())
.unwrap_or(&NO_IFD);

IfdDecoder {
inner: tag_reader::TagReader {
decoder: &mut self.value_reader,
ifd: self.image.ifd.as_ref().unwrap(),
ifd,
},
}
}
Expand Down Expand Up @@ -1272,25 +1359,25 @@ 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.
pub fn find_tag(&mut self, tag: Tag) -> TiffResult<Option<ifd::Value>> {
self.image_ifd().find_tag(tag)
self.current_directory_ifd().find_tag(tag)
}

/// Tries to retrieve a tag in the current image directory and convert it to the desired
/// unsigned type.
pub fn find_tag_unsigned<T: TryFrom<u64>>(&mut self, tag: Tag) -> TiffResult<Option<T>> {
self.image_ifd().find_tag_unsigned(tag)
self.current_directory_ifd().find_tag_unsigned(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.
pub fn get_tag_unsigned<T: TryFrom<u64>>(&mut self, tag: Tag) -> TiffResult<T> {
self.image_ifd().get_tag_unsigned(tag)
self.current_directory_ifd().get_tag_unsigned(tag)
}

/// Tries to retrieve a tag from the current image directory.
/// Returns an error if the tag is not present
pub fn get_tag(&mut self, tag: Tag) -> TiffResult<ifd::Value> {
self.image_ifd().get_tag(tag)
self.current_directory_ifd().get_tag(tag)
}

pub fn get_tag_u32(&mut self, tag: Tag) -> TiffResult<u32> {
Expand Down Expand Up @@ -1345,7 +1432,7 @@ impl<R: Read + Seek> Decoder<R> {
}

pub fn tag_iter(&mut self) -> impl Iterator<Item = TiffResult<(Tag, ifd::Value)>> + '_ {
self.image_ifd().tag_iter()
self.current_directory_ifd().tag_iter()
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/directory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ pub struct Directory {
impl Directory {
/// Create a directory in an initial state without entries. Note that an empty directory can
/// not be encoded in a file, it must contain at least one entry.
pub fn empty() -> Self {
pub const fn empty() -> Self {
Directory {
entries: BTreeMap::new(),
next_ifd: None,
Expand Down
Loading
Loading