diff --git a/src/decoder/image.rs b/src/decoder/image.rs index a4a9189..a811b52 100644 --- a/src/decoder/image.rs +++ b/src/decoder/image.rs @@ -1,4 +1,5 @@ use super::ifd::{Directory, Value}; +use super::stream::{add_app14segment, JpegTagApp14Transform}; use super::tag_reader::TagReader; use super::Limits; use super::{stream::SmartReader, ChunkType}; @@ -6,6 +7,7 @@ use crate::tags::{CompressionMethod, PhotometricInterpretation, Predictor, Sampl use crate::{TiffError, TiffFormatError, TiffResult, TiffUnsupportedError}; use std::convert::{TryFrom, TryInto}; use std::io::{Read, Seek}; +use std::sync::Arc; #[derive(Debug)] pub(crate) struct StripDecodeState { @@ -87,7 +89,7 @@ pub(crate) struct Image { pub photometric_interpretation: PhotometricInterpretation, pub compression_method: CompressionMethod, pub predictor: Predictor, - pub jpeg_tables: Option>, + pub jpeg_tables: Option>>, pub chunk_type: ChunkType, pub strip_decoder: Option, pub tile_attributes: Option, @@ -130,7 +132,7 @@ impl Image { let jpeg_tables = if compression_method == CompressionMethod::ModernJPEG && ifd.contains_key(&Tag::JPEGTables) { - let vec = tag_reader + let mut vec = tag_reader .find_tag(Tag::JPEGTables)? .unwrap() .into_u8_vec()?; @@ -139,7 +141,13 @@ impl Image { TiffFormatError::InvalidTagValueType(Tag::JPEGTables), )); } - Some(vec) + + // TODO: Can we avoid this somehow? + if photometric_interpretation == PhotometricInterpretation::RGB { + add_app14segment(&mut vec, JpegTagApp14Transform::App14TransformUnknown) + } + + Some(Arc::new(vec)) } else { None }; diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index 5923590..48ee92e 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -1,6 +1,7 @@ use std::collections::{HashMap, HashSet}; use std::convert::TryFrom; -use std::io::{self, Read, Seek}; +use std::io::{self, Cursor, Read, Seek}; +use std::sync::Arc; use std::{cmp, ops::Range}; use crate::{ @@ -1030,9 +1031,12 @@ impl Decoder { // Construct necessary reader to perform decompression. self.goto_offset_u64(offset)?; let byte_order = self.reader.byte_order; - let compression_method = self.image().compression_method; - let reader = Self::create_reader(&mut self.reader, compression_method, length)?; + let compression_method = self.image().compression_method; + let jpeg_tables = self.image().jpeg_tables.clone(); + + let reader = + Self::create_reader(&mut self.reader, compression_method, length, jpeg_tables)?; // Read into output buffer. { @@ -1068,9 +1072,9 @@ impl Decoder { } /// Decompresses the tile into the supplied buffer. - fn expand_tile<'a>( + fn expand_tile( &mut self, - mut buffer: DecodingBuffer<'a>, + mut buffer: DecodingBuffer, offset: u64, compressed_length: u64, tile: usize, @@ -1089,11 +1093,16 @@ impl Decoder { self.goto_offset_u64(offset)?; let line_samples = output_width * self.image().bits_per_sample.len(); - let compression_method = self.image().compression_method; let photometric_interpretation = self.image().photometric_interpretation; + let compression_method = self.image().compression_method; + let jpeg_tables = self.image().jpeg_tables.clone(); - let mut reader = - Self::create_reader(&mut self.reader, compression_method, compressed_length)?; + let mut reader = Self::create_reader( + &mut self.reader, + compression_method, + compressed_length, + jpeg_tables, + )?; for row in 0..(tile_length - padding_down) { let buf = match &mut buffer { @@ -1135,6 +1144,7 @@ impl Decoder { reader: &'r mut SmartReader, compression_method: CompressionMethod, compressed_length: u64, + jpeg_tables: Option>>, ) -> TiffResult> { Ok(match compression_method { CompressionMethod::None => Box::new(reader), @@ -1145,6 +1155,19 @@ impl Decoder { CompressionMethod::Deflate | CompressionMethod::OldDeflate => { Box::new(DeflateReader::new(reader)) } + CompressionMethod::ModernJPEG => { + if jpeg_tables.is_some() && compressed_length < 2 { + return Err(TiffError::FormatError( + TiffFormatError::InvalidTagValueType(Tag::JPEGTables), + )); + } + + let jpeg_reader = JpegReader::new(reader, compressed_length, jpeg_tables)?; + let mut decoder = jpeg::Decoder::new(jpeg_reader); + let data = decoder.decode().unwrap(); + + Box::new(Cursor::new(data)) + } method => { return Err(TiffError::UnsupportedError( TiffUnsupportedError::UnsupportedCompressionMethod(method), @@ -1193,65 +1216,9 @@ impl Decoder { Ok(u32::try_from(self.image().chunk_offsets.len())?) } + #[deprecated = "Use read_image instead"] pub fn read_jpeg(&mut self) -> TiffResult { - self.check_chunk_type(ChunkType::Strip)?; - - if self.image().chunk_offsets.len() == 0 { - return Err(TiffError::FormatError(TiffFormatError::RequiredTagEmpty( - Tag::StripOffsets, - ))); - } - - if self.image().chunk_offsets[0] as usize > self.limits.intermediate_buffer_size - || self - .image() - .chunk_bytes - .iter() - .any(|&x| x as usize > self.limits.intermediate_buffer_size) - { - return Err(TiffError::LimitsExceeded); - } - - let mut res_img = Vec::with_capacity(self.image().chunk_offsets[0] as usize); - - for idx in 0..self.image.chunk_offsets.len() { - let offset = self.image.chunk_offsets[idx]; - let length = self.image.chunk_bytes[idx]; - - self.goto_offset_u64(offset)?; - - if self.image().jpeg_tables.is_some() && length < 2 { - return Err(TiffError::FormatError( - TiffFormatError::InvalidTagValueType(Tag::JPEGTables), - )); - } - // TODO: avoid this clone - let jpeg_tables = self.image().jpeg_tables.clone(); - - let jpeg_reader = - JpegReader::new(&mut self.reader, u32::try_from(length)?, &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)) + self.read_image() } pub fn read_strip_to_buffer(&mut self, mut buffer: DecodingBuffer) -> TiffResult<()> { @@ -1473,6 +1440,7 @@ impl Decoder { .as_ref() .unwrap() .get_offset(tile); + self.read_tile_to_buffer(&mut result.as_buffer(buffer_offset), tile, width)?; } @@ -1505,12 +1473,9 @@ impl Decoder { /// Decodes the entire image and return it as a Vector pub fn read_image(&mut self) -> TiffResult { - let result = match (self.image().chunk_type, self.image().compression_method) { - (_, CompressionMethod::ModernJPEG) => self.read_jpeg()?, - (ChunkType::Strip, _) => self.read_stripped_image()?, - (ChunkType::Tile, _) => self.read_tiled_image()?, - }; - - Ok(result) + match self.image().chunk_type { + ChunkType::Strip => self.read_stripped_image(), + ChunkType::Tile => self.read_tiled_image(), + } } } diff --git a/src/decoder/stream.rs b/src/decoder/stream.rs index d5af64a..5e8ffdd 100644 --- a/src/decoder/stream.rs +++ b/src/decoder/stream.rs @@ -1,7 +1,10 @@ //! All IO functionality needed for TIFF decoding use std::convert::TryFrom; -use std::io::{self, BufRead, BufReader, Read, Seek, Take}; +use std::io::{self, BufRead, BufReader, Read, Seek, SeekFrom, Take}; +use std::sync::Arc; + +use crate::tags::PhotometricInterpretation; /// Byte order of the TIFF file. #[derive(Clone, Copy, Debug)] @@ -187,8 +190,13 @@ impl Read for LZWReader { /// pub(crate) struct JpegReader { + jpeg_tables: Option>>, + buffer: io::Cursor>, + + offset: usize, } + impl JpegReader { /// Constructs new JpegReader wrapping a SmartReader. /// Because JPEG compression in TIFF allows to save quantization and/or huffman tables in one @@ -204,22 +212,23 @@ impl JpegReader { pub fn new( reader: &mut SmartReader, - length: u32, - jpeg_tables: &Option>, + length: u64, + jpeg_tables: Option>>, ) -> io::Result where R: Read + Seek, { // Read jpeg image data let mut segment = vec![0; length as usize]; + reader.read_exact(&mut segment[..])?; match jpeg_tables { - Some(tables) => { + Some(jpeg_tables) => { assert!( - tables.len() >= 2, + jpeg_tables.len() >= 2, "jpeg_tables, if given, must be at least 2 bytes long. Got {:?}", - tables + jpeg_tables ); assert!( @@ -228,26 +237,105 @@ impl JpegReader { length ); - let mut jpeg_data = tables.clone(); - let truncated_length = jpeg_data.len() - 2; - jpeg_data.truncate(truncated_length); - jpeg_data.extend_from_slice(&segment[2..]); + let mut buffer = io::Cursor::new(segment); + // Skip the first two bytes (marker bytes) + buffer.seek(SeekFrom::Start(2))?; Ok(JpegReader { - buffer: io::Cursor::new(jpeg_data), + buffer, + jpeg_tables: Some(jpeg_tables), + offset: 0, }) } None => Ok(JpegReader { buffer: io::Cursor::new(segment), + jpeg_tables: None, + offset: 0, }), } } } +#[repr(u8)] +pub(crate) enum JpegTagApp14Transform { + // App14TransformYCCK = 2, + // App14TransformYCbCr = 1, + App14TransformUnknown = 0, +} + +pub(crate) fn add_app14segment(jpeg_tables: &mut Vec, transform: JpegTagApp14Transform) { + // Add JPEG Tag APP14 Adobe segment to jpeg_tables. + // This segment stores image encoding information for DCT filters. + // When `transform` value is 0 which is defined as Unknown, jpeg-decoder interpret the + // color-space of image as RGB(3 channels) or CMYK(4). + let mut app14_offset = None; + let mut dht_offset = None; + for (offset, window) in jpeg_tables.windows(2).enumerate() { + if window == [0xff, 0xee] { + app14_offset = Some(offset); + break; + } + if window == [0xff, 0xc4] { + dht_offset = Some(offset); + } + } + match (app14_offset, dht_offset) { + (Some(i), _) => { + jpeg_tables[i + 16] = transform as u8; + } + (None, Some(sos_offset)) => { + let app14segment: [u8; 16] = [ + 0xff, + 0xee, + 0x00, + 0x0e, + 0x41, + 0x64, + 0x6f, + 0x62, + 0x65, + 0x00, + 0x64, + 0x00, + 0x00, + 0x00, + 0x00, + transform as u8, + ]; + jpeg_tables.splice(sos_offset..sos_offset, app14segment.iter().copied()); + } + (None, None) => {} + } +} + impl Read for JpegReader { // #[inline] fn read(&mut self, buf: &mut [u8]) -> io::Result { - self.buffer.read(buf) + let mut start = 0; + + if let Some(jpeg_tables) = &self.jpeg_tables { + if jpeg_tables.len() - 2 > self.offset { + // Read (rest of) jpeg_tables to buf (without the last two bytes) + let size_remaining = jpeg_tables.len() - self.offset - 2; + let to_copy = size_remaining.min(buf.len()); + + buf[start..start + to_copy] + .copy_from_slice(&jpeg_tables[self.offset..self.offset + to_copy]); + + self.offset += to_copy; + + if to_copy == buf.len() { + return Ok(to_copy); + } + + start += to_copy; + } + } + + let read = self.buffer.read(&mut buf[start..])?; + self.offset += read; + + Ok(read + start) } } diff --git a/tests/decode_images.rs b/tests/decode_images.rs index ee6ccd1..d2a4419 100644 --- a/tests/decode_images.rs +++ b/tests/decode_images.rs @@ -390,7 +390,9 @@ fn oom() { let err = decoder.read_image().unwrap_err(); match err { - tiff::TiffError::LimitsExceeded => {} + tiff::TiffError::UnsupportedError(tiff::TiffUnsupportedError::InterpretationWithBits( + .., + )) => {} unexpected => panic!("Unexpected error {}", unexpected), } } @@ -452,6 +454,7 @@ fn invalid_jpeg_tag_2() { match err { TiffError::FormatError(TiffFormatError::InvalidTagValueType(tags::Tag::JPEGTables)) => {} + TiffError::LimitsExceeded => {} unexpected => panic!("Unexpected error {}", unexpected), } }