diff --git a/src/decoder/image.rs b/src/decoder/image.rs index a335610..c525ed2 100644 --- a/src/decoder/image.rs +++ b/src/decoder/image.rs @@ -4,8 +4,8 @@ use super::stream::{ PackBitsReader, }; use super::tag_reader::TagReader; +use super::{fp_predict_f32, fp_predict_f64, DecodingBuffer, Limits}; use super::{stream::SmartReader, ChunkType}; -use super::{DecodingBuffer, Limits}; use crate::tags::{CompressionMethod, PhotometricInterpretation, Predictor, SampleFormat, Tag}; use crate::{ColorType, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError}; use std::convert::{TryFrom, TryInto}; @@ -399,7 +399,20 @@ impl Image { | (ColorType::CMYK(n), _) | (ColorType::Gray(n), _) if usize::from(n) == buffer.byte_len() * 8 => {} - (ColorType::Gray(n), DecodingBuffer::U8(_)) if n <= 8 => {} + (ColorType::Gray(n), DecodingBuffer::U8(_)) if n < 8 => match self.predictor { + Predictor::None => {} + Predictor::Horizontal => { + return Err(TiffError::UnsupportedError( + TiffUnsupportedError::HorizontalPredictor(color_type), + )) + } + Predictor::FloatingPoint => { + return Err(TiffError::UnsupportedError( + TiffUnsupportedError::FloatingPointPredictor(color_type), + )); + } + Predictor::__NonExhaustive => unreachable!(), + }, (type_, _) => { return Err(TiffError::UnsupportedError( TiffUnsupportedError::UnsupportedColorType(type_), @@ -407,6 +420,24 @@ impl Image { } } + // Validate that the predictor is supported for the sample type. + match (self.predictor, &buffer) { + (Predictor::Horizontal, DecodingBuffer::F32(_)) + | (Predictor::Horizontal, DecodingBuffer::F64(_)) => { + return Err(TiffError::UnsupportedError( + TiffUnsupportedError::HorizontalPredictor(color_type), + )); + } + (Predictor::FloatingPoint, DecodingBuffer::F32(_)) + | (Predictor::FloatingPoint, DecodingBuffer::F64(_)) => {} + (Predictor::FloatingPoint, _) => { + return Err(TiffError::UnsupportedError( + TiffUnsupportedError::FloatingPointPredictor(color_type), + )); + } + _ => {} + } + let compressed_bytes = self.chunk_bytes.get(chunk).ok_or(TiffError::FormatError( TiffFormatError::InconsistentSizesEncountered, ))?; @@ -443,10 +474,33 @@ impl Image { let tile = &mut buffer.as_bytes_mut()[..total_samples * byte_len]; reader.read_exact(tile)?; - super::fix_endianness(&mut buffer.subrange(0..total_samples), byte_order); + for row in 0..(chunk_dimensions.1 - padding.1) { + let row_start = row * output_width * samples; + let row_end = (row + 1) * output_width * samples; + let row = buffer.subrange(row_start..row_end); + super::fix_endianness_and_predict(row, samples, byte_order, predictor); + } if photometric_interpretation == PhotometricInterpretation::WhiteIsZero { super::invert_colors(&mut buffer.subrange(0..total_samples), color_type); } + } else if padding.0 > 0 && self.predictor == Predictor::FloatingPoint { + // The floating point predictor shuffles the padding bytes into the encoded output, so + // this case is handled specially when needed. + let mut encoded = vec![0u8; chunk_dimensions.0 * samples * byte_len]; + for row in 0..(chunk_dimensions.1 - padding.1) { + let row_start = row * output_width * samples; + let row_end = row_start + (chunk_dimensions.0 - padding.0) * samples; + + reader.read_exact(&mut encoded)?; + match buffer.subrange(row_start..row_end) { + DecodingBuffer::F32(buf) => fp_predict_f32(&mut encoded, buf, samples), + DecodingBuffer::F64(buf) => fp_predict_f64(&mut encoded, buf, samples), + _ => unreachable!(), + } + if photometric_interpretation == PhotometricInterpretation::WhiteIsZero { + super::invert_colors(&mut buffer.subrange(row_start..row_end), color_type); + } + } } else { for row in 0..(chunk_dimensions.1 - padding.1) { let row_start = row * output_width * samples; @@ -461,29 +515,14 @@ impl Image { io::copy(&mut reader.by_ref().take(len), &mut io::sink())?; } - super::fix_endianness(&mut buffer.subrange(row_start..row_end), byte_order); + let mut row = buffer.subrange(row_start..row_end); + super::fix_endianness_and_predict(row.copy(), samples, byte_order, predictor); if photometric_interpretation == PhotometricInterpretation::WhiteIsZero { - super::invert_colors(&mut buffer.subrange(row_start..row_end), color_type); + super::invert_colors(&mut row, color_type); } } } - match predictor { - Predictor::None => {} - Predictor::Horizontal => { - super::rev_hpredict( - buffer, - ( - u32::try_from(chunk_dimensions.0 - padding.0)?, - u32::try_from(chunk_dimensions.1 - padding.1)?, - ), - output_width, - color_type, - )?; - } - Predictor::__NonExhaustive => unreachable!(), - } - Ok(()) } } diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index ab58464..c4299af 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -359,95 +359,78 @@ impl Wrapping for i64 { } } -fn rev_hpredict_nsamp( - image: &mut [T], - size: (u32, u32), // Size of the block - img_width: usize, // Width of the image (this distinction is needed for tiles) - samples: usize, -) -> TiffResult<()> -where - T: Copy + Wrapping, -{ - let width = usize::try_from(size.0)?; - let height = usize::try_from(size.1)?; - for row in 0..height { - for col in samples..width * samples { - let prev_pixel = image[(row * img_width * samples + col - samples)]; - let pixel = &mut image[(row * img_width * samples + col)]; - *pixel = pixel.wrapping_add(prev_pixel); - } +fn rev_hpredict_nsamp(image: &mut [T], samples: usize) { + for col in samples..image.len() { + image[col] = image[col].wrapping_add(image[col - samples]); } - Ok(()) } -fn rev_hpredict( - image: DecodingBuffer, - size: (u32, u32), - img_width: usize, - color_type: ColorType, -) -> TiffResult<()> { - // TODO: use bits_per_sample.len() after implementing type 3 predictor - let samples = match color_type { - ColorType::Gray(8) | ColorType::Gray(16) | ColorType::Gray(32) | ColorType::Gray(64) => 1, - ColorType::RGB(8) | ColorType::RGB(16) | ColorType::RGB(32) | ColorType::RGB(64) => 3, - ColorType::RGBA(8) - | ColorType::RGBA(16) - | ColorType::RGBA(32) - | ColorType::RGBA(64) - | ColorType::CMYK(8) - | ColorType::CMYK(16) - | ColorType::CMYK(32) - | ColorType::CMYK(64) => 4, - _ => { - return Err(TiffError::UnsupportedError( - TiffUnsupportedError::HorizontalPredictor(color_type), - )) - } - }; +pub fn fp_predict_f32(input: &mut [u8], output: &mut [f32], samples: usize) { + rev_hpredict_nsamp(input, samples); + for i in 0..output.len() { + // TODO: use f32::from_be_bytes() when we can (version 1.40) + output[i] = f32::from_bits(u32::from_be_bytes([ + input[input.len() / 4 * 0 + i], + input[input.len() / 4 * 1 + i], + input[input.len() / 4 * 2 + i], + input[input.len() / 4 * 3 + i], + ])); + } +} + +pub fn fp_predict_f64(input: &mut [u8], output: &mut [f64], samples: usize) { + rev_hpredict_nsamp(input, samples); + for i in 0..output.len() { + // TODO: use f64::from_be_bytes() when we can (version 1.40) + output[i] = f64::from_bits(u64::from_be_bytes([ + input[input.len() / 8 * 0 + i], + input[input.len() / 8 * 1 + i], + input[input.len() / 8 * 2 + i], + input[input.len() / 8 * 3 + i], + input[input.len() / 8 * 4 + i], + input[input.len() / 8 * 5 + i], + input[input.len() / 8 * 6 + i], + input[input.len() / 8 * 7 + i], + ])); + } +} - match image { - DecodingBuffer::U8(buf) => { - rev_hpredict_nsamp(buf, size, img_width, samples)?; - } - DecodingBuffer::U16(buf) => { - rev_hpredict_nsamp(buf, size, img_width, samples)?; - } - DecodingBuffer::U32(buf) => { - rev_hpredict_nsamp(buf, size, img_width, samples)?; - } - DecodingBuffer::U64(buf) => { - rev_hpredict_nsamp(buf, size, img_width, samples)?; - } - DecodingBuffer::F32(_buf) => { - // FIXME: check how this is defined. - // See issue #89. - // rev_hpredict_nsamp(buf, size, img_width,samples)?; - return Err(TiffError::UnsupportedError( - TiffUnsupportedError::HorizontalPredictor(color_type), - )); - } - DecodingBuffer::F64(_buf) => { - //FIXME: check how this is defined. - // See issue #89. - // rev_hpredict_nsamp(buf, size, img_width,samples)?; - return Err(TiffError::UnsupportedError( - TiffUnsupportedError::HorizontalPredictor(color_type), - )); - } - DecodingBuffer::I8(buf) => { - rev_hpredict_nsamp(buf, size, img_width, samples)?; - } - DecodingBuffer::I16(buf) => { - rev_hpredict_nsamp(buf, size, img_width, samples)?; +fn fix_endianness_and_predict( + mut image: DecodingBuffer, + samples: usize, + byte_order: ByteOrder, + predictor: Predictor, +) { + match predictor { + Predictor::None => { + fix_endianness(&mut image, byte_order); } - DecodingBuffer::I32(buf) => { - rev_hpredict_nsamp(buf, size, img_width, samples)?; + Predictor::Horizontal => { + fix_endianness(&mut image, byte_order); + match image { + DecodingBuffer::U8(buf) => rev_hpredict_nsamp(buf, samples), + DecodingBuffer::U16(buf) => rev_hpredict_nsamp(buf, samples), + DecodingBuffer::U32(buf) => rev_hpredict_nsamp(buf, samples), + DecodingBuffer::U64(buf) => rev_hpredict_nsamp(buf, samples), + DecodingBuffer::I8(buf) => rev_hpredict_nsamp(buf, samples), + DecodingBuffer::I16(buf) => rev_hpredict_nsamp(buf, samples), + DecodingBuffer::I32(buf) => rev_hpredict_nsamp(buf, samples), + DecodingBuffer::I64(buf) => rev_hpredict_nsamp(buf, samples), + DecodingBuffer::F32(_) | DecodingBuffer::F64(_) => { + unreachable!("Caller should have validated arguments. Please file a bug.") + } + } } - DecodingBuffer::I64(buf) => { - rev_hpredict_nsamp(buf, size, img_width, samples)?; + Predictor::FloatingPoint => { + let mut buffer_copy = image.as_bytes_mut().to_vec(); + match image { + DecodingBuffer::F32(buf) => fp_predict_f32(&mut buffer_copy, buf, samples), + DecodingBuffer::F64(buf) => fp_predict_f64(&mut buffer_copy, buf, samples), + _ => unreachable!("Caller should have validated arguments. Please file a bug."), + } } + Predictor::__NonExhaustive => unreachable!(), } - Ok(()) } fn invert_colors_unsigned(buffer: &mut [T], max: T) diff --git a/src/error.rs b/src/error.rs index 45ea5b0..0f91ad8 100644 --- a/src/error.rs +++ b/src/error.rs @@ -134,6 +134,7 @@ impl fmt::Display for TiffFormatError { /// stability guarantees. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum TiffUnsupportedError { + FloatingPointPredictor(ColorType), HorizontalPredictor(ColorType), InterpretationWithBits(PhotometricInterpretation, Vec), UnknownInterpretation, @@ -154,6 +155,11 @@ impl fmt::Display for TiffUnsupportedError { fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { use self::TiffUnsupportedError::*; match *self { + FloatingPointPredictor(color_type) => write!( + fmt, + "Floating point predictor for {:?} is unsupported.", + color_type + ), HorizontalPredictor(color_type) => write!( fmt, "Horizontal predictor for {:?} is unsupported.", diff --git a/src/tags.rs b/src/tags.rs index f0a2c07..04cb5ec 100644 --- a/src/tags.rs +++ b/src/tags.rs @@ -214,6 +214,7 @@ tags! { pub enum Predictor(u16) { None = 1, Horizontal = 2, + FloatingPoint = 3, } } diff --git a/tests/decode_images.rs b/tests/decode_images.rs index 5d947dc..df43524 100644 --- a/tests/decode_images.rs +++ b/tests/decode_images.rs @@ -494,3 +494,13 @@ fn timeout() { fn test_no_rows_per_strip() { test_image_sum_u8("no_rows_per_strip.tiff", ColorType::RGB(8), 99448840); } + +#[test] +fn test_predictor_3_rgb_f32() { + test_image_sum_f32("predictor-3-rgb-f32.tif", ColorType::RGB(32), 54004.33); +} + +#[test] +fn test_predictor_3_gray_f32() { + test_image_sum_f32("predictor-3-gray-f32.tif", ColorType::Gray(32), 20008.275); +} diff --git a/tests/images/predictor-3-gray-f32.tif b/tests/images/predictor-3-gray-f32.tif new file mode 100644 index 0000000..9fbda84 Binary files /dev/null and b/tests/images/predictor-3-gray-f32.tif differ diff --git a/tests/images/predictor-3-rgb-f32.tif b/tests/images/predictor-3-rgb-f32.tif new file mode 100644 index 0000000..d338f59 Binary files /dev/null and b/tests/images/predictor-3-rgb-f32.tif differ