diff --git a/src/decoder/image.rs b/src/decoder/image.rs index c5c884f..6d82d7f 100644 --- a/src/decoder/image.rs +++ b/src/decoder/image.rs @@ -1,8 +1,5 @@ use super::ifd::{Directory, Value}; -use super::stream::{ - add_app14segment, ByteOrder, DeflateReader, JpegReader, JpegTagApp14Transform, LZWReader, - PackBitsReader, -}; +use super::stream::{ByteOrder, DeflateReader, JpegReader, LZWReader, PackBitsReader}; use super::tag_reader::TagReader; use super::{fp_predict_f32, fp_predict_f64, DecodingBuffer, Limits}; use super::{stream::SmartReader, ChunkType}; @@ -121,7 +118,7 @@ impl Image { let jpeg_tables = if compression_method == CompressionMethod::ModernJPEG && ifd.contains_key(&Tag::JPEGTables) { - let mut vec = tag_reader + let vec = tag_reader .find_tag(Tag::JPEGTables)? .unwrap() .into_u8_vec()?; @@ -131,11 +128,6 @@ impl Image { )); } - // TODO: Can we avoid this somehow? - if photometric_interpretation == PhotometricInterpretation::RGB { - add_app14segment(&mut vec, JpegTagApp14Transform::App14TransformUnknown) - } - Some(Arc::new(vec)) } else { None @@ -332,6 +324,7 @@ impl Image { fn create_reader<'r, R: 'r + Read>( reader: R, + photometric_interpretation: PhotometricInterpretation, compression_method: CompressionMethod, compressed_length: u64, jpeg_tables: Option>>, @@ -354,14 +347,36 @@ impl Image { let jpeg_reader = JpegReader::new(reader, compressed_length, jpeg_tables)?; let mut decoder = jpeg::Decoder::new(jpeg_reader); - let data = match decoder.decode() { - Ok(data) => data, - Err(e) => { - return Err(TiffError::FormatError(TiffFormatError::Format( - e.to_string(), - ))) + + match photometric_interpretation { + PhotometricInterpretation::RGB => { + decoder.set_color_transform(jpeg::ColorTransform::RGB) } - }; + PhotometricInterpretation::WhiteIsZero => { + decoder.set_color_transform(jpeg::ColorTransform::None) + } + PhotometricInterpretation::BlackIsZero => { + decoder.set_color_transform(jpeg::ColorTransform::None) + } + PhotometricInterpretation::TransparencyMask => { + decoder.set_color_transform(jpeg::ColorTransform::None) + } + PhotometricInterpretation::CMYK => { + decoder.set_color_transform(jpeg::ColorTransform::CMYK) + } + PhotometricInterpretation::YCbCr => { + decoder.set_color_transform(jpeg::ColorTransform::YCbCr) + } + photometric_interpretation => { + return Err(TiffError::UnsupportedError( + TiffUnsupportedError::UnsupportedInterpretation( + photometric_interpretation, + ), + )); + } + } + + let data = decoder.decode()?; Box::new(Cursor::new(data)) } @@ -509,8 +524,13 @@ impl Image { let padding_right = chunk_dims.0 - data_dims.0; let jpeg_tables = self.jpeg_tables.clone(); - let mut reader = - Self::create_reader(reader, compression_method, *compressed_bytes, jpeg_tables)?; + let mut reader = Self::create_reader( + reader, + photometric_interpretation, + compression_method, + *compressed_bytes, + jpeg_tables, + )?; if output_width == data_dims.0 as usize && padding_right == 0 { let total_samples = data_dims.0 as usize * data_dims.1 as usize * samples; diff --git a/src/decoder/stream.rs b/src/decoder/stream.rs index 160ea38..76f3b28 100644 --- a/src/decoder/stream.rs +++ b/src/decoder/stream.rs @@ -250,58 +250,6 @@ impl JpegReader { } } -#[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 { diff --git a/src/error.rs b/src/error.rs index 3a4aeae..a401c6f 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,8 +1,12 @@ use std::error::Error; use std::fmt; +use std::fmt::Display; use std::io; use std::str; use std::string; +use std::sync::Arc; + +use jpeg::UnsupportedFeature; use crate::decoder::{ifd::Value, ChunkType}; use crate::tags::{ @@ -69,6 +73,7 @@ pub enum TiffFormatError { RequiredTagEmpty(Tag), StripTileTagConflict, CycleInOffsets, + JpegDecoder(JpegDecoderError), } impl fmt::Display for TiffFormatError { @@ -119,6 +124,7 @@ impl fmt::Display for TiffFormatError { RequiredTagEmpty(ref val) => write!(fmt, "Required tag {:?} was empty.", val), StripTileTagConflict => write!(fmt, "File should contain either (StripByteCounts and StripOffsets) or (TileByteCounts and TileOffsets), other combination was found."), CycleInOffsets => write!(fmt, "File contained a cycle in the list of IFDs"), + JpegDecoder(ref error) => write!(fmt, "{}", error), } } } @@ -146,6 +152,8 @@ pub enum TiffUnsupportedError { UnsupportedBitsPerChannel(u8), UnsupportedPlanarConfig(Option), UnsupportedDataType, + UnsupportedInterpretation(PhotometricInterpretation), + UnsupportedJpegFeature(UnsupportedFeature), } impl fmt::Display for TiffUnsupportedError { @@ -191,6 +199,16 @@ impl fmt::Display for TiffUnsupportedError { write!(fmt, "Unsupported planar configuration “{:?}”.", config) } UnsupportedDataType => write!(fmt, "Unsupported data type."), + UnsupportedInterpretation(interpretation) => { + write!( + fmt, + "Unsupported photometric interpretation \"{:?}\".", + interpretation + ) + } + UnsupportedJpegFeature(ref unsupported_feature) => { + write!(fmt, "Unsupported JPEG feature {:?}", unsupported_feature) + } } } } @@ -310,5 +328,42 @@ impl From for TiffError { } } +#[derive(Debug, Clone)] +pub struct JpegDecoderError { + inner: Arc, +} + +impl JpegDecoderError { + fn new(error: jpeg::Error) -> Self { + Self { + inner: Arc::new(error), + } + } +} + +impl PartialEq for JpegDecoderError { + fn eq(&self, other: &Self) -> bool { + Arc::ptr_eq(&self.inner, &other.inner) + } +} + +impl Display for JpegDecoderError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.inner.fmt(f) + } +} + +impl From for TiffError { + fn from(error: JpegDecoderError) -> Self { + TiffError::FormatError(TiffFormatError::JpegDecoder(error)) + } +} + +impl From for TiffError { + fn from(error: jpeg::Error) -> Self { + JpegDecoderError::new(error).into() + } +} + /// Result of an image decoding/encoding process pub type TiffResult = Result;