Compare commits
33
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6cdf3d8d22 | ||
|
|
0a2b3f91ab | ||
|
|
99b3c03ce3 | ||
|
|
19ac30ac3f | ||
|
|
2058b7f3ca | ||
|
|
2ccbe132e0 | ||
|
|
714e470595 | ||
|
|
4adba46b9f | ||
|
|
695208cffe | ||
|
|
4acdfea783 | ||
|
|
64286c2110 | ||
|
|
12c98535f6 | ||
|
|
1556713d6e | ||
|
|
682ef0c375 | ||
|
|
bc71086a96 | ||
|
|
e67bac763f | ||
|
|
839655b975 | ||
|
|
85635c3cd2 | ||
|
|
481a2ab54a | ||
|
|
7c6c327817 | ||
|
|
9f1de91880 | ||
|
|
4a68feab5a | ||
|
|
b5afb8cca4 | ||
|
|
c8d43262e9 | ||
|
|
80ce77190e | ||
|
|
c75aa98ce2 | ||
|
|
c1087f6f08 | ||
|
|
8cc6bc657d | ||
|
|
9fcc2a3b65 | ||
|
|
666f871d30 | ||
|
|
a919b8febf | ||
|
|
85b5df7a62 | ||
|
|
34232d8141 |
No files matched your search
+16
@@ -1,3 +1,19 @@
|
|||||||
|
# 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
|
# Version 0.10.2
|
||||||
|
|
||||||
Republished 0.10.1, fixing an accidentally removed method (`Decoder::find_tag_unsigned_vec`).
|
Republished 0.10.1, fixing an accidentally removed method (`Decoder::find_tag_unsigned_vec`).
|
||||||
|
|||||||
+6
-3
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "tiff"
|
name = "tiff"
|
||||||
version = "0.10.2"
|
version = "0.10.3"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
|
||||||
@@ -20,10 +20,12 @@ 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 }
|
||||||
zune-jpeg = { version = "0.4.17", optional = true }
|
zune-jpeg = { version = "0.5.1", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
criterion = "0.3.1"
|
criterion = "0.3.1"
|
||||||
@@ -31,10 +33,11 @@ criterion = "0.3.1"
|
|||||||
crc32fast = "1.5"
|
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 |
|
||||||
|
|
||||||
|
|||||||
+218
-120
@@ -376,7 +376,10 @@ impl Entry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a mem_reader for the offset/value field
|
/// Returns a mem_reader for the offset/value field
|
||||||
fn r(&self, byte_order: ByteOrder) -> EndianReader<io::Cursor<Vec<u8>>> {
|
pub(crate) fn offset_field_reader(
|
||||||
|
&self,
|
||||||
|
byte_order: ByteOrder,
|
||||||
|
) -> EndianReader<io::Cursor<Vec<u8>>> {
|
||||||
EndianReader::new(io::Cursor::new(self.offset.to_vec()), byte_order)
|
EndianReader::new(io::Cursor::new(self.offset.to_vec()), byte_order)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -392,43 +395,25 @@ impl Entry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let bo = reader.byte_order;
|
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.
|
// Case 2: there is one value.
|
||||||
if self.count == 1 {
|
if self.count == 1 {
|
||||||
// 2a: the value is 5-8 bytes and we're in BigTiff mode.
|
// 2a: the value is 5-8 bytes and we're in BigTiff mode.
|
||||||
if bigtiff && value_bytes > 4 && value_bytes <= 8 {
|
if bigtiff && value_bytes > 4 && value_bytes <= 8 {
|
||||||
return Ok(match self.type_ {
|
return Ok(match self.type_ {
|
||||||
Type::LONG8 => UnsignedBig(self.r(bo).read_u64()?),
|
Type::LONG8 => UnsignedBig(self.offset_field_reader(bo).read_u64()?),
|
||||||
Type::SLONG8 => SignedBig(self.r(bo).read_i64()?),
|
Type::SLONG8 => SignedBig(self.offset_field_reader(bo).read_i64()?),
|
||||||
Type::DOUBLE => Double(self.r(bo).read_f64()?),
|
Type::DOUBLE => Double(self.offset_field_reader(bo).read_f64()?),
|
||||||
Type::RATIONAL => {
|
Type::RATIONAL => {
|
||||||
let mut r = self.r(bo);
|
let mut r = self.offset_field_reader(bo);
|
||||||
Rational(r.read_u32()?, r.read_u32()?)
|
Rational(r.read_u32()?, r.read_u32()?)
|
||||||
}
|
}
|
||||||
Type::SRATIONAL => {
|
Type::SRATIONAL => {
|
||||||
let mut r = self.r(bo);
|
let mut r = self.offset_field_reader(bo);
|
||||||
SRational(r.read_i32()?, r.read_i32()?)
|
SRational(r.read_i32()?, r.read_i32()?)
|
||||||
}
|
}
|
||||||
Type::IFD8 => IfdBig(self.r(bo).read_u64()?),
|
Type::IFD8 => IfdBig(self.offset_field_reader(bo).read_u64()?),
|
||||||
Type::BYTE
|
Type::BYTE
|
||||||
| Type::SBYTE
|
| Type::SBYTE
|
||||||
| Type::ASCII
|
| Type::ASCII
|
||||||
@@ -447,11 +432,11 @@ impl Entry {
|
|||||||
Type::BYTE => Byte(self.offset[0]),
|
Type::BYTE => Byte(self.offset[0]),
|
||||||
Type::SBYTE => SignedByte(self.offset[0] as i8),
|
Type::SBYTE => SignedByte(self.offset[0] as i8),
|
||||||
Type::UNDEFINED => Byte(self.offset[0]),
|
Type::UNDEFINED => Byte(self.offset[0]),
|
||||||
Type::SHORT => Short(self.r(bo).read_u16()?),
|
Type::SHORT => Short(self.offset_field_reader(bo).read_u16()?),
|
||||||
Type::SSHORT => SignedShort(self.r(bo).read_i16()?),
|
Type::SSHORT => SignedShort(self.offset_field_reader(bo).read_i16()?),
|
||||||
Type::LONG => Unsigned(self.r(bo).read_u32()?),
|
Type::LONG => Unsigned(self.offset_field_reader(bo).read_u32()?),
|
||||||
Type::SLONG => Signed(self.r(bo).read_i32()?),
|
Type::SLONG => Signed(self.offset_field_reader(bo).read_i32()?),
|
||||||
Type::FLOAT => Float(self.r(bo).read_f32()?),
|
Type::FLOAT => Float(self.offset_field_reader(bo).read_f32()?),
|
||||||
Type::ASCII => {
|
Type::ASCII => {
|
||||||
if self.offset[0] == 0 {
|
if self.offset[0] == 0 {
|
||||||
Ascii("".to_string())
|
Ascii("".to_string())
|
||||||
@@ -460,28 +445,28 @@ impl Entry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Type::LONG8 => {
|
Type::LONG8 => {
|
||||||
reader.goto_offset(self.r(bo).read_u32()?.into())?;
|
reader.goto_offset(self.offset_field_reader(bo).read_u32()?.into())?;
|
||||||
UnsignedBig(reader.read_u64()?)
|
UnsignedBig(reader.read_u64()?)
|
||||||
}
|
}
|
||||||
Type::SLONG8 => {
|
Type::SLONG8 => {
|
||||||
reader.goto_offset(self.r(bo).read_u32()?.into())?;
|
reader.goto_offset(self.offset_field_reader(bo).read_u32()?.into())?;
|
||||||
SignedBig(reader.read_i64()?)
|
SignedBig(reader.read_i64()?)
|
||||||
}
|
}
|
||||||
Type::DOUBLE => {
|
Type::DOUBLE => {
|
||||||
reader.goto_offset(self.r(bo).read_u32()?.into())?;
|
reader.goto_offset(self.offset_field_reader(bo).read_u32()?.into())?;
|
||||||
Double(reader.read_f64()?)
|
Double(reader.read_f64()?)
|
||||||
}
|
}
|
||||||
Type::RATIONAL => {
|
Type::RATIONAL => {
|
||||||
reader.goto_offset(self.r(bo).read_u32()?.into())?;
|
reader.goto_offset(self.offset_field_reader(bo).read_u32()?.into())?;
|
||||||
Rational(reader.read_u32()?, reader.read_u32()?)
|
Rational(reader.read_u32()?, reader.read_u32()?)
|
||||||
}
|
}
|
||||||
Type::SRATIONAL => {
|
Type::SRATIONAL => {
|
||||||
reader.goto_offset(self.r(bo).read_u32()?.into())?;
|
reader.goto_offset(self.offset_field_reader(bo).read_u32()?.into())?;
|
||||||
SRational(reader.read_i32()?, reader.read_i32()?)
|
SRational(reader.read_i32()?, reader.read_i32()?)
|
||||||
}
|
}
|
||||||
Type::IFD => Ifd(self.r(bo).read_u32()?),
|
Type::IFD => Ifd(self.offset_field_reader(bo).read_u32()?),
|
||||||
Type::IFD8 => {
|
Type::IFD8 => {
|
||||||
reader.goto_offset(self.r(bo).read_u32()?.into())?;
|
reader.goto_offset(self.offset_field_reader(bo).read_u32()?.into())?;
|
||||||
IfdBig(reader.read_u64()?)
|
IfdBig(reader.read_u64()?)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -512,7 +497,7 @@ impl Entry {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
Type::SHORT => {
|
Type::SHORT => {
|
||||||
let mut r = self.r(bo);
|
let mut r = self.offset_field_reader(bo);
|
||||||
let mut v = Vec::new();
|
let mut v = Vec::new();
|
||||||
for _ in 0..self.count {
|
for _ in 0..self.count {
|
||||||
v.push(Short(r.read_u16()?));
|
v.push(Short(r.read_u16()?));
|
||||||
@@ -520,7 +505,7 @@ impl Entry {
|
|||||||
return Ok(List(v));
|
return Ok(List(v));
|
||||||
}
|
}
|
||||||
Type::SSHORT => {
|
Type::SSHORT => {
|
||||||
let mut r = self.r(bo);
|
let mut r = self.offset_field_reader(bo);
|
||||||
let mut v = Vec::new();
|
let mut v = Vec::new();
|
||||||
for _ in 0..self.count {
|
for _ in 0..self.count {
|
||||||
v.push(SignedShort(r.read_i16()?));
|
v.push(SignedShort(r.read_i16()?));
|
||||||
@@ -528,7 +513,7 @@ impl Entry {
|
|||||||
return Ok(List(v));
|
return Ok(List(v));
|
||||||
}
|
}
|
||||||
Type::LONG => {
|
Type::LONG => {
|
||||||
let mut r = self.r(bo);
|
let mut r = self.offset_field_reader(bo);
|
||||||
let mut v = Vec::new();
|
let mut v = Vec::new();
|
||||||
for _ in 0..self.count {
|
for _ in 0..self.count {
|
||||||
v.push(Unsigned(r.read_u32()?));
|
v.push(Unsigned(r.read_u32()?));
|
||||||
@@ -536,7 +521,7 @@ impl Entry {
|
|||||||
return Ok(List(v));
|
return Ok(List(v));
|
||||||
}
|
}
|
||||||
Type::SLONG => {
|
Type::SLONG => {
|
||||||
let mut r = self.r(bo);
|
let mut r = self.offset_field_reader(bo);
|
||||||
let mut v = Vec::new();
|
let mut v = Vec::new();
|
||||||
for _ in 0..self.count {
|
for _ in 0..self.count {
|
||||||
v.push(Signed(r.read_i32()?));
|
v.push(Signed(r.read_i32()?));
|
||||||
@@ -544,7 +529,7 @@ impl Entry {
|
|||||||
return Ok(List(v));
|
return Ok(List(v));
|
||||||
}
|
}
|
||||||
Type::FLOAT => {
|
Type::FLOAT => {
|
||||||
let mut r = self.r(bo);
|
let mut r = self.offset_field_reader(bo);
|
||||||
let mut v = Vec::new();
|
let mut v = Vec::new();
|
||||||
for _ in 0..self.count {
|
for _ in 0..self.count {
|
||||||
v.push(Float(r.read_f32()?));
|
v.push(Float(r.read_f32()?));
|
||||||
@@ -552,7 +537,7 @@ impl Entry {
|
|||||||
return Ok(List(v));
|
return Ok(List(v));
|
||||||
}
|
}
|
||||||
Type::IFD => {
|
Type::IFD => {
|
||||||
let mut r = self.r(bo);
|
let mut r = self.offset_field_reader(bo);
|
||||||
let mut v = Vec::new();
|
let mut v = Vec::new();
|
||||||
for _ in 0..self.count {
|
for _ in 0..self.count {
|
||||||
v.push(Ifd(r.read_u32()?));
|
v.push(Ifd(r.read_u32()?));
|
||||||
@@ -571,74 +556,149 @@ impl Entry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Case 4: there is more than one value, and it doesn't fit in the offset field.
|
// 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_ {
|
match self.type_ {
|
||||||
// TODO check if this could give wrong results
|
Type::BYTE | Type::UNDEFINED => {
|
||||||
// at a different endianess of file/computer.
|
v = Self::vec_with_capacity(self.count, limits)?;
|
||||||
Type::BYTE => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
self.decode_values(self.count, self.type_, reader, |bytes| {
|
||||||
let mut buf = [0; 1];
|
v.extend(bytes.iter().copied().map(Byte))
|
||||||
reader.inner().read_exact(&mut buf)?;
|
})
|
||||||
Ok(UnsignedBig(u64::from(buf[0])))
|
}
|
||||||
}),
|
Type::SBYTE => {
|
||||||
Type::SBYTE => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
v = Self::vec_with_capacity(self.count, limits)?;
|
||||||
Ok(SignedBig(i64::from(reader.read_i8()?)))
|
self.decode_values(self.count, self.type_, reader, |bytes| {
|
||||||
}),
|
v.extend(bytes.iter().copied().map(|v| SignedByte(v as i8)))
|
||||||
Type::SHORT => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
})
|
||||||
Ok(Short(reader.read_u16()?))
|
}
|
||||||
}),
|
Type::SHORT => {
|
||||||
Type::SSHORT => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
v = Self::vec_with_capacity(self.count, limits)?;
|
||||||
Ok(SignedShort(reader.read_i16()?))
|
self.decode_values(self.count, self.type_, reader, |bytes| {
|
||||||
}),
|
v.extend(
|
||||||
Type::LONG => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
bytes
|
||||||
Ok(Unsigned(reader.read_u32()?))
|
.chunks_exact(2)
|
||||||
}),
|
.map(|ch| Short(u16::from_ne_bytes(ch.try_into().unwrap()))),
|
||||||
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| {
|
Type::SSHORT => {
|
||||||
Ok(Float(reader.read_f32()?))
|
v = Self::vec_with_capacity(self.count, limits)?;
|
||||||
}),
|
self.decode_values(self.count, self.type_, reader, |bytes| {
|
||||||
Type::DOUBLE => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
v.extend(
|
||||||
Ok(Double(reader.read_f64()?))
|
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()))),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
Type::RATIONAL => {
|
Type::RATIONAL => {
|
||||||
self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
v = Self::vec_with_capacity(self.count, limits)?;
|
||||||
Ok(Rational(reader.read_u32()?, reader.read_u32()?))
|
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()),
|
||||||
|
)
|
||||||
|
}))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
Type::SRATIONAL => {
|
Type::SRATIONAL => {
|
||||||
self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
v = Self::vec_with_capacity(self.count, limits)?;
|
||||||
Ok(SRational(reader.read_i32()?, reader.read_i32()?))
|
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()),
|
||||||
|
)
|
||||||
|
}))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
Type::LONG8 => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
Type::LONG8 => {
|
||||||
Ok(UnsignedBig(reader.read_u64()?))
|
v = Self::vec_with_capacity(self.count, limits)?;
|
||||||
}),
|
self.decode_values(self.count, self.type_, reader, |bytes| {
|
||||||
Type::SLONG8 => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
v.extend(
|
||||||
Ok(SignedBig(reader.read_i64()?))
|
bytes
|
||||||
}),
|
.chunks_exact(8)
|
||||||
Type::IFD => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
.map(|ch| UnsignedBig(u64::from_ne_bytes(ch.try_into().unwrap()))),
|
||||||
Ok(Ifd(reader.read_u32()?))
|
)
|
||||||
}),
|
})
|
||||||
Type::IFD8 => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
}
|
||||||
Ok(IfdBig(reader.read_u64()?))
|
Type::SLONG8 => {
|
||||||
}),
|
v = Self::vec_with_capacity(self.count, limits)?;
|
||||||
Type::UNDEFINED => {
|
self.decode_values(self.count, self.type_, reader, |bytes| {
|
||||||
self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
v.extend(
|
||||||
let mut buf = [0; 1];
|
bytes
|
||||||
reader.inner().read_exact(&mut buf)?;
|
.chunks_exact(8)
|
||||||
Ok(Byte(buf[0]))
|
.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::ASCII => {
|
Type::ASCII => {
|
||||||
let n = usize::try_from(self.count)?;
|
let n = usize::try_from(self.count)?;
|
||||||
if n > limits.decoding_buffer_size {
|
|
||||||
return Err(TiffError::LimitsExceeded);
|
|
||||||
}
|
|
||||||
|
|
||||||
if bigtiff {
|
if n > limits.decoding_buffer_size {
|
||||||
reader.goto_offset(self.r(bo).read_u64()?)?
|
return Err(dbg!(TiffError::LimitsExceeded));
|
||||||
} else {
|
|
||||||
reader.goto_offset(self.r(bo).read_u32()?.into())?
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut out = vec![0; n];
|
let mut out = vec![0; n];
|
||||||
@@ -647,43 +707,81 @@ impl Entry {
|
|||||||
if let Some(first) = out.iter().position(|&b| b == 0) {
|
if let Some(first) = out.iter().position(|&b| b == 0) {
|
||||||
out.truncate(first);
|
out.truncate(first);
|
||||||
}
|
}
|
||||||
Ok(Ascii(String::from_utf8(out)?))
|
|
||||||
}
|
return Ok(Ascii(String::from_utf8(out)?));
|
||||||
}
|
}
|
||||||
|
}?;
|
||||||
|
|
||||||
|
Ok(List(v))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
fn vec_with_capacity(
|
||||||
fn decode_offset<R, F>(
|
|
||||||
&self,
|
|
||||||
value_count: u64,
|
value_count: u64,
|
||||||
bo: ByteOrder,
|
|
||||||
bigtiff: bool,
|
|
||||||
limits: &super::Limits,
|
limits: &super::Limits,
|
||||||
reader: &mut EndianReader<R>,
|
) -> Result<Vec<Value>, TiffError> {
|
||||||
decode_fn: F,
|
|
||||||
) -> TiffResult<Value>
|
|
||||||
where
|
|
||||||
R: Read + Seek,
|
|
||||||
F: Fn(&mut EndianReader<R>) -> TiffResult<Value>,
|
|
||||||
{
|
|
||||||
let value_count = usize::try_from(value_count)?;
|
let value_count = usize::try_from(value_count)?;
|
||||||
|
|
||||||
if value_count > limits.decoding_buffer_size / mem::size_of::<Value>() {
|
if value_count > limits.decoding_buffer_size / mem::size_of::<Value>() {
|
||||||
return Err(TiffError::LimitsExceeded);
|
return Err(TiffError::LimitsExceeded);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut v = Vec::with_capacity(value_count);
|
Ok(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 {
|
let offset = if bigtiff {
|
||||||
self.r(bo).read_u64()?
|
self.offset_field_reader(bo).read_u64()?
|
||||||
} else {
|
} else {
|
||||||
self.r(bo).read_u32()?.into()
|
self.offset_field_reader(bo).read_u32()?.into()
|
||||||
};
|
};
|
||||||
|
|
||||||
reader.goto_offset(offset)?;
|
reader.goto_offset(offset)?;
|
||||||
|
|
||||||
for _ in 0..value_count {
|
Ok(())
|
||||||
v.push(decode_fn(reader)?)
|
|
||||||
}
|
}
|
||||||
Ok(List(v))
|
|
||||||
|
#[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);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+26
-9
@@ -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;
|
||||||
@@ -144,12 +145,15 @@ impl Image {
|
|||||||
.map(SampleFormat::from_u16_exhaustive)
|
.map(SampleFormat::from_u16_exhaustive)
|
||||||
.collect();
|
.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.
|
// TODO: for now, only homogenous formats across samples are supported.
|
||||||
if !sample_format.windows(2).all(|s| s[0] == s[1]) {
|
if !sample_format.iter().all(|&s| s == format) {
|
||||||
return Err(TiffUnsupportedError::UnsupportedSampleFormat(sample_format).into());
|
return Err(TiffUnsupportedError::UnsupportedSampleFormat(sample_format).into());
|
||||||
}
|
}
|
||||||
|
format
|
||||||
sample_format[0]
|
|
||||||
}
|
}
|
||||||
None => SampleFormat::Uint,
|
None => SampleFormat::Uint,
|
||||||
};
|
};
|
||||||
@@ -343,6 +347,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)),
|
||||||
@@ -384,7 +391,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),
|
||||||
@@ -436,13 +445,14 @@ impl Image {
|
|||||||
let mut jpeg_data = Vec::new();
|
let mut jpeg_data = Vec::new();
|
||||||
jpeg_reader.read_to_end(&mut jpeg_data)?;
|
jpeg_reader.read_to_end(&mut jpeg_data)?;
|
||||||
|
|
||||||
let mut decoder = zune_jpeg::JpegDecoder::new(jpeg_data);
|
let mut decoder =
|
||||||
|
zune_jpeg::JpegDecoder::new(zune_core::bytestream::ZCursor::new(jpeg_data));
|
||||||
let mut options: zune_core::options::DecoderOptions = Default::default();
|
let mut options: zune_core::options::DecoderOptions = Default::default();
|
||||||
|
|
||||||
// Disable color conversion by setting the output colorspace to the input
|
// Disable color conversion by setting the output colorspace to the input
|
||||||
// colorspace.
|
// colorspace.
|
||||||
decoder.decode_headers()?;
|
decoder.decode_headers()?;
|
||||||
if let Some(colorspace) = decoder.get_input_colorspace() {
|
if let Some(colorspace) = decoder.input_colorspace() {
|
||||||
options = options.jpeg_set_out_colorspace(colorspace);
|
options = options.jpeg_set_out_colorspace(colorspace);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -452,6 +462,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),
|
||||||
@@ -658,6 +674,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 {
|
||||||
@@ -674,7 +691,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
|
||||||
@@ -691,7 +708,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 {
|
||||||
@@ -713,7 +730,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)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+35
-30
@@ -475,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) => {
|
||||||
@@ -512,42 +523,31 @@ 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.
|
||||||
fn fix_endianness(buf: &mut [u8], byte_order: ByteOrder, bit_depth: u8) {
|
fn fix_endianness(buf: &mut [u8], byte_order: ByteOrder, bit_depth: u8) {
|
||||||
match byte_order {
|
let host = ByteOrder::native();
|
||||||
ByteOrder::LittleEndian => match bit_depth {
|
|
||||||
0..=8 => {}
|
let class = match bit_depth {
|
||||||
9..=16 => buf.chunks_exact_mut(2).for_each(|v| {
|
0..=8 => crate::tags::EndianBytes::One,
|
||||||
v.copy_from_slice(&u16::from_le_bytes((*v).try_into().unwrap()).to_ne_bytes())
|
9..=16 => crate::tags::EndianBytes::Two,
|
||||||
}),
|
17..=32 => crate::tags::EndianBytes::Four,
|
||||||
17..=32 => buf.chunks_exact_mut(4).for_each(|v| {
|
_ => crate::tags::EndianBytes::Eight,
|
||||||
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> {
|
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)?;
|
||||||
@@ -731,6 +731,11 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the byte_order of the file.
|
/// 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 {
|
pub fn byte_order(&self) -> ByteOrder {
|
||||||
self.value_reader.reader.byte_order
|
self.value_reader.reader.byte_order
|
||||||
}
|
}
|
||||||
|
|||||||
+101
-20
@@ -1,15 +1,7 @@
|
|||||||
//! 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.
|
pub use crate::tags::ByteOrder;
|
||||||
#[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.
|
/// Reader that is aware of the byte order.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -46,17 +38,6 @@ 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
|
/// Reads an i16
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn read_i16(&mut self) -> Result<i16, io::Error> {
|
pub fn read_i16(&mut self) -> Result<i16, io::Error> {
|
||||||
@@ -276,6 +257,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::*;
|
||||||
|
|||||||
+2
-7
@@ -37,8 +37,9 @@ pub type Predictor = crate::tags::Predictor;
|
|||||||
#[cfg(feature = "deflate")]
|
#[cfg(feature = "deflate")]
|
||||||
pub type DeflateLevel = compression::DeflateLevel;
|
pub type DeflateLevel = compression::DeflateLevel;
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq)]
|
#[derive(Clone, Copy, PartialEq, Default)]
|
||||||
pub enum Compression {
|
pub enum Compression {
|
||||||
|
#[default]
|
||||||
Uncompressed,
|
Uncompressed,
|
||||||
#[cfg(feature = "lzw")]
|
#[cfg(feature = "lzw")]
|
||||||
Lzw,
|
Lzw,
|
||||||
@@ -47,12 +48,6 @@ pub enum Compression {
|
|||||||
Packbits,
|
Packbits,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Compression {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::Uncompressed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Compression {
|
impl Compression {
|
||||||
fn tag(&self) -> CompressionMethod {
|
fn tag(&self) -> CompressionMethod {
|
||||||
match self {
|
match self {
|
||||||
|
|||||||
+115
@@ -193,6 +193,45 @@ 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! {
|
tags! {
|
||||||
/// See [TIFF compression tags](https://www.awaresystems.be/imaging/tiff/tifftags/compression.html)
|
/// See [TIFF compression tags](https://www.awaresystems.be/imaging/tiff/tifftags/compression.html)
|
||||||
/// for reference.
|
/// for reference.
|
||||||
@@ -271,3 +310,79 @@ pub enum SampleFormat(u16) unknown(
|
|||||||
Void = 4,
|
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,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.
Binary file not shown.
Reference in new issue
Block a user