Compare commits
31
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
695208cffe | ||
|
|
4acdfea783 | ||
|
|
64286c2110 | ||
|
|
12c98535f6 | ||
|
|
1556713d6e | ||
|
|
682ef0c375 | ||
|
|
bc71086a96 | ||
|
|
e67bac763f | ||
|
|
839655b975 | ||
|
|
85635c3cd2 | ||
|
|
481a2ab54a | ||
|
|
7c6c327817 | ||
|
|
9f1de91880 | ||
|
|
4a68feab5a | ||
|
|
b5afb8cca4 | ||
|
|
c8d43262e9 | ||
|
|
80ce77190e | ||
|
|
c75aa98ce2 | ||
|
|
c1087f6f08 | ||
|
|
8cc6bc657d | ||
|
|
9fcc2a3b65 | ||
|
|
666f871d30 | ||
|
|
a919b8febf | ||
|
|
85b5df7a62 | ||
|
|
756ea3e813 | ||
|
|
0eead083f6 | ||
|
|
34232d8141 | ||
|
|
cbcf6b97d4 | ||
|
|
19c811e86d | ||
|
|
50be15e356 | ||
|
|
718f2cb455 |
No files matched your search
+37
@@ -1,3 +1,40 @@
|
|||||||
|
# Version 0.10.3
|
||||||
|
|
||||||
|
New features:
|
||||||
|
- Group 4 `fax` is now recognized as a compression type and decoded.
|
||||||
|
- A crate feature `fax` controls related dependencies, on by default.
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
- The presence of PhotometricInterpretation::WhiteIsZero now returns an error
|
||||||
|
if the color type can not be inverted by the library where they were
|
||||||
|
previously silently ignored. Please report any missing color types.
|
||||||
|
|
||||||
|
Fixes:
|
||||||
|
- The PhotometricInterpretation::WhiteIsZero is now also applied to 1-bit
|
||||||
|
images, as well as 2-bit and 4-bit sample types where previously the presence
|
||||||
|
of this tag was ignored.
|
||||||
|
|
||||||
|
# Version 0.10.2
|
||||||
|
|
||||||
|
Republished 0.10.1, fixing an accidentally removed method (`Decoder::find_tag_unsigned_vec`).
|
||||||
|
|
||||||
|
# Version 0.10.1
|
||||||
|
|
||||||
|
New features:
|
||||||
|
- Added a dedicated `Directory` type (#277)
|
||||||
|
- Expose methods related to `Directory` publicly (#278)
|
||||||
|
- Implement writing directories (#281, #283)
|
||||||
|
- Add ICC (color profile) to the set of known tags (#287)
|
||||||
|
- Added `Decoder.inner()` to access the underlying reader (#289)
|
||||||
|
- Added methods for reading into a pre-allocated byte buffer (#285)
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
- No longer perform color conversion when decoding embedded JPEG. This fixes decoding for JPEG images with alpha channel. (#274)
|
||||||
|
- More lenient LZW decoding to match libtiff (#231)
|
||||||
|
|
||||||
|
Fixes:
|
||||||
|
- Fixed seeking multiple times in a row (#279)
|
||||||
|
|
||||||
# Version 0.10.0
|
# Version 0.10.0
|
||||||
|
|
||||||
New features:
|
New features:
|
||||||
|
|||||||
+6
-3
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "tiff"
|
name = "tiff"
|
||||||
version = "0.10.0"
|
version = "0.10.3"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
|
||||||
@@ -20,6 +20,8 @@ exclude = ["tests/images/*", "tests/fuzz_images/*"]
|
|||||||
half = { version = "2.4.1" }
|
half = { version = "2.4.1" }
|
||||||
quick-error = "2.0.1"
|
quick-error = "2.0.1"
|
||||||
|
|
||||||
|
# Rename to avoid confusion with the feature name.
|
||||||
|
fax34 = { package = "fax", version = "0.2.6", optional = true }
|
||||||
flate2 = { version = "1.0.20", optional = true }
|
flate2 = { version = "1.0.20", optional = true }
|
||||||
weezl = { version = "0.1.10", optional = true }
|
weezl = { version = "0.1.10", optional = true }
|
||||||
zstd = { version = "0.13", optional = true }
|
zstd = { version = "0.13", optional = true }
|
||||||
@@ -28,13 +30,14 @@ zune-jpeg = { version = "0.4.17", optional = true }
|
|||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
criterion = "0.3.1"
|
criterion = "0.3.1"
|
||||||
# We may already depend on this via flate2
|
# We may already depend on this via flate2
|
||||||
crc32fast = "1"
|
crc32fast = "1.5"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["deflate", "jpeg", "lzw"]
|
default = ["deflate", "fax", "jpeg", "lzw"]
|
||||||
|
|
||||||
# Compression algorithms
|
# Compression algorithms
|
||||||
deflate = ["dep:flate2"]
|
deflate = ["dep:flate2"]
|
||||||
|
fax = ["dep:fax34"]
|
||||||
jpeg = ["dep:zune-jpeg"]
|
jpeg = ["dep:zune-jpeg"]
|
||||||
lzw = ["dep:weezl"]
|
lzw = ["dep:weezl"]
|
||||||
zstd = ["dep:zstd"]
|
zstd = ["dep:zstd"]
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ This table lists photometric interpretations and sample formats which are suppor
|
|||||||
| LZW | ✓ | ✓ |
|
| LZW | ✓ | ✓ |
|
||||||
| Deflate | ✓ | ✓ |
|
| Deflate | ✓ | ✓ |
|
||||||
| PackBits | ✓ | ✓ |
|
| PackBits | ✓ | ✓ |
|
||||||
|
| Fax4 | ✓ | not yet |
|
||||||
| JPEG | ✓ | not yet |
|
| JPEG | ✓ | not yet |
|
||||||
| ZSTD | ✓ | not yet |
|
| ZSTD | ✓ | not yet |
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -577,10 +577,10 @@ impl Entry {
|
|||||||
Type::BYTE => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
Type::BYTE => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
||||||
let mut buf = [0; 1];
|
let mut buf = [0; 1];
|
||||||
reader.inner().read_exact(&mut buf)?;
|
reader.inner().read_exact(&mut buf)?;
|
||||||
Ok(UnsignedBig(u64::from(buf[0])))
|
Ok(Byte(buf[0]))
|
||||||
}),
|
}),
|
||||||
Type::SBYTE => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
Type::SBYTE => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
||||||
Ok(SignedBig(i64::from(reader.read_i8()?)))
|
Ok(SignedByte(reader.read_i8()?))
|
||||||
}),
|
}),
|
||||||
Type::SHORT => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
Type::SHORT => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
||||||
Ok(Short(reader.read_u16()?))
|
Ok(Short(reader.read_u16()?))
|
||||||
|
|||||||
+19
-18
@@ -9,6 +9,7 @@ use crate::tags::{
|
|||||||
use crate::{
|
use crate::{
|
||||||
ColorType, Directory, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, UsageError,
|
ColorType, Directory, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, UsageError,
|
||||||
};
|
};
|
||||||
|
|
||||||
use std::io::{self, Cursor, Read, Seek};
|
use std::io::{self, Cursor, Read, Seek};
|
||||||
use std::num::NonZeroUsize;
|
use std::num::NonZeroUsize;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@@ -86,19 +87,7 @@ impl Image {
|
|||||||
decoder: &mut ValueReader<R>,
|
decoder: &mut ValueReader<R>,
|
||||||
ifd: Directory,
|
ifd: Directory,
|
||||||
) -> TiffResult<Image> {
|
) -> TiffResult<Image> {
|
||||||
let img = Self::from_ref(decoder, &ifd)?;
|
let mut tag_reader = TagReader { decoder, ifd: &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 width = tag_reader.require_tag(Tag::ImageWidth)?.into_u32()?;
|
||||||
let height = tag_reader.require_tag(Tag::ImageLength)?.into_u32()?;
|
let height = tag_reader.require_tag(Tag::ImageLength)?.into_u32()?;
|
||||||
@@ -300,7 +289,7 @@ impl Image {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Ok(Image {
|
Ok(Image {
|
||||||
ifd: None,
|
ifd: Some(ifd),
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
bits_per_sample: bits_per_sample[0],
|
bits_per_sample: bits_per_sample[0],
|
||||||
@@ -355,6 +344,9 @@ impl Image {
|
|||||||
),
|
),
|
||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
|
// TODO: treatment of WhiteIsZero is not quite consistent with `invert_colors` that is
|
||||||
|
// later called when that interpretation is read. That function does not support
|
||||||
|
// Multiband as a color type and will error. It's unclear how to resolve that exactly.
|
||||||
PhotometricInterpretation::BlackIsZero | PhotometricInterpretation::WhiteIsZero => {
|
PhotometricInterpretation::BlackIsZero | PhotometricInterpretation::WhiteIsZero => {
|
||||||
match self.samples {
|
match self.samples {
|
||||||
1 => Ok(ColorType::Gray(self.bits_per_sample)),
|
1 => Ok(ColorType::Gray(self.bits_per_sample)),
|
||||||
@@ -396,7 +388,9 @@ impl Image {
|
|||||||
reader: R,
|
reader: R,
|
||||||
compression_method: CompressionMethod,
|
compression_method: CompressionMethod,
|
||||||
compressed_length: u64,
|
compressed_length: u64,
|
||||||
#[allow(unused_variables)] jpeg_tables: Option<&[u8]>,
|
// FIXME: these should be `expect` attributes or we choose another way of passing them.
|
||||||
|
#[cfg_attr(not(feature = "jpeg"), allow(unused_variables))] jpeg_tables: Option<&[u8]>,
|
||||||
|
#[cfg_attr(not(feature = "fax"), allow(unused_variables))] dimensions: (u32, u32),
|
||||||
) -> TiffResult<Box<dyn Read + 'r>> {
|
) -> TiffResult<Box<dyn Read + 'r>> {
|
||||||
Ok(match compression_method {
|
Ok(match compression_method {
|
||||||
CompressionMethod::None => Box::new(reader),
|
CompressionMethod::None => Box::new(reader),
|
||||||
@@ -464,6 +458,12 @@ impl Image {
|
|||||||
|
|
||||||
Box::new(Cursor::new(data))
|
Box::new(Cursor::new(data))
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "fax")]
|
||||||
|
CompressionMethod::Fax4 => Box::new(super::stream::Group4Reader::new(
|
||||||
|
dimensions,
|
||||||
|
reader,
|
||||||
|
compressed_length,
|
||||||
|
)?),
|
||||||
method => {
|
method => {
|
||||||
return Err(TiffError::UnsupportedError(
|
return Err(TiffError::UnsupportedError(
|
||||||
TiffUnsupportedError::UnsupportedCompressionMethod(method),
|
TiffUnsupportedError::UnsupportedCompressionMethod(method),
|
||||||
@@ -670,6 +670,7 @@ impl Image {
|
|||||||
compression_method,
|
compression_method,
|
||||||
*compressed_bytes,
|
*compressed_bytes,
|
||||||
self.jpeg_tables.as_deref().map(|a| &**a),
|
self.jpeg_tables.as_deref().map(|a| &**a),
|
||||||
|
chunk_dims,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
if output_row_stride == chunk_row_bytes {
|
if output_row_stride == chunk_row_bytes {
|
||||||
@@ -686,7 +687,7 @@ impl Image {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
if photometric_interpretation == PhotometricInterpretation::WhiteIsZero {
|
if photometric_interpretation == PhotometricInterpretation::WhiteIsZero {
|
||||||
super::invert_colors(tile, color_type, self.sample_format);
|
super::invert_colors(tile, color_type, self.sample_format)?;
|
||||||
}
|
}
|
||||||
} else if chunk_row_bytes > data_row_bytes && self.predictor == Predictor::FloatingPoint {
|
} else if chunk_row_bytes > data_row_bytes && self.predictor == Predictor::FloatingPoint {
|
||||||
// The floating point predictor shuffles the padding bytes into the encoded output, so
|
// The floating point predictor shuffles the padding bytes into the encoded output, so
|
||||||
@@ -703,7 +704,7 @@ impl Image {
|
|||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
if photometric_interpretation == PhotometricInterpretation::WhiteIsZero {
|
if photometric_interpretation == PhotometricInterpretation::WhiteIsZero {
|
||||||
super::invert_colors(row, color_type, self.sample_format);
|
super::invert_colors(row, color_type, self.sample_format)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -725,7 +726,7 @@ impl Image {
|
|||||||
predictor,
|
predictor,
|
||||||
);
|
);
|
||||||
if photometric_interpretation == PhotometricInterpretation::WhiteIsZero {
|
if photometric_interpretation == PhotometricInterpretation::WhiteIsZero {
|
||||||
super::invert_colors(row, color_type, self.sample_format);
|
super::invert_colors(row, color_type, self.sample_format)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+75
-137
@@ -23,7 +23,7 @@ mod stream;
|
|||||||
mod tag_reader;
|
mod tag_reader;
|
||||||
|
|
||||||
/// Result of a decoding process
|
/// Result of a decoding process
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug)]
|
||||||
pub enum DecodingResult {
|
pub enum DecodingResult {
|
||||||
/// A vector of unsigned bytes
|
/// A vector of unsigned bytes
|
||||||
U8(Vec<u8>),
|
U8(Vec<u8>),
|
||||||
@@ -138,7 +138,7 @@ impl DecodingResult {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn as_buffer(&mut self, start: usize) -> DecodingBuffer {
|
pub fn as_buffer(&mut self, start: usize) -> DecodingBuffer<'_> {
|
||||||
match *self {
|
match *self {
|
||||||
DecodingResult::U8(ref mut buf) => DecodingBuffer::U8(&mut buf[start..]),
|
DecodingResult::U8(ref mut buf) => DecodingBuffer::U8(&mut buf[start..]),
|
||||||
DecodingResult::U16(ref mut buf) => DecodingBuffer::U16(&mut buf[start..]),
|
DecodingResult::U16(ref mut buf) => DecodingBuffer::U16(&mut buf[start..]),
|
||||||
@@ -349,10 +349,6 @@ where
|
|||||||
ifd_offsets: Vec<IfdPointer>,
|
ifd_offsets: Vec<IfdPointer>,
|
||||||
/// Map from the ifd into the `ifd_offsets` ordered list.
|
/// Map from the ifd into the `ifd_offsets` ordered list.
|
||||||
seen_ifds: cycles::IfdCycles,
|
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,
|
image: Image,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -479,11 +475,22 @@ fn fix_endianness_and_predict(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn invert_colors(buf: &mut [u8], color_type: ColorType, sample_format: SampleFormat) {
|
fn invert_colors(
|
||||||
|
buf: &mut [u8],
|
||||||
|
color_type: ColorType,
|
||||||
|
sample_format: SampleFormat,
|
||||||
|
) -> TiffResult<()> {
|
||||||
match (color_type, sample_format) {
|
match (color_type, sample_format) {
|
||||||
(ColorType::Gray(8), SampleFormat::Uint) => {
|
// Where pixels do not cross a byte boundary
|
||||||
|
(ColorType::Gray(1 | 2 | 4 | 8), SampleFormat::Uint) => {
|
||||||
for x in buf {
|
for x in buf {
|
||||||
*x = 0xff - *x;
|
// Equivalent to both of the following:
|
||||||
|
//
|
||||||
|
// *x = 0xff - *x
|
||||||
|
// *x = !*x
|
||||||
|
//
|
||||||
|
// since -x = !x+1
|
||||||
|
*x = !*x;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(ColorType::Gray(16), SampleFormat::Uint) => {
|
(ColorType::Gray(16), SampleFormat::Uint) => {
|
||||||
@@ -516,8 +523,14 @@ fn invert_colors(buf: &mut [u8], color_type: ColorType, sample_format: SampleFor
|
|||||||
x.copy_from_slice(&(1.0 - v).to_ne_bytes());
|
x.copy_from_slice(&(1.0 - v).to_ne_bytes());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {
|
||||||
|
return Err(TiffError::UnsupportedError(
|
||||||
|
TiffUnsupportedError::UnknownInterpretation,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fix endianness. If `byte_order` matches the host, then conversion is a no-op.
|
/// Fix endianness. If `byte_order` matches the host, then conversion is a no-op.
|
||||||
@@ -551,7 +564,6 @@ fn fix_endianness(buf: &mut [u8], byte_order: ByteOrder, bit_depth: u8) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<R: Read + Seek> Decoder<R> {
|
impl<R: Read + Seek> Decoder<R> {
|
||||||
/// Create a new decoder that decodes from the stream ```r```
|
|
||||||
pub fn new(mut r: R) -> TiffResult<Decoder<R>> {
|
pub fn new(mut r: R) -> TiffResult<Decoder<R>> {
|
||||||
let mut endianess = Vec::with_capacity(2);
|
let mut endianess = Vec::with_capacity(2);
|
||||||
(&mut r).take(2).read_to_end(&mut endianess)?;
|
(&mut r).take(2).read_to_end(&mut endianess)?;
|
||||||
@@ -628,7 +640,6 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
chunk_offsets: Vec::new(),
|
chunk_offsets: Vec::new(),
|
||||||
chunk_bytes: Vec::new(),
|
chunk_bytes: Vec::new(),
|
||||||
},
|
},
|
||||||
non_image_ifd: None,
|
|
||||||
};
|
};
|
||||||
decoder.next_image()?;
|
decoder.next_image()?;
|
||||||
Ok(decoder)
|
Ok(decoder)
|
||||||
@@ -658,85 +669,48 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
|
|
||||||
/// Loads the IFD at the specified index in the list, if one exists
|
/// Loads the IFD at the specified index in the list, if one exists
|
||||||
pub fn seek_to_image(&mut self, ifd_index: usize) -> TiffResult<()> {
|
pub fn seek_to_image(&mut self, ifd_index: usize) -> TiffResult<()> {
|
||||||
let ifd = self.seek_to_directory(ifd_index)?;
|
// 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
|
||||||
self.non_image_ifd = None;
|
if ifd_index >= self.ifd_offsets.len() {
|
||||||
self.image = Image::from_reader(&mut self.value_reader, ifd)?;
|
// We possibly need to load in the next IFD
|
||||||
Ok(())
|
if self.next_ifd.is_none() {
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
self.current_ifd = None;
|
||||||
|
|
||||||
self.next_ifd()
|
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,
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn next_ifd(&mut self) -> TiffResult<Directory> {
|
fn next_ifd(&mut self) -> TiffResult<Directory> {
|
||||||
let Some(next_ifd) = self.next_ifd.take() else {
|
let Some(next_ifd) = self.next_ifd.take() else {
|
||||||
self.current_ifd = None;
|
|
||||||
self.non_image_ifd = None;
|
|
||||||
|
|
||||||
return Err(TiffError::FormatError(
|
return Err(TiffError::FormatError(
|
||||||
TiffFormatError::ImageFileDirectoryNotFound,
|
TiffFormatError::ImageFileDirectoryNotFound,
|
||||||
));
|
));
|
||||||
@@ -763,44 +737,10 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
/// To determine whether there are more images call `TIFFDecoder::more_images` instead.
|
/// To determine whether there are more images call `TIFFDecoder::more_images` instead.
|
||||||
pub fn next_image(&mut self) -> TiffResult<()> {
|
pub fn next_image(&mut self) -> TiffResult<()> {
|
||||||
let ifd = self.next_ifd()?;
|
let ifd = self.next_ifd()?;
|
||||||
self.non_image_ifd = None;
|
|
||||||
self.image = Image::from_reader(&mut self.value_reader, ifd)?;
|
self.image = Image::from_reader(&mut self.value_reader, ifd)?;
|
||||||
Ok(())
|
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.
|
/// Returns `true` if there is at least one more image available.
|
||||||
pub fn more_images(&self) -> bool {
|
pub fn more_images(&self) -> bool {
|
||||||
self.next_ifd.is_some()
|
self.next_ifd.is_some()
|
||||||
@@ -1303,22 +1243,11 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Get the IFD decoder for our current image IFD.
|
/// Get the IFD decoder for our current image IFD.
|
||||||
fn current_directory_ifd(&mut self) -> IfdDecoder<'_> {
|
fn image_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 {
|
IfdDecoder {
|
||||||
inner: tag_reader::TagReader {
|
inner: tag_reader::TagReader {
|
||||||
decoder: &mut self.value_reader,
|
decoder: &mut self.value_reader,
|
||||||
ifd,
|
ifd: self.image.ifd.as_ref().unwrap(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1359,25 +1288,34 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
/// Tries to retrieve a tag from the current image directory.
|
/// Tries to retrieve a tag from the current image directory.
|
||||||
/// Return `Ok(None)` if the tag is not present.
|
/// Return `Ok(None)` if the tag is not present.
|
||||||
pub fn find_tag(&mut self, tag: Tag) -> TiffResult<Option<ifd::Value>> {
|
pub fn find_tag(&mut self, tag: Tag) -> TiffResult<Option<ifd::Value>> {
|
||||||
self.current_directory_ifd().find_tag(tag)
|
self.image_ifd().find_tag(tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tries to retrieve a tag in the current image directory and convert it to the desired
|
/// Tries to retrieve a tag in the current image directory and convert it to the desired
|
||||||
/// unsigned type.
|
/// unsigned type.
|
||||||
pub fn find_tag_unsigned<T: TryFrom<u64>>(&mut self, tag: Tag) -> TiffResult<Option<T>> {
|
pub fn find_tag_unsigned<T: TryFrom<u64>>(&mut self, tag: Tag) -> TiffResult<Option<T>> {
|
||||||
self.current_directory_ifd().find_tag_unsigned(tag)
|
self.image_ifd().find_tag_unsigned(tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tries to retrieve a vector of all a tag's values and convert them to the desired unsigned
|
||||||
|
/// type.
|
||||||
|
pub fn find_tag_unsigned_vec<T: TryFrom<u64>>(
|
||||||
|
&mut self,
|
||||||
|
tag: Tag,
|
||||||
|
) -> TiffResult<Option<Vec<T>>> {
|
||||||
|
self.image_ifd().find_tag_unsigned_vec(tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tries to retrieve a tag from the current image directory and convert it to the desired
|
/// 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.
|
/// 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> {
|
pub fn get_tag_unsigned<T: TryFrom<u64>>(&mut self, tag: Tag) -> TiffResult<T> {
|
||||||
self.current_directory_ifd().get_tag_unsigned(tag)
|
self.image_ifd().get_tag_unsigned(tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tries to retrieve a tag from the current image directory.
|
/// Tries to retrieve a tag from the current image directory.
|
||||||
/// Returns an error if the tag is not present
|
/// Returns an error if the tag is not present
|
||||||
pub fn get_tag(&mut self, tag: Tag) -> TiffResult<ifd::Value> {
|
pub fn get_tag(&mut self, tag: Tag) -> TiffResult<ifd::Value> {
|
||||||
self.current_directory_ifd().get_tag(tag)
|
self.image_ifd().get_tag(tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_tag_u32(&mut self, tag: Tag) -> TiffResult<u32> {
|
pub fn get_tag_u32(&mut self, tag: Tag) -> TiffResult<u32> {
|
||||||
@@ -1432,7 +1370,7 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn tag_iter(&mut self) -> impl Iterator<Item = TiffResult<(Tag, ifd::Value)>> + '_ {
|
pub fn tag_iter(&mut self) -> impl Iterator<Item = TiffResult<(Tag, ifd::Value)>> + '_ {
|
||||||
self.current_directory_ifd().tag_iter()
|
self.image_ifd().tag_iter()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+100
-1
@@ -1,5 +1,4 @@
|
|||||||
//! All IO functionality needed for TIFF decoding
|
//! All IO functionality needed for TIFF decoding
|
||||||
|
|
||||||
use std::io::{self, BufRead, BufReader, Read, Seek, Take};
|
use std::io::{self, BufRead, BufReader, Read, Seek, Take};
|
||||||
|
|
||||||
/// Byte order of the TIFF file.
|
/// Byte order of the TIFF file.
|
||||||
@@ -276,6 +275,106 @@ impl<R: Read> Read for PackBitsReader<R> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "fax")]
|
||||||
|
pub struct Group4Reader<R: Read> {
|
||||||
|
decoder: fax34::decoder::Group4Decoder<io::Bytes<io::BufReader<io::Take<R>>>>,
|
||||||
|
line_buf: io::Cursor<Vec<u8>>,
|
||||||
|
height: u16,
|
||||||
|
width: u16,
|
||||||
|
y: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "fax")]
|
||||||
|
impl<R: Read> Group4Reader<R> {
|
||||||
|
pub fn new(
|
||||||
|
dimensions: (u32, u32),
|
||||||
|
reader: R,
|
||||||
|
compressed_length: u64,
|
||||||
|
) -> crate::TiffResult<Self> {
|
||||||
|
let width = u16::try_from(dimensions.0)?;
|
||||||
|
let height = u16::try_from(dimensions.1)?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
decoder: fax34::decoder::Group4Decoder::new(
|
||||||
|
io::BufReader::new(reader.take(compressed_length)).bytes(),
|
||||||
|
width,
|
||||||
|
)?,
|
||||||
|
line_buf: io::Cursor::new(Vec::with_capacity(width.into())),
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
y: 0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "fax")]
|
||||||
|
impl<R: Read> Read for Group4Reader<R> {
|
||||||
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||||
|
// Either we have not read any line or we are at the end of a line.
|
||||||
|
if self.line_buf.position() as usize == self.line_buf.get_ref().len()
|
||||||
|
&& self.y < self.height
|
||||||
|
{
|
||||||
|
let next = self.decoder.advance().map_err(std::io::Error::other)?;
|
||||||
|
|
||||||
|
match next {
|
||||||
|
fax34::decoder::DecodeStatus::End => (),
|
||||||
|
fax34::decoder::DecodeStatus::Incomplete => {
|
||||||
|
self.y += 1;
|
||||||
|
|
||||||
|
// We known `transitions` yields exactly `self.width` items (per doc).
|
||||||
|
// FIXME: performance. We do not need an individual pixel iterator, filling
|
||||||
|
// memory especially for long runs can be much quicker. The `transitions` are
|
||||||
|
// the positions at which each run-length ends, i.e. the prefix sum of run
|
||||||
|
// lengths. Runs in fax4 start with white.
|
||||||
|
let transitions = fax34::decoder::pels(self.decoder.transition(), self.width);
|
||||||
|
|
||||||
|
let buffer = self.line_buf.get_mut();
|
||||||
|
buffer.resize(usize::from(self.width).div_ceil(8), 0u8);
|
||||||
|
|
||||||
|
let target = &mut buffer[..];
|
||||||
|
|
||||||
|
// Note: it may seem strange to treat black as 0b1 and white as 0b0 despite all
|
||||||
|
// our streams by default decoding as-if PhotometricInterpretation::BlackIsMin.
|
||||||
|
// This is however consistent with libtiff. It seems that fax4's "White"
|
||||||
|
// differs from what libtiff thinks of as "White". For content, a line of data
|
||||||
|
// is in runlength encoding of white-black-white-black always starting with
|
||||||
|
// white. In libtiff, the loop always does both colors in one go and the
|
||||||
|
// structure is:
|
||||||
|
//
|
||||||
|
// ```
|
||||||
|
// for (; runs < erun; runs += 2)
|
||||||
|
// // white run
|
||||||
|
// do { *lp++ = 0L; } while (…)
|
||||||
|
// // black run
|
||||||
|
// do { *lp++ = -1L; } while (…)
|
||||||
|
// ```
|
||||||
|
//
|
||||||
|
// So indeed the Fax4::White run is implemented by filling with zeros.
|
||||||
|
let mut bits = transitions.map(|c| match c {
|
||||||
|
fax34::Color::Black => true,
|
||||||
|
fax34::Color::White => false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Assemble bits in MSB as per our library representation for buffer.
|
||||||
|
for byte in target {
|
||||||
|
let mut val = 0;
|
||||||
|
|
||||||
|
for (idx, bit) in bits.by_ref().take(8).enumerate() {
|
||||||
|
val |= u8::from(bit) << (7 - idx % 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
*byte = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.line_buf.set_position(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.line_buf.read(buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
+1
-1
@@ -29,7 +29,7 @@ pub struct Directory {
|
|||||||
impl Directory {
|
impl Directory {
|
||||||
/// Create a directory in an initial state without entries. Note that an empty directory can
|
/// 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.
|
/// not be encoded in a file, it must contain at least one entry.
|
||||||
pub const fn empty() -> Self {
|
pub fn empty() -> Self {
|
||||||
Directory {
|
Directory {
|
||||||
entries: BTreeMap::new(),
|
entries: BTreeMap::new(),
|
||||||
next_ifd: None,
|
next_ifd: None,
|
||||||
|
|||||||
+1
-1
@@ -198,7 +198,7 @@ impl<W: Write + Seek, K: TiffKind> TiffEncoder<W, K> {
|
|||||||
&mut self,
|
&mut self,
|
||||||
width: u32,
|
width: u32,
|
||||||
height: u32,
|
height: u32,
|
||||||
) -> TiffResult<ImageEncoder<W, C, K>> {
|
) -> TiffResult<ImageEncoder<'_, W, C, K>> {
|
||||||
let encoder = Self::chain_directory(&mut self.writer, &mut self.last_ifd_chain)?;
|
let encoder = Self::chain_directory(&mut self.writer, &mut self.last_ifd_chain)?;
|
||||||
ImageEncoder::new(encoder, width, height, self.compression, self.predictor)
|
ImageEncoder::new(encoder, width, height, self.compression, self.predictor)
|
||||||
}
|
}
|
||||||
|
|||||||
+28
-28
@@ -15,7 +15,7 @@ pub trait TiffValue {
|
|||||||
|
|
||||||
/// Access this value as an contiguous sequence of bytes.
|
/// Access this value as an contiguous sequence of bytes.
|
||||||
/// If their is no trivial representation, allocate it on the heap.
|
/// If their is no trivial representation, allocate it on the heap.
|
||||||
fn data(&self) -> Cow<[u8]>;
|
fn data(&self) -> Cow<'_, [u8]>;
|
||||||
|
|
||||||
/// Write this value to a TiffWriter.
|
/// Write this value to a TiffWriter.
|
||||||
/// While the default implementation will work in all cases, it may require unnecessary allocations.
|
/// While the default implementation will work in all cases, it may require unnecessary allocations.
|
||||||
@@ -34,7 +34,7 @@ impl TiffValue for [u8] {
|
|||||||
self.len()
|
self.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(self)
|
Cow::Borrowed(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -47,7 +47,7 @@ impl TiffValue for [i8] {
|
|||||||
self.len()
|
self.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(bytecast::i8_as_ne_bytes(self))
|
Cow::Borrowed(bytecast::i8_as_ne_bytes(self))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -60,7 +60,7 @@ impl TiffValue for [u16] {
|
|||||||
self.len()
|
self.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(bytecast::u16_as_ne_bytes(self))
|
Cow::Borrowed(bytecast::u16_as_ne_bytes(self))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -73,7 +73,7 @@ impl TiffValue for [i16] {
|
|||||||
self.len()
|
self.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(bytecast::i16_as_ne_bytes(self))
|
Cow::Borrowed(bytecast::i16_as_ne_bytes(self))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -86,7 +86,7 @@ impl TiffValue for [u32] {
|
|||||||
self.len()
|
self.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(bytecast::u32_as_ne_bytes(self))
|
Cow::Borrowed(bytecast::u32_as_ne_bytes(self))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -99,7 +99,7 @@ impl TiffValue for [i32] {
|
|||||||
self.len()
|
self.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(bytecast::i32_as_ne_bytes(self))
|
Cow::Borrowed(bytecast::i32_as_ne_bytes(self))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -112,7 +112,7 @@ impl TiffValue for [u64] {
|
|||||||
self.len()
|
self.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(bytecast::u64_as_ne_bytes(self))
|
Cow::Borrowed(bytecast::u64_as_ne_bytes(self))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -125,7 +125,7 @@ impl TiffValue for [i64] {
|
|||||||
self.len()
|
self.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(bytecast::i64_as_ne_bytes(self))
|
Cow::Borrowed(bytecast::i64_as_ne_bytes(self))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -138,7 +138,7 @@ impl TiffValue for [f32] {
|
|||||||
self.len()
|
self.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
// We write using native endian so this should be safe
|
// We write using native endian so this should be safe
|
||||||
Cow::Borrowed(bytecast::f32_as_ne_bytes(self))
|
Cow::Borrowed(bytecast::f32_as_ne_bytes(self))
|
||||||
}
|
}
|
||||||
@@ -152,7 +152,7 @@ impl TiffValue for [f64] {
|
|||||||
self.len()
|
self.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
// We write using native endian so this should be safe
|
// We write using native endian so this should be safe
|
||||||
Cow::Borrowed(bytecast::f64_as_ne_bytes(self))
|
Cow::Borrowed(bytecast::f64_as_ne_bytes(self))
|
||||||
}
|
}
|
||||||
@@ -171,7 +171,7 @@ impl TiffValue for u8 {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(from_ref(self))
|
Cow::Borrowed(from_ref(self))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -189,7 +189,7 @@ impl TiffValue for i8 {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(bytecast::i8_as_ne_bytes(from_ref(self)))
|
Cow::Borrowed(bytecast::i8_as_ne_bytes(from_ref(self)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -207,7 +207,7 @@ impl TiffValue for u16 {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(bytecast::u16_as_ne_bytes(from_ref(self)))
|
Cow::Borrowed(bytecast::u16_as_ne_bytes(from_ref(self)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -225,7 +225,7 @@ impl TiffValue for i16 {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(bytecast::i16_as_ne_bytes(from_ref(self)))
|
Cow::Borrowed(bytecast::i16_as_ne_bytes(from_ref(self)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -243,7 +243,7 @@ impl TiffValue for u32 {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(bytecast::u32_as_ne_bytes(from_ref(self)))
|
Cow::Borrowed(bytecast::u32_as_ne_bytes(from_ref(self)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -261,7 +261,7 @@ impl TiffValue for i32 {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(bytecast::i32_as_ne_bytes(from_ref(self)))
|
Cow::Borrowed(bytecast::i32_as_ne_bytes(from_ref(self)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -279,7 +279,7 @@ impl TiffValue for u64 {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(bytecast::u64_as_ne_bytes(from_ref(self)))
|
Cow::Borrowed(bytecast::u64_as_ne_bytes(from_ref(self)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -297,7 +297,7 @@ impl TiffValue for i64 {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(bytecast::i64_as_ne_bytes(from_ref(self)))
|
Cow::Borrowed(bytecast::i64_as_ne_bytes(from_ref(self)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -315,7 +315,7 @@ impl TiffValue for f32 {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(bytecast::f32_as_ne_bytes(from_ref(self)))
|
Cow::Borrowed(bytecast::f32_as_ne_bytes(from_ref(self)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -333,7 +333,7 @@ impl TiffValue for f64 {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(bytecast::f64_as_ne_bytes(from_ref(self)))
|
Cow::Borrowed(bytecast::f64_as_ne_bytes(from_ref(self)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -351,7 +351,7 @@ impl TiffValue for Ifd {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(bytecast::u32_as_ne_bytes(from_ref(&self.0)))
|
Cow::Borrowed(bytecast::u32_as_ne_bytes(from_ref(&self.0)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -369,7 +369,7 @@ impl TiffValue for Ifd8 {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(bytecast::u64_as_ne_bytes(from_ref(&self.0)))
|
Cow::Borrowed(bytecast::u64_as_ne_bytes(from_ref(&self.0)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -388,7 +388,7 @@ impl TiffValue for Rational {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Owned({
|
Cow::Owned({
|
||||||
let first_dword = bytecast::u32_as_ne_bytes(from_ref(&self.n));
|
let first_dword = bytecast::u32_as_ne_bytes(from_ref(&self.n));
|
||||||
let second_dword = bytecast::u32_as_ne_bytes(from_ref(&self.d));
|
let second_dword = bytecast::u32_as_ne_bytes(from_ref(&self.d));
|
||||||
@@ -411,7 +411,7 @@ impl TiffValue for SRational {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Owned({
|
Cow::Owned({
|
||||||
let first_dword = bytecast::i32_as_ne_bytes(from_ref(&self.n));
|
let first_dword = bytecast::i32_as_ne_bytes(from_ref(&self.n));
|
||||||
let second_dword = bytecast::i32_as_ne_bytes(from_ref(&self.d));
|
let second_dword = bytecast::i32_as_ne_bytes(from_ref(&self.d));
|
||||||
@@ -438,7 +438,7 @@ impl TiffValue for str {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Owned({
|
Cow::Owned({
|
||||||
if self.is_ascii() && !self.bytes().any(|b| b == 0) {
|
if self.is_ascii() && !self.bytes().any(|b| b == 0) {
|
||||||
let bytes: &[u8] = self.as_bytes();
|
let bytes: &[u8] = self.as_bytes();
|
||||||
@@ -462,7 +462,7 @@ impl<T: TiffValue + ?Sized> TiffValue for &'_ T {
|
|||||||
(*self).write(writer)
|
(*self).write(writer)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
T::data(self)
|
T::data(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -484,7 +484,7 @@ macro_rules! impl_tiff_value_for_contiguous_sequence {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
let mut buf: Vec<u8> = Vec::with_capacity(Self::BYTE_LEN as usize * self.len());
|
let mut buf: Vec<u8> = Vec::with_capacity(Self::BYTE_LEN as usize * self.len());
|
||||||
for x in self {
|
for x in self {
|
||||||
buf.extend_from_slice(&x.data());
|
buf.extend_from_slice(&x.data());
|
||||||
|
|||||||
@@ -672,3 +672,15 @@ fn bytes_gray_f32() {
|
|||||||
byte_order_u32,
|
byte_order_u32,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "fax")]
|
||||||
|
fn test_fax4() {
|
||||||
|
test_image_sum_u8("fax4.tiff", ColorType::Gray(1), 7802706);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "fax")]
|
||||||
|
fn test_fax4_white_is_min() {
|
||||||
|
test_image_sum_u8("imagemagick_group4.tiff", ColorType::Gray(1), 3742820);
|
||||||
|
}
|
||||||
Binary file not shown.
Binary file not shown.
+1
-129
@@ -1,7 +1,4 @@
|
|||||||
use tiff::{
|
use tiff::decoder::Decoder;
|
||||||
decoder::Decoder,
|
|
||||||
tags::{IfdPointer, Tag},
|
|
||||||
};
|
|
||||||
|
|
||||||
// This file has the following IFD structure:
|
// This file has the following IFD structure:
|
||||||
//
|
//
|
||||||
@@ -42,128 +39,3 @@ fn decode_seek_chain() {
|
|||||||
assert_eq!(Some(offset2), decoder.ifd_pointer());
|
assert_eq!(Some(offset2), decoder.ifd_pointer());
|
||||||
assert!(!decoder.more_images());
|
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