diff --git a/.gitignore b/.gitignore index 143b1ca0..835097c2 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ /target/ **/*.rs.bk Cargo.lock +.idea/ diff --git a/Cargo.toml b/Cargo.toml index baf432fd..81838b49 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,9 +20,13 @@ exclude = ["tests/images/*", "tests/fuzz_images/*"] weezl = "0.1.0" jpeg = { package = "jpeg-decoder", version = "0.3.0", default-features = false } flate2 = "1.0.20" +image = "0.24.8" +itertools = "0.13.0" [dev-dependencies] +clap = { version = "4.0.32", features = ["derive"] } criterion = "0.3.1" +kamadak-exif = "0.5.5" [[bench]] name = "lzw" diff --git a/examples/print-exif.rs b/examples/print-exif.rs new file mode 100644 index 00000000..1830f74e --- /dev/null +++ b/examples/print-exif.rs @@ -0,0 +1,54 @@ +extern crate exif; +extern crate tiff; + +use tiff::{ + decoder::TiffDecoder, + ifd::{Directory, ImageFileDirectory, ProcessedEntry}, + tags::{GpsTag, Tag}, +}; + +use clap::Parser; +use std::fs::File; +use std::path::PathBuf; + +#[derive(Parser)] +#[command(author, version, about, long_about = None)] +struct Cli { + /// Path to the exposure folder containing the index.tse file + #[arg(required = true)] + path: PathBuf, +} + +fn main() { + let args = Cli::parse(); + + let img_file = File::open(args.path).expect("Cannot find test image!"); + let mut decoder = TiffDecoder::new(img_file).expect("Cannot create decoder"); + let mut exif: Directory = decoder + .get_exif_data() + .expect("Unable to read Exif data") + .into_iter() + .collect(); + print!("{exif}"); + + exif = decoder + .get_exif_ifd(Tag::ExifIfd) + .expect("Unable to read Exif data") + .into_iter() + .collect(); + print!("{exif}"); + + let gps_exif = decoder + .get_gps_ifd() + .expect("Unable to read Exif data") + .into_iter() + .collect::>(); + print!("{gps_exif}"); + + exif = decoder + .get_exif_ifd(Tag::InteropIfd) + .expect("Unable to read Exif data") + .into_iter() + .collect(); + print!("{exif}"); +} diff --git a/src/decoder/decoded_entry.rs b/src/decoder/decoded_entry.rs new file mode 100644 index 00000000..1ed64d2a --- /dev/null +++ b/src/decoder/decoded_entry.rs @@ -0,0 +1,442 @@ +use super::stream::{ByteOrder, EndianReader, SmartReader}; +use crate::tags::Type; +use crate::{TiffError, TiffFormatError, TiffKind, TiffResult}; +use std::io::{self, Read, Seek}; +use std::mem; + +use crate::decoder::fix_endianness; +use crate::ifd::{ + BufferedEntry, + Value::{ + self, Ascii, Byte, Double, Float, Ifd, IfdBig, List, Rational, SRational, Short, Signed, + SignedBig, Unsigned, UnsignedBig, + }, +}; + +#[derive(Clone)] +pub struct DecodedEntry { + type_: Type, + count: K::OffsetType, + offset: Vec, +} + +impl ::std::fmt::Debug for DecodedEntry { + fn fmt(&self, fmt: &mut ::std::fmt::Formatter) -> Result<(), ::std::fmt::Error> { + fmt.write_str(&format!( + "Entry {{ type_: {:?}, count: {:?}, offset: {:?} }}", + self.type_, self.count, &self.offset + )) + } +} + +impl DecodedEntry { + pub fn new(type_: Type, count: K::OffsetType, offset: &[u8]) -> Self { + Self { + type_, + count, + offset: offset.to_vec(), + } + } + + /// Returns a mem_reader for the offset/value field + fn r(&self, byte_order: ByteOrder) -> SmartReader>> { + SmartReader::wrap(io::Cursor::new(self.offset.clone()), byte_order) + } + + pub fn val( + &self, + limits: &super::Limits, + reader: &mut SmartReader, + ) -> TiffResult { + let count: usize = self + .count + .clone() + .try_into() + .map_err(|_| TiffError::LimitsExceeded)?; + + // Case 1: there are no values so we can return immediately. + if count == 0 { + return Ok(List(Vec::new())); + } + + let bo = reader.byte_order(); + + 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 count.checked_mul(tag_size) { + Some(n) => n, + None => { + return Err(TiffError::LimitsExceeded); + } + }; + + // Case 2: there is one value. + if count == 1 { + // 2a: the value is 5-8 bytes and we're in BigTiff mode. + if K::is_big() && value_bytes > 4 && value_bytes <= 8 { + return Ok(match self.type_ { + 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.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::IFD8 => IfdBig(self.r(bo).read_u64()?), + Type::BYTE + | Type::SBYTE + | Type::ASCII + | Type::UNDEFINED + | Type::SHORT + | Type::SSHORT + | Type::LONG + | Type::SLONG + | Type::FLOAT + | Type::IFD => unreachable!(), + }); + } + + // 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 { + return Err(TiffError::FormatError(TiffFormatError::InvalidTag)); + } + } + Type::LONG8 => { + reader.goto_offset(self.r(bo).read_u32()?.into())?; + UnsignedBig(reader.read_u64()?) + } + Type::SLONG8 => { + reader.goto_offset(self.r(bo).read_u32()?.into())?; + SignedBig(reader.read_i64()?) + } + Type::DOUBLE => { + reader.goto_offset(self.r(bo).read_u32()?.into())?; + Double(reader.read_f64()?) + } + Type::RATIONAL => { + reader.goto_offset(self.r(bo).read_u32()?.into())?; + Rational(reader.read_u32()?, reader.read_u32()?) + } + Type::SRATIONAL => { + reader.goto_offset(self.r(bo).read_u32()?.into())?; + SRational(reader.read_i32()?, reader.read_i32()?) + } + Type::IFD => Ifd(self.r(bo).read_u32()?), + Type::IFD8 => { + reader.goto_offset(self.r(bo).read_u32()?.into())?; + IfdBig(reader.read_u64()?) + } + }); + } + + // Case 3: There is more than one value, but it fits in the offset field. + if value_bytes <= 4 || K::is_big() && value_bytes <= 8 { + match self.type_ { + Type::BYTE => return offset_to_bytes(count, self), + Type::SBYTE => return offset_to_sbytes(count, self), + Type::ASCII => { + let mut buf = vec![0; count]; + self.r(bo).read_exact(&mut buf)?; + if buf.is_ascii() && buf.ends_with(&[0]) { + let v = std::str::from_utf8(&buf)?; + let v = v.trim_matches(char::from(0)); + return Ok(Ascii(v.into())); + } else { + return Err(TiffError::FormatError(TiffFormatError::InvalidTag)); + } + } + Type::UNDEFINED => { + return Ok(List( + self.offset[0..count].iter().map(|&b| Byte(b)).collect(), + )); + } + Type::SHORT => { + let mut r = self.r(bo); + let mut v = Vec::new(); + for _ in 0..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..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..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..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..count { + v.push(Float(r.read_f32()?)); + } + return Ok(List(v)); + } + Type::IFD => { + let mut r = self.r(bo); + let mut v = Vec::new(); + for _ in 0..count { + v.push(Ifd(r.read_u32()?)); + } + return Ok(List(v)); + } + Type::LONG8 + | Type::SLONG8 + | Type::RATIONAL + | Type::SRATIONAL + | Type::DOUBLE + | Type::IFD8 => { + 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(count, bo, limits, reader, |reader| { + let mut buf = [0; 1]; + reader.read_exact(&mut buf)?; + Ok(UnsignedBig(u64::from(buf[0]))) + }), + Type::SBYTE => self.decode_offset(count, bo, limits, reader, |reader| { + Ok(SignedBig(i64::from(reader.read_i8()?))) + }), + Type::SHORT => self.decode_offset(count, bo, limits, reader, |reader| { + Ok(UnsignedBig(u64::from(reader.read_u16()?))) + }), + Type::SSHORT => self.decode_offset(count, bo, limits, reader, |reader| { + Ok(SignedBig(i64::from(reader.read_i16()?))) + }), + Type::LONG => self.decode_offset(count, bo, limits, reader, |reader| { + Ok(Unsigned(reader.read_u32()?)) + }), + Type::SLONG => self.decode_offset(count, bo, limits, reader, |reader| { + Ok(Signed(reader.read_i32()?)) + }), + Type::FLOAT => self.decode_offset(count, bo, limits, reader, |reader| { + Ok(Float(reader.read_f32()?)) + }), + Type::DOUBLE => self.decode_offset(count, bo, limits, reader, |reader| { + Ok(Double(reader.read_f64()?)) + }), + Type::RATIONAL => self.decode_offset(count, bo, limits, reader, |reader| { + Ok(Rational(reader.read_u32()?, reader.read_u32()?)) + }), + Type::SRATIONAL => self.decode_offset(count, bo, limits, reader, |reader| { + Ok(SRational(reader.read_i32()?, reader.read_i32()?)) + }), + Type::LONG8 => self.decode_offset(count, bo, limits, reader, |reader| { + Ok(UnsignedBig(reader.read_u64()?)) + }), + Type::SLONG8 => self.decode_offset(count, bo, limits, reader, |reader| { + Ok(SignedBig(reader.read_i64()?)) + }), + Type::IFD => self.decode_offset(count, bo, limits, reader, |reader| { + Ok(Ifd(reader.read_u32()?)) + }), + Type::IFD8 => self.decode_offset(count, bo, limits, reader, |reader| { + Ok(IfdBig(reader.read_u64()?)) + }), + Type::UNDEFINED => self.decode_offset(count, bo, limits, reader, |reader| { + let mut buf = [0; 1]; + reader.read_exact(&mut buf)?; + Ok(Byte(buf[0])) + }), + Type::ASCII => { + if count > limits.decoding_buffer_size { + return Err(TiffError::LimitsExceeded); + } + + if K::is_big() { + reader.goto_offset(self.r(bo).read_u64()?)? + } else { + reader.goto_offset(self.r(bo).read_u32()?.into())? + } + + let mut out = vec![0; count]; + reader.read_exact(&mut out)?; + // Strings may be null-terminated, so we trim anything downstream of the null byte + if let Some(first) = out.iter().position(|&b| b == 0) { + out.truncate(first); + } + Ok(Ascii(String::from_utf8(out)?)) + } + } + } + + #[inline] + fn decode_offset( + &self, + value_count: usize, + bo: ByteOrder, + limits: &super::Limits, + reader: &mut SmartReader, + decode_fn: F, + ) -> TiffResult + where + R: Read + Seek, + F: Fn(&mut SmartReader) -> TiffResult, + { + //let value_count = usize::try_from(value_count)?; + if value_count > limits.decoding_buffer_size / mem::size_of::() { + return Err(TiffError::LimitsExceeded); + } + + let mut v = Vec::with_capacity(value_count); + + let offset = if K::is_big() { + self.r(bo).read_u64()? + } else { + self.r(bo).read_u32()?.into() + }; + reader.goto_offset(offset)?; + + for _ in 0..value_count { + v.push(decode_fn(reader)?) + } + Ok(List(v)) + } + + /// retrieve entry with data read into a buffer (to cache it for writing) + pub fn as_buffered( + &self, + reader: &mut SmartReader, + ) -> TiffResult { + let count: usize = self + .count + .clone() + .try_into() + .map_err(|_| TiffError::LimitsExceeded)?; + + // establish byte order + let bo = reader.byte_order(); + let native_bo; + #[cfg(target_endian = "little")] + { + native_bo = ByteOrder::LittleEndian; + } + #[cfg(not(target_endian = "little"))] + { + native_bo = ByteOrder::BigEndian; + } + + // establish size + 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 count.checked_mul(tag_size) { + Some(n) => n, + None => { + return Err(TiffError::LimitsExceeded); + } + }; + + let mut buf = vec![0; value_bytes as usize]; + if value_bytes <= 4 || (K::is_big() && value_bytes <= 8) { + // read values that fit within the IFD entry + self.r(bo).read(&mut buf)?; + } else { + // values that use a pointer + // read pointed data + if K::is_big() { + reader.goto_offset(self.r(bo).read_u64()?.into())?; + } else { + reader.goto_offset(self.r(bo).read_u32()?.into())?; + } + reader.read_exact(&mut buf)?; + } + + // convert buffer to native byte order + if native_bo != bo { + let bit_size = match self.type_ { + Type::RATIONAL | Type::SRATIONAL => 32, + _ => 8 * tag_size as u8, + }; + + fix_endianness(&mut buf, bo, bit_size); + } + + Ok(BufferedEntry { + type_: self.type_, + count: self.count.clone().into(), + data: buf, + }) + } +} + +/// Extracts a list of BYTE tags stored in an offset +#[inline] +fn offset_to_bytes(n: usize, entry: &DecodedEntry) -> TiffResult { + Ok(List( + entry.offset[0..n] + .iter() + .map(|&e| Unsigned(u32::from(e))) + .collect(), + )) +} + +/// Extracts a list of SBYTE tags stored in an offset +#[inline] +fn offset_to_sbytes(n: usize, entry: &DecodedEntry) -> TiffResult { + Ok(List( + entry.offset[0..n] + .iter() + .map(|&e| Signed(i32::from(e as i8))) + .collect(), + )) +} diff --git a/src/decoder/ifd.rs b/src/decoder/ifd.rs deleted file mode 100644 index 9be0d354..00000000 --- a/src/decoder/ifd.rs +++ /dev/null @@ -1,699 +0,0 @@ -//! Function for reading TIFF tags - -use std::collections::HashMap; -use std::io::{self, Read, Seek}; -use std::mem; -use std::str; - -use super::stream::{ByteOrder, EndianReader, SmartReader}; -use crate::tags::{Tag, Type}; -use crate::{TiffError, TiffFormatError, TiffResult}; - -use self::Value::{ - Ascii, Byte, Double, Float, Ifd, IfdBig, List, Rational, RationalBig, SRational, SRationalBig, - Short, Signed, SignedBig, SignedByte, SignedShort, Unsigned, UnsignedBig, -}; - -#[allow(unused_qualifications)] -#[derive(Debug, Clone, PartialEq)] -#[non_exhaustive] -pub enum Value { - Byte(u8), - Short(u16), - SignedByte(i8), - SignedShort(i16), - Signed(i32), - SignedBig(i64), - Unsigned(u32), - UnsignedBig(u64), - Float(f32), - Double(f64), - List(Vec), - Rational(u32, u32), - RationalBig(u64, u64), - SRational(i32, i32), - SRationalBig(i64, i64), - Ascii(String), - Ifd(u32), - IfdBig(u64), -} - -impl Value { - pub fn into_u8(self) -> TiffResult { - match self { - Byte(val) => Ok(val), - val => Err(TiffError::FormatError(TiffFormatError::ByteExpected(val))), - } - } - pub fn into_i8(self) -> TiffResult { - match self { - SignedByte(val) => Ok(val), - val => Err(TiffError::FormatError(TiffFormatError::SignedByteExpected( - val, - ))), - } - } - - pub fn into_u16(self) -> TiffResult { - 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_i16(self) -> TiffResult { - match self { - SignedByte(val) => Ok(val.into()), - SignedShort(val) => Ok(val), - Signed(val) => Ok(i16::try_from(val)?), - SignedBig(val) => Ok(i16::try_from(val)?), - val => Err(TiffError::FormatError( - TiffFormatError::SignedShortExpected(val), - )), - } - } - - pub fn into_u32(self) -> TiffResult { - match self { - Short(val) => Ok(val.into()), - Unsigned(val) => Ok(val), - UnsignedBig(val) => Ok(u32::try_from(val)?), - Ifd(val) => Ok(val), - IfdBig(val) => Ok(u32::try_from(val)?), - val => Err(TiffError::FormatError( - TiffFormatError::UnsignedIntegerExpected(val), - )), - } - } - - pub fn into_i32(self) -> TiffResult { - match self { - SignedByte(val) => Ok(val.into()), - SignedShort(val) => Ok(val.into()), - Signed(val) => Ok(val), - SignedBig(val) => Ok(i32::try_from(val)?), - val => Err(TiffError::FormatError( - TiffFormatError::SignedIntegerExpected(val), - )), - } - } - - pub fn into_u64(self) -> TiffResult { - match self { - Short(val) => Ok(val.into()), - Unsigned(val) => Ok(val.into()), - UnsignedBig(val) => Ok(val), - Ifd(val) => Ok(val.into()), - IfdBig(val) => Ok(val), - val => Err(TiffError::FormatError( - TiffFormatError::UnsignedIntegerExpected(val), - )), - } - } - - pub fn into_i64(self) -> TiffResult { - match self { - SignedByte(val) => Ok(val.into()), - SignedShort(val) => Ok(val.into()), - Signed(val) => Ok(val.into()), - SignedBig(val) => Ok(val), - val => Err(TiffError::FormatError( - TiffFormatError::SignedIntegerExpected(val), - )), - } - } - - pub fn into_f32(self) -> TiffResult { - match self { - Float(val) => Ok(val), - val => Err(TiffError::FormatError( - TiffFormatError::SignedIntegerExpected(val), - )), - } - } - - pub fn into_f64(self) -> TiffResult { - match self { - Double(val) => Ok(val), - val => Err(TiffError::FormatError( - TiffFormatError::SignedIntegerExpected(val), - )), - } - } - - pub fn into_string(self) -> TiffResult { - match self { - Ascii(val) => Ok(val), - val => Err(TiffError::FormatError( - TiffFormatError::SignedIntegerExpected(val), - )), - } - } - - pub fn into_u32_vec(self) -> TiffResult> { - match self { - List(vec) => { - let mut new_vec = Vec::with_capacity(vec.len()); - for v in vec { - new_vec.push(v.into_u32()?) - } - Ok(new_vec) - } - Unsigned(val) => Ok(vec![val]), - UnsignedBig(val) => Ok(vec![u32::try_from(val)?]), - Rational(numerator, denominator) => Ok(vec![numerator, denominator]), - RationalBig(numerator, denominator) => { - Ok(vec![u32::try_from(numerator)?, u32::try_from(denominator)?]) - } - Ifd(val) => Ok(vec![val]), - IfdBig(val) => Ok(vec![u32::try_from(val)?]), - Ascii(val) => Ok(val.chars().map(u32::from).collect()), - val => Err(TiffError::FormatError( - TiffFormatError::UnsignedIntegerExpected(val), - )), - } - } - - pub fn into_u8_vec(self) -> TiffResult> { - match self { - List(vec) => { - let mut new_vec = Vec::with_capacity(vec.len()); - for v in vec { - new_vec.push(v.into_u8()?) - } - Ok(new_vec) - } - Byte(val) => Ok(vec![val]), - - val => Err(TiffError::FormatError( - TiffFormatError::UnsignedIntegerExpected(val), - )), - } - } - - pub fn into_u16_vec(self) -> TiffResult> { - 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> { - match self { - List(vec) => { - let mut new_vec = Vec::with_capacity(vec.len()); - for v in vec { - match v { - SRational(numerator, denominator) => { - new_vec.push(numerator); - new_vec.push(denominator); - } - SRationalBig(numerator, denominator) => { - new_vec.push(i32::try_from(numerator)?); - new_vec.push(i32::try_from(denominator)?); - } - _ => new_vec.push(v.into_i32()?), - } - } - Ok(new_vec) - } - SignedByte(val) => Ok(vec![val.into()]), - SignedShort(val) => Ok(vec![val.into()]), - Signed(val) => Ok(vec![val]), - SignedBig(val) => Ok(vec![i32::try_from(val)?]), - SRational(numerator, denominator) => Ok(vec![numerator, denominator]), - SRationalBig(numerator, denominator) => { - Ok(vec![i32::try_from(numerator)?, i32::try_from(denominator)?]) - } - val => Err(TiffError::FormatError( - TiffFormatError::SignedIntegerExpected(val), - )), - } - } - - pub fn into_f32_vec(self) -> TiffResult> { - match self { - List(vec) => { - let mut new_vec = Vec::with_capacity(vec.len()); - for v in vec { - new_vec.push(v.into_f32()?) - } - Ok(new_vec) - } - Float(val) => Ok(vec![val]), - val => Err(TiffError::FormatError( - TiffFormatError::UnsignedIntegerExpected(val), - )), - } - } - - pub fn into_f64_vec(self) -> TiffResult> { - match self { - List(vec) => { - let mut new_vec = Vec::with_capacity(vec.len()); - for v in vec { - new_vec.push(v.into_f64()?) - } - Ok(new_vec) - } - Double(val) => Ok(vec![val]), - val => Err(TiffError::FormatError( - TiffFormatError::UnsignedIntegerExpected(val), - )), - } - } - - pub fn into_u64_vec(self) -> TiffResult> { - match self { - List(vec) => { - let mut new_vec = Vec::with_capacity(vec.len()); - for v in vec { - new_vec.push(v.into_u64()?) - } - Ok(new_vec) - } - Unsigned(val) => Ok(vec![val.into()]), - UnsignedBig(val) => Ok(vec![val]), - Rational(numerator, denominator) => Ok(vec![numerator.into(), denominator.into()]), - RationalBig(numerator, denominator) => Ok(vec![numerator, denominator]), - Ifd(val) => Ok(vec![val.into()]), - IfdBig(val) => Ok(vec![val]), - Ascii(val) => Ok(val.chars().map(u32::from).map(u64::from).collect()), - val => Err(TiffError::FormatError( - TiffFormatError::UnsignedIntegerExpected(val), - )), - } - } - - pub fn into_i64_vec(self) -> TiffResult> { - match self { - List(vec) => { - let mut new_vec = Vec::with_capacity(vec.len()); - for v in vec { - match v { - SRational(numerator, denominator) => { - new_vec.push(numerator.into()); - new_vec.push(denominator.into()); - } - SRationalBig(numerator, denominator) => { - new_vec.push(numerator); - new_vec.push(denominator); - } - _ => new_vec.push(v.into_i64()?), - } - } - Ok(new_vec) - } - SignedByte(val) => Ok(vec![val.into()]), - SignedShort(val) => Ok(vec![val.into()]), - Signed(val) => Ok(vec![val.into()]), - SignedBig(val) => Ok(vec![val]), - SRational(numerator, denominator) => Ok(vec![numerator.into(), denominator.into()]), - SRationalBig(numerator, denominator) => Ok(vec![numerator, denominator]), - val => Err(TiffError::FormatError( - TiffFormatError::SignedIntegerExpected(val), - )), - } - } -} - -#[derive(Clone)] -pub struct Entry { - type_: Type, - count: u64, - offset: [u8; 8], -} - -impl ::std::fmt::Debug for Entry { - fn fmt(&self, fmt: &mut ::std::fmt::Formatter) -> Result<(), ::std::fmt::Error> { - fmt.write_str(&format!( - "Entry {{ type_: {:?}, count: {:?}, offset: {:?} }}", - self.type_, self.count, &self.offset - )) - } -} - -impl Entry { - pub fn new(type_: Type, count: u32, offset: [u8; 4]) -> Entry { - let mut offset = offset.to_vec(); - offset.append(&mut vec![0; 4]); - Entry::new_u64(type_, count.into(), offset[..].try_into().unwrap()) - } - - pub fn new_u64(type_: Type, count: u64, offset: [u8; 8]) -> Entry { - Entry { - type_, - count, - offset, - } - } - - /// Returns a mem_reader for the offset/value field - fn r(&self, byte_order: ByteOrder) -> SmartReader>> { - SmartReader::wrap(io::Cursor::new(self.offset.to_vec()), byte_order) - } - - pub fn val( - &self, - limits: &super::Limits, - bigtiff: bool, - reader: &mut SmartReader, - ) -> TiffResult { - // Case 1: there are no values so we can return immediately. - if self.count == 0 { - return Ok(List(Vec::new())); - } - - let bo = reader.byte_order(); - - 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.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.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::IFD8 => IfdBig(self.r(bo).read_u64()?), - Type::BYTE - | Type::SBYTE - | Type::ASCII - | Type::UNDEFINED - | Type::SHORT - | Type::SSHORT - | Type::LONG - | Type::SLONG - | Type::FLOAT - | Type::IFD => unreachable!(), - }); - } - - // 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 { - return Err(TiffError::FormatError(TiffFormatError::InvalidTag)); - } - } - Type::LONG8 => { - reader.goto_offset(self.r(bo).read_u32()?.into())?; - UnsignedBig(reader.read_u64()?) - } - Type::SLONG8 => { - reader.goto_offset(self.r(bo).read_u32()?.into())?; - SignedBig(reader.read_i64()?) - } - Type::DOUBLE => { - reader.goto_offset(self.r(bo).read_u32()?.into())?; - Double(reader.read_f64()?) - } - Type::RATIONAL => { - reader.goto_offset(self.r(bo).read_u32()?.into())?; - Rational(reader.read_u32()?, reader.read_u32()?) - } - Type::SRATIONAL => { - reader.goto_offset(self.r(bo).read_u32()?.into())?; - SRational(reader.read_i32()?, reader.read_i32()?) - } - Type::IFD => Ifd(self.r(bo).read_u32()?), - Type::IFD8 => { - reader.goto_offset(self.r(bo).read_u32()?.into())?; - IfdBig(reader.read_u64()?) - } - }); - } - - // Case 3: There is more than one value, but it fits in the offset field. - if value_bytes <= 4 || bigtiff && value_bytes <= 8 { - match self.type_ { - Type::BYTE => return offset_to_bytes(self.count as usize, self), - Type::SBYTE => return 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)?; - if buf.is_ascii() && buf.ends_with(&[0]) { - let v = str::from_utf8(&buf)?; - let v = v.trim_matches(char::from(0)); - return Ok(Ascii(v.into())); - } else { - return Err(TiffError::FormatError(TiffFormatError::InvalidTag)); - } - } - 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::IFD => { - let mut r = self.r(bo); - let mut v = Vec::new(); - for _ in 0..self.count { - v.push(Ifd(r.read_u32()?)); - } - return Ok(List(v)); - } - Type::LONG8 - | Type::SLONG8 - | Type::RATIONAL - | Type::SRATIONAL - | Type::DOUBLE - | Type::IFD8 => { - 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, bigtiff, limits, reader, |reader| { - let mut buf = [0; 1]; - reader.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(UnsignedBig(u64::from(reader.read_u16()?))) - }), - Type::SSHORT => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| { - Ok(SignedBig(i64::from(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 => { - self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| { - Ok(Rational(reader.read_u32()?, reader.read_u32()?)) - }) - } - Type::SRATIONAL => { - self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| { - Ok(SRational(reader.read_i32()?, reader.read_i32()?)) - }) - } - 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.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(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]; - reader.read_exact(&mut out)?; - // Strings may be null-terminated, so we trim anything downstream of the null byte - if let Some(first) = out.iter().position(|&b| b == 0) { - out.truncate(first); - } - Ok(Ascii(String::from_utf8(out)?)) - } - } - } - - #[inline] - fn decode_offset( - &self, - value_count: u64, - bo: ByteOrder, - bigtiff: bool, - limits: &super::Limits, - reader: &mut SmartReader, - decode_fn: F, - ) -> TiffResult - where - R: Read + Seek, - F: Fn(&mut SmartReader) -> TiffResult, - { - let value_count = usize::try_from(value_count)?; - if value_count > limits.decoding_buffer_size / mem::size_of::() { - return Err(TiffError::LimitsExceeded); - } - - let mut v = Vec::with_capacity(value_count); - - let offset = if bigtiff { - self.r(bo).read_u64()? - } else { - self.r(bo).read_u32()?.into() - }; - reader.goto_offset(offset)?; - - for _ in 0..value_count { - v.push(decode_fn(reader)?) - } - Ok(List(v)) - } -} - -/// Extracts a list of BYTE tags stored in an offset -#[inline] -fn offset_to_bytes(n: usize, entry: &Entry) -> TiffResult { - Ok(List( - entry.offset[0..n] - .iter() - .map(|&e| Unsigned(u32::from(e))) - .collect(), - )) -} - -/// Extracts a list of SBYTE tags stored in an offset -#[inline] -fn offset_to_sbytes(n: usize, entry: &Entry) -> TiffResult { - Ok(List( - entry.offset[0..n] - .iter() - .map(|&e| Signed(i32::from(e as i8))) - .collect(), - )) -} - -/// Type representing an Image File Directory -pub type Directory = HashMap; diff --git a/src/decoder/image.rs b/src/decoder/image.rs index 97658f45..077bfedb 100644 --- a/src/decoder/image.rs +++ b/src/decoder/image.rs @@ -1,12 +1,15 @@ -use super::ifd::{Directory, Value}; use super::stream::{ByteOrder, DeflateReader, LZWReader, PackBitsReader}; use super::tag_reader::TagReader; +use super::DecodedEntry; use super::{predict_f32, predict_f64, Limits}; use super::{stream::SmartReader, ChunkType}; +use crate::ifd::{Directory, Value}; use crate::tags::{ CompressionMethod, PhotometricInterpretation, PlanarConfiguration, Predictor, SampleFormat, Tag, }; -use crate::{ColorType, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, UsageError}; +use crate::{ + ColorType, TiffError, TiffFormatError, TiffKind, TiffResult, TiffUnsupportedError, UsageError, +}; use std::io::{self, Cursor, Read, Seek}; use std::sync::Arc; @@ -59,8 +62,8 @@ impl TileAttributes { } #[derive(Debug)] -pub(crate) struct Image { - pub ifd: Option, +pub(crate) struct Image { + pub ifd: Option>>, pub width: u32, pub height: u32, pub bits_per_sample: u8, @@ -78,18 +81,16 @@ pub(crate) struct Image { pub chunk_bytes: Vec, } -impl Image { +impl Image { pub fn from_reader( reader: &mut SmartReader, - ifd: Directory, + ifd: Directory>, limits: &Limits, - bigtiff: bool, - ) -> TiffResult { - let mut tag_reader = TagReader { + ) -> TiffResult> { + let mut tag_reader = TagReader::<_, K> { reader, limits, ifd: &ifd, - bigtiff, }; let width = tag_reader.require_tag(Tag::ImageWidth)?.into_u32()?; @@ -202,6 +203,7 @@ impl Image { let planes = match planar_config { PlanarConfiguration::Chunky => 1, PlanarConfiguration::Planar => samples, + PlanarConfiguration::Unknown(_) => unreachable!(), }; let chunk_type; @@ -284,6 +286,14 @@ impl Image { )); } } + (false, false, false, false) => { + // allow reading Tiff without image data + chunk_type = ChunkType::None; // the ChunkType will make sure an error is thrown later if trying to read image data + strip_decoder = None; + tile_attributes = None; + chunk_offsets = Vec::new(); + chunk_bytes = Vec::new(); + } (_, _, _, _) => { return Err(TiffError::FormatError( TiffFormatError::StripTileTagConflict, @@ -364,6 +374,11 @@ impl Image { vec![self.bits_per_sample; self.samples as usize], ), )), + PhotometricInterpretation::Unknown(_) => Err(TiffError::UnsupportedError( + TiffUnsupportedError::UnsupportedPhotometricInterpretation( + self.photometric_interpretation, + ), + )), } } @@ -466,6 +481,7 @@ impl Image { match self.planar_config { PlanarConfiguration::Chunky => self.samples.into(), PlanarConfiguration::Planar => 1, + PlanarConfiguration::Unknown(_) => unreachable!(), } } @@ -474,6 +490,7 @@ impl Image { match self.planar_config { PlanarConfiguration::Chunky => 1, PlanarConfiguration::Planar => self.samples.into(), + PlanarConfiguration::Unknown(_) => unreachable!(), } } @@ -508,6 +525,11 @@ impl Image { u32::try_from(tile_attrs.tile_length)?, )) } + ChunkType::None => { + return Err(TiffError::FormatError( + TiffFormatError::StripTileTagConflict, + )) + } } } @@ -540,6 +562,11 @@ impl Image { Ok((u32::try_from(tile_width)?, u32::try_from(tile_length)?)) } + ChunkType::None => { + return Err(TiffError::FormatError( + TiffFormatError::StripTileTagConflict, + )) + } } } @@ -580,6 +607,11 @@ impl Image { TiffUnsupportedError::FloatingPointPredictor(color_type), )); } + Predictor::Unknown(code) => { + return Err(TiffError::FormatError(TiffFormatError::UnknownPredictor( + code, + ))); + } }, type_ => { return Err(TiffError::UnsupportedError( @@ -656,7 +688,7 @@ impl Image { samples, byte_order, predictor, - ); + )?; } if photometric_interpretation == PhotometricInterpretation::WhiteIsZero { super::invert_colors(tile, color_type, self.sample_format); @@ -701,7 +733,7 @@ impl Image { samples, byte_order, predictor, - ); + )?; if photometric_interpretation == PhotometricInterpretation::WhiteIsZero { super::invert_colors(row, color_type, self.sample_format); } diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index 60fb8424..c08495b3 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -1,20 +1,27 @@ -use std::collections::{HashMap, HashSet}; -use std::io::{self, Read, Seek}; +use std::collections::HashSet; +use std::convert::TryFrom; +use std::io::{self, Cursor, Read, Seek, Write}; +use crate::encoder::{DirectoryEncoder, GenericTiffEncoder}; use crate::{ - bytecast, ColorType, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, UsageError, + bytecast, ColorType, TiffError, TiffFormatError, TiffKind, TiffKindBig, TiffKindStandard, + TiffResult, TiffUnsupportedError, UsageError, }; -use self::ifd::Directory; +use self::decoded_entry::DecodedEntry; use self::image::Image; +use crate::ifd::{BufferedEntry, Directory, ImageFileDirectory, Value}; use crate::tags::{ - CompressionMethod, PhotometricInterpretation, PlanarConfiguration, Predictor, SampleFormat, - Tag, Type, + CompressionMethod, GpsTag, PhotometricInterpretation, PlanarConfiguration, Predictor, + SampleFormat, Tag, Type, }; use self::stream::{ByteOrder, EndianReader, SmartReader}; -pub mod ifd; +pub type TiffDecoder = GenericTiffDecoder; +pub type BigTiffDecoder = GenericTiffDecoder; + +mod decoded_entry; mod image; mod stream; mod tag_reader; @@ -187,6 +194,7 @@ impl<'a> DecodingBuffer<'a> { pub enum ChunkType { Strip, Tile, + None, } /// Decoding limits @@ -242,17 +250,17 @@ impl Default for Limits { /// /// Currently does not support decoding of interlaced images #[derive(Debug)] -pub struct Decoder +pub struct GenericTiffDecoder where R: Read + Seek, + K: TiffKind, { reader: SmartReader, - bigtiff: bool, limits: Limits, next_ifd: Option, ifd_offsets: Vec, seen_ifds: HashSet, - image: Image, + pub(crate) image: Image, } fn rev_hpredict_nsamp(buf: &mut [u8], bit_depth: u8, samples: usize) { @@ -329,7 +337,7 @@ fn fix_endianness_and_predict( samples: usize, byte_order: ByteOrder, predictor: Predictor, -) { +) -> TiffResult<()> { match predictor { Predictor::None => { fix_endianness(buf, byte_order, bit_depth); @@ -346,7 +354,14 @@ fn fix_endianness_and_predict( _ => unreachable!("Caller should have validated arguments. Please file a bug."), } } + Predictor::Unknown(code) => { + return Err(TiffError::FormatError(TiffFormatError::UnknownPredictor( + code, + ))) + } } + + Ok(()) } fn invert_colors(buf: &mut [u8], color_type: ColorType, sample_format: SampleFormat) { @@ -420,9 +435,9 @@ fn fix_endianness(buf: &mut [u8], byte_order: ByteOrder, bit_depth: u8) { }; } -impl Decoder { +impl GenericTiffDecoder { /// Create a new decoder that decodes from the stream ```r``` - pub fn new(mut r: R) -> TiffResult> { + pub fn new(mut r: R) -> TiffResult> { let mut endianess = Vec::with_capacity(2); (&mut r).take(2).read_to_end(&mut endianess)?; let byte_order = match &*endianess { @@ -459,6 +474,9 @@ impl Decoder { )) } }; + + assert!(K::is_big() == bigtiff, "Tiff format is invalid !"); + let next_ifd = if bigtiff { Some(reader.read_u64()?) } else { @@ -469,9 +487,8 @@ impl Decoder { seen_ifds.insert(*next_ifd.as_ref().unwrap()); let ifd_offsets = vec![*next_ifd.as_ref().unwrap()]; - let mut decoder = Decoder { + let mut decoder = GenericTiffDecoder { reader, - bigtiff, limits: Default::default(), next_ifd, ifd_offsets, @@ -499,7 +516,7 @@ impl Decoder { Ok(decoder) } - pub fn with_limits(mut self, limits: Limits) -> Decoder { + pub fn with_limits(mut self, limits: Limits) -> GenericTiffDecoder { self.limits = limits; self } @@ -512,10 +529,14 @@ impl Decoder { self.image().colortype() } - fn image(&self) -> &Image { + fn image(&self) -> &Image { &self.image } + pub fn inner(&mut self) -> &mut SmartReader { + &mut self.reader + } + /// 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 @@ -543,9 +564,9 @@ impl Decoder { // 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, _next_ifd) = Self::read_ifd(&mut self.reader, self.bigtiff, *ifd_offset)?; + let (ifd, _next_ifd) = Self::read_ifd(&mut self.reader, *ifd_offset)?; - self.image = Image::from_reader(&mut self.reader, ifd, &self.limits, self.bigtiff)?; + self.image = Image::from_reader(&mut self.reader, ifd, &self.limits)?; Ok(()) } else { @@ -555,18 +576,14 @@ impl Decoder { } } - fn next_ifd(&mut self) -> TiffResult<(Directory, Option)> { + fn next_ifd(&mut self) -> TiffResult<(Directory>, Option)> { if self.next_ifd.is_none() { return Err(TiffError::FormatError( TiffFormatError::ImageFileDirectoryNotFound, )); } - let (ifd, next_ifd) = Self::read_ifd( - &mut self.reader, - self.bigtiff, - self.next_ifd.take().unwrap(), - )?; + let (ifd, next_ifd) = Self::read_ifd(&mut self.reader, self.next_ifd.take().unwrap())?; if let Some(next) = next_ifd { if !self.seen_ifds.insert(next) { @@ -585,7 +602,7 @@ impl Decoder { pub fn next_image(&mut self) -> TiffResult<()> { let (ifd, _next_ifd) = self.next_ifd()?; - self.image = Image::from_reader(&mut self.reader, ifd, &self.limits, self.bigtiff)?; + self.image = Image::from_reader(&mut self.reader, ifd, &self.limits)?; Ok(()) } @@ -601,7 +618,7 @@ impl Decoder { #[inline] pub fn read_ifd_offset(&mut self) -> Result { - if self.bigtiff { + if K::is_big() { self.read_long8() } else { self.read_long().map(u64::from) @@ -677,7 +694,7 @@ impl Decoder { /// Reads a TIFF IFA offset/value field #[inline] pub fn read_offset(&mut self) -> TiffResult<[u8; 4]> { - if self.bigtiff { + if K::is_big() { return Err(TiffError::FormatError( TiffFormatError::InconsistentSizesEncountered, )); @@ -712,11 +729,8 @@ impl Decoder { // Tag 2 bytes // Type 2 bytes // Count 4 bytes - // Value 4 bytes either a pointer the value itself - fn read_entry( - reader: &mut SmartReader, - bigtiff: bool, - ) -> TiffResult> { + // Value 4 bytes either a pointer or the value itself + fn read_entry(reader: &mut SmartReader) -> TiffResult)>> { let tag = Tag::from_u16_exhaustive(reader.read_u16()?); let type_ = match Type::from_u16(reader.read_u16()?) { Some(t) => t, @@ -727,39 +741,38 @@ impl Decoder { return Ok(None); } }; - let entry = if bigtiff { - let mut offset = [0; 8]; + let entry = if K::is_big() { + let mut offset = [0; 8]; let count = reader.read_u64()?; reader.read_exact(&mut offset)?; - ifd::Entry::new_u64(type_, count, offset) + DecodedEntry::new(type_, K::convert_offset(count)?, &offset) } else { let mut offset = [0; 4]; - let count = reader.read_u32()?; reader.read_exact(&mut offset)?; - ifd::Entry::new(type_, count, offset) + DecodedEntry::new(type_, count.into(), &offset) }; + Ok(Some((tag, entry))) } /// Reads the IFD starting at the indicated location. - fn read_ifd( + pub fn read_ifd( reader: &mut SmartReader, - bigtiff: bool, ifd_location: u64, - ) -> TiffResult<(Directory, Option)> { + ) -> TiffResult<(Directory>, Option)> { reader.goto_offset(ifd_location)?; + let mut dir = Directory::new(); - let mut dir: Directory = HashMap::new(); - - let num_tags = if bigtiff { + let num_tags = if K::is_big() { reader.read_u64()? } else { reader.read_u16()?.into() }; + for _ in 0..num_tags { - let (tag, entry) = match Self::read_entry(reader, bigtiff)? { + let (tag, entry) = match Self::read_entry(reader)? { Some(val) => val, None => { continue; @@ -768,7 +781,7 @@ impl Decoder { dir.insert(tag, entry); } - let next_ifd = if bigtiff { + let next_ifd = if K::is_big() { reader.read_u64()? } else { reader.read_u32()?.into() @@ -782,19 +795,19 @@ impl Decoder { Ok((dir, next_ifd)) } + pub fn find_tag_entry(&self, tag: Tag) -> Option> { + self.image().ifd.as_ref().and_then(|i| i.get(&tag).cloned()) + } + /// Tries to retrieve a tag. /// Return `Ok(None)` if the tag is not present. - pub fn find_tag(&mut self, tag: Tag) -> TiffResult> { - let entry = match self.image().ifd.as_ref().unwrap().get(&tag) { + pub fn find_tag(&mut self, tag: Tag) -> TiffResult> { + let entry = match self.find_tag_entry(tag) { None => return Ok(None), Some(entry) => entry.clone(), }; - Ok(Some(entry.val( - &self.limits, - self.bigtiff, - &mut self.reader, - )?)) + Ok(Some(entry.val(&self.limits, &mut self.reader)?)) } /// Tries to retrieve a tag and convert it to the desired unsigned type. @@ -836,7 +849,7 @@ impl Decoder { /// Tries to retrieve a tag. /// Returns an error if the tag is not present - pub fn get_tag(&mut self, tag: Tag) -> TiffResult { + pub fn get_tag(&mut self, tag: Tag) -> TiffResult { match self.find_tag(tag)? { Some(val) => Ok(val), None => Err(TiffError::FormatError( @@ -849,6 +862,7 @@ impl Decoder { pub fn get_tag_u32(&mut self, tag: Tag) -> TiffResult { self.get_tag(tag)?.into_u32() } + pub fn get_tag_u64(&mut self, tag: Tag) -> TiffResult { self.get_tag(tag)?.into_u64() } @@ -871,6 +885,7 @@ impl Decoder { pub fn get_tag_u16_vec(&mut self, tag: Tag) -> TiffResult> { self.get_tag(tag)?.into_u16_vec() } + pub fn get_tag_u64_vec(&mut self, tag: Tag) -> TiffResult> { self.get_tag(tag)?.into_u64_vec() } @@ -929,6 +944,11 @@ impl Decoder { let strips = match self.image().planar_config { PlanarConfiguration::Chunky => height / rows_per_strip, PlanarConfiguration::Planar => height / rows_per_strip * self.image().samples as u32, + PlanarConfiguration::Unknown(code) => { + return Err(TiffError::FormatError( + TiffFormatError::UnknownPlanarConfiguration(code), + )) + } }; Ok(strips) @@ -1103,4 +1123,111 @@ impl Decoder { Ok(result) } + + pub fn get_exif_data(&mut self) -> TiffResult> { + // create new IFD + let mut ifd = Directory::new(); + + // copy Exif tags from main IFD + if let Some(ref main_ifd) = self.image.ifd { + for (tag, entry) in main_ifd.iter() { + ifd.insert(tag.clone(), entry.as_buffered(&mut self.reader)?); + } + } + + Ok(ifd) + } + + pub fn get_exif_ifd(&mut self, tag: Tag) -> TiffResult> { + // create new IFD + let mut ifd = Directory::new(); + + if let Some(offset) = self.find_tag(tag)? { + let offset = if K::is_big() { + offset.into_u64()? + } else { + offset.into_u32()?.into() + }; + + let (fetched, _) = Self::read_ifd(&mut self.reader, offset)?; + + // loop through entries + for (tag, value) in fetched.into_iter() { + let b_entry = value.as_buffered(&mut self.reader)?; + ifd.insert(tag, b_entry); + } + } + + Ok(ifd) + } + + pub fn get_gps_ifd(&mut self) -> TiffResult> { + let ifd = self.get_exif_ifd(Tag::GpsIfd)?; + + let mut gps_ifd = ImageFileDirectory::::new(); + ifd.into_iter().for_each(|(t, e)| { + gps_ifd.insert(GpsTag::from_u16(t.to_u16()).unwrap(), e); + }); + + Ok(gps_ifd) + } + + /// Extracts the EXIF metadata (if present) and returns it in a light TIFF format + pub fn read_exif(&mut self) -> TiffResult> { + // create tiff encoder for result + let mut exifdata = Cursor::new(Vec::new()); + let mut encoder = GenericTiffEncoder::<_, T>::new(Write::by_ref(&mut exifdata))?; + + // create new IFD + let mut ifd0 = encoder.new_directory()?; + + // copy Exif tags from main IFD + if let Some(ref main_ifd) = self.image.ifd { + for (tag, entry) in main_ifd.iter() { + let b_entry = entry.as_buffered(&mut self.reader)?; + ifd0.write_tag(*tag, b_entry)?; + } + } + + // copy sub-ifds + self.copy_ifd(Tag::ExifIfd, &mut ifd0)?; + self.copy_ifd(Tag::GpsIfd, &mut ifd0)?; + self.copy_ifd(Tag::InteropIfd, &mut ifd0)?; + + ifd0.finish()?; + + Ok(exifdata.into_inner()) + } + + fn copy_ifd( + &mut self, + tag: Tag, + new_ifd: &mut DirectoryEncoder, + ) -> TiffResult<()> { + let exif_ifd_offset = self.find_tag(tag)?; + if exif_ifd_offset.is_some() { + let offset = if K::is_big() { + exif_ifd_offset.unwrap().into_u64()? + } else { + exif_ifd_offset.unwrap().into_u32()?.into() + }; + + // create sub-ifd + new_ifd.subdirectory_start(); + + let (ifd, _trash1) = Self::read_ifd(&mut self.reader, offset)?; + + // loop through entries + ifd.into_iter().for_each(|(tag, value)| { + let b_entry = value.as_buffered(&mut self.reader).unwrap(); + new_ifd.write_tag(tag, b_entry).unwrap(); + }); + + // return to ifd0 and write offset + let ifd_offset = new_ifd.subdirectory_close()?; + new_ifd.write_tag(tag, ifd_offset as u32)?; + } + + Ok(()) + } } diff --git a/src/decoder/stream.rs b/src/decoder/stream.rs index 8a995b09..91a9fee4 100644 --- a/src/decoder/stream.rs +++ b/src/decoder/stream.rs @@ -3,7 +3,7 @@ use std::io::{self, BufRead, BufReader, Read, Seek, Take}; /// Byte order of the TIFF file. -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] pub enum ByteOrder { /// little endian byte order LittleEndian, diff --git a/src/decoder/tag_reader.rs b/src/decoder/tag_reader.rs index 3ae2d176..3b26c26f 100644 --- a/src/decoder/tag_reader.rs +++ b/src/decoder/tag_reader.rs @@ -1,25 +1,27 @@ use std::io::{Read, Seek}; use crate::tags::Tag; -use crate::{TiffError, TiffFormatError, TiffResult}; +use crate::{TiffError, TiffFormatError, TiffKind, TiffResult}; -use super::ifd::{Directory, Value}; +use super::decoded_entry::DecodedEntry; use super::stream::SmartReader; use super::Limits; +use crate::ifd::{Directory, Value}; -pub(crate) struct TagReader<'a, R: Read + Seek> { +pub(crate) struct TagReader<'a, R: Read + Seek, K: TiffKind> { pub reader: &'a mut SmartReader, - pub ifd: &'a Directory, + pub ifd: &'a Directory>, pub limits: &'a Limits, - pub bigtiff: bool, } -impl<'a, R: Read + Seek> TagReader<'a, R> { + +impl<'a, R: Read + Seek, K: TiffKind> TagReader<'a, R, K> { pub(crate) fn find_tag(&mut self, tag: Tag) -> TiffResult> { Ok(match self.ifd.get(&tag) { - Some(entry) => Some(entry.clone().val(self.limits, self.bigtiff, self.reader)?), + Some(entry) => Some(entry.clone().val(self.limits, self.reader)?), None => None, }) } + pub(crate) fn require_tag(&mut self, tag: Tag) -> TiffResult { match self.find_tag(tag)? { Some(val) => Ok(val), @@ -28,6 +30,7 @@ impl<'a, R: Read + Seek> TagReader<'a, R> { )), } } + pub fn find_tag_uint_vec>(&mut self, tag: Tag) -> TiffResult>> { self.find_tag(tag)? .map(|v| v.into_u64_vec()) diff --git a/src/encoder/directory_encoder.rs b/src/encoder/directory_encoder.rs new file mode 100644 index 00000000..9d026fd0 --- /dev/null +++ b/src/encoder/directory_encoder.rs @@ -0,0 +1,187 @@ +use crate::{ + encoder::{TiffValue, TiffWriter}, + error::{TiffError, TiffResult, UsageError}, + ifd::{BufferedEntry, ImageFileDirectory}, + tags::Tag, + TiffKind, +}; +use std::{ + io::{Seek, Write}, + marker::PhantomData, + mem, +}; + +/// Low level interface to encode ifd directories. +/// +/// You should call `finish` on this when you are finished with it. +/// Encoding can silently fail while this is dropping. +pub struct DirectoryEncoder<'a, W: 'a + Write + Seek, K: TiffKind> { + pub writer: &'a mut TiffWriter, + dropped: bool, + ifd_pointer_pos: u64, + ifd: ImageFileDirectory, + sub_ifd: Option>, + _phantom: PhantomData, +} + +impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> { + pub fn new(writer: &'a mut TiffWriter) -> TiffResult { + // the previous word is the IFD offset position + let ifd_pointer_pos = writer.offset() - mem::size_of::() as u64; + writer.pad_word_boundary()?; // TODO: Do we need to adjust this for BigTiff? + Ok(DirectoryEncoder:: { + writer, + dropped: false, + ifd_pointer_pos, + ifd: ImageFileDirectory::new(), + sub_ifd: None, + _phantom: ::std::marker::PhantomData, + }) + } + + pub fn contains(&self, tag: &Tag) -> bool { + self.ifd.contains_key(&(*tag).into()) + } + + /// Start writing to sub-IFD + pub fn subdirectory_start(&mut self) { + self.sub_ifd = Some(ImageFileDirectory::new()); + } + + /// Stop writing to sub-IFD and resume master IFD, returns offset of sub-IFD + pub fn subdirectory_close(&mut self) -> TiffResult { + let ifd = self + .sub_ifd + .to_owned() + .ok_or(TiffError::UsageError(UsageError::CloseNonExistentIfd))?; + self.sub_ifd = None; + + let offset = self.write_directory(ifd)?; + K::write_offset(self.writer, 0)?; + + Ok(offset) + } + + /// Write a single ifd tag. + pub fn write_tag(&mut self, tag: Tag, value: T) -> TiffResult<()> { + let mut bytes = Vec::with_capacity(value.bytes()); + { + let mut writer = TiffWriter::new(&mut bytes); + value.write(&mut writer)?; + } + + let active_ifd = match &self.sub_ifd { + None => &mut self.ifd, + Some(_v) => self.sub_ifd.as_mut().unwrap(), + }; + + active_ifd.insert( + tag.into(), + BufferedEntry { + type_: value.is_type(), + count: value.count().try_into()?, + data: bytes, + }, + ); + + Ok(()) + } + + fn write_directory>( + &mut self, + mut ifd: ImageFileDirectory, + ) -> TiffResult { + // Start by writing out all values + for &mut BufferedEntry { + data: ref mut bytes, + .. + } in ifd.values_mut() + { + // Amount of bytes available in the Tiff type + let data_bytes = K::OffsetType::BYTE_LEN as usize; + + if bytes.len() > data_bytes { + // If the data does not fit in the entry + // Record the offset + let offset = self.writer.offset(); + self.writer.write_bytes(bytes)?; + // Overwrite the data with a buffer matching the offset size + *bytes = vec![0; data_bytes]; // TODO Maybe just truncate ? + // Write the offset to the data + K::write_offset(&mut TiffWriter::new(bytes as &mut [u8]), offset)?; + } else { + // Pad the data with zeros to the correct length + while bytes.len() < data_bytes { + bytes.push(0); + } + } + } + + // Record the offset + let ifd_offset = self.writer.offset(); + + // Actually write the ifd + K::write_entry_count(self.writer, ifd.len())?; + for ( + tag, + BufferedEntry { + type_: field_type, + count, + data: offset, // At this point data is of size K::OffsetType::BYTE_LEN + }, + ) in ifd.into_iter() + { + self.writer.write_u16(tag.into())?; + self.writer.write_u16(field_type.to_u16())?; + K::convert_offset(count)?.write(self.writer)?; + self.writer.write_bytes(&offset)?; + } + + Ok(ifd_offset) + } + + /// Write some data to the tiff file, the offset of the data is returned. + /// + /// This could be used to write tiff strips. + pub fn write_data(&mut self, value: T) -> TiffResult { + let offset = self.writer.offset(); + value.write(self.writer)?; + Ok(offset) + } + + /// Provides the number of bytes written by the underlying TiffWriter during the last call. + pub fn last_written(&self) -> u64 { + self.writer.last_written() + } + + pub fn finish_internal(&mut self) -> TiffResult<()> { + if self.sub_ifd.is_some() { + self.subdirectory_close()?; + } + + let ifd_pointer = self.write_directory(self.ifd.to_owned())?; + let curr_pos = self.writer.offset(); + + self.writer.goto_offset(self.ifd_pointer_pos)?; + K::write_offset(self.writer, ifd_pointer)?; + self.writer.goto_offset(curr_pos)?; + K::write_offset(self.writer, 0)?; + + self.dropped = true; + + Ok(()) + } + + /// Write out the ifd directory. + pub fn finish(mut self) -> TiffResult<()> { + self.finish_internal() + } +} + +impl<'a, W: Write + Seek, K: TiffKind> Drop for DirectoryEncoder<'a, W, K> { + fn drop(&mut self) { + if !self.dropped { + let _ = self.finish_internal(); + } + } +} diff --git a/src/encoder/mod.rs b/src/encoder/mod.rs index 152a77a6..8492b8c6 100644 --- a/src/encoder/mod.rs +++ b/src/encoder/mod.rs @@ -1,28 +1,29 @@ -pub use tiff_value::*; - -use std::{ - cmp, - collections::BTreeMap, - io::{self, Seek, Write}, - marker::PhantomData, - mem, - num::TryFromIntError, -}; - -use crate::{ - error::TiffResult, - tags::{CompressionMethod, ResolutionUnit, Tag}, - TiffError, TiffFormatError, -}; - pub mod colortype; pub mod compression; +mod directory_encoder; mod tiff_value; mod writer; use self::colortype::*; use self::compression::*; -use self::writer::*; +pub use self::writer::*; +use crate::{ + decoder::GenericTiffDecoder, + error::TiffResult, + ifd::Directory, + tags::{CompressionMethod, ResolutionUnit, Tag}, + TiffError, TiffFormatError, TiffKind, TiffKindBig, TiffKindStandard, +}; +pub use directory_encoder::DirectoryEncoder; +use std::{ + cmp, + io::{self, Cursor, Read, Seek, Write}, + marker::PhantomData, +}; +pub use tiff_value::*; + +pub type TiffEncoder = GenericTiffEncoder; +pub type BigTiffEncoder = GenericTiffEncoder; /// Encoder for Tiff and BigTiff files. /// @@ -40,47 +41,25 @@ use self::writer::*; /// use tiff::encoder::*; /// /// // create a standard Tiff file -/// let mut tiff = TiffEncoder::new(&mut file).unwrap(); +/// let mut tiff = GenericTiffEncoder::<_, tiff::TiffKindStandard>::new(&mut file).unwrap(); /// tiff.write_image::(100, 100, &image_data).unwrap(); /// /// // create a BigTiff file -/// let mut bigtiff = TiffEncoder::new_big(&mut file).unwrap(); +/// let mut bigtiff = GenericTiffEncoder::<_, tiff::TiffKindBig>::new(&mut file).unwrap(); /// bigtiff.write_image::(100, 100, &image_data).unwrap(); /// /// # } /// ``` -pub struct TiffEncoder { +pub struct GenericTiffEncoder { writer: TiffWriter, kind: PhantomData, } -/// Constructor functions to create standard Tiff files. -impl TiffEncoder { - /// Creates a new encoder for standard Tiff files. - /// - /// To create BigTiff files, use [`new_big`][TiffEncoder::new_big] or - /// [`new_generic`][TiffEncoder::new_generic]. - pub fn new(writer: W) -> TiffResult> { - TiffEncoder::new_generic(writer) - } -} - -/// Constructor functions to create BigTiff files. -impl TiffEncoder { - /// Creates a new encoder for BigTiff files. - /// - /// To create standard Tiff files, use [`new`][TiffEncoder::new] or - /// [`new_generic`][TiffEncoder::new_generic]. - pub fn new_big(writer: W) -> TiffResult { - TiffEncoder::new_generic(writer) - } -} - /// Generic functions that are available for both Tiff and BigTiff encoders. -impl TiffEncoder { +impl GenericTiffEncoder { /// Creates a new Tiff or BigTiff encoder, inferred from the return type. - pub fn new_generic(writer: W) -> TiffResult { - let mut encoder = TiffEncoder { + pub fn new(writer: W) -> TiffResult { + let mut encoder = GenericTiffEncoder { writer: TiffWriter::new(writer), kind: PhantomData, }; @@ -92,7 +71,7 @@ impl TiffEncoder { /// Create a [`DirectoryEncoder`] to encode an ifd directory. pub fn new_directory(&mut self) -> TiffResult> { - DirectoryEncoder::new(&mut self.writer) + DirectoryEncoder::::new(&mut self.writer) } /// Create an [`ImageEncoder`] to encode an image one slice at a time. @@ -101,7 +80,7 @@ impl TiffEncoder { width: u32, height: u32, ) -> TiffResult> { - let encoder = DirectoryEncoder::new(&mut self.writer)?; + let encoder = DirectoryEncoder::::new(&mut self.writer)?; ImageEncoder::new(encoder, width, height) } @@ -112,7 +91,7 @@ impl TiffEncoder { height: u32, compression: D, ) -> TiffResult> { - let encoder = DirectoryEncoder::new(&mut self.writer)?; + let encoder = DirectoryEncoder::::new(&mut self.writer)?; ImageEncoder::with_compression(encoder, width, height, compression) } @@ -126,7 +105,7 @@ impl TiffEncoder { where [C::Inner]: TiffValue, { - let encoder = DirectoryEncoder::new(&mut self.writer)?; + let encoder = DirectoryEncoder::::new(&mut self.writer)?; let image: ImageEncoder = ImageEncoder::new(encoder, width, height)?; image.write_data(data) } @@ -142,143 +121,13 @@ impl TiffEncoder { where [C::Inner]: TiffValue, { - let encoder = DirectoryEncoder::new(&mut self.writer)?; + let encoder = DirectoryEncoder::::new(&mut self.writer)?; let image: ImageEncoder = ImageEncoder::with_compression(encoder, width, height, compression)?; image.write_data(data) } } -/// Low level interface to encode ifd directories. -/// -/// You should call `finish` on this when you are finished with it. -/// Encoding can silently fail while this is dropping. -pub struct DirectoryEncoder<'a, W: 'a + Write + Seek, K: TiffKind> { - writer: &'a mut TiffWriter, - dropped: bool, - // We use BTreeMap to make sure tags are written in correct order - ifd_pointer_pos: u64, - ifd: BTreeMap>, -} - -impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> { - fn new(writer: &'a mut TiffWriter) -> TiffResult { - // the previous word is the IFD offset position - let ifd_pointer_pos = writer.offset() - mem::size_of::() as u64; - writer.pad_word_boundary()?; // TODO: Do we need to adjust this for BigTiff? - Ok(DirectoryEncoder { - writer, - dropped: false, - ifd_pointer_pos, - ifd: BTreeMap::new(), - }) - } - - /// Write a single ifd tag. - pub fn write_tag(&mut self, tag: Tag, value: T) -> TiffResult<()> { - let mut bytes = Vec::with_capacity(value.bytes()); - { - let mut writer = TiffWriter::new(&mut bytes); - value.write(&mut writer)?; - } - - self.ifd.insert( - tag.to_u16(), - DirectoryEntry { - data_type: ::FIELD_TYPE.to_u16(), - count: value.count().try_into()?, - data: bytes, - }, - ); - - Ok(()) - } - - fn write_directory(&mut self) -> TiffResult { - // Start by writing out all values - for &mut DirectoryEntry { - data: ref mut bytes, - .. - } in self.ifd.values_mut() - { - let data_bytes = mem::size_of::(); - - if bytes.len() > data_bytes { - let offset = self.writer.offset(); - self.writer.write_bytes(bytes)?; - *bytes = vec![0; data_bytes]; - let mut writer = TiffWriter::new(bytes as &mut [u8]); - K::write_offset(&mut writer, offset)?; - } else { - while bytes.len() < data_bytes { - bytes.push(0); - } - } - } - - let offset = self.writer.offset(); - - K::write_entry_count(self.writer, self.ifd.len())?; - for ( - tag, - DirectoryEntry { - data_type: field_type, - count, - data: offset, - }, - ) in self.ifd.iter() - { - self.writer.write_u16(*tag)?; - self.writer.write_u16(*field_type)?; - (*count).write(self.writer)?; - self.writer.write_bytes(offset)?; - } - - Ok(offset) - } - - /// Write some data to the tiff file, the offset of the data is returned. - /// - /// This could be used to write tiff strips. - pub fn write_data(&mut self, value: T) -> TiffResult { - let offset = self.writer.offset(); - value.write(self.writer)?; - Ok(offset) - } - - /// Provides the number of bytes written by the underlying TiffWriter during the last call. - fn last_written(&self) -> u64 { - self.writer.last_written() - } - - fn finish_internal(&mut self) -> TiffResult<()> { - let ifd_pointer = self.write_directory()?; - let curr_pos = self.writer.offset(); - - self.writer.goto_offset(self.ifd_pointer_pos)?; - K::write_offset(self.writer, ifd_pointer)?; - self.writer.goto_offset(curr_pos)?; - K::write_offset(self.writer, 0)?; - - self.dropped = true; - - Ok(()) - } - - /// Write out the ifd directory. - pub fn finish(mut self) -> TiffResult<()> { - self.finish_internal() - } -} - -impl<'a, W: Write + Seek, K: TiffKind> Drop for DirectoryEncoder<'a, W, K> { - fn drop(&mut self) { - if !self.dropped { - let _ = self.finish_internal(); - } - } -} - /// Type to encode images strip by strip. /// /// You should call `finish` on this when you are finished with it. @@ -293,7 +142,7 @@ impl<'a, W: Write + Seek, K: TiffKind> Drop for DirectoryEncoder<'a, W, K> { /// use tiff::encoder::*; /// use tiff::tags::Tag; /// -/// let mut tiff = TiffEncoder::new(&mut file).unwrap(); +/// let mut tiff = GenericTiffEncoder::<_, tiff::TiffKindStandard>::new(&mut file).unwrap(); /// let mut image = tiff.new_image::(100, 100).unwrap(); /// /// // You can encode tags here @@ -507,6 +356,64 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind, D: Compression> self.encoder.write_tag(Tag::YResolution, value).unwrap(); } + pub fn set_exif_tag(&mut self, tag: Tag, value: E) -> TiffResult<()> { + self.encoder.write_tag(tag, value) + } + + pub fn set_exif_tags(&mut self, ifd: Directory) -> TiffResult<()> { + for (tag, value) in ifd.into_iter() { + self.encoder.write_tag(tag, value)?; + } + + Ok(()) + } + + /// Write Exif data from TIFF encoded byte block + pub fn exif_tags(&mut self, source: Vec) -> TiffResult<()> { + let mut decoder = GenericTiffDecoder::<_, F>::new(Cursor::new(source))?; + + for (t, e) in decoder.get_exif_data()?.into_iter() { + if !self.encoder.contains(&t) { + self.encoder.write_tag(t, e)?; + } + } + + // copy sub-ifds + self.copy_ifd(Tag::ExifIfd, &mut decoder)?; + self.copy_ifd(Tag::GpsIfd, &mut decoder)?; + self.copy_ifd(Tag::InteropIfd, &mut decoder)?; + + Ok(()) + } + + fn copy_ifd( + &mut self, + tag: Tag, + decoder: &mut GenericTiffDecoder, + ) -> TiffResult<()> { + let exif_ifd_offset = decoder.find_tag(tag)?; + if exif_ifd_offset.is_some() { + let offset = exif_ifd_offset.unwrap().into_u32()?.into(); + + // create sub-ifd + self.encoder.subdirectory_start(); + + let (ifd, _trash1) = GenericTiffDecoder::<_, F>::read_ifd(decoder.inner(), offset)?; + + // loop through entries + ifd.into_iter().for_each(|(tag, value)| { + let b_entry = value.as_buffered(decoder.inner()).unwrap(); + self.encoder.write_tag(tag, b_entry).unwrap(); + }); + + // return to ifd0 and write offset + let ifd_offset = self.encoder.subdirectory_close()?; + self.encoder.write_tag(tag, ifd_offset as u32)?; + } + + Ok(()) + } + /// Set image number of lines per strip /// /// This function needs to be called before any calls to `write_data` or @@ -561,120 +468,3 @@ impl<'a, W: Write + Seek, C: ColorType, K: TiffKind, D: Compression> Drop } } } - -struct DirectoryEntry { - data_type: u16, - count: S, - data: Vec, -} - -/// Trait to abstract over Tiff/BigTiff differences. -/// -/// Implemented for [`TiffKindStandard`] and [`TiffKindBig`]. -pub trait TiffKind { - /// The type of offset fields, `u32` for normal Tiff, `u64` for BigTiff. - type OffsetType: TryFrom + Into + TiffValue; - - /// Needed for the `convert_slice` method. - type OffsetArrayType: ?Sized + TiffValue; - - /// Write the (Big)Tiff header. - fn write_header(writer: &mut TiffWriter) -> TiffResult<()>; - - /// Convert a file offset to `Self::OffsetType`. - /// - /// This returns an error for normal Tiff if the offset is larger than `u32::MAX`. - fn convert_offset(offset: u64) -> TiffResult; - - /// Write an offset value to the given writer. - /// - /// Like `convert_offset`, this errors if `offset > u32::MAX` for normal Tiff. - fn write_offset(writer: &mut TiffWriter, offset: u64) -> TiffResult<()>; - - /// Write the IFD entry count field with the given `count` value. - /// - /// The entry count field is an `u16` for normal Tiff and `u64` for BigTiff. Errors - /// if the given `usize` is larger than the representable values. - fn write_entry_count(writer: &mut TiffWriter, count: usize) -> TiffResult<()>; - - /// Internal helper method for satisfying Rust's type checker. - /// - /// The `TiffValue` trait is implemented for both primitive values (e.g. `u8`, `u32`) and - /// slices of primitive values (e.g. `[u8]`, `[u32]`). However, this is not represented in - /// the type system, so there is no guarantee that that for all `T: TiffValue` there is also - /// an implementation of `TiffValue` for `[T]`. This method works around that problem by - /// providing a conversion from `[T]` to some value that implements `TiffValue`, thereby - /// making all slices of `OffsetType` usable with `write_tag` and similar methods. - /// - /// Implementations of this trait should always set `OffsetArrayType` to `[OffsetType]`. - fn convert_slice(slice: &[Self::OffsetType]) -> &Self::OffsetArrayType; -} - -/// Create a standard Tiff file. -pub struct TiffKindStandard; - -impl TiffKind for TiffKindStandard { - type OffsetType = u32; - type OffsetArrayType = [u32]; - - fn write_header(writer: &mut TiffWriter) -> TiffResult<()> { - write_tiff_header(writer)?; - // blank the IFD offset location - writer.write_u32(0)?; - - Ok(()) - } - - fn convert_offset(offset: u64) -> TiffResult { - Ok(Self::OffsetType::try_from(offset)?) - } - - fn write_offset(writer: &mut TiffWriter, offset: u64) -> TiffResult<()> { - writer.write_u32(u32::try_from(offset)?)?; - Ok(()) - } - - fn write_entry_count(writer: &mut TiffWriter, count: usize) -> TiffResult<()> { - writer.write_u16(u16::try_from(count)?)?; - - Ok(()) - } - - fn convert_slice(slice: &[Self::OffsetType]) -> &Self::OffsetArrayType { - slice - } -} - -/// Create a BigTiff file. -pub struct TiffKindBig; - -impl TiffKind for TiffKindBig { - type OffsetType = u64; - type OffsetArrayType = [u64]; - - fn write_header(writer: &mut TiffWriter) -> TiffResult<()> { - write_bigtiff_header(writer)?; - // blank the IFD offset location - writer.write_u64(0)?; - - Ok(()) - } - - fn convert_offset(offset: u64) -> TiffResult { - Ok(offset) - } - - fn write_offset(writer: &mut TiffWriter, offset: u64) -> TiffResult<()> { - writer.write_u64(offset)?; - Ok(()) - } - - fn write_entry_count(writer: &mut TiffWriter, count: usize) -> TiffResult<()> { - writer.write_u64(u64::try_from(count)?)?; - Ok(()) - } - - fn convert_slice(slice: &[Self::OffsetType]) -> &Self::OffsetArrayType { - slice - } -} diff --git a/src/encoder/tiff_value.rs b/src/encoder/tiff_value.rs index 43653f42..0cbd017f 100644 --- a/src/encoder/tiff_value.rs +++ b/src/encoder/tiff_value.rs @@ -7,7 +7,7 @@ use super::writer::TiffWriter; /// Trait for types that can be encoded in a tiff file pub trait TiffValue { const BYTE_LEN: u8; - const FIELD_TYPE: Type; + fn is_type(&self) -> Type; fn count(&self) -> usize; fn bytes(&self) -> usize { self.count() * usize::from(Self::BYTE_LEN) @@ -28,7 +28,9 @@ pub trait TiffValue { impl TiffValue for [u8] { const BYTE_LEN: u8 = 1; - const FIELD_TYPE: Type = Type::BYTE; + fn is_type(&self) -> Type { + Type::BYTE + } fn count(&self) -> usize { self.len() @@ -41,7 +43,9 @@ impl TiffValue for [u8] { impl TiffValue for [i8] { const BYTE_LEN: u8 = 1; - const FIELD_TYPE: Type = Type::SBYTE; + fn is_type(&self) -> Type { + Type::SBYTE + } fn count(&self) -> usize { self.len() @@ -54,7 +58,9 @@ impl TiffValue for [i8] { impl TiffValue for [u16] { const BYTE_LEN: u8 = 2; - const FIELD_TYPE: Type = Type::SHORT; + fn is_type(&self) -> Type { + Type::SHORT + } fn count(&self) -> usize { self.len() @@ -67,7 +73,9 @@ impl TiffValue for [u16] { impl TiffValue for [i16] { const BYTE_LEN: u8 = 2; - const FIELD_TYPE: Type = Type::SSHORT; + fn is_type(&self) -> Type { + Type::SSHORT + } fn count(&self) -> usize { self.len() @@ -80,7 +88,9 @@ impl TiffValue for [i16] { impl TiffValue for [u32] { const BYTE_LEN: u8 = 4; - const FIELD_TYPE: Type = Type::LONG; + fn is_type(&self) -> Type { + Type::LONG + } fn count(&self) -> usize { self.len() @@ -93,7 +103,9 @@ impl TiffValue for [u32] { impl TiffValue for [i32] { const BYTE_LEN: u8 = 4; - const FIELD_TYPE: Type = Type::SLONG; + fn is_type(&self) -> Type { + Type::SLONG + } fn count(&self) -> usize { self.len() @@ -106,7 +118,9 @@ impl TiffValue for [i32] { impl TiffValue for [u64] { const BYTE_LEN: u8 = 8; - const FIELD_TYPE: Type = Type::LONG8; + fn is_type(&self) -> Type { + Type::LONG8 + } fn count(&self) -> usize { self.len() @@ -119,7 +133,9 @@ impl TiffValue for [u64] { impl TiffValue for [i64] { const BYTE_LEN: u8 = 8; - const FIELD_TYPE: Type = Type::SLONG8; + fn is_type(&self) -> Type { + Type::SLONG8 + } fn count(&self) -> usize { self.len() @@ -132,7 +148,9 @@ impl TiffValue for [i64] { impl TiffValue for [f32] { const BYTE_LEN: u8 = 4; - const FIELD_TYPE: Type = Type::FLOAT; + fn is_type(&self) -> Type { + Type::FLOAT + } fn count(&self) -> usize { self.len() @@ -146,7 +164,9 @@ impl TiffValue for [f32] { impl TiffValue for [f64] { const BYTE_LEN: u8 = 8; - const FIELD_TYPE: Type = Type::DOUBLE; + fn is_type(&self) -> Type { + Type::DOUBLE + } fn count(&self) -> usize { self.len() @@ -160,7 +180,9 @@ impl TiffValue for [f64] { impl TiffValue for u8 { const BYTE_LEN: u8 = 1; - const FIELD_TYPE: Type = Type::BYTE; + fn is_type(&self) -> Type { + Type::BYTE + } fn count(&self) -> usize { 1 @@ -178,7 +200,9 @@ impl TiffValue for u8 { impl TiffValue for i8 { const BYTE_LEN: u8 = 1; - const FIELD_TYPE: Type = Type::SBYTE; + fn is_type(&self) -> Type { + Type::SBYTE + } fn count(&self) -> usize { 1 @@ -196,7 +220,9 @@ impl TiffValue for i8 { impl TiffValue for u16 { const BYTE_LEN: u8 = 2; - const FIELD_TYPE: Type = Type::SHORT; + fn is_type(&self) -> Type { + Type::SHORT + } fn count(&self) -> usize { 1 @@ -214,7 +240,9 @@ impl TiffValue for u16 { impl TiffValue for i16 { const BYTE_LEN: u8 = 2; - const FIELD_TYPE: Type = Type::SSHORT; + fn is_type(&self) -> Type { + Type::SSHORT + } fn count(&self) -> usize { 1 @@ -232,7 +260,9 @@ impl TiffValue for i16 { impl TiffValue for u32 { const BYTE_LEN: u8 = 4; - const FIELD_TYPE: Type = Type::LONG; + fn is_type(&self) -> Type { + Type::LONG + } fn count(&self) -> usize { 1 @@ -250,7 +280,9 @@ impl TiffValue for u32 { impl TiffValue for i32 { const BYTE_LEN: u8 = 4; - const FIELD_TYPE: Type = Type::SLONG; + fn is_type(&self) -> Type { + Type::SLONG + } fn count(&self) -> usize { 1 @@ -268,7 +300,9 @@ impl TiffValue for i32 { impl TiffValue for u64 { const BYTE_LEN: u8 = 8; - const FIELD_TYPE: Type = Type::LONG8; + fn is_type(&self) -> Type { + Type::LONG8 + } fn count(&self) -> usize { 1 @@ -286,7 +320,9 @@ impl TiffValue for u64 { impl TiffValue for i64 { const BYTE_LEN: u8 = 8; - const FIELD_TYPE: Type = Type::SLONG8; + fn is_type(&self) -> Type { + Type::SLONG8 + } fn count(&self) -> usize { 1 @@ -304,7 +340,9 @@ impl TiffValue for i64 { impl TiffValue for f32 { const BYTE_LEN: u8 = 4; - const FIELD_TYPE: Type = Type::FLOAT; + fn is_type(&self) -> Type { + Type::FLOAT + } fn count(&self) -> usize { 1 @@ -322,7 +360,9 @@ impl TiffValue for f32 { impl TiffValue for f64 { const BYTE_LEN: u8 = 8; - const FIELD_TYPE: Type = Type::DOUBLE; + fn is_type(&self) -> Type { + Type::DOUBLE + } fn count(&self) -> usize { 1 @@ -340,7 +380,9 @@ impl TiffValue for f64 { impl TiffValue for Ifd { const BYTE_LEN: u8 = 4; - const FIELD_TYPE: Type = Type::IFD; + fn is_type(&self) -> Type { + Type::IFD + } fn count(&self) -> usize { 1 @@ -358,7 +400,9 @@ impl TiffValue for Ifd { impl TiffValue for Ifd8 { const BYTE_LEN: u8 = 8; - const FIELD_TYPE: Type = Type::IFD8; + fn is_type(&self) -> Type { + Type::IFD8 + } fn count(&self) -> usize { 1 @@ -376,7 +420,9 @@ impl TiffValue for Ifd8 { impl TiffValue for Rational { const BYTE_LEN: u8 = 8; - const FIELD_TYPE: Type = Type::RATIONAL; + fn is_type(&self) -> Type { + Type::RATIONAL + } fn count(&self) -> usize { 1 @@ -399,7 +445,9 @@ impl TiffValue for Rational { impl TiffValue for SRational { const BYTE_LEN: u8 = 8; - const FIELD_TYPE: Type = Type::SRATIONAL; + fn is_type(&self) -> Type { + Type::SRATIONAL + } fn count(&self) -> usize { 1 @@ -422,7 +470,9 @@ impl TiffValue for SRational { impl TiffValue for str { const BYTE_LEN: u8 = 1; - const FIELD_TYPE: Type = Type::ASCII; + fn is_type(&self) -> Type { + Type::ASCII + } fn count(&self) -> usize { self.len() + 1 @@ -452,7 +502,9 @@ impl TiffValue for str { impl<'a, T: TiffValue + ?Sized> TiffValue for &'a T { const BYTE_LEN: u8 = T::BYTE_LEN; - const FIELD_TYPE: Type = T::FIELD_TYPE; + fn is_type(&self) -> Type { + (*self).is_type() + } fn count(&self) -> usize { (*self).count() @@ -471,7 +523,9 @@ macro_rules! impl_tiff_value_for_contiguous_sequence { ($inner_type:ty; $bytes:expr; $field_type:expr) => { impl $crate::encoder::TiffValue for [$inner_type] { const BYTE_LEN: u8 = $bytes; - const FIELD_TYPE: Type = $field_type; + fn is_type(&self) -> Type { + $field_type + } fn count(&self) -> usize { self.len() diff --git a/src/error.rs b/src/error.rs index 7dc781e0..daafba89 100644 --- a/src/error.rs +++ b/src/error.rs @@ -8,7 +8,8 @@ use std::sync::Arc; use jpeg::UnsupportedFeature; -use crate::decoder::{ifd::Value, ChunkType}; +use crate::decoder::ChunkType; +use crate::ifd::Value; use crate::tags::{ CompressionMethod, PhotometricInterpretation, PlanarConfiguration, SampleFormat, Tag, }; @@ -156,6 +157,7 @@ pub enum TiffUnsupportedError { InterpretationWithBits(PhotometricInterpretation, Vec), UnknownInterpretation, UnknownCompressionMethod, + UnsupportedPhotometricInterpretation(PhotometricInterpretation), UnsupportedCompressionMethod(CompressionMethod), UnsupportedSampleDepth(u8), UnsupportedSampleFormat(Vec), @@ -210,6 +212,9 @@ impl fmt::Display for TiffUnsupportedError { UnsupportedBitsPerChannel(bits) => { write!(fmt, "{} bits per channel not supported", bits) } + UnsupportedPhotometricInterpretation(pi) => { + write!(fmt, "Unsupported photometric interpretation: {}", pi) + } UnsupportedPlanarConfig(config) => { write!(fmt, "Unsupported planar configuration “{:?}”.", config) } @@ -236,12 +241,14 @@ impl fmt::Display for TiffUnsupportedError { pub enum UsageError { InvalidChunkType(ChunkType, ChunkType), InvalidChunkIndex(u32), + CloseNonExistentIfd, } impl fmt::Display for UsageError { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { use self::UsageError::*; match *self { + CloseNonExistentIfd => write!(fmt, "Attempted to close a non-existent IFD"), InvalidChunkType(expected, actual) => { write!( fmt, diff --git a/src/ifd.rs b/src/ifd.rs new file mode 100644 index 00000000..c39b9c01 --- /dev/null +++ b/src/ifd.rs @@ -0,0 +1,654 @@ +//! Abstractions over TIFF tags + +use std::borrow::Cow; +use std::collections::BTreeMap; +use std::mem::size_of; + +use crate::encoder::TiffValue; +use crate::tags::{DispatchFormat, Tag, Type}; +use crate::{TiffError, TiffFormatError, TiffResult}; + +use self::Value::{ + Ascii, Byte, Double, Float, Ifd, IfdBig, List, Rational, RationalBig, SRational, SRationalBig, + Short, Signed, SignedBig, SignedByte, SignedShort, Unsigned, UnsignedBig, +}; + +use itertools::Itertools; + +#[allow(unused_qualifications)] +#[derive(Debug, Clone, PartialEq)] +#[non_exhaustive] +pub enum Value { + Byte(u8), + Short(u16), + SignedByte(i8), + SignedShort(i16), + Signed(i32), + SignedBig(i64), + Unsigned(u32), + UnsignedBig(u64), + Float(f32), + Double(f64), + List(Vec), + Rational(u32, u32), + RationalBig(u64, u64), + SRational(i32, i32), + SRationalBig(i64, i64), + Ascii(String), + Ifd(u32), + IfdBig(u64), + Undefined(u8), +} + +impl std::fmt::Display for Value { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + match self { + Value::Byte(e) => write!(f, "{e}"), + Value::Short(e) => write!(f, "{e}"), + Value::SignedByte(e) => write!(f, "{e}"), + Value::SignedShort(e) => write!(f, "{e}"), + Value::Signed(e) => write!(f, "{e}"), + Value::SignedBig(e) => write!(f, "{e}"), + Value::Unsigned(e) => write!(f, "{e}"), + Value::UnsignedBig(e) => write!(f, "{e}"), + Value::Float(e) => write!(f, "{e}"), + Value::Double(e) => write!(f, "{e}"), + Value::Rational(e1, e2) => { + let a_mul = (*e1 as u128) * 1000; + let b = *e2 as u128; + let div = a_mul / b; + + let frac = div % 1000; + let rest = div / 1000; + + if frac != 0 { + write!(f, "{rest}.{frac:#03}") + } else { + write!(f, "{rest}") + } + } + Value::RationalBig(e1, e2) => write!(f, "{e1}/{e2}"), + Value::SRational(e1, e2) => write!(f, "{e1}/{e2}"), + Value::SRationalBig(e1, e2) => write!(f, "{e1}/{e2}"), + Value::Ascii(e) => write!(f, "{e}"), + Value::Ifd(e) => write!(f, "IFD offset: {e}"), + Value::IfdBig(e) => write!(f, "IFD offset: {e}"), + Value::Undefined(e) => write!(f, "{e}"), + Value::List(_) => todo!(), + } + } +} + +impl Value { + pub fn into_u8(self) -> TiffResult { + match self { + Byte(val) => Ok(val), + val => Err(TiffError::FormatError(TiffFormatError::ByteExpected(val))), + } + } + + pub fn into_i8(self) -> TiffResult { + match self { + SignedByte(val) => Ok(val), + val => Err(TiffError::FormatError(TiffFormatError::SignedByteExpected( + val, + ))), + } + } + + pub fn into_u16(self) -> TiffResult { + 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_i16(self) -> TiffResult { + match self { + SignedByte(val) => Ok(val.into()), + SignedShort(val) => Ok(val), + Signed(val) => Ok(i16::try_from(val)?), + SignedBig(val) => Ok(i16::try_from(val)?), + val => Err(TiffError::FormatError( + TiffFormatError::SignedShortExpected(val), + )), + } + } + + pub fn into_u32(self) -> TiffResult { + match self { + Short(val) => Ok(val.into()), + Unsigned(val) => Ok(val), + UnsignedBig(val) => Ok(u32::try_from(val)?), + Ifd(val) => Ok(val), + IfdBig(val) => Ok(u32::try_from(val)?), + val => Err(TiffError::FormatError( + TiffFormatError::UnsignedIntegerExpected(val), + )), + } + } + + pub fn into_i32(self) -> TiffResult { + match self { + SignedByte(val) => Ok(val.into()), + SignedShort(val) => Ok(val.into()), + Signed(val) => Ok(val), + SignedBig(val) => Ok(i32::try_from(val)?), + val => Err(TiffError::FormatError( + TiffFormatError::SignedIntegerExpected(val), + )), + } + } + + pub fn into_u64(self) -> TiffResult { + match self { + Short(val) => Ok(val.into()), + Unsigned(val) => Ok(val.into()), + UnsignedBig(val) => Ok(val), + Ifd(val) => Ok(val.into()), + IfdBig(val) => Ok(val), + val => Err(TiffError::FormatError( + TiffFormatError::UnsignedIntegerExpected(val), + )), + } + } + + pub fn into_i64(self) -> TiffResult { + match self { + SignedByte(val) => Ok(val.into()), + SignedShort(val) => Ok(val.into()), + Signed(val) => Ok(val.into()), + SignedBig(val) => Ok(val), + val => Err(TiffError::FormatError( + TiffFormatError::SignedIntegerExpected(val), + )), + } + } + + pub fn into_f32(self) -> TiffResult { + match self { + Float(val) => Ok(val), + Rational(num, den) => Ok(num as f32 / den as f32), + SRational(num, den) => Ok(num as f32 / den as f32), + val => Err(TiffError::FormatError( + TiffFormatError::SignedIntegerExpected(val), + )), + } + } + + pub fn into_f64(self) -> TiffResult { + match self { + Float(val) => Ok(val as f64), + Rational(num, den) => Ok(num as f64 / den as f64), + SRational(num, den) => Ok(num as f64 / den as f64), + Double(val) => Ok(val), + val => Err(TiffError::FormatError( + TiffFormatError::SignedIntegerExpected(val), + )), + } + } + + pub fn into_string(self) -> TiffResult { + match self { + Ascii(val) => Ok(val), + val => Err(TiffError::FormatError( + TiffFormatError::SignedIntegerExpected(val), + )), + } + } + + pub fn into_u32_vec(self) -> TiffResult> { + match self { + List(vec) => { + let mut new_vec = Vec::with_capacity(vec.len()); + for v in vec { + new_vec.push(v.into_u32()?) + } + Ok(new_vec) + } + Unsigned(val) => Ok(vec![val]), + UnsignedBig(val) => Ok(vec![u32::try_from(val)?]), + Rational(numerator, denominator) => Ok(vec![numerator, denominator]), + RationalBig(numerator, denominator) => { + Ok(vec![u32::try_from(numerator)?, u32::try_from(denominator)?]) + } + Ifd(val) => Ok(vec![val]), + IfdBig(val) => Ok(vec![u32::try_from(val)?]), + Ascii(val) => Ok(val.chars().map(u32::from).collect()), + val => Err(TiffError::FormatError( + TiffFormatError::UnsignedIntegerExpected(val), + )), + } + } + + pub fn into_u8_vec(self) -> TiffResult> { + match self { + List(vec) => { + let mut new_vec = Vec::with_capacity(vec.len()); + for v in vec { + new_vec.push(v.into_u8()?) + } + Ok(new_vec) + } + Byte(val) => Ok(vec![val]), + + val => Err(TiffError::FormatError( + TiffFormatError::UnsignedIntegerExpected(val), + )), + } + } + + pub fn into_u16_vec(self) -> TiffResult> { + 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> { + match self { + List(vec) => { + let mut new_vec = Vec::with_capacity(vec.len()); + for v in vec { + match v { + SRational(numerator, denominator) => { + new_vec.push(numerator); + new_vec.push(denominator); + } + SRationalBig(numerator, denominator) => { + new_vec.push(i32::try_from(numerator)?); + new_vec.push(i32::try_from(denominator)?); + } + _ => new_vec.push(v.into_i32()?), + } + } + Ok(new_vec) + } + SignedByte(val) => Ok(vec![val.into()]), + SignedShort(val) => Ok(vec![val.into()]), + Signed(val) => Ok(vec![val]), + SignedBig(val) => Ok(vec![i32::try_from(val)?]), + SRational(numerator, denominator) => Ok(vec![numerator, denominator]), + SRationalBig(numerator, denominator) => { + Ok(vec![i32::try_from(numerator)?, i32::try_from(denominator)?]) + } + val => Err(TiffError::FormatError( + TiffFormatError::SignedIntegerExpected(val), + )), + } + } + + pub fn into_f32_vec(self) -> TiffResult> { + match self { + List(vec) => { + let mut new_vec = Vec::with_capacity(vec.len()); + for v in vec { + new_vec.push(v.into_f32()?) + } + Ok(new_vec) + } + Float(val) => Ok(vec![val]), + val => Err(TiffError::FormatError( + TiffFormatError::UnsignedIntegerExpected(val), + )), + } + } + + pub fn into_f64_vec(self) -> TiffResult> { + match self { + List(vec) => { + let mut new_vec = Vec::with_capacity(vec.len()); + for v in vec { + new_vec.push(v.into_f64()?) + } + Ok(new_vec) + } + Double(val) => Ok(vec![val]), + val => Err(TiffError::FormatError( + TiffFormatError::UnsignedIntegerExpected(val), + )), + } + } + + pub fn into_u64_vec(self) -> TiffResult> { + match self { + List(vec) => { + let mut new_vec = Vec::with_capacity(vec.len()); + for v in vec { + new_vec.push(v.into_u64()?) + } + Ok(new_vec) + } + Unsigned(val) => Ok(vec![val.into()]), + UnsignedBig(val) => Ok(vec![val]), + Rational(numerator, denominator) => Ok(vec![numerator.into(), denominator.into()]), + RationalBig(numerator, denominator) => Ok(vec![numerator, denominator]), + Ifd(val) => Ok(vec![val.into()]), + IfdBig(val) => Ok(vec![val]), + Ascii(val) => Ok(val.chars().map(u32::from).map(u64::from).collect()), + val => Err(TiffError::FormatError( + TiffFormatError::UnsignedIntegerExpected(val), + )), + } + } + + pub fn into_i64_vec(self) -> TiffResult> { + match self { + List(vec) => { + let mut new_vec = Vec::with_capacity(vec.len()); + for v in vec { + match v { + SRational(numerator, denominator) => { + new_vec.push(numerator.into()); + new_vec.push(denominator.into()); + } + SRationalBig(numerator, denominator) => { + new_vec.push(numerator); + new_vec.push(denominator); + } + _ => new_vec.push(v.into_i64()?), + } + } + Ok(new_vec) + } + SignedByte(val) => Ok(vec![val.into()]), + SignedShort(val) => Ok(vec![val.into()]), + Signed(val) => Ok(vec![val.into()]), + SignedBig(val) => Ok(vec![val]), + SRational(numerator, denominator) => Ok(vec![numerator.into(), denominator.into()]), + SRationalBig(numerator, denominator) => Ok(vec![numerator, denominator]), + val => Err(TiffError::FormatError( + TiffFormatError::SignedIntegerExpected(val), + )), + } + } +} + +/// Entry with buffered instead of read data +#[derive(Clone, Debug)] +pub struct BufferedEntry { + pub type_: Type, + pub count: u64, + pub data: Vec, +} + +/// Implement TiffValue to allow writing this data with encoder +impl TiffValue for BufferedEntry { + const BYTE_LEN: u8 = 1; + + fn is_type(&self) -> Type { + self.type_ + } + + fn count(&self) -> usize { + self.count.clone().try_into().unwrap() + } + + fn bytes(&self) -> usize { + let tag_size = self.type_.size() as u32; + + match self.count.checked_mul(tag_size.into()) { + Some(n) => n.try_into().unwrap_or_default(), + None => 0usize, + } + } + + fn data(&self) -> Cow<[u8]> { + Cow::Borrowed(&self.data) + } +} + +impl From for BufferedEntry { + fn from(pe: ProcessedEntry) -> Self { + Self { + type_: pe.kind(), + count: pe.count() as u64, + data: pe.data(), + } + } +} + +/// Entry with buffered instead of read data +/// +/// The type of tag is determined by the contents of the list, its count being the size of +/// the list. +#[derive(Clone, Debug)] +pub struct ProcessedEntry(Vec); + +impl std::fmt::Display for ProcessedEntry { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + write!(f, "{}", self.0.iter().map(|v| format!("{v}")).join(", "),) + } +} + +macro_rules! cast { + ($be:expr, $type:ty, $value:expr) => {{ + assert!($be.data.len() as u64 == size_of::<$type>() as u64 * $be.count); + $be.data + .chunks_exact(size_of::<$type>()) + .into_iter() + .map(|i| <$type>::from_ne_bytes(i.try_into().expect("Unreachable"))) + .map($value) + .collect() + }}; + + ($be:expr, $type:ty, $second:ty, $value:expr) => {{ + assert!($be.data.len() as u64 == size_of::<$type>() as u64 * $be.count * 2); + $be.data + .chunks_exact(size_of::<$type>()) + .into_iter() + .map(|i| <$type>::from_ne_bytes(i.try_into().expect("Unreachable"))) + .tuples::<($type, $type)>() + .map(|(n, d)| $value(n, d)) + .collect() + }}; +} + +impl From for ProcessedEntry { + fn from(be: BufferedEntry) -> Self { + let contents: Vec = match be.type_ { + Type::BYTE => be.data.into_iter().map(Value::Byte).collect(), + Type::SBYTE => be + .data + .into_iter() + .map(|b| Value::SignedByte(i8::from_ne_bytes([b; 1]))) + .collect(), + Type::SHORT => cast!(be, u16, Value::Short), + Type::LONG => cast!(be, u32, Value::Unsigned), + Type::SLONG8 => cast!(be, u64, Value::UnsignedBig), + Type::SSHORT => cast!(be, i16, Value::SignedShort), + Type::SLONG => cast!(be, i32, Value::Signed), + Type::LONG8 => cast!(be, i64, Value::SignedBig), + Type::FLOAT => cast!(be, f32, Value::Float), + Type::DOUBLE => cast!(be, f64, Value::Double), + Type::RATIONAL => cast!(be, u32, u32, Value::Rational), + Type::SRATIONAL => cast!(be, i32, i32, Value::SRational), + Type::IFD => cast!(be, u32, Value::Ifd), + Type::IFD8 => cast!(be, u64, Value::IfdBig), + Type::UNDEFINED => be.data.into_iter().map(Value::Undefined).collect(), + Type::ASCII => { + vec![Value::Ascii(String::from_utf8(be.data).unwrap_or_default())] + } + }; + + ProcessedEntry(contents) + } +} + +impl From for ProcessedEntry { + fn from(v: Value) -> Self { + ProcessedEntry(vec![v]) + } +} + +impl ProcessedEntry { + pub fn iter(&self) -> std::slice::Iter<'_, Value> { + self.0.iter() + } + + pub fn kind(&self) -> Type { + match self.0.first() { + Some(v) => match v { + Value::Byte(_) => Type::BYTE, + Value::Short(_) => Type::SHORT, + Value::SignedByte(_) => Type::SBYTE, + Value::SignedShort(_) => Type::SSHORT, + Value::Signed(_) => Type::SLONG, + Value::SignedBig(_) => Type::SLONG8, + Value::Unsigned(_) => Type::LONG, + Value::UnsignedBig(_) => Type::LONG8, + Value::Float(_) => Type::FLOAT, + Value::Double(_) => Type::DOUBLE, + Value::List(_) => Type::UNDEFINED, + Value::Rational(_, _) => Type::RATIONAL, + Value::SRational(_, _) => Type::SRATIONAL, + Value::Ascii(_) => Type::ASCII, + Value::Ifd(_) => Type::IFD, + Value::IfdBig(_) => Type::IFD8, + Value::Undefined(_) => Type::UNDEFINED, + Value::RationalBig(_, _) | Value::SRationalBig(_, _) => unreachable!(), + }, + None => Type::UNDEFINED, + } + } + + pub fn count(&self) -> usize { + self.0.len() + } + + fn data(&self) -> Vec { + let mut data = Vec::with_capacity(self.count() * self.kind().size()); + + for v in &self.0 { + match v { + Value::Byte(e) => data.push(*e), + Value::Short(e) => data.extend_from_slice(&e.to_ne_bytes()), + Value::SignedByte(e) => data.push(*e as u8), + Value::SignedShort(e) => data.extend_from_slice(&e.to_ne_bytes()), + Value::Signed(e) => data.extend_from_slice(&e.to_ne_bytes()), + Value::SignedBig(e) => data.extend_from_slice(&e.to_ne_bytes()), + Value::Unsigned(e) => data.extend_from_slice(&e.to_ne_bytes()), + Value::UnsignedBig(e) => data.extend_from_slice(&e.to_ne_bytes()), + Value::Float(e) => data.extend_from_slice(&e.to_ne_bytes()), + Value::Double(e) => data.extend_from_slice(&e.to_ne_bytes()), + Value::List(_) => todo!(), + Value::Rational(n, d) => { + data.extend_from_slice(&n.to_ne_bytes()); + data.extend_from_slice(&d.to_ne_bytes()); + } + Value::RationalBig(n, d) => { + data.extend_from_slice(&n.to_ne_bytes()); + data.extend_from_slice(&d.to_ne_bytes()); + } + Value::SRational(n, d) => { + data.extend_from_slice(&n.to_ne_bytes()); + data.extend_from_slice(&d.to_ne_bytes()); + } + Value::SRationalBig(n, d) => { + data.extend_from_slice(&n.to_ne_bytes()); + data.extend_from_slice(&d.to_ne_bytes()); + } + Value::Ascii(e) => data.extend_from_slice(e.as_bytes()), + Value::Ifd(e) => data.extend_from_slice(&e.to_ne_bytes()), + Value::IfdBig(e) => data.extend_from_slice(&e.to_ne_bytes()), + Value::Undefined(e) => data.push(*e), + } + } + + data + } +} + +/// Type representing an Image File Directory +#[derive(Debug, Clone)] +pub struct ImageFileDirectory, E>(BTreeMap); +pub type Directory = ImageFileDirectory; + +impl Default for ImageFileDirectory +where + T: Ord + Into, +{ + fn default() -> Self { + ImageFileDirectory(BTreeMap::new()) + } +} + +impl ImageFileDirectory +where + T: Ord + Into, +{ + pub fn new() -> Self { + ImageFileDirectory(BTreeMap::new()) + } + + pub fn insert(&mut self, tag: T, entry: E) -> Option { + self.0.insert(tag, entry) + } + + pub fn into_iter(self) -> std::collections::btree_map::IntoIter { + self.0.into_iter() + } + + pub fn contains_key(&self, tag: &T) -> bool { + self.0.contains_key(&tag) + } + + pub fn get(&self, tag: &T) -> Option<&E> { + self.0.get(&tag) + } + + pub fn get_mut(&mut self, tag: &T) -> Option<&mut E> { + self.0.get_mut(&tag) + } + + pub fn len(&self) -> usize { + self.0.len() + } + + pub fn iter(&self) -> std::collections::btree_map::Iter { + self.0.iter() + } + + pub fn values_mut(&mut self) -> std::collections::btree_map::ValuesMut { + self.0.values_mut() + } +} + +impl FromIterator<(T, K)> for ImageFileDirectory +where + T: Ord + Into, + K: Into, +{ + fn from_iter>(iter: I) -> Self { + ImageFileDirectory(iter.into_iter().map(|(t, k)| (t, k.into())).collect()) + } +} + +impl std::fmt::Display for ImageFileDirectory +where + T: DispatchFormat + Ord + std::fmt::Display + Into, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut refs = self.iter().collect::>(); + refs.sort_by(|lhs, rhs| lhs.0.cmp(&rhs.0)); + + for (tag, entry) in refs { + writeln!(f, "{tag}: {}", tag.format(&entry))?; + } + + Ok(()) + } +} diff --git a/src/lib.rs b/src/lib.rs index a4522ced..e8987302 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,9 +13,12 @@ mod bytecast; pub mod decoder; pub mod encoder; mod error; +pub mod ifd; pub mod tags; +mod tiff_kind; pub use self::error::{TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, UsageError}; +pub use tiff_kind::*; /// An enumeration over supported color types and their bit depths #[derive(Copy, PartialEq, Eq, Debug, Clone, Hash)] diff --git a/src/tags.rs b/src/tags.rs index 3b86dc1f..e7d84685 100644 --- a/src/tags.rs +++ b/src/tags.rs @@ -1,3 +1,6 @@ +use crate::ifd::ProcessedEntry; +use itertools::Itertools; + macro_rules! tags { { // Permit arbitrary meta items, which include documentation. @@ -36,8 +39,18 @@ macro_rules! tags { } } + impl std::fmt::Display for $name { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match *self { + $( $name::$tag => write!(f, stringify!($tag)), )* + $( $name::Unknown(n) => { $unknown_doc; write!(f, "{n:x}")}, )* + } + } + } + tags!($name, $ty, $($unknown_doc)*); }; + // For u16 tags, provide direct inherent primitive conversion methods. ($name:tt, u16, $($unknown_doc:literal)*) => { impl $name { @@ -59,6 +72,21 @@ macro_rules! tags { Self::__to_inner_type(self) } } + + impl Into for $name { + fn into(self) -> u16 { + self.to_u16() + } + } + + $( + impl From for $name { + fn from(raw: u16) -> Self { + $unknown_doc; + <$name>::from_u16_exhaustive(raw) + } + } + )* }; // For other tag types, do nothing for now. With concat_idents one could // provide inherent conversion methods for all types. @@ -68,6 +96,7 @@ macro_rules! tags { // Note: These tags appear in the order they are mentioned in the TIFF reference tags! { /// TIFF tags +#[derive(Ord, PartialOrd)] pub enum Tag(u16) unknown("A private or extension tag") { // Baseline tags: Artist = 315, @@ -78,7 +107,6 @@ pub enum Tag(u16) unknown("A private or extension tag") { // palette-color images (PhotometricInterpretation 3) ColorMap = 320, // TODO add support Compression = 259, // TODO add support for 2 and 32773 - Copyright = 33_432, DateTime = 306, ExtraSamples = 338, // TODO add support FillOrder = 266, // TODO add support @@ -98,7 +126,12 @@ pub enum Tag(u16) unknown("A private or extension tag") { Orientation = 274, // TODO add support PhotometricInterpretation = 262, PlanarConfiguration = 284, + PageName = 0x11d, ResolutionUnit = 296, // TODO add support + PageNumber = 0x129, + Predictor = 0x13d, + WhitePoint = 0x13e, + PrimaryChromacities = 0x13f, RowsPerStrip = 278, SamplesPerPixel = 277, Software = 305, @@ -109,7 +142,6 @@ pub enum Tag(u16) unknown("A private or extension tag") { XResolution = 282, YResolution = 283, // Advanced tags - Predictor = 317, TileWidth = 322, TileLength = 323, TileOffsets = 324, @@ -120,6 +152,7 @@ pub enum Tag(u16) unknown("A private or extension tag") { SMaxSampleValue = 341, // TODO add support // JPEG JPEGTables = 347, + ApplicationNotes = 0x2bc, // GeoTIFF ModelPixelScaleTag = 33550, // (SoftDesk) ModelTransformationTag = 34264, // (JPL Carto Group) @@ -127,7 +160,64 @@ pub enum Tag(u16) unknown("A private or extension tag") { GeoKeyDirectoryTag = 34735, // (SPOT) GeoDoubleParamsTag = 34736, // (SPOT) GeoAsciiParamsTag = 34737, // (SPOT) + Copyright = 0x8298, + ExposureTime = 0x829a, + FNumber = 0x829b, + ExifIfd = 0x8769, + GpsIfd = 0x8825, + ISO = 0x8827, + ICCProfile = 0x8773, + ExifVersion = 0x9000, + DateTimeOriginal = 0x9003, + CreateDate = 0x9004, + ComponentsConfiguration = 0x9101, + ExposureCompensation = 0x9204, + MeteringMode = 0x9207, + FocalLength = 0x920a, + UserComment = 0x9286, GdalNodata = 42113, // Contains areas with missing data + FlashpixVersion = 0xa000, + ColorSpace = 0xa001, + InteropIfd = 0xa005, +} +} + +tags! { +/// Tag space of GPS ifds +#[derive(Ord, PartialOrd)] +pub enum GpsTag(u16) unknown("A private or extension tag") { + GPSVersionID = 0x0000, + GPSLatitudeRef = 0x0001, + GPSLatitude = 0x0002, + GPSLongitudeRef = 0x0003, + GPSLongitude = 0x0004, + GPSAltitudeRef = 0x0005, + GPSAltitude = 0x0006, + GPSTimeStamp = 0x0007, + GPSSatellites = 0x0008, + GPSStatus = 0x0009, + GPSMeasureMode = 0x000a, + GPSDOP = 0x000b, + GPSSpeedRef = 0x000c, + GPSSpeed = 0x000d, + GPSTrackRef = 0x000e, + GPSTrack = 0x000f, + GPSImgDirectionRef = 0x0010, + GPSImgDirection = 0x0011, + GPSMapDatum = 0x0012, + GPSDestLatitudeRef = 0x0013, + GPSDestLatitude = 0x0014, + GPSDestLongitudeRef = 0x0015, + GPSDestLongitude = 0x0016, + GPSDestBearingRef = 0x0017, + GPSDestBearing = 0x0018, + GPSDestDistanceRef = 0x0019, + GPSDestDistance = 0x001a, + GPSProcessingMethod = 0x001b, + GPSAreaInformation = 0x001c, + GPSDateStamp = 0x001d, + GPSDifferential = 0x001e, + GPSHPositioningError = 0x001f, } } @@ -169,6 +259,19 @@ pub enum Type(u16) { } } +impl Type { + /// Returns the size of the type in bytes. + pub fn size(&self) -> usize { + match self { + Type::BYTE | Type::ASCII | Type::SBYTE | Type::UNDEFINED => 1, + Type::SHORT | Type::SSHORT => 2, + Type::LONG | Type::SLONG | Type::FLOAT | Type::IFD => 4, + Type::RATIONAL | Type::SRATIONAL | Type::DOUBLE => 8, + Type::LONG8 | Type::SLONG8 | Type::IFD8 => 8, + } + } +} + tags! { /// See [TIFF compression tags](https://www.awaresystems.be/imaging/tiff/tifftags/compression.html) /// for reference. @@ -188,7 +291,7 @@ pub enum CompressionMethod(u16) unknown("A custom compression method") { } tags! { -pub enum PhotometricInterpretation(u16) { +pub enum PhotometricInterpretation(u16) unknown("Unknown photometric interpolation") { WhiteIsZero = 0, BlackIsZero = 1, RGB = 2, @@ -201,14 +304,14 @@ pub enum PhotometricInterpretation(u16) { } tags! { -pub enum PlanarConfiguration(u16) { +pub enum PlanarConfiguration(u16) unknown("Unknown planar configuration") { Chunky = 1, Planar = 2, } } tags! { -pub enum Predictor(u16) { +pub enum Predictor(u16) unknown("Unknown predictor") { None = 1, Horizontal = 2, FloatingPoint = 3, @@ -217,7 +320,7 @@ pub enum Predictor(u16) { tags! { /// Type to represent resolution units -pub enum ResolutionUnit(u16) { +pub enum ResolutionUnit(u16) unknown("Unknown resolution unit") { None = 1, Inch = 2, Centimeter = 3, @@ -232,3 +335,91 @@ pub enum SampleFormat(u16) unknown("An unknown extension sample format") { Void = 4, } } + +tags! { +pub enum ColorSpace(u16) unknown("An unknown colorspace") { + SRGB = 1, + AdobeRGB = 2, + WideGamutRGB = 0xfffd, + ICCProfile = 0xfffe, + Uncalibrated = 0xffff, +} +} + +tags! { +pub enum MeteringMode(u16) unknown("An unknown metering mode") { + Average = 1, + CenterWeightedAverage = 2, + Spot = 3, + MultiSpot = 4, + MultiSegment = 5, + Partial = 6, + Other = 255, +} +} + +tags! { +pub enum Orientation(u16) unknown("An unknown orientation") { + Horizontal = 1, + MirrorHorizontal = 2, + Rotated180 = 3, + MirrorVertical = 4, + MirrorHorizontalRotated270CW = 5, + Rotated90CW = 6, + MirrorHorizontalRotated90CW = 7, + Rotated270CW = 8, +} +} + +pub trait DispatchFormat { + fn format(&self, e: &ProcessedEntry) -> String; +} + +macro_rules! intercept_u16 { + ($slice:expr, $target:ty) => { + $slice + .iter() + .filter_map(|v| v.clone().into_u16().ok()) + .map(|c| <$target>::from(c).to_string()) + .join(", ") + }; +} + +impl DispatchFormat for Tag { + fn format(&self, e: &ProcessedEntry) -> String { + match (self, e.kind()) { + (Tag::Orientation, Type::SHORT) => intercept_u16!(e, Orientation), + (Tag::Compression, Type::SHORT) => intercept_u16!(e, CompressionMethod), + (Tag::PhotometricInterpretation, Type::SHORT) => { + intercept_u16!(e, PhotometricInterpretation) + } + (Tag::PlanarConfiguration, Type::SHORT) => intercept_u16!(e, PlanarConfiguration), + (Tag::Predictor, Type::SHORT) => intercept_u16!(e, Predictor), + (Tag::ResolutionUnit, Type::SHORT) => intercept_u16!(e, ResolutionUnit), + (Tag::SampleFormat, Type::SHORT) => intercept_u16!(e, SampleFormat), + (Tag::ColorSpace, Type::SHORT) => intercept_u16!(e, ColorSpace), + (Tag::MeteringMode, Type::SHORT) => intercept_u16!(e, MeteringMode), + (_, _) => e.iter().map(|v| format!("{v}")).join(", "), + } + } +} + +fn format_coords(e: &ProcessedEntry) -> String { + let mut iter = e.iter(); + format!( + "{} deg {}' {:.2}\"", + iter.next().unwrap().clone().into_f32().unwrap_or_default(), + iter.next().unwrap().clone().into_f32().unwrap_or_default(), + iter.next().unwrap().clone().into_f32().unwrap_or_default(), + ) +} + +impl DispatchFormat for GpsTag { + fn format(&self, e: &ProcessedEntry) -> String { + match (self, e.kind()) { + (GpsTag::GPSLatitude, Type::RATIONAL) if e.count() == 3 => format_coords(e), + (GpsTag::GPSLongitude, Type::RATIONAL) if e.count() == 3 => format_coords(e), + (_, _) => e.iter().map(|v| format!("{v}")).join(", "), + } + } +} diff --git a/src/tiff_kind.rs b/src/tiff_kind.rs new file mode 100644 index 00000000..65eff349 --- /dev/null +++ b/src/tiff_kind.rs @@ -0,0 +1,132 @@ +use std::{io::Write, num::TryFromIntError}; + +use crate::{ + encoder::{write_bigtiff_header, write_tiff_header, TiffValue, TiffWriter}, + error::TiffResult, +}; + +/// Trait to abstract over Tiff/BigTiff differences. +/// +/// Implemented for [`TiffKindStandard`] and [`TiffKindBig`]. +pub trait TiffKind +where + Self: Clone + std::fmt::Debug + Sized, +{ + /// The type of offset fields, `u32` for normal Tiff, `u64` for BigTiff. + type OffsetType: TryFrom + + TryInto + + Into + + From + + Clone + + std::fmt::Debug + + TiffValue; + + /// Needed for the `convert_slice` method. + type OffsetArrayType: ?Sized + TiffValue; + + fn is_big() -> bool { + Self::OffsetType::BYTE_LEN == 8 + } + + /// Write the (Big)Tiff header. + fn write_header(writer: &mut TiffWriter) -> TiffResult<()>; + + /// Convert a file offset to `Self::OffsetType`. + /// + /// This returns an error for normal Tiff if the offset is larger than `u32::MAX`. + fn convert_offset(offset: u64) -> TiffResult; + + /// Write an offset value to the given writer. + /// + /// Like `convert_offset`, this errors if `offset > u32::MAX` for normal Tiff. + fn write_offset(writer: &mut TiffWriter, offset: u64) -> TiffResult<()>; + + /// Write the IFD entry count field with the given `count` value. + /// + /// The entry count field is an `u16` for normal Tiff and `u64` for BigTiff. Errors + /// if the given `usize` is larger than the representable values. + fn write_entry_count(writer: &mut TiffWriter, count: usize) -> TiffResult<()>; + + /// Internal helper method for satisfying Rust's type checker. + /// + /// The `TiffValue` trait is implemented for both primitive values (e.g. `u8`, `u32`) and + /// slices of primitive values (e.g. `[u8]`, `[u32]`). However, this is not represented in + /// the type system, so there is no guarantee that that for all `T: TiffValue` there is also + /// an implementation of `TiffValue` for `[T]`. This method works around that problem by + /// providing a conversion from `[T]` to some value that implements `TiffValue`, thereby + /// making all slices of `OffsetType` usable with `write_tag` and similar methods. + /// + /// Implementations of this trait should always set `OffsetArrayType` to `[OffsetType]`. + fn convert_slice(slice: &[Self::OffsetType]) -> &Self::OffsetArrayType; +} + +/// Create a standard Tiff file. +#[derive(Clone, Debug)] +pub struct TiffKindStandard; + +impl TiffKind for TiffKindStandard { + type OffsetType = u32; + type OffsetArrayType = [u32]; + + fn write_header(writer: &mut TiffWriter) -> TiffResult<()> { + write_tiff_header(writer)?; + // blank the IFD offset location + writer.write_u32(0)?; + + Ok(()) + } + + fn convert_offset(offset: u64) -> TiffResult { + Ok(Self::OffsetType::try_from(offset)?) + } + + fn write_offset(writer: &mut TiffWriter, offset: u64) -> TiffResult<()> { + writer.write_u32(u32::try_from(offset)?)?; + Ok(()) + } + + fn write_entry_count(writer: &mut TiffWriter, count: usize) -> TiffResult<()> { + writer.write_u16(u16::try_from(count)?)?; + + Ok(()) + } + + fn convert_slice(slice: &[Self::OffsetType]) -> &Self::OffsetArrayType { + slice + } +} + +/// Create a BigTiff file. +#[derive(Clone, Debug)] +pub struct TiffKindBig; + +impl TiffKind for TiffKindBig { + type OffsetType = u64; + type OffsetArrayType = [u64]; + + fn write_header(writer: &mut TiffWriter) -> TiffResult<()> { + write_bigtiff_header(writer)?; + // blank the IFD offset location + writer.write_u64(0)?; + + Ok(()) + } + + fn convert_offset(offset: u64) -> TiffResult { + Ok(offset) + } + + fn write_offset(writer: &mut TiffWriter, offset: u64) -> TiffResult<()> { + writer.write_u64(offset)?; + Ok(()) + } + + fn write_entry_count(writer: &mut TiffWriter, count: usize) -> TiffResult<()> { + writer.write_u64(u64::try_from(count)?)?; + Ok(()) + } + + fn convert_slice(slice: &[Self::OffsetType]) -> &Self::OffsetArrayType { + slice + } +} diff --git a/tests/decode_bigtiff_images.rs b/tests/decode_bigtiff_images.rs index 9113f425..b473053e 100644 --- a/tests/decode_bigtiff_images.rs +++ b/tests/decode_bigtiff_images.rs @@ -1,8 +1,6 @@ extern crate tiff; -use tiff::decoder::Decoder; -use tiff::tags::Tag; -use tiff::ColorType; +use tiff::{decoder::BigTiffDecoder, tags::Tag, ColorType}; use std::fs::File; use std::path::PathBuf; @@ -15,7 +13,7 @@ fn test_big_tiff() { for filename in filenames.iter() { let path = PathBuf::from(TEST_IMAGE_DIR).join(filename); let img_file = File::open(path).expect("Cannot find test image!"); - let mut decoder = Decoder::new(img_file).expect("Cannot create decoder"); + let mut decoder = BigTiffDecoder::new(img_file).expect("Cannot create decoder"); assert_eq!( decoder.dimensions().expect("Cannot get dimensions"), (64, 64) diff --git a/tests/decode_geotiff_images.rs b/tests/decode_geotiff_images.rs index 92ef7482..ca9e6644 100644 --- a/tests/decode_geotiff_images.rs +++ b/tests/decode_geotiff_images.rs @@ -1,8 +1,10 @@ extern crate tiff; -use tiff::decoder::{Decoder, DecodingResult}; -use tiff::tags::Tag; -use tiff::ColorType; +use tiff::{ + decoder::{DecodingResult, TiffDecoder}, + tags::Tag, + ColorType, +}; use std::fs::File; use std::path::PathBuf; @@ -15,7 +17,7 @@ fn test_geo_tiff() { for filename in filenames.iter() { let path = PathBuf::from(TEST_IMAGE_DIR).join(filename); let img_file = File::open(path).expect("Cannot find test image!"); - let mut decoder = Decoder::new(img_file).expect("Cannot create decoder"); + let mut decoder = TiffDecoder::new(img_file).expect("Cannot create decoder"); decoder = decoder.with_limits(tiff::decoder::Limits::unlimited()); assert_eq!( diff --git a/tests/decode_images.rs b/tests/decode_images.rs index 77f48e9d..19b898ca 100644 --- a/tests/decode_images.rs +++ b/tests/decode_images.rs @@ -1,9 +1,14 @@ extern crate tiff; -use tiff::decoder::{ifd, Decoder, DecodingResult}; -use tiff::ColorType; +use tiff::decoder::DecodingResult; +use tiff::ifd::Value; +use tiff::{ + decoder::{BigTiffDecoder, TiffDecoder}, + ColorType, TiffKindStandard, +}; use std::fs::File; +use std::io::{Cursor, Write}; use std::path::PathBuf; const TEST_IMAGE_DIR: &str = "./tests/images/"; @@ -13,7 +18,7 @@ macro_rules! test_image_sum { fn $name(file: &str, expected_type: ColorType, expected_sum: $sum_ty) { let path = PathBuf::from(TEST_IMAGE_DIR).join(file); let img_file = File::open(path).expect("Cannot find test image!"); - let mut decoder = Decoder::new(img_file).expect("Cannot create decoder"); + let mut decoder = TiffDecoder::new(img_file).expect("Cannot create decoder"); assert_eq!(decoder.colortype().unwrap(), expected_type); let img_res = decoder.read_image().unwrap(); @@ -43,7 +48,7 @@ test_image_sum!(test_image_sum_f64, F64, f64); fn test_image_color_type_unsupported(file: &str, expected_type: ColorType) { let path = PathBuf::from(TEST_IMAGE_DIR).join(file); let img_file = File::open(path).expect("Cannot find test image!"); - let mut decoder = Decoder::new(img_file).expect("Cannot create decoder"); + let mut decoder = TiffDecoder::new(img_file).expect("Cannot create decoder"); assert_eq!(decoder.colortype().unwrap(), expected_type); assert!(match decoder.read_image() { Err(tiff::TiffError::UnsupportedError( @@ -165,10 +170,10 @@ fn test_string_tags() { for filename in filenames.iter() { let path = PathBuf::from(TEST_IMAGE_DIR).join(filename); let img_file = File::open(path).expect("Cannot find test image!"); - let mut decoder = Decoder::new(img_file).expect("Cannot create decoder"); + let mut decoder = TiffDecoder::new(img_file).expect("Cannot create decoder"); let software = decoder.get_tag(tiff::tags::Tag::Software).unwrap(); match software { - ifd::Value::Ascii(s) => assert_eq!( + Value::Ascii(s) => assert_eq!( &s, "GraphicsMagick 1.2 unreleased Q16 http://www.GraphicsMagick.org/" ), @@ -189,7 +194,7 @@ fn test_decode_data() { } } let file = File::open("./tests/decodedata-rgb-3c-8b.tiff").unwrap(); - let mut decoder = Decoder::new(file).unwrap(); + let mut decoder = TiffDecoder::new(file).unwrap(); assert_eq!(decoder.colortype().unwrap(), ColorType::RGB(8)); assert_eq!(decoder.dimensions().unwrap(), (100, 100)); if let DecodingResult::U8(img_res) = decoder.read_image().unwrap() { @@ -210,7 +215,7 @@ fn issue_69() { //fn test_gray_alpha_u8() //{ //let img_file = File::open("./tests/images/minisblack-2c-8b-alpha.tiff").expect("Cannot find test image!"); -//let mut decoder = Decoder::new(img_file).expect("Cannot create decoder"); +//let mut decoder = TiffDecoder::new(img_file).expect("Cannot create decoder"); //assert_eq!(decoder.colortype().unwrap(), ColorType::GrayA(8)); //let img_res = decoder.read_image(); //assert!(img_res.is_ok()); @@ -268,7 +273,7 @@ fn test_tiled_incremental() { let path = PathBuf::from(TEST_IMAGE_DIR).join(file); let img_file = File::open(path).expect("Cannot find test image!"); - let mut decoder = Decoder::new(img_file).expect("Cannot create decoder"); + let mut decoder = TiffDecoder::new(img_file).expect("Cannot create decoder"); assert_eq!(decoder.colortype().unwrap(), expected_type); let tiles = decoder.tile_count().unwrap(); @@ -293,7 +298,7 @@ fn test_planar_rgb_u8() { let path = PathBuf::from(TEST_IMAGE_DIR).join(file); let img_file = File::open(path).expect("Cannot find test image!"); - let mut decoder = Decoder::new(img_file).expect("Cannot create decoder"); + let mut decoder = TiffDecoder::new(img_file).expect("Cannot create decoder"); assert_eq!(decoder.colortype().unwrap(), expected_type); let chunks = decoder.strip_count().unwrap(); @@ -339,7 +344,7 @@ fn test_div_zero() { 178, 178, 178, ]; - let err = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err(); + let err = TiffDecoder::new(std::io::Cursor::new(&image)).unwrap_err(); match err { TiffError::FormatError(TiffFormatError::StripTileTagConflict) => {} @@ -357,7 +362,7 @@ fn test_too_many_value_bytes() { 0, 89, 89, 89, 89, 89, 89, 89, 89, 96, 1, 20, 89, 89, 89, 89, 18, ]; - let error = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err(); + let error = BigTiffDecoder::new(std::io::Cursor::new(&image)).unwrap_err(); match error { tiff::TiffError::LimitsExceeded => {} @@ -375,7 +380,7 @@ fn fuzzer_testcase5() { 178, 178, 178, ]; - let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err(); + let _ = TiffDecoder::new(std::io::Cursor::new(&image)).unwrap_err(); } #[test] @@ -388,7 +393,7 @@ fn fuzzer_testcase1() { 178, ]; - let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err(); + let _ = TiffDecoder::new(std::io::Cursor::new(&image)).unwrap_err(); } #[test] @@ -401,7 +406,7 @@ fn fuzzer_testcase6() { 178, 178, ]; - let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err(); + let _ = TiffDecoder::new(std::io::Cursor::new(&image)).unwrap_err(); } #[test] @@ -413,7 +418,7 @@ fn oom() { 0, 0, 0, 40, 0, 0, 0, 23, 1, 4, 0, 1, 0, 0, 0, 178, 48, 178, 178, 178, 178, 162, 178, ]; - let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err(); + let _ = TiffDecoder::new(std::io::Cursor::new(&image)).unwrap_err(); } #[test] @@ -425,7 +430,7 @@ fn fuzzer_testcase4() { 0, 0, 0, 40, 0, 0, 0, 23, 1, 4, 0, 1, 0, 0, 0, 48, 178, 178, 178, 0, 1, 0, 13, 13, ]; - let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err(); + let _ = TiffDecoder::new(std::io::Cursor::new(&image)).unwrap_err(); } #[test] @@ -441,7 +446,7 @@ fn fuzzer_testcase2() { 73, ]; - let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err(); + let _ = TiffDecoder::new(std::io::Cursor::new(&image)).unwrap_err(); } #[test] @@ -457,7 +462,7 @@ fn invalid_jpeg_tag_2() { 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 36, 73, 73, 0, 42, 36, 36, 36, 36, 0, 0, 8, 0, ]; - let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err(); + let _ = TiffDecoder::new(std::io::Cursor::new(&image)).unwrap_err(); } #[test] @@ -470,7 +475,7 @@ fn fuzzer_testcase3() { 255, 255, ]; - let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err(); + let _ = TiffDecoder::new(std::io::Cursor::new(&image)).unwrap_err(); } #[test] @@ -488,7 +493,7 @@ fn timeout() { 0, 0, 73, 73, 42, 0, 8, 0, 0, 0, 0, 0, 32, ]; - let error = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err(); + let error = TiffDecoder::new(std::io::Cursor::new(&image)).unwrap_err(); match error { TiffError::FormatError(TiffFormatError::CycleInOffsets) => {} @@ -510,3 +515,62 @@ fn test_predictor_3_rgb_f32() { fn test_predictor_3_gray_f32() { test_image_sum_f32("predictor-3-gray-f32.tif", ColorType::Gray(32), 20008.275); } + +#[test] +fn test_exif_decoding() { + let path = PathBuf::from(TEST_IMAGE_DIR).join("exif.tif"); + let img_file = File::open(path).expect("Cannot find test image!"); + let mut decoder = TiffDecoder::new(img_file).expect("Cannot create decoder"); + let raw_exif = decoder + .read_exif::() + .expect("Unable to read Exif data"); + + let mut output = Cursor::new(Vec::new()); + output.write(&raw_exif).expect("Unable to write output"); + output.flush().expect("Unable to flush writer"); + + output.set_position(0); + let sum: u64 = output.into_inner().into_iter().map(u64::from).sum(); + assert_eq!(sum, 4955); +} + +extern crate exif; +#[test] +fn test_exif_parsing() { + let path = PathBuf::from(TEST_IMAGE_DIR).join("exif.tif"); + let img_file = File::open(path).expect("Cannot find test image!"); + let mut decoder = TiffDecoder::new(img_file).expect("Cannot create decoder"); + let raw_exif = decoder + .read_exif::() + .expect("Unable to read Exif data"); + + let exifreader = exif::Reader::new(); + let exif_data = exifreader + .read_raw(raw_exif) + .expect("Unable to parse Exif data"); + + match exif_data.get_field(exif::Tag::Orientation, exif::In::PRIMARY) { + Some(orientation) => { + assert_eq!(orientation.value.get_uint(0).unwrap(), 1); + } + None => panic!("Orientation tag missing"), + } + + match exif_data.get_field(exif::Tag::ColorSpace, exif::In::PRIMARY) { + Some(colourspace) => { + assert_eq!(colourspace.value.get_uint(0).unwrap(), 1); + } + None => panic!("Colourspace tag missing"), + } + + match exif_data.get_field(exif::Tag::ExposureBiasValue, exif::In::PRIMARY) { + Some(exposurecomp) => match exposurecomp.value { + exif::Value::SRational(ref v) if !v.is_empty() => { + let value = v[0]; + assert_eq!(value.to_f32(), -2.0f32) + } + _ => panic!("ExposureCompensation has wrong type"), + }, + None => panic!("ExposureCompensation tag missing"), + } +} diff --git a/tests/encode_images.rs b/tests/encode_images.rs index 532ed21c..f788eb0f 100644 --- a/tests/encode_images.rs +++ b/tests/encode_images.rs @@ -1,12 +1,16 @@ extern crate tiff; -use tiff::decoder::{ifd, Decoder, DecodingResult}; -use tiff::encoder::{colortype, Ifd, Ifd8, SRational, TiffEncoder}; +use tiff::decoder::DecodingResult; +use tiff::encoder::{colortype, Ifd, Ifd8, SRational}; use tiff::tags::Tag; -use tiff::ColorType; +use tiff::{ + decoder::{BigTiffDecoder, TiffDecoder}, + encoder::{BigTiffEncoder, TiffEncoder}, + ifd, ColorType, TiffKindStandard, +}; use std::fs::File; -use std::io::{Cursor, Seek, SeekFrom}; +use std::io::{Cursor, Seek, SeekFrom, Write}; use std::path::PathBuf; #[test] @@ -33,7 +37,7 @@ fn encode_decode() { } { file.seek(SeekFrom::Start(0)).unwrap(); - let mut decoder = Decoder::new(&mut file).unwrap(); + let mut decoder = TiffDecoder::new(&mut file).unwrap(); assert_eq!(decoder.colortype().unwrap(), ColorType::RGB(8)); assert_eq!(decoder.dimensions().unwrap(), (100, 100)); assert_eq!( @@ -61,7 +65,7 @@ fn encode_decode_big() { } let mut file = Cursor::new(Vec::new()); { - let mut tiff = TiffEncoder::new_big(&mut file).unwrap(); + let mut tiff = BigTiffEncoder::new(&mut file).unwrap(); let mut image = tiff.new_image::(100, 100).unwrap(); image @@ -72,7 +76,7 @@ fn encode_decode_big() { } { file.seek(SeekFrom::Start(0)).unwrap(); - let mut decoder = Decoder::new(&mut file).unwrap(); + let mut decoder = BigTiffDecoder::new(&mut file).unwrap(); assert_eq!(decoder.colortype().unwrap(), ColorType::RGB(8)); assert_eq!(decoder.dimensions().unwrap(), (100, 100)); assert_eq!( @@ -121,7 +125,7 @@ fn test_encode_ifd() { // Rewind the cursor for reading data.set_position(0); { - let mut decoder = Decoder::new(&mut data).unwrap(); + let mut decoder = TiffDecoder::new(&mut data).unwrap(); assert_eq!(decoder.assert_tag_u32(65000), 42); assert_eq!(decoder.assert_tag_u32_vec(65000), [42]); @@ -159,7 +163,7 @@ macro_rules! test_roundtrip { ) { let path = PathBuf::from(TEST_IMAGE_DIR).join(file); let img_file = File::open(path).expect("Cannot find test image!"); - let mut decoder = Decoder::new(img_file).expect("Cannot create decoder"); + let mut decoder = TiffDecoder::new(img_file).expect("Cannot create decoder"); assert_eq!(decoder.colortype().unwrap(), expected_type); let image_data = match decoder.read_image().unwrap() { @@ -176,7 +180,7 @@ macro_rules! test_roundtrip { } file.seek(SeekFrom::Start(0)).unwrap(); { - let mut decoder = Decoder::new(&mut file).unwrap(); + let mut decoder = TiffDecoder::new(&mut file).unwrap(); if let DecodingResult::$buffer(img_res) = decoder.read_image().unwrap() { assert_eq!(image_data, img_res); } else { @@ -292,7 +296,7 @@ trait AssertDecode { fn assert_tag_i64_vec(&mut self, tag: u16) -> Vec; } -impl AssertDecode for Decoder { +impl AssertDecode for TiffDecoder { fn assert_tag_u32(&mut self, tag: u16) -> u32 { self.get_tag(Tag::Unknown(tag)).unwrap().into_u32().unwrap() } @@ -358,7 +362,7 @@ fn test_multiple_byte() { data.set_position(0); { - let mut decoder = Decoder::new(&mut data).unwrap(); + let mut decoder = TiffDecoder::new(&mut data).unwrap(); assert_eq!(decoder.assert_tag_u32_vec(65000), [1]); assert_eq!(decoder.assert_tag_u32_vec(65001), [1, 2]); @@ -439,7 +443,7 @@ fn test_signed() { //Rewind the cursor for reading data.set_position(0); { - let mut decoder = Decoder::new(&mut data).unwrap(); + let mut decoder = TiffDecoder::new(&mut data).unwrap(); assert_eq!(decoder.assert_tag_i32(65000), -1); assert_eq!(decoder.assert_tag_i32_vec(65001), [-1]); @@ -491,7 +495,7 @@ fn test_multipage_image() { img_file.seek(SeekFrom::Start(0)).unwrap(); { - let mut img_decoder = Decoder::new(&mut img_file).unwrap(); + let mut img_decoder = TiffDecoder::new(&mut img_file).unwrap(); // check the dimensions of the image in the first page assert_eq!(img_decoder.dimensions().unwrap(), (2, 2)); @@ -526,7 +530,7 @@ fn test_rows_per_strip() { file.seek(SeekFrom::Start(0)).unwrap(); { - let mut decoder = Decoder::new(&mut file).unwrap(); + let mut decoder = TiffDecoder::new(&mut file).unwrap(); assert_eq!(decoder.get_tag_u64(Tag::RowsPerStrip).unwrap(), 2); assert_eq!(decoder.strip_count().unwrap(), 50); @@ -539,3 +543,35 @@ fn test_rows_per_strip() { } } } + +#[test] +fn test_recode_exif_data() { + let path = PathBuf::from(TEST_IMAGE_DIR).join("exif.tif"); + let img_file = File::open(path).expect("Cannot find test image!"); + let mut decoder = TiffDecoder::new(img_file).expect("Cannot create decoder"); + let raw_exif = decoder + .read_exif::() + .expect("Unable to read Exif data"); + let image_data = decoder.read_image().expect("Unable to decode"); + + let mut output = Cursor::new(Vec::new()); + let mut tiff = TiffEncoder::new(&mut output).expect("Unable to create TIFF"); + let (width, heigth) = decoder.dimensions().expect("Unable to read dimension"); + let mut image = tiff + .new_image::(width, heigth) + .expect("Unable to create encoder"); + image + .exif_tags::(raw_exif) + .expect("Unable to write Exif data"); + if let DecodingResult::U8(vec) = image_data { + image + .write_data(vec.as_slice()) + .expect("Unable to write image data"); + output.flush().expect("Unable to flush output"); + output.set_position(0); + let sum: u64 = output.into_inner().into_iter().map(u64::from).sum(); + assert_eq!(sum, 64497); + } else { + panic!("Wrong data type"); + } +} diff --git a/tests/encode_images_with_compression.rs b/tests/encode_images_with_compression.rs index 1cf178f2..29bf49ed 100644 --- a/tests/encode_images_with_compression.rs +++ b/tests/encode_images_with_compression.rs @@ -2,11 +2,12 @@ extern crate tiff; use std::io::{Cursor, Seek, Write}; use tiff::{ - decoder::{Decoder, DecodingResult}, + decoder::{DecodingResult, TiffDecoder}, + encoder::TiffEncoder, encoder::{ colortype::{self, ColorType}, compression::*, - TiffEncoder, TiffValue, + TiffValue, }, }; @@ -111,7 +112,7 @@ fn encode_decode_with_compression(compression: C) { // Decode tiff data.set_position(0); { - let mut decoder = Decoder::new(data).unwrap(); + let mut decoder = TiffDecoder::new(data).unwrap(); // Check the RGB image assert_eq!( diff --git a/tests/fuzz_tests.rs b/tests/fuzz_tests.rs index 446d6bd7..39756f03 100644 --- a/tests/fuzz_tests.rs +++ b/tests/fuzz_tests.rs @@ -1,7 +1,6 @@ extern crate tiff; -use tiff::decoder::Decoder; -use tiff::TiffResult; +use tiff::{decoder::TiffDecoder, TiffResult}; use std::fs::File; @@ -13,7 +12,7 @@ fn test_directory bool>(path: &str, f: F) { } fn decode_tiff(file: File) -> TiffResult<()> { - let mut decoder = Decoder::new(file)?; + let mut decoder = TiffDecoder::new(file)?; decoder.read_image()?; Ok(()) } diff --git a/tests/images/exif.tif b/tests/images/exif.tif new file mode 100644 index 00000000..7c0b41bc Binary files /dev/null and b/tests/images/exif.tif differ