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/mod.rs b/src/decoder/mod.rs index 40dd25c..17420ee 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, LZWReader, PackBitsReader, SmartReader, JpegReader }; pub mod ifd; @@ -710,6 +710,32 @@ impl Decoder { } } + pub fn get_all_tags(&mut self) -> Vec<(Tag, ifd::Value)> { + let mut all_tags = Vec::new(); + + let dir = match &self.ifd { + Some(d) => d, + None => { + println!("No tags found"); + return all_tags; + } + }; + let keys = dir.keys().cloned().collect::>(); + // go through all tags + for key in keys { + let tag_value = match self.get_tag(key) { + Ok(val) => val, + _ => { + println!("Could not unwrap {:?}", key); + continue; + } + }; + all_tags.push((key, tag_value)); + } + + all_tags + } + /// Tries to retrieve a tag and convert it to the desired type. pub fn get_tag_u32(&mut self, tag: Tag) -> TiffResult { self.get_tag(tag)?.into_u32() @@ -928,6 +954,56 @@ impl Decoder { Ok(()) } + pub fn read_jpeg_tiles(&mut self) -> TiffResult { + let tile_offsets = self.get_tag_u32_vec(Tag::TileOffsets)?; + let tile_bytes = self.get_tag_u32_vec(Tag::TileByteCounts)?; + let jpeg_tables: Vec = self.get_tag_u8_vec(Tag::JPEGTables)?; + + let mut res_img = Vec::with_capacity(tile_offsets[0] as usize); + + for (idx, offset) in tile_offsets.iter().enumerate() { + + self.goto_offset(*offset)?; + let jpeg_reader = JpegReader::new(&mut self.reader, tile_bytes[idx], &jpeg_tables)?; + let mut decoder = jpeg::Decoder::new(jpeg_reader); + + match decoder.decode() { + Err(e) => { + panic!("The ERror was: {}", e); + }, + Ok(mut val) => res_img.append(&mut val), + } + } + + Ok(DecodingResult::U8(res_img)) + + } + + pub fn read_jpeg_strips(&mut self) -> TiffResult { + let strip_offsets = self.get_tag_u32_vec(Tag::StripOffsets)?; + let strip_bytes = self.get_tag_u32_vec(Tag::StripByteCounts)?; + let jpeg_tables: Vec = self.get_tag_u8_vec(Tag::JPEGTables)?; + + let mut res_img = Vec::with_capacity(strip_offsets[0] as usize); + + for (idx, offset) in strip_offsets.iter().enumerate() { + + self.goto_offset(*offset)?; + let jpeg_reader = JpegReader::new(&mut self.reader, strip_bytes[idx], &jpeg_tables)?; + let mut decoder = jpeg::Decoder::new(jpeg_reader); + + match decoder.decode() { + Err(e) => { + panic!("Error when reading jpeg tiles: {}", e); + }, + Ok(mut val) => res_img.append(&mut val), + } + } + + Ok(DecodingResult::U8(res_img)) + } + + pub fn read_strip_to_buffer(&mut self, mut buffer: DecodingBuffer) -> TiffResult<()> { self.initialize_strip_decoder()?; @@ -1050,6 +1126,14 @@ 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 { + match self.find_tag(Tag::TileOffsets) { + Ok(None) | Err(_) => return self.read_jpeg_strips(), + Ok(_) => return self.read_jpeg_tiles(), + } + } + self.initialize_strip_decoder()?; let rows_per_strip = usize::try_from(self.get_tag_u32(Tag::RowsPerStrip).unwrap_or(self.height))?; diff --git a/src/decoder/stream.rs b/src/decoder/stream.rs index 7c7affd..56ef877 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,76 @@ impl EndianReader for LZWReader { } } +/// +/// ## JPEG Reader (for "new-style" JPEG format (TIFF compression tag 7)) +/// + +pub struct JpegReader { + buffer: io::Cursor>, + byte_order: ByteOrder, // for jpeg image data always ..., +} +// Only tested with qtables in jpegtables, but it contain both qtables and huffman tables or either +impl JpegReader { + /// Constructs new JpegReader wrapping a SmartReader. + /// Because Jpeg compression in TIFF allows to save quantization and/or huffman tables in one + /// central loacation, the constructor accepts this data as `jpeg_tables` here containing either + /// both. + /// These `jpeg_tables` are simply prepended to the remaining jpeg image data. + /// Because these jpegtables 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 this 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: &Vec + ) -> 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[..])?; + + let mut jpeg_data = jpeg_tables.clone(); + jpeg_data.truncate(jpeg_data.len() - 2); + + jpeg_data.extend_from_slice(&mut segment[2..]); + + Ok( + JpegReader { + buffer: io::Cursor::new(jpeg_data), + 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 +460,10 @@ impl EndianReader for PackBitsReader { } } +/// +/// ## SmartReader Reader +/// + /// Reader that is aware of the byte order. #[derive(Debug)] pub struct SmartReader diff --git a/src/lib.rs b/src/lib.rs index f9346cb..4867d61 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,6 +8,7 @@ extern crate miniz_oxide; extern crate weezl; +extern crate jpeg; mod bytecast; pub mod decoder; diff --git a/src/tags.rs b/src/tags.rs index 1364fa6..a4e03f6 100644 --- a/src/tags.rs +++ b/src/tags.rs @@ -111,6 +111,13 @@ pub enum Tag(u16) unknown("A private or extension tag") { Threshholding = 263, // TODO add support XResolution = 282, YResolution = 283, + // JPEG + TileWidth = 322, + TileLength = 323, + TileOffsets = 324, + TileByteCounts = 325, + SampleFormat = 339, + JPEGTables = 347, // Advanced tags Predictor = 317, TileWidth = 322, @@ -142,6 +149,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, @@ -149,6 +158,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,