Compare commits
30
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ab0ce47230 | ||
|
|
f993cfb940 | ||
|
|
8c4bd58493 | ||
|
|
c073841a9d | ||
|
|
36a16db659 | ||
|
|
d70c31dcbf | ||
|
|
b589d10a27 | ||
|
|
f4378eeb80 | ||
|
|
49015c6e93 | ||
|
|
29402875cb | ||
|
|
33453289b6 | ||
|
|
ce0ca5c234 | ||
|
|
f671293bd8 | ||
|
|
48d52bed9b | ||
|
|
7707060982 | ||
|
|
c370ac4b92 | ||
|
|
b2bcbbaf23 | ||
|
|
d07da5a6cb | ||
|
|
a3c81e9830 | ||
|
|
46e51cc67e | ||
|
|
f6a762b72c | ||
|
|
23672b2490 | ||
|
|
ca74c03520 | ||
|
|
2942f9de97 | ||
|
|
29add10ca7 | ||
|
|
59d32a66a7 | ||
|
|
5a6b2ad8f3 | ||
|
|
c746653269 | ||
|
|
cc75d051af | ||
|
|
2e51ee4b15 |
No files matched your search
@@ -45,6 +45,10 @@ jobs:
|
||||
- name: Cargo check
|
||||
run: cargo check --features "${{ matrix.feature }}" --no-default-features
|
||||
|
||||
- name: Cargo clippy
|
||||
if: ${{ matrix.feature != '' }}
|
||||
run: cargo clippy --no-deps --all-features --all-targets -- -D warnings
|
||||
|
||||
other_archs:
|
||||
# github actions does not support 32-bit or big endian systems directly, but
|
||||
# it does support QEMU. so we install qemu, then build and run the tests on
|
||||
|
||||
@@ -27,6 +27,8 @@ zune-jpeg = { version = "0.4.17", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = "0.3.1"
|
||||
# We may already depend on this via flate2
|
||||
crc32fast = "1"
|
||||
|
||||
[features]
|
||||
default = ["deflate", "jpeg", "lzw"]
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
//! the unsafe code guidelines).
|
||||
//!
|
||||
//! TODO: Would like to use std-lib here.
|
||||
// Until we implement predictors for f16, we don't need f16_as_ne_bytes. (Due to the macro we do
|
||||
// not apply this directly to the functions). And rust-version is not new enough for `expect`.
|
||||
#![allow(dead_code)]
|
||||
use std::{mem, slice};
|
||||
|
||||
use half::f16;
|
||||
|
||||
+1
-1
@@ -445,7 +445,7 @@ impl Entry {
|
||||
// 2b: the value is at most 4 bytes or doesn't fit in the offset field.
|
||||
return Ok(match self.type_ {
|
||||
Type::BYTE => Byte(self.offset[0]),
|
||||
Type::SBYTE => SignedByte(i8::from(self.offset[0] as i8)),
|
||||
Type::SBYTE => SignedByte(self.offset[0] as i8),
|
||||
Type::UNDEFINED => Byte(self.offset[0]),
|
||||
Type::SHORT => Short(self.r(bo).read_u16()?),
|
||||
Type::SSHORT => SignedShort(self.r(bo).read_i16()?),
|
||||
|
||||
+38
-13
@@ -10,6 +10,7 @@ use crate::{
|
||||
ColorType, Directory, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, UsageError,
|
||||
};
|
||||
use std::io::{self, Cursor, Read, Seek};
|
||||
use std::num::NonZeroUsize;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -29,10 +30,10 @@ pub(crate) struct TileAttributes {
|
||||
|
||||
impl TileAttributes {
|
||||
pub fn tiles_across(&self) -> usize {
|
||||
(self.image_width + self.tile_width - 1) / self.tile_width
|
||||
self.image_width.div_ceil(self.tile_width)
|
||||
}
|
||||
pub fn tiles_down(&self) -> usize {
|
||||
(self.image_height + self.tile_length - 1) / self.tile_length
|
||||
self.image_height.div_ceil(self.tile_length)
|
||||
}
|
||||
fn padding_right(&self) -> usize {
|
||||
(self.tile_width - self.image_width % self.tile_width) % self.tile_width
|
||||
@@ -85,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()?;
|
||||
@@ -287,7 +300,7 @@ impl Image {
|
||||
};
|
||||
|
||||
Ok(Image {
|
||||
ifd: Some(ifd),
|
||||
ifd: None,
|
||||
width,
|
||||
height,
|
||||
bits_per_sample: bits_per_sample[0],
|
||||
@@ -363,6 +376,22 @@ impl Image {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn minimum_row_stride(&self, dims: (u32, u32)) -> Option<NonZeroUsize> {
|
||||
let (width, height) = dims;
|
||||
|
||||
let row_stride = u64::from(width)
|
||||
.saturating_mul(self.samples_per_pixel() as u64)
|
||||
.saturating_mul(self.bits_per_sample as u64)
|
||||
.div_ceil(8);
|
||||
|
||||
// Note: row stride should be smaller than the len if we have an actual buffer. If there
|
||||
// are no pixels in the buffer (height _or_ width is 0) then the stride is not well defined
|
||||
// and we return `None`.
|
||||
(height > 0)
|
||||
.then_some(row_stride as usize)
|
||||
.and_then(NonZeroUsize::new)
|
||||
}
|
||||
|
||||
fn create_reader<'r, R: 'r + Read>(
|
||||
reader: R,
|
||||
compression_method: CompressionMethod,
|
||||
@@ -625,12 +654,12 @@ impl Image {
|
||||
let chunk_row_bits = (u64::from(chunk_dims.0) * u64::from(self.bits_per_sample))
|
||||
.checked_mul(samples as u64)
|
||||
.ok_or(TiffError::LimitsExceeded)?;
|
||||
let chunk_row_bytes: usize = ((chunk_row_bits + 7) / 8).try_into()?;
|
||||
let chunk_row_bytes: usize = chunk_row_bits.div_ceil(8).try_into()?;
|
||||
|
||||
let data_row_bits = (u64::from(data_dims.0) * u64::from(self.bits_per_sample))
|
||||
.checked_mul(samples as u64)
|
||||
.ok_or(TiffError::LimitsExceeded)?;
|
||||
let data_row_bytes: usize = ((data_row_bits + 7) / 8).try_into()?;
|
||||
let data_row_bytes: usize = data_row_bits.div_ceil(8).try_into()?;
|
||||
|
||||
// TODO: Should these return errors instead?
|
||||
assert!(output_row_stride >= data_row_bytes);
|
||||
@@ -643,11 +672,11 @@ impl Image {
|
||||
self.jpeg_tables.as_deref().map(|a| &**a),
|
||||
)?;
|
||||
|
||||
if output_row_stride == chunk_row_bytes as usize {
|
||||
if output_row_stride == chunk_row_bytes {
|
||||
let tile = &mut buf[..chunk_row_bytes * data_dims.1 as usize];
|
||||
reader.read_exact(tile)?;
|
||||
|
||||
for row in tile.chunks_mut(chunk_row_bytes as usize) {
|
||||
for row in tile.chunks_mut(chunk_row_bytes) {
|
||||
super::fix_endianness_and_predict(
|
||||
row,
|
||||
color_type.bit_depth(),
|
||||
@@ -678,11 +707,7 @@ impl Image {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (_i, row) in buf
|
||||
.chunks_mut(output_row_stride)
|
||||
.take(data_dims.1 as usize)
|
||||
.enumerate()
|
||||
{
|
||||
for row in buf.chunks_mut(output_row_stride).take(data_dims.1 as usize) {
|
||||
let row = &mut row[..data_row_bytes];
|
||||
reader.read_exact(row)?;
|
||||
|
||||
|
||||
+411
-96
@@ -1,5 +1,7 @@
|
||||
use std::alloc::{Layout, LayoutError};
|
||||
use std::collections::BTreeMap;
|
||||
use std::io::{self, Read, Seek};
|
||||
use std::num::NonZeroUsize;
|
||||
|
||||
use crate::tags::{
|
||||
CompressionMethod, IfdPointer, PhotometricInterpretation, PlanarConfiguration, Predictor,
|
||||
@@ -21,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>),
|
||||
@@ -197,6 +199,88 @@ impl<'a> DecodingBuffer<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// 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]
|
||||
pub struct BufferLayoutPreference {
|
||||
/// Minimum byte size of the buffer to read image data.
|
||||
pub len: usize,
|
||||
/// Minimum number of bytes to represent a row of image data of the requested content.
|
||||
pub row_stride: Option<NonZeroUsize>,
|
||||
}
|
||||
|
||||
/// The count and matching discriminant for a `DecodingBuffer`.
|
||||
#[derive(Clone)]
|
||||
enum DecodingExtent {
|
||||
U8(usize),
|
||||
U16(usize),
|
||||
U32(usize),
|
||||
U64(usize),
|
||||
F16(usize),
|
||||
F32(usize),
|
||||
F64(usize),
|
||||
I8(usize),
|
||||
I16(usize),
|
||||
I32(usize),
|
||||
I64(usize),
|
||||
}
|
||||
|
||||
impl DecodingExtent {
|
||||
fn to_result_buffer(&self, limits: &Limits) -> TiffResult<DecodingResult> {
|
||||
match *self {
|
||||
DecodingExtent::U8(count) => DecodingResult::new_u8(count, limits),
|
||||
DecodingExtent::U16(count) => DecodingResult::new_u16(count, limits),
|
||||
DecodingExtent::U32(count) => DecodingResult::new_u32(count, limits),
|
||||
DecodingExtent::U64(count) => DecodingResult::new_u64(count, limits),
|
||||
DecodingExtent::F16(count) => DecodingResult::new_f16(count, limits),
|
||||
DecodingExtent::F32(count) => DecodingResult::new_f32(count, limits),
|
||||
DecodingExtent::F64(count) => DecodingResult::new_f64(count, limits),
|
||||
DecodingExtent::I8(count) => DecodingResult::new_i8(count, limits),
|
||||
DecodingExtent::I16(count) => DecodingResult::new_i16(count, limits),
|
||||
DecodingExtent::I32(count) => DecodingResult::new_i32(count, limits),
|
||||
DecodingExtent::I64(count) => DecodingResult::new_i64(count, limits),
|
||||
}
|
||||
}
|
||||
|
||||
fn preferred_layout(self) -> TiffResult<Layout> {
|
||||
fn overflow(_: LayoutError) -> TiffError {
|
||||
TiffError::LimitsExceeded
|
||||
}
|
||||
|
||||
match self {
|
||||
DecodingExtent::U8(count) => Layout::array::<u8>(count),
|
||||
DecodingExtent::U16(count) => Layout::array::<u16>(count),
|
||||
DecodingExtent::U32(count) => Layout::array::<u32>(count),
|
||||
DecodingExtent::U64(count) => Layout::array::<u64>(count),
|
||||
DecodingExtent::F16(count) => Layout::array::<f16>(count),
|
||||
DecodingExtent::F32(count) => Layout::array::<f32>(count),
|
||||
DecodingExtent::F64(count) => Layout::array::<f64>(count),
|
||||
DecodingExtent::I8(count) => Layout::array::<i8>(count),
|
||||
DecodingExtent::I16(count) => Layout::array::<i16>(count),
|
||||
DecodingExtent::I32(count) => Layout::array::<i32>(count),
|
||||
DecodingExtent::I64(count) => Layout::array::<i64>(count),
|
||||
}
|
||||
.map_err(overflow)
|
||||
}
|
||||
|
||||
fn assert_layout(self, buffer: usize) -> TiffResult<()> {
|
||||
let needed_bytes = self.preferred_layout()?.size();
|
||||
if buffer < needed_bytes {
|
||||
Err(TiffError::UsageError(
|
||||
UsageError::InsufficientOutputBufferSize {
|
||||
needed: needed_bytes,
|
||||
provided: buffer,
|
||||
},
|
||||
))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
/// Chunk type of the internal representation
|
||||
pub enum ChunkType {
|
||||
@@ -206,6 +290,7 @@ pub enum ChunkType {
|
||||
|
||||
/// 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
|
||||
@@ -218,10 +303,6 @@ pub struct Limits {
|
||||
/// 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,
|
||||
/// The purpose of this is to prevent all the fields of the struct from
|
||||
/// being public, as this would make adding new fields a major version
|
||||
/// bump.
|
||||
_non_exhaustive: (),
|
||||
}
|
||||
|
||||
impl Limits {
|
||||
@@ -234,10 +315,9 @@ impl Limits {
|
||||
/// naturally, the machine running the program does not have infinite memory.
|
||||
pub fn unlimited() -> Limits {
|
||||
Limits {
|
||||
decoding_buffer_size: usize::max_value(),
|
||||
ifd_value_size: usize::max_value(),
|
||||
intermediate_buffer_size: usize::max_value(),
|
||||
_non_exhaustive: (),
|
||||
decoding_buffer_size: usize::MAX,
|
||||
ifd_value_size: usize::MAX,
|
||||
intermediate_buffer_size: usize::MAX,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -248,7 +328,6 @@ impl Default for Limits {
|
||||
decoding_buffer_size: 256 * 1024 * 1024,
|
||||
intermediate_buffer_size: 128 * 1024 * 1024,
|
||||
ifd_value_size: 1024 * 1024,
|
||||
_non_exhaustive: (),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -270,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,
|
||||
}
|
||||
|
||||
@@ -545,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)
|
||||
@@ -574,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() {
|
||||
let ifd = self.seek_to_directory(ifd_index)?;
|
||||
self.non_image_ifd = None;
|
||||
self.image = Image::from_reader(&mut self.value_reader, ifd)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
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)?;
|
||||
|
||||
self.next_ifd = ifd.next();
|
||||
self.current_ifd = Some(ifd_offset);
|
||||
|
||||
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;
|
||||
|
||||
return Err(TiffError::FormatError(
|
||||
TiffFormatError::ImageFileDirectoryNotFound,
|
||||
));
|
||||
}
|
||||
|
||||
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)?;
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
Err(TiffError::FormatError(
|
||||
TiffFormatError::ImageFileDirectoryNotFound,
|
||||
))
|
||||
}
|
||||
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,
|
||||
));
|
||||
@@ -642,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()
|
||||
@@ -665,6 +820,11 @@ 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> {
|
||||
@@ -837,17 +997,26 @@ impl<R: Read + Seek> Decoder<R> {
|
||||
chunk_index: u32,
|
||||
output_width: usize,
|
||||
) -> TiffResult<()> {
|
||||
let offset = self.image.chunk_file_range(chunk_index)?.0;
|
||||
self.goto_offset_u64(offset)?;
|
||||
|
||||
let output_row_stride = (output_width as u64)
|
||||
.saturating_mul(self.image.samples_per_pixel() as u64)
|
||||
.saturating_mul(self.image.bits_per_sample as u64)
|
||||
/ 8;
|
||||
.div_ceil(8);
|
||||
|
||||
self.read_chunk_to_bytes(buffer.as_bytes_mut(), chunk_index, output_row_stride)
|
||||
}
|
||||
|
||||
fn read_chunk_to_bytes(
|
||||
&mut self,
|
||||
buffer: &mut [u8],
|
||||
chunk_index: u32,
|
||||
output_row_stride: u64,
|
||||
) -> 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.as_bytes_mut(),
|
||||
buffer,
|
||||
output_row_stride.try_into()?,
|
||||
chunk_index,
|
||||
)?;
|
||||
@@ -856,56 +1025,109 @@ impl<R: Read + Seek> Decoder<R> {
|
||||
}
|
||||
|
||||
fn result_buffer(&self, width: usize, height: usize) -> TiffResult<DecodingResult> {
|
||||
let bits_per_sample = self.image().bits_per_sample;
|
||||
self.result_extent(width, height)?
|
||||
.to_result_buffer(&self.value_reader.limits)
|
||||
}
|
||||
|
||||
// FIXME: it's unusual for this method to take a `usize` dimension parameter since those would
|
||||
// typically come from the image data. The preferred consistent representation should be `u32`
|
||||
// and that would avoid some unusual casts `usize -> u64` with a `u64::from` instead.
|
||||
fn result_extent(&self, width: usize, height: usize) -> TiffResult<DecodingExtent> {
|
||||
let bits_per_sample = self.image.bits_per_sample;
|
||||
|
||||
// If samples take up more than one byte, we expand it to a full number (integer or float)
|
||||
// and the number of samples in that integer type just counts the number in the underlying
|
||||
// image itself. Otherwise, a row is represented as the byte slice of bitfields without
|
||||
// expansion.
|
||||
let row_samples = if bits_per_sample >= 8 {
|
||||
width
|
||||
} else {
|
||||
((((width as u64) * bits_per_sample as u64) + 7) / 8)
|
||||
((width as u64) * bits_per_sample as u64)
|
||||
.div_ceil(8)
|
||||
.try_into()
|
||||
.map_err(|_| TiffError::LimitsExceeded)?
|
||||
};
|
||||
|
||||
let buffer_size = row_samples
|
||||
.checked_mul(height)
|
||||
.and_then(|x| x.checked_mul(self.image().samples_per_pixel()))
|
||||
.and_then(|x| x.checked_mul(self.image.samples_per_pixel()))
|
||||
.ok_or(TiffError::LimitsExceeded)?;
|
||||
|
||||
let max_sample_bits = self.image().bits_per_sample;
|
||||
let limits = &self.value_reader.limits;
|
||||
|
||||
match self.image().sample_format {
|
||||
SampleFormat::Uint => match max_sample_bits {
|
||||
n if n <= 8 => DecodingResult::new_u8(buffer_size, limits),
|
||||
n if n <= 16 => DecodingResult::new_u16(buffer_size, limits),
|
||||
n if n <= 32 => DecodingResult::new_u32(buffer_size, limits),
|
||||
n if n <= 64 => DecodingResult::new_u64(buffer_size, limits),
|
||||
n => Err(TiffError::UnsupportedError(
|
||||
Ok(match self.image().sample_format {
|
||||
SampleFormat::Uint => match bits_per_sample {
|
||||
n if n <= 8 => DecodingExtent::U8(buffer_size),
|
||||
n if n <= 16 => DecodingExtent::U16(buffer_size),
|
||||
n if n <= 32 => DecodingExtent::U32(buffer_size),
|
||||
n if n <= 64 => DecodingExtent::U64(buffer_size),
|
||||
n => {
|
||||
return Err(TiffError::UnsupportedError(
|
||||
TiffUnsupportedError::UnsupportedBitsPerChannel(n),
|
||||
)),
|
||||
},
|
||||
SampleFormat::IEEEFP => match max_sample_bits {
|
||||
16 => DecodingResult::new_f16(buffer_size, limits),
|
||||
32 => DecodingResult::new_f32(buffer_size, limits),
|
||||
64 => DecodingResult::new_f64(buffer_size, limits),
|
||||
n => Err(TiffError::UnsupportedError(
|
||||
TiffUnsupportedError::UnsupportedBitsPerChannel(n),
|
||||
)),
|
||||
},
|
||||
SampleFormat::Int => match max_sample_bits {
|
||||
n if n <= 8 => DecodingResult::new_i8(buffer_size, limits),
|
||||
n if n <= 16 => DecodingResult::new_i16(buffer_size, limits),
|
||||
n if n <= 32 => DecodingResult::new_i32(buffer_size, limits),
|
||||
n if n <= 64 => DecodingResult::new_i64(buffer_size, limits),
|
||||
n => Err(TiffError::UnsupportedError(
|
||||
TiffUnsupportedError::UnsupportedBitsPerChannel(n),
|
||||
)),
|
||||
},
|
||||
format => Err(TiffUnsupportedError::UnsupportedSampleFormat(vec![format]).into()),
|
||||
))
|
||||
}
|
||||
},
|
||||
SampleFormat::IEEEFP => match bits_per_sample {
|
||||
16 => DecodingExtent::F16(buffer_size),
|
||||
32 => DecodingExtent::F32(buffer_size),
|
||||
64 => DecodingExtent::F64(buffer_size),
|
||||
n => {
|
||||
return Err(TiffError::UnsupportedError(
|
||||
TiffUnsupportedError::UnsupportedBitsPerChannel(n),
|
||||
))
|
||||
}
|
||||
},
|
||||
SampleFormat::Int => match bits_per_sample {
|
||||
n if n <= 8 => DecodingExtent::I8(buffer_size),
|
||||
n if n <= 16 => DecodingExtent::I16(buffer_size),
|
||||
n if n <= 32 => DecodingExtent::I32(buffer_size),
|
||||
n if n <= 64 => DecodingExtent::I64(buffer_size),
|
||||
n => {
|
||||
return Err(TiffError::UnsupportedError(
|
||||
TiffUnsupportedError::UnsupportedBitsPerChannel(n),
|
||||
))
|
||||
}
|
||||
},
|
||||
format => {
|
||||
return Err(TiffUnsupportedError::UnsupportedSampleFormat(vec![format]).into())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// # 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_chunk_buffer_layout(
|
||||
&mut self,
|
||||
chunk_index: u32,
|
||||
) -> TiffResult<BufferLayoutPreference> {
|
||||
let data_dims = self.image().chunk_data_dimensions(chunk_index)?;
|
||||
let layout = self
|
||||
.result_extent(data_dims.0 as usize, data_dims.1 as usize)?
|
||||
.preferred_layout()?;
|
||||
|
||||
let row_stride = self.image.minimum_row_stride(data_dims);
|
||||
|
||||
Ok(BufferLayoutPreference {
|
||||
len: layout.size(),
|
||||
row_stride,
|
||||
})
|
||||
}
|
||||
|
||||
/// Read the specified chunk (at index `chunk_index`) and return the binary data as a Vector.
|
||||
///
|
||||
/// # 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`.
|
||||
pub fn read_chunk(&mut self, chunk_index: u32) -> TiffResult<DecodingResult> {
|
||||
let data_dims = self.image().chunk_data_dimensions(chunk_index)?;
|
||||
|
||||
@@ -916,6 +1138,32 @@ impl<R: Read + Seek> Decoder<R> {
|
||||
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.
|
||||
///
|
||||
/// # 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`.
|
||||
pub fn read_chunk_bytes(&mut self, chunk_index: u32, buffer: &mut [u8]) -> TiffResult<()> {
|
||||
let data_dims = self.image().chunk_data_dimensions(chunk_index)?;
|
||||
|
||||
let extent = self.result_extent(data_dims.0 as usize, data_dims.1 as usize)?;
|
||||
extent.assert_layout(buffer.len())?;
|
||||
|
||||
let output_row_stride = u64::from(data_dims.0)
|
||||
.saturating_mul(self.image.samples_per_pixel() as u64)
|
||||
.saturating_mul(self.image.bits_per_sample as u64)
|
||||
.div_ceil(8);
|
||||
|
||||
self.read_chunk_to_bytes(buffer, chunk_index, output_row_stride)?;
|
||||
|
||||
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`.
|
||||
pub fn chunk_dimensions(&self) -> (u32, u32) {
|
||||
@@ -930,13 +1178,69 @@ impl<R: Read + Seek> Decoder<R> {
|
||||
.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 data_dims @ (width, height) = (self.image.width, self.image.height);
|
||||
|
||||
let layout = self
|
||||
.result_extent(width as usize, height as usize)?
|
||||
.preferred_layout()?;
|
||||
|
||||
let row_stride = self.image.minimum_row_stride(data_dims);
|
||||
|
||||
Ok(BufferLayoutPreference {
|
||||
len: layout.size(),
|
||||
row_stride,
|
||||
})
|
||||
}
|
||||
|
||||
/// Decodes the entire image and return it as a Vector
|
||||
///
|
||||
/// # 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`.
|
||||
pub fn read_image(&mut self) -> TiffResult<DecodingResult> {
|
||||
let width = self.image().width;
|
||||
let height = self.image().height;
|
||||
|
||||
let mut result = self.result_buffer(width as usize, height as usize)?;
|
||||
self.read_image_bytes(result.as_buffer(0).as_bytes_mut())?;
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// # 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`.
|
||||
pub fn read_image_bytes(&mut self, buffer: &mut [u8]) -> TiffResult<()> {
|
||||
let width = self.image().width;
|
||||
let height = self.image().height;
|
||||
|
||||
let extent = self.result_extent(width as usize, height as usize)?;
|
||||
extent.assert_layout(buffer.len())?;
|
||||
|
||||
if width == 0 || height == 0 {
|
||||
return Ok(result);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let chunk_dimensions = self.image().chunk_dimensions()?;
|
||||
@@ -960,12 +1264,12 @@ impl<R: Read + Seek> Decoder<R> {
|
||||
let output_row_bits = (width as u64 * self.image.bits_per_sample as u64)
|
||||
.checked_mul(samples as u64)
|
||||
.ok_or(TiffError::LimitsExceeded)?;
|
||||
let output_row_stride: usize = ((output_row_bits + 7) / 8).try_into()?;
|
||||
let output_row_stride: usize = output_row_bits.div_ceil(8).try_into()?;
|
||||
|
||||
let chunk_row_bits = (chunk_dimensions.0 as u64 * self.image.bits_per_sample as u64)
|
||||
.checked_mul(samples as u64)
|
||||
.ok_or(TiffError::LimitsExceeded)?;
|
||||
let chunk_row_bytes: usize = ((chunk_row_bits + 7) / 8).try_into()?;
|
||||
let chunk_row_bytes: usize = chunk_row_bits.div_ceil(8).try_into()?;
|
||||
|
||||
let chunks_across = ((width - 1) / chunk_dimensions.0 + 1) as usize;
|
||||
|
||||
@@ -989,21 +1293,32 @@ impl<R: Read + Seek> Decoder<R> {
|
||||
y * output_row_stride * chunk_dimensions.1 as usize + x * chunk_row_bytes;
|
||||
self.image.expand_chunk(
|
||||
&mut self.value_reader,
|
||||
&mut result.as_buffer(0).as_bytes_mut()[buffer_offset..],
|
||||
&mut buffer[buffer_offset..],
|
||||
output_row_stride,
|
||||
chunk as u32,
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 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,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1044,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> {
|
||||
@@ -1117,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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+11
-13
@@ -135,20 +135,17 @@ impl<R: Read> EndianReader<R> {
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// # READERS
|
||||
///
|
||||
|
||||
///
|
||||
/// ## Deflate Reader
|
||||
///
|
||||
//
|
||||
// # READERS
|
||||
//
|
||||
|
||||
/// Type alias for the deflate Reader
|
||||
#[cfg(feature = "deflate")]
|
||||
pub type DeflateReader<R> = flate2::read::ZlibDecoder<R>;
|
||||
|
||||
///
|
||||
/// ## LZW Reader
|
||||
///
|
||||
//
|
||||
// ## LZW Reader
|
||||
//
|
||||
|
||||
/// Reader that decompresses LZW streams
|
||||
#[cfg(feature = "lzw")]
|
||||
@@ -207,10 +204,11 @@ impl<R: Read> Read for LZWReader<R> {
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// ## PackBits Reader
|
||||
///
|
||||
//
|
||||
// ## PackBits Reader
|
||||
//
|
||||
|
||||
/// Internal state machine for the PackBitsReader.
|
||||
enum PackBitsReaderState {
|
||||
Header,
|
||||
Literal,
|
||||
|
||||
+9
-1
@@ -27,7 +27,9 @@ pub struct Directory {
|
||||
}
|
||||
|
||||
impl Directory {
|
||||
pub fn empty() -> Self {
|
||||
/// 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 const fn empty() -> Self {
|
||||
Directory {
|
||||
entries: BTreeMap::new(),
|
||||
next_ifd: None,
|
||||
@@ -72,6 +74,12 @@ impl Directory {
|
||||
self.entries.len()
|
||||
}
|
||||
|
||||
/// Check if there are any entries in this directory. Note that an empty directory can not be
|
||||
/// encoded in the file, it must contain at least one entry.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.entries.is_empty()
|
||||
}
|
||||
|
||||
/// Get the pointer to the next IFD, if it was defined.
|
||||
pub fn next(&self) -> Option<IfdPointer> {
|
||||
self.next_ifd.map(|n| IfdPointer(n.get()))
|
||||
|
||||
@@ -11,21 +11,17 @@ pub struct Deflate {
|
||||
/// It allows trading compression ratio for compression speed.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[non_exhaustive]
|
||||
#[derive(Default)]
|
||||
pub enum DeflateLevel {
|
||||
/// The fastest possible compression mode.
|
||||
Fast = 1,
|
||||
/// The conserative choice between speed and ratio.
|
||||
#[default]
|
||||
Balanced = 6,
|
||||
/// The best compression available with Deflate.
|
||||
Best = 9,
|
||||
}
|
||||
|
||||
impl Default for DeflateLevel {
|
||||
fn default() -> Self {
|
||||
DeflateLevel::Balanced
|
||||
}
|
||||
}
|
||||
|
||||
impl Deflate {
|
||||
/// Create a new deflate compressor with a specific level of compression.
|
||||
pub fn with_level(level: DeflateLevel) -> Self {
|
||||
|
||||
+6
-6
@@ -296,7 +296,7 @@ impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> {
|
||||
}
|
||||
|
||||
let entry = Self::write_value(
|
||||
&mut self.writer,
|
||||
self.writer,
|
||||
&DirectoryEntry {
|
||||
data_type: <T>::FIELD_TYPE,
|
||||
count: value.count().try_into()?,
|
||||
@@ -370,14 +370,14 @@ impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> {
|
||||
|
||||
if bytes.len() > in_entry_bytes {
|
||||
let offset = writer.offset();
|
||||
writer.write_bytes(&bytes)?;
|
||||
writer.write_bytes(bytes)?;
|
||||
|
||||
let offset = K::convert_offset(offset)?;
|
||||
offset_bytes[..offset.bytes()].copy_from_slice(&offset.data());
|
||||
} else {
|
||||
// Note: we have indicated our native byte order in the header, hence this
|
||||
// corresponds to our byte order no matter the value type.
|
||||
offset_bytes[..bytes.len()].copy_from_slice(&bytes);
|
||||
offset_bytes[..bytes.len()].copy_from_slice(bytes);
|
||||
}
|
||||
|
||||
// Undoing some hidden type. Offset is either u32 or u64. Due to the trait API being public
|
||||
@@ -532,11 +532,11 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind> ImageEncoder<'a, W, T,
|
||||
let rows_per_strip = {
|
||||
match compression.tag() {
|
||||
CompressionMethod::PackBits => 1, // Each row must be packed separately. Do not compress across row boundaries
|
||||
_ => (1_000_000 + row_bytes - 1) / row_bytes,
|
||||
_ => 1_000_000_u64.div_ceil(row_bytes),
|
||||
}
|
||||
};
|
||||
|
||||
let strip_count = (u64::from(height) + rows_per_strip - 1) / rows_per_strip;
|
||||
let strip_count = u64::from(height).div_ceil(rows_per_strip);
|
||||
|
||||
encoder.write_tag(Tag::ImageWidth, width)?;
|
||||
encoder.write_tag(Tag::ImageLength, height)?;
|
||||
@@ -705,7 +705,7 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind> ImageEncoder<'a, W, T,
|
||||
self.encoder.write_tag(Tag::RowsPerStrip, value)?;
|
||||
|
||||
let value: u64 = value as u64;
|
||||
self.strip_count = (self.height as u64 + value - 1) / value;
|
||||
self.strip_count = (self.height as u64).div_ceil(value);
|
||||
self.rows_per_strip = value;
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -450,7 +450,7 @@ impl TiffValue for str {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: TiffValue + ?Sized> TiffValue for &'a T {
|
||||
impl<T: TiffValue + ?Sized> TiffValue for &'_ T {
|
||||
const BYTE_LEN: u8 = T::BYTE_LEN;
|
||||
const FIELD_TYPE: Type = T::FIELD_TYPE;
|
||||
|
||||
|
||||
@@ -184,6 +184,9 @@ quick_error! {
|
||||
PredictorUnavailable {
|
||||
display("the requested predictor is not available")
|
||||
}
|
||||
InsufficientOutputBufferSize { needed: usize, provided: usize } {
|
||||
display("the borrowed output buffer is not large enough for the decoded data, needed {needed} but have {provided}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+20
-10
@@ -2,7 +2,7 @@ macro_rules! tags {
|
||||
{
|
||||
// Permit arbitrary meta items, which include documentation.
|
||||
$( #[$enum_attr:meta] )*
|
||||
$vis:vis enum $name:ident($ty:tt) $(unknown($unknown_doc:literal))* {
|
||||
$vis:vis enum $name:ident($ty:tt) $(unknown(#[$unknown_meta:meta] $unknown_doc:ident))* {
|
||||
// Each of the `Name = Val,` permitting documentation.
|
||||
$($(#[$ident_attr:meta])* $tag:ident = $val:expr,)*
|
||||
}
|
||||
@@ -13,7 +13,7 @@ macro_rules! tags {
|
||||
pub enum $name {
|
||||
$($(#[$ident_attr])* $tag,)*
|
||||
$(
|
||||
#[doc = $unknown_doc]
|
||||
#[$unknown_meta]
|
||||
Unknown($ty),
|
||||
)*
|
||||
}
|
||||
@@ -31,7 +31,7 @@ macro_rules! tags {
|
||||
fn __to_inner_type(&self) -> $ty {
|
||||
match *self {
|
||||
$( $name::$tag => $val, )*
|
||||
$( $name::Unknown(n) => { $unknown_doc; n }, )*
|
||||
$( $name::Unknown($unknown_doc) => { $unknown_doc }, )*
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,7 @@ macro_rules! tags {
|
||||
tags!($name, $ty, $($unknown_doc)*);
|
||||
};
|
||||
// For u16 tags, provide direct inherent primitive conversion methods.
|
||||
($name:tt, u16, $($unknown_doc:literal)*) => {
|
||||
($name:tt, u16, $($unknown_doc:ident)*) => {
|
||||
impl $name {
|
||||
#[inline(always)]
|
||||
pub fn from_u16(val: u16) -> Option<Self> {
|
||||
@@ -48,9 +48,8 @@ macro_rules! tags {
|
||||
|
||||
$(
|
||||
#[inline(always)]
|
||||
pub fn from_u16_exhaustive(val: u16) -> Self {
|
||||
$unknown_doc;
|
||||
Self::__from_inner_type(val).unwrap_or_else(|_| $name::Unknown(val))
|
||||
pub fn from_u16_exhaustive($unknown_doc: u16) -> Self {
|
||||
Self::__from_inner_type($unknown_doc).unwrap_or_else(|_| $name::Unknown($unknown_doc))
|
||||
}
|
||||
)*
|
||||
|
||||
@@ -68,7 +67,10 @@ macro_rules! tags {
|
||||
// Note: These tags appear in the order they are mentioned in the TIFF reference
|
||||
tags! {
|
||||
/// TIFF tags
|
||||
pub enum Tag(u16) unknown("A private or extension tag") {
|
||||
pub enum Tag(u16) unknown(
|
||||
/// A private or extension tag
|
||||
unknown
|
||||
) {
|
||||
// Baseline tags:
|
||||
Artist = 315,
|
||||
// grayscale images PhotometricInterpretation 1 or 3
|
||||
@@ -131,6 +133,8 @@ pub enum Tag(u16) unknown("A private or extension tag") {
|
||||
ExifDirectory = 0x8769,
|
||||
// <https://web.archive.org/web/20131111073619/http://www.exif.org/Exif2-1.PDF>
|
||||
GpsDirectory = 0x8825,
|
||||
// <https://www.color.org/technotes/ICC-Technote-ProfileEmbedding.pdf>
|
||||
IccProfile = 34675,
|
||||
GeoKeyDirectoryTag = 34735, // (SPOT)
|
||||
GeoDoubleParamsTag = 34736, // (SPOT)
|
||||
GeoAsciiParamsTag = 34737, // (SPOT)
|
||||
@@ -192,7 +196,10 @@ pub enum Type(u16) {
|
||||
tags! {
|
||||
/// See [TIFF compression tags](https://www.awaresystems.be/imaging/tiff/tifftags/compression.html)
|
||||
/// for reference.
|
||||
pub enum CompressionMethod(u16) unknown("A custom compression method") {
|
||||
pub enum CompressionMethod(u16) unknown(
|
||||
/// A custom compression method
|
||||
unknown
|
||||
) {
|
||||
None = 1,
|
||||
Huffman = 2,
|
||||
Fax3 = 3,
|
||||
@@ -254,7 +261,10 @@ pub enum ResolutionUnit(u16) {
|
||||
}
|
||||
|
||||
tags! {
|
||||
pub enum SampleFormat(u16) unknown("An unknown extension sample format") {
|
||||
pub enum SampleFormat(u16) unknown(
|
||||
/// An unknown extension sample format
|
||||
unknown
|
||||
) {
|
||||
Uint = 1,
|
||||
Int = 2,
|
||||
IEEEFP = 3,
|
||||
|
||||
+160
-10
@@ -4,6 +4,7 @@ use tiff::decoder::{ifd, Decoder, DecodingResult};
|
||||
use tiff::ColorType;
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::{Read, Seek};
|
||||
use std::path::PathBuf;
|
||||
|
||||
const TEST_IMAGE_DIR: &str = "./tests/images/";
|
||||
@@ -45,12 +46,12 @@ fn test_image_color_type_unsupported(file: &str, expected_type: ColorType) {
|
||||
let img_file = File::open(path).expect("Cannot find test image!");
|
||||
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
|
||||
assert_eq!(decoder.colortype().unwrap(), expected_type);
|
||||
assert!(match decoder.read_image() {
|
||||
assert!(matches!(
|
||||
decoder.read_image(),
|
||||
Err(tiff::TiffError::UnsupportedError(
|
||||
tiff::TiffUnsupportedError::UnsupportedColorType(_),
|
||||
)) => true,
|
||||
_ => false,
|
||||
});
|
||||
))
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -172,7 +173,7 @@ fn test_string_tags() {
|
||||
&s,
|
||||
"GraphicsMagick 1.2 unreleased Q16 http://www.GraphicsMagick.org/"
|
||||
),
|
||||
_ => assert!(false),
|
||||
other => unreachable!("Expected Ascii tag from file, got {other:?}"),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -231,6 +232,54 @@ fn test_tiled_rect_rgb_u8() {
|
||||
test_image_sum_u8("tiled-rect-rgb-u8.tif", ColorType::RGB(8), 62081032);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_inner_access() {
|
||||
let path = PathBuf::from(TEST_IMAGE_DIR).join("tiled-rect-rgb-u8.tif");
|
||||
let img_file = File::open(path).expect("Cannot find test image!");
|
||||
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
|
||||
assert_eq!(decoder.colortype().unwrap(), ColorType::RGB(8));
|
||||
|
||||
let c = decoder
|
||||
.get_tag(tiff::tags::Tag::Compression)
|
||||
.unwrap()
|
||||
.into_u16()
|
||||
.unwrap();
|
||||
assert_eq!(c, tiff::tags::CompressionMethod::None.to_u16());
|
||||
|
||||
// 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
|
||||
.get_tag_u32_vec(tiff::tags::Tag::TileOffsets)
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.next()
|
||||
.unwrap();
|
||||
let first_byte_count = decoder
|
||||
.get_tag_u32_vec(tiff::tags::Tag::TileByteCounts)
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.next()
|
||||
.unwrap();
|
||||
decoder
|
||||
.inner()
|
||||
.seek(std::io::SeekFrom::Start(first_offset as u64))
|
||||
.expect("Cannot seek to tile offset");
|
||||
let mut buf = vec![0; first_byte_count as usize];
|
||||
decoder
|
||||
.inner()
|
||||
.read_exact(&mut buf)
|
||||
.expect("Cannot read tile data");
|
||||
let raw_sum: u64 = buf.into_iter().map(<u64>::from).sum();
|
||||
|
||||
match decoder.read_chunk(0).unwrap() {
|
||||
DecodingResult::U8(chunk) => {
|
||||
let sum: u64 = chunk.into_iter().map(<u64>::from).sum();
|
||||
assert_eq!(sum, raw_sum);
|
||||
}
|
||||
_ => panic!("Wrong bit depth"),
|
||||
}
|
||||
}
|
||||
|
||||
/* #[test]
|
||||
fn test_tiled_jpeg_rgb_u8() {
|
||||
test_image_sum_u8("tiled-jpeg-rgb-u8.tif", ColorType::RGB(8), 93031606);
|
||||
@@ -292,6 +341,11 @@ fn test_tiled_incremental() {
|
||||
|
||||
#[test]
|
||||
fn test_planar_rgb_u8() {
|
||||
test_image_sum_u8("planar-rgb-u8.tif", ColorType::RGB(8), 15417630);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_planar_bands() {
|
||||
// gdal_translate tiled-rgb-u8.tif planar-rgb-u8.tif -co INTERLEAVE=BAND -co COMPRESS=LZW -co PROFILE=BASELINE
|
||||
let file = "planar-rgb-u8.tif";
|
||||
let expected_type = ColorType::RGB(8);
|
||||
@@ -328,8 +382,6 @@ fn test_planar_rgb_u8() {
|
||||
}
|
||||
_ => panic!("Wrong bit depth"),
|
||||
}
|
||||
|
||||
test_image_sum_u8(file, ColorType::RGB(8), 15417630);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -348,7 +400,7 @@ fn test_div_zero() {
|
||||
|
||||
match err {
|
||||
TiffError::FormatError(TiffFormatError::StripTileTagConflict) => {}
|
||||
unexpected => panic!("Unexpected error {}", unexpected),
|
||||
unexpected => panic!("Unexpected error {unexpected}"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -366,7 +418,7 @@ fn test_too_many_value_bytes() {
|
||||
|
||||
match error {
|
||||
tiff::TiffError::LimitsExceeded => {}
|
||||
unexpected => panic!("Unexpected error {}", unexpected),
|
||||
unexpected => panic!("Unexpected error {unexpected}"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -497,7 +549,7 @@ fn timeout() {
|
||||
|
||||
match error {
|
||||
TiffError::FormatError(TiffFormatError::CycleInOffsets) => {}
|
||||
e => panic!("Unexpected error {:?}", e),
|
||||
e => panic!("Unexpected error {e:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -522,3 +574,101 @@ fn test_zstd_compression() {
|
||||
// gdal_translate -co COMPRESS=ZSTD -co ZSTD_LEVEL=20 int16.tif int16_zstd.tif
|
||||
test_image_sum_i16("int16_zstd.tif", ColorType::Gray(16), 354396);
|
||||
}
|
||||
|
||||
fn test_image_bytes(
|
||||
file: &str,
|
||||
expected: &[(ColorType, u32)],
|
||||
// Represent all samples as little endian, ensuring check sums on all platforms.
|
||||
normalize_byte_order: fn(&mut [u8]),
|
||||
) {
|
||||
let path = PathBuf::from(TEST_IMAGE_DIR).join(file);
|
||||
let img_file = File::open(path).expect("Cannot find test image!");
|
||||
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
|
||||
|
||||
let mut buffer = vec![0u8; 0];
|
||||
for (idx, &(expected_type, expected_crc)) in expected.iter().enumerate() {
|
||||
if idx > 0 {
|
||||
decoder
|
||||
.next_image()
|
||||
.expect("mismatched number of directories");
|
||||
}
|
||||
|
||||
assert_eq!(decoder.colortype().unwrap(), expected_type);
|
||||
let layout = decoder
|
||||
.image_buffer_layout()
|
||||
.expect("Cannot describe buffer layout");
|
||||
|
||||
buffer.resize(layout.len, 0u8);
|
||||
decoder.read_image_bytes(&mut buffer).unwrap();
|
||||
normalize_byte_order(&mut buffer);
|
||||
|
||||
// Well almost a crc..
|
||||
assert_eq!(crc32fast::hash(&buffer), expected_crc);
|
||||
}
|
||||
}
|
||||
|
||||
fn byte_order_u16(bytes: &mut [u8]) {
|
||||
for chunk in bytes.chunks_exact_mut(2) {
|
||||
let n = u16::from_ne_bytes(chunk.try_into().unwrap());
|
||||
chunk.copy_from_slice(&n.to_le_bytes());
|
||||
}
|
||||
}
|
||||
|
||||
fn byte_order_u32(bytes: &mut [u8]) {
|
||||
for chunk in bytes.chunks_exact_mut(4) {
|
||||
let n = u32::from_ne_bytes(chunk.try_into().unwrap());
|
||||
chunk.copy_from_slice(&n.to_le_bytes());
|
||||
}
|
||||
}
|
||||
|
||||
fn byte_order_u64(bytes: &mut [u8]) {
|
||||
for chunk in bytes.chunks_exact_mut(8) {
|
||||
let n = u64::from_ne_bytes(chunk.try_into().unwrap());
|
||||
chunk.copy_from_slice(&n.to_le_bytes());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bytes_cmyk_3c_8b() {
|
||||
test_image_bytes(
|
||||
"cmyk-3c-8b.tiff",
|
||||
&[(ColorType::CMYK(8), 2935032230)],
|
||||
|_| (),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bytes_gray_16b() {
|
||||
test_image_bytes(
|
||||
"minisblack-1c-16b.tiff",
|
||||
&[(ColorType::Gray(16), 3524597615)],
|
||||
byte_order_u16,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bytes_gray_32b() {
|
||||
test_image_bytes(
|
||||
"gradient-1c-32b.tiff",
|
||||
&[(ColorType::Gray(32), 2257036530)],
|
||||
byte_order_u32,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bytes_gray_u64() {
|
||||
test_image_bytes(
|
||||
"gradient-1c-64b.tiff",
|
||||
&[(ColorType::Gray(64), 2156547113)],
|
||||
byte_order_u64,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bytes_gray_f32() {
|
||||
test_image_bytes(
|
||||
"gradient-1c-32b-float.tiff",
|
||||
&[(ColorType::Gray(32), 3232356730)],
|
||||
byte_order_u32,
|
||||
);
|
||||
}
|
||||
@@ -621,7 +621,7 @@ fn test_rows_per_strip() {
|
||||
let img2 = [i; 2 * 100];
|
||||
match decoder.read_chunk(i as u32).unwrap() {
|
||||
DecodingResult::U8(data) => assert_eq!(&img2[..], &data[..]),
|
||||
other => panic!("Incorrect strip type {:?}", other),
|
||||
other => panic!("Incorrect strip type {other:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -652,8 +652,6 @@ fn test_auxiliary_directory() {
|
||||
encoder.write_tag(Tag::ExifDirectory, exif.offset).unwrap();
|
||||
}
|
||||
|
||||
drop(tiff);
|
||||
|
||||
data.set_position(0);
|
||||
let mut decoder = Decoder::new(&mut data).unwrap();
|
||||
|
||||
|
||||
@@ -116,7 +116,7 @@ fn encode_decode_with_compression(compression: Compression) {
|
||||
assert_eq!(
|
||||
match decoder.read_image() {
|
||||
Ok(DecodingResult::U16(image_data)) => image_data,
|
||||
unexpected => panic!("Descoding RGB failed: {:?}", unexpected),
|
||||
unexpected => panic!("Descoding RGB failed: {unexpected:?}"),
|
||||
},
|
||||
image_rgb.reference_data()
|
||||
);
|
||||
@@ -126,7 +126,7 @@ fn encode_decode_with_compression(compression: Compression) {
|
||||
assert_eq!(
|
||||
match decoder.read_image() {
|
||||
Ok(DecodingResult::U8(image_data)) => image_data,
|
||||
unexpected => panic!("Decoding grayscale failed: {:?}", unexpected),
|
||||
unexpected => panic!("Decoding grayscale failed: {unexpected:?}"),
|
||||
},
|
||||
image_grayscale.reference_data()
|
||||
);
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
extern crate tiff;
|
||||
|
||||
use tiff::decoder::{Decoder, DecodingResult};
|
||||
use tiff::encoder::{colortype, Compression, Predictor, TiffEncoder};
|
||||
use tiff::encoder::{colortype, Predictor, TiffEncoder};
|
||||
use tiff::ColorType;
|
||||
|
||||
use std::fs::File;
|
||||
|
||||
+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