From 8b153809821026c4bd34362d43618356b308da37 Mon Sep 17 00:00:00 2001 From: SeaEagle1 Date: Fri, 19 Jan 2024 16:59:37 +0100 Subject: [PATCH 01/20] Support Exif data export --- .gitignore | 1 + src/decoder/ifd.rs | 139 ++++++++++++++++++++++++++++++++++++++ src/decoder/mod.rs | 61 ++++++++++++++++- src/decoder/stream.rs | 2 +- src/encoder/mod.rs | 37 ++++++++-- src/encoder/tiff_value.rs | 62 ++++++++--------- tests/decode_images.rs | 14 ++++ tests/images/exif.tif | Bin 0 -> 672 bytes 8 files changed, 278 insertions(+), 38 deletions(-) create mode 100644 tests/images/exif.tif 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/src/decoder/ifd.rs b/src/decoder/ifd.rs index ecba47fe..0f804673 100644 --- a/src/decoder/ifd.rs +++ b/src/decoder/ifd.rs @@ -1,5 +1,6 @@ //! Function for reading TIFF tags +use std::borrow::Cow; use std::collections::HashMap; use std::convert::{TryFrom, TryInto}; use std::io::{self, Read, Seek}; @@ -9,6 +10,7 @@ use std::str; use super::stream::{ByteOrder, EndianReader, SmartReader}; use crate::tags::{Tag, Type}; use crate::{TiffError, TiffFormatError, TiffResult}; +use crate::encoder::TiffValue; use self::Value::{ Ascii, Byte, Double, Float, Ifd, IfdBig, List, Rational, RationalBig, SRational, SRationalBig, @@ -642,8 +644,145 @@ impl Entry { } Ok(List(v)) } + + pub fn as_buffered(&self, + bigtiff: bool, + reader: &mut SmartReader ) -> TiffResult { + 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); + } + }; + + let native_bo; + #[cfg(target_endian = "little")] + {native_bo = ByteOrder::LittleEndian;} + #[cfg(not(target_endian = "little"))] + {native_bo = ByteOrder::BigEndian;} + + let mut buf = vec![0; value_bytes as usize]; + if value_bytes <= 4 || (bigtiff && value_bytes <= 8) { + self.r(bo).read(&mut buf)?; + + + match self.type_ { // for multi-byte values + Type::SHORT | Type::SSHORT | Type::LONG | Type::SLONG | Type::FLOAT | Type::IFD + | Type::LONG8 | Type::SLONG8 | Type::DOUBLE | Type::IFD8 => + if native_bo != bo { // if byte-order is non-native + // reverse byte order + let mut new_buf = vec![0; value_bytes as usize]; + for i in 0..value_bytes { + new_buf[i as usize] = buf[(value_bytes - 1 - i) as usize]; + } + buf = new_buf; + } + _=>{} + } + + } else { + if bigtiff { + reader.goto_offset(self.r(bo).read_u64()?.into())?; + } else { + reader.goto_offset(self.r(bo).read_u32()?.into())?; + } + reader.read_exact(&mut buf)?; + + match self.type_ { // for multi-byte values + Type::LONG8 | Type::SLONG8 | Type::DOUBLE => + if native_bo != bo { // if byte-order is non-native + // reverse byte order + let mut new_buf = vec![0; value_bytes as usize]; + for i in 0..value_bytes { + new_buf[i as usize] = buf[(value_bytes - 1 - i) as usize]; + } + buf = new_buf; + } + Type::RATIONAL | Type::SRATIONAL => + if native_bo != bo { // if byte-order is non-native + // reverse byte order + let mut new_buf = vec![0; 8]; + new_buf[0] = buf[3]; + new_buf[1] = buf[2]; + new_buf[2] = buf[1]; + new_buf[3] = buf[0]; + new_buf[4] = buf[7]; + new_buf[5] = buf[6]; + new_buf[6] = buf[5]; + new_buf[7] = buf[4]; + buf = new_buf; + } + _=>{} + } + } + + Ok(BufferedEntry{ + type_: self.type_, + count: self.count.clone(), + data: buf + }) + } } +#[derive(Clone)] +pub struct BufferedEntry { + type_: Type, + count: u64, + data: Vec, +} + +impl TiffValue for BufferedEntry { + const BYTE_LEN: u8 = 1; + + fn is_type(&self) -> Type { + self.type_ + } + + fn count(&self) -> usize { + self.count.clone() as usize + } + + fn bytes(&self) -> usize { + 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 => 0, + }; + value_bytes as usize + } + + fn data(&self) -> Cow<[u8]> { + Cow::Borrowed(&self.data) + } +} + + + /// Extracts a list of BYTE tags stored in an offset #[inline] fn offset_to_bytes(n: usize, entry: &Entry) -> TiffResult { diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index cf2c18ed..e81f9922 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -1,11 +1,12 @@ use std::collections::{HashMap, HashSet}; use std::convert::TryFrom; -use std::io::{self, Read, Seek}; +use std::io::{self, Cursor, Read, Seek, Write}; use std::ops::Range; use crate::{ bytecast, ColorType, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, UsageError, }; +use crate::encoder::{DirectoryEncoder, TiffEncoder, TiffKind}; use self::ifd::Directory; use self::image::Image; @@ -1179,4 +1180,62 @@ impl Decoder { Ok(result) } + + /// 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 = TiffEncoder::new(Write::by_ref(&mut exifdata))?; + + // create new IFD + let mut ifd0 = encoder.new_directory()?; + let ifd = self.image.ifd.as_ref().unwrap(); + + // copy Exif tags from main IFD + let exif_tags = [Tag::ImageWidth,Tag::ImageLength,Tag::PhotometricInterpretation, + Tag::ImageDescription,Tag::Make,Tag::Model,Tag::Orientation,Tag::XResolution, + Tag::YResolution,Tag::ResolutionUnit,Tag::Software,Tag::DateTime,Tag::Artist, + Tag::HostComputer,Tag::Unknown(33432)]; + exif_tags.into_iter().for_each(| tag | { + let entry = ifd.get(&tag); + if entry.is_some() { + let b_entry = entry.unwrap().as_buffered(self.bigtiff.clone(), &mut self.reader).unwrap(); + ifd0.write_tag(tag, b_entry).unwrap(); + } + }); + + // find Exif sub-IFD and copy it whole + let exif_ifd_offset = self.find_tag(Tag::Unknown(34665))?; + if exif_ifd_offset.is_some() { + let offset = if self.bigtiff { + exif_ifd_offset.unwrap().into_u64()? + } else { + exif_ifd_offset.unwrap().into_u32()?.into() + }; + + // create sub-ifd + ifd0.subdirectory_start(); + // copy entries + self.copy_ifd(offset, &mut ifd0)?; + // return to ifd0 and write offset + let ifd_offset = ifd0.subirectory_close()?; + ifd0.write_tag(Tag::Unknown(34665), ifd_offset as u32)?; + } + ifd0.finish()?; + + Ok(exifdata.into_inner()) + } + + fn copy_ifd(&mut self, offset: u64, new_ifd: &mut DirectoryEncoder) -> TiffResult<()> { + let (ifd, _trash1) = + Self::read_ifd(&mut self.reader, self.bigtiff.clone(), offset)?; + + // loop through entries + ifd.into_iter().for_each(|(tag, value)| { + let b_entry = value.as_buffered(self.bigtiff.clone(), &mut self.reader).unwrap(); + new_ifd.write_tag(tag, b_entry).unwrap(); + }); + + Ok(()) + } } diff --git a/src/decoder/stream.rs b/src/decoder/stream.rs index 978f0e70..54973112 100644 --- a/src/decoder/stream.rs +++ b/src/decoder/stream.rs @@ -4,7 +4,7 @@ use std::convert::TryFrom; 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/encoder/mod.rs b/src/encoder/mod.rs index ddb9487c..627f576f 100644 --- a/src/encoder/mod.rs +++ b/src/encoder/mod.rs @@ -160,6 +160,7 @@ pub struct DirectoryEncoder<'a, W: 'a + Write + Seek, K: TiffKind> { // We use BTreeMap to make sure tags are written in correct order ifd_pointer_pos: u64, ifd: BTreeMap>, + sub_ifd: Option>>, } impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> { @@ -172,9 +173,22 @@ impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> { dropped: false, ifd_pointer_pos, ifd: BTreeMap::new(), + sub_ifd: None, }) } + /// Start writing to sub-IFD + pub fn subdirectory_start(&mut self) { + self.sub_ifd = Some(BTreeMap::new()); + } + + /// Stop writing to sub-IFD and resume master IFD, returns offset of sub-IFD + pub fn subirectory_close(&mut self) -> TiffResult { + let offset = self.write_directory()?; + self.sub_ifd = None; + 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()); @@ -183,10 +197,15 @@ impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> { value.write(&mut writer)?; } - self.ifd.insert( + let active_ifd = match &self.sub_ifd { + None => &mut self.ifd, + Some(_v) => self.sub_ifd.as_mut().unwrap(), + }; + + active_ifd.insert( tag.to_u16(), DirectoryEntry { - data_type: ::FIELD_TYPE.to_u16(), + data_type: value.is_type().to_u16(), count: value.count().try_into()?, data: bytes, }, @@ -196,11 +215,16 @@ impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> { } fn write_directory(&mut self) -> TiffResult { + let active_ifd = match &self.sub_ifd { + None => &mut self.ifd, + Some(_v) => self.sub_ifd.as_mut().unwrap(), + }; + // Start by writing out all values for &mut DirectoryEntry { data: ref mut bytes, .. - } in self.ifd.values_mut() + } in active_ifd.values_mut() { let data_bytes = mem::size_of::(); @@ -219,7 +243,7 @@ impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> { let offset = self.writer.offset(); - K::write_entry_count(self.writer, self.ifd.len())?; + K::write_entry_count(self.writer, active_ifd.len())?; for ( tag, DirectoryEntry { @@ -227,7 +251,7 @@ impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> { count, data: offset, }, - ) in self.ifd.iter() + ) in active_ifd.iter() { self.writer.write_u16(*tag)?; self.writer.write_u16(*field_type)?; @@ -253,6 +277,9 @@ impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> { } fn finish_internal(&mut self) -> TiffResult<()> { + if self.sub_ifd.is_some() { + self.subirectory_close()?; + } let ifd_pointer = self.write_directory()?; let curr_pos = self.writer.offset(); diff --git a/src/encoder/tiff_value.rs b/src/encoder/tiff_value.rs index 43653f42..a9d93d12 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,7 @@ 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,8 +41,8 @@ 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,8 +54,8 @@ 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,8 +67,8 @@ 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 +80,7 @@ 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 +93,7 @@ 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 +106,7 @@ 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 +119,7 @@ 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 +132,7 @@ 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 +146,7 @@ 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 +160,7 @@ 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 +178,7 @@ 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 +196,7 @@ 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 +214,7 @@ 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 +232,7 @@ 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 +250,7 @@ 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 +268,7 @@ 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 +286,7 @@ 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 +304,7 @@ 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 +322,7 @@ 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 +340,7 @@ 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 +358,7 @@ 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 +376,7 @@ 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 +399,7 @@ 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 +422,7 @@ 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 +452,7 @@ 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 +471,7 @@ 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/tests/decode_images.rs b/tests/decode_images.rs index 77f48e9d..501e2b66 100644 --- a/tests/decode_images.rs +++ b/tests/decode_images.rs @@ -4,6 +4,7 @@ use tiff::decoder::{ifd, Decoder, DecodingResult}; use tiff::ColorType; use std::fs::File; +use std::io::Write; use std::path::PathBuf; const TEST_IMAGE_DIR: &str = "./tests/images/"; @@ -510,3 +511,16 @@ 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 = Decoder::new(img_file).expect("Cannot create decoder"); + let raw_exif = decoder.read_exif().expect("Unable to read Exif data"); + + let mut output = File::create(PathBuf::from(TEST_IMAGE_DIR).join("exif.out")) + .expect("Unable to open output file"); + output.write(&raw_exif).expect("Unable to write output"); + output.flush().expect("Unable to flush writer"); +} \ No newline at end of file diff --git a/tests/images/exif.tif b/tests/images/exif.tif new file mode 100644 index 0000000000000000000000000000000000000000..7c0b41bcf8510e0f608bb62cd5d12f8e1d09951a GIT binary patch literal 672 zcmb7Au}%U(5PbtH8cj5iD2ZBF8mr6EVPmXluHy$F5WGZe#2>J+C6rcvmlYpiX-PQW z-t2BR=M}J*nSDF^-kY1vGTZ?;LK|BcB4OCnEVnaaarlt9QDS@dElpP^Sy~P^h)=$9LUFF*pMX}{k_&_?beXF4* zlsy+uZ}aiXD1QdX+-qqQ=87s*l%ThvSRsdFOi-a#YO@k*ot4k4|3HgDtAsemDmC9R F;}4Qg0?q&c literal 0 HcmV?d00001 From f449d51dbd2538e9c54ddc481e6d20730d076646 Mon Sep 17 00:00:00 2001 From: SeaEagle1 Date: Sat, 20 Jan 2024 01:11:38 +0100 Subject: [PATCH 02/20] Clean up decoder interface to allow decoding of Exif-only TIFF blocks (ie. decode it's own Exif output) --- src/decoder/image.rs | 17 +++++++++++ src/decoder/mod.rs | 71 ++++++++++++++++++++++++-------------------- src/tags.rs | 9 ++++++ 3 files changed, 65 insertions(+), 32 deletions(-) diff --git a/src/decoder/image.rs b/src/decoder/image.rs index 55e1e996..8591ab2d 100644 --- a/src/decoder/image.rs +++ b/src/decoder/image.rs @@ -285,6 +285,13 @@ impl Image { )); } } + (false,false,false,false) => { + chunk_type = ChunkType::None; + strip_decoder = None; + tile_attributes = None; + chunk_offsets = Vec::new(); + chunk_bytes = Vec::new(); + }, // Tiff without image data (_, _, _, _) => { return Err(TiffError::FormatError( TiffFormatError::StripTileTagConflict, @@ -504,6 +511,11 @@ impl Image { u32::try_from(tile_attrs.tile_length)?, )) } + ChunkType::None => { + return Err(TiffError::FormatError( + TiffFormatError::StripTileTagConflict, + )) + } } } @@ -536,6 +548,11 @@ impl Image { Ok((u32::try_from(tile_width)?, u32::try_from(tile_length)?)) } + ChunkType::None => { + return Err(TiffError::FormatError( + TiffFormatError::StripTileTagConflict, + )) + } } } diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index e81f9922..c75025ca 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -6,14 +6,12 @@ use std::ops::Range; use crate::{ bytecast, ColorType, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, UsageError, }; +use crate::decoder::ifd::Entry; use crate::encoder::{DirectoryEncoder, TiffEncoder, TiffKind}; use self::ifd::Directory; use self::image::Image; -use crate::tags::{ - CompressionMethod, PhotometricInterpretation, PlanarConfiguration, Predictor, SampleFormat, - Tag, Type, -}; +use crate::tags::{CompressionMethod, EXIF_TAGS, PhotometricInterpretation, PlanarConfiguration, Predictor, SampleFormat, Tag, Type}; use self::stream::{ByteOrder, EndianReader, SmartReader}; @@ -241,6 +239,7 @@ impl<'a> DecodingBuffer<'a> { pub enum ChunkType { Strip, Tile, + None, } /// Decoding limits @@ -609,6 +608,10 @@ impl Decoder { &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 @@ -837,7 +840,7 @@ impl Decoder { } /// Reads the IFD starting at the indicated location. - fn read_ifd( + pub fn read_ifd( reader: &mut SmartReader, bigtiff: bool, ifd_location: u64, @@ -875,10 +878,14 @@ impl Decoder { Ok((dir, next_ifd)) } + pub fn find_tag_entry(&self, tag: Tag) -> Option { + self.image().ifd.as_ref().unwrap().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) { + let entry = match self.find_tag_entry(tag) { None => return Ok(None), Some(entry) => entry.clone(), }; @@ -1189,23 +1196,29 @@ impl Decoder { // create new IFD let mut ifd0 = encoder.new_directory()?; - let ifd = self.image.ifd.as_ref().unwrap(); // copy Exif tags from main IFD - let exif_tags = [Tag::ImageWidth,Tag::ImageLength,Tag::PhotometricInterpretation, - Tag::ImageDescription,Tag::Make,Tag::Model,Tag::Orientation,Tag::XResolution, - Tag::YResolution,Tag::ResolutionUnit,Tag::Software,Tag::DateTime,Tag::Artist, - Tag::HostComputer,Tag::Unknown(33432)]; + let exif_tags = EXIF_TAGS; exif_tags.into_iter().for_each(| tag | { - let entry = ifd.get(&tag); + let entry = self.find_tag_entry(tag); if entry.is_some() { let b_entry = entry.unwrap().as_buffered(self.bigtiff.clone(), &mut self.reader).unwrap(); ifd0.write_tag(tag, b_entry).unwrap(); } }); - // find Exif sub-IFD and copy it whole - let exif_ifd_offset = self.find_tag(Tag::Unknown(34665))?; + // 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 self.bigtiff { exif_ifd_offset.unwrap().into_u64()? @@ -1214,27 +1227,21 @@ impl Decoder { }; // create sub-ifd - ifd0.subdirectory_start(); - // copy entries - self.copy_ifd(offset, &mut ifd0)?; - // return to ifd0 and write offset - let ifd_offset = ifd0.subirectory_close()?; - ifd0.write_tag(Tag::Unknown(34665), ifd_offset as u32)?; - } - ifd0.finish()?; - - Ok(exifdata.into_inner()) - } + new_ifd.subdirectory_start(); - fn copy_ifd(&mut self, offset: u64, new_ifd: &mut DirectoryEncoder) -> TiffResult<()> { - let (ifd, _trash1) = + let (ifd, _trash1) = Self::read_ifd(&mut self.reader, self.bigtiff.clone(), offset)?; - // loop through entries - ifd.into_iter().for_each(|(tag, value)| { - let b_entry = value.as_buffered(self.bigtiff.clone(), &mut self.reader).unwrap(); - new_ifd.write_tag(tag, b_entry).unwrap(); - }); + // loop through entries + ifd.into_iter().for_each(|(tag, value)| { + let b_entry = value.as_buffered(self.bigtiff.clone(), &mut self.reader).unwrap(); + new_ifd.write_tag(tag, b_entry).unwrap(); + }); + + // return to ifd0 and write offset + let ifd_offset = new_ifd.subirectory_close()?; + new_ifd.write_tag(tag, ifd_offset as u32)?; + } Ok(()) } diff --git a/src/tags.rs b/src/tags.rs index 3b86dc1f..d7c69726 100644 --- a/src/tags.rs +++ b/src/tags.rs @@ -128,9 +128,18 @@ pub enum Tag(u16) unknown("A private or extension tag") { GeoDoubleParamsTag = 34736, // (SPOT) GeoAsciiParamsTag = 34737, // (SPOT) GdalNodata = 42113, // Contains areas with missing data + GpsIfd = 34853, + ExifIfd = 34665, + InteropIfd = 40965, } } +/// List of Tiff tags in the image IFD read as part of the Exif metadata +pub const EXIF_TAGS: [Tag; 15] = [Tag::ImageWidth,Tag::ImageLength,Tag::PhotometricInterpretation, +Tag::ImageDescription,Tag::Make,Tag::Model,Tag::Orientation,Tag::XResolution, +Tag::YResolution,Tag::ResolutionUnit,Tag::Software,Tag::DateTime,Tag::Artist, +Tag::HostComputer,Tag::Unknown(33432)]; + tags! { /// The type of an IFD entry (a 2 byte field). pub enum Type(u16) { From 44f2da65bc72f52ef0f5c9cd8636eddc27386f0f Mon Sep 17 00:00:00 2001 From: SeaEagle1 Date: Sat, 20 Jan 2024 01:12:07 +0100 Subject: [PATCH 03/20] Exif encoding support --- src/encoder/mod.rs | 50 ++++++++++++++++++++++++++++++++++++++++++ tests/encode_images.rs | 24 +++++++++++++++++++- 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/src/encoder/mod.rs b/src/encoder/mod.rs index 627f576f..b1962de9 100644 --- a/src/encoder/mod.rs +++ b/src/encoder/mod.rs @@ -9,12 +9,15 @@ use std::{ mem, num::TryFromIntError, }; +use std::io::{Cursor, Read}; use crate::{ error::TiffResult, tags::{CompressionMethod, ResolutionUnit, Tag}, TiffError, TiffFormatError, }; +use crate::decoder::Decoder; +use crate::tags::EXIF_TAGS; pub mod colortype; pub mod compression; @@ -535,6 +538,53 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind, D: Compression> self.encoder.write_tag(Tag::YResolution, value).unwrap(); } + /// Write Exif data from TIFF encoded byte block + pub fn exif_tags(&mut self, source: Vec) -> TiffResult<()> { + let mut decoder = Decoder::new(Cursor::new(source))?; + + // copy Exif tags to main IFD + let exif_tags = EXIF_TAGS; + exif_tags.into_iter().for_each(| tag | { + let entry = decoder.find_tag_entry(tag); + if entry.is_some() && !self.encoder.ifd.contains_key(&tag.to_u16()) { + let b_entry = entry.unwrap().as_buffered(false, decoder.inner()).unwrap(); + self.encoder.write_tag(tag, b_entry).unwrap(); + } + }); + + // 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 Decoder) -> 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) = + Decoder::read_ifd(decoder.inner(), false, offset)?; + + // loop through entries + ifd.into_iter().for_each(|(tag, value)| { + let b_entry = value.as_buffered(false, decoder.inner()).unwrap(); + self.encoder.write_tag(tag, b_entry).unwrap(); + }); + + // return to ifd0 and write offset + let ifd_offset = self.encoder.subirectory_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 diff --git a/tests/encode_images.rs b/tests/encode_images.rs index 57c569fa..f730a7b8 100644 --- a/tests/encode_images.rs +++ b/tests/encode_images.rs @@ -6,7 +6,7 @@ use tiff::tags::Tag; use tiff::ColorType; use std::fs::File; -use std::io::{Cursor, Seek, SeekFrom}; +use std::io::{Cursor, Seek, SeekFrom, Write}; use std::path::PathBuf; #[test] @@ -527,3 +527,25 @@ fn test_rows_per_strip() { } } } + +#[test] +fn 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 = Decoder::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 output = Cursor::new(Vec::new()); + let mut output = File::create(PathBuf::from(TEST_IMAGE_DIR).join("exif.out.tiff")) + .expect("Unable to open output file"); + + 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"); + } +} \ No newline at end of file From fd302406ad4273671a519d97048342dba7a14df4 Mon Sep 17 00:00:00 2001 From: SeaEagle1 Date: Sat, 20 Jan 2024 17:14:44 +0100 Subject: [PATCH 04/20] Make tests actually test output integrity --- Cargo.toml | 1 + src/encoder/mod.rs | 1 + tests/decode_images.rs | 45 +++++++++++++++++++++++++++++++++++++++--- tests/encode_images.rs | 12 ++++++----- 4 files changed, 51 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index baf432fd..a06a6138 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ flate2 = "1.0.20" [dev-dependencies] criterion = "0.3.1" +kamadak-exif = "0.5.5" [[bench]] name = "lzw" diff --git a/src/encoder/mod.rs b/src/encoder/mod.rs index b1962de9..a6d8245b 100644 --- a/src/encoder/mod.rs +++ b/src/encoder/mod.rs @@ -188,6 +188,7 @@ impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> { /// Stop writing to sub-IFD and resume master IFD, returns offset of sub-IFD pub fn subirectory_close(&mut self) -> TiffResult { let offset = self.write_directory()?; + K::write_offset(self.writer, 0)?; self.sub_ifd = None; Ok(offset) } diff --git a/tests/decode_images.rs b/tests/decode_images.rs index 501e2b66..0608a318 100644 --- a/tests/decode_images.rs +++ b/tests/decode_images.rs @@ -4,8 +4,9 @@ use tiff::decoder::{ifd, Decoder, DecodingResult}; use tiff::ColorType; use std::fs::File; -use std::io::Write; +use std::io::{Cursor, Write}; use std::path::PathBuf; +use tiff::decoder::ifd::Value; const TEST_IMAGE_DIR: &str = "./tests/images/"; @@ -519,8 +520,46 @@ fn test_exif_decoding() { let mut decoder = Decoder::new(img_file).expect("Cannot create decoder"); let raw_exif = decoder.read_exif().expect("Unable to read Exif data"); - let mut output = File::create(PathBuf::from(TEST_IMAGE_DIR).join("exif.out")) - .expect("Unable to open output file"); + 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, 4177); +} + +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 = Decoder::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"), + } } \ No newline at end of file diff --git a/tests/encode_images.rs b/tests/encode_images.rs index f730a7b8..5dd92605 100644 --- a/tests/encode_images.rs +++ b/tests/encode_images.rs @@ -529,17 +529,14 @@ fn test_rows_per_strip() { } #[test] -fn recode_exif_data () { +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 = Decoder::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 output = Cursor::new(Vec::new()); - let mut output = File::create(PathBuf::from(TEST_IMAGE_DIR).join("exif.out.tiff")) - .expect("Unable to open output file"); - + 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"); @@ -547,5 +544,10 @@ fn recode_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, 64202); + } else { + panic!("Wrong data type"); } } \ No newline at end of file From ec8452b0f6d72ed7850610edac2a0dcc15e2ac9d Mon Sep 17 00:00:00 2001 From: SeaEagle1 Date: Sat, 20 Jan 2024 17:26:15 +0100 Subject: [PATCH 05/20] Improve comments --- src/decoder/ifd.rs | 24 +++++++++++++----------- src/decoder/image.rs | 6 +++--- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/decoder/ifd.rs b/src/decoder/ifd.rs index 0f804673..c7d12ab5 100644 --- a/src/decoder/ifd.rs +++ b/src/decoder/ifd.rs @@ -645,11 +645,19 @@ impl Entry { Ok(List(v)) } + /// retrieve entry with data read into a buffer (to cache it for writing) pub fn as_buffered(&self, bigtiff: bool, reader: &mut SmartReader ) -> TiffResult { + // 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, @@ -661,7 +669,6 @@ impl Entry { | Type::SRATIONAL | Type::IFD8 => 8, }; - let value_bytes = match self.count.checked_mul(tag_size) { Some(n) => n, None => { @@ -669,17 +676,11 @@ impl Entry { } }; - let native_bo; - #[cfg(target_endian = "little")] - {native_bo = ByteOrder::LittleEndian;} - #[cfg(not(target_endian = "little"))] - {native_bo = ByteOrder::BigEndian;} - let mut buf = vec![0; value_bytes as usize]; + // read values that fit within the IFD entry if value_bytes <= 4 || (bigtiff && value_bytes <= 8) { self.r(bo).read(&mut buf)?; - match self.type_ { // for multi-byte values Type::SHORT | Type::SSHORT | Type::LONG | Type::SLONG | Type::FLOAT | Type::IFD | Type::LONG8 | Type::SLONG8 | Type::DOUBLE | Type::IFD8 => @@ -694,7 +695,8 @@ impl Entry { _=>{} } - } else { + } else { // values that use a pointer + // read pointed data if bigtiff { reader.goto_offset(self.r(bo).read_u64()?.into())?; } else { @@ -738,6 +740,7 @@ impl Entry { } } +/// Entry with buffered instead of read data #[derive(Clone)] pub struct BufferedEntry { type_: Type, @@ -745,6 +748,7 @@ pub struct BufferedEntry { data: Vec, } +/// Implement TiffValue to allow writing this data with encoder impl TiffValue for BufferedEntry { const BYTE_LEN: u8 = 1; @@ -781,8 +785,6 @@ impl TiffValue for BufferedEntry { } } - - /// Extracts a list of BYTE tags stored in an offset #[inline] fn offset_to_bytes(n: usize, entry: &Entry) -> TiffResult { diff --git a/src/decoder/image.rs b/src/decoder/image.rs index 8591ab2d..a400af93 100644 --- a/src/decoder/image.rs +++ b/src/decoder/image.rs @@ -285,13 +285,13 @@ impl Image { )); } } - (false,false,false,false) => { - chunk_type = ChunkType::None; + (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(); - }, // Tiff without image data + }, (_, _, _, _) => { return Err(TiffError::FormatError( TiffFormatError::StripTileTagConflict, From fd64bb187fbb46d531a17bfdadaf30546bcddcb1 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Skutnik Date: Fri, 23 Aug 2024 22:50:07 +0300 Subject: [PATCH 06/20] Created global ifd definition --- Cargo.toml | 1 + examples/print-exif.rs | 44 ++ src/decoder/decoded_entry.rs | 488 ++++++++++++++ src/decoder/ifd.rs | 862 ------------------------ src/decoder/image.rs | 21 +- src/decoder/mod.rs | 151 +++-- src/decoder/tag_reader.rs | 17 +- src/encoder/mod.rs | 205 ++---- src/error.rs | 3 +- src/ifd.rs | 411 +++++++++++ src/lib.rs | 3 + src/tags.rs | 1 + src/tiff_kind.rs | 132 ++++ tests/decode_bigtiff_images.rs | 6 +- tests/decode_geotiff_images.rs | 11 +- tests/decode_images.rs | 89 ++- tests/encode_images.rs | 54 +- tests/encode_images_with_compression.rs | 3 +- tests/fuzz_tests.rs | 5 +- 19 files changed, 1332 insertions(+), 1175 deletions(-) create mode 100644 examples/print-exif.rs create mode 100644 src/decoder/decoded_entry.rs delete mode 100644 src/decoder/ifd.rs create mode 100644 src/ifd.rs create mode 100644 src/tiff_kind.rs diff --git a/Cargo.toml b/Cargo.toml index a06a6138..d12d1914 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ jpeg = { package = "jpeg-decoder", version = "0.3.0", default-features = false } flate2 = "1.0.20" [dev-dependencies] +clap = { version = "4.0.32", features = ["derive"] } criterion = "0.3.1" kamadak-exif = "0.5.5" diff --git a/examples/print-exif.rs b/examples/print-exif.rs new file mode 100644 index 00000000..581026a3 --- /dev/null +++ b/examples/print-exif.rs @@ -0,0 +1,44 @@ +extern crate exif; +extern crate tiff; + +use tiff::{decoder::Decoder, tags::Tag, TiffKindBig, TiffKindStandard}; + +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 = Decoder::<_, TiffKindStandard>::new(img_file).expect("Cannot create decoder"); + let exif = decoder.get_exif_data().expect("Unable to read Exif data"); + + println!("Base: {exif:#?}"); + + let exif = decoder + .get_exif_ifd(Tag::ExifIfd) + .expect("Unable to read Exif data"); + + println!("Extra: {exif:#?}"); + + let exif = decoder + .get_exif_ifd(Tag::GpsIfd) + .expect("Unable to read Exif data"); + + println!("GPS: {exif:#?}"); + + let exif = decoder + .get_exif_ifd(Tag::InteropIfd) + .expect("Unable to read Exif data"); + + println!("Interop: {exif:#?}"); +} diff --git a/src/decoder/decoded_entry.rs b/src/decoder/decoded_entry.rs new file mode 100644 index 00000000..8cefa93e --- /dev/null +++ b/src/decoder/decoded_entry.rs @@ -0,0 +1,488 @@ +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::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]; + // read values that fit within the IFD entry + if value_bytes <= 4 || (K::is_big() && value_bytes <= 8) { + self.r(bo).read(&mut buf)?; + + match self.type_ { + // for multi-byte values + Type::SHORT + | Type::SSHORT + | Type::LONG + | Type::SLONG + | Type::FLOAT + | Type::IFD + | Type::LONG8 + | Type::SLONG8 + | Type::DOUBLE + | Type::IFD8 => { + if native_bo != bo { + // if byte-order is non-native + // reverse byte order + let mut new_buf = vec![0; value_bytes as usize]; + for i in 0..value_bytes { + new_buf[i as usize] = buf[(value_bytes - 1 - i) as usize]; + } + buf = new_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)?; + + match self.type_ { + // for multi-byte values + Type::LONG8 | Type::SLONG8 | Type::DOUBLE => { + if native_bo != bo { + // if byte-order is non-native + // reverse byte order + let mut new_buf = vec![0; value_bytes as usize]; + for i in 0..value_bytes { + new_buf[i as usize] = buf[(value_bytes - 1 - i) as usize]; + } + buf = new_buf; + } + } + Type::RATIONAL | Type::SRATIONAL => { + if native_bo != bo { + // if byte-order is non-native + // reverse byte order + let mut new_buf = vec![0; 8]; + new_buf[0] = buf[3]; + new_buf[1] = buf[2]; + new_buf[2] = buf[1]; + new_buf[3] = buf[0]; + new_buf[4] = buf[7]; + new_buf[5] = buf[6]; + new_buf[6] = buf[5]; + new_buf[7] = buf[4]; + buf = new_buf; + } + } + _ => {} + } + } + + 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 efc2697c..00000000 --- a/src/decoder/ifd.rs +++ /dev/null @@ -1,862 +0,0 @@ -//! Function for reading TIFF tags - -use std::borrow::Cow; -use std::collections::HashMap; -use std::io::{self, Read, Seek}; -use std::mem; -use std::str; - -use super::stream::{ByteOrder, EndianReader, SmartReader}; -use crate::encoder::TiffValue; -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)) - } - - /// retrieve entry with data read into a buffer (to cache it for writing) - pub fn as_buffered( - &self, - bigtiff: bool, - reader: &mut SmartReader, - ) -> TiffResult { - // 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 self.count.checked_mul(tag_size) { - Some(n) => n, - None => { - return Err(TiffError::LimitsExceeded); - } - }; - - let mut buf = vec![0; value_bytes as usize]; - // read values that fit within the IFD entry - if value_bytes <= 4 || (bigtiff && value_bytes <= 8) { - self.r(bo).read(&mut buf)?; - - match self.type_ { - // for multi-byte values - Type::SHORT - | Type::SSHORT - | Type::LONG - | Type::SLONG - | Type::FLOAT - | Type::IFD - | Type::LONG8 - | Type::SLONG8 - | Type::DOUBLE - | Type::IFD8 => { - if native_bo != bo { - // if byte-order is non-native - // reverse byte order - let mut new_buf = vec![0; value_bytes as usize]; - for i in 0..value_bytes { - new_buf[i as usize] = buf[(value_bytes - 1 - i) as usize]; - } - buf = new_buf; - } - } - _ => {} - } - } else { - // values that use a pointer - // read pointed data - if bigtiff { - reader.goto_offset(self.r(bo).read_u64()?.into())?; - } else { - reader.goto_offset(self.r(bo).read_u32()?.into())?; - } - reader.read_exact(&mut buf)?; - - match self.type_ { - // for multi-byte values - Type::LONG8 | Type::SLONG8 | Type::DOUBLE => { - if native_bo != bo { - // if byte-order is non-native - // reverse byte order - let mut new_buf = vec![0; value_bytes as usize]; - for i in 0..value_bytes { - new_buf[i as usize] = buf[(value_bytes - 1 - i) as usize]; - } - buf = new_buf; - } - } - Type::RATIONAL | Type::SRATIONAL => { - if native_bo != bo { - // if byte-order is non-native - // reverse byte order - let mut new_buf = vec![0; 8]; - new_buf[0] = buf[3]; - new_buf[1] = buf[2]; - new_buf[2] = buf[1]; - new_buf[3] = buf[0]; - new_buf[4] = buf[7]; - new_buf[5] = buf[6]; - new_buf[6] = buf[5]; - new_buf[7] = buf[4]; - buf = new_buf; - } - } - _ => {} - } - } - - Ok(BufferedEntry { - type_: self.type_, - count: self.count.clone(), - data: buf, - }) - } -} - -/// Entry with buffered instead of read data -#[derive(Clone)] -pub struct BufferedEntry { - type_: Type, - count: u64, - 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() as usize - } - - fn bytes(&self) -> usize { - 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 => 0, - }; - value_bytes as usize - } - - fn data(&self) -> Cow<[u8]> { - Cow::Borrowed(&self.data) - } -} - -/// 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 08206d47..25174c26 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()?; diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index 0f991618..183e247b 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -1,16 +1,16 @@ -use std::collections::{HashMap, HashSet}; +use std::collections::HashSet; use std::convert::TryFrom; use std::io::{self, Cursor, Read, Seek, Write}; -use std::ops::Range; -use crate::decoder::ifd::Entry; -use crate::encoder::{DirectoryEncoder, TiffEncoder, TiffKind}; +use crate::encoder::{DirectoryEncoder, TiffEncoder}; use crate::{ - bytecast, ColorType, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, UsageError, + bytecast, ColorType, TiffError, TiffFormatError, TiffKind, TiffResult, TiffUnsupportedError, + UsageError, }; -use self::ifd::Directory; +use self::decoded_entry::DecodedEntry; use self::image::Image; +use crate::ifd::{BufferedEntry, Directory, Value}; use crate::tags::{ CompressionMethod, PhotometricInterpretation, PlanarConfiguration, Predictor, SampleFormat, Tag, Type, EXIF_TAGS, @@ -18,7 +18,7 @@ use crate::tags::{ use self::stream::{ByteOrder, EndianReader, SmartReader}; -pub mod ifd; +mod decoded_entry; mod image; mod stream; mod tag_reader; @@ -247,17 +247,17 @@ impl Default for Limits { /// /// Currently does not support decoding of interlaced images #[derive(Debug)] -pub struct Decoder +pub struct Decoder where R: Read + Seek, + K: TiffKind, { reader: SmartReader, - bigtiff: bool, limits: Limits, next_ifd: Option, ifd_offsets: Vec, seen_ifds: HashSet, - image: Image, + image: Image, } fn rev_hpredict_nsamp(buf: &mut [u8], bit_depth: u8, samples: usize) { @@ -425,9 +425,9 @@ fn fix_endianness(buf: &mut [u8], byte_order: ByteOrder, bit_depth: u8) { }; } -impl Decoder { +impl Decoder { /// 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 { @@ -464,6 +464,9 @@ impl Decoder { )) } }; + + assert!(K::is_big() == bigtiff, "Tiff format is invalid !"); + let next_ifd = if bigtiff { Some(reader.read_u64()?) } else { @@ -476,7 +479,6 @@ impl Decoder { let mut decoder = Decoder { reader, - bigtiff, limits: Default::default(), next_ifd, ifd_offsets, @@ -504,7 +506,7 @@ impl Decoder { Ok(decoder) } - pub fn with_limits(mut self, limits: Limits) -> Decoder { + pub fn with_limits(mut self, limits: Limits) -> Decoder { self.limits = limits; self } @@ -517,7 +519,7 @@ impl Decoder { self.image().colortype() } - fn image(&self) -> &Image { + fn image(&self) -> &Image { &self.image } @@ -552,9 +554,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 { @@ -564,18 +566,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) { @@ -594,7 +592,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(()) } @@ -610,7 +608,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) @@ -686,7 +684,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, )); @@ -721,11 +719,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, @@ -736,39 +731,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. 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; @@ -777,7 +771,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() @@ -791,23 +785,19 @@ impl Decoder { Ok((dir, next_ifd)) } - pub fn find_tag_entry(&self, tag: Tag) -> Option { + pub fn find_tag_entry(&self, tag: Tag) -> Option> { self.image().ifd.as_ref().unwrap().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> { + 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. @@ -849,7 +839,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( @@ -1117,11 +1107,49 @@ 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 + let exif_tags = EXIF_TAGS; + for tag in exif_tags.into_iter() { + if let Some(entry) = self.find_tag_entry(tag) { + ifd.insert(tag, 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) + } + /// Extracts the EXIF metadata (if present) and returns it in a light TIFF format - pub fn read_exif(&mut self) -> TiffResult> { + pub fn read_exif(&mut self) -> TiffResult> { // create tiff encoder for result let mut exifdata = Cursor::new(Vec::new()); - let mut encoder = TiffEncoder::new(Write::by_ref(&mut exifdata))?; + let mut encoder = TiffEncoder::<_, T>::new(Write::by_ref(&mut exifdata))?; // create new IFD let mut ifd0 = encoder.new_directory()?; @@ -1131,10 +1159,7 @@ impl Decoder { exif_tags.into_iter().for_each(|tag| { let entry = self.find_tag_entry(tag); if entry.is_some() { - let b_entry = entry - .unwrap() - .as_buffered(self.bigtiff.clone(), &mut self.reader) - .unwrap(); + let b_entry = entry.unwrap().as_buffered(&mut self.reader).unwrap(); ifd0.write_tag(tag, b_entry).unwrap(); } }); @@ -1149,14 +1174,14 @@ impl Decoder { Ok(exifdata.into_inner()) } - fn copy_ifd( + fn copy_ifd( &mut self, tag: Tag, - new_ifd: &mut DirectoryEncoder, + new_ifd: &mut DirectoryEncoder, ) -> TiffResult<()> { let exif_ifd_offset = self.find_tag(tag)?; if exif_ifd_offset.is_some() { - let offset = if self.bigtiff { + let offset = if K::is_big() { exif_ifd_offset.unwrap().into_u64()? } else { exif_ifd_offset.unwrap().into_u32()?.into() @@ -1165,13 +1190,11 @@ impl Decoder { // create sub-ifd new_ifd.subdirectory_start(); - let (ifd, _trash1) = Self::read_ifd(&mut self.reader, self.bigtiff.clone(), offset)?; + 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(self.bigtiff.clone(), &mut self.reader) - .unwrap(); + let b_entry = value.as_buffered(&mut self.reader).unwrap(); new_ifd.write_tag(tag, b_entry).unwrap(); }); 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/mod.rs b/src/encoder/mod.rs index 7973a2d1..7438ecf3 100644 --- a/src/encoder/mod.rs +++ b/src/encoder/mod.rs @@ -1,22 +1,20 @@ pub use tiff_value::*; +use std::io::{Cursor, Read}; use std::{ cmp, - collections::BTreeMap, io::{self, Seek, Write}, marker::PhantomData, mem, - num::TryFromIntError, }; -use std::io::{Cursor, Read}; use crate::{ + decoder::Decoder, error::TiffResult, - tags::{CompressionMethod, ResolutionUnit, Tag}, - TiffError, TiffFormatError, + ifd::{BufferedEntry, Directory}, + tags::{CompressionMethod, ResolutionUnit, Tag, EXIF_TAGS}, + TiffError, TiffFormatError, TiffKind, TiffKindStandard, }; -use crate::decoder::Decoder; -use crate::tags::EXIF_TAGS; pub mod colortype; pub mod compression; @@ -25,7 +23,7 @@ mod writer; use self::colortype::*; use self::compression::*; -use self::writer::*; +pub use self::writer::*; /// Encoder for Tiff and BigTiff files. /// @@ -58,18 +56,18 @@ pub struct TiffEncoder { } /// Constructor functions to create standard Tiff files. -impl TiffEncoder { +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> { + pub fn new(writer: W) -> TiffResult> { TiffEncoder::new_generic(writer) } } /// Constructor functions to create BigTiff files. -impl TiffEncoder { +impl TiffEncoder { /// Creates a new encoder for BigTiff files. /// /// To create standard Tiff files, use [`new`][TiffEncoder::new] or @@ -95,7 +93,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. @@ -104,7 +102,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) } @@ -115,7 +113,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) } @@ -129,7 +127,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) } @@ -145,7 +143,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::with_compression(encoder, width, height, compression)?; image.write_data(data) @@ -159,10 +157,10 @@ impl TiffEncoder { 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>, - sub_ifd: Option>>, + ifd: Directory, + sub_ifd: Option>, + _phantom: ::std::marker::PhantomData, } impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> { @@ -170,18 +168,19 @@ impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> { // 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 { + Ok(DirectoryEncoder:: { writer, dropped: false, ifd_pointer_pos, - ifd: BTreeMap::new(), + ifd: Directory::new(), sub_ifd: None, + _phantom: ::std::marker::PhantomData, }) } /// Start writing to sub-IFD pub fn subdirectory_start(&mut self) { - self.sub_ifd = Some(BTreeMap::new()); + self.sub_ifd = Some(Directory::new()); } /// Stop writing to sub-IFD and resume master IFD, returns offset of sub-IFD @@ -206,9 +205,9 @@ impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> { }; active_ifd.insert( - tag.to_u16(), - DirectoryEntry { - data_type: value.is_type().to_u16(), + tag, + BufferedEntry { + type_: value.is_type(), count: value.count().try_into()?, data: bytes, }, @@ -224,12 +223,12 @@ impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> { }; // Start by writing out all values - for &mut DirectoryEntry { + for &mut BufferedEntry { data: ref mut bytes, .. } in active_ifd.values_mut() { - let data_bytes = mem::size_of::(); + let data_bytes = K::OffsetType::BYTE_LEN as usize; if bytes.len() > data_bytes { let offset = self.writer.offset(); @@ -249,17 +248,17 @@ impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> { K::write_entry_count(self.writer, active_ifd.len())?; for ( tag, - DirectoryEntry { - data_type: field_type, + BufferedEntry { + type_: field_type, count, data: offset, }, ) in active_ifd.iter() { - self.writer.write_u16(*tag)?; - self.writer.write_u16(*field_type)?; - (*count).write(self.writer)?; - self.writer.write_bytes(offset)?; + self.writer.write_u16(tag.to_u16())?; + self.writer.write_u16(field_type.to_u16())?; + K::convert_offset(*count)?.write(self.writer)?; + self.writer.write_bytes(&offset)?; } Ok(offset) @@ -281,7 +280,7 @@ impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> { fn finish_internal(&mut self) -> TiffResult<()> { if self.sub_ifd.is_some() { - self.subirectory_close()?; + self.subirectory_close()?; } let ifd_pointer = self.write_directory()?; let curr_pos = self.writer.offset(); @@ -539,15 +538,15 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind, D: Compression> } /// Write Exif data from TIFF encoded byte block - pub fn exif_tags(&mut self, source: Vec) -> TiffResult<()> { - let mut decoder = Decoder::new(Cursor::new(source))?; + pub fn exif_tags(&mut self, source: Vec) -> TiffResult<()> { + let mut decoder = Decoder::<_, F>::new(Cursor::new(source))?; // copy Exif tags to main IFD let exif_tags = EXIF_TAGS; - exif_tags.into_iter().for_each(| tag | { + exif_tags.into_iter().for_each(|tag| { let entry = decoder.find_tag_entry(tag); - if entry.is_some() && !self.encoder.ifd.contains_key(&tag.to_u16()) { - let b_entry = entry.unwrap().as_buffered(false, decoder.inner()).unwrap(); + if entry.is_some() && !self.encoder.ifd.contains_key(&tag) { + let b_entry = entry.unwrap().as_buffered(decoder.inner()).unwrap(); self.encoder.write_tag(tag, b_entry).unwrap(); } }); @@ -560,7 +559,11 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind, D: Compression> Ok(()) } - fn copy_ifd(&mut self, tag: Tag, decoder: &mut Decoder) -> TiffResult<()> { + fn copy_ifd( + &mut self, + tag: Tag, + decoder: &mut Decoder, + ) -> TiffResult<()> { let exif_ifd_offset = decoder.find_tag(tag)?; if exif_ifd_offset.is_some() { let offset = exif_ifd_offset.unwrap().into_u32()?.into(); @@ -568,12 +571,11 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind, D: Compression> // create sub-ifd self.encoder.subdirectory_start(); - let (ifd, _trash1) = - Decoder::read_ifd(decoder.inner(), false, offset)?; + let (ifd, _trash1) = Decoder::<_, F>::read_ifd(decoder.inner(), offset)?; // loop through entries ifd.into_iter().for_each(|(tag, value)| { - let b_entry = value.as_buffered(false, decoder.inner()).unwrap(); + let b_entry = value.as_buffered(decoder.inner()).unwrap(); self.encoder.write_tag(tag, b_entry).unwrap(); }); @@ -639,120 +641,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/error.rs b/src/error.rs index 7dc781e0..f11dde80 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, }; diff --git a/src/ifd.rs b/src/ifd.rs new file mode 100644 index 00000000..514c4ceb --- /dev/null +++ b/src/ifd.rs @@ -0,0 +1,411 @@ +//! Abstractions over TIFF tags + +use std::borrow::Cow; +use std::collections::BTreeMap; + +use crate::encoder::TiffValue; +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), + )), + } + } +} + +/// 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: u32 = 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, + }; + + match self.count.checked_mul(tag_size.into()) { + Some(n) => n.try_into().unwrap(), + None => 0usize, + } + } + + fn data(&self) -> Cow<[u8]> { + Cow::Borrowed(&self.data) + } +} + +/// Type representing an Image File Directory +#[derive(Debug)] +pub struct Directory(BTreeMap); + +impl Directory { + pub fn new() -> Self { + Directory(BTreeMap::new()) + } + + pub fn insert(&mut self, tag: Tag, 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: &Tag) -> bool { + self.0.contains_key(&tag) + } + + pub fn get(&self, tag: &Tag) -> Option<&E> { + self.0.get(&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() + } +} 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 af580f12..3514f11c 100644 --- a/src/tags.rs +++ b/src/tags.rs @@ -68,6 +68,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, 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..bd241121 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::Decoder, tags::Tag, ColorType, TiffKindBig}; 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 = Decoder::<_, TiffKindBig>::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..c504d836 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::{Decoder, DecodingResult}, + tags::Tag, + ColorType, TiffKindStandard, +}; use std::fs::File; use std::path::PathBuf; @@ -15,7 +17,8 @@ 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 = + Decoder::<_, TiffKindStandard>::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 0608a318..ddfa3dd2 100644 --- a/tests/decode_images.rs +++ b/tests/decode_images.rs @@ -1,12 +1,12 @@ extern crate tiff; -use tiff::decoder::{ifd, Decoder, DecodingResult}; -use tiff::ColorType; +use tiff::decoder::{Decoder, DecodingResult}; +use tiff::ifd::Value; +use tiff::{ColorType, TiffKindBig, TiffKindStandard}; use std::fs::File; use std::io::{Cursor, Write}; use std::path::PathBuf; -use tiff::decoder::ifd::Value; const TEST_IMAGE_DIR: &str = "./tests/images/"; @@ -15,7 +15,8 @@ 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 = + Decoder::<_, TiffKindStandard>::new(img_file).expect("Cannot create decoder"); assert_eq!(decoder.colortype().unwrap(), expected_type); let img_res = decoder.read_image().unwrap(); @@ -45,7 +46,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 = Decoder::<_, TiffKindStandard>::new(img_file).expect("Cannot create decoder"); assert_eq!(decoder.colortype().unwrap(), expected_type); assert!(match decoder.read_image() { Err(tiff::TiffError::UnsupportedError( @@ -167,10 +168,11 @@ 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 = + Decoder::<_, TiffKindStandard>::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/" ), @@ -191,7 +193,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 = Decoder::<_, TiffKindStandard>::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() { @@ -212,7 +214,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 = Decoder::<_, TiffKindStandard>::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()); @@ -270,7 +272,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 = Decoder::<_, TiffKindStandard>::new(img_file).expect("Cannot create decoder"); assert_eq!(decoder.colortype().unwrap(), expected_type); let tiles = decoder.tile_count().unwrap(); @@ -295,7 +297,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 = Decoder::<_, TiffKindStandard>::new(img_file).expect("Cannot create decoder"); assert_eq!(decoder.colortype().unwrap(), expected_type); let chunks = decoder.strip_count().unwrap(); @@ -341,7 +343,8 @@ fn test_div_zero() { 178, 178, 178, ]; - let err = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err(); + let err = tiff::decoder::Decoder::<_, TiffKindStandard>::new(std::io::Cursor::new(&image)) + .unwrap_err(); match err { TiffError::FormatError(TiffFormatError::StripTileTagConflict) => {} @@ -359,7 +362,8 @@ 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 = + tiff::decoder::Decoder::<_, TiffKindBig>::new(std::io::Cursor::new(&image)).unwrap_err(); match error { tiff::TiffError::LimitsExceeded => {} @@ -377,7 +381,8 @@ fn fuzzer_testcase5() { 178, 178, 178, ]; - let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err(); + let _ = tiff::decoder::Decoder::<_, TiffKindStandard>::new(std::io::Cursor::new(&image)) + .unwrap_err(); } #[test] @@ -390,7 +395,8 @@ fn fuzzer_testcase1() { 178, ]; - let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err(); + let _ = tiff::decoder::Decoder::<_, TiffKindStandard>::new(std::io::Cursor::new(&image)) + .unwrap_err(); } #[test] @@ -403,7 +409,8 @@ fn fuzzer_testcase6() { 178, 178, ]; - let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err(); + let _ = tiff::decoder::Decoder::<_, TiffKindStandard>::new(std::io::Cursor::new(&image)) + .unwrap_err(); } #[test] @@ -415,7 +422,8 @@ 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 _ = tiff::decoder::Decoder::<_, TiffKindStandard>::new(std::io::Cursor::new(&image)) + .unwrap_err(); } #[test] @@ -427,7 +435,8 @@ 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 _ = tiff::decoder::Decoder::<_, TiffKindStandard>::new(std::io::Cursor::new(&image)) + .unwrap_err(); } #[test] @@ -443,7 +452,8 @@ fn fuzzer_testcase2() { 73, ]; - let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err(); + let _ = tiff::decoder::Decoder::<_, TiffKindStandard>::new(std::io::Cursor::new(&image)) + .unwrap_err(); } #[test] @@ -459,7 +469,8 @@ 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 _ = tiff::decoder::Decoder::<_, TiffKindStandard>::new(std::io::Cursor::new(&image)) + .unwrap_err(); } #[test] @@ -472,7 +483,8 @@ fn fuzzer_testcase3() { 255, 255, ]; - let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err(); + let _ = tiff::decoder::Decoder::<_, TiffKindStandard>::new(std::io::Cursor::new(&image)) + .unwrap_err(); } #[test] @@ -490,7 +502,8 @@ 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 = tiff::decoder::Decoder::<_, TiffKindStandard>::new(std::io::Cursor::new(&image)) + .unwrap_err(); match error { TiffError::FormatError(TiffFormatError::CycleInOffsets) => {} @@ -517,15 +530,17 @@ fn test_predictor_3_gray_f32() { 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 = Decoder::new(img_file).expect("Cannot create decoder"); - let raw_exif = decoder.read_exif().expect("Unable to read Exif data"); + let mut decoder = Decoder::<_, TiffKindStandard>::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(); + let sum: u64 = output.into_inner().into_iter().map(u64::from).sum(); assert_eq!(sum, 4177); } @@ -534,21 +549,27 @@ extern crate exif; 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 = Decoder::new(img_file).expect("Cannot create decoder"); - let raw_exif = decoder.read_exif().expect("Unable to read Exif data"); + let mut decoder = Decoder::<_, TiffKindStandard>::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"); + 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); }, + 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); }, + Some(colourspace) => { + assert_eq!(colourspace.value.get_uint(0).unwrap(), 1); + } None => panic!("Colourspace tag missing"), } @@ -559,7 +580,7 @@ fn test_exif_parsing() { assert_eq!(value.to_f32(), -2.0f32) } _ => panic!("ExposureCompensation has wrong type"), - } + }, None => panic!("ExposureCompensation tag missing"), } -} \ No newline at end of file +} diff --git a/tests/encode_images.rs b/tests/encode_images.rs index 79bd692c..7df63c4f 100644 --- a/tests/encode_images.rs +++ b/tests/encode_images.rs @@ -1,9 +1,9 @@ extern crate tiff; -use tiff::decoder::{ifd, Decoder, DecodingResult}; +use tiff::decoder::{Decoder, DecodingResult}; use tiff::encoder::{colortype, Ifd, Ifd8, SRational, TiffEncoder}; use tiff::tags::Tag; -use tiff::ColorType; +use tiff::{ifd, ColorType, TiffKind, TiffKindBig, TiffKindStandard}; use std::fs::File; use std::io::{Cursor, Seek, SeekFrom, Write}; @@ -22,7 +22,7 @@ fn encode_decode() { } let mut file = Cursor::new(Vec::new()); { - let mut tiff = TiffEncoder::new(&mut file).unwrap(); + let mut tiff = TiffEncoder::<_, TiffKindStandard>::new(&mut file).unwrap(); let mut image = tiff.new_image::(100, 100).unwrap(); image @@ -33,7 +33,7 @@ fn encode_decode() { } { file.seek(SeekFrom::Start(0)).unwrap(); - let mut decoder = Decoder::new(&mut file).unwrap(); + let mut decoder = Decoder::<_, TiffKindStandard>::new(&mut file).unwrap(); assert_eq!(decoder.colortype().unwrap(), ColorType::RGB(8)); assert_eq!(decoder.dimensions().unwrap(), (100, 100)); assert_eq!( @@ -61,7 +61,7 @@ fn encode_decode_big() { } let mut file = Cursor::new(Vec::new()); { - let mut tiff = TiffEncoder::new_big(&mut file).unwrap(); + let mut tiff = TiffEncoder::<_, TiffKindBig>::new_big(&mut file).unwrap(); let mut image = tiff.new_image::(100, 100).unwrap(); image @@ -72,7 +72,7 @@ fn encode_decode_big() { } { file.seek(SeekFrom::Start(0)).unwrap(); - let mut decoder = Decoder::new(&mut file).unwrap(); + let mut decoder = Decoder::<_, TiffKindBig>::new(&mut file).unwrap(); assert_eq!(decoder.colortype().unwrap(), ColorType::RGB(8)); assert_eq!(decoder.dimensions().unwrap(), (100, 100)); assert_eq!( @@ -92,7 +92,7 @@ fn test_encode_ifd() { let mut data = Cursor::new(Vec::new()); { - let mut tiff = TiffEncoder::new(&mut data).unwrap(); + let mut tiff = TiffEncoder::<_, TiffKindStandard>::new(&mut data).unwrap(); let mut image_encoder = tiff.new_image::(1, 1).unwrap(); image_encoder.write_strip(&[1]).unwrap(); let encoder = image_encoder.encoder(); @@ -121,7 +121,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 = Decoder::<_, TiffKindStandard>::new(&mut data).unwrap(); assert_eq!(decoder.assert_tag_u32(65000), 42); assert_eq!(decoder.assert_tag_u32_vec(65000), [42]); @@ -143,7 +143,7 @@ fn test_encode_undersized_buffer() { let input_data = vec![1, 2, 3]; let output = Vec::new(); let mut output_stream = Cursor::new(output); - if let Ok(mut tiff) = TiffEncoder::new(&mut output_stream) { + if let Ok(mut tiff) = TiffEncoder::<_, TiffKindStandard>::new(&mut output_stream) { let res = tiff.write_image::(50, 50, &input_data); assert!(res.is_err()); } @@ -159,7 +159,8 @@ 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 = + Decoder::<_, TiffKindStandard>::new(img_file).expect("Cannot create decoder"); assert_eq!(decoder.colortype().unwrap(), expected_type); let image_data = match decoder.read_image().unwrap() { @@ -169,14 +170,14 @@ macro_rules! test_roundtrip { let mut file = Cursor::new(Vec::new()); { - let mut tiff = TiffEncoder::new(&mut file).unwrap(); + let mut tiff = TiffEncoder::<_, TiffKindStandard>::new(&mut file).unwrap(); let (width, height) = decoder.dimensions().unwrap(); tiff.write_image::(width, height, &image_data).unwrap(); } file.seek(SeekFrom::Start(0)).unwrap(); { - let mut decoder = Decoder::new(&mut file).unwrap(); + let mut decoder = Decoder::<_, TiffKindStandard>::new(&mut file).unwrap(); if let DecodingResult::$buffer(img_res) = decoder.read_image().unwrap() { assert_eq!(image_data, img_res); } else { @@ -292,7 +293,7 @@ trait AssertDecode { fn assert_tag_i64_vec(&mut self, tag: u16) -> Vec; } -impl AssertDecode for Decoder { +impl AssertDecode for Decoder { fn assert_tag_u32(&mut self, tag: u16) -> u32 { self.get_tag(Tag::Unknown(tag)).unwrap().into_u32().unwrap() } @@ -336,7 +337,7 @@ fn test_multiple_byte() { let mut data = Cursor::new(Vec::new()); { - let mut tiff = TiffEncoder::new(&mut data).unwrap(); + let mut tiff = TiffEncoder::<_, TiffKindStandard>::new(&mut data).unwrap(); let mut image_encoder = tiff.new_image::(1, 1).unwrap(); image_encoder.write_strip(&[1]).unwrap(); let encoder = image_encoder.encoder(); @@ -358,7 +359,7 @@ fn test_multiple_byte() { data.set_position(0); { - let mut decoder = Decoder::new(&mut data).unwrap(); + let mut decoder = Decoder::<_, TiffKindStandard>::new(&mut data).unwrap(); assert_eq!(decoder.assert_tag_u32_vec(65000), [1]); assert_eq!(decoder.assert_tag_u32_vec(65001), [1, 2]); @@ -377,7 +378,7 @@ fn test_signed() { } { - let mut tiff = TiffEncoder::new(&mut data).unwrap(); + let mut tiff = TiffEncoder::<_, TiffKindStandard>::new(&mut data).unwrap(); let mut image_encoder = tiff.new_image::(1, 1).unwrap(); image_encoder.write_strip(&[1]).unwrap(); let encoder = image_encoder.encoder(); @@ -439,7 +440,7 @@ fn test_signed() { //Rewind the cursor for reading data.set_position(0); { - let mut decoder = Decoder::new(&mut data).unwrap(); + let mut decoder = Decoder::<_, TiffKindStandard>::new(&mut data).unwrap(); assert_eq!(decoder.assert_tag_i32(65000), -1); assert_eq!(decoder.assert_tag_i32_vec(65001), [-1]); @@ -473,7 +474,7 @@ fn test_multipage_image() { { // first create a multipage image with 2 images - let mut img_encoder = TiffEncoder::new(&mut img_file).unwrap(); + let mut img_encoder = TiffEncoder::<_, TiffKindStandard>::new(&mut img_file).unwrap(); // write first grayscale image (2x2 16-bit) let img1: Vec = [1, 2, 3, 4].to_vec(); @@ -491,7 +492,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 = Decoder::<_, TiffKindStandard>::new(&mut img_file).unwrap(); // check the dimensions of the image in the first page assert_eq!(img_decoder.dimensions().unwrap(), (2, 2)); @@ -506,7 +507,7 @@ fn test_multipage_image() { fn test_rows_per_strip() { let mut file = Cursor::new(Vec::new()); { - let mut img_encoder = TiffEncoder::new(&mut file).unwrap(); + let mut img_encoder = TiffEncoder::<_, TiffKindStandard>::new(&mut file).unwrap(); let mut image = img_encoder.new_image::(100, 100).unwrap(); assert_eq!(image.next_strip_sample_count(), 100 * 100); @@ -526,7 +527,7 @@ fn test_rows_per_strip() { file.seek(SeekFrom::Start(0)).unwrap(); { - let mut decoder = Decoder::new(&mut file).unwrap(); + let mut decoder = Decoder::<_, TiffKindStandard>::new(&mut file).unwrap(); assert_eq!(decoder.get_tag_u64(Tag::RowsPerStrip).unwrap(), 2); assert_eq!(decoder.strip_count().unwrap(), 50); @@ -544,18 +545,21 @@ fn test_rows_per_strip() { 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 = Decoder::new(img_file).expect("Cannot create decoder"); - let raw_exif = decoder.read_exif().expect("Unable to read Exif data"); + let mut decoder = Decoder::<_, TiffKindStandard>::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 mut tiff = + TiffEncoder::<_, TiffKindStandard>::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) + .exif_tags::(raw_exif) .expect("Unable to write Exif data"); if let DecodingResult::U8(vec) = image_data { image diff --git a/tests/encode_images_with_compression.rs b/tests/encode_images_with_compression.rs index 1cf178f2..fcf7e8b8 100644 --- a/tests/encode_images_with_compression.rs +++ b/tests/encode_images_with_compression.rs @@ -8,6 +8,7 @@ use tiff::{ compression::*, TiffEncoder, TiffValue, }, + TiffKindStandard, }; trait TestImage: From::Inner>> { @@ -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 = Decoder::<_, TiffKindStandard>::new(data).unwrap(); // Check the RGB image assert_eq!( diff --git a/tests/fuzz_tests.rs b/tests/fuzz_tests.rs index 446d6bd7..3a02348e 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::Decoder, TiffKindStandard, 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 = Decoder::<_, TiffKindStandard>::new(file)?; decoder.read_image()?; Ok(()) } From 3f4b59cc73aba6ddb41cff39af645de28c766a25 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Skutnik Date: Fri, 23 Aug 2024 23:37:36 +0300 Subject: [PATCH 07/20] Add type aliases to avoid generics mess --- examples/print-exif.rs | 4 +- src/decoder/mod.rs | 14 +++--- src/encoder/mod.rs | 46 ++++++-------------- src/lib.rs | 6 +++ tests/decode_bigtiff_images.rs | 4 +- tests/decode_geotiff_images.rs | 9 +--- tests/decode_images.rs | 57 ++++++++++--------------- tests/encode_images.rs | 53 ++++++++++++----------- tests/encode_images_with_compression.rs | 8 ++-- tests/fuzz_tests.rs | 4 +- 10 files changed, 86 insertions(+), 119 deletions(-) diff --git a/examples/print-exif.rs b/examples/print-exif.rs index 581026a3..621a3c74 100644 --- a/examples/print-exif.rs +++ b/examples/print-exif.rs @@ -1,7 +1,7 @@ extern crate exif; extern crate tiff; -use tiff::{decoder::Decoder, tags::Tag, TiffKindBig, TiffKindStandard}; +use tiff::{tags::Tag, TiffDecoder}; use clap::Parser; use std::fs::File; @@ -19,7 +19,7 @@ fn main() { let args = Cli::parse(); let img_file = File::open(args.path).expect("Cannot find test image!"); - let mut decoder = Decoder::<_, TiffKindStandard>::new(img_file).expect("Cannot create decoder"); + let mut decoder = TiffDecoder::new(img_file).expect("Cannot create decoder"); let exif = decoder.get_exif_data().expect("Unable to read Exif data"); println!("Base: {exif:#?}"); diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index 183e247b..bfe481ec 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -2,7 +2,7 @@ use std::collections::HashSet; use std::convert::TryFrom; use std::io::{self, Cursor, Read, Seek, Write}; -use crate::encoder::{DirectoryEncoder, TiffEncoder}; +use crate::encoder::{DirectoryEncoder, GenericTiffEncoder}; use crate::{ bytecast, ColorType, TiffError, TiffFormatError, TiffKind, TiffResult, TiffUnsupportedError, UsageError, @@ -247,7 +247,7 @@ 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, @@ -425,9 +425,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 { @@ -477,7 +477,7 @@ 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, limits: Default::default(), next_ifd, @@ -506,7 +506,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 } @@ -1149,7 +1149,7 @@ impl Decoder { pub fn read_exif(&mut self) -> TiffResult> { // create tiff encoder for result let mut exifdata = Cursor::new(Vec::new()); - let mut encoder = TiffEncoder::<_, T>::new(Write::by_ref(&mut exifdata))?; + let mut encoder = GenericTiffEncoder::<_, T>::new(Write::by_ref(&mut exifdata))?; // create new IFD let mut ifd0 = encoder.new_directory()?; diff --git a/src/encoder/mod.rs b/src/encoder/mod.rs index 7438ecf3..cad61e39 100644 --- a/src/encoder/mod.rs +++ b/src/encoder/mod.rs @@ -9,11 +9,11 @@ use std::{ }; use crate::{ - decoder::Decoder, + decoder::GenericTiffDecoder, error::TiffResult, ifd::{BufferedEntry, Directory}, tags::{CompressionMethod, ResolutionUnit, Tag, EXIF_TAGS}, - TiffError, TiffFormatError, TiffKind, TiffKindStandard, + TiffError, TiffFormatError, TiffKind, }; pub mod colortype; @@ -41,47 +41,25 @@ pub 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, }; @@ -323,7 +301,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 @@ -539,7 +517,7 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind, D: Compression> /// Write Exif data from TIFF encoded byte block pub fn exif_tags(&mut self, source: Vec) -> TiffResult<()> { - let mut decoder = Decoder::<_, F>::new(Cursor::new(source))?; + let mut decoder = GenericTiffDecoder::<_, F>::new(Cursor::new(source))?; // copy Exif tags to main IFD let exif_tags = EXIF_TAGS; @@ -562,7 +540,7 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind, D: Compression> fn copy_ifd( &mut self, tag: Tag, - decoder: &mut Decoder, + decoder: &mut GenericTiffDecoder, ) -> TiffResult<()> { let exif_ifd_offset = decoder.find_tag(tag)?; if exif_ifd_offset.is_some() { @@ -571,7 +549,7 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind, D: Compression> // create sub-ifd self.encoder.subdirectory_start(); - let (ifd, _trash1) = Decoder::<_, F>::read_ifd(decoder.inner(), offset)?; + let (ifd, _trash1) = GenericTiffDecoder::<_, F>::read_ifd(decoder.inner(), offset)?; // loop through entries ifd.into_iter().for_each(|(tag, value)| { diff --git a/src/lib.rs b/src/lib.rs index e8987302..48c2c08c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,6 +20,12 @@ mod tiff_kind; pub use self::error::{TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, UsageError}; pub use tiff_kind::*; +pub type TiffEncoder = encoder::GenericTiffEncoder; +pub type BigTiffEncoder = encoder::GenericTiffEncoder; + +pub type TiffDecoder = decoder::GenericTiffDecoder; +pub type BigTiffDecoder = decoder::GenericTiffDecoder; + /// An enumeration over supported color types and their bit depths #[derive(Copy, PartialEq, Eq, Debug, Clone, Hash)] pub enum ColorType { diff --git a/tests/decode_bigtiff_images.rs b/tests/decode_bigtiff_images.rs index bd241121..70fe6004 100644 --- a/tests/decode_bigtiff_images.rs +++ b/tests/decode_bigtiff_images.rs @@ -1,6 +1,6 @@ extern crate tiff; -use tiff::{decoder::Decoder, tags::Tag, ColorType, TiffKindBig}; +use tiff::{tags::Tag, BigTiffDecoder, ColorType}; use std::fs::File; use std::path::PathBuf; @@ -13,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::<_, TiffKindBig>::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 c504d836..f74da8c4 100644 --- a/tests/decode_geotiff_images.rs +++ b/tests/decode_geotiff_images.rs @@ -1,10 +1,6 @@ extern crate tiff; -use tiff::{ - decoder::{Decoder, DecodingResult}, - tags::Tag, - ColorType, TiffKindStandard, -}; +use tiff::{decoder::DecodingResult, tags::Tag, ColorType, TiffDecoder}; use std::fs::File; use std::path::PathBuf; @@ -17,8 +13,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::<_, TiffKindStandard>::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 ddfa3dd2..b86b9f75 100644 --- a/tests/decode_images.rs +++ b/tests/decode_images.rs @@ -1,8 +1,8 @@ extern crate tiff; -use tiff::decoder::{Decoder, DecodingResult}; +use tiff::decoder::DecodingResult; use tiff::ifd::Value; -use tiff::{ColorType, TiffKindBig, TiffKindStandard}; +use tiff::{BigTiffDecoder, ColorType, TiffDecoder, TiffKindStandard}; use std::fs::File; use std::io::{Cursor, Write}; @@ -15,8 +15,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::<_, TiffKindStandard>::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(); @@ -46,7 +45,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::<_, TiffKindStandard>::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( @@ -168,8 +167,7 @@ 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::<_, TiffKindStandard>::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 { Value::Ascii(s) => assert_eq!( @@ -193,7 +191,7 @@ fn test_decode_data() { } } let file = File::open("./tests/decodedata-rgb-3c-8b.tiff").unwrap(); - let mut decoder = Decoder::<_, TiffKindStandard>::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() { @@ -214,7 +212,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::<_, TiffKindStandard>::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()); @@ -272,7 +270,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::<_, TiffKindStandard>::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(); @@ -297,7 +295,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::<_, TiffKindStandard>::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(); @@ -343,8 +341,7 @@ fn test_div_zero() { 178, 178, 178, ]; - let err = tiff::decoder::Decoder::<_, TiffKindStandard>::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) => {} @@ -362,8 +359,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::<_, TiffKindBig>::new(std::io::Cursor::new(&image)).unwrap_err(); + let error = BigTiffDecoder::new(std::io::Cursor::new(&image)).unwrap_err(); match error { tiff::TiffError::LimitsExceeded => {} @@ -381,8 +377,7 @@ fn fuzzer_testcase5() { 178, 178, 178, ]; - let _ = tiff::decoder::Decoder::<_, TiffKindStandard>::new(std::io::Cursor::new(&image)) - .unwrap_err(); + let _ = TiffDecoder::new(std::io::Cursor::new(&image)).unwrap_err(); } #[test] @@ -395,8 +390,7 @@ fn fuzzer_testcase1() { 178, ]; - let _ = tiff::decoder::Decoder::<_, TiffKindStandard>::new(std::io::Cursor::new(&image)) - .unwrap_err(); + let _ = TiffDecoder::new(std::io::Cursor::new(&image)).unwrap_err(); } #[test] @@ -409,8 +403,7 @@ fn fuzzer_testcase6() { 178, 178, ]; - let _ = tiff::decoder::Decoder::<_, TiffKindStandard>::new(std::io::Cursor::new(&image)) - .unwrap_err(); + let _ = TiffDecoder::new(std::io::Cursor::new(&image)).unwrap_err(); } #[test] @@ -422,8 +415,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::<_, TiffKindStandard>::new(std::io::Cursor::new(&image)) - .unwrap_err(); + let _ = TiffDecoder::new(std::io::Cursor::new(&image)).unwrap_err(); } #[test] @@ -435,8 +427,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::<_, TiffKindStandard>::new(std::io::Cursor::new(&image)) - .unwrap_err(); + let _ = TiffDecoder::new(std::io::Cursor::new(&image)).unwrap_err(); } #[test] @@ -452,8 +443,7 @@ fn fuzzer_testcase2() { 73, ]; - let _ = tiff::decoder::Decoder::<_, TiffKindStandard>::new(std::io::Cursor::new(&image)) - .unwrap_err(); + let _ = TiffDecoder::new(std::io::Cursor::new(&image)).unwrap_err(); } #[test] @@ -469,8 +459,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::<_, TiffKindStandard>::new(std::io::Cursor::new(&image)) - .unwrap_err(); + let _ = TiffDecoder::new(std::io::Cursor::new(&image)).unwrap_err(); } #[test] @@ -483,8 +472,7 @@ fn fuzzer_testcase3() { 255, 255, ]; - let _ = tiff::decoder::Decoder::<_, TiffKindStandard>::new(std::io::Cursor::new(&image)) - .unwrap_err(); + let _ = TiffDecoder::new(std::io::Cursor::new(&image)).unwrap_err(); } #[test] @@ -502,8 +490,7 @@ fn timeout() { 0, 0, 73, 73, 42, 0, 8, 0, 0, 0, 0, 0, 32, ]; - let error = tiff::decoder::Decoder::<_, TiffKindStandard>::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) => {} @@ -530,7 +517,7 @@ fn test_predictor_3_gray_f32() { 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 = Decoder::<_, TiffKindStandard>::new(img_file).expect("Cannot create decoder"); + let mut decoder = TiffDecoder::new(img_file).expect("Cannot create decoder"); let raw_exif = decoder .read_exif::() .expect("Unable to read Exif data"); @@ -549,7 +536,7 @@ extern crate exif; 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 = Decoder::<_, TiffKindStandard>::new(img_file).expect("Cannot create decoder"); + let mut decoder = TiffDecoder::new(img_file).expect("Cannot create decoder"); let raw_exif = decoder .read_exif::() .expect("Unable to read Exif data"); diff --git a/tests/encode_images.rs b/tests/encode_images.rs index 7df63c4f..17b55b07 100644 --- a/tests/encode_images.rs +++ b/tests/encode_images.rs @@ -1,9 +1,12 @@ extern crate tiff; -use tiff::decoder::{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::{ifd, ColorType, TiffKind, TiffKindBig, TiffKindStandard}; +use tiff::{ + ifd, BigTiffDecoder, BigTiffEncoder, ColorType, TiffDecoder, TiffEncoder, TiffKind, + TiffKindBig, TiffKindStandard, +}; use std::fs::File; use std::io::{Cursor, Seek, SeekFrom, Write}; @@ -22,7 +25,7 @@ fn encode_decode() { } let mut file = Cursor::new(Vec::new()); { - let mut tiff = TiffEncoder::<_, TiffKindStandard>::new(&mut file).unwrap(); + let mut tiff = TiffEncoder::new(&mut file).unwrap(); let mut image = tiff.new_image::(100, 100).unwrap(); image @@ -33,7 +36,7 @@ fn encode_decode() { } { file.seek(SeekFrom::Start(0)).unwrap(); - let mut decoder = Decoder::<_, TiffKindStandard>::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 +64,7 @@ fn encode_decode_big() { } let mut file = Cursor::new(Vec::new()); { - let mut tiff = TiffEncoder::<_, TiffKindBig>::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 +75,7 @@ fn encode_decode_big() { } { file.seek(SeekFrom::Start(0)).unwrap(); - let mut decoder = Decoder::<_, TiffKindBig>::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!( @@ -92,7 +95,7 @@ fn test_encode_ifd() { let mut data = Cursor::new(Vec::new()); { - let mut tiff = TiffEncoder::<_, TiffKindStandard>::new(&mut data).unwrap(); + let mut tiff = TiffEncoder::new(&mut data).unwrap(); let mut image_encoder = tiff.new_image::(1, 1).unwrap(); image_encoder.write_strip(&[1]).unwrap(); let encoder = image_encoder.encoder(); @@ -121,7 +124,7 @@ fn test_encode_ifd() { // Rewind the cursor for reading data.set_position(0); { - let mut decoder = Decoder::<_, TiffKindStandard>::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]); @@ -143,7 +146,7 @@ fn test_encode_undersized_buffer() { let input_data = vec![1, 2, 3]; let output = Vec::new(); let mut output_stream = Cursor::new(output); - if let Ok(mut tiff) = TiffEncoder::<_, TiffKindStandard>::new(&mut output_stream) { + if let Ok(mut tiff) = TiffEncoder::new(&mut output_stream) { let res = tiff.write_image::(50, 50, &input_data); assert!(res.is_err()); } @@ -159,8 +162,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::<_, TiffKindStandard>::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() { @@ -170,14 +172,14 @@ macro_rules! test_roundtrip { let mut file = Cursor::new(Vec::new()); { - let mut tiff = TiffEncoder::<_, TiffKindStandard>::new(&mut file).unwrap(); + let mut tiff = TiffEncoder::new(&mut file).unwrap(); let (width, height) = decoder.dimensions().unwrap(); tiff.write_image::(width, height, &image_data).unwrap(); } file.seek(SeekFrom::Start(0)).unwrap(); { - let mut decoder = Decoder::<_, TiffKindStandard>::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 { @@ -293,7 +295,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() } @@ -337,7 +339,7 @@ fn test_multiple_byte() { let mut data = Cursor::new(Vec::new()); { - let mut tiff = TiffEncoder::<_, TiffKindStandard>::new(&mut data).unwrap(); + let mut tiff = TiffEncoder::new(&mut data).unwrap(); let mut image_encoder = tiff.new_image::(1, 1).unwrap(); image_encoder.write_strip(&[1]).unwrap(); let encoder = image_encoder.encoder(); @@ -359,7 +361,7 @@ fn test_multiple_byte() { data.set_position(0); { - let mut decoder = Decoder::<_, TiffKindStandard>::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]); @@ -378,7 +380,7 @@ fn test_signed() { } { - let mut tiff = TiffEncoder::<_, TiffKindStandard>::new(&mut data).unwrap(); + let mut tiff = TiffEncoder::new(&mut data).unwrap(); let mut image_encoder = tiff.new_image::(1, 1).unwrap(); image_encoder.write_strip(&[1]).unwrap(); let encoder = image_encoder.encoder(); @@ -440,7 +442,7 @@ fn test_signed() { //Rewind the cursor for reading data.set_position(0); { - let mut decoder = Decoder::<_, TiffKindStandard>::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]); @@ -474,7 +476,7 @@ fn test_multipage_image() { { // first create a multipage image with 2 images - let mut img_encoder = TiffEncoder::<_, TiffKindStandard>::new(&mut img_file).unwrap(); + let mut img_encoder = TiffEncoder::new(&mut img_file).unwrap(); // write first grayscale image (2x2 16-bit) let img1: Vec = [1, 2, 3, 4].to_vec(); @@ -492,7 +494,7 @@ fn test_multipage_image() { img_file.seek(SeekFrom::Start(0)).unwrap(); { - let mut img_decoder = Decoder::<_, TiffKindStandard>::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)); @@ -507,7 +509,7 @@ fn test_multipage_image() { fn test_rows_per_strip() { let mut file = Cursor::new(Vec::new()); { - let mut img_encoder = TiffEncoder::<_, TiffKindStandard>::new(&mut file).unwrap(); + let mut img_encoder = TiffEncoder::new(&mut file).unwrap(); let mut image = img_encoder.new_image::(100, 100).unwrap(); assert_eq!(image.next_strip_sample_count(), 100 * 100); @@ -527,7 +529,7 @@ fn test_rows_per_strip() { file.seek(SeekFrom::Start(0)).unwrap(); { - let mut decoder = Decoder::<_, TiffKindStandard>::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); @@ -545,15 +547,14 @@ fn test_rows_per_strip() { 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 = Decoder::<_, TiffKindStandard>::new(img_file).expect("Cannot create decoder"); + 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::<_, TiffKindStandard>::new(&mut output).expect("Unable to create TIFF"); + 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) diff --git a/tests/encode_images_with_compression.rs b/tests/encode_images_with_compression.rs index fcf7e8b8..b1632c45 100644 --- a/tests/encode_images_with_compression.rs +++ b/tests/encode_images_with_compression.rs @@ -2,13 +2,13 @@ extern crate tiff; use std::io::{Cursor, Seek, Write}; use tiff::{ - decoder::{Decoder, DecodingResult}, + decoder::DecodingResult, encoder::{ colortype::{self, ColorType}, compression::*, - TiffEncoder, TiffValue, + TiffValue, }, - TiffKindStandard, + TiffDecoder, TiffEncoder, TiffKindStandard, }; trait TestImage: From::Inner>> { @@ -112,7 +112,7 @@ fn encode_decode_with_compression(compression: C) { // Decode tiff data.set_position(0); { - let mut decoder = Decoder::<_, TiffKindStandard>::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 3a02348e..ced351b5 100644 --- a/tests/fuzz_tests.rs +++ b/tests/fuzz_tests.rs @@ -1,6 +1,6 @@ extern crate tiff; -use tiff::{decoder::Decoder, TiffKindStandard, TiffResult}; +use tiff::{TiffDecoder, TiffResult}; use std::fs::File; @@ -12,7 +12,7 @@ fn test_directory bool>(path: &str, f: F) { } fn decode_tiff(file: File) -> TiffResult<()> { - let mut decoder = Decoder::<_, TiffKindStandard>::new(file)?; + let mut decoder = TiffDecoder::new(file)?; decoder.read_image()?; Ok(()) } From ace1b1884d1f3bec81ee45aa5c20533efe0e131b Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Skutnik Date: Sat, 24 Aug 2024 16:43:39 +0300 Subject: [PATCH 08/20] Avoid moving symbols for no reason --- Cargo.toml | 1 + examples/print-exif.rs | 2 +- src/decoder/mod.rs | 7 +++++-- src/encoder/mod.rs | 5 ++++- src/ifd.rs | 5 +++++ src/lib.rs | 6 ------ tests/decode_bigtiff_images.rs | 2 +- tests/decode_geotiff_images.rs | 6 +++++- tests/decode_images.rs | 5 ++++- tests/encode_images.rs | 5 +++-- tests/encode_images_with_compression.rs | 4 ++-- tests/fuzz_tests.rs | 2 +- 12 files changed, 32 insertions(+), 18 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d12d1914..a8b2e5a9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ 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" [dev-dependencies] clap = { version = "4.0.32", features = ["derive"] } diff --git a/examples/print-exif.rs b/examples/print-exif.rs index 621a3c74..7826a1ef 100644 --- a/examples/print-exif.rs +++ b/examples/print-exif.rs @@ -1,7 +1,7 @@ extern crate exif; extern crate tiff; -use tiff::{tags::Tag, TiffDecoder}; +use tiff::{decoder::TiffDecoder, tags::Tag}; use clap::Parser; use std::fs::File; diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index bfe481ec..65c3a4f1 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -4,8 +4,8 @@ use std::io::{self, Cursor, Read, Seek, Write}; use crate::encoder::{DirectoryEncoder, GenericTiffEncoder}; use crate::{ - bytecast, ColorType, TiffError, TiffFormatError, TiffKind, TiffResult, TiffUnsupportedError, - UsageError, + bytecast, ColorType, TiffError, TiffFormatError, TiffKind, TiffKindBig, TiffKindStandard, + TiffResult, TiffUnsupportedError, UsageError, }; use self::decoded_entry::DecodedEntry; @@ -18,6 +18,9 @@ use crate::tags::{ use self::stream::{ByteOrder, EndianReader, SmartReader}; +pub type TiffDecoder = GenericTiffDecoder; +pub type BigTiffDecoder = GenericTiffDecoder; + mod decoded_entry; mod image; mod stream; diff --git a/src/encoder/mod.rs b/src/encoder/mod.rs index cad61e39..4039c0e4 100644 --- a/src/encoder/mod.rs +++ b/src/encoder/mod.rs @@ -13,7 +13,7 @@ use crate::{ error::TiffResult, ifd::{BufferedEntry, Directory}, tags::{CompressionMethod, ResolutionUnit, Tag, EXIF_TAGS}, - TiffError, TiffFormatError, TiffKind, + TiffError, TiffFormatError, TiffKind, TiffKindBig, TiffKindStandard, }; pub mod colortype; @@ -25,6 +25,9 @@ use self::colortype::*; use self::compression::*; pub use self::writer::*; +pub type TiffEncoder = GenericTiffEncoder; +pub type BigTiffEncoder = GenericTiffEncoder; + /// Encoder for Tiff and BigTiff files. /// /// With this type you can get a `DirectoryEncoder` or a `ImageEncoder` diff --git a/src/ifd.rs b/src/ifd.rs index 514c4ceb..e08cd412 100644 --- a/src/ifd.rs +++ b/src/ifd.rs @@ -43,6 +43,7 @@ impl Value { val => Err(TiffError::FormatError(TiffFormatError::ByteExpected(val))), } } + pub fn into_i8(self) -> TiffResult { match self { SignedByte(val) => Ok(val), @@ -397,6 +398,10 @@ impl Directory { self.0.get(&tag) } + pub fn get_mut(&mut self, tag: &Tag) -> Option<&mut E> { + self.0.get_mut(&tag) + } + pub fn len(&self) -> usize { self.0.len() } diff --git a/src/lib.rs b/src/lib.rs index 48c2c08c..e8987302 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,12 +20,6 @@ mod tiff_kind; pub use self::error::{TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, UsageError}; pub use tiff_kind::*; -pub type TiffEncoder = encoder::GenericTiffEncoder; -pub type BigTiffEncoder = encoder::GenericTiffEncoder; - -pub type TiffDecoder = decoder::GenericTiffDecoder; -pub type BigTiffDecoder = decoder::GenericTiffDecoder; - /// An enumeration over supported color types and their bit depths #[derive(Copy, PartialEq, Eq, Debug, Clone, Hash)] pub enum ColorType { diff --git a/tests/decode_bigtiff_images.rs b/tests/decode_bigtiff_images.rs index 70fe6004..b473053e 100644 --- a/tests/decode_bigtiff_images.rs +++ b/tests/decode_bigtiff_images.rs @@ -1,6 +1,6 @@ extern crate tiff; -use tiff::{tags::Tag, BigTiffDecoder, ColorType}; +use tiff::{decoder::BigTiffDecoder, tags::Tag, ColorType}; use std::fs::File; use std::path::PathBuf; diff --git a/tests/decode_geotiff_images.rs b/tests/decode_geotiff_images.rs index f74da8c4..ca9e6644 100644 --- a/tests/decode_geotiff_images.rs +++ b/tests/decode_geotiff_images.rs @@ -1,6 +1,10 @@ extern crate tiff; -use tiff::{decoder::DecodingResult, tags::Tag, ColorType, TiffDecoder}; +use tiff::{ + decoder::{DecodingResult, TiffDecoder}, + tags::Tag, + ColorType, +}; use std::fs::File; use std::path::PathBuf; diff --git a/tests/decode_images.rs b/tests/decode_images.rs index b86b9f75..8aef6bce 100644 --- a/tests/decode_images.rs +++ b/tests/decode_images.rs @@ -2,7 +2,10 @@ extern crate tiff; use tiff::decoder::DecodingResult; use tiff::ifd::Value; -use tiff::{BigTiffDecoder, ColorType, TiffDecoder, TiffKindStandard}; +use tiff::{ + decoder::{BigTiffDecoder, TiffDecoder}, + ColorType, TiffKindStandard, +}; use std::fs::File; use std::io::{Cursor, Write}; diff --git a/tests/encode_images.rs b/tests/encode_images.rs index 17b55b07..606184bf 100644 --- a/tests/encode_images.rs +++ b/tests/encode_images.rs @@ -4,8 +4,9 @@ use tiff::decoder::DecodingResult; use tiff::encoder::{colortype, Ifd, Ifd8, SRational}; use tiff::tags::Tag; use tiff::{ - ifd, BigTiffDecoder, BigTiffEncoder, ColorType, TiffDecoder, TiffEncoder, TiffKind, - TiffKindBig, TiffKindStandard, + decoder::{BigTiffDecoder, TiffDecoder}, + encoder::{BigTiffEncoder, TiffEncoder}, + ifd, ColorType, TiffKindStandard, }; use std::fs::File; diff --git a/tests/encode_images_with_compression.rs b/tests/encode_images_with_compression.rs index b1632c45..29bf49ed 100644 --- a/tests/encode_images_with_compression.rs +++ b/tests/encode_images_with_compression.rs @@ -2,13 +2,13 @@ extern crate tiff; use std::io::{Cursor, Seek, Write}; use tiff::{ - decoder::DecodingResult, + decoder::{DecodingResult, TiffDecoder}, + encoder::TiffEncoder, encoder::{ colortype::{self, ColorType}, compression::*, TiffValue, }, - TiffDecoder, TiffEncoder, TiffKindStandard, }; trait TestImage: From::Inner>> { diff --git a/tests/fuzz_tests.rs b/tests/fuzz_tests.rs index ced351b5..39756f03 100644 --- a/tests/fuzz_tests.rs +++ b/tests/fuzz_tests.rs @@ -1,6 +1,6 @@ extern crate tiff; -use tiff::{TiffDecoder, TiffResult}; +use tiff::{decoder::TiffDecoder, TiffResult}; use std::fs::File; From 9e3d000511b0f025a4c4c7804fc5c2db924fd3b9 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Skutnik Date: Wed, 28 Aug 2024 21:45:32 +0300 Subject: [PATCH 09/20] Added ProcessedEntry --- Cargo.toml | 1 + examples/print-exif.rs | 56 +++++++++++----- src/decoder/mod.rs | 17 ++++- src/ifd.rs | 143 ++++++++++++++++++++++++++++++++++++++--- src/tags.rs | 56 ++++++++++++++-- 5 files changed, 240 insertions(+), 33 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a8b2e5a9..81838b49 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ 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"] } diff --git a/examples/print-exif.rs b/examples/print-exif.rs index 7826a1ef..22b92451 100644 --- a/examples/print-exif.rs +++ b/examples/print-exif.rs @@ -1,7 +1,11 @@ extern crate exif; extern crate tiff; -use tiff::{decoder::TiffDecoder, tags::Tag}; +use tiff::{ + decoder::TiffDecoder, + ifd::{process, ProcessedEntry}, + tags::{GpsTag, Tag}, +}; use clap::Parser; use std::fs::File; @@ -20,25 +24,47 @@ fn main() { let img_file = File::open(args.path).expect("Cannot find test image!"); let mut decoder = TiffDecoder::new(img_file).expect("Cannot create decoder"); - let exif = decoder.get_exif_data().expect("Unable to read Exif data"); + let mut exif = decoder + .get_exif_data() + .expect("Unable to read Exif data") + .into_iter() + .map(|(id, be)| process(be, false).map(|e| (id, e))) + .collect::, _>>() + .unwrap(); - println!("Base: {exif:#?}"); + exif.sort_by(|lhs, rhs| lhs.0.cmp(&rhs.0)); + exif.into_iter() + .for_each(|(id, entry)| println!("{id:?}:\t{entry}")); - let exif = decoder + decoder .get_exif_ifd(Tag::ExifIfd) - .expect("Unable to read Exif data"); + .expect("Unable to read Exif data") + .into_iter() + .map(|(id, be)| process(be, false).map(|e| (id, e))) + .collect::, _>>() + .unwrap() + .into_iter() + .for_each(|(id, entry)| println!("{id:?}:\t{entry}")); - println!("Extra: {exif:#?}"); + let mut exif = decoder + .get_gps_ifd() + .expect("Unable to read Exif data") + .into_iter() + .map(|(id, be)| process(be, false).map(|e| (id, e))) + .collect::, _>>() + .unwrap(); - let exif = decoder - .get_exif_ifd(Tag::GpsIfd) - .expect("Unable to read Exif data"); + exif.sort_by(|lhs, rhs| lhs.0.cmp(&rhs.0)); + exif.into_iter() + .for_each(|(id, entry)| println!("{id:?}:\t{entry}")); - println!("GPS: {exif:#?}"); - - let exif = decoder + decoder .get_exif_ifd(Tag::InteropIfd) - .expect("Unable to read Exif data"); - - println!("Interop: {exif:#?}"); + .expect("Unable to read Exif data") + .into_iter() + .map(|(id, be)| process(be, false).map(|e| (id, e))) + .collect::, _>>() + .unwrap() + .into_iter() + .for_each(|(id, entry)| println!("{id:?}:\t{entry}")); } diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index 65c3a4f1..fd5abd29 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -10,10 +10,10 @@ use crate::{ use self::decoded_entry::DecodedEntry; use self::image::Image; -use crate::ifd::{BufferedEntry, Directory, Value}; +use crate::ifd::{BufferedEntry, Directory, ImageFileDirectory, Value}; use crate::tags::{ - CompressionMethod, PhotometricInterpretation, PlanarConfiguration, Predictor, SampleFormat, - Tag, Type, EXIF_TAGS, + CompressionMethod, GpsTag, PhotometricInterpretation, PlanarConfiguration, Predictor, + SampleFormat, Tag, Type, EXIF_TAGS, }; use self::stream::{ByteOrder, EndianReader, SmartReader}; @@ -1148,6 +1148,17 @@ impl GenericTiffDecoder { 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 diff --git a/src/ifd.rs b/src/ifd.rs index e08cd412..7685fb41 100644 --- a/src/ifd.rs +++ b/src/ifd.rs @@ -2,6 +2,7 @@ use std::borrow::Cow; use std::collections::BTreeMap; +use std::mem::size_of; use crate::encoder::TiffValue; use crate::tags::{Tag, Type}; @@ -12,6 +13,8 @@ use self::Value::{ Short, Signed, SignedBig, SignedByte, SignedShort, Unsigned, UnsignedBig, }; +use itertools::Itertools; + #[allow(unused_qualifications)] #[derive(Debug, Clone, PartialEq)] #[non_exhaustive] @@ -34,6 +37,46 @@ pub enum Value { 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 { @@ -373,32 +416,112 @@ impl TiffValue for BufferedEntry { } } +macro_rules! step_through { + ($vec:expr, $type:ty, $big_endian:expr) => { + (0..$vec.len()).step_by(size_of::<$type>()).map(|i| { + Ok(if $big_endian { + <$type>::from_be_bytes($vec[i..i + size_of::<$type>()].try_into()?) + } else { + <$type>::from_le_bytes($vec[i..i + size_of::<$type>()].try_into()?) + }) + }) + }; +} + +macro_rules! cast { + ($be:expr, $big_endian:expr, $type:ty, $value:expr) => {{ + assert!($be.data.len() as u64 == size_of::<$type>() as u64 * $be.count); + step_through!($be.data, $type, $big_endian) + .collect::, Box>>()? + .into_iter() + .map($value) + .collect() + }}; + + ($be:expr, $big_endian:expr, $type:ty, $second:ty, $value:expr) => {{ + assert!($be.data.len() as u64 == size_of::<$type>() as u64 * $be.count * 2); + step_through!($be.data, $type, $big_endian) + .collect::, Box>>()? + .into_iter() + .tuples::<($type, $type)>() + .map(|(n, d)| $value(n, d)) + .collect() + }}; +} + +pub fn process( + be: BufferedEntry, + is_big_endian: bool, +) -> Result> { + let contents: Vec = match be.type_ { + Type::BYTE => be.data.into_iter().map(Value::Byte).collect(), + Type::SBYTE => be + .data + .into_iter() + .map(|b| i8::from_be_bytes([b; 1])) + .map(Value::SignedByte) + .collect(), + Type::SHORT => cast!(be, is_big_endian, u16, Value::Short), + Type::LONG => cast!(be, is_big_endian, u32, Value::Unsigned), + Type::SLONG8 => cast!(be, is_big_endian, u64, Value::UnsignedBig), + Type::SSHORT => cast!(be, is_big_endian, i16, Value::SignedShort), + Type::SLONG => cast!(be, is_big_endian, i32, Value::Signed), + Type::LONG8 => cast!(be, is_big_endian, i64, Value::SignedBig), + Type::FLOAT => cast!(be, is_big_endian, f32, Value::Float), + Type::DOUBLE => cast!(be, is_big_endian, f64, Value::Double), + Type::RATIONAL => cast!(be, is_big_endian, u32, u32, Value::Rational), + Type::SRATIONAL => cast!(be, is_big_endian, i32, i32, Value::SRational), + Type::IFD => cast!(be, is_big_endian, u32, Value::Ifd), + Type::IFD8 => cast!(be, is_big_endian, u64, Value::IfdBig), + Type::UNDEFINED => be.data.into_iter().map(Value::Undefined).collect(), + Type::ASCII => { + vec![Value::Ascii(String::from_utf8(be.data)?)] + } + }; + + Ok(ProcessedEntry(contents)) +} + +/// Entry with buffered instead of read data +#[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(", ")) + } +} + /// Type representing an Image File Directory #[derive(Debug)] -pub struct Directory(BTreeMap); +pub struct ImageFileDirectory(BTreeMap); +pub type Directory = ImageFileDirectory; -impl Directory { +impl ImageFileDirectory +where + T: Ord, +{ pub fn new() -> Self { - Directory(BTreeMap::new()) + ImageFileDirectory(BTreeMap::new()) } - pub fn insert(&mut self, tag: Tag, entry: E) -> Option { + 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 { + pub fn into_iter(self) -> std::collections::btree_map::IntoIter { self.0.into_iter() } - pub fn contains_key(&self, tag: &Tag) -> bool { + pub fn contains_key(&self, tag: &T) -> bool { self.0.contains_key(&tag) } - pub fn get(&self, tag: &Tag) -> Option<&E> { + pub fn get(&self, tag: &T) -> Option<&E> { self.0.get(&tag) } - pub fn get_mut(&mut self, tag: &Tag) -> Option<&mut E> { + pub fn get_mut(&mut self, tag: &T) -> Option<&mut E> { self.0.get_mut(&tag) } @@ -406,11 +529,11 @@ impl Directory { self.0.len() } - pub fn iter(&self) -> std::collections::btree_map::Iter { + pub fn iter(&self) -> std::collections::btree_map::Iter { self.0.iter() } - pub fn values_mut(&mut self) -> std::collections::btree_map::ValuesMut { + pub fn values_mut(&mut self) -> std::collections::btree_map::ValuesMut { self.0.values_mut() } } diff --git a/src/tags.rs b/src/tags.rs index 3514f11c..070b8a5f 100644 --- a/src/tags.rs +++ b/src/tags.rs @@ -79,7 +79,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 @@ -128,10 +127,57 @@ pub enum Tag(u16) unknown("A private or extension tag") { GeoKeyDirectoryTag = 34735, // (SPOT) GeoDoubleParamsTag = 34736, // (SPOT) GeoAsciiParamsTag = 34737, // (SPOT) + Copyright = 0x8298, + ExifIfd = 0x8769, + GpsIfd = 0x8825, + ISO = 0x8827, + ExifVersion = 0x9000, + DateTimeOriginal = 0x9003, + CreateDate = 0x9004, + ComponentsConfiguration = 0x9101, + UserComment = 0x9286, GdalNodata = 42113, // Contains areas with missing data - GpsIfd = 34853, - ExifIfd = 34665, - InteropIfd = 40965, + FlaspixVersion = 0xa000, + 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, } } @@ -151,7 +197,7 @@ pub const EXIF_TAGS: [Tag; 15] = [ Tag::DateTime, Tag::Artist, Tag::HostComputer, - Tag::Unknown(33432), + Tag::Copyright, ]; tags! { From c7a8bff58422ea5fe34101292085dac4d06a2d92 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Skutnik Date: Fri, 13 Sep 2024 18:19:10 +0300 Subject: [PATCH 10/20] Avoid panic on missing ifd --- src/decoder/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index fd5abd29..36070525 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -789,7 +789,7 @@ impl GenericTiffDecoder { } pub fn find_tag_entry(&self, tag: Tag) -> Option> { - self.image().ifd.as_ref().unwrap().get(&tag).cloned() + self.image().ifd.as_ref().and_then(|i| i.get(&tag).cloned()) } /// Tries to retrieve a tag. From 4248d9d5e7615f4030e714593697043172aaad98 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Skutnik Date: Fri, 13 Sep 2024 19:10:26 +0300 Subject: [PATCH 11/20] Remove hardcoded exif tag list --- src/decoder/mod.rs | 23 ++++++++++------------- src/encoder/mod.rs | 14 +++++--------- src/ifd.rs | 20 +++++++++++++++++++- src/tags.rs | 19 ------------------- 4 files changed, 34 insertions(+), 42 deletions(-) diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index 36070525..94631383 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -13,7 +13,7 @@ use self::image::Image; use crate::ifd::{BufferedEntry, Directory, ImageFileDirectory, Value}; use crate::tags::{ CompressionMethod, GpsTag, PhotometricInterpretation, PlanarConfiguration, Predictor, - SampleFormat, Tag, Type, EXIF_TAGS, + SampleFormat, Tag, Type, }; use self::stream::{ByteOrder, EndianReader, SmartReader}; @@ -260,7 +260,7 @@ where 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) { @@ -1115,10 +1115,9 @@ impl GenericTiffDecoder { let mut ifd = Directory::new(); // copy Exif tags from main IFD - let exif_tags = EXIF_TAGS; - for tag in exif_tags.into_iter() { - if let Some(entry) = self.find_tag_entry(tag) { - ifd.insert(tag, entry.as_buffered(&mut self.reader)?); + 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)?); } } @@ -1169,14 +1168,12 @@ impl GenericTiffDecoder { let mut ifd0 = encoder.new_directory()?; // copy Exif tags from main IFD - let exif_tags = EXIF_TAGS; - exif_tags.into_iter().for_each(|tag| { - let entry = self.find_tag_entry(tag); - if entry.is_some() { - let b_entry = entry.unwrap().as_buffered(&mut self.reader).unwrap(); - ifd0.write_tag(tag, b_entry).unwrap(); + 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)?; diff --git a/src/encoder/mod.rs b/src/encoder/mod.rs index 4039c0e4..266f5581 100644 --- a/src/encoder/mod.rs +++ b/src/encoder/mod.rs @@ -12,7 +12,7 @@ use crate::{ decoder::GenericTiffDecoder, error::TiffResult, ifd::{BufferedEntry, Directory}, - tags::{CompressionMethod, ResolutionUnit, Tag, EXIF_TAGS}, + tags::{CompressionMethod, ResolutionUnit, Tag}, TiffError, TiffFormatError, TiffKind, TiffKindBig, TiffKindStandard, }; @@ -522,15 +522,11 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind, D: Compression> pub fn exif_tags(&mut self, source: Vec) -> TiffResult<()> { let mut decoder = GenericTiffDecoder::<_, F>::new(Cursor::new(source))?; - // copy Exif tags to main IFD - let exif_tags = EXIF_TAGS; - exif_tags.into_iter().for_each(|tag| { - let entry = decoder.find_tag_entry(tag); - if entry.is_some() && !self.encoder.ifd.contains_key(&tag) { - let b_entry = entry.unwrap().as_buffered(decoder.inner()).unwrap(); - self.encoder.write_tag(tag, b_entry).unwrap(); + for (t, e) in decoder.get_exif_data()?.into_iter() { + if !self.encoder.ifd.contains_key(&t) { + self.encoder.write_tag(t, e).unwrap(); } - }); + } // copy sub-ifds self.copy_ifd(Tag::ExifIfd, &mut decoder)?; diff --git a/src/ifd.rs b/src/ifd.rs index 7685fb41..9fcc544e 100644 --- a/src/ifd.rs +++ b/src/ifd.rs @@ -493,10 +493,19 @@ impl std::fmt::Display for ProcessedEntry { } /// Type representing an Image File Directory -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct ImageFileDirectory(BTreeMap); pub type Directory = ImageFileDirectory; +impl Default for ImageFileDirectory +where + T: Ord, +{ + fn default() -> Self { + ImageFileDirectory(BTreeMap::new()) + } +} + impl ImageFileDirectory where T: Ord, @@ -537,3 +546,12 @@ where self.0.values_mut() } } + +impl FromIterator<(T, E)> for ImageFileDirectory +where + T: Ord, +{ + fn from_iter>(iter: I) -> Self { + ImageFileDirectory(iter.into_iter().collect()) + } +} diff --git a/src/tags.rs b/src/tags.rs index 070b8a5f..226250ca 100644 --- a/src/tags.rs +++ b/src/tags.rs @@ -181,25 +181,6 @@ pub enum GpsTag(u16) unknown("A private or extension tag") { } } -/// List of Tiff tags in the image IFD read as part of the Exif metadata -pub const EXIF_TAGS: [Tag; 15] = [ - Tag::ImageWidth, - Tag::ImageLength, - Tag::PhotometricInterpretation, - Tag::ImageDescription, - Tag::Make, - Tag::Model, - Tag::Orientation, - Tag::XResolution, - Tag::YResolution, - Tag::ResolutionUnit, - Tag::Software, - Tag::DateTime, - Tag::Artist, - Tag::HostComputer, - Tag::Copyright, -]; - tags! { /// The type of an IFD entry (a 2 byte field). pub enum Type(u16) { From b8841113af4f61bb82a5f1e0e33f418f42658789 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Skutnik Date: Sat, 14 Sep 2024 11:55:16 +0300 Subject: [PATCH 12/20] Fix endianness using crate methods --- examples/print-exif.rs | 8 ++--- src/decoder/decoded_entry.rs | 68 ++++++------------------------------ src/decoder/mod.rs | 2 ++ src/encoder/mod.rs | 2 +- src/ifd.rs | 47 +++++++++++-------------- tests/decode_images.rs | 2 +- tests/encode_images.rs | 2 +- 7 files changed, 41 insertions(+), 90 deletions(-) diff --git a/examples/print-exif.rs b/examples/print-exif.rs index 22b92451..97524526 100644 --- a/examples/print-exif.rs +++ b/examples/print-exif.rs @@ -28,7 +28,7 @@ fn main() { .get_exif_data() .expect("Unable to read Exif data") .into_iter() - .map(|(id, be)| process(be, false).map(|e| (id, e))) + .map(|(id, be)| process(be).map(|e| (id, e))) .collect::, _>>() .unwrap(); @@ -40,7 +40,7 @@ fn main() { .get_exif_ifd(Tag::ExifIfd) .expect("Unable to read Exif data") .into_iter() - .map(|(id, be)| process(be, false).map(|e| (id, e))) + .map(|(id, be)| process(be).map(|e| (id, e))) .collect::, _>>() .unwrap() .into_iter() @@ -50,7 +50,7 @@ fn main() { .get_gps_ifd() .expect("Unable to read Exif data") .into_iter() - .map(|(id, be)| process(be, false).map(|e| (id, e))) + .map(|(id, be)| process(be).map(|e| (id, e))) .collect::, _>>() .unwrap(); @@ -62,7 +62,7 @@ fn main() { .get_exif_ifd(Tag::InteropIfd) .expect("Unable to read Exif data") .into_iter() - .map(|(id, be)| process(be, false).map(|e| (id, e))) + .map(|(id, be)| process(be).map(|e| (id, e))) .collect::, _>>() .unwrap() .into_iter() diff --git a/src/decoder/decoded_entry.rs b/src/decoder/decoded_entry.rs index 8cefa93e..1ed64d2a 100644 --- a/src/decoder/decoded_entry.rs +++ b/src/decoder/decoded_entry.rs @@ -4,6 +4,7 @@ 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::{ @@ -386,34 +387,9 @@ impl DecodedEntry { }; let mut buf = vec![0; value_bytes as usize]; - // read values that fit within the IFD entry if value_bytes <= 4 || (K::is_big() && value_bytes <= 8) { + // read values that fit within the IFD entry self.r(bo).read(&mut buf)?; - - match self.type_ { - // for multi-byte values - Type::SHORT - | Type::SSHORT - | Type::LONG - | Type::SLONG - | Type::FLOAT - | Type::IFD - | Type::LONG8 - | Type::SLONG8 - | Type::DOUBLE - | Type::IFD8 => { - if native_bo != bo { - // if byte-order is non-native - // reverse byte order - let mut new_buf = vec![0; value_bytes as usize]; - for i in 0..value_bytes { - new_buf[i as usize] = buf[(value_bytes - 1 - i) as usize]; - } - buf = new_buf; - } - } - _ => {} - } } else { // values that use a pointer // read pointed data @@ -423,38 +399,16 @@ impl DecodedEntry { reader.goto_offset(self.r(bo).read_u32()?.into())?; } reader.read_exact(&mut buf)?; + } - match self.type_ { - // for multi-byte values - Type::LONG8 | Type::SLONG8 | Type::DOUBLE => { - if native_bo != bo { - // if byte-order is non-native - // reverse byte order - let mut new_buf = vec![0; value_bytes as usize]; - for i in 0..value_bytes { - new_buf[i as usize] = buf[(value_bytes - 1 - i) as usize]; - } - buf = new_buf; - } - } - Type::RATIONAL | Type::SRATIONAL => { - if native_bo != bo { - // if byte-order is non-native - // reverse byte order - let mut new_buf = vec![0; 8]; - new_buf[0] = buf[3]; - new_buf[1] = buf[2]; - new_buf[2] = buf[1]; - new_buf[3] = buf[0]; - new_buf[4] = buf[7]; - new_buf[5] = buf[6]; - new_buf[6] = buf[5]; - new_buf[7] = buf[4]; - buf = new_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 { diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index 94631383..e47cfe5f 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -855,6 +855,7 @@ impl GenericTiffDecoder { 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() } @@ -877,6 +878,7 @@ impl GenericTiffDecoder { 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() } diff --git a/src/encoder/mod.rs b/src/encoder/mod.rs index 266f5581..c68ceb81 100644 --- a/src/encoder/mod.rs +++ b/src/encoder/mod.rs @@ -524,7 +524,7 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind, D: Compression> for (t, e) in decoder.get_exif_data()?.into_iter() { if !self.encoder.ifd.contains_key(&t) { - self.encoder.write_tag(t, e).unwrap(); + self.encoder.write_tag(t, e)?; } } diff --git a/src/ifd.rs b/src/ifd.rs index 9fcc544e..ac6b15d2 100644 --- a/src/ifd.rs +++ b/src/ifd.rs @@ -417,30 +417,28 @@ impl TiffValue for BufferedEntry { } macro_rules! step_through { - ($vec:expr, $type:ty, $big_endian:expr) => { + ($vec:expr, $type:ty) => { (0..$vec.len()).step_by(size_of::<$type>()).map(|i| { - Ok(if $big_endian { - <$type>::from_be_bytes($vec[i..i + size_of::<$type>()].try_into()?) - } else { - <$type>::from_le_bytes($vec[i..i + size_of::<$type>()].try_into()?) - }) + Ok(<$type>::from_ne_bytes( + $vec[i..i + size_of::<$type>()].try_into()?, + )) }) }; } macro_rules! cast { - ($be:expr, $big_endian:expr, $type:ty, $value:expr) => {{ + ($be:expr, $type:ty, $value:expr) => {{ assert!($be.data.len() as u64 == size_of::<$type>() as u64 * $be.count); - step_through!($be.data, $type, $big_endian) + step_through!($be.data, $type) .collect::, Box>>()? .into_iter() .map($value) .collect() }}; - ($be:expr, $big_endian:expr, $type:ty, $second:ty, $value:expr) => {{ + ($be:expr, $type:ty, $second:ty, $value:expr) => {{ assert!($be.data.len() as u64 == size_of::<$type>() as u64 * $be.count * 2); - step_through!($be.data, $type, $big_endian) + step_through!($be.data, $type) .collect::, Box>>()? .into_iter() .tuples::<($type, $type)>() @@ -449,10 +447,7 @@ macro_rules! cast { }}; } -pub fn process( - be: BufferedEntry, - is_big_endian: bool, -) -> Result> { +pub fn process(be: BufferedEntry) -> Result> { let contents: Vec = match be.type_ { Type::BYTE => be.data.into_iter().map(Value::Byte).collect(), Type::SBYTE => be @@ -461,18 +456,18 @@ pub fn process( .map(|b| i8::from_be_bytes([b; 1])) .map(Value::SignedByte) .collect(), - Type::SHORT => cast!(be, is_big_endian, u16, Value::Short), - Type::LONG => cast!(be, is_big_endian, u32, Value::Unsigned), - Type::SLONG8 => cast!(be, is_big_endian, u64, Value::UnsignedBig), - Type::SSHORT => cast!(be, is_big_endian, i16, Value::SignedShort), - Type::SLONG => cast!(be, is_big_endian, i32, Value::Signed), - Type::LONG8 => cast!(be, is_big_endian, i64, Value::SignedBig), - Type::FLOAT => cast!(be, is_big_endian, f32, Value::Float), - Type::DOUBLE => cast!(be, is_big_endian, f64, Value::Double), - Type::RATIONAL => cast!(be, is_big_endian, u32, u32, Value::Rational), - Type::SRATIONAL => cast!(be, is_big_endian, i32, i32, Value::SRational), - Type::IFD => cast!(be, is_big_endian, u32, Value::Ifd), - Type::IFD8 => cast!(be, is_big_endian, u64, Value::IfdBig), + 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)?)] diff --git a/tests/decode_images.rs b/tests/decode_images.rs index 8aef6bce..19b898ca 100644 --- a/tests/decode_images.rs +++ b/tests/decode_images.rs @@ -531,7 +531,7 @@ fn test_exif_decoding() { output.set_position(0); let sum: u64 = output.into_inner().into_iter().map(u64::from).sum(); - assert_eq!(sum, 4177); + assert_eq!(sum, 4955); } extern crate exif; diff --git a/tests/encode_images.rs b/tests/encode_images.rs index 606184bf..f788eb0f 100644 --- a/tests/encode_images.rs +++ b/tests/encode_images.rs @@ -570,7 +570,7 @@ fn test_recode_exif_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, 64202); + assert_eq!(sum, 64497); } else { panic!("Wrong data type"); } From 3d95771be6f887eff38fb364faca0449e1d02f6f Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Skutnik Date: Sat, 14 Sep 2024 12:58:51 +0300 Subject: [PATCH 13/20] Impl From for ProcessedEntry --- examples/print-exif.rs | 34 +++++++++--------- src/ifd.rs | 82 ++++++++++++++++++++---------------------- 2 files changed, 55 insertions(+), 61 deletions(-) diff --git a/examples/print-exif.rs b/examples/print-exif.rs index 97524526..6324c4a3 100644 --- a/examples/print-exif.rs +++ b/examples/print-exif.rs @@ -3,7 +3,7 @@ extern crate tiff; use tiff::{ decoder::TiffDecoder, - ifd::{process, ProcessedEntry}, + ifd::ProcessedEntry, tags::{GpsTag, Tag}, }; @@ -28,43 +28,43 @@ fn main() { .get_exif_data() .expect("Unable to read Exif data") .into_iter() - .map(|(id, be)| process(be).map(|e| (id, e))) - .collect::, _>>() - .unwrap(); + .map(|(id, be)| (id, be.into())) + .collect::>(); exif.sort_by(|lhs, rhs| lhs.0.cmp(&rhs.0)); exif.into_iter() .for_each(|(id, entry)| println!("{id:?}:\t{entry}")); - decoder + let mut exif = decoder .get_exif_ifd(Tag::ExifIfd) .expect("Unable to read Exif data") .into_iter() - .map(|(id, be)| process(be).map(|e| (id, e))) - .collect::, _>>() - .unwrap() - .into_iter() + .map(|(id, be)| (id, be.into())) + .collect::>(); + + exif.sort_by(|lhs, rhs| lhs.0.cmp(&rhs.0)); + exif.into_iter() .for_each(|(id, entry)| println!("{id:?}:\t{entry}")); let mut exif = decoder .get_gps_ifd() .expect("Unable to read Exif data") .into_iter() - .map(|(id, be)| process(be).map(|e| (id, e))) - .collect::, _>>() - .unwrap(); + .map(|(id, be)| (id, be.into())) + .collect::>(); exif.sort_by(|lhs, rhs| lhs.0.cmp(&rhs.0)); exif.into_iter() .for_each(|(id, entry)| println!("{id:?}:\t{entry}")); - decoder + let mut exif = decoder .get_exif_ifd(Tag::InteropIfd) .expect("Unable to read Exif data") .into_iter() - .map(|(id, be)| process(be).map(|e| (id, e))) - .collect::, _>>() - .unwrap() - .into_iter() + .map(|(id, be)| (id, be.into())) + .collect::>(); + + exif.sort_by(|lhs, rhs| lhs.0.cmp(&rhs.0)); + exif.into_iter() .for_each(|(id, entry)| println!("{id:?}:\t{entry}")); } diff --git a/src/ifd.rs b/src/ifd.rs index ac6b15d2..74516c24 100644 --- a/src/ifd.rs +++ b/src/ifd.rs @@ -416,67 +416,29 @@ impl TiffValue for BufferedEntry { } } -macro_rules! step_through { - ($vec:expr, $type:ty) => { - (0..$vec.len()).step_by(size_of::<$type>()).map(|i| { - Ok(<$type>::from_ne_bytes( - $vec[i..i + size_of::<$type>()].try_into()?, - )) - }) - }; -} - macro_rules! cast { ($be:expr, $type:ty, $value:expr) => {{ assert!($be.data.len() as u64 == size_of::<$type>() as u64 * $be.count); - step_through!($be.data, $type) - .collect::, Box>>()? + $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); - step_through!($be.data, $type) - .collect::, Box>>()? + $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() }}; } -pub fn process(be: BufferedEntry) -> Result> { - let contents: Vec = match be.type_ { - Type::BYTE => be.data.into_iter().map(Value::Byte).collect(), - Type::SBYTE => be - .data - .into_iter() - .map(|b| i8::from_be_bytes([b; 1])) - .map(Value::SignedByte) - .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)?)] - } - }; - - Ok(ProcessedEntry(contents)) -} - /// Entry with buffered instead of read data #[derive(Clone, Debug)] pub struct ProcessedEntry(Vec); @@ -487,6 +449,38 @@ impl std::fmt::Display for ProcessedEntry { } } +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| i8::from_be_bytes([b; 1])) + .map(Value::SignedByte) + .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) + } +} + /// Type representing an Image File Directory #[derive(Debug, Clone)] pub struct ImageFileDirectory(BTreeMap); From ed14fb0f3b1f1a379d4ea6d5fa2afc5192442cfb Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Skutnik Date: Sat, 14 Sep 2024 15:11:00 +0300 Subject: [PATCH 14/20] Added more errors, automatic formatting of defined tags --- examples/print-exif.rs | 42 ++++++++++-------------------- src/decoder/image.rs | 17 +++++++++++-- src/decoder/mod.rs | 14 +++++++++- src/error.rs | 4 +++ src/ifd.rs | 32 +++++++++++++++++++---- src/tags.rs | 58 +++++++++++++++++++++++++++++++++++++++--- 6 files changed, 126 insertions(+), 41 deletions(-) diff --git a/examples/print-exif.rs b/examples/print-exif.rs index 6324c4a3..1830f74e 100644 --- a/examples/print-exif.rs +++ b/examples/print-exif.rs @@ -3,7 +3,7 @@ extern crate tiff; use tiff::{ decoder::TiffDecoder, - ifd::ProcessedEntry, + ifd::{Directory, ImageFileDirectory, ProcessedEntry}, tags::{GpsTag, Tag}, }; @@ -24,47 +24,31 @@ fn main() { 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 = decoder + let mut exif: Directory = decoder .get_exif_data() .expect("Unable to read Exif data") .into_iter() - .map(|(id, be)| (id, be.into())) - .collect::>(); + .collect(); + print!("{exif}"); - exif.sort_by(|lhs, rhs| lhs.0.cmp(&rhs.0)); - exif.into_iter() - .for_each(|(id, entry)| println!("{id:?}:\t{entry}")); - - let mut exif = decoder + exif = decoder .get_exif_ifd(Tag::ExifIfd) .expect("Unable to read Exif data") .into_iter() - .map(|(id, be)| (id, be.into())) - .collect::>(); - - exif.sort_by(|lhs, rhs| lhs.0.cmp(&rhs.0)); - exif.into_iter() - .for_each(|(id, entry)| println!("{id:?}:\t{entry}")); + .collect(); + print!("{exif}"); - let mut exif = decoder + let gps_exif = decoder .get_gps_ifd() .expect("Unable to read Exif data") .into_iter() - .map(|(id, be)| (id, be.into())) - .collect::>(); + .collect::>(); + print!("{gps_exif}"); - exif.sort_by(|lhs, rhs| lhs.0.cmp(&rhs.0)); - exif.into_iter() - .for_each(|(id, entry)| println!("{id:?}:\t{entry}")); - - let mut exif = decoder + exif = decoder .get_exif_ifd(Tag::InteropIfd) .expect("Unable to read Exif data") .into_iter() - .map(|(id, be)| (id, be.into())) - .collect::>(); - - exif.sort_by(|lhs, rhs| lhs.0.cmp(&rhs.0)); - exif.into_iter() - .for_each(|(id, entry)| println!("{id:?}:\t{entry}")); + .collect(); + print!("{exif}"); } diff --git a/src/decoder/image.rs b/src/decoder/image.rs index 25174c26..077bfedb 100644 --- a/src/decoder/image.rs +++ b/src/decoder/image.rs @@ -203,6 +203,7 @@ impl Image { let planes = match planar_config { PlanarConfiguration::Chunky => 1, PlanarConfiguration::Planar => samples, + PlanarConfiguration::Unknown(_) => unreachable!(), }; let chunk_type; @@ -373,6 +374,11 @@ impl Image { vec![self.bits_per_sample; self.samples as usize], ), )), + PhotometricInterpretation::Unknown(_) => Err(TiffError::UnsupportedError( + TiffUnsupportedError::UnsupportedPhotometricInterpretation( + self.photometric_interpretation, + ), + )), } } @@ -475,6 +481,7 @@ impl Image { match self.planar_config { PlanarConfiguration::Chunky => self.samples.into(), PlanarConfiguration::Planar => 1, + PlanarConfiguration::Unknown(_) => unreachable!(), } } @@ -483,6 +490,7 @@ impl Image { match self.planar_config { PlanarConfiguration::Chunky => 1, PlanarConfiguration::Planar => self.samples.into(), + PlanarConfiguration::Unknown(_) => unreachable!(), } } @@ -599,6 +607,11 @@ impl Image { TiffUnsupportedError::FloatingPointPredictor(color_type), )); } + Predictor::Unknown(code) => { + return Err(TiffError::FormatError(TiffFormatError::UnknownPredictor( + code, + ))); + } }, type_ => { return Err(TiffError::UnsupportedError( @@ -675,7 +688,7 @@ impl Image { samples, byte_order, predictor, - ); + )?; } if photometric_interpretation == PhotometricInterpretation::WhiteIsZero { super::invert_colors(tile, color_type, self.sample_format); @@ -720,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 e47cfe5f..df4fa7e5 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -337,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); @@ -354,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) { @@ -937,6 +944,11 @@ impl GenericTiffDecoder { 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) diff --git a/src/error.rs b/src/error.rs index f11dde80..a3705444 100644 --- a/src/error.rs +++ b/src/error.rs @@ -157,6 +157,7 @@ pub enum TiffUnsupportedError { InterpretationWithBits(PhotometricInterpretation, Vec), UnknownInterpretation, UnknownCompressionMethod, + UnsupportedPhotometricInterpretation(PhotometricInterpretation), UnsupportedCompressionMethod(CompressionMethod), UnsupportedSampleDepth(u8), UnsupportedSampleFormat(Vec), @@ -211,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) } diff --git a/src/ifd.rs b/src/ifd.rs index 74516c24..a9b1be99 100644 --- a/src/ifd.rs +++ b/src/ifd.rs @@ -5,7 +5,7 @@ use std::collections::BTreeMap; use std::mem::size_of; use crate::encoder::TiffValue; -use crate::tags::{Tag, Type}; +use crate::tags::{DispatchFormat, Tag, Type}; use crate::{TiffError, TiffFormatError, TiffResult}; use self::Value::{ @@ -440,12 +440,15 @@ macro_rules! cast { } /// 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(", ")) + write!(f, "{}", self.0.iter().map(|v| format!("{v}")).join(", "),) } } @@ -536,11 +539,30 @@ where } } -impl FromIterator<(T, E)> for ImageFileDirectory +impl FromIterator<(T, K)> for ImageFileDirectory where T: Ord, + K: Into, { - fn from_iter>(iter: I) -> Self { - ImageFileDirectory(iter.into_iter().collect()) + 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, +{ + 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 { + let entry: String = entry.0.iter().map(|v| tag.format(v)).join(", "); + + writeln!(f, "{tag}: {entry}")?; + } + + Ok(()) } } diff --git a/src/tags.rs b/src/tags.rs index 226250ca..79650905 100644 --- a/src/tags.rs +++ b/src/tags.rs @@ -1,3 +1,5 @@ +use crate::ifd::Value; + macro_rules! tags { { // Permit arbitrary meta items, which include documentation. @@ -36,6 +38,15 @@ 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. @@ -59,6 +70,7 @@ macro_rules! tags { Self::__to_inner_type(self) } } + }; // For other tag types, do nothing for now. With concat_idents one could // provide inherent conversion methods for all types. @@ -238,7 +250,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, @@ -251,14 +263,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, @@ -267,7 +279,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, @@ -282,3 +294,41 @@ pub enum SampleFormat(u16) unknown("An unknown extension sample format") { Void = 4, } } + +pub trait DispatchFormat { + fn format(&self, e: &Value) -> String; +} + +impl DispatchFormat for Tag { + fn format(&self, e: &Value) -> String { + match (self, e) { + (Tag::Compression, Value::Short(c)) => { + format!("{}", CompressionMethod::from_u16_exhaustive(*c)) + } + (Tag::PhotometricInterpretation, Value::Short(c)) => { + format!("{}", PhotometricInterpretation::from_u16_exhaustive(*c)) + } + (Tag::PlanarConfiguration, Value::Short(c)) => { + format!("{}", PlanarConfiguration::from_u16_exhaustive(*c)) + } + (Tag::Predictor, Value::Short(c)) => { + format!("{}", Predictor::from_u16_exhaustive(*c)) + } + (Tag::ResolutionUnit, Value::Short(c)) => { + format!("{}", ResolutionUnit::from_u16_exhaustive(*c)) + } + (Tag::SampleFormat, Value::Short(c)) => { + format!("{}", SampleFormat::from_u16_exhaustive(*c)) + } + (_, value) => format!("{value}"), + } + } +} + +impl DispatchFormat for GpsTag { + fn format(&self, e: &Value) -> String { + match (self, e) { + (_, value) => format!("{value}"), + } + } +} From 530fe1f86513f7bf863d42d723f9badb074e8174 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Skutnik Date: Sat, 14 Sep 2024 15:42:46 +0300 Subject: [PATCH 15/20] Add tags and tag formats --- src/tags.rs | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/src/tags.rs b/src/tags.rs index 79650905..57974cec 100644 --- a/src/tags.rs +++ b/src/tags.rs @@ -49,6 +49,7 @@ macro_rules! tags { tags!($name, $ty, $($unknown_doc)*); }; + // For u16 tags, provide direct inherent primitive conversion methods. ($name:tt, u16, $($unknown_doc:literal)*) => { impl $name { @@ -110,7 +111,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, @@ -121,7 +127,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, @@ -132,6 +137,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) @@ -140,16 +146,22 @@ pub enum Tag(u16) unknown("A private or extension tag") { 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, + FocalLength = 0x920a, UserComment = 0x9286, GdalNodata = 42113, // Contains areas with missing data - FlaspixVersion = 0xa000, + FlashpixVersion = 0xa000, + ColorSpace = 0xa001, InteropIfd = 0xa005, } } @@ -295,6 +307,16 @@ pub enum SampleFormat(u16) unknown("An unknown extension sample format") { } } +tags! { +pub enum ColorSpace(u16) unknown("An unknown colorspace") { + sRGB = 1, + AdobeRGB = 2, + WideGamutRGB = 0xfffd, + ICCProfile = 0xfffe, + Uncalibrated = 0xffff, +} +} + pub trait DispatchFormat { fn format(&self, e: &Value) -> String; } @@ -320,6 +342,9 @@ impl DispatchFormat for Tag { (Tag::SampleFormat, Value::Short(c)) => { format!("{}", SampleFormat::from_u16_exhaustive(*c)) } + (Tag::ColorSpace, Value::Short(c)) => { + format!("{}", ColorSpace::from_u16_exhaustive(*c)) + } (_, value) => format!("{value}"), } } From 5bc9394cd97ba888c715698bced6a0068b6772be Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Skutnik Date: Sat, 14 Sep 2024 20:42:55 +0300 Subject: [PATCH 16/20] Refactor formatting to access entire entry --- src/ifd.rs | 45 +++++++++++++++++++++++-- src/tags.rs | 97 +++++++++++++++++++++++++++++++++++++---------------- 2 files changed, 110 insertions(+), 32 deletions(-) diff --git a/src/ifd.rs b/src/ifd.rs index a9b1be99..c167ed8a 100644 --- a/src/ifd.rs +++ b/src/ifd.rs @@ -172,6 +172,8 @@ impl Value { 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), )), @@ -180,6 +182,9 @@ impl Value { 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), @@ -484,6 +489,42 @@ impl From for ProcessedEntry { } } +impl ProcessedEntry { + pub fn iter(&self) -> std::slice::Iter<'_, Value> { + self.0.iter() + } + + pub fn count(&self) -> usize { + self.0.len() + } + + 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, + } + } +} + /// Type representing an Image File Directory #[derive(Debug, Clone)] pub struct ImageFileDirectory(BTreeMap); @@ -558,9 +599,7 @@ where refs.sort_by(|lhs, rhs| lhs.0.cmp(&rhs.0)); for (tag, entry) in refs { - let entry: String = entry.0.iter().map(|v| tag.format(v)).join(", "); - - writeln!(f, "{tag}: {entry}")?; + writeln!(f, "{tag}: {}", tag.format(&entry))?; } Ok(()) diff --git a/src/tags.rs b/src/tags.rs index 57974cec..332f11fc 100644 --- a/src/tags.rs +++ b/src/tags.rs @@ -1,4 +1,5 @@ -use crate::ifd::Value; +use crate::ifd::ProcessedEntry; +use itertools::Itertools; macro_rules! tags { { @@ -157,6 +158,7 @@ pub enum Tag(u16) unknown("A private or extension tag") { CreateDate = 0x9004, ComponentsConfiguration = 0x9101, ExposureCompensation = 0x9204, + MeteringMode = 0x9207, FocalLength = 0x920a, UserComment = 0x9286, GdalNodata = 42113, // Contains areas with missing data @@ -309,7 +311,7 @@ pub enum SampleFormat(u16) unknown("An unknown extension sample format") { tags! { pub enum ColorSpace(u16) unknown("An unknown colorspace") { - sRGB = 1, + SRGB = 1, AdobeRGB = 2, WideGamutRGB = 0xfffd, ICCProfile = 0xfffe, @@ -317,43 +319,80 @@ pub enum ColorSpace(u16) unknown("An unknown colorspace") { } } +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: &Value) -> String; + 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_u16_exhaustive(c).to_string()) + .join(", ") + }; } impl DispatchFormat for Tag { - fn format(&self, e: &Value) -> String { - match (self, e) { - (Tag::Compression, Value::Short(c)) => { - format!("{}", CompressionMethod::from_u16_exhaustive(*c)) - } - (Tag::PhotometricInterpretation, Value::Short(c)) => { - format!("{}", PhotometricInterpretation::from_u16_exhaustive(*c)) - } - (Tag::PlanarConfiguration, Value::Short(c)) => { - format!("{}", PlanarConfiguration::from_u16_exhaustive(*c)) - } - (Tag::Predictor, Value::Short(c)) => { - format!("{}", Predictor::from_u16_exhaustive(*c)) - } - (Tag::ResolutionUnit, Value::Short(c)) => { - format!("{}", ResolutionUnit::from_u16_exhaustive(*c)) + 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::SampleFormat, Value::Short(c)) => { - format!("{}", SampleFormat::from_u16_exhaustive(*c)) - } - (Tag::ColorSpace, Value::Short(c)) => { - format!("{}", ColorSpace::from_u16_exhaustive(*c)) - } - (_, value) => format!("{value}"), + (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: &Value) -> String { - match (self, e) { - (_, value) => format!("{value}"), + 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(", "), } } } From 290ba08aed5fe5585fc94beade4b3e814555c49c Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Skutnik Date: Sat, 14 Sep 2024 22:07:51 +0300 Subject: [PATCH 17/20] Interchangeable Buffered/Processed entries --- src/ifd.rs | 72 ++++++++++++++++++++++++++++++++++++++++++----------- src/tags.rs | 13 ++++++++++ 2 files changed, 70 insertions(+), 15 deletions(-) diff --git a/src/ifd.rs b/src/ifd.rs index c167ed8a..92beaefe 100644 --- a/src/ifd.rs +++ b/src/ifd.rs @@ -398,17 +398,7 @@ impl TiffValue for BufferedEntry { } fn bytes(&self) -> usize { - let tag_size: u32 = 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 tag_size = self.type_.size() as u32; match self.count.checked_mul(tag_size.into()) { Some(n) => n.try_into().unwrap(), @@ -489,15 +479,21 @@ impl From for ProcessedEntry { } } +impl From for BufferedEntry { + fn from(pe: ProcessedEntry) -> Self { + Self { + type_: pe.kind(), + count: pe.count() as u64, + data: pe.data(), + } + } +} + impl ProcessedEntry { pub fn iter(&self) -> std::slice::Iter<'_, Value> { self.0.iter() } - pub fn count(&self) -> usize { - self.0.len() - } - pub fn kind(&self) -> Type { match self.0.first() { Some(v) => match v { @@ -523,6 +519,52 @@ impl ProcessedEntry { 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 diff --git a/src/tags.rs b/src/tags.rs index 332f11fc..dbad73da 100644 --- a/src/tags.rs +++ b/src/tags.rs @@ -245,6 +245,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. From 13a11ee56f144898406ebc4f70eb0e5e7a8d759a Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Skutnik Date: Sat, 14 Sep 2024 22:09:04 +0300 Subject: [PATCH 18/20] Refactor DirectoryEncoder --- src/encoder/directory_encoder.rs | 171 +++++++++++++++++++++++++++ src/encoder/mod.rs | 192 +++---------------------------- 2 files changed, 186 insertions(+), 177 deletions(-) create mode 100644 src/encoder/directory_encoder.rs diff --git a/src/encoder/directory_encoder.rs b/src/encoder/directory_encoder.rs new file mode 100644 index 00000000..be292af6 --- /dev/null +++ b/src/encoder/directory_encoder.rs @@ -0,0 +1,171 @@ +use crate::{ + encoder::{TiffValue, TiffWriter}, + error::TiffResult, + ifd::{BufferedEntry, Directory}, + 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, + pub ifd: Directory, + 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: Directory::new(), + sub_ifd: None, + _phantom: ::std::marker::PhantomData, + }) + } + + /// Start writing to sub-IFD + pub fn subdirectory_start(&mut self) { + self.sub_ifd = Some(Directory::new()); + } + + /// Stop writing to sub-IFD and resume master IFD, returns offset of sub-IFD + pub fn subirectory_close(&mut self) -> TiffResult { + let offset = self.write_directory()?; + K::write_offset(self.writer, 0)?; + self.sub_ifd = None; + 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, + BufferedEntry { + type_: value.is_type(), + count: value.count().try_into()?, + data: bytes, + }, + ); + + Ok(()) + } + + fn write_directory(&mut self) -> TiffResult { + let active_ifd = match &self.sub_ifd { + None => &mut self.ifd, + Some(_v) => self.sub_ifd.as_mut().unwrap(), + }; + + // Start by writing out all values + for &mut BufferedEntry { + data: ref mut bytes, + .. + } in active_ifd.values_mut() + { + let data_bytes = K::OffsetType::BYTE_LEN as usize; + + 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, active_ifd.len())?; + for ( + tag, + BufferedEntry { + type_: field_type, + count, + data: offset, + }, + ) in active_ifd.iter() + { + self.writer.write_u16(tag.to_u16())?; + self.writer.write_u16(field_type.to_u16())?; + K::convert_offset(*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. + pub fn last_written(&self) -> u64 { + self.writer.last_written() + } + + pub fn finish_internal(&mut self) -> TiffResult<()> { + if self.sub_ifd.is_some() { + self.subirectory_close()?; + } + 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(); + } + } +} diff --git a/src/encoder/mod.rs b/src/encoder/mod.rs index c68ceb81..95168e18 100644 --- a/src/encoder/mod.rs +++ b/src/encoder/mod.rs @@ -1,29 +1,26 @@ -pub use tiff_value::*; - -use std::io::{Cursor, Read}; -use std::{ - cmp, - io::{self, Seek, Write}, - marker::PhantomData, - mem, -}; - -use crate::{ - decoder::GenericTiffDecoder, - error::TiffResult, - ifd::{BufferedEntry, Directory}, - tags::{CompressionMethod, ResolutionUnit, Tag}, - TiffError, TiffFormatError, TiffKind, TiffKindBig, TiffKindStandard, -}; - pub mod colortype; pub mod compression; +mod directory_encoder; mod tiff_value; mod writer; use self::colortype::*; use self::compression::*; pub use self::writer::*; +use crate::{ + decoder::GenericTiffDecoder, + error::TiffResult, + tags::{CompressionMethod, ResolutionUnit, Tag}, + TiffError, TiffFormatError, TiffKind, TiffKindBig, TiffKindStandard, +}; +pub use directory_encoder::DirectoryEncoder; +use std::io::{Cursor, Read}; +use std::{ + cmp, + io::{self, Seek, Write}, + marker::PhantomData, +}; +pub use tiff_value::*; pub type TiffEncoder = GenericTiffEncoder; pub type BigTiffEncoder = GenericTiffEncoder; @@ -131,165 +128,6 @@ impl GenericTiffEncoder { } } -/// 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, - ifd_pointer_pos: u64, - ifd: Directory, - sub_ifd: Option>, - _phantom: ::std::marker::PhantomData, -} - -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: Directory::new(), - sub_ifd: None, - _phantom: ::std::marker::PhantomData, - }) - } - - /// Start writing to sub-IFD - pub fn subdirectory_start(&mut self) { - self.sub_ifd = Some(Directory::new()); - } - - /// Stop writing to sub-IFD and resume master IFD, returns offset of sub-IFD - pub fn subirectory_close(&mut self) -> TiffResult { - let offset = self.write_directory()?; - K::write_offset(self.writer, 0)?; - self.sub_ifd = None; - 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, - BufferedEntry { - type_: value.is_type(), - count: value.count().try_into()?, - data: bytes, - }, - ); - - Ok(()) - } - - fn write_directory(&mut self) -> TiffResult { - let active_ifd = match &self.sub_ifd { - None => &mut self.ifd, - Some(_v) => self.sub_ifd.as_mut().unwrap(), - }; - - // Start by writing out all values - for &mut BufferedEntry { - data: ref mut bytes, - .. - } in active_ifd.values_mut() - { - let data_bytes = K::OffsetType::BYTE_LEN as usize; - - 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, active_ifd.len())?; - for ( - tag, - BufferedEntry { - type_: field_type, - count, - data: offset, - }, - ) in active_ifd.iter() - { - self.writer.write_u16(tag.to_u16())?; - self.writer.write_u16(field_type.to_u16())?; - K::convert_offset(*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<()> { - if self.sub_ifd.is_some() { - self.subirectory_close()?; - } - 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. From b3d985388a640f504c70f5fad48491101ad59a51 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Skutnik Date: Sat, 14 Sep 2024 23:37:46 +0300 Subject: [PATCH 19/20] Refactor --- src/decoder/mod.rs | 2 +- src/encoder/directory_encoder.rs | 60 +++++++++++++++++++------------- src/encoder/mod.rs | 18 ++++++++-- src/error.rs | 2 ++ src/ifd.rs | 51 +++++++++++++++------------ src/tags.rs | 16 ++++++++- 6 files changed, 97 insertions(+), 52 deletions(-) diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index df4fa7e5..c08495b3 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -1224,7 +1224,7 @@ impl GenericTiffDecoder { }); // return to ifd0 and write offset - let ifd_offset = new_ifd.subirectory_close()?; + let ifd_offset = new_ifd.subdirectory_close()?; new_ifd.write_tag(tag, ifd_offset as u32)?; } diff --git a/src/encoder/directory_encoder.rs b/src/encoder/directory_encoder.rs index be292af6..9ff2991e 100644 --- a/src/encoder/directory_encoder.rs +++ b/src/encoder/directory_encoder.rs @@ -1,7 +1,7 @@ use crate::{ encoder::{TiffValue, TiffWriter}, - error::TiffResult, - ifd::{BufferedEntry, Directory}, + error::{TiffError, TiffResult, UsageError}, + ifd::{BufferedEntry, Directory, ImageFileDirectory}, tags::Tag, TiffKind, }; @@ -45,10 +45,16 @@ impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> { } /// Stop writing to sub-IFD and resume master IFD, returns offset of sub-IFD - pub fn subirectory_close(&mut self) -> TiffResult { - let offset = self.write_directory()?; - K::write_offset(self.writer, 0)?; + 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) } @@ -77,52 +83,57 @@ impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> { Ok(()) } - fn write_directory(&mut self) -> TiffResult { - let active_ifd = match &self.sub_ifd { - None => &mut self.ifd, - Some(_v) => self.sub_ifd.as_mut().unwrap(), - }; - + fn write_directory>( + &mut self, + mut ifd: ImageFileDirectory, + ) -> TiffResult { // Start by writing out all values for &mut BufferedEntry { data: ref mut bytes, .. - } in active_ifd.values_mut() + } 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)?; - *bytes = vec![0; data_bytes]; - let mut writer = TiffWriter::new(bytes as &mut [u8]); - K::write_offset(&mut writer, offset)?; + // 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); } } } - let offset = self.writer.offset(); + // Record the offset + let ifd_offset = self.writer.offset(); - K::write_entry_count(self.writer, active_ifd.len())?; + // Actually write the ifd + K::write_entry_count(self.writer, ifd.len())?; for ( tag, BufferedEntry { type_: field_type, count, - data: offset, + data: offset, // At this point data is of size K::OffsetType::BYTE_LEN }, - ) in active_ifd.iter() + ) in ifd.into_iter() { - self.writer.write_u16(tag.to_u16())?; + self.writer.write_u16(tag.into())?; self.writer.write_u16(field_type.to_u16())?; - K::convert_offset(*count)?.write(self.writer)?; + K::convert_offset(count)?.write(self.writer)?; self.writer.write_bytes(&offset)?; } - Ok(offset) + Ok(ifd_offset) } /// Write some data to the tiff file, the offset of the data is returned. @@ -141,9 +152,10 @@ impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> { pub fn finish_internal(&mut self) -> TiffResult<()> { if self.sub_ifd.is_some() { - self.subirectory_close()?; + self.subdirectory_close()?; } - let ifd_pointer = self.write_directory()?; + + let ifd_pointer = self.write_directory(self.ifd.to_owned())?; let curr_pos = self.writer.offset(); self.writer.goto_offset(self.ifd_pointer_pos)?; diff --git a/src/encoder/mod.rs b/src/encoder/mod.rs index 95168e18..b128b60b 100644 --- a/src/encoder/mod.rs +++ b/src/encoder/mod.rs @@ -10,14 +10,14 @@ 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::io::{Cursor, Read}; use std::{ cmp, - io::{self, Seek, Write}, + io::{self, Cursor, Read, Seek, Write}, marker::PhantomData, }; pub use tiff_value::*; @@ -356,6 +356,18 @@ 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))?; @@ -395,7 +407,7 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind, D: Compression> }); // return to ifd0 and write offset - let ifd_offset = self.encoder.subirectory_close()?; + let ifd_offset = self.encoder.subdirectory_close()?; self.encoder.write_tag(tag, ifd_offset as u32)?; } diff --git a/src/error.rs b/src/error.rs index a3705444..daafba89 100644 --- a/src/error.rs +++ b/src/error.rs @@ -241,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 index 92beaefe..3f3645c6 100644 --- a/src/ifd.rs +++ b/src/ifd.rs @@ -401,7 +401,7 @@ impl TiffValue for BufferedEntry { let tag_size = self.type_.size() as u32; match self.count.checked_mul(tag_size.into()) { - Some(n) => n.try_into().unwrap(), + Some(n) => n.try_into().unwrap_or_default(), None => 0usize, } } @@ -411,6 +411,29 @@ impl TiffValue for BufferedEntry { } } +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); @@ -434,19 +457,6 @@ macro_rules! cast { }}; } -/// 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(", "),) - } -} - impl From for ProcessedEntry { fn from(be: BufferedEntry) -> Self { let contents: Vec = match be.type_ { @@ -454,8 +464,7 @@ impl From for ProcessedEntry { Type::SBYTE => be .data .into_iter() - .map(|b| i8::from_be_bytes([b; 1])) - .map(Value::SignedByte) + .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), @@ -479,13 +488,9 @@ impl From for ProcessedEntry { } } -impl From for BufferedEntry { - fn from(pe: ProcessedEntry) -> Self { - Self { - type_: pe.kind(), - count: pe.count() as u64, - data: pe.data(), - } +impl From for ProcessedEntry { + fn from(v: Value) -> Self { + ProcessedEntry(vec![v]) } } diff --git a/src/tags.rs b/src/tags.rs index dbad73da..e7d84685 100644 --- a/src/tags.rs +++ b/src/tags.rs @@ -73,6 +73,20 @@ macro_rules! tags { } } + 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. @@ -366,7 +380,7 @@ macro_rules! intercept_u16 { $slice .iter() .filter_map(|v| v.clone().into_u16().ok()) - .map(|c| <$target>::from_u16_exhaustive(c).to_string()) + .map(|c| <$target>::from(c).to_string()) .join(", ") }; } From 9746037b4255a8ef7be34fd689b7b956ec90457c Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Skutnik Date: Sat, 14 Sep 2024 23:50:54 +0300 Subject: [PATCH 20/20] DirectoryEncoder accepts u16 internally --- src/encoder/directory_encoder.rs | 16 ++++++++++------ src/encoder/mod.rs | 2 +- src/ifd.rs | 10 +++++----- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/encoder/directory_encoder.rs b/src/encoder/directory_encoder.rs index 9ff2991e..9d026fd0 100644 --- a/src/encoder/directory_encoder.rs +++ b/src/encoder/directory_encoder.rs @@ -1,7 +1,7 @@ use crate::{ encoder::{TiffValue, TiffWriter}, error::{TiffError, TiffResult, UsageError}, - ifd::{BufferedEntry, Directory, ImageFileDirectory}, + ifd::{BufferedEntry, ImageFileDirectory}, tags::Tag, TiffKind, }; @@ -19,8 +19,8 @@ pub struct DirectoryEncoder<'a, W: 'a + Write + Seek, K: TiffKind> { pub writer: &'a mut TiffWriter, dropped: bool, ifd_pointer_pos: u64, - pub ifd: Directory, - sub_ifd: Option>, + ifd: ImageFileDirectory, + sub_ifd: Option>, _phantom: PhantomData, } @@ -33,15 +33,19 @@ impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> { writer, dropped: false, ifd_pointer_pos, - ifd: Directory::new(), + 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(Directory::new()); + self.sub_ifd = Some(ImageFileDirectory::new()); } /// Stop writing to sub-IFD and resume master IFD, returns offset of sub-IFD @@ -72,7 +76,7 @@ impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> { }; active_ifd.insert( - tag, + tag.into(), BufferedEntry { type_: value.is_type(), count: value.count().try_into()?, diff --git a/src/encoder/mod.rs b/src/encoder/mod.rs index b128b60b..8492b8c6 100644 --- a/src/encoder/mod.rs +++ b/src/encoder/mod.rs @@ -373,7 +373,7 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind, D: Compression> let mut decoder = GenericTiffDecoder::<_, F>::new(Cursor::new(source))?; for (t, e) in decoder.get_exif_data()?.into_iter() { - if !self.encoder.ifd.contains_key(&t) { + if !self.encoder.contains(&t) { self.encoder.write_tag(t, e)?; } } diff --git a/src/ifd.rs b/src/ifd.rs index 3f3645c6..c39b9c01 100644 --- a/src/ifd.rs +++ b/src/ifd.rs @@ -574,12 +574,12 @@ impl ProcessedEntry { /// Type representing an Image File Directory #[derive(Debug, Clone)] -pub struct ImageFileDirectory(BTreeMap); +pub struct ImageFileDirectory, E>(BTreeMap); pub type Directory = ImageFileDirectory; impl Default for ImageFileDirectory where - T: Ord, + T: Ord + Into, { fn default() -> Self { ImageFileDirectory(BTreeMap::new()) @@ -588,7 +588,7 @@ where impl ImageFileDirectory where - T: Ord, + T: Ord + Into, { pub fn new() -> Self { ImageFileDirectory(BTreeMap::new()) @@ -629,7 +629,7 @@ where impl FromIterator<(T, K)> for ImageFileDirectory where - T: Ord, + T: Ord + Into, K: Into, { fn from_iter>(iter: I) -> Self { @@ -639,7 +639,7 @@ where impl std::fmt::Display for ImageFileDirectory where - T: DispatchFormat + Ord + std::fmt::Display, + T: DispatchFormat + Ord + std::fmt::Display + Into, { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let mut refs = self.iter().collect::>();