From 6dc7a266d30291db1e706c8133357931f9e2a053 Mon Sep 17 00:00:00 2001 From: Jonathan Behrens Date: Mon, 22 Jul 2024 20:13:39 -0700 Subject: [PATCH] Make decoding internally operate on byte buffers (#230) --- src/decoder/image.rs | 144 +++++++++--------- src/decoder/mod.rs | 340 +++++++++++++++++-------------------------- src/error.rs | 2 + src/lib.rs | 14 ++ 4 files changed, 229 insertions(+), 271 deletions(-) diff --git a/src/decoder/image.rs b/src/decoder/image.rs index af206787..97658f45 100644 --- a/src/decoder/image.rs +++ b/src/decoder/image.rs @@ -1,7 +1,7 @@ use super::ifd::{Directory, Value}; use super::stream::{ByteOrder, DeflateReader, LZWReader, PackBitsReader}; use super::tag_reader::TagReader; -use super::{fp_predict_f32, fp_predict_f64, DecodingBuffer, Limits}; +use super::{predict_f32, predict_f64, Limits}; use super::{stream::SmartReader, ChunkType}; use crate::tags::{ CompressionMethod, PhotometricInterpretation, PlanarConfiguration, Predictor, SampleFormat, Tag, @@ -64,7 +64,6 @@ pub(crate) struct Image { pub width: u32, pub height: u32, pub bits_per_sample: u8, - #[allow(unused)] pub samples: u16, pub sample_format: SampleFormat, pub photometric_interpretation: PhotometricInterpretation, @@ -171,8 +170,9 @@ impl Image { )); } - // This library (and libtiff) do not support mixed sample formats. - if bits_per_sample.iter().any(|&b| b != bits_per_sample[0]) { + // This library (and libtiff) do not support mixed sample formats and zero bits per sample + // doesn't make sense. + if bits_per_sample.iter().any(|&b| b != bits_per_sample[0]) || bits_per_sample[0] == 0 { return Err(TiffUnsupportedError::InconsistentBitsPerSample(bits_per_sample).into()); } @@ -546,35 +546,29 @@ impl Image { pub(crate) fn expand_chunk( &self, reader: impl Read, - mut buffer: DecodingBuffer, - output_width: usize, + buf: &mut [u8], + output_row_stride: usize, byte_order: ByteOrder, chunk_index: u32, limits: &Limits, ) -> TiffResult<()> { - // Validate that the provided buffer is of the expected type. + // Validate that the color type is supported. let color_type = self.colortype()?; - match (color_type, &buffer) { - (ColorType::RGB(n), _) - | (ColorType::RGBA(n), _) - | (ColorType::CMYK(n), _) - | (ColorType::YCbCr(n), _) - | (ColorType::Gray(n), _) - | ( - ColorType::Multiband { - bit_depth: n, - num_samples: _, - }, - _, - ) if usize::from(n) == buffer.byte_len() * 8 => {} - ( - ColorType::Gray(n) - | ColorType::Multiband { - bit_depth: n, - num_samples: _, - }, - DecodingBuffer::U8(_), - ) if n < 8 => match self.predictor { + match color_type { + ColorType::RGB(n) + | ColorType::RGBA(n) + | ColorType::CMYK(n) + | ColorType::YCbCr(n) + | ColorType::Gray(n) + | ColorType::Multiband { + bit_depth: n, + num_samples: _, + } if n == 8 || n == 16 || n == 32 || n == 64 => {} + ColorType::Gray(n) + | ColorType::Multiband { + bit_depth: n, + num_samples: _, + } if n < 8 => match self.predictor { Predictor::None => {} Predictor::Horizontal => { return Err(TiffError::UnsupportedError( @@ -587,7 +581,7 @@ impl Image { )); } }, - (type_, _) => { + type_ => { return Err(TiffError::UnsupportedError( TiffUnsupportedError::UnsupportedColorType(type_), )); @@ -595,15 +589,14 @@ impl Image { } // Validate that the predictor is supported for the sample type. - match (self.predictor, &buffer) { - (Predictor::Horizontal, DecodingBuffer::F32(_)) - | (Predictor::Horizontal, DecodingBuffer::F64(_)) => { + match (self.predictor, self.sample_format) { + (Predictor::Horizontal, SampleFormat::Int | SampleFormat::Uint) => {} + (Predictor::Horizontal, _) => { return Err(TiffError::UnsupportedError( TiffUnsupportedError::HorizontalPredictor(color_type), )); } - (Predictor::FloatingPoint, DecodingBuffer::F32(_)) - | (Predictor::FloatingPoint, DecodingBuffer::F64(_)) => {} + (Predictor::FloatingPoint, SampleFormat::IEEEFP) => {} (Predictor::FloatingPoint, _) => { return Err(TiffError::UnsupportedError( TiffUnsupportedError::FloatingPointPredictor(color_type), @@ -622,7 +615,6 @@ impl Image { return Err(TiffError::LimitsExceeded); } - let byte_len = buffer.byte_len(); let compression_method = self.compression_method; let photometric_interpretation = self.photometric_interpretation; let predictor = self.predictor; @@ -631,7 +623,19 @@ impl Image { let chunk_dims = self.chunk_dimensions()?; let data_dims = self.chunk_data_dimensions(chunk_index)?; - let padding_right = chunk_dims.0 - data_dims.0; + let chunk_row_bits = (u64::from(chunk_dims.0) * u64::from(self.bits_per_sample)) + .checked_mul(samples as u64) + .ok_or(TiffError::LimitsExceeded)?; + let chunk_row_bytes: usize = ((chunk_row_bits + 7) / 8).try_into()?; + + let data_row_bits = (u64::from(data_dims.0) * u64::from(self.bits_per_sample)) + .checked_mul(samples as u64) + .ok_or(TiffError::LimitsExceeded)?; + let data_row_bytes: usize = ((data_row_bits + 7) / 8).try_into()?; + + // TODO: Should these return errors instead? + assert!(output_row_stride >= data_row_bytes); + assert!(buf.len() >= output_row_stride * (data_dims.1 as usize - 1) + data_row_bytes); let mut reader = Self::create_reader( reader, @@ -641,57 +645,65 @@ impl Image { self.jpeg_tables.as_deref().map(|a| &**a), )?; - 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; - let tile = &mut buffer.as_bytes_mut()[..total_samples * byte_len]; + if output_row_stride == chunk_row_bytes as usize { + let tile = &mut buf[..chunk_row_bytes * data_dims.1 as usize]; reader.read_exact(tile)?; - for row in 0..data_dims.1 as usize { - 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); + for row in tile.chunks_mut(chunk_row_bytes as usize) { + super::fix_endianness_and_predict( + row, + color_type.bit_depth(), + samples, + byte_order, + predictor, + ); } if photometric_interpretation == PhotometricInterpretation::WhiteIsZero { - super::invert_colors(&mut buffer.subrange(0..total_samples), color_type); + super::invert_colors(tile, color_type, self.sample_format); } - } else if padding_right > 0 && self.predictor == Predictor::FloatingPoint { + } else if chunk_row_bytes > data_row_bytes && 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_dims.0 as usize * samples * byte_len]; - - for row in 0..data_dims.1 as usize { - let row_start = row * output_width * samples; - let row_end = row_start + data_dims.0 as usize * samples; - + let mut encoded = vec![0u8; chunk_row_bytes]; + for row in buf.chunks_mut(output_row_stride).take(data_dims.1 as usize) { 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), + + let row = &mut row[..data_row_bytes]; + match color_type.bit_depth() { + 32 => predict_f32(&mut encoded, row, samples), + 64 => predict_f64(&mut encoded, row, samples), _ => unreachable!(), } if photometric_interpretation == PhotometricInterpretation::WhiteIsZero { - super::invert_colors(&mut buffer.subrange(row_start..row_end), color_type); + super::invert_colors(row, color_type, self.sample_format); } } } else { - for row in 0..data_dims.1 as usize { - let row_start = row * output_width * samples; - let row_end = row_start + data_dims.0 as usize * samples; - - let row = &mut buffer.as_bytes_mut()[(row_start * byte_len)..(row_end * byte_len)]; + for (i, row) in buf + .chunks_mut(output_row_stride) + .take(data_dims.1 as usize) + .enumerate() + { + let row = &mut row[..data_row_bytes]; reader.read_exact(row)?; + println!("chunk={chunk_index}, index={i}"); + // Skip horizontal padding - if padding_right > 0 { - let len = u64::try_from(padding_right as usize * samples * byte_len)?; + if chunk_row_bytes > data_row_bytes { + let len = u64::try_from(chunk_row_bytes - data_row_bytes)?; io::copy(&mut reader.by_ref().take(len), &mut io::sink())?; } - let mut row = buffer.subrange(row_start..row_end); - super::fix_endianness_and_predict(row.copy(), samples, byte_order, predictor); + super::fix_endianness_and_predict( + row, + color_type.bit_depth(), + samples, + byte_order, + predictor, + ); if photometric_interpretation == PhotometricInterpretation::WhiteIsZero { - super::invert_colors(&mut row, color_type); + super::invert_colors(row, color_type, self.sample_format); } } } diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index 574caff7..60fb8424 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -1,6 +1,5 @@ use std::collections::{HashMap, HashSet}; use std::io::{self, Read, Seek}; -use std::ops::Range; use crate::{ bytecast, ColorType, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, UsageError, @@ -167,57 +166,6 @@ pub enum DecodingBuffer<'a> { } impl<'a> DecodingBuffer<'a> { - fn byte_len(&self) -> usize { - match *self { - DecodingBuffer::U8(_) => 1, - DecodingBuffer::U16(_) => 2, - DecodingBuffer::U32(_) => 4, - DecodingBuffer::U64(_) => 8, - DecodingBuffer::F32(_) => 4, - DecodingBuffer::F64(_) => 8, - DecodingBuffer::I8(_) => 1, - DecodingBuffer::I16(_) => 2, - DecodingBuffer::I32(_) => 4, - DecodingBuffer::I64(_) => 8, - } - } - - fn copy<'b>(&'b mut self) -> DecodingBuffer<'b> - where - 'a: 'b, - { - match *self { - DecodingBuffer::U8(ref mut buf) => DecodingBuffer::U8(buf), - DecodingBuffer::U16(ref mut buf) => DecodingBuffer::U16(buf), - DecodingBuffer::U32(ref mut buf) => DecodingBuffer::U32(buf), - DecodingBuffer::U64(ref mut buf) => DecodingBuffer::U64(buf), - DecodingBuffer::F32(ref mut buf) => DecodingBuffer::F32(buf), - DecodingBuffer::F64(ref mut buf) => DecodingBuffer::F64(buf), - DecodingBuffer::I8(ref mut buf) => DecodingBuffer::I8(buf), - DecodingBuffer::I16(ref mut buf) => DecodingBuffer::I16(buf), - DecodingBuffer::I32(ref mut buf) => DecodingBuffer::I32(buf), - DecodingBuffer::I64(ref mut buf) => DecodingBuffer::I64(buf), - } - } - - fn subrange<'b>(&'b mut self, range: Range) -> DecodingBuffer<'b> - where - 'a: 'b, - { - match *self { - DecodingBuffer::U8(ref mut buf) => DecodingBuffer::U8(&mut buf[range]), - DecodingBuffer::U16(ref mut buf) => DecodingBuffer::U16(&mut buf[range]), - DecodingBuffer::U32(ref mut buf) => DecodingBuffer::U32(&mut buf[range]), - DecodingBuffer::U64(ref mut buf) => DecodingBuffer::U64(&mut buf[range]), - DecodingBuffer::F32(ref mut buf) => DecodingBuffer::F32(&mut buf[range]), - DecodingBuffer::F64(ref mut buf) => DecodingBuffer::F64(&mut buf[range]), - DecodingBuffer::I8(ref mut buf) => DecodingBuffer::I8(&mut buf[range]), - DecodingBuffer::I16(ref mut buf) => DecodingBuffer::I16(&mut buf[range]), - DecodingBuffer::I32(ref mut buf) => DecodingBuffer::I32(&mut buf[range]), - DecodingBuffer::I64(ref mut buf) => DecodingBuffer::I64(&mut buf[range]), - } - } - fn as_bytes_mut(&mut self) -> &mut [u8] { match self { DecodingBuffer::U8(ref mut buf) => buf, @@ -307,82 +255,62 @@ where image: Image, } -trait Wrapping { - fn wrapping_add(&self, other: Self) -> Self; -} - -impl Wrapping for u8 { - fn wrapping_add(&self, other: Self) -> Self { - u8::wrapping_add(*self, other) - } -} - -impl Wrapping for u16 { - fn wrapping_add(&self, other: Self) -> Self { - u16::wrapping_add(*self, other) - } -} - -impl Wrapping for u32 { - fn wrapping_add(&self, other: Self) -> Self { - u32::wrapping_add(*self, other) - } -} - -impl Wrapping for u64 { - fn wrapping_add(&self, other: Self) -> Self { - u64::wrapping_add(*self, other) - } -} - -impl Wrapping for i8 { - fn wrapping_add(&self, other: Self) -> Self { - i8::wrapping_add(*self, other) - } -} - -impl Wrapping for i16 { - fn wrapping_add(&self, other: Self) -> Self { - i16::wrapping_add(*self, other) - } -} - -impl Wrapping for i32 { - fn wrapping_add(&self, other: Self) -> Self { - i32::wrapping_add(*self, other) - } -} - -impl Wrapping for i64 { - fn wrapping_add(&self, other: Self) -> Self { - i64::wrapping_add(*self, other) +fn rev_hpredict_nsamp(buf: &mut [u8], bit_depth: u8, samples: usize) { + match bit_depth { + 0..=8 => { + for i in samples..buf.len() { + buf[i] = buf[i].wrapping_add(buf[i - samples]); + } + } + 9..=16 => { + for i in (samples * 2..buf.len()).step_by(2) { + let v = u16::from_ne_bytes(buf[i..][..2].try_into().unwrap()); + let p = u16::from_ne_bytes(buf[i - samples..][..2].try_into().unwrap()); + buf[i..][..2].copy_from_slice(&(v.wrapping_add(p)).to_ne_bytes()); + } + } + 17..=32 => { + for i in (samples * 4..buf.len()).step_by(4) { + let v = u32::from_ne_bytes(buf[i..][..4].try_into().unwrap()); + let p = u32::from_ne_bytes(buf[i - samples..][..4].try_into().unwrap()); + buf[i..][..4].copy_from_slice(&(v.wrapping_add(p)).to_ne_bytes()); + } + } + 33..=64 => { + for i in (samples * 8..buf.len()).step_by(8) { + let v = u64::from_ne_bytes(buf[i..][..8].try_into().unwrap()); + let p = u64::from_ne_bytes(buf[i - samples..][..8].try_into().unwrap()); + buf[i..][..8].copy_from_slice(&(v.wrapping_add(p)).to_ne_bytes()); + } + } + _ => { + unreachable!("Caller should have validated arguments. Please file a bug.") + } } } -fn rev_hpredict_nsamp(image: &mut [T], samples: usize) { - for col in samples..image.len() { - image[col] = image[col].wrapping_add(image[col - samples]); +fn predict_f32(input: &mut [u8], output: &mut [u8], samples: usize) { + for i in samples..input.len() { + input[i] = input[i].wrapping_add(input[i - samples]); } -} -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([ + for (i, chunk) in output.chunks_mut(4).enumerate() { + chunk.copy_from_slice(&u32::to_ne_bytes(u32::from_be_bytes([ input[i], input[input.len() / 4 + 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([ +fn predict_f64(input: &mut [u8], output: &mut [u8], samples: usize) { + for i in samples..input.len() { + input[i] = input[i].wrapping_add(input[i - samples]); + } + + for (i, chunk) in output.chunks_mut(8).enumerate() { + chunk.copy_from_slice(&u64::to_ne_bytes(u64::from_be_bytes([ input[i], input[input.len() / 8 + i], input[input.len() / 8 * 2 + i], @@ -391,122 +319,103 @@ pub fn fp_predict_f64(input: &mut [u8], output: &mut [f64], samples: usize) { input[input.len() / 8 * 5 + i], input[input.len() / 8 * 6 + i], input[input.len() / 8 * 7 + i], - ])); + ]))); } } fn fix_endianness_and_predict( - mut image: DecodingBuffer, + buf: &mut [u8], + bit_depth: u8, samples: usize, byte_order: ByteOrder, predictor: Predictor, ) { match predictor { Predictor::None => { - fix_endianness(&mut image, byte_order); + fix_endianness(buf, byte_order, bit_depth); } 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.") - } - } + fix_endianness(buf, byte_order, bit_depth); + rev_hpredict_nsamp(buf, bit_depth, 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), + let mut buffer_copy = buf.to_vec(); + match bit_depth { + 32 => predict_f32(&mut buffer_copy, buf, samples), + 64 => predict_f64(&mut buffer_copy, buf, samples), _ => unreachable!("Caller should have validated arguments. Please file a bug."), } } } } -fn invert_colors_unsigned(buffer: &mut [T], max: T) -where - T: std::ops::Sub + std::ops::Sub + Copy, -{ - for datum in buffer.iter_mut() { - *datum = max - *datum - } -} - -fn invert_colors_fp(buffer: &mut [T], max: T) -where - T: std::ops::Sub + std::ops::Sub + Copy, -{ - for datum in buffer.iter_mut() { - // FIXME: assumes [0, 1) range for floats - *datum = max - *datum - } -} - -fn invert_colors(buf: &mut DecodingBuffer, color_type: ColorType) { - match (color_type, buf) { - (ColorType::Gray(64), DecodingBuffer::U64(ref mut buffer)) => { - invert_colors_unsigned(buffer, 0xffff_ffff_ffff_ffff); +fn invert_colors(buf: &mut [u8], color_type: ColorType, sample_format: SampleFormat) { + match (color_type, sample_format) { + (ColorType::Gray(8), SampleFormat::Uint) => { + for x in buf { + *x = 0xff - *x; + } } - (ColorType::Gray(32), DecodingBuffer::U32(ref mut buffer)) => { - invert_colors_unsigned(buffer, 0xffff_ffff); + (ColorType::Gray(16), SampleFormat::Uint) => { + for x in buf.chunks_mut(2) { + let v = u16::from_ne_bytes(x.try_into().unwrap()); + x.copy_from_slice(&(0xffff - v).to_ne_bytes()); + } } - (ColorType::Gray(16), DecodingBuffer::U16(ref mut buffer)) => { - invert_colors_unsigned(buffer, 0xffff); + (ColorType::Gray(32), SampleFormat::Uint) => { + for x in buf.chunks_mut(4) { + let v = u32::from_ne_bytes(x.try_into().unwrap()); + x.copy_from_slice(&(0xffff_ffff - v).to_ne_bytes()); + } } - (ColorType::Gray(n), DecodingBuffer::U8(ref mut buffer)) if n <= 8 => { - invert_colors_unsigned(buffer, 0xff); + (ColorType::Gray(64), SampleFormat::Uint) => { + for x in buf.chunks_mut(8) { + let v = u64::from_ne_bytes(x.try_into().unwrap()); + x.copy_from_slice(&(0xffff_ffff_ffff_ffff - v).to_ne_bytes()); + } } - (ColorType::Gray(32), DecodingBuffer::F32(ref mut buffer)) => { - invert_colors_fp(buffer, 1.0); + (ColorType::Gray(32), SampleFormat::IEEEFP) => { + for x in buf.chunks_mut(4) { + let v = f32::from_ne_bytes(x.try_into().unwrap()); + x.copy_from_slice(&(1.0 - v).to_ne_bytes()); + } } - (ColorType::Gray(64), DecodingBuffer::F64(ref mut buffer)) => { - invert_colors_fp(buffer, 1.0); + (ColorType::Gray(64), SampleFormat::IEEEFP) => { + for x in buf.chunks_mut(8) { + let v = f64::from_ne_bytes(x.try_into().unwrap()); + x.copy_from_slice(&(1.0 - v).to_ne_bytes()); + } } _ => {} } } /// Fix endianness. If `byte_order` matches the host, then conversion is a no-op. -fn fix_endianness(buf: &mut DecodingBuffer, byte_order: ByteOrder) { +fn fix_endianness(buf: &mut [u8], byte_order: ByteOrder, bit_depth: u8) { match byte_order { - ByteOrder::LittleEndian => match buf { - DecodingBuffer::U8(_) | DecodingBuffer::I8(_) => {} - DecodingBuffer::U16(b) => b.iter_mut().for_each(|v| *v = u16::from_le(*v)), - DecodingBuffer::I16(b) => b.iter_mut().for_each(|v| *v = i16::from_le(*v)), - DecodingBuffer::U32(b) => b.iter_mut().for_each(|v| *v = u32::from_le(*v)), - DecodingBuffer::I32(b) => b.iter_mut().for_each(|v| *v = i32::from_le(*v)), - DecodingBuffer::U64(b) => b.iter_mut().for_each(|v| *v = u64::from_le(*v)), - DecodingBuffer::I64(b) => b.iter_mut().for_each(|v| *v = i64::from_le(*v)), - DecodingBuffer::F32(b) => b - .iter_mut() - .for_each(|v| *v = f32::from_bits(u32::from_le(v.to_bits()))), - DecodingBuffer::F64(b) => b - .iter_mut() - .for_each(|v| *v = f64::from_bits(u64::from_le(v.to_bits()))), + ByteOrder::LittleEndian => match bit_depth { + 0..=8 => {} + 9..=16 => buf.chunks_exact_mut(2).for_each(|v| { + v.copy_from_slice(&u16::from_le_bytes((*v).try_into().unwrap()).to_ne_bytes()) + }), + 17..=32 => buf.chunks_exact_mut(4).for_each(|v| { + v.copy_from_slice(&u32::from_le_bytes((*v).try_into().unwrap()).to_ne_bytes()) + }), + _ => buf.chunks_exact_mut(8).for_each(|v| { + v.copy_from_slice(&u64::from_le_bytes((*v).try_into().unwrap()).to_ne_bytes()) + }), }, - ByteOrder::BigEndian => match buf { - DecodingBuffer::U8(_) | DecodingBuffer::I8(_) => {} - DecodingBuffer::U16(b) => b.iter_mut().for_each(|v| *v = u16::from_be(*v)), - DecodingBuffer::I16(b) => b.iter_mut().for_each(|v| *v = i16::from_be(*v)), - DecodingBuffer::U32(b) => b.iter_mut().for_each(|v| *v = u32::from_be(*v)), - DecodingBuffer::I32(b) => b.iter_mut().for_each(|v| *v = i32::from_be(*v)), - DecodingBuffer::U64(b) => b.iter_mut().for_each(|v| *v = u64::from_be(*v)), - DecodingBuffer::I64(b) => b.iter_mut().for_each(|v| *v = i64::from_be(*v)), - DecodingBuffer::F32(b) => b - .iter_mut() - .for_each(|v| *v = f32::from_bits(u32::from_be(v.to_bits()))), - DecodingBuffer::F64(b) => b - .iter_mut() - .for_each(|v| *v = f64::from_bits(u64::from_be(v.to_bits()))), + ByteOrder::BigEndian => match bit_depth { + 0..=8 => {} + 9..=16 => buf.chunks_exact_mut(2).for_each(|v| { + v.copy_from_slice(&u16::from_be_bytes((*v).try_into().unwrap()).to_ne_bytes()) + }), + 17..=32 => buf.chunks_exact_mut(4).for_each(|v| { + v.copy_from_slice(&u32::from_be_bytes((*v).try_into().unwrap()).to_ne_bytes()) + }), + _ => buf.chunks_exact_mut(8).for_each(|v| { + v.copy_from_slice(&u64::from_be_bytes((*v).try_into().unwrap()).to_ne_bytes()) + }), }, }; } @@ -1042,10 +951,15 @@ impl Decoder { let byte_order = self.reader.byte_order; + let output_row_stride = (output_width as u64) + .saturating_mul(self.image.samples_per_pixel() as u64) + .saturating_mul(self.image.bits_per_sample as u64) + / 8; + self.image.expand_chunk( &mut self.reader, - buffer.copy(), - output_width, + buffer.as_bytes_mut(), + output_row_stride.try_into()?, byte_order, chunk_index, &self.limits, @@ -1146,8 +1060,23 @@ impl Decoder { )); } + let output_row_bits = (width as u64 * self.image.bits_per_sample as u64) + .checked_mul(samples as u64) + .ok_or(TiffError::LimitsExceeded)?; + let output_row_stride: usize = ((output_row_bits + 7) / 8).try_into()?; + + let chunk_row_bits = (chunk_dimensions.0 as u64 * self.image.bits_per_sample as u64) + .checked_mul(samples as u64) + .ok_or(TiffError::LimitsExceeded)?; + let chunk_row_bytes: usize = ((chunk_row_bits + 7) / 8).try_into()?; + let chunks_across = ((width - 1) / chunk_dimensions.0 + 1) as usize; - let strip_samples = width as usize * chunk_dimensions.1 as usize * samples; + + if chunks_across > 1 && chunk_row_bits % 8 != 0 { + return Err(TiffError::UnsupportedError( + TiffUnsupportedError::MisalignedTileBoundaries, + )); + } let image_chunks = self.image().chunk_offsets.len() / self.image().strips_per_pixel(); // For multi-band images, only the first band is read. @@ -1159,12 +1088,13 @@ impl Decoder { let x = chunk % chunks_across; let y = chunk / chunks_across; - let buffer_offset = y * strip_samples + x * chunk_dimensions.0 as usize * samples; + let buffer_offset = + y * output_row_stride * chunk_dimensions.1 as usize + x * chunk_row_bytes; let byte_order = self.reader.byte_order; self.image.expand_chunk( &mut self.reader, - result.as_buffer(buffer_offset).copy(), - width as usize, + &mut result.as_buffer(0).as_bytes_mut()[buffer_offset..], + output_row_stride, byte_order, chunk as u32, &self.limits, diff --git a/src/error.rs b/src/error.rs index 71fd972f..7dc781e0 100644 --- a/src/error.rs +++ b/src/error.rs @@ -165,6 +165,7 @@ pub enum TiffUnsupportedError { UnsupportedDataType, UnsupportedInterpretation(PhotometricInterpretation), UnsupportedJpegFeature(UnsupportedFeature), + MisalignedTileBoundaries, } impl fmt::Display for TiffUnsupportedError { @@ -223,6 +224,7 @@ impl fmt::Display for TiffUnsupportedError { UnsupportedJpegFeature(ref unsupported_feature) => { write!(fmt, "Unsupported JPEG feature {:?}", unsupported_feature) } + MisalignedTileBoundaries => write!(fmt, "Tile rows are not aligned to byte boundaries"), } } } diff --git a/src/lib.rs b/src/lib.rs index 1940723a..a4522ced 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -44,3 +44,17 @@ pub enum ColorType { /// Pixel has multiple bands/channels Multiband { bit_depth: u8, num_samples: u16 }, } +impl ColorType { + fn bit_depth(&self) -> u8 { + match *self { + ColorType::Gray(b) + | ColorType::RGB(b) + | ColorType::Palette(b) + | ColorType::GrayA(b) + | ColorType::RGBA(b) + | ColorType::CMYK(b) + | ColorType::YCbCr(b) + | ColorType::Multiband { bit_depth: b, .. } => b, + } + } +}