Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ab0ce47230 | ||
|
|
f993cfb940 | ||
|
|
8c4bd58493 | ||
|
|
c073841a9d |
No files matched your search
-41
@@ -1,44 +1,3 @@
|
||||
# Version 0.10.4 (unreleased)
|
||||
|
||||
- `Directory` now implements `FromIterator<(Tag, Value)>`.
|
||||
|
||||
# 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
|
||||
|
||||
New features:
|
||||
|
||||
+4
-7
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tiff"
|
||||
version = "0.10.3"
|
||||
version = "0.10.0"
|
||||
edition = "2021"
|
||||
resolver = "2"
|
||||
|
||||
@@ -20,24 +20,21 @@ exclude = ["tests/images/*", "tests/fuzz_images/*"]
|
||||
half = { version = "2.4.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 }
|
||||
weezl = { version = "0.1.10", optional = true }
|
||||
zstd = { version = "0.13", optional = true }
|
||||
zune-jpeg = { version = "0.5.1", optional = true }
|
||||
zune-jpeg = { version = "0.4.17", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = "0.3.1"
|
||||
# We may already depend on this via flate2
|
||||
crc32fast = "1.5"
|
||||
crc32fast = "1"
|
||||
|
||||
[features]
|
||||
default = ["deflate", "fax", "jpeg", "lzw"]
|
||||
default = ["deflate", "jpeg", "lzw"]
|
||||
|
||||
# Compression algorithms
|
||||
deflate = ["dep:flate2"]
|
||||
fax = ["dep:fax34"]
|
||||
jpeg = ["dep:zune-jpeg"]
|
||||
lzw = ["dep:weezl"]
|
||||
zstd = ["dep:zstd"]
|
||||
|
||||
@@ -36,7 +36,6 @@ This table lists photometric interpretations and sample formats which are suppor
|
||||
| LZW | ✓ | ✓ |
|
||||
| Deflate | ✓ | ✓ |
|
||||
| PackBits | ✓ | ✓ |
|
||||
| Fax4 | ✓ | not yet |
|
||||
| JPEG | ✓ | not yet |
|
||||
| ZSTD | ✓ | not yet |
|
||||
|
||||
|
||||
+120
-226
@@ -332,12 +332,6 @@ impl Value {
|
||||
}
|
||||
}
|
||||
|
||||
/// A combination of type, count, and offset.
|
||||
///
|
||||
/// In a TIFF the data offset portion of an entry is used for inline data in case the length of the
|
||||
/// encoded value does not exceed the size of the offset field. Since the size of the offset field
|
||||
/// depends on the file kind (4 bytes for standard TIFF, 8 bytes for BigTIFF) the interpretation of
|
||||
/// this struct is only complete in combination with file metadata.
|
||||
#[derive(Clone)]
|
||||
pub struct Entry {
|
||||
type_: Type,
|
||||
@@ -355,14 +349,12 @@ impl ::std::fmt::Debug for Entry {
|
||||
}
|
||||
|
||||
impl Entry {
|
||||
/// Create a new entry fit to be added to a standard TIFF IFD.
|
||||
pub fn new(type_: Type, count: u32, offset: [u8; 4]) -> Entry {
|
||||
let mut entry_off = [0u8; 8];
|
||||
entry_off[..4].copy_from_slice(&offset);
|
||||
Entry::new_u64(type_, count.into(), entry_off)
|
||||
}
|
||||
|
||||
/// Create a new entry with data for a Big TIFF IFD.
|
||||
pub fn new_u64(type_: Type, count: u64, offset: [u8; 8]) -> Entry {
|
||||
Entry {
|
||||
type_,
|
||||
@@ -384,10 +376,7 @@ impl Entry {
|
||||
}
|
||||
|
||||
/// Returns a mem_reader for the offset/value field
|
||||
pub(crate) fn offset_field_reader(
|
||||
&self,
|
||||
byte_order: ByteOrder,
|
||||
) -> EndianReader<io::Cursor<Vec<u8>>> {
|
||||
fn r(&self, byte_order: ByteOrder) -> EndianReader<io::Cursor<Vec<u8>>> {
|
||||
EndianReader::new(io::Cursor::new(self.offset.to_vec()), byte_order)
|
||||
}
|
||||
|
||||
@@ -403,25 +392,43 @@ impl Entry {
|
||||
}
|
||||
|
||||
let bo = reader.byte_order;
|
||||
let value_bytes = self.type_.value_bytes(self.count)?;
|
||||
|
||||
let tag_size = match self.type_ {
|
||||
Type::BYTE | Type::SBYTE | Type::ASCII | Type::UNDEFINED => 1,
|
||||
Type::SHORT | Type::SSHORT => 2,
|
||||
Type::LONG | Type::SLONG | Type::FLOAT | Type::IFD => 4,
|
||||
Type::LONG8
|
||||
| Type::SLONG8
|
||||
| Type::DOUBLE
|
||||
| Type::RATIONAL
|
||||
| Type::SRATIONAL
|
||||
| Type::IFD8 => 8,
|
||||
};
|
||||
|
||||
let value_bytes = match self.count.checked_mul(tag_size) {
|
||||
Some(n) => n,
|
||||
None => {
|
||||
return Err(TiffError::LimitsExceeded);
|
||||
}
|
||||
};
|
||||
|
||||
// Case 2: there is one value.
|
||||
if self.count == 1 {
|
||||
// 2a: the value is 5-8 bytes and we're in BigTiff mode.
|
||||
if bigtiff && value_bytes > 4 && value_bytes <= 8 {
|
||||
return Ok(match self.type_ {
|
||||
Type::LONG8 => UnsignedBig(self.offset_field_reader(bo).read_u64()?),
|
||||
Type::SLONG8 => SignedBig(self.offset_field_reader(bo).read_i64()?),
|
||||
Type::DOUBLE => Double(self.offset_field_reader(bo).read_f64()?),
|
||||
Type::LONG8 => UnsignedBig(self.r(bo).read_u64()?),
|
||||
Type::SLONG8 => SignedBig(self.r(bo).read_i64()?),
|
||||
Type::DOUBLE => Double(self.r(bo).read_f64()?),
|
||||
Type::RATIONAL => {
|
||||
let mut r = self.offset_field_reader(bo);
|
||||
let mut r = self.r(bo);
|
||||
Rational(r.read_u32()?, r.read_u32()?)
|
||||
}
|
||||
Type::SRATIONAL => {
|
||||
let mut r = self.offset_field_reader(bo);
|
||||
let mut r = self.r(bo);
|
||||
SRational(r.read_i32()?, r.read_i32()?)
|
||||
}
|
||||
Type::IFD8 => IfdBig(self.offset_field_reader(bo).read_u64()?),
|
||||
Type::IFD8 => IfdBig(self.r(bo).read_u64()?),
|
||||
Type::BYTE
|
||||
| Type::SBYTE
|
||||
| Type::ASCII
|
||||
@@ -440,11 +447,11 @@ impl Entry {
|
||||
Type::BYTE => Byte(self.offset[0]),
|
||||
Type::SBYTE => SignedByte(self.offset[0] as i8),
|
||||
Type::UNDEFINED => Byte(self.offset[0]),
|
||||
Type::SHORT => Short(self.offset_field_reader(bo).read_u16()?),
|
||||
Type::SSHORT => SignedShort(self.offset_field_reader(bo).read_i16()?),
|
||||
Type::LONG => Unsigned(self.offset_field_reader(bo).read_u32()?),
|
||||
Type::SLONG => Signed(self.offset_field_reader(bo).read_i32()?),
|
||||
Type::FLOAT => Float(self.offset_field_reader(bo).read_f32()?),
|
||||
Type::SHORT => Short(self.r(bo).read_u16()?),
|
||||
Type::SSHORT => SignedShort(self.r(bo).read_i16()?),
|
||||
Type::LONG => Unsigned(self.r(bo).read_u32()?),
|
||||
Type::SLONG => Signed(self.r(bo).read_i32()?),
|
||||
Type::FLOAT => Float(self.r(bo).read_f32()?),
|
||||
Type::ASCII => {
|
||||
if self.offset[0] == 0 {
|
||||
Ascii("".to_string())
|
||||
@@ -453,28 +460,28 @@ impl Entry {
|
||||
}
|
||||
}
|
||||
Type::LONG8 => {
|
||||
reader.goto_offset(self.offset_field_reader(bo).read_u32()?.into())?;
|
||||
reader.goto_offset(self.r(bo).read_u32()?.into())?;
|
||||
UnsignedBig(reader.read_u64()?)
|
||||
}
|
||||
Type::SLONG8 => {
|
||||
reader.goto_offset(self.offset_field_reader(bo).read_u32()?.into())?;
|
||||
reader.goto_offset(self.r(bo).read_u32()?.into())?;
|
||||
SignedBig(reader.read_i64()?)
|
||||
}
|
||||
Type::DOUBLE => {
|
||||
reader.goto_offset(self.offset_field_reader(bo).read_u32()?.into())?;
|
||||
reader.goto_offset(self.r(bo).read_u32()?.into())?;
|
||||
Double(reader.read_f64()?)
|
||||
}
|
||||
Type::RATIONAL => {
|
||||
reader.goto_offset(self.offset_field_reader(bo).read_u32()?.into())?;
|
||||
reader.goto_offset(self.r(bo).read_u32()?.into())?;
|
||||
Rational(reader.read_u32()?, reader.read_u32()?)
|
||||
}
|
||||
Type::SRATIONAL => {
|
||||
reader.goto_offset(self.offset_field_reader(bo).read_u32()?.into())?;
|
||||
reader.goto_offset(self.r(bo).read_u32()?.into())?;
|
||||
SRational(reader.read_i32()?, reader.read_i32()?)
|
||||
}
|
||||
Type::IFD => Ifd(self.offset_field_reader(bo).read_u32()?),
|
||||
Type::IFD => Ifd(self.r(bo).read_u32()?),
|
||||
Type::IFD8 => {
|
||||
reader.goto_offset(self.offset_field_reader(bo).read_u32()?.into())?;
|
||||
reader.goto_offset(self.r(bo).read_u32()?.into())?;
|
||||
IfdBig(reader.read_u64()?)
|
||||
}
|
||||
});
|
||||
@@ -505,7 +512,7 @@ impl Entry {
|
||||
));
|
||||
}
|
||||
Type::SHORT => {
|
||||
let mut r = self.offset_field_reader(bo);
|
||||
let mut r = self.r(bo);
|
||||
let mut v = Vec::new();
|
||||
for _ in 0..self.count {
|
||||
v.push(Short(r.read_u16()?));
|
||||
@@ -513,7 +520,7 @@ impl Entry {
|
||||
return Ok(List(v));
|
||||
}
|
||||
Type::SSHORT => {
|
||||
let mut r = self.offset_field_reader(bo);
|
||||
let mut r = self.r(bo);
|
||||
let mut v = Vec::new();
|
||||
for _ in 0..self.count {
|
||||
v.push(SignedShort(r.read_i16()?));
|
||||
@@ -521,7 +528,7 @@ impl Entry {
|
||||
return Ok(List(v));
|
||||
}
|
||||
Type::LONG => {
|
||||
let mut r = self.offset_field_reader(bo);
|
||||
let mut r = self.r(bo);
|
||||
let mut v = Vec::new();
|
||||
for _ in 0..self.count {
|
||||
v.push(Unsigned(r.read_u32()?));
|
||||
@@ -529,7 +536,7 @@ impl Entry {
|
||||
return Ok(List(v));
|
||||
}
|
||||
Type::SLONG => {
|
||||
let mut r = self.offset_field_reader(bo);
|
||||
let mut r = self.r(bo);
|
||||
let mut v = Vec::new();
|
||||
for _ in 0..self.count {
|
||||
v.push(Signed(r.read_i32()?));
|
||||
@@ -537,7 +544,7 @@ impl Entry {
|
||||
return Ok(List(v));
|
||||
}
|
||||
Type::FLOAT => {
|
||||
let mut r = self.offset_field_reader(bo);
|
||||
let mut r = self.r(bo);
|
||||
let mut v = Vec::new();
|
||||
for _ in 0..self.count {
|
||||
v.push(Float(r.read_f32()?));
|
||||
@@ -545,7 +552,7 @@ impl Entry {
|
||||
return Ok(List(v));
|
||||
}
|
||||
Type::IFD => {
|
||||
let mut r = self.offset_field_reader(bo);
|
||||
let mut r = self.r(bo);
|
||||
let mut v = Vec::new();
|
||||
for _ in 0..self.count {
|
||||
v.push(Ifd(r.read_u32()?));
|
||||
@@ -564,149 +571,74 @@ impl Entry {
|
||||
}
|
||||
|
||||
// Case 4: there is more than one value, and it doesn't fit in the offset field.
|
||||
let mut v;
|
||||
self.set_reader_offset(bo, bigtiff, reader)?;
|
||||
|
||||
match self.type_ {
|
||||
Type::BYTE | Type::UNDEFINED => {
|
||||
v = Self::vec_with_capacity(self.count, limits)?;
|
||||
self.decode_values(self.count, self.type_, reader, |bytes| {
|
||||
v.extend(bytes.iter().copied().map(Byte))
|
||||
})
|
||||
}
|
||||
Type::SBYTE => {
|
||||
v = Self::vec_with_capacity(self.count, limits)?;
|
||||
self.decode_values(self.count, self.type_, reader, |bytes| {
|
||||
v.extend(bytes.iter().copied().map(|v| SignedByte(v as i8)))
|
||||
})
|
||||
}
|
||||
Type::SHORT => {
|
||||
v = Self::vec_with_capacity(self.count, limits)?;
|
||||
self.decode_values(self.count, self.type_, reader, |bytes| {
|
||||
v.extend(
|
||||
bytes
|
||||
.chunks_exact(2)
|
||||
.map(|ch| Short(u16::from_ne_bytes(ch.try_into().unwrap()))),
|
||||
)
|
||||
})
|
||||
}
|
||||
Type::SSHORT => {
|
||||
v = Self::vec_with_capacity(self.count, limits)?;
|
||||
self.decode_values(self.count, self.type_, reader, |bytes| {
|
||||
v.extend(
|
||||
bytes
|
||||
.chunks_exact(2)
|
||||
.map(|ch| SignedShort(i16::from_ne_bytes(ch.try_into().unwrap()))),
|
||||
)
|
||||
})
|
||||
}
|
||||
Type::LONG => {
|
||||
v = Self::vec_with_capacity(self.count, limits)?;
|
||||
self.decode_values(self.count, self.type_, reader, |bytes| {
|
||||
v.extend(
|
||||
bytes
|
||||
.chunks_exact(4)
|
||||
.map(|ch| Unsigned(u32::from_ne_bytes(ch.try_into().unwrap()))),
|
||||
)
|
||||
})
|
||||
}
|
||||
Type::SLONG => {
|
||||
v = Self::vec_with_capacity(self.count, limits)?;
|
||||
self.decode_values(self.count, self.type_, reader, |bytes| {
|
||||
v.extend(
|
||||
bytes
|
||||
.chunks_exact(4)
|
||||
.map(|ch| Signed(i32::from_ne_bytes(ch.try_into().unwrap()))),
|
||||
)
|
||||
})
|
||||
}
|
||||
Type::FLOAT => {
|
||||
v = Self::vec_with_capacity(self.count, limits)?;
|
||||
self.decode_values(self.count, self.type_, reader, |bytes| {
|
||||
v.extend(
|
||||
bytes
|
||||
.chunks_exact(4)
|
||||
.map(|ch| Float(f32::from_ne_bytes(ch.try_into().unwrap()))),
|
||||
)
|
||||
})
|
||||
}
|
||||
Type::DOUBLE => {
|
||||
v = Self::vec_with_capacity(self.count, limits)?;
|
||||
self.decode_values(self.count, self.type_, reader, |bytes| {
|
||||
v.extend(
|
||||
bytes
|
||||
.chunks_exact(8)
|
||||
.map(|ch| Double(f64::from_ne_bytes(ch.try_into().unwrap()))),
|
||||
)
|
||||
})
|
||||
}
|
||||
// TODO check if this could give wrong results
|
||||
// at a different endianess of file/computer.
|
||||
Type::BYTE => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
||||
let mut buf = [0; 1];
|
||||
reader.inner().read_exact(&mut buf)?;
|
||||
Ok(UnsignedBig(u64::from(buf[0])))
|
||||
}),
|
||||
Type::SBYTE => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
||||
Ok(SignedBig(i64::from(reader.read_i8()?)))
|
||||
}),
|
||||
Type::SHORT => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
||||
Ok(Short(reader.read_u16()?))
|
||||
}),
|
||||
Type::SSHORT => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
||||
Ok(SignedShort(reader.read_i16()?))
|
||||
}),
|
||||
Type::LONG => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
||||
Ok(Unsigned(reader.read_u32()?))
|
||||
}),
|
||||
Type::SLONG => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
||||
Ok(Signed(reader.read_i32()?))
|
||||
}),
|
||||
Type::FLOAT => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
||||
Ok(Float(reader.read_f32()?))
|
||||
}),
|
||||
Type::DOUBLE => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
||||
Ok(Double(reader.read_f64()?))
|
||||
}),
|
||||
Type::RATIONAL => {
|
||||
v = Self::vec_with_capacity(self.count, limits)?;
|
||||
self.decode_values(self.count, self.type_, reader, |bytes| {
|
||||
v.extend(bytes.chunks_exact(8).map(|ch| {
|
||||
Rational(
|
||||
u32::from_ne_bytes(ch[..4].try_into().unwrap()),
|
||||
u32::from_ne_bytes(ch[4..].try_into().unwrap()),
|
||||
)
|
||||
}))
|
||||
self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
||||
Ok(Rational(reader.read_u32()?, reader.read_u32()?))
|
||||
})
|
||||
}
|
||||
Type::SRATIONAL => {
|
||||
v = Self::vec_with_capacity(self.count, limits)?;
|
||||
self.decode_values(self.count, self.type_, reader, |bytes| {
|
||||
v.extend(bytes.chunks_exact(8).map(|ch| {
|
||||
SRational(
|
||||
i32::from_ne_bytes(ch[..4].try_into().unwrap()),
|
||||
i32::from_ne_bytes(ch[4..].try_into().unwrap()),
|
||||
)
|
||||
}))
|
||||
self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
||||
Ok(SRational(reader.read_i32()?, reader.read_i32()?))
|
||||
})
|
||||
}
|
||||
Type::LONG8 => {
|
||||
v = Self::vec_with_capacity(self.count, limits)?;
|
||||
self.decode_values(self.count, self.type_, reader, |bytes| {
|
||||
v.extend(
|
||||
bytes
|
||||
.chunks_exact(8)
|
||||
.map(|ch| UnsignedBig(u64::from_ne_bytes(ch.try_into().unwrap()))),
|
||||
)
|
||||
})
|
||||
}
|
||||
Type::SLONG8 => {
|
||||
v = Self::vec_with_capacity(self.count, limits)?;
|
||||
self.decode_values(self.count, self.type_, reader, |bytes| {
|
||||
v.extend(
|
||||
bytes
|
||||
.chunks_exact(8)
|
||||
.map(|ch| SignedBig(i64::from_ne_bytes(ch.try_into().unwrap()))),
|
||||
)
|
||||
})
|
||||
}
|
||||
Type::IFD => {
|
||||
v = Self::vec_with_capacity(self.count, limits)?;
|
||||
self.decode_values(self.count, self.type_, reader, |bytes| {
|
||||
v.extend(
|
||||
bytes
|
||||
.chunks_exact(4)
|
||||
.map(|ch| Ifd(u32::from_ne_bytes(ch.try_into().unwrap()))),
|
||||
)
|
||||
})
|
||||
}
|
||||
Type::IFD8 => {
|
||||
v = Self::vec_with_capacity(self.count, limits)?;
|
||||
self.decode_values(self.count, self.type_, reader, |bytes| {
|
||||
v.extend(
|
||||
bytes
|
||||
.chunks_exact(8)
|
||||
.map(|ch| IfdBig(u64::from_ne_bytes(ch.try_into().unwrap()))),
|
||||
)
|
||||
Type::LONG8 => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
||||
Ok(UnsignedBig(reader.read_u64()?))
|
||||
}),
|
||||
Type::SLONG8 => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
||||
Ok(SignedBig(reader.read_i64()?))
|
||||
}),
|
||||
Type::IFD => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
||||
Ok(Ifd(reader.read_u32()?))
|
||||
}),
|
||||
Type::IFD8 => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
||||
Ok(IfdBig(reader.read_u64()?))
|
||||
}),
|
||||
Type::UNDEFINED => {
|
||||
self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
||||
let mut buf = [0; 1];
|
||||
reader.inner().read_exact(&mut buf)?;
|
||||
Ok(Byte(buf[0]))
|
||||
})
|
||||
}
|
||||
Type::ASCII => {
|
||||
let n = usize::try_from(self.count)?;
|
||||
|
||||
if n > limits.decoding_buffer_size {
|
||||
return Err(dbg!(TiffError::LimitsExceeded));
|
||||
return Err(TiffError::LimitsExceeded);
|
||||
}
|
||||
|
||||
if bigtiff {
|
||||
reader.goto_offset(self.r(bo).read_u64()?)?
|
||||
} else {
|
||||
reader.goto_offset(self.r(bo).read_u32()?.into())?
|
||||
}
|
||||
|
||||
let mut out = vec![0; n];
|
||||
@@ -715,81 +647,43 @@ impl Entry {
|
||||
if let Some(first) = out.iter().position(|&b| b == 0) {
|
||||
out.truncate(first);
|
||||
}
|
||||
|
||||
return Ok(Ascii(String::from_utf8(out)?));
|
||||
Ok(Ascii(String::from_utf8(out)?))
|
||||
}
|
||||
}?;
|
||||
|
||||
Ok(List(v))
|
||||
}
|
||||
}
|
||||
|
||||
fn vec_with_capacity(
|
||||
#[inline]
|
||||
fn decode_offset<R, F>(
|
||||
&self,
|
||||
value_count: u64,
|
||||
bo: ByteOrder,
|
||||
bigtiff: bool,
|
||||
limits: &super::Limits,
|
||||
) -> Result<Vec<Value>, TiffError> {
|
||||
reader: &mut EndianReader<R>,
|
||||
decode_fn: F,
|
||||
) -> TiffResult<Value>
|
||||
where
|
||||
R: Read + Seek,
|
||||
F: Fn(&mut EndianReader<R>) -> TiffResult<Value>,
|
||||
{
|
||||
let value_count = usize::try_from(value_count)?;
|
||||
|
||||
if value_count > limits.decoding_buffer_size / mem::size_of::<Value>() {
|
||||
return Err(TiffError::LimitsExceeded);
|
||||
}
|
||||
|
||||
Ok(Vec::with_capacity(value_count))
|
||||
}
|
||||
let mut v = Vec::with_capacity(value_count);
|
||||
|
||||
fn set_reader_offset<R>(
|
||||
&self,
|
||||
bo: ByteOrder,
|
||||
bigtiff: bool,
|
||||
reader: &mut EndianReader<R>,
|
||||
) -> TiffResult<()>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
let offset = if bigtiff {
|
||||
self.offset_field_reader(bo).read_u64()?
|
||||
self.r(bo).read_u64()?
|
||||
} else {
|
||||
self.offset_field_reader(bo).read_u32()?.into()
|
||||
self.r(bo).read_u32()?.into()
|
||||
};
|
||||
|
||||
reader.goto_offset(offset)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn decode_values<R, F>(
|
||||
&self,
|
||||
value_count: u64,
|
||||
type_: Type,
|
||||
reader: &mut EndianReader<R>,
|
||||
mut collect: F,
|
||||
) -> TiffResult<()>
|
||||
where
|
||||
R: Read + Seek,
|
||||
F: FnMut(&[u8]),
|
||||
{
|
||||
let mut total_bytes = type_.value_bytes(value_count)?;
|
||||
let mut buffer = [0u8; 512];
|
||||
|
||||
let buf_unit = usize::from(type_.byte_len());
|
||||
let mul_of_ty = buffer.len() / buf_unit * buf_unit;
|
||||
|
||||
let cls = type_.endian_bytes();
|
||||
let native = ByteOrder::native();
|
||||
|
||||
while total_bytes > 0 {
|
||||
// `now <= mul_of_ty < 512` so casting is mathematical
|
||||
let now = total_bytes.min(mul_of_ty as u64);
|
||||
total_bytes -= now;
|
||||
|
||||
let buffer = &mut buffer[..now as usize];
|
||||
reader.inner().read_exact(buffer)?;
|
||||
|
||||
reader.byte_order.convert_endian_bytes(cls, buffer, native);
|
||||
collect(buffer);
|
||||
for _ in 0..value_count {
|
||||
v.push(decode_fn(reader)?)
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok(List(v))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+23
-28
@@ -9,7 +9,6 @@ use crate::tags::{
|
||||
use crate::{
|
||||
ColorType, Directory, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, UsageError,
|
||||
};
|
||||
|
||||
use std::io::{self, Cursor, Read, Seek};
|
||||
use std::num::NonZeroUsize;
|
||||
use std::sync::Arc;
|
||||
@@ -87,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()?;
|
||||
@@ -145,15 +156,12 @@ impl Image {
|
||||
.map(SampleFormat::from_u16_exhaustive)
|
||||
.collect();
|
||||
|
||||
let Some(format) = sample_format.first().copied() else {
|
||||
// Reject empty sample formats
|
||||
return Err(TiffFormatError::InvalidTagValueType(Tag::SampleFormat).into());
|
||||
};
|
||||
// TODO: for now, only homogenous formats across samples are supported.
|
||||
if !sample_format.iter().all(|&s| s == format) {
|
||||
if !sample_format.windows(2).all(|s| s[0] == s[1]) {
|
||||
return Err(TiffUnsupportedError::UnsupportedSampleFormat(sample_format).into());
|
||||
}
|
||||
format
|
||||
|
||||
sample_format[0]
|
||||
}
|
||||
None => SampleFormat::Uint,
|
||||
};
|
||||
@@ -292,7 +300,7 @@ impl Image {
|
||||
};
|
||||
|
||||
Ok(Image {
|
||||
ifd: Some(ifd),
|
||||
ifd: None,
|
||||
width,
|
||||
height,
|
||||
bits_per_sample: bits_per_sample[0],
|
||||
@@ -347,9 +355,6 @@ 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 => {
|
||||
match self.samples {
|
||||
1 => Ok(ColorType::Gray(self.bits_per_sample)),
|
||||
@@ -391,9 +396,7 @@ impl Image {
|
||||
reader: R,
|
||||
compression_method: CompressionMethod,
|
||||
compressed_length: u64,
|
||||
// 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),
|
||||
#[allow(unused_variables)] jpeg_tables: Option<&[u8]>,
|
||||
) -> TiffResult<Box<dyn Read + 'r>> {
|
||||
Ok(match compression_method {
|
||||
CompressionMethod::None => Box::new(reader),
|
||||
@@ -445,14 +448,13 @@ impl Image {
|
||||
let mut jpeg_data = Vec::new();
|
||||
jpeg_reader.read_to_end(&mut jpeg_data)?;
|
||||
|
||||
let mut decoder =
|
||||
zune_jpeg::JpegDecoder::new(zune_core::bytestream::ZCursor::new(jpeg_data));
|
||||
let mut decoder = zune_jpeg::JpegDecoder::new(jpeg_data);
|
||||
let mut options: zune_core::options::DecoderOptions = Default::default();
|
||||
|
||||
// Disable color conversion by setting the output colorspace to the input
|
||||
// colorspace.
|
||||
decoder.decode_headers()?;
|
||||
if let Some(colorspace) = decoder.input_colorspace() {
|
||||
if let Some(colorspace) = decoder.get_input_colorspace() {
|
||||
options = options.jpeg_set_out_colorspace(colorspace);
|
||||
}
|
||||
|
||||
@@ -462,12 +464,6 @@ impl Image {
|
||||
|
||||
Box::new(Cursor::new(data))
|
||||
}
|
||||
#[cfg(feature = "fax")]
|
||||
CompressionMethod::Fax4 => Box::new(super::stream::Group4Reader::new(
|
||||
dimensions,
|
||||
reader,
|
||||
compressed_length,
|
||||
)?),
|
||||
method => {
|
||||
return Err(TiffError::UnsupportedError(
|
||||
TiffUnsupportedError::UnsupportedCompressionMethod(method),
|
||||
@@ -674,7 +670,6 @@ impl Image {
|
||||
compression_method,
|
||||
*compressed_bytes,
|
||||
self.jpeg_tables.as_deref().map(|a| &**a),
|
||||
chunk_dims,
|
||||
)?;
|
||||
|
||||
if output_row_stride == chunk_row_bytes {
|
||||
@@ -691,7 +686,7 @@ impl Image {
|
||||
);
|
||||
}
|
||||
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 {
|
||||
// The floating point predictor shuffles the padding bytes into the encoded output, so
|
||||
@@ -708,7 +703,7 @@ impl Image {
|
||||
_ => unreachable!(),
|
||||
}
|
||||
if photometric_interpretation == PhotometricInterpretation::WhiteIsZero {
|
||||
super::invert_colors(row, color_type, self.sample_format)?;
|
||||
super::invert_colors(row, color_type, self.sample_format);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -730,7 +725,7 @@ impl Image {
|
||||
predictor,
|
||||
);
|
||||
if photometric_interpretation == PhotometricInterpretation::WhiteIsZero {
|
||||
super::invert_colors(row, color_type, self.sample_format)?;
|
||||
super::invert_colors(row, color_type, self.sample_format);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+164
-146
@@ -23,7 +23,7 @@ mod stream;
|
||||
mod tag_reader;
|
||||
|
||||
/// Result of a decoding process
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum DecodingResult {
|
||||
/// A vector of unsigned bytes
|
||||
U8(Vec<u8>),
|
||||
@@ -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 {
|
||||
DecodingResult::U8(ref mut buf) => DecodingBuffer::U8(&mut buf[start..]),
|
||||
DecodingResult::U16(ref mut buf) => DecodingBuffer::U16(&mut buf[start..]),
|
||||
@@ -349,6 +349,10 @@ where
|
||||
ifd_offsets: Vec<IfdPointer>,
|
||||
/// Map from the ifd into the `ifd_offsets` ordered list.
|
||||
seen_ifds: cycles::IfdCycles,
|
||||
/// The directory, if we have not yet read it as an image.
|
||||
/// This is prioritized _over_ the image. Hence it must _not_ be set if we are currently
|
||||
/// reading a true image IFD that is instead stored in the `image` attribute.
|
||||
non_image_ifd: Option<Directory>,
|
||||
image: Image,
|
||||
}
|
||||
|
||||
@@ -368,82 +372,27 @@ pub struct IfdDecoder<'lt> {
|
||||
}
|
||||
|
||||
fn rev_hpredict_nsamp(buf: &mut [u8], bit_depth: u8, samples: usize) {
|
||||
fn one_byte_predict<const N: usize>(buf: &mut [u8]) {
|
||||
for i in N..buf.len() {
|
||||
buf[i] = buf[i].wrapping_add(buf[i - N]);
|
||||
}
|
||||
}
|
||||
|
||||
fn two_bytes_predict<const N: usize>(buf: &mut [u8]) {
|
||||
for i in (2 * N..buf.len()).step_by(2) {
|
||||
let v = u16::from_ne_bytes(buf[i..][..2].try_into().unwrap());
|
||||
let p = u16::from_ne_bytes(buf[i - 2 * N..][..2].try_into().unwrap());
|
||||
buf[i..][..2].copy_from_slice(&(v.wrapping_add(p)).to_ne_bytes());
|
||||
}
|
||||
}
|
||||
|
||||
fn four_bytes_predict<const N: usize>(buf: &mut [u8]) {
|
||||
for i in (N * 4..buf.len()).step_by(4) {
|
||||
let v = u32::from_ne_bytes(buf[i..][..4].try_into().unwrap());
|
||||
let p = u32::from_ne_bytes(buf[i - 4 * N..][..4].try_into().unwrap());
|
||||
buf[i..][..4].copy_from_slice(&(v.wrapping_add(p)).to_ne_bytes());
|
||||
}
|
||||
}
|
||||
|
||||
match (bit_depth, samples) {
|
||||
// Note we can't use `windows` or so due to the overlap between each iteration. We split
|
||||
// the cases by the samples / lookback constant so that each is optimized individually.
|
||||
// This is more code generated but each loop can then have a different vectorization
|
||||
// strategy.
|
||||
(0..=8, 1) => one_byte_predict::<1>(buf),
|
||||
(0..=8, 2) => one_byte_predict::<2>(buf),
|
||||
(0..=8, 3) => one_byte_predict::<3>(buf),
|
||||
(0..=8, 4) => one_byte_predict::<4>(buf),
|
||||
// The generic, sub-optimal case for the above.
|
||||
(0..=8, _) => {
|
||||
match bit_depth {
|
||||
0..=8 => {
|
||||
for i in samples..buf.len() {
|
||||
buf[i] = buf[i].wrapping_add(buf[i - samples]);
|
||||
}
|
||||
}
|
||||
(9..=16, 1) => {
|
||||
two_bytes_predict::<1>(buf);
|
||||
}
|
||||
(9..=16, 2) => {
|
||||
two_bytes_predict::<2>(buf);
|
||||
}
|
||||
(9..=16, 3) => {
|
||||
two_bytes_predict::<3>(buf);
|
||||
}
|
||||
(9..=16, 4) => {
|
||||
two_bytes_predict::<4>(buf);
|
||||
}
|
||||
(9..=16, _) => {
|
||||
9..=16 => {
|
||||
for i in (samples * 2..buf.len()).step_by(2) {
|
||||
let v = u16::from_ne_bytes(buf[i..][..2].try_into().unwrap());
|
||||
let p = u16::from_ne_bytes(buf[i - 2 * samples..][..2].try_into().unwrap());
|
||||
buf[i..][..2].copy_from_slice(&(v.wrapping_add(p)).to_ne_bytes());
|
||||
}
|
||||
}
|
||||
(17..=32, 1) => {
|
||||
four_bytes_predict::<1>(buf);
|
||||
}
|
||||
(17..=32, 2) => {
|
||||
four_bytes_predict::<2>(buf);
|
||||
}
|
||||
(17..=32, 3) => {
|
||||
four_bytes_predict::<3>(buf);
|
||||
}
|
||||
(17..=32, 4) => {
|
||||
four_bytes_predict::<4>(buf);
|
||||
}
|
||||
(17..=32, _) => {
|
||||
17..=32 => {
|
||||
for i in (samples * 4..buf.len()).step_by(4) {
|
||||
let v = u32::from_ne_bytes(buf[i..][..4].try_into().unwrap());
|
||||
let p = u32::from_ne_bytes(buf[i - 4 * samples..][..4].try_into().unwrap());
|
||||
buf[i..][..4].copy_from_slice(&(v.wrapping_add(p)).to_ne_bytes());
|
||||
}
|
||||
}
|
||||
(33..=64, _) => {
|
||||
33..=64 => {
|
||||
for i in (samples * 8..buf.len()).step_by(8) {
|
||||
let v = u64::from_ne_bytes(buf[i..][..8].try_into().unwrap());
|
||||
let p = u64::from_ne_bytes(buf[i - 8 * samples..][..8].try_into().unwrap());
|
||||
@@ -530,22 +479,11 @@ fn fix_endianness_and_predict(
|
||||
}
|
||||
}
|
||||
|
||||
fn invert_colors(
|
||||
buf: &mut [u8],
|
||||
color_type: ColorType,
|
||||
sample_format: SampleFormat,
|
||||
) -> TiffResult<()> {
|
||||
fn invert_colors(buf: &mut [u8], color_type: ColorType, sample_format: SampleFormat) {
|
||||
match (color_type, sample_format) {
|
||||
// Where pixels do not cross a byte boundary
|
||||
(ColorType::Gray(1 | 2 | 4 | 8), SampleFormat::Uint) => {
|
||||
(ColorType::Gray(8), SampleFormat::Uint) => {
|
||||
for x in buf {
|
||||
// Equivalent to both of the following:
|
||||
//
|
||||
// *x = 0xff - *x
|
||||
// *x = !*x
|
||||
//
|
||||
// since -x = !x+1
|
||||
*x = !*x;
|
||||
*x = 0xff - *x;
|
||||
}
|
||||
}
|
||||
(ColorType::Gray(16), SampleFormat::Uint) => {
|
||||
@@ -578,31 +516,42 @@ fn invert_colors(
|
||||
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.
|
||||
fn fix_endianness(buf: &mut [u8], byte_order: ByteOrder, bit_depth: u8) {
|
||||
let host = ByteOrder::native();
|
||||
|
||||
let class = match bit_depth {
|
||||
0..=8 => crate::tags::EndianBytes::One,
|
||||
9..=16 => crate::tags::EndianBytes::Two,
|
||||
17..=32 => crate::tags::EndianBytes::Four,
|
||||
_ => crate::tags::EndianBytes::Eight,
|
||||
match byte_order {
|
||||
ByteOrder::LittleEndian => match bit_depth {
|
||||
0..=8 => {}
|
||||
9..=16 => buf.chunks_exact_mut(2).for_each(|v| {
|
||||
v.copy_from_slice(&u16::from_le_bytes((*v).try_into().unwrap()).to_ne_bytes())
|
||||
}),
|
||||
17..=32 => buf.chunks_exact_mut(4).for_each(|v| {
|
||||
v.copy_from_slice(&u32::from_le_bytes((*v).try_into().unwrap()).to_ne_bytes())
|
||||
}),
|
||||
_ => buf.chunks_exact_mut(8).for_each(|v| {
|
||||
v.copy_from_slice(&u64::from_le_bytes((*v).try_into().unwrap()).to_ne_bytes())
|
||||
}),
|
||||
},
|
||||
ByteOrder::BigEndian => match bit_depth {
|
||||
0..=8 => {}
|
||||
9..=16 => buf.chunks_exact_mut(2).for_each(|v| {
|
||||
v.copy_from_slice(&u16::from_be_bytes((*v).try_into().unwrap()).to_ne_bytes())
|
||||
}),
|
||||
17..=32 => buf.chunks_exact_mut(4).for_each(|v| {
|
||||
v.copy_from_slice(&u32::from_be_bytes((*v).try_into().unwrap()).to_ne_bytes())
|
||||
}),
|
||||
_ => buf.chunks_exact_mut(8).for_each(|v| {
|
||||
v.copy_from_slice(&u64::from_be_bytes((*v).try_into().unwrap()).to_ne_bytes())
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
host.convert_endian_bytes(class, buf, byte_order);
|
||||
}
|
||||
|
||||
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>> {
|
||||
let mut endianess = Vec::with_capacity(2);
|
||||
(&mut r).take(2).read_to_end(&mut endianess)?;
|
||||
@@ -679,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)
|
||||
@@ -708,48 +658,85 @@ impl<R: Read + Seek> Decoder<R> {
|
||||
|
||||
/// Loads the IFD at the specified index in the list, if one exists
|
||||
pub fn seek_to_image(&mut self, ifd_index: usize) -> TiffResult<()> {
|
||||
// Check whether we have seen this IFD before, if so then the index will be less than the length of the list of ifd offsets
|
||||
if ifd_index >= self.ifd_offsets.len() {
|
||||
// We possibly need to load in the next IFD
|
||||
if self.next_ifd.is_none() {
|
||||
self.current_ifd = None;
|
||||
let ifd = self.seek_to_directory(ifd_index)?;
|
||||
self.non_image_ifd = None;
|
||||
self.image = Image::from_reader(&mut self.value_reader, ifd)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
return Err(TiffError::FormatError(
|
||||
TiffFormatError::ImageFileDirectoryNotFound,
|
||||
));
|
||||
}
|
||||
fn seek_to_directory(&mut self, ifd_index: usize) -> TiffResult<Directory> {
|
||||
if ifd_index < self.ifd_offsets.len() {
|
||||
// If the index is within the list of ifds then we can load the selected image/IFD
|
||||
let ifd_offset = self.ifd_offsets[ifd_index];
|
||||
let ifd = self.value_reader.read_directory(ifd_offset)?;
|
||||
|
||||
loop {
|
||||
// Follow the list until we find the one we want, or we reach the end, whichever happens first
|
||||
let ifd = self.next_ifd()?;
|
||||
|
||||
if ifd.next().is_none() {
|
||||
break;
|
||||
}
|
||||
|
||||
if ifd_index < self.ifd_offsets.len() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the index is within the list of ifds then we can load the selected image/IFD
|
||||
if let Some(ifd_offset) = self.ifd_offsets.get(ifd_index) {
|
||||
let ifd = self.value_reader.read_directory(*ifd_offset)?;
|
||||
self.next_ifd = ifd.next();
|
||||
self.current_ifd = Some(*ifd_offset);
|
||||
self.image = Image::from_reader(&mut self.value_reader, ifd)?;
|
||||
self.current_ifd = Some(ifd_offset);
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
Err(TiffError::FormatError(
|
||||
TiffFormatError::ImageFileDirectoryNotFound,
|
||||
))
|
||||
return Ok(ifd);
|
||||
}
|
||||
|
||||
// Follow the list until we find the one we want, or we reach the end, whichever happens
|
||||
// first. How many IFDs to read only for their `next` field?
|
||||
let step_over = self.ifd_offsets.len() - ifd_index;
|
||||
|
||||
for _ in 0..step_over {
|
||||
// FIXME: for optimization we only need to read the offset of this one, not its whole
|
||||
// data. However on buffered files this should be a rather small difference unless
|
||||
// you're traversing a lot of directories.
|
||||
|
||||
// Detecting an end-of-file is done by `next_ifd`. We ignore the IFD itself (but not
|
||||
// via an ignore pattern to avoid silencing must_use accidentally).
|
||||
self.next_ifd()?;
|
||||
}
|
||||
|
||||
// self.next_ifd, self.current_ifd will be setup by `next_ifd`.
|
||||
self.next_ifd()
|
||||
}
|
||||
|
||||
/// Start the chain of image directories from a new location.
|
||||
///
|
||||
/// This enters a new chain of image file directories with the indicated offset as the new root
|
||||
/// (similar to the file having its initial offset at the given IFD position). After this call,
|
||||
/// [`Self::seek_to_image`] has the new root at index `0` and works relative to the new root.
|
||||
///
|
||||
/// This is not atomic with regards to errors. If the function returns an error, the decoder is
|
||||
/// left in an intermediate state where it does not point to any image. A valid state can be
|
||||
/// recovered by calling [`Self::restart_at_image`] with a valid offset and a successful seek.
|
||||
pub fn restart_at_image(&mut self, offset: IfdPointer) -> TiffResult<()> {
|
||||
let ifd = self.restart_at_offset(offset)?;
|
||||
self.non_image_ifd = None;
|
||||
self.image = Image::from_reader(&mut self.value_reader, ifd)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Start the chain of non-image directories from a new location.
|
||||
///
|
||||
/// See [`Self::restart_at_image`] for details, except this method does not attempt to
|
||||
/// interpret the directory in the sequence as an image. Instead, it may be used to read a
|
||||
/// sequence of auxiliary IFDs that are not necessarily images. For instance, a directory
|
||||
/// referred to in the SubIfd tag may be a thumbnail
|
||||
pub fn restart_at_directory(&mut self, offset: IfdPointer) -> TiffResult<()> {
|
||||
let ifd = self.restart_at_offset(offset)?;
|
||||
self.non_image_ifd = Some(ifd);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn restart_at_offset(&mut self, offset: IfdPointer) -> TiffResult<Directory> {
|
||||
self.ifd_offsets.clear();
|
||||
self.ifd_offsets.push(offset);
|
||||
|
||||
self.next_ifd = Some(offset);
|
||||
self.current_ifd = None;
|
||||
|
||||
self.next_ifd()
|
||||
}
|
||||
|
||||
fn next_ifd(&mut self) -> TiffResult<Directory> {
|
||||
let Some(next_ifd) = self.next_ifd.take() else {
|
||||
self.current_ifd = None;
|
||||
self.non_image_ifd = None;
|
||||
|
||||
return Err(TiffError::FormatError(
|
||||
TiffFormatError::ImageFileDirectoryNotFound,
|
||||
));
|
||||
@@ -776,21 +763,50 @@ 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()
|
||||
}
|
||||
|
||||
/// Returns the byte_order of the file.
|
||||
///
|
||||
/// # Usage
|
||||
///
|
||||
/// This is only relevant to interpreting raw bytes read from tags. The image decoding methods
|
||||
/// will correct to the host byte order automatically.
|
||||
pub fn byte_order(&self) -> ByteOrder {
|
||||
self.value_reader.reader.byte_order
|
||||
}
|
||||
@@ -1287,11 +1303,22 @@ impl<R: Read + Seek> Decoder<R> {
|
||||
}
|
||||
|
||||
/// Get the IFD decoder for our current image IFD.
|
||||
fn image_ifd(&mut self) -> IfdDecoder<'_> {
|
||||
fn current_directory_ifd(&mut self) -> IfdDecoder<'_> {
|
||||
// Special fallback. We do not want to error handle not having read a current directory, in
|
||||
// particular as the behavior having a directory without tags will produce these errors
|
||||
// anyways (most likely). Note that an empty directory is invalid in a TIFF.
|
||||
static NO_IFD: Directory = Directory::empty();
|
||||
|
||||
let ifd = self
|
||||
.non_image_ifd
|
||||
.as_ref()
|
||||
.or(self.image.ifd.as_ref())
|
||||
.unwrap_or(&NO_IFD);
|
||||
|
||||
IfdDecoder {
|
||||
inner: tag_reader::TagReader {
|
||||
decoder: &mut self.value_reader,
|
||||
ifd: self.image.ifd.as_ref().unwrap(),
|
||||
ifd,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1332,34 +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)
|
||||
}
|
||||
|
||||
/// 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)
|
||||
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> {
|
||||
@@ -1414,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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+23
-101
@@ -1,7 +1,15 @@
|
||||
//! All IO functionality needed for TIFF decoding
|
||||
|
||||
use std::io::{self, BufRead, BufReader, Read, Seek, Take};
|
||||
|
||||
pub use crate::tags::ByteOrder;
|
||||
/// Byte order of the TIFF file.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum ByteOrder {
|
||||
/// little endian byte order
|
||||
LittleEndian,
|
||||
/// big endian byte order
|
||||
BigEndian,
|
||||
}
|
||||
|
||||
/// Reader that is aware of the byte order.
|
||||
#[derive(Debug)]
|
||||
@@ -38,6 +46,17 @@ impl<R: Read> EndianReader<R> {
|
||||
})
|
||||
}
|
||||
|
||||
/// Reads an i8
|
||||
#[inline(always)]
|
||||
pub fn read_i8(&mut self) -> Result<i8, io::Error> {
|
||||
let mut n = [0u8; 1];
|
||||
self.reader.read_exact(&mut n)?;
|
||||
Ok(match self.byte_order {
|
||||
ByteOrder::LittleEndian => i8::from_le_bytes(n),
|
||||
ByteOrder::BigEndian => i8::from_be_bytes(n),
|
||||
})
|
||||
}
|
||||
|
||||
/// Reads an i16
|
||||
#[inline(always)]
|
||||
pub fn read_i16(&mut self) -> Result<i16, io::Error> {
|
||||
@@ -168,6 +187,9 @@ impl<R: Read> Read for LZWReader<R> {
|
||||
}
|
||||
}
|
||||
Ok(weezl::LzwStatus::NoProgress) => {
|
||||
assert_eq!(result.consumed_in, 0);
|
||||
assert_eq!(result.consumed_out, 0);
|
||||
assert!(self.reader.buffer().is_empty());
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::UnexpectedEof,
|
||||
"no lzw end code found",
|
||||
@@ -254,106 +276,6 @@ 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)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
+1
-31
@@ -29,7 +29,7 @@ pub struct Directory {
|
||||
impl Directory {
|
||||
/// Create a directory in an initial state without entries. Note that an empty directory can
|
||||
/// not be encoded in a file, it must contain at least one entry.
|
||||
pub fn empty() -> Self {
|
||||
pub const fn empty() -> Self {
|
||||
Directory {
|
||||
entries: BTreeMap::new(),
|
||||
next_ifd: None,
|
||||
@@ -104,28 +104,6 @@ impl Directory {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn encoded_len<K: crate::encoder::TiffKind>(&self) -> u64 {
|
||||
let (len_count, len_offset);
|
||||
|
||||
match std::mem::size_of::<K::OffsetType>() {
|
||||
offset @ 4 => {
|
||||
// non-BigTiff uses a `u16` here.
|
||||
len_count = 2;
|
||||
len_offset = offset as u64;
|
||||
}
|
||||
offset => {
|
||||
let offset = offset as u64;
|
||||
// assuming BigTIFF
|
||||
len_count = offset;
|
||||
len_offset = offset;
|
||||
}
|
||||
}
|
||||
|
||||
// Each entry is 12 bytes in classic TIFF, 20 bytes in BigTIFF.
|
||||
let entry_bytes = 4 + 2 * len_offset;
|
||||
len_count + entry_bytes * self.entries.len() as u64 + len_offset
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Directory {
|
||||
@@ -140,14 +118,6 @@ impl fmt::Debug for Directory {
|
||||
}
|
||||
}
|
||||
|
||||
impl core::iter::FromIterator<(Tag, Entry)> for Directory {
|
||||
fn from_iter<T: IntoIterator<Item = (Tag, Entry)>>(iter: T) -> Self {
|
||||
let mut dir = Directory::empty();
|
||||
dir.extend(iter);
|
||||
dir
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Directory;
|
||||
|
||||
@@ -14,7 +14,7 @@ impl Compression for Uncompressed {
|
||||
|
||||
impl CompressionAlgorithm for Uncompressed {
|
||||
fn write_to<W: Write>(&mut self, writer: &mut W, bytes: &[u8]) -> Result<u64, io::Error> {
|
||||
writer.write_all(bytes).map(|()| bytes.len() as u64)
|
||||
writer.write(bytes).map(|byte_count| byte_count as u64)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+13
-127
@@ -37,9 +37,8 @@ pub type Predictor = crate::tags::Predictor;
|
||||
#[cfg(feature = "deflate")]
|
||||
pub type DeflateLevel = compression::DeflateLevel;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Default)]
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub enum Compression {
|
||||
#[default]
|
||||
Uncompressed,
|
||||
#[cfg(feature = "lzw")]
|
||||
Lzw,
|
||||
@@ -48,6 +47,12 @@ pub enum Compression {
|
||||
Packbits,
|
||||
}
|
||||
|
||||
impl Default for Compression {
|
||||
fn default() -> Self {
|
||||
Self::Uncompressed
|
||||
}
|
||||
}
|
||||
|
||||
impl Compression {
|
||||
fn tag(&self) -> CompressionMethod {
|
||||
match self {
|
||||
@@ -74,10 +79,10 @@ impl Compression {
|
||||
|
||||
/// Encoder for Tiff and BigTiff files.
|
||||
///
|
||||
/// With this type you can get a [`DirectoryEncoder`] or a [`ImageEncoder`]
|
||||
/// With this type you can get a `DirectoryEncoder` or a `ImageEncoder`
|
||||
/// to encode Tiff/BigTiff ifd directories with images.
|
||||
///
|
||||
/// See [`DirectoryEncoder`] and [`ImageEncoder`].
|
||||
/// See `DirectoryEncoder` and `ImageEncoder`.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
@@ -171,7 +176,7 @@ impl<W: Write + Seek, K: TiffKind> TiffEncoder<W, K> {
|
||||
Self::chain_directory(&mut self.writer, &mut self.last_ifd_chain)
|
||||
}
|
||||
|
||||
/// Create a [`DirectoryEncoder`] to encode an ifd (directory).
|
||||
/// Create a [`DirectoryEncoder`] to encode an ifd directory.
|
||||
///
|
||||
/// The caller is responsible for ensuring that the directory is a valid image in the main TIFF
|
||||
/// IFD sequence. To encode additional directories that are not linked into the sequence, use
|
||||
@@ -180,7 +185,7 @@ impl<W: Write + Seek, K: TiffKind> TiffEncoder<W, K> {
|
||||
Self::chain_directory(&mut self.writer, &mut self.last_ifd_chain)
|
||||
}
|
||||
|
||||
/// Create a [`DirectoryEncoder`] to encode a non-image directory.
|
||||
/// Create a [`DirectoryEncoder`] to encode an ifd directory.
|
||||
///
|
||||
/// The directory is not linked into the sequence of directories. For instance, encode Exif
|
||||
/// directories or SubIfd directories with this method.
|
||||
@@ -193,7 +198,7 @@ impl<W: Write + Seek, K: TiffKind> TiffEncoder<W, K> {
|
||||
&mut self,
|
||||
width: 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)?;
|
||||
ImageEncoder::new(encoder, width, height, self.compression, self.predictor)
|
||||
}
|
||||
@@ -244,12 +249,6 @@ pub struct DirectoryEncoder<'a, W: 'a + Write + Seek, K: TiffKind> {
|
||||
}
|
||||
|
||||
/// The offset of an encoded directory in the file.
|
||||
///
|
||||
/// Both the `offset` and `pointer` field are a complete description of the start offset of the
|
||||
/// dictionary which is used to point to it. The struct also describes the extent covered by the
|
||||
/// encoding of the directory. The constructors [`new`][DirectoryOffset::new] allow the checked
|
||||
/// conversion. Note that even though the offsets are public do not expect their information to
|
||||
/// propagate when modified.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct DirectoryOffset<K: TiffKind> {
|
||||
/// The start of the directory as a Tiff value.
|
||||
@@ -261,9 +260,7 @@ pub struct DirectoryOffset<K: TiffKind> {
|
||||
pub offset: K::OffsetType,
|
||||
/// The start of the directory as a pure offset.
|
||||
pub pointer: IfdPointer,
|
||||
/// The offset of its sequence link field, in our private representation. Before exposing this
|
||||
/// make sure that we don't want any invariants (e.g. pointer being smaller than this). The
|
||||
/// type should be fine.
|
||||
/// The offset of its sequence link field, in our private representation.
|
||||
ifd_chain: NonZeroU64,
|
||||
/// The kind of Tiff file the offset is for.
|
||||
kind: PhantomData<K>,
|
||||
@@ -321,17 +318,6 @@ impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> {
|
||||
Ok(offset)
|
||||
}
|
||||
|
||||
/// Insert previously written key-value entries.
|
||||
///
|
||||
/// It is the caller's responsibility to ensure that the data referred to by the entries is
|
||||
/// actually (or will be) written to the file. Note that small values are necessarily inline in
|
||||
/// the entry, the size limit depends on the Tiff kind. So do not confuse a directory from a
|
||||
/// BigTiff in a standard tiff or the other way around.
|
||||
pub fn extend_from(&mut self, dir: &Directory) {
|
||||
let entries = dir.iter().map(|(tag, val)| (tag, val.clone()));
|
||||
self.directory.extend(entries);
|
||||
}
|
||||
|
||||
/// Define the parent directory.
|
||||
///
|
||||
/// Each directory has an offset based link to its successor, forming a linked list in the
|
||||
@@ -348,11 +334,6 @@ impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Write the IFD to the file and return the offset it is written to.
|
||||
///
|
||||
/// The offset can be used as the value, or as part of a list of values, in the entry of
|
||||
/// another directory. If you're constructing the entry directly you'll want to use an
|
||||
/// appropriate type variant for this such as [`Type::IFD`].
|
||||
pub fn finish_with_offsets(mut self) -> TiffResult<DirectoryOffset<K>> {
|
||||
self.finish_internal()
|
||||
}
|
||||
@@ -810,71 +791,6 @@ pub trait TiffKind {
|
||||
fn convert_slice(slice: &[Self::OffsetType]) -> &Self::OffsetArrayType;
|
||||
}
|
||||
|
||||
impl<K: TiffKind> DirectoryOffset<K> {
|
||||
/// Construct the directory pointer information from its raw offset.
|
||||
///
|
||||
/// Fails in these cases:
|
||||
///
|
||||
/// - if start offset is too large to be represented with the chosen TIFF format, i.e. a value
|
||||
/// larger than [`u32::MAX`] with a standard tiff which is only valid in a BigTiff. This
|
||||
/// returns [`TiffError::IntSizeError`].
|
||||
/// - the extent of the directory would overflow the file length. This returns a
|
||||
/// [`TiffError::UsageError`].
|
||||
///
|
||||
/// The library does *not* validate the contents or offset in any way, it just performs the
|
||||
/// necessary math assuming the data is an accurate representation of existing content.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// If you have any custom way of writing a directory to a file that can not go through the
|
||||
/// usual [`TiffEncoder::extra_directory`] then you can reconstruct the offset information as
|
||||
/// long as you provide all the necessary data to the library. You can then use it as a parent
|
||||
/// directory, i.e. have the library overwrite its `next` field.
|
||||
///
|
||||
/// ```
|
||||
/// use tiff::{
|
||||
/// encoder::{TiffEncoder, DirectoryOffset},
|
||||
/// Directory,
|
||||
/// tags::IfdPointer,
|
||||
/// };
|
||||
///
|
||||
/// # let mut file = std::io::Cursor::new(Vec::new());
|
||||
/// let mut tiff = TiffEncoder::new(&mut file)?;
|
||||
///
|
||||
/// // … some custom data writes, assume we know where a directory was written
|
||||
/// # fn reconstruction_of_your_directory() -> Directory { Directory::from_iter([]) }
|
||||
/// let known_offset = IfdPointer(1024);
|
||||
/// let known_contents: Directory = reconstruction_of_your_directory();
|
||||
///
|
||||
/// let reconstructed = DirectoryOffset::new(known_offset, &known_contents)?;
|
||||
///
|
||||
/// // Now we can use it as if the directory was written by the `TiffEncoder` itself.
|
||||
/// let mut chain = tiff.extra_directory()?;
|
||||
/// chain.set_parent(&reconstructed);
|
||||
/// chain.finish_with_offsets()?;
|
||||
///
|
||||
/// # Ok::<_, tiff::TiffError>(())
|
||||
/// ```
|
||||
pub fn new(pointer: IfdPointer, dir: &Directory) -> TiffResult<Self> {
|
||||
let offset = K::convert_offset(pointer.0)?;
|
||||
let encoded = dir.encoded_len::<K>();
|
||||
let offset_field_offset = encoded - mem::size_of_val(&offset) as u64;
|
||||
|
||||
let ifd_chain = pointer
|
||||
.0
|
||||
.checked_add(offset_field_offset)
|
||||
.and_then(NonZeroU64::new)
|
||||
.ok_or(TiffError::UsageError(UsageError::ZeroIfdPointer))?;
|
||||
|
||||
Ok(DirectoryOffset {
|
||||
pointer,
|
||||
offset,
|
||||
ifd_chain,
|
||||
kind: PhantomData,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a standard Tiff file.
|
||||
pub struct TiffKindStandard;
|
||||
|
||||
@@ -943,33 +859,3 @@ impl TiffKind for TiffKindBig {
|
||||
slice
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn directory_offset_new_equivalent_to_writing() {
|
||||
type K = TiffKindStandard;
|
||||
|
||||
let dir = Directory::from_iter(vec![
|
||||
(
|
||||
Tag::ImageWidth,
|
||||
Entry::new(Type::LONG, 1, 100u32.to_ne_bytes()),
|
||||
),
|
||||
(
|
||||
Tag::ImageLength,
|
||||
Entry::new(Type::LONG, 1, 200u32.to_ne_bytes()),
|
||||
),
|
||||
]);
|
||||
|
||||
let data = std::io::Cursor::new(vec![]);
|
||||
let mut file = TiffEncoder::new(data).unwrap();
|
||||
|
||||
let mut as_dir_encoder = file.extra_directory().unwrap();
|
||||
as_dir_encoder.extend_from(&dir);
|
||||
let dir_extent = as_dir_encoder.finish_with_offsets().unwrap();
|
||||
|
||||
// Check we can recreate the extent had we written ourselves.
|
||||
let synth = DirectoryOffset::<K>::new(dir_extent.pointer, &dir).unwrap();
|
||||
|
||||
assert_eq!(synth.pointer, dir_extent.pointer);
|
||||
assert_eq!(synth.offset, dir_extent.offset);
|
||||
assert_eq!(synth.ifd_chain, dir_extent.ifd_chain);
|
||||
}
|
||||
+28
-28
@@ -15,7 +15,7 @@ pub trait TiffValue {
|
||||
|
||||
/// Access this value as an contiguous sequence of bytes.
|
||||
/// 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.
|
||||
/// While the default implementation will work in all cases, it may require unnecessary allocations.
|
||||
@@ -34,7 +34,7 @@ impl TiffValue for [u8] {
|
||||
self.len()
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<'_, [u8]> {
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
Cow::Borrowed(self)
|
||||
}
|
||||
}
|
||||
@@ -47,7 +47,7 @@ impl TiffValue for [i8] {
|
||||
self.len()
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<'_, [u8]> {
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
Cow::Borrowed(bytecast::i8_as_ne_bytes(self))
|
||||
}
|
||||
}
|
||||
@@ -60,7 +60,7 @@ impl TiffValue for [u16] {
|
||||
self.len()
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<'_, [u8]> {
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
Cow::Borrowed(bytecast::u16_as_ne_bytes(self))
|
||||
}
|
||||
}
|
||||
@@ -73,7 +73,7 @@ impl TiffValue for [i16] {
|
||||
self.len()
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<'_, [u8]> {
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
Cow::Borrowed(bytecast::i16_as_ne_bytes(self))
|
||||
}
|
||||
}
|
||||
@@ -86,7 +86,7 @@ impl TiffValue for [u32] {
|
||||
self.len()
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<'_, [u8]> {
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
Cow::Borrowed(bytecast::u32_as_ne_bytes(self))
|
||||
}
|
||||
}
|
||||
@@ -99,7 +99,7 @@ impl TiffValue for [i32] {
|
||||
self.len()
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<'_, [u8]> {
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
Cow::Borrowed(bytecast::i32_as_ne_bytes(self))
|
||||
}
|
||||
}
|
||||
@@ -112,7 +112,7 @@ impl TiffValue for [u64] {
|
||||
self.len()
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<'_, [u8]> {
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
Cow::Borrowed(bytecast::u64_as_ne_bytes(self))
|
||||
}
|
||||
}
|
||||
@@ -125,7 +125,7 @@ impl TiffValue for [i64] {
|
||||
self.len()
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<'_, [u8]> {
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
Cow::Borrowed(bytecast::i64_as_ne_bytes(self))
|
||||
}
|
||||
}
|
||||
@@ -138,7 +138,7 @@ impl TiffValue for [f32] {
|
||||
self.len()
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<'_, [u8]> {
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
// We write using native endian so this should be safe
|
||||
Cow::Borrowed(bytecast::f32_as_ne_bytes(self))
|
||||
}
|
||||
@@ -152,7 +152,7 @@ impl TiffValue for [f64] {
|
||||
self.len()
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<'_, [u8]> {
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
// We write using native endian so this should be safe
|
||||
Cow::Borrowed(bytecast::f64_as_ne_bytes(self))
|
||||
}
|
||||
@@ -171,7 +171,7 @@ impl TiffValue for u8 {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<'_, [u8]> {
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
Cow::Borrowed(from_ref(self))
|
||||
}
|
||||
}
|
||||
@@ -189,7 +189,7 @@ impl TiffValue for i8 {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<'_, [u8]> {
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
Cow::Borrowed(bytecast::i8_as_ne_bytes(from_ref(self)))
|
||||
}
|
||||
}
|
||||
@@ -207,7 +207,7 @@ impl TiffValue for u16 {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<'_, [u8]> {
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
Cow::Borrowed(bytecast::u16_as_ne_bytes(from_ref(self)))
|
||||
}
|
||||
}
|
||||
@@ -225,7 +225,7 @@ impl TiffValue for i16 {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<'_, [u8]> {
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
Cow::Borrowed(bytecast::i16_as_ne_bytes(from_ref(self)))
|
||||
}
|
||||
}
|
||||
@@ -243,7 +243,7 @@ impl TiffValue for u32 {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<'_, [u8]> {
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
Cow::Borrowed(bytecast::u32_as_ne_bytes(from_ref(self)))
|
||||
}
|
||||
}
|
||||
@@ -261,7 +261,7 @@ impl TiffValue for i32 {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<'_, [u8]> {
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
Cow::Borrowed(bytecast::i32_as_ne_bytes(from_ref(self)))
|
||||
}
|
||||
}
|
||||
@@ -279,7 +279,7 @@ impl TiffValue for u64 {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<'_, [u8]> {
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
Cow::Borrowed(bytecast::u64_as_ne_bytes(from_ref(self)))
|
||||
}
|
||||
}
|
||||
@@ -297,7 +297,7 @@ impl TiffValue for i64 {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<'_, [u8]> {
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
Cow::Borrowed(bytecast::i64_as_ne_bytes(from_ref(self)))
|
||||
}
|
||||
}
|
||||
@@ -315,7 +315,7 @@ impl TiffValue for f32 {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<'_, [u8]> {
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
Cow::Borrowed(bytecast::f32_as_ne_bytes(from_ref(self)))
|
||||
}
|
||||
}
|
||||
@@ -333,7 +333,7 @@ impl TiffValue for f64 {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<'_, [u8]> {
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
Cow::Borrowed(bytecast::f64_as_ne_bytes(from_ref(self)))
|
||||
}
|
||||
}
|
||||
@@ -351,7 +351,7 @@ impl TiffValue for Ifd {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<'_, [u8]> {
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
Cow::Borrowed(bytecast::u32_as_ne_bytes(from_ref(&self.0)))
|
||||
}
|
||||
}
|
||||
@@ -369,7 +369,7 @@ impl TiffValue for Ifd8 {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<'_, [u8]> {
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
Cow::Borrowed(bytecast::u64_as_ne_bytes(from_ref(&self.0)))
|
||||
}
|
||||
}
|
||||
@@ -388,7 +388,7 @@ impl TiffValue for Rational {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<'_, [u8]> {
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
Cow::Owned({
|
||||
let first_dword = bytecast::u32_as_ne_bytes(from_ref(&self.n));
|
||||
let second_dword = bytecast::u32_as_ne_bytes(from_ref(&self.d));
|
||||
@@ -411,7 +411,7 @@ impl TiffValue for SRational {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<'_, [u8]> {
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
Cow::Owned({
|
||||
let first_dword = bytecast::i32_as_ne_bytes(from_ref(&self.n));
|
||||
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({
|
||||
if self.is_ascii() && !self.bytes().any(|b| b == 0) {
|
||||
let bytes: &[u8] = self.as_bytes();
|
||||
@@ -462,7 +462,7 @@ impl<T: TiffValue + ?Sized> TiffValue for &'_ T {
|
||||
(*self).write(writer)
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<'_, [u8]> {
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
T::data(self)
|
||||
}
|
||||
}
|
||||
@@ -484,7 +484,7 @@ macro_rules! impl_tiff_value_for_contiguous_sequence {
|
||||
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());
|
||||
for x in self {
|
||||
buf.extend_from_slice(&x.data());
|
||||
|
||||
@@ -190,58 +190,3 @@ impl<W: Seek> TiffWriter<W> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encoder_on_short_writes() {
|
||||
struct ShortWriter<T> {
|
||||
inner: T,
|
||||
}
|
||||
|
||||
impl<T: std::io::Write> std::io::Write for ShortWriter<T> {
|
||||
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||
let lower_bound = buf.len().min(1);
|
||||
let lower = buf.len().saturating_sub(1).max(lower_bound);
|
||||
|
||||
self.inner.write(&buf[..lower])
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> std::io::Result<()> {
|
||||
self.inner.flush()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: std::io::Seek> std::io::Seek for ShortWriter<T> {
|
||||
fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
|
||||
self.inner.seek(pos)
|
||||
}
|
||||
}
|
||||
|
||||
let mut write = ShortWriter {
|
||||
inner: std::io::Cursor::new(vec![0u8; 0]),
|
||||
};
|
||||
|
||||
let imgdata = vec![42u8; 100 * 100];
|
||||
for comp in [
|
||||
super::Compression::Uncompressed,
|
||||
super::Compression::Packbits,
|
||||
] {
|
||||
write.inner.set_position(0);
|
||||
write.inner.get_mut().clear();
|
||||
|
||||
{
|
||||
let mut tiff_writer = super::TiffEncoder::new(&mut write)
|
||||
.unwrap()
|
||||
.with_compression(comp);
|
||||
|
||||
tiff_writer
|
||||
.write_image::<super::Gray8>(100, 100, &imgdata)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
write.inner.set_position(0);
|
||||
let mut tiff_reader = crate::decoder::Decoder::new(&mut write.inner).unwrap();
|
||||
let data = tiff_reader.read_image().unwrap();
|
||||
|
||||
assert!(matches!(data, crate::decoder::DecodingResult::U8(v) if v == imgdata));
|
||||
}
|
||||
}
|
||||
@@ -187,9 +187,6 @@ quick_error! {
|
||||
InsufficientOutputBufferSize { needed: usize, provided: usize } {
|
||||
display("the borrowed output buffer is not large enough for the decoded data, needed {needed} but have {provided}")
|
||||
}
|
||||
ZeroIfdPointer {
|
||||
display("the offset 0 can not point to a valid IFD")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
-115
@@ -193,45 +193,6 @@ pub enum Type(u16) {
|
||||
}
|
||||
}
|
||||
|
||||
impl Type {
|
||||
pub(crate) fn byte_len(&self) -> u8 {
|
||||
match *self {
|
||||
Type::BYTE | Type::SBYTE | Type::ASCII | Type::UNDEFINED => 1,
|
||||
Type::SHORT | Type::SSHORT => 2,
|
||||
Type::LONG | Type::SLONG | Type::FLOAT | Type::IFD => 4,
|
||||
Type::LONG8
|
||||
| Type::SLONG8
|
||||
| Type::DOUBLE
|
||||
| Type::RATIONAL
|
||||
| Type::SRATIONAL
|
||||
| Type::IFD8 => 8,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn value_bytes(&self, count: u64) -> Result<u64, crate::error::TiffError> {
|
||||
let tag_size = u64::from(self.byte_len());
|
||||
|
||||
match count.checked_mul(tag_size) {
|
||||
Some(n) => Ok(n),
|
||||
None => Err(crate::error::TiffError::LimitsExceeded),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn endian_bytes(self) -> EndianBytes {
|
||||
match self {
|
||||
Type::BYTE | Type::SBYTE | Type::ASCII | Type::UNDEFINED => EndianBytes::One,
|
||||
Type::SHORT | Type::SSHORT => EndianBytes::Two,
|
||||
Type::LONG
|
||||
| Type::SLONG
|
||||
| Type::FLOAT
|
||||
| Type::IFD
|
||||
| Type::RATIONAL
|
||||
| Type::SRATIONAL => EndianBytes::Four,
|
||||
Type::LONG8 | Type::SLONG8 | Type::DOUBLE | Type::IFD8 => EndianBytes::Eight,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tags! {
|
||||
/// See [TIFF compression tags](https://www.awaresystems.be/imaging/tiff/tifftags/compression.html)
|
||||
/// for reference.
|
||||
@@ -310,79 +271,3 @@ pub enum SampleFormat(u16) unknown(
|
||||
Void = 4,
|
||||
}
|
||||
}
|
||||
|
||||
/// Byte order of the TIFF file.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum ByteOrder {
|
||||
/// little endian byte order
|
||||
LittleEndian,
|
||||
/// big endian byte order
|
||||
BigEndian,
|
||||
}
|
||||
|
||||
impl ByteOrder {
|
||||
/// Get the byte order representing the running target.
|
||||
///
|
||||
/// The infallibility of this method represents the fact that only little and big endian
|
||||
/// systems are supported by the library. No mixed endian and no other weird stuff. (Note: as
|
||||
/// of Rust 1.90 this is a tautology as Rust itself only has those two kinds).
|
||||
pub const fn native() -> Self {
|
||||
match () {
|
||||
#[cfg(target_endian = "little")]
|
||||
() => ByteOrder::LittleEndian,
|
||||
#[cfg(target_endian = "big")]
|
||||
() => ByteOrder::BigEndian,
|
||||
#[cfg(not(any(target_endian = "big", target_endian = "little")))]
|
||||
() => compile_error!("Unsupported target"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Given a typed buffer, convert its contents to the specified byte order in-place.
|
||||
///
|
||||
/// The buffer is assumed to represent an array of the given type. If the length of the buffer
|
||||
/// is not divisible into an integer number of values, the behavior for the remaining bytes it
|
||||
/// not specified.
|
||||
pub fn convert(self, ty: Type, buffer: &mut [u8], to: ByteOrder) {
|
||||
self.convert_endian_bytes(ty.endian_bytes(), buffer, to)
|
||||
}
|
||||
|
||||
pub(crate) fn convert_endian_bytes(self, cls: EndianBytes, buffer: &mut [u8], to: ByteOrder) {
|
||||
if self == to {
|
||||
return;
|
||||
}
|
||||
|
||||
// FIXME: at MSRV 1.89 or higher use `slice::as_chunks_mut`.
|
||||
match cls {
|
||||
EndianBytes::One => {
|
||||
// No change needed
|
||||
}
|
||||
EndianBytes::Two => {
|
||||
for chunk in buffer.chunks_exact_mut(2) {
|
||||
let chunk: &mut [u8; 2] = chunk.try_into().unwrap();
|
||||
*chunk = u16::from_be_bytes(*chunk).to_le_bytes();
|
||||
}
|
||||
}
|
||||
EndianBytes::Four => {
|
||||
for chunk in buffer.chunks_exact_mut(4) {
|
||||
let chunk: &mut [u8; 4] = chunk.try_into().unwrap();
|
||||
*chunk = u32::from_be_bytes(*chunk).to_le_bytes();
|
||||
}
|
||||
}
|
||||
EndianBytes::Eight => {
|
||||
for chunk in buffer.chunks_exact_mut(8) {
|
||||
let chunk: &mut [u8; 8] = chunk.try_into().unwrap();
|
||||
*chunk = u64::from_be_bytes(*chunk).to_le_bytes();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The size of individual byte-order corrected elements.
|
||||
#[derive(Clone, Copy)]
|
||||
pub(crate) enum EndianBytes {
|
||||
One,
|
||||
Two,
|
||||
Four,
|
||||
Eight,
|
||||
}
|
||||
@@ -672,15 +672,3 @@ fn bytes_gray_f32() {
|
||||
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.
Binary file not shown.
Binary file not shown.
+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