diff --git a/Cargo.toml b/Cargo.toml index eb644d5..268e58a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ exclude = ["tests/images/*", "tests/fuzz_images/*"] [dependencies] weezl = "0.1.0" +jpeg = { package = "jpeg-decoder", version = "0.1.17", default-features = false } [dependencies.miniz_oxide] version = "0.4.1" diff --git a/src/decoder/ifd.rs b/src/decoder/ifd.rs index d11319b..ced9a81 100644 --- a/src/decoder/ifd.rs +++ b/src/decoder/ifd.rs @@ -10,13 +10,14 @@ use tags::{Tag, Type}; use {TiffError, TiffFormatError, TiffResult, TiffUnsupportedError}; use self::Value::{ - Ascii, Double, Float, List, Rational, RationalBig, SRational, SRationalBig, Signed, SignedBig, - Unsigned, UnsignedBig, + Ascii, Byte, Double, Float, List, Rational, RationalBig, SRational, SRationalBig, Signed, + SignedBig, Unsigned, UnsignedBig, }; #[allow(unused_qualifications)] #[derive(Debug, Clone, PartialEq)] pub enum Value { + Byte(u8), Signed(i32), SignedBig(i64), Unsigned(u32), @@ -34,6 +35,13 @@ pub enum Value { } impl Value { + pub fn into_u8(self) -> TiffResult { + match self { + Byte(val) => Ok(val), + val => Err(TiffError::FormatError(TiffFormatError::ByteExpected(val))), + } + } + pub fn into_u32(self) -> TiffResult { match self { Unsigned(val) => Ok(val), @@ -114,6 +122,23 @@ impl Value { } } + 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_i32_vec(self) -> TiffResult> { match self { List(vec) => { @@ -426,6 +451,9 @@ impl Entry { Ok(Ascii(string)) } } + (Type::UNDEFINED, n) => self.decode_offset(n, bo, limits, decoder, |decoder| { + Ok(Byte(u8::from(decoder.read_byte()?))) + }), _ => Err(TiffError::UnsupportedError( TiffUnsupportedError::UnsupportedDataType, )), diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index 6d6281f..4cc1bd7 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -9,7 +9,7 @@ use self::ifd::Directory; use tags::{CompressionMethod, PhotometricInterpretation, Predictor, SampleFormat, Tag, Type}; use self::stream::{ - ByteOrder, DeflateReader, EndianReader, LZWReader, PackBitsReader, SmartReader, + ByteOrder, DeflateReader, EndianReader, JpegReader, LZWReader, PackBitsReader, SmartReader, }; pub mod ifd; @@ -165,6 +165,9 @@ pub struct Limits { /// The maximum size of any ifd value in bytes, the default is /// 1MiB. pub ifd_value_size: usize, + /// Maximum size for intermediate buffer which may be used to limit the amount of data read per + /// segment even if the entire image is decoded at once. + pub intermediate_buffer_size: usize, /// The purpose of this is to prevent all the fields of the struct from /// being public, as this would make adding new fields a major version /// bump. @@ -175,6 +178,7 @@ impl Default for Limits { fn default() -> Limits { Limits { decoding_buffer_size: 256 * 1024 * 1024, + intermediate_buffer_size: 128 * 1024 * 1024, ifd_value_size: 1024 * 1024, _non_exhaustive: (), } @@ -638,7 +642,9 @@ impl Decoder { for _ in 0..num_tags { let (tag, entry) = match self.read_entry()? { Some(val) => val, - None => continue, // Unknown data type in tag, skip + None => { + continue; + } // Unknown data type in tag, skip }; dir.insert(tag, entry); } @@ -746,6 +752,11 @@ impl Decoder { self.get_tag(tag)?.into_f64_vec() } + /// Tries to retrieve a tag and convert it to a 8bit vector. + pub fn get_tag_u8_vec(&mut self, tag: Tag) -> TiffResult> { + self.get_tag(tag)?.into_u8_vec() + } + /// Decompresses the strip into the supplied buffer. /// Returns the number of bytes read. fn expand_strip<'a>( @@ -923,6 +934,60 @@ impl Decoder { Ok(()) } + pub fn read_jpeg(&mut self) -> TiffResult { + let offsets = self.get_tag_u32_vec(Tag::StripOffsets)?; + let bytes = self.get_tag_u32_vec(Tag::StripByteCounts)?; + + let jpeg_tables: Option> = match self.find_tag(Tag::JPEGTables) { + Ok(None) => None, + Ok(_) => Some(self.get_tag_u8_vec(Tag::JPEGTables)?), + Err(e) => return Err(e), + }; + + if offsets.len() == 0 { + return Err(TiffError::FormatError(TiffFormatError::RequiredTagEmpty( + Tag::StripOffsets, + ))); + } + if offsets.len() != bytes.len() { + return Err(TiffError::FormatError( + TiffFormatError::InconsistentSizesEncountered, + )); + } + + let mut res_img = Vec::with_capacity(offsets[0] as usize); + + for (idx, offset) in offsets.iter().enumerate() { + if bytes[idx] as usize > self.limits.intermediate_buffer_size { + return Err(TiffError::LimitsExceeded); + } + + self.goto_offset(*offset)?; + let jpeg_reader = JpegReader::new(&mut self.reader, bytes[idx], &jpeg_tables)?; + let mut decoder = jpeg::Decoder::new(jpeg_reader); + + match decoder.decode() { + Ok(mut val) => res_img.append(&mut val), + Err(e) => { + return match e { + jpeg::Error::Io(io_err) => Err(TiffError::IoError(io_err)), + jpeg::Error::Format(fmt_err) => { + Err(TiffError::FormatError(TiffFormatError::Format(fmt_err))) + } + jpeg::Error::Unsupported(_) => Err(TiffError::UnsupportedError( + TiffUnsupportedError::UnknownInterpretation, + )), + jpeg::Error::Internal(_) => Err(TiffError::UnsupportedError( + TiffUnsupportedError::UnknownInterpretation, + )), + } + } + } + } + + Ok(DecodingResult::U8(res_img)) + } + pub fn read_strip_to_buffer(&mut self, mut buffer: DecodingBuffer) -> TiffResult<()> { self.initialize_strip_decoder()?; @@ -1045,6 +1110,10 @@ impl Decoder { /// Decodes the entire image and return it as a Vector pub fn read_image(&mut self) -> TiffResult { + if self.compression_method == CompressionMethod::ModernJPEG { + return self.read_jpeg(); + } + self.initialize_strip_decoder()?; let rows_per_strip = usize::try_from(self.get_tag_u32(Tag::RowsPerStrip).unwrap_or(self.height))?; @@ -1057,7 +1126,6 @@ impl Decoder { for i in 0..usize::try_from(self.strip_count()?)? { self.read_strip_to_buffer(result.as_buffer(samples_per_strip * i))?; } - Ok(result) } } diff --git a/src/decoder/stream.rs b/src/decoder/stream.rs index 7c7affd..327e6de 100644 --- a/src/decoder/stream.rs +++ b/src/decoder/stream.rs @@ -187,6 +187,14 @@ pub trait EndianReader: Read { } } +/// +/// # READERS +/// + +/// +/// ## Deflate Reader +/// + /// Reader that decompresses DEFLATE streams pub struct DeflateReader { buffer: io::Cursor>, @@ -233,6 +241,10 @@ impl EndianReader for DeflateReader { } } +/// +/// ## LZW Reader +/// + /// Reader that decompresses LZW streams pub struct LZWReader { buffer: io::Cursor>, @@ -306,6 +318,79 @@ impl EndianReader for LZWReader { } } +/// +/// ## JPEG Reader (for "new-style" JPEG format (TIFF compression tag 7)) +/// + +pub(crate) struct JpegReader { + buffer: io::Cursor>, + byte_order: ByteOrder, +} +impl JpegReader { + /// Constructs new JpegReader wrapping a SmartReader. + /// Because JPEG compression in TIFF allows to save quantization and/or huffman tables in one + /// central location, the constructor accepts this data as `jpeg_tables` here containing either + /// or both. + /// These `jpeg_tables` are simply prepended to the remaining jpeg image data. + /// Because these `jpeg_tables` start with a `SOI` (HEX: `0xFFD8`) or __start of image__ marker + /// which is also at the beginning of the remaining JPEG image data and would + /// confuse the JPEG renderer, one of these has to be taken off. In this case the first two + /// bytes of the remaining JPEG data is removed because it follows `jpeg_tables`. + /// Similary, `jpeg_tables` ends with a `EOI` (HEX: `0xFFD9`) or __end of image__ marker, + /// this has to be removed as well (last two bytes of `jpeg_tables`). + + pub fn new( + reader: &mut SmartReader, + length: u32, + jpeg_tables: &Option>, + ) -> io::Result + where + R: Read + Seek, + { + let order = reader.byte_order; + + // Read jpeg image data + let mut segment = vec![0; length as usize]; + reader.read_exact(&mut segment[..])?; + + match jpeg_tables { + Some(tables) => { + let mut jpeg_data = tables.clone(); + let truncated_length = jpeg_data.len() - 2; + jpeg_data.truncate(truncated_length); + jpeg_data.extend_from_slice(&mut segment[2..]); + + Ok(JpegReader { + buffer: io::Cursor::new(jpeg_data), + byte_order: order, + }) + } + None => Ok(JpegReader { + buffer: io::Cursor::new(segment), + byte_order: order, + }), + } + } +} + +impl Read for JpegReader { + // #[inline] + fn read(&mut self, buf: &mut [u8]) -> io::Result { + self.buffer.read(buf) + } +} + +impl EndianReader for JpegReader { + #[inline(always)] + fn byte_order(&self) -> ByteOrder { + self.byte_order + } +} + +/// +/// ## PackBits Reader +/// + /// Reader that unpacks Apple's `PackBits` format pub struct PackBitsReader { buffer: io::Cursor>, @@ -378,6 +463,10 @@ impl EndianReader for PackBitsReader { } } +/// +/// ## SmartReader Reader +/// + /// Reader that is aware of the byte order. #[derive(Debug)] pub struct SmartReader diff --git a/src/error.rs b/src/error.rs index 5ace43c..51aef38 100644 --- a/src/error.rs +++ b/src/error.rs @@ -45,9 +45,12 @@ pub enum TiffFormatError { InvalidTagValueType(Tag), RequiredTagNotFound(Tag), UnknownPredictor(u16), + ByteExpected(Value), UnsignedIntegerExpected(Value), SignedIntegerExpected(Value), InflateError(InflateError), + Format(String), + RequiredTagEmpty(Tag), #[doc(hidden)] /// Do not match against this variant. It may get removed. __NonExhaustive, @@ -69,6 +72,7 @@ impl fmt::Display for TiffFormatError { UnknownPredictor(ref predictor) => { write!(fmt, "Unknown predictor “{}” encountered", predictor) } + ByteExpected(ref val) => write!(fmt, "Expected byte, {:?} found.", val), UnsignedIntegerExpected(ref val) => { write!(fmt, "Expected unsigned integer, {:?} found.", val) } @@ -76,6 +80,8 @@ impl fmt::Display for TiffFormatError { write!(fmt, "Expected signed integer, {:?} found.", val) } InflateError(_) => write!(fmt, "Failed to decode inflate data."), + Format(ref val) => write!(fmt, "Invalid format: {:?}.", val), + RequiredTagEmpty(ref val) => write!(fmt, "Required tag {:?} was empty.", val), __NonExhaustive => unreachable!(), } } diff --git a/src/lib.rs b/src/lib.rs index f9346cb..7de0f5e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,6 +6,7 @@ //! # Related Links //! * - The TIFF specification +extern crate jpeg; extern crate miniz_oxide; extern crate weezl; diff --git a/src/tags.rs b/src/tags.rs index d28808a..2741725 100644 --- a/src/tags.rs +++ b/src/tags.rs @@ -111,6 +111,8 @@ pub enum Tag(u16) unknown("A private or extension tag") { Threshholding = 263, // TODO add support XResolution = 282, YResolution = 283, + // JPEG + JPEGTables = 347, // Advanced tags Predictor = 317, TileWidth = 322, @@ -130,6 +132,7 @@ pub enum Type(u16) { LONG = 4, RATIONAL = 5, SBYTE = 6, + UNDEFINED = 7, SSHORT = 8, SLONG = 9, SRATIONAL = 10, @@ -141,6 +144,8 @@ pub enum Type(u16) { } tags! { +/// See [TIFF compression tags](https://www.awaresystems.be/imaging/tiff/tifftags/compression.html) +/// for reference. pub enum CompressionMethod(u16) { None = 1, Huffman = 2, @@ -148,6 +153,8 @@ pub enum CompressionMethod(u16) { Fax4 = 4, LZW = 5, JPEG = 6, + // "Extended JPEG" or "new JPEG" style + ModernJPEG = 7, Deflate = 8, OldDeflate = 0x80B2, PackBits = 0x8005,