Compare commits
4
Commits
cieluv
...
restart-at
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ab0ce47230 | ||
|
|
f993cfb940 | ||
|
|
8c4bd58493 | ||
|
|
c073841a9d |
No files matched your search
+14
-2
@@ -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()?;
|
||||
@@ -288,7 +300,7 @@ impl Image {
|
||||
};
|
||||
|
||||
Ok(Image {
|
||||
ifd: Some(ifd),
|
||||
ifd: None,
|
||||
width,
|
||||
height,
|
||||
bits_per_sample: bits_per_sample[0],
|
||||
|
||||
+128
-41
@@ -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>),
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
@@ -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;
|
||||
let ifd = self.seek_to_directory(ifd_index)?;
|
||||
self.non_image_ifd = None;
|
||||
self.image = Image::from_reader(&mut self.value_reader, ifd)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
return Err(TiffError::FormatError(
|
||||
TiffFormatError::ImageFileDirectoryNotFound,
|
||||
));
|
||||
}
|
||||
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)?;
|
||||
|
||||
loop {
|
||||
// Follow the list until we find the one we want, or we reach the end, whichever happens first
|
||||
let ifd = self.next_ifd()?;
|
||||
|
||||
if ifd.next().is_none() {
|
||||
break;
|
||||
}
|
||||
|
||||
if ifd_index < self.ifd_offsets.len() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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)?;
|
||||
self.current_ifd = Some(ifd_offset);
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
Err(TiffError::FormatError(
|
||||
TiffFormatError::ImageFileDirectoryNotFound,
|
||||
))
|
||||
return Ok(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;
|
||||
|
||||
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,
|
||||
));
|
||||
@@ -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()
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -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> {
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -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,
|
||||
|
||||
+129
-1
@@ -1,4 +1,7 @@
|
||||
use tiff::decoder::Decoder;
|
||||
use tiff::{
|
||||
decoder::Decoder,
|
||||
tags::{IfdPointer, Tag},
|
||||
};
|
||||
|
||||
// This file has the following IFD structure:
|
||||
//
|
||||
@@ -39,3 +42,128 @@ fn decode_seek_chain() {
|
||||
assert_eq!(Some(offset2), decoder.ifd_pointer());
|
||||
assert!(!decoder.more_images());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_seek_recover() {
|
||||
let file = File::open(TEST_IMAGE_SUBIFD).expect("Cannot open test image");
|
||||
let mut decoder = Decoder::new(file).expect("Invalid format to create decoder");
|
||||
|
||||
// Remember the offsets in the first iteration.
|
||||
let offset0 = decoder.ifd_pointer().expect("First IFD pointer not found");
|
||||
decoder.next_image().unwrap();
|
||||
let offset1 = decoder.ifd_pointer().expect("Second IFD pointer not found");
|
||||
decoder.next_image().unwrap();
|
||||
let offset2 = decoder.ifd_pointer().expect("Third IFD pointer not found");
|
||||
|
||||
// oops!
|
||||
let fails = decoder.restart_at_image(IfdPointer(0xdead_beef));
|
||||
assert!(fails.is_err());
|
||||
|
||||
// However we can recover by restarting our seek.
|
||||
decoder
|
||||
.restart_at_image(offset0)
|
||||
.expect("Failed to restart IFDs from first image");
|
||||
assert_eq!(Some(offset0), decoder.ifd_pointer());
|
||||
decoder.next_image().unwrap();
|
||||
assert_eq!(Some(offset1), decoder.ifd_pointer());
|
||||
decoder.next_image().unwrap();
|
||||
assert_eq!(Some(offset2), decoder.ifd_pointer());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_seek_directory() {
|
||||
let file = File::open(TEST_IMAGE_SUBIFD).expect("Cannot open test image");
|
||||
let mut decoder = Decoder::new(file).expect("Invalid format to create decoder");
|
||||
|
||||
let offset = decoder.ifd_pointer().expect("First IFD pointer not found");
|
||||
let img0 = decoder.read_image().unwrap();
|
||||
|
||||
decoder.next_image().unwrap();
|
||||
let img1 = decoder.read_image().unwrap();
|
||||
assert_ne!(img0, img1);
|
||||
|
||||
{
|
||||
// Due to the special test file structure.
|
||||
let subifd = decoder
|
||||
.get_tag(Tag::SubIfd)
|
||||
.unwrap()
|
||||
.into_ifd_vec()
|
||||
.unwrap()[0];
|
||||
|
||||
// Let's try to manually interpret this as an image.
|
||||
decoder
|
||||
.restart_at_directory(subifd)
|
||||
.expect("Failed to restart IFDs at SubIfd");
|
||||
decoder
|
||||
.current_directory_as_image()
|
||||
.expect("Failed to read SubIfd image");
|
||||
|
||||
assert_eq!(decoder.dimensions().unwrap(), (32, 32));
|
||||
let subimage = decoder.read_image().unwrap();
|
||||
// Verify we did not accidentally read another image.
|
||||
assert_ne!(img0, subimage);
|
||||
assert_ne!(img1, subimage);
|
||||
}
|
||||
|
||||
// And finally, seek back to the first image.
|
||||
decoder
|
||||
.restart_at_image(offset)
|
||||
.expect("First IFD no longer readable");
|
||||
assert_eq!(Some(offset), decoder.ifd_pointer());
|
||||
|
||||
let img0_try2 = decoder.read_image().unwrap();
|
||||
assert_eq!(img0, img0_try2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_subifds() {
|
||||
let explore_chain = |decoder: &mut Decoder<_>, subifds: &mut Vec<_>| {
|
||||
let mut len = 0usize;
|
||||
|
||||
loop {
|
||||
len += 1;
|
||||
|
||||
if let Ok(subifd) = decoder.get_tag(Tag::SubIfd) {
|
||||
let prelen = subifds.len();
|
||||
subifds.extend(subifd.into_ifd_vec().expect("Failed to decode SubIfd tag"));
|
||||
eprintln!("New roots: {:?}", &subifds[prelen..]);
|
||||
} else {
|
||||
eprintln!("No roots");
|
||||
}
|
||||
|
||||
if !decoder.more_images() {
|
||||
break;
|
||||
}
|
||||
|
||||
decoder.next_image().unwrap();
|
||||
}
|
||||
|
||||
len
|
||||
};
|
||||
|
||||
let file = File::open(TEST_IMAGE_SUBIFD).expect("Cannot open test image");
|
||||
let mut decoder = Decoder::new(file).expect("Invalid format to create decoder");
|
||||
|
||||
let mut subifds = vec![];
|
||||
let mut chain_lengths = vec![];
|
||||
chain_lengths.push(explore_chain(&mut decoder, &mut subifds));
|
||||
|
||||
while !subifds.is_empty() {
|
||||
let root = subifds.remove(0);
|
||||
let manual_directory = decoder.read_directory(root).unwrap();
|
||||
eprintln!("Manual directory: {:?}", manual_directory);
|
||||
|
||||
decoder.restart_at_directory(root).unwrap();
|
||||
let prelen = subifds.len();
|
||||
chain_lengths.push(explore_chain(&mut decoder, &mut subifds));
|
||||
eprintln!("Chains roots: {:?}", &subifds[prelen..]);
|
||||
}
|
||||
|
||||
// Traversal Order in this graph:
|
||||
// Image 0 -> Image 1 -> Image 2
|
||||
// |-> Image 3
|
||||
// |-> Image 4 -> Image 5
|
||||
// | \-> Image 7 -> Image 8
|
||||
// \-> Image 6
|
||||
assert_eq!(chain_lengths, [3, 1, 2, 1, 2]);
|
||||
}
|
||||
Reference in new issue
Block a user