Compare commits
27
Commits
checkpoint
..
v0.6.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
49f9b3941d | ||
|
|
fc80c5e3dd | ||
|
|
95f0a34bcc | ||
|
|
3e042df438 | ||
|
|
5346a441ae | ||
|
|
76173069a3 | ||
|
|
6cdd340933 | ||
|
|
ef004cee48 | ||
|
|
6d7d92c605 | ||
|
|
53437dd7b4 | ||
|
|
0c06d32ee4 | ||
|
|
4d8c64bf1e | ||
|
|
7da8f01f75 | ||
|
|
67105fe4ec | ||
|
|
1b38ff9410 | ||
|
|
c9703dcb8a | ||
|
|
e2caf86f6a | ||
|
|
923b37d647 | ||
|
|
9f4799912f | ||
|
|
4b218be4ac | ||
|
|
9a0041286e | ||
|
|
0a4b51055e | ||
|
|
96d6d52830 | ||
|
|
62a1a500a5 | ||
|
|
3953bd31e1 | ||
|
|
83004c8344 | ||
|
|
8ab9d30eff |
No files matched your search
+17
@@ -1,3 +1,20 @@
|
||||
# Version 0.6.1
|
||||
|
||||
New features:
|
||||
* Support for reading `u16` and ascii string tags.
|
||||
* Added `Limits::unlimited` for disabling all limits.
|
||||
* Added `ImageEncoder::rows_per_strip` to overwrite the default.
|
||||
|
||||
Changes:
|
||||
* The default strip size for chunked encoding is now 1MB, up from 8KB. This
|
||||
should lead to more efficient decoding and compression.
|
||||
|
||||
Fixes:
|
||||
* Fixed a bug where LZW compressed strips could not be decoded, instead
|
||||
returning an error `Inconsistent sizes encountered`.
|
||||
* Reading a tag with a complex type and a single value returns the proper Value
|
||||
variant, instead of a vector with one entry.
|
||||
|
||||
# Version 0.6.0
|
||||
|
||||
New features:
|
||||
|
||||
+2
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tiff"
|
||||
version = "0.6.0"
|
||||
version = "0.6.1"
|
||||
authors = [
|
||||
"ccgn",
|
||||
"bvssvni <bvssvni@gmail.com>",
|
||||
@@ -15,6 +15,7 @@ license = "MIT"
|
||||
description = "TIFF decoding and encoding library in pure Rust"
|
||||
repository = "https://github.com/image-rs/image-tiff"
|
||||
exclude = ["tests/images/*", "tests/fuzz_images/*"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
weezl = "0.1.0"
|
||||
|
||||
+235
-161
@@ -6,18 +6,19 @@ use std::io::{self, Read, Seek};
|
||||
use std::mem;
|
||||
|
||||
use super::stream::{ByteOrder, EndianReader, SmartReader};
|
||||
use tags::{Tag, Type};
|
||||
use {TiffError, TiffFormatError, TiffResult, TiffUnsupportedError};
|
||||
use crate::tags::{Tag, Type};
|
||||
use crate::{TiffError, TiffFormatError, TiffResult};
|
||||
|
||||
use self::Value::{
|
||||
Ascii, Byte, Double, Float, List, Rational, RationalBig, SRational, SRationalBig, Signed,
|
||||
SignedBig, Unsigned, UnsignedBig,
|
||||
Ascii, Byte, Double, Float, List, Rational, RationalBig, SRational, SRationalBig, Short,
|
||||
Signed, SignedBig, Unsigned, UnsignedBig,
|
||||
};
|
||||
|
||||
#[allow(unused_qualifications)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Value {
|
||||
Byte(u8),
|
||||
Short(u16),
|
||||
Signed(i32),
|
||||
SignedBig(i64),
|
||||
Unsigned(u32),
|
||||
@@ -42,8 +43,20 @@ impl Value {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_u16(self) -> TiffResult<u16> {
|
||||
match self {
|
||||
Short(val) => Ok(val),
|
||||
Unsigned(val) => Ok(u16::try_from(val)?),
|
||||
UnsignedBig(val) => Ok(u16::try_from(val)?),
|
||||
val => Err(TiffError::FormatError(
|
||||
TiffFormatError::UnsignedIntegerExpected(val),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_u32(self) -> TiffResult<u32> {
|
||||
match self {
|
||||
Short(val) => Ok(val.into()),
|
||||
Unsigned(val) => Ok(val),
|
||||
UnsignedBig(val) => Ok(u32::try_from(val)?),
|
||||
val => Err(TiffError::FormatError(
|
||||
@@ -64,6 +77,7 @@ impl Value {
|
||||
|
||||
pub fn into_u64(self) -> TiffResult<u64> {
|
||||
match self {
|
||||
Short(val) => Ok(val.into()),
|
||||
Unsigned(val) => Ok(val.into()),
|
||||
UnsignedBig(val) => Ok(val),
|
||||
val => Err(TiffError::FormatError(
|
||||
@@ -100,6 +114,15 @@ impl Value {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_string(self) -> TiffResult<String> {
|
||||
match self {
|
||||
Ascii(val) => Ok(val),
|
||||
val => Err(TiffError::FormatError(
|
||||
TiffFormatError::SignedIntegerExpected(val),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_u32_vec(self) -> TiffResult<Vec<u32>> {
|
||||
match self {
|
||||
List(vec) => {
|
||||
@@ -139,6 +162,22 @@ impl Value {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_u16_vec(self) -> TiffResult<Vec<u16>> {
|
||||
match self {
|
||||
List(vec) => {
|
||||
let mut new_vec = Vec::with_capacity(vec.len());
|
||||
for v in vec {
|
||||
new_vec.push(v.into_u16()?)
|
||||
}
|
||||
Ok(new_vec)
|
||||
}
|
||||
Short(val) => Ok(vec![val]),
|
||||
val => Err(TiffError::FormatError(
|
||||
TiffFormatError::UnsignedIntegerExpected(val),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_i32_vec(self) -> TiffResult<Vec<i32>> {
|
||||
match self {
|
||||
List(vec) => {
|
||||
@@ -293,172 +332,207 @@ impl Entry {
|
||||
limits: &super::Limits,
|
||||
decoder: &mut super::Decoder<R>,
|
||||
) -> TiffResult<Value> {
|
||||
// Case 1: there are no values so we can return immediately.
|
||||
if self.count == 0 {
|
||||
return Ok(List(Vec::new()));
|
||||
}
|
||||
|
||||
let bo = decoder.byte_order();
|
||||
let bigtiff_value = if decoder.bigtiff {
|
||||
match (self.type_, self.count) {
|
||||
(Type::BYTE, 5) => Some(offset_to_bytes(5, self)?),
|
||||
(Type::BYTE, 6) => Some(offset_to_bytes(6, self)?),
|
||||
(Type::BYTE, 7) => Some(offset_to_bytes(7, self)?),
|
||||
(Type::BYTE, 8) => Some(offset_to_bytes(8, self)?),
|
||||
(Type::SBYTE, 5) => Some(offset_to_sbytes(5, self)?),
|
||||
(Type::SBYTE, 6) => Some(offset_to_sbytes(6, self)?),
|
||||
(Type::SBYTE, 7) => Some(offset_to_sbytes(7, self)?),
|
||||
(Type::SBYTE, 8) => Some(offset_to_sbytes(8, self)?),
|
||||
(Type::SHORT, 3) => {
|
||||
let mut r = self.r(bo);
|
||||
Some(List(vec![
|
||||
Unsigned(u32::from(r.read_u16()?)),
|
||||
Unsigned(u32::from(r.read_u16()?)),
|
||||
Unsigned(u32::from(r.read_u16()?)),
|
||||
]))
|
||||
}
|
||||
(Type::SSHORT, 3) => {
|
||||
let mut r = self.r(bo);
|
||||
Some(List(vec![
|
||||
Signed(i32::from(r.read_i16()?)),
|
||||
Signed(i32::from(r.read_i16()?)),
|
||||
Signed(i32::from(r.read_i16()?)),
|
||||
]))
|
||||
}
|
||||
(Type::SHORT, 4) => {
|
||||
let mut r = self.r(bo);
|
||||
Some(List(vec![
|
||||
Unsigned(u32::from(r.read_u16()?)),
|
||||
Unsigned(u32::from(r.read_u16()?)),
|
||||
Unsigned(u32::from(r.read_u16()?)),
|
||||
Unsigned(u32::from(r.read_u16()?)),
|
||||
]))
|
||||
}
|
||||
(Type::SSHORT, 4) => {
|
||||
let mut r = self.r(bo);
|
||||
Some(List(vec![
|
||||
Signed(i32::from(r.read_i16()?)),
|
||||
Signed(i32::from(r.read_i16()?)),
|
||||
Signed(i32::from(r.read_i16()?)),
|
||||
Signed(i32::from(r.read_i16()?)),
|
||||
]))
|
||||
}
|
||||
(Type::LONG, 2) => {
|
||||
let mut r = self.r(bo);
|
||||
Some(List(vec![Unsigned(r.read_u32()?), Unsigned(r.read_u32()?)]))
|
||||
}
|
||||
(Type::SLONG, 2) => {
|
||||
let mut r = self.r(bo);
|
||||
Some(List(vec![Signed(r.read_i32()?), Signed(r.read_i32()?)]))
|
||||
}
|
||||
(Type::RATIONAL, 1) => {
|
||||
let mut r = self.r(bo);
|
||||
let numerator = r.read_u32()?;
|
||||
let denominator = r.read_u32()?;
|
||||
Some(Rational(numerator, denominator))
|
||||
}
|
||||
(Type::SRATIONAL, 1) => {
|
||||
let mut r = self.r(bo);
|
||||
let numerator = r.read_i32()?;
|
||||
let denominator = r.read_i32()?;
|
||||
Some(SRational(numerator, denominator))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let value_bytes = self.count
|
||||
* match self.type_ {
|
||||
Type::BYTE | Type::SBYTE | Type::ASCII | Type::UNDEFINED => 1,
|
||||
Type::SHORT | Type::SSHORT => 2,
|
||||
Type::LONG | Type::SLONG | Type::FLOAT => 4,
|
||||
Type::LONG8 | Type::DOUBLE | Type::RATIONAL | Type::SRATIONAL => 8,
|
||||
Type::__NonExhaustive => unreachable!(),
|
||||
};
|
||||
|
||||
if let Some(v) = bigtiff_value {
|
||||
Ok(v)
|
||||
} else {
|
||||
match (self.type_, self.count) {
|
||||
// TODO check if this could give wrong results
|
||||
// at a different endianess of file/computer.
|
||||
(Type::BYTE, 1) => Ok(Unsigned(u32::from(self.offset[0]))),
|
||||
(Type::BYTE, 2) => offset_to_bytes(2, self),
|
||||
(Type::BYTE, 3) => offset_to_bytes(3, self),
|
||||
(Type::BYTE, 4) => offset_to_bytes(4, self),
|
||||
(Type::BYTE, n) => self.decode_offset(n, bo, limits, decoder, |decoder| {
|
||||
Ok(UnsignedBig(u64::from(decoder.read_byte()?)))
|
||||
}),
|
||||
(Type::SBYTE, 1) => Ok(Signed(i32::from(self.offset[0] as i8))),
|
||||
(Type::SBYTE, 2) => offset_to_sbytes(2, self),
|
||||
(Type::SBYTE, 3) => offset_to_sbytes(3, self),
|
||||
(Type::SBYTE, 4) => offset_to_sbytes(4, self),
|
||||
(Type::SBYTE, n) => self.decode_offset(n, bo, limits, decoder, |decoder| {
|
||||
Ok(SignedBig(i64::from(decoder.read_byte()? as i8)))
|
||||
}),
|
||||
(Type::SHORT, 1) => Ok(Unsigned(u32::from(self.r(bo).read_u16()?))),
|
||||
(Type::SSHORT, 1) => Ok(Signed(i32::from(self.r(bo).read_i16()?))),
|
||||
(Type::SHORT, 2) => {
|
||||
let mut r = self.r(bo);
|
||||
Ok(List(vec![
|
||||
Unsigned(u32::from(r.read_u16()?)),
|
||||
Unsigned(u32::from(r.read_u16()?)),
|
||||
]))
|
||||
}
|
||||
(Type::SSHORT, 2) => {
|
||||
let mut r = self.r(bo);
|
||||
Ok(List(vec![
|
||||
Signed(i32::from(r.read_i16()?)),
|
||||
Signed(i32::from(r.read_i16()?)),
|
||||
]))
|
||||
}
|
||||
(Type::SHORT, n) => self.decode_offset(n, bo, limits, decoder, |decoder| {
|
||||
Ok(UnsignedBig(u64::from(decoder.read_short()?)))
|
||||
}),
|
||||
(Type::SSHORT, n) => self.decode_offset(n, bo, limits, decoder, |decoder| {
|
||||
Ok(SignedBig(i64::from(decoder.read_sshort()?)))
|
||||
}),
|
||||
(Type::LONG, 1) => Ok(Unsigned(self.r(bo).read_u32()?)),
|
||||
(Type::SLONG, 1) => Ok(Signed(self.r(bo).read_i32()?)),
|
||||
(Type::LONG, n) => self.decode_offset(n, bo, limits, decoder, |decoder| {
|
||||
Ok(Unsigned(decoder.read_long()?))
|
||||
}),
|
||||
(Type::SLONG, n) => self.decode_offset(n, bo, limits, decoder, |decoder| {
|
||||
Ok(Signed(decoder.read_slong()?))
|
||||
}),
|
||||
(Type::FLOAT, n) => self.decode_offset(n, bo, limits, decoder, |decoder| {
|
||||
Ok(Float(decoder.read_float()?))
|
||||
}),
|
||||
(Type::DOUBLE, n) => self.decode_offset(n, bo, limits, decoder, |decoder| {
|
||||
Ok(Double(decoder.read_double()?))
|
||||
}),
|
||||
(Type::RATIONAL, n) => self.decode_offset(n, bo, limits, decoder, |decoder| {
|
||||
Ok(Rational(decoder.read_long()?, decoder.read_long()?))
|
||||
}),
|
||||
(Type::SRATIONAL, n) => self.decode_offset(n, bo, limits, decoder, |decoder| {
|
||||
Ok(SRational(decoder.read_slong()?, decoder.read_slong()?))
|
||||
}),
|
||||
(Type::LONG8, n) => self.decode_offset(n, bo, limits, decoder, |decoder| {
|
||||
Ok(UnsignedBig(decoder.read_long8()?))
|
||||
}),
|
||||
(Type::ASCII, n) => {
|
||||
let n = usize::try_from(n)?;
|
||||
if n > limits.decoding_buffer_size {
|
||||
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 decoder.bigtiff && value_bytes > 4 && value_bytes <= 8 {
|
||||
return Ok(match self.type_ {
|
||||
Type::LONG8 => UnsignedBig(self.r(bo).read_u64()?),
|
||||
Type::DOUBLE => Double(self.r(bo).read_f64()?),
|
||||
Type::RATIONAL => {
|
||||
let mut r = self.r(bo);
|
||||
Rational(r.read_u32()?, r.read_u32()?)
|
||||
}
|
||||
Type::SRATIONAL => {
|
||||
let mut r = self.r(bo);
|
||||
SRational(r.read_i32()?, r.read_i32()?)
|
||||
}
|
||||
Type::BYTE
|
||||
| Type::SBYTE
|
||||
| Type::ASCII
|
||||
| Type::UNDEFINED
|
||||
| Type::SHORT
|
||||
| Type::SSHORT
|
||||
| Type::LONG
|
||||
| Type::SLONG
|
||||
| Type::FLOAT => unreachable!(),
|
||||
Type::__NonExhaustive => unreachable!(),
|
||||
});
|
||||
}
|
||||
|
||||
if (n <= 4 && !decoder.bigtiff) || (n <= 8 && decoder.bigtiff) {
|
||||
let mut buf = vec![0; n];
|
||||
self.r(bo).read_exact(&mut buf)?;
|
||||
let v = String::from_utf8(buf)?;
|
||||
let v = v.trim_matches(char::from(0));
|
||||
Ok(Ascii(v.into()))
|
||||
// 2b: the value is at most 4 bytes or doesn't fit in the offset field.
|
||||
return Ok(match self.type_ {
|
||||
Type::BYTE => Unsigned(u32::from(self.offset[0])),
|
||||
Type::SBYTE => Signed(i32::from(self.offset[0] as i8)),
|
||||
Type::UNDEFINED => Byte(self.offset[0]),
|
||||
Type::SHORT => Unsigned(u32::from(self.r(bo).read_u16()?)),
|
||||
Type::SSHORT => Signed(i32::from(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())
|
||||
} else {
|
||||
if decoder.bigtiff {
|
||||
decoder.goto_offset_u64(self.r(bo).read_u64()?)?
|
||||
} else {
|
||||
decoder.goto_offset(self.r(bo).read_u32()?)?
|
||||
}
|
||||
let string = decoder.read_string(n)?;
|
||||
Ok(Ascii(string))
|
||||
return Err(TiffError::FormatError(TiffFormatError::InvalidTag));
|
||||
}
|
||||
}
|
||||
(Type::UNDEFINED, n) => self.decode_offset(n, bo, limits, decoder, |decoder| {
|
||||
Ok(Byte(u8::from(decoder.read_byte()?)))
|
||||
}),
|
||||
_ => Err(TiffError::UnsupportedError(
|
||||
TiffUnsupportedError::UnsupportedDataType,
|
||||
)),
|
||||
Type::LONG8 => {
|
||||
decoder.goto_offset(self.r(bo).read_u32()?)?;
|
||||
UnsignedBig(decoder.read_long8()?)
|
||||
}
|
||||
Type::DOUBLE => {
|
||||
decoder.goto_offset(self.r(bo).read_u32()?)?;
|
||||
Double(decoder.read_double()?)
|
||||
}
|
||||
Type::RATIONAL => {
|
||||
decoder.goto_offset(self.r(bo).read_u32()?)?;
|
||||
Rational(decoder.read_long()?, decoder.read_long()?)
|
||||
}
|
||||
Type::SRATIONAL => {
|
||||
decoder.goto_offset(self.r(bo).read_u32()?)?;
|
||||
SRational(decoder.read_slong()?, decoder.read_slong()?)
|
||||
}
|
||||
Type::__NonExhaustive => unreachable!(),
|
||||
});
|
||||
}
|
||||
|
||||
// Case 3: There is more than one value, but it fits in the offset field.
|
||||
if value_bytes <= 4 || decoder.bigtiff && value_bytes <= 8 {
|
||||
match self.type_ {
|
||||
Type::BYTE => return Ok(offset_to_bytes(self.count as usize, self)?),
|
||||
Type::SBYTE => return Ok(offset_to_sbytes(self.count as usize, self)?),
|
||||
Type::ASCII => {
|
||||
let mut buf = vec![0; self.count as usize];
|
||||
self.r(bo).read_exact(&mut buf)?;
|
||||
let v = String::from_utf8(buf)?;
|
||||
let v = v.trim_matches(char::from(0));
|
||||
return Ok(Ascii(v.into()));
|
||||
}
|
||||
Type::UNDEFINED => {
|
||||
return Ok(List(
|
||||
self.offset[0..self.count as usize]
|
||||
.iter()
|
||||
.map(|&b| Byte(b))
|
||||
.collect(),
|
||||
));
|
||||
}
|
||||
Type::SHORT => {
|
||||
let mut r = self.r(bo);
|
||||
let mut v = Vec::new();
|
||||
for _ in 0..self.count {
|
||||
v.push(Short(r.read_u16()?));
|
||||
}
|
||||
return Ok(List(v));
|
||||
}
|
||||
Type::SSHORT => {
|
||||
let mut r = self.r(bo);
|
||||
let mut v = Vec::new();
|
||||
for _ in 0..self.count {
|
||||
v.push(Signed(i32::from(r.read_i16()?)));
|
||||
}
|
||||
return Ok(List(v));
|
||||
}
|
||||
Type::LONG => {
|
||||
let mut r = self.r(bo);
|
||||
let mut v = Vec::new();
|
||||
for _ in 0..self.count {
|
||||
v.push(Unsigned(r.read_u32()?));
|
||||
}
|
||||
return Ok(List(v));
|
||||
}
|
||||
Type::SLONG => {
|
||||
let mut r = self.r(bo);
|
||||
let mut v = Vec::new();
|
||||
for _ in 0..self.count {
|
||||
v.push(Signed(r.read_i32()?));
|
||||
}
|
||||
return Ok(List(v));
|
||||
}
|
||||
Type::FLOAT => {
|
||||
let mut r = self.r(bo);
|
||||
let mut v = Vec::new();
|
||||
for _ in 0..self.count {
|
||||
v.push(Float(r.read_f32()?));
|
||||
}
|
||||
return Ok(List(v));
|
||||
}
|
||||
Type::LONG8 | Type::RATIONAL | Type::SRATIONAL | Type::DOUBLE => unreachable!(),
|
||||
Type::__NonExhaustive => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
// Case 4: there is more than one value, and it doesn't fit in the offset field.
|
||||
match self.type_ {
|
||||
// TODO check if this could give wrong results
|
||||
// at a different endianess of file/computer.
|
||||
Type::BYTE => self.decode_offset(self.count, bo, limits, decoder, |decoder| {
|
||||
Ok(UnsignedBig(u64::from(decoder.read_byte()?)))
|
||||
}),
|
||||
Type::SBYTE => self.decode_offset(self.count, bo, limits, decoder, |decoder| {
|
||||
Ok(SignedBig(i64::from(decoder.read_byte()? as i8)))
|
||||
}),
|
||||
Type::SHORT => self.decode_offset(self.count, bo, limits, decoder, |decoder| {
|
||||
Ok(UnsignedBig(u64::from(decoder.read_short()?)))
|
||||
}),
|
||||
Type::SSHORT => self.decode_offset(self.count, bo, limits, decoder, |decoder| {
|
||||
Ok(SignedBig(i64::from(decoder.read_sshort()?)))
|
||||
}),
|
||||
Type::LONG => self.decode_offset(self.count, bo, limits, decoder, |decoder| {
|
||||
Ok(Unsigned(decoder.read_long()?))
|
||||
}),
|
||||
Type::SLONG => self.decode_offset(self.count, bo, limits, decoder, |decoder| {
|
||||
Ok(Signed(decoder.read_slong()?))
|
||||
}),
|
||||
Type::FLOAT => self.decode_offset(self.count, bo, limits, decoder, |decoder| {
|
||||
Ok(Float(decoder.read_float()?))
|
||||
}),
|
||||
Type::DOUBLE => self.decode_offset(self.count, bo, limits, decoder, |decoder| {
|
||||
Ok(Double(decoder.read_double()?))
|
||||
}),
|
||||
Type::RATIONAL => self.decode_offset(self.count, bo, limits, decoder, |decoder| {
|
||||
Ok(Rational(decoder.read_long()?, decoder.read_long()?))
|
||||
}),
|
||||
Type::SRATIONAL => self.decode_offset(self.count, bo, limits, decoder, |decoder| {
|
||||
Ok(SRational(decoder.read_slong()?, decoder.read_slong()?))
|
||||
}),
|
||||
Type::LONG8 => self.decode_offset(self.count, bo, limits, decoder, |decoder| {
|
||||
Ok(UnsignedBig(decoder.read_long8()?))
|
||||
}),
|
||||
Type::UNDEFINED => self.decode_offset(self.count, bo, limits, decoder, |decoder| {
|
||||
Ok(Byte(u8::from(decoder.read_byte()?)))
|
||||
}),
|
||||
Type::ASCII => {
|
||||
let n = usize::try_from(self.count)?;
|
||||
if n > limits.decoding_buffer_size {
|
||||
return Err(TiffError::LimitsExceeded);
|
||||
}
|
||||
|
||||
if decoder.bigtiff {
|
||||
decoder.goto_offset_u64(self.r(bo).read_u64()?)?
|
||||
} else {
|
||||
decoder.goto_offset(self.r(bo).read_u32()?)?
|
||||
}
|
||||
let string = decoder.read_string(n)?;
|
||||
Ok(Ascii(string))
|
||||
}
|
||||
Type::__NonExhaustive => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
||||
+52
-62
@@ -3,10 +3,12 @@ use std::collections::HashMap;
|
||||
use std::convert::TryFrom;
|
||||
use std::io::{self, Read, Seek};
|
||||
|
||||
use {ColorType, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError};
|
||||
use crate::{ColorType, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError};
|
||||
|
||||
use self::ifd::Directory;
|
||||
use tags::{CompressionMethod, PhotometricInterpretation, Predictor, SampleFormat, Tag, Type};
|
||||
use crate::tags::{
|
||||
CompressionMethod, PhotometricInterpretation, Predictor, SampleFormat, Tag, Type,
|
||||
};
|
||||
|
||||
use self::stream::{
|
||||
ByteOrder, DeflateReader, EndianReader, JpegReader, LZWReader, PackBitsReader, SmartReader,
|
||||
@@ -174,6 +176,24 @@ pub struct Limits {
|
||||
_non_exhaustive: (),
|
||||
}
|
||||
|
||||
impl Limits {
|
||||
/// A configuration that does not impose any limits.
|
||||
///
|
||||
/// This is a good start if the caller only wants to impose selective limits, contrary to the
|
||||
/// default limits which allows selectively disabling limits.
|
||||
///
|
||||
/// Note that this configuration is likely to crash on excessively large images since,
|
||||
/// naturally, the machine running the program does not have infinite memory.
|
||||
pub fn unlimited() -> Limits {
|
||||
Limits {
|
||||
decoding_buffer_size: usize::max_value(),
|
||||
ifd_value_size: usize::max_value(),
|
||||
intermediate_buffer_size: usize::max_value(),
|
||||
_non_exhaustive: (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Limits {
|
||||
fn default() -> Limits {
|
||||
Limits {
|
||||
@@ -198,7 +218,6 @@ where
|
||||
bigtiff: bool,
|
||||
limits: Limits,
|
||||
next_ifd: Option<u64>,
|
||||
current_ifd: Option<u64>,
|
||||
ifd: Option<Directory>,
|
||||
width: u32,
|
||||
height: u32,
|
||||
@@ -210,11 +229,6 @@ where
|
||||
strip_decoder: Option<StripDecodeState>,
|
||||
}
|
||||
|
||||
/// Stores state necessary to seek back (or forward) to particular part of the tiff.
|
||||
pub struct Checkpoint {
|
||||
offset: u64,
|
||||
}
|
||||
|
||||
trait Wrapping {
|
||||
fn wrapping_add(&self, other: Self) -> Self;
|
||||
}
|
||||
@@ -319,7 +333,6 @@ impl<R: Read + Seek> Decoder<R> {
|
||||
bigtiff: false,
|
||||
limits: Default::default(),
|
||||
next_ifd: None,
|
||||
current_ifd: None,
|
||||
ifd: None,
|
||||
width: 0,
|
||||
height: 0,
|
||||
@@ -347,6 +360,11 @@ impl<R: Read + Seek> Decoder<R> {
|
||||
PhotometricInterpretation::RGB => match self.bits_per_sample[..] {
|
||||
[r, g, b] if [r, r] == [g, b] => Ok(ColorType::RGB(r)),
|
||||
[r, g, b, a] if [r, r, r] == [g, b, a] => Ok(ColorType::RGBA(r)),
|
||||
// FIXME: We should _ignore_ other components. In particular:
|
||||
// > Beware of extra components. Some TIFF files may have more components per pixel
|
||||
// than you think. A Baseline TIFF reader must skip over them gracefully,using the
|
||||
// values of the SamplesPerPixel and BitsPerSample fields.
|
||||
// > -- TIFF 6.0 Specification, Section 7, Additional Baseline requirements.
|
||||
_ => Err(TiffError::UnsupportedError(
|
||||
TiffUnsupportedError::InterpretationWithBits(
|
||||
self.photometric_interpretation,
|
||||
@@ -477,6 +495,7 @@ impl<R: Read + Seek> Decoder<R> {
|
||||
}
|
||||
_ => return Err(TiffUnsupportedError::UnsupportedSampleDepth(self.samples).into()),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -592,44 +611,6 @@ impl<R: Read + Seek> Decoder<R> {
|
||||
self.reader.seek(io::SeekFrom::Start(offset)).map(|_| ())
|
||||
}
|
||||
|
||||
/// Retrieve a checkpoint that allows seeking before the TIFF header.
|
||||
///
|
||||
/// This is a special checkpoint where the caller should call `init` after restoring the
|
||||
/// checkpoint and no ifd is being read.
|
||||
pub fn checkpoint_init(&self) -> Checkpoint {
|
||||
Checkpoint { offset: Checkpoint::INITIAL_OFFSET }
|
||||
}
|
||||
|
||||
/// Retrieve a `Checkpoint` that can be used to seek to the current image directory.
|
||||
pub fn checkpoint(&self) -> Checkpoint {
|
||||
match self.current_ifd {
|
||||
None => self.checkpoint_init(),
|
||||
Some(offset) => Checkpoint { offset },
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the next image directory as a checkpoint.
|
||||
///
|
||||
/// This allows jumping forward to the next image directory later, without needing to pay the
|
||||
/// additional cost of parsing it right away.
|
||||
pub fn checkpoint_next(&self) -> Option<Checkpoint> {
|
||||
match self.next_ifd {
|
||||
None => None,
|
||||
Some(offset) => Some(Checkpoint { offset })
|
||||
}
|
||||
}
|
||||
|
||||
/// Restore the state of a previous checkpoint.
|
||||
pub fn restore(&mut self, checkpoint: &Checkpoint) -> TiffResult<()> {
|
||||
if checkpoint.offset == Checkpoint::INITIAL_OFFSET {
|
||||
self.goto_offset_u64(0)?;
|
||||
Ok(())
|
||||
} else {
|
||||
self.next_ifd = Some(checkpoint.offset);
|
||||
self.next_image()
|
||||
}
|
||||
}
|
||||
|
||||
/// Reads a IFD entry.
|
||||
// An IFD entry has four fields:
|
||||
//
|
||||
@@ -665,10 +646,7 @@ impl<R: Read + Seek> Decoder<R> {
|
||||
TiffFormatError::ImageFileDirectoryNotFound,
|
||||
))
|
||||
}
|
||||
Some(offset) => {
|
||||
self.goto_offset_u64(offset)?;
|
||||
self.current_ifd = Some(offset);
|
||||
}
|
||||
Some(offset) => self.goto_offset_u64(offset)?,
|
||||
}
|
||||
let num_tags = if self.bigtiff {
|
||||
self.read_long8()?
|
||||
@@ -774,6 +752,10 @@ impl<R: Read + Seek> Decoder<R> {
|
||||
pub fn get_tag_u32_vec(&mut self, tag: Tag) -> TiffResult<Vec<u32>> {
|
||||
self.get_tag(tag)?.into_u32_vec()
|
||||
}
|
||||
|
||||
pub fn get_tag_u16_vec(&mut self, tag: Tag) -> TiffResult<Vec<u16>> {
|
||||
self.get_tag(tag)?.into_u16_vec()
|
||||
}
|
||||
pub fn get_tag_u64_vec(&mut self, tag: Tag) -> TiffResult<Vec<u64>> {
|
||||
self.get_tag(tag)?.into_u64_vec()
|
||||
}
|
||||
@@ -793,6 +775,11 @@ impl<R: Read + Seek> Decoder<R> {
|
||||
self.get_tag(tag)?.into_u8_vec()
|
||||
}
|
||||
|
||||
/// Tries to retrieve a tag and convert it to a ascii vector.
|
||||
pub fn get_tag_ascii_string(&mut self, tag: Tag) -> TiffResult<String> {
|
||||
self.get_tag(tag)?.into_string()
|
||||
}
|
||||
|
||||
/// Decompresses the strip into the supplied buffer.
|
||||
/// Returns the number of bytes read.
|
||||
fn expand_strip<'a>(
|
||||
@@ -800,7 +787,7 @@ impl<R: Read + Seek> Decoder<R> {
|
||||
buffer: DecodingBuffer<'a>,
|
||||
offset: u64,
|
||||
length: u64,
|
||||
max_uncompressed_length: usize,
|
||||
strip_sample_count: usize,
|
||||
) -> TiffResult<usize> {
|
||||
let color_type = self.colortype()?;
|
||||
self.goto_offset_u64(offset)?;
|
||||
@@ -816,7 +803,7 @@ impl<R: Read + Seek> Decoder<R> {
|
||||
let (bytes, reader) = LZWReader::new(
|
||||
&mut self.reader,
|
||||
usize::try_from(length)?,
|
||||
max_uncompressed_length * buffer.byte_len(),
|
||||
strip_sample_count * buffer.byte_len(),
|
||||
)?;
|
||||
(bytes, Box::new(reader))
|
||||
}
|
||||
@@ -827,8 +814,7 @@ impl<R: Read + Seek> Decoder<R> {
|
||||
(bytes, Box::new(reader))
|
||||
}
|
||||
CompressionMethod::OldDeflate => {
|
||||
let (bytes, reader) =
|
||||
DeflateReader::new(&mut self.reader, max_uncompressed_length)?;
|
||||
let (bytes, reader) = DeflateReader::new(&mut self.reader, strip_sample_count)?;
|
||||
(bytes, Box::new(reader))
|
||||
}
|
||||
method => {
|
||||
@@ -838,9 +824,14 @@ impl<R: Read + Seek> Decoder<R> {
|
||||
}
|
||||
};
|
||||
|
||||
if bytes / buffer.byte_len() > max_uncompressed_length {
|
||||
// FIXME: this might be suboptimal. We might default remaining bits to ´0`, which some
|
||||
// other decoders might do.
|
||||
if bytes / buffer.byte_len() > strip_sample_count {
|
||||
return Err(TiffError::FormatError(
|
||||
TiffFormatError::InconsistentSizesEncountered,
|
||||
TiffFormatError::UnexpectedCompressedData {
|
||||
actual_bytes: bytes,
|
||||
required_bytes: strip_sample_count * buffer.byte_len(),
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
@@ -1073,7 +1064,10 @@ impl<R: Read + Seek> Decoder<R> {
|
||||
|
||||
if units_read < buffer_size {
|
||||
return Err(TiffError::FormatError(
|
||||
TiffFormatError::InconsistentSizesEncountered,
|
||||
TiffFormatError::InconsistentStripSamples {
|
||||
actual_samples: units_read,
|
||||
required_samples: buffer_size,
|
||||
},
|
||||
));
|
||||
}
|
||||
if let Ok(predictor) = self.get_tag_unsigned(Tag::Predictor) {
|
||||
@@ -1165,7 +1159,3 @@ impl<R: Read + Seek> Decoder<R> {
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
impl Checkpoint {
|
||||
const INITIAL_OFFSET: u64 = 0;
|
||||
}
|
||||
@@ -267,10 +267,11 @@ impl LZWReader {
|
||||
let mut uncompressed = Vec::with_capacity(max_uncompressed_length);
|
||||
let mut decoder = weezl::decode::Decoder::with_tiff_size_switch(weezl::BitOrder::Msb, 8);
|
||||
let mut bytes_read = 0;
|
||||
while bytes_read < compressed_length && uncompressed.len() < max_uncompressed_length {
|
||||
|
||||
while uncompressed.len() < max_uncompressed_length {
|
||||
let bytes_written = uncompressed.len();
|
||||
uncompressed.reserve(1 << 12);
|
||||
let buffer_space = uncompressed.capacity();
|
||||
let buffer_space = uncompressed.capacity().min(max_uncompressed_length);
|
||||
uncompressed.resize(buffer_space, 0u8);
|
||||
|
||||
let result = decoder.decode_bytes(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use tags::{PhotometricInterpretation, SampleFormat};
|
||||
use crate::tags::{PhotometricInterpretation, SampleFormat};
|
||||
|
||||
/// Trait for different colortypes that can be encoded.
|
||||
pub trait ColorType {
|
||||
|
||||
+31
-5
@@ -3,9 +3,9 @@ use std::convert::TryFrom;
|
||||
use std::io::{Seek, Write};
|
||||
use std::{cmp, io, mem};
|
||||
|
||||
use bytecast;
|
||||
use error::{TiffError, TiffFormatError, TiffResult};
|
||||
use tags::{self, ResolutionUnit, Tag, Type};
|
||||
use crate::bytecast;
|
||||
use crate::error::{TiffError, TiffFormatError, TiffResult};
|
||||
use crate::tags::{self, ResolutionUnit, Tag, Type};
|
||||
|
||||
pub mod colortype;
|
||||
mod writer;
|
||||
@@ -589,6 +589,9 @@ impl<'a, W: Write + Seek> Drop for DirectoryEncoder<'a, W> {
|
||||
/// // You can encode tags here
|
||||
/// image.encoder().write_tag(Tag::Artist, "Image-tiff").unwrap();
|
||||
///
|
||||
/// // Strip size can be configured before writing data
|
||||
/// image.rows_per_strip(2).unwrap();
|
||||
///
|
||||
/// let mut idx = 0;
|
||||
/// while image.next_strip_sample_count() > 0 {
|
||||
/// let sample_count = image.next_strip_sample_count() as usize;
|
||||
@@ -622,8 +625,9 @@ impl<'a, W: 'a + Write + Seek, T: ColorType> ImageEncoder<'a, W, T> {
|
||||
let row_samples = u64::from(width) * u64::try_from(<T>::BITS_PER_SAMPLE.len())?;
|
||||
let row_bytes = row_samples * u64::from(<T::Inner>::BYTE_LEN);
|
||||
|
||||
// As per tiff spec each strip should be about 8k long
|
||||
let rows_per_strip = (8000 + row_bytes - 1) / row_bytes;
|
||||
// Limit the strip size to prevent potential memory and security issues.
|
||||
// Also keep the multiple strip handling 'oiled'
|
||||
let rows_per_strip = (1_000_000 + row_bytes - 1) / row_bytes;
|
||||
|
||||
let strip_count = (u64::from(height) + rows_per_strip - 1) / rows_per_strip;
|
||||
|
||||
@@ -755,6 +759,28 @@ impl<'a, W: 'a + Write + Seek, T: ColorType> ImageEncoder<'a, W, T> {
|
||||
self.encoder.write_tag(Tag::YResolution, value).unwrap();
|
||||
}
|
||||
|
||||
/// Set image number of lines per strip
|
||||
///
|
||||
/// This function needs to be called before any calls to `write_data` or
|
||||
/// `write_strip` and will return an error otherwise.
|
||||
pub fn rows_per_strip(&mut self, value: u32) -> TiffResult<()> {
|
||||
if self.strip_idx != 0 {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"Cannot change strip size after data was written",
|
||||
)
|
||||
.into());
|
||||
}
|
||||
// Write tag as 32 bits
|
||||
self.encoder.write_tag(Tag::RowsPerStrip, value)?;
|
||||
|
||||
let value: u64 = value as u64;
|
||||
self.strip_count = (self.height as u64 + value - 1) / value;
|
||||
self.rows_per_strip = value;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn finish_internal(&mut self) -> TiffResult<()> {
|
||||
self.encoder
|
||||
.write_tag(Tag::StripOffsets, &*self.strip_offsets)?;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use error::TiffResult;
|
||||
use crate::error::TiffResult;
|
||||
use std::io::{self, Seek, SeekFrom, Write};
|
||||
|
||||
pub fn write_tiff_header<W: Write>(writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||
|
||||
+33
-3
@@ -3,10 +3,12 @@ use std::fmt;
|
||||
use std::io;
|
||||
use std::string;
|
||||
|
||||
use decoder::ifd::Value;
|
||||
use crate::decoder::ifd::Value;
|
||||
use crate::tags::{
|
||||
CompressionMethod, PhotometricInterpretation, PlanarConfiguration, SampleFormat, Tag,
|
||||
};
|
||||
use crate::ColorType;
|
||||
use miniz_oxide::inflate::TINFLStatus;
|
||||
use tags::{CompressionMethod, PhotometricInterpretation, PlanarConfiguration, SampleFormat, Tag};
|
||||
use ColorType;
|
||||
|
||||
/// Tiff error kinds.
|
||||
#[derive(Debug)]
|
||||
@@ -41,6 +43,14 @@ pub enum TiffFormatError {
|
||||
TiffSignatureInvalid,
|
||||
ImageFileDirectoryNotFound,
|
||||
InconsistentSizesEncountered,
|
||||
UnexpectedCompressedData {
|
||||
actual_bytes: usize,
|
||||
required_bytes: usize,
|
||||
},
|
||||
InconsistentStripSamples {
|
||||
actual_samples: usize,
|
||||
required_samples: usize,
|
||||
},
|
||||
InvalidTag,
|
||||
InvalidTagValueType(Tag),
|
||||
RequiredTagNotFound(Tag),
|
||||
@@ -64,6 +74,26 @@ impl fmt::Display for TiffFormatError {
|
||||
TiffSignatureInvalid => write!(fmt, "TIFF signature invalid."),
|
||||
ImageFileDirectoryNotFound => write!(fmt, "Image file directory not found."),
|
||||
InconsistentSizesEncountered => write!(fmt, "Inconsistent sizes encountered."),
|
||||
UnexpectedCompressedData {
|
||||
actual_bytes,
|
||||
required_bytes,
|
||||
} => {
|
||||
write!(
|
||||
fmt,
|
||||
"Decompression returned different amount of bytes than expected: got {}, expected {}.",
|
||||
actual_bytes, required_bytes
|
||||
)
|
||||
}
|
||||
InconsistentStripSamples {
|
||||
actual_samples,
|
||||
required_samples,
|
||||
} => {
|
||||
write!(
|
||||
fmt,
|
||||
"Inconsistent elements in strip: got {}, expected {}.",
|
||||
actual_samples, required_samples
|
||||
)
|
||||
}
|
||||
InvalidTag => write!(fmt, "Image contains invalid tag."),
|
||||
InvalidTagValueType(ref tag) => {
|
||||
write!(fmt, "Tag `{:?}` did not have the expected value type.", tag)
|
||||
|
||||
@@ -120,6 +120,14 @@ pub enum Tag(u16) unknown("A private or extension tag") {
|
||||
TileOffsets = 324,
|
||||
TileByteCounts = 325,
|
||||
SampleFormat = 339,
|
||||
// GeoTIFF
|
||||
ModelPixelScaleTag = 33550, // (SoftDesk)
|
||||
ModelTransformationTag = 34264, // (JPL Carto Group)
|
||||
ModelTiepointTag = 33922, // (Intergraph)
|
||||
GeoKeyDirectoryTag = 34735, // (SPOT)
|
||||
GeoDoubleParamsTag = 34736, // (SPOT)
|
||||
GeoAsciiParamsTag = 34737, // (SPOT)
|
||||
GdalNodata = 42113, // Contains areas with missing data
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -360,3 +360,42 @@ fn test_multipage_image() {
|
||||
assert_eq!(img_decoder.dimensions().unwrap(), (3, 3));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// verify rows per strip setting
|
||||
fn test_rows_per_strip() {
|
||||
let mut file = Cursor::new(Vec::new());
|
||||
{
|
||||
let mut img_encoder = TiffEncoder::new(&mut file).unwrap();
|
||||
|
||||
let mut image = img_encoder.new_image::<colortype::Gray8>(100, 100).unwrap();
|
||||
assert_eq!(image.next_strip_sample_count(), 100 * 100);
|
||||
image.rows_per_strip(2).unwrap();
|
||||
assert_eq!(image.next_strip_sample_count(), 2 * 100);
|
||||
|
||||
let img2: Vec<u8> = vec![0; 2 * 100];
|
||||
image.write_strip(&img2[..]).unwrap();
|
||||
assert!(image.rows_per_strip(5).is_err());
|
||||
for i in 1..50 {
|
||||
let img2: Vec<u8> = vec![i; 2 * 100];
|
||||
image.write_strip(&img2[..]).unwrap();
|
||||
}
|
||||
assert!(image.write_strip(&img2[..]).is_err());
|
||||
image.finish().unwrap();
|
||||
}
|
||||
|
||||
file.seek(SeekFrom::Start(0)).unwrap();
|
||||
{
|
||||
let mut decoder = Decoder::new(&mut file).unwrap();
|
||||
assert_eq!(decoder.get_tag_u64(Tag::RowsPerStrip).unwrap(), 2);
|
||||
assert_eq!(decoder.strip_count().unwrap(), 50);
|
||||
|
||||
for i in 0..50 {
|
||||
let img2 = [i; 2 * 100];
|
||||
match decoder.read_strip().unwrap() {
|
||||
DecodingResult::U8(data) => assert_eq!(&img2[..], &data[..]),
|
||||
other => panic!("Incorrect strip type {:?}", other),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user