From d27e7d4a6ba4e525b71eed1369db2e6f50e46069 Mon Sep 17 00:00:00 2001 From: Fee Fladder Date: Thu, 12 Jun 2025 15:41:24 +0200 Subject: [PATCH 1/3] added struct, changed decoding and predictor API to allow in-place modification --- src/bytecast.rs | 36 ++++++ src/cog.rs | 2 +- src/decoder.rs | 60 +++++---- src/decoding_result.rs | 123 ++++++++++++++++++ src/lib.rs | 3 + src/predictor.rs | 285 +++++++++++++++++++++-------------------- src/tiff/error.rs | 2 +- src/tile.rs | 65 ++++++++-- 8 files changed, 402 insertions(+), 174 deletions(-) create mode 100644 src/bytecast.rs create mode 100644 src/decoding_result.rs diff --git a/src/bytecast.rs b/src/bytecast.rs new file mode 100644 index 0000000..f3b5c59 --- /dev/null +++ b/src/bytecast.rs @@ -0,0 +1,36 @@ +//! Trivial, internal byte transmutation. +//! +//! A dependency like bytemuck would give us extra assurance of the safety but overall would not +//! reduce the amount of total unsafety. We don't use it in the interface where the traits would +//! really become useful. +//! +//! SAFETY: These are benign casts as we apply them to fixed size integer types only. All of them +//! are naturally aligned, valid for all bit patterns and their alignment is surely at most their +//! size (we assert the latter fact since it is 'implementation defined' if following the letter of +//! the unsafe code guidelines). +//! +//! TODO: Would like to use std-lib here. +// from image-tiff src/bytecast.rs +// see https://stackoverflow.com/a/29042896/14681457 +use std::{mem, slice}; + +macro_rules! integral_slice_as_bytes{($int:ty, $const:ident $(,$mut:ident)*) => { + pub(crate) fn $const(slice: &[$int]) -> &[u8] { + assert!(mem::align_of::<$int>() <= mem::size_of::<$int>()); + unsafe { slice::from_raw_parts(slice.as_ptr() as *const u8, mem::size_of_val(slice)) } + } + $(pub(crate) fn $mut(slice: &mut [$int]) -> &mut [u8] { + assert!(mem::align_of::<$int>() <= mem::size_of::<$int>()); + unsafe { slice::from_raw_parts_mut(slice.as_mut_ptr() as *mut u8, mem::size_of_val(slice)) } + })* +}} + +integral_slice_as_bytes!(i8, i8_as_ne_bytes, i8_as_ne_mut_bytes); +integral_slice_as_bytes!(u16, u16_as_ne_bytes, u16_as_ne_mut_bytes); +integral_slice_as_bytes!(i16, i16_as_ne_bytes, i16_as_ne_mut_bytes); +integral_slice_as_bytes!(u32, u32_as_ne_bytes, u32_as_ne_mut_bytes); +integral_slice_as_bytes!(i32, i32_as_ne_bytes, i32_as_ne_mut_bytes); +integral_slice_as_bytes!(u64, u64_as_ne_bytes, u64_as_ne_mut_bytes); +integral_slice_as_bytes!(i64, i64_as_ne_bytes, i64_as_ne_mut_bytes); +integral_slice_as_bytes!(f32, f32_as_ne_bytes, f32_as_ne_mut_bytes); +integral_slice_as_bytes!(f64, f64_as_ne_bytes, f64_as_ne_mut_bytes); diff --git a/src/cog.rs b/src/cog.rs index 99a0f14..0a39a40 100644 --- a/src/cog.rs +++ b/src/cog.rs @@ -52,7 +52,7 @@ mod test { let ifd = &tiff.ifds[1]; let tile = ifd.fetch_tile(0, 0, reader.as_ref()).await.unwrap(); let tile = tile.decode(&Default::default()).unwrap(); - std::fs::write("img.buf", tile).unwrap(); + std::fs::write("img.buf", tile.as_u8_buf()).unwrap(); } #[ignore = "local file"] diff --git a/src/decoder.rs b/src/decoder.rs index faf5a17..ef7927f 100644 --- a/src/decoder.rs +++ b/src/decoder.rs @@ -54,10 +54,11 @@ pub trait Decoder: Debug + Send + Sync { /// Decode a TIFF tile. fn decode_tile( &self, - buffer: Bytes, + compressed_buffer: Bytes, + result_buffer: &mut [u8], photometric_interpretation: PhotometricInterpretation, jpeg_tables: Option<&[u8]>, - ) -> AsyncTiffResult; + ) -> AsyncTiffResult<()>; } /// A decoder for the Deflate compression method. @@ -67,14 +68,14 @@ pub struct DeflateDecoder; impl Decoder for DeflateDecoder { fn decode_tile( &self, - buffer: Bytes, + compressed_buffer: Bytes, + result_buffer: &mut [u8], _photometric_interpretation: PhotometricInterpretation, _jpeg_tables: Option<&[u8]>, - ) -> AsyncTiffResult { - let mut decoder = ZlibDecoder::new(Cursor::new(buffer)); - let mut buf = Vec::new(); - decoder.read_to_end(&mut buf)?; - Ok(buf.into()) + ) -> AsyncTiffResult<()> { + let mut decoder = ZlibDecoder::new(Cursor::new(compressed_buffer)); + decoder.read_exact(result_buffer)?; + Ok(()) } } @@ -85,11 +86,17 @@ pub struct JPEGDecoder; impl Decoder for JPEGDecoder { fn decode_tile( &self, - buffer: Bytes, + compressed_buffer: Bytes, + result_buffer: &mut [u8], photometric_interpretation: PhotometricInterpretation, jpeg_tables: Option<&[u8]>, - ) -> AsyncTiffResult { - decode_modern_jpeg(buffer, photometric_interpretation, jpeg_tables) + ) -> AsyncTiffResult<()> { + decode_modern_jpeg( + compressed_buffer, + result_buffer, + photometric_interpretation, + jpeg_tables, + ) } } @@ -100,14 +107,18 @@ pub struct LZWDecoder; impl Decoder for LZWDecoder { fn decode_tile( &self, - buffer: Bytes, + compressed_buffer: Bytes, + result_buffer: &mut [u8], _photometric_interpretation: PhotometricInterpretation, _jpeg_tables: Option<&[u8]>, - ) -> AsyncTiffResult { + ) -> AsyncTiffResult<()> { // https://github.com/image-rs/image-tiff/blob/90ae5b8e54356a35e266fb24e969aafbcb26e990/src/decoder/stream.rs#L147 let mut decoder = weezl::decode::Decoder::with_tiff_size_switch(weezl::BitOrder::Msb, 8); - let decoded = decoder.decode(&buffer).expect("failed to decode LZW data"); - Ok(decoded.into()) + let decoded = decoder + .decode(&compressed_buffer) + .expect("failed to decode LZW data"); + result_buffer.copy_from_slice(&decoded); + Ok(()) } } @@ -118,20 +129,24 @@ pub struct UncompressedDecoder; impl Decoder for UncompressedDecoder { fn decode_tile( &self, - buffer: Bytes, + compressed_buffer: Bytes, + result_buffer: &mut [u8], _photometric_interpretation: PhotometricInterpretation, _jpeg_tables: Option<&[u8]>, - ) -> AsyncTiffResult { - Ok(buffer) + ) -> AsyncTiffResult<()> { + assert_eq!(compressed_buffer.len(), result_buffer.len()); + result_buffer.copy_from_slice(&compressed_buffer); + Ok(()) } } // https://github.com/image-rs/image-tiff/blob/3bfb43e83e31b0da476832067ada68a82b378b7b/src/decoder/image.rs#L389-L450 fn decode_modern_jpeg( - buf: Bytes, + compressed_buffer: Bytes, + result_buffer: &mut [u8], photometric_interpretation: PhotometricInterpretation, jpeg_tables: Option<&[u8]>, -) -> AsyncTiffResult { +) -> AsyncTiffResult<()> { // Construct new jpeg_reader wrapping a SmartReader. // // JPEG compression in TIFF allows saving quantization and/or huffman tables in one central @@ -142,7 +157,7 @@ fn decode_modern_jpeg( // data is removed because it 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`). - let reader = Cursor::new(buf); + let reader = Cursor::new(compressed_buffer); let jpeg_reader = match jpeg_tables { Some(jpeg_tables) => { @@ -177,5 +192,6 @@ fn decode_modern_jpeg( } let data = decoder.decode()?; - Ok(data.into()) + result_buffer.copy_from_slice(&data); + Ok(()) } diff --git a/src/decoding_result.rs b/src/decoding_result.rs new file mode 100644 index 0000000..7596bdb --- /dev/null +++ b/src/decoding_result.rs @@ -0,0 +1,123 @@ +use crate::{ + bytecast::*, + error::{AsyncTiffError, AsyncTiffResult}, + predictor::PredictorInfo, + tiff::{tags::SampleFormat, TiffError, TiffUnsupportedError}, +}; + +/// Result of a decoding process +#[derive(Debug)] +#[non_exhaustive] +pub enum DecodingResult { + /// A vector of unsigned bytes + U8(Vec), + /// A vector of unsigned words + U16(Vec), + /// A vector of 32 bit unsigned ints + U32(Vec), + /// A vector of 64 bit unsigned ints + U64(Vec), + /// A vector of 32 bit IEEE floats + F32(Vec), + /// A vector of 64 bit IEEE floats + F64(Vec), + /// A vector of 8 bit signed ints + I8(Vec), + /// A vector of 16 bit signed ints + I16(Vec), + /// A vector of 32 bit signed ints + I32(Vec), + /// A vector of 64 bit signed ints + I64(Vec), +} + +impl DecodingResult { + /// use this result as a `&mut[u8]` buffer + pub fn as_mut_u8_buf(&mut self) -> &mut [u8] { + match self { + DecodingResult::U8(v) => v, + DecodingResult::U16(v) => u16_as_ne_mut_bytes(v), + DecodingResult::U32(v) => u32_as_ne_mut_bytes(v), + DecodingResult::U64(v) => u64_as_ne_mut_bytes(v), + DecodingResult::I8(v) => i8_as_ne_mut_bytes(v), + DecodingResult::I16(v) => i16_as_ne_mut_bytes(v), + DecodingResult::I32(v) => i32_as_ne_mut_bytes(v), + DecodingResult::I64(v) => i64_as_ne_mut_bytes(v), + DecodingResult::F32(v) => f32_as_ne_mut_bytes(v), + DecodingResult::F64(v) => f64_as_ne_mut_bytes(v), + } + } + + /// use this result as a `&[u8]` buffer + pub fn as_u8_buf(&self) -> &[u8] { + match self { + DecodingResult::U8(v) => v, + DecodingResult::U16(v) => u16_as_ne_bytes(v), + DecodingResult::U32(v) => u32_as_ne_bytes(v), + DecodingResult::U64(v) => u64_as_ne_bytes(v), + DecodingResult::I8(v) => i8_as_ne_bytes(v), + DecodingResult::I16(v) => i16_as_ne_bytes(v), + DecodingResult::I32(v) => i32_as_ne_bytes(v), + DecodingResult::I64(v) => i64_as_ne_bytes(v), + DecodingResult::F32(v) => f32_as_ne_bytes(v), + DecodingResult::F64(v) => f64_as_ne_bytes(v), + } + } + + /// create a properly sized `Self` from PredictorInfo struct for a single chunk (tile/strip) + // similar to image-tiff's Decoder::result_buffer + pub fn from_predictor_info( + info: PredictorInfo, + chunk_x: usize, + chunk_y: usize, + ) -> AsyncTiffResult { + // this part is outside of result_buffer + + // since the calculations are in pixels rather than bytes (predictors + // use bytes), we do them here, rather than adding even more functions + // to PredictorInfo + let width = info.chunk_width_pixels(chunk_x as _)?; + let height = info.output_rows(chunk_y as _)?; + + let buffer_size = match (width as usize) + .checked_mul(height) + .and_then(|x| x.checked_mul(info.samples_per_pixel as _)) + { + Some(s) => s, + None => return Err(AsyncTiffError::InternalTIFFError(TiffError::IntSizeError)), + }; + + let max_sample_bits = info.bits_per_sample(); + + match info.sample_format { + SampleFormat::Uint => match max_sample_bits { + n if n <= 8 => Ok(DecodingResult::U8(vec![0u8; buffer_size])), + n if n <= 16 => Ok(DecodingResult::U16(vec![0u16; buffer_size])), + n if n <= 32 => Ok(DecodingResult::U32(vec![0u32; buffer_size])), + n if n <= 64 => Ok(DecodingResult::U64(vec![0u64; buffer_size])), + n => Err(AsyncTiffError::InternalTIFFError( + TiffError::UnsupportedError(TiffUnsupportedError::UnsupportedBitsPerChannel(n)), + )), + }, + SampleFormat::Int => match max_sample_bits { + n if n <= 8 => Ok(DecodingResult::I8(vec![0i8; buffer_size])), + n if n <= 16 => Ok(DecodingResult::I16(vec![0i16; buffer_size])), + n if n <= 32 => Ok(DecodingResult::I32(vec![0i32; buffer_size])), + n if n <= 64 => Ok(DecodingResult::I64(vec![0i64; buffer_size])), + n => Err(AsyncTiffError::InternalTIFFError( + TiffError::UnsupportedError(TiffUnsupportedError::UnsupportedBitsPerChannel(n)), + )), + }, + SampleFormat::IEEEFP => match max_sample_bits { + 32 => Ok(DecodingResult::F32(vec![0f32; buffer_size])), + 64 => Ok(DecodingResult::F64(vec![0f64; buffer_size])), + n => Err(AsyncTiffError::InternalTIFFError( + TiffError::UnsupportedError(TiffUnsupportedError::UnsupportedBitsPerChannel(n)), + )), + }, + format => Err(AsyncTiffError::InternalTIFFError( + TiffUnsupportedError::UnsupportedSampleFormat(vec![format]).into(), + )), + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 6c9d7de..ee67569 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,8 +3,10 @@ pub mod reader; // TODO: maybe rename this mod +pub(crate) mod bytecast; mod cog; pub mod decoder; +mod decoding_result; pub mod error; pub mod geo; mod ifd; @@ -14,5 +16,6 @@ pub mod tiff; mod tile; pub use cog::TIFF; +pub use decoding_result::DecodingResult; pub use ifd::ImageFileDirectory; pub use tile::Tile; diff --git a/src/predictor.rs b/src/predictor.rs index f897b80..9c9be9a 100644 --- a/src/predictor.rs +++ b/src/predictor.rs @@ -1,10 +1,8 @@ //! Predictors for no predictor, horizontal and floating-point use std::fmt::Debug; -use bytes::{Bytes, BytesMut}; - use crate::error::AsyncTiffError; -use crate::tiff::tags::PlanarConfiguration; +use crate::tiff::tags::{PlanarConfiguration, SampleFormat}; use crate::ImageFileDirectory; use crate::{error::AsyncTiffResult, reader::Endianness}; @@ -16,7 +14,7 @@ use crate::{error::AsyncTiffResult, reader::Endianness}; /// Also provides convenience functions /// #[derive(Debug, Clone, Copy)] -pub(crate) struct PredictorInfo { +pub struct PredictorInfo { /// endianness endianness: Endianness, /// width of the image in pixels @@ -32,10 +30,23 @@ pub(crate) struct PredictorInfo { /// bits per sample /// /// We only support a single bits_per_sample across all samples - bits_per_sample: u16, + pub(crate) bits_per_sample: u16, /// number of samples per pixel - samples_per_pixel: u16, - + pub(crate) samples_per_pixel: u16, + /// sample format (used for creating DecodingResult) + pub(crate) sample_format: SampleFormat, + /// planar configuration, determines the shape of the resulting image + /// ```raw + /// [ + /// RGBRGBRGB + /// ] + /// vs + /// [ + /// RRR, + /// GGG, + /// BBB + /// ] + /// ``` planar_configuration: PlanarConfiguration, } @@ -71,16 +82,17 @@ impl PredictorInfo { image_height: ifd.image_height, chunk_width, chunk_height, - planar_configuration: ifd.planar_configuration, bits_per_sample: ifd.bits_per_sample[0], samples_per_pixel: ifd.samples_per_pixel, + sample_format: ifd.sample_format[0], + planar_configuration: ifd.planar_configuration, } } /// chunk width in pixels, taking padding into account /// /// strips are considered image-width chunks - fn chunk_width_pixels(&self, x: u32) -> AsyncTiffResult { + pub(crate) fn chunk_width_pixels(&self, x: u32) -> AsyncTiffResult { let chunks_across = self.chunks_across(); if x >= chunks_across { Err(AsyncTiffError::TileIndexError(x, chunks_across)) @@ -113,7 +125,7 @@ impl PredictorInfo { } /// the number of rows the output has, taking padding and PlanarConfiguration into account. - fn output_rows(&self, y: u32) -> AsyncTiffResult { + pub(crate) fn output_rows(&self, y: u32) -> AsyncTiffResult { match self.planar_configuration { PlanarConfiguration::Chunky => Ok(self.chunk_height_pixels(y)? as usize), PlanarConfiguration::Planar => { @@ -143,24 +155,23 @@ impl PredictorInfo { } } -/// reverse horizontal predictor +/// in-place reverse horizontal predictor /// /// fixes byte order before reversing differencing pub(crate) fn unpredict_hdiff( - buffer: Bytes, + buffer: &mut [u8], predictor_info: &PredictorInfo, tile_x: u32, -) -> AsyncTiffResult { +) -> AsyncTiffResult<()> { let output_row_stride = predictor_info.output_row_stride(tile_x)?; let samples = predictor_info.samples_per_pixel as usize; let bit_depth = predictor_info.bits_per_sample; - let buffer = fix_endianness(buffer, predictor_info.endianness, bit_depth); - let mut res = BytesMut::from(buffer); - for buf in res.chunks_mut(output_row_stride) { + fix_endianness(buffer, predictor_info.endianness, bit_depth); + for buf in buffer.chunks_mut(output_row_stride) { rev_hpredict_nsamp(buf, bit_depth, samples); } - Ok(res.into()) + Ok(()) } /// Reverse predictor convenience function for horizontal differencing @@ -202,51 +213,45 @@ pub fn rev_hpredict_nsamp(buf: &mut [u8], bit_depth: u16, samples: usize) { } } -/// Fix endianness. If `byte_order` matches the host, then conversion is a no-op. +/// Fix endianness in-place. If `byte_order` matches the host, then conversion is a no-op. /// // from image-tiff -pub fn fix_endianness(buffer: Bytes, byte_order: Endianness, bit_depth: u16) -> Bytes { +pub fn fix_endianness(buffer: &mut [u8], byte_order: Endianness, bit_depth: u16) { #[cfg(target_endian = "little")] if let Endianness::LittleEndian = byte_order { - return buffer; + return; } #[cfg(target_endian = "big")] if let Endianness::BigEndian = byte_order { - return buffer; + return; } match byte_order { - Endianness::LittleEndian => { - let mut buf = BytesMut::from(buffer); - 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()) - }), - } - buf.freeze() - } + Endianness::LittleEndian => match bit_depth { + 0..=8 => {} + 9..=16 => buffer.chunks_exact_mut(2).for_each(|v| { + v.copy_from_slice(&u16::from_le_bytes((*v).try_into().unwrap()).to_ne_bytes()) + }), + 17..=32 => buffer.chunks_exact_mut(4).for_each(|v| { + v.copy_from_slice(&u32::from_le_bytes((*v).try_into().unwrap()).to_ne_bytes()) + }), + _ => buffer.chunks_exact_mut(8).for_each(|v| { + v.copy_from_slice(&u64::from_le_bytes((*v).try_into().unwrap()).to_ne_bytes()) + }), + }, Endianness::BigEndian => { - let mut buf = BytesMut::from(buffer); match bit_depth { 0..=8 => {} - 9..=16 => buf.chunks_exact_mut(2).for_each(|v| { + 9..=16 => buffer.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| { + 17..=32 => buffer.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| { + _ => buffer.chunks_exact_mut(8).for_each(|v| { v.copy_from_slice(&u64::from_be_bytes((*v).try_into().unwrap()).to_ne_bytes()) }), }; - buf.freeze() } } } @@ -258,21 +263,20 @@ pub fn fix_endianness(buffer: Bytes, byte_order: Endianness, bit_depth: u16) -> /// /// If the tile has horizontal padding, it will shorten the output. pub(crate) fn unpredict_float( - buffer: Bytes, + in_buffer: &mut [u8], + out_buffer: &mut [u8], predictor_info: &PredictorInfo, tile_x: u32, - tile_y: u32, -) -> AsyncTiffResult { +) -> AsyncTiffResult<()> { let output_row_stride = predictor_info.output_row_stride(tile_x)?; - let mut res: BytesMut = - BytesMut::zeroed(output_row_stride * predictor_info.output_rows(tile_y)?); + // let mut res: BytesMut = + // BytesMut::zeroed(output_row_stride * predictor_info.output_rows(tile_y)?); let bit_depth = predictor_info.bits_per_sample; if predictor_info.chunk_width_pixels(tile_x)? == predictor_info.chunk_width { // no special padding handling - let mut input = BytesMut::from(buffer); - for (in_buf, out_buf) in input + for (in_buf, out_buf) in in_buffer .chunks_mut(output_row_stride) - .zip(res.chunks_mut(output_row_stride)) + .zip(out_buffer.chunks_mut(output_row_stride)) { match bit_depth { 16 => rev_predict_f16(in_buf, out_buf, predictor_info.samples_per_pixel as _), @@ -287,16 +291,14 @@ pub(crate) fn unpredict_float( } } else { // specially handle padding bytes - // create a buffer for the full width - let mut input = BytesMut::from(buffer); - let input_row_stride = predictor_info.chunk_width as usize * predictor_info.bits_per_sample as usize / 8; - for (in_buf, out_buf) in input + for (in_buf, out_buf) in in_buffer .chunks_mut(input_row_stride) - .zip(res.chunks_mut(output_row_stride)) + .zip(out_buffer.chunks_mut(output_row_stride)) { - let mut out_row = BytesMut::zeroed(input_row_stride); + // create a buffer for the full width + let mut out_row = vec![0; input_row_stride]; match bit_depth { 16 => rev_predict_f16(in_buf, &mut out_row, predictor_info.samples_per_pixel as _), 32 => rev_predict_f32(in_buf, &mut out_row, predictor_info.samples_per_pixel as _), @@ -311,7 +313,7 @@ pub(crate) fn unpredict_float( out_buf.copy_from_slice(&out_row[..output_row_stride]); } } - Ok(res.into()) + Ok(()) } /// Reverse floating point prediction @@ -395,8 +397,6 @@ pub fn rev_predict_f64(input: &mut [u8], output: &mut [u8], samples: usize) { mod test { use std::vec; - use bytes::Bytes; - use crate::{ predictor::{unpredict_float, unpredict_hdiff}, reader::Endianness, @@ -412,6 +412,7 @@ mod test { chunk_height: 4, bits_per_sample: 8, samples_per_pixel: 1, + sample_format: SampleFormat::Uint, // not used planar_configuration: PlanarConfiguration::Chunky, }; #[rustfmt::skip] @@ -535,104 +536,118 @@ mod test { predictor_info.bits_per_sample = 8; assert_eq!(-1i32 as u8, 255); println!("testing u8"); - let buffer = Bytes::from(input.iter().map(|v| *v as u8).collect::>()); - let res = Bytes::from(expected.clone()); - assert_eq!(unpredict_hdiff(buffer, &predictor_info, x).unwrap(), res); + let mut res = input.iter().map(|v| *v as u8).collect::>(); + unpredict_hdiff(&mut res, &predictor_info, x).unwrap(); + assert_eq!(res, expected); assert_eq!(-1i32 as u16, u16::MAX); println!("testing u16"); predictor_info.bits_per_sample = 16; - let buffer = Bytes::from(input.iter().flat_map(|v| (*v as u16).to_le_bytes()).collect::>()); - let res = Bytes::from(expected.iter().flat_map(|v| (*v as u16).to_ne_bytes()).collect::>()); - assert_eq!(unpredict_hdiff(buffer, &predictor_info, x).unwrap(), res); + let mut res = input.iter().flat_map(|v| (*v as u16).to_le_bytes()).collect::>(); + let exp = expected.iter().flat_map(|v| (*v as u16).to_ne_bytes()).collect::>(); + println!("result buffer: {:?}", &res); + unpredict_hdiff(&mut res, &predictor_info, x).unwrap(); + assert_eq!(res, exp); assert_eq!(-1i32 as u32, u32::MAX); println!("testing u32"); predictor_info.bits_per_sample = 32; - let buffer = Bytes::from(input.iter().flat_map(|v| (*v as u32).to_le_bytes()).collect::>()); - let res = Bytes::from(expected.iter().flat_map(|v| (*v as u32).to_ne_bytes()).collect::>()); - assert_eq!(unpredict_hdiff(buffer, &predictor_info, x).unwrap(), res); + let mut res = input.iter().flat_map(|v| (*v as u32).to_le_bytes()).collect::>(); + let exp = expected.iter().flat_map(|v| (*v as u32).to_ne_bytes()).collect::>(); + println!("result buffer: {:?}", &res); + unpredict_hdiff(&mut res, &predictor_info, x).unwrap(); + assert_eq!(res, exp); assert_eq!(-1i32 as u64, u64::MAX); println!("testing u64"); predictor_info.bits_per_sample = 64; - let buffer = Bytes::from(input.iter().flat_map(|v| (*v as u64).to_le_bytes()).collect::>()); - let res = Bytes::from(expected.iter().flat_map(|v| (*v as u64).to_ne_bytes()).collect::>()); - assert_eq!(unpredict_hdiff(buffer, &predictor_info, x).unwrap(), res); + let mut res = input.iter().flat_map(|v| (*v as u64).to_le_bytes()).collect::>(); + let exp = expected.iter().flat_map(|v| (*v as u64).to_ne_bytes()).collect::>(); + unpredict_hdiff(&mut res, &predictor_info, x).unwrap(); + assert_eq!(res, exp); println!("ints littleendian"); predictor_info.bits_per_sample = 8; println!("testing i8"); - let buffer = Bytes::from(input.iter().flat_map(|v| (*v as i8).to_le_bytes()).collect::>()); - println!("{:?}", &buffer[..]); - let res = Bytes::from(expected.clone()); - assert_eq!(unpredict_hdiff(buffer, &predictor_info, x).unwrap()[..], res[..]); + let mut res = input.iter().flat_map(|v| (*v as i8).to_le_bytes()).collect::>(); + println!("{:?}", &res[..]); + unpredict_hdiff(&mut res, &predictor_info, x).unwrap(); + assert_eq!(res, expected); println!("testing i16"); predictor_info.bits_per_sample = 16; - let buffer = Bytes::from(input.iter().flat_map(|v| (*v as i16).to_le_bytes()).collect::>()); - let res = Bytes::from(expected.iter().flat_map(|v| (*v as i16).to_ne_bytes()).collect::>()); - assert_eq!(unpredict_hdiff(buffer, &predictor_info, x).unwrap(), res); + let mut res = input.iter().flat_map(|v| (*v as i16).to_le_bytes()).collect::>(); + let exp = expected.iter().flat_map(|v| (*v as i16).to_ne_bytes()).collect::>(); + unpredict_hdiff(&mut res, &predictor_info, x).unwrap(); + assert_eq!(res, exp); println!("testing i32"); predictor_info.bits_per_sample = 32; - let buffer = Bytes::from(input.iter().flat_map(|v| v.to_le_bytes()).collect::>()); - let res = Bytes::from(expected.iter().flat_map(|v| (*v as i32).to_ne_bytes()).collect::>()); - assert_eq!(unpredict_hdiff(buffer, &predictor_info, x).unwrap(), res); + let mut res = input.iter().flat_map(|v| v.to_le_bytes()).collect::>(); + let exp = expected.iter().flat_map(|v| (*v as i32).to_ne_bytes()).collect::>(); + unpredict_hdiff(&mut res, &predictor_info, x).unwrap(); + assert_eq!(res, exp); println!("testing i64"); predictor_info.bits_per_sample = 64; - let buffer = Bytes::from(input.iter().flat_map(|v| (*v as i64).to_le_bytes()).collect::>()); - let res = Bytes::from(expected.iter().flat_map(|v| (*v as i64).to_ne_bytes()).collect::>()); - assert_eq!(unpredict_hdiff(buffer, &predictor_info, x).unwrap(), res); + let mut res = input.iter().flat_map(|v| (*v as i64).to_le_bytes()).collect::>(); + let exp = expected.iter().flat_map(|v| (*v as i64).to_ne_bytes()).collect::>(); + unpredict_hdiff(&mut res, &predictor_info, x).unwrap(); + assert_eq!(res, exp); println!("uints bigendian"); predictor_info.endianness = Endianness::BigEndian; predictor_info.bits_per_sample = 8; assert_eq!(-1i32 as u8, 255); println!("testing u8"); - let buffer = Bytes::from(input.iter().map(|v| *v as u8).collect::>()); - let res = Bytes::from(expected.clone()); - assert_eq!(unpredict_hdiff(buffer, &predictor_info, x).unwrap(), res); + let mut res = input.iter().map(|v| *v as u8).collect::>(); + unpredict_hdiff(&mut res, &predictor_info, x).unwrap(); + assert_eq!(res, expected); assert_eq!(-1i32 as u16, u16::MAX); println!("testing u16"); predictor_info.bits_per_sample = 16; - let buffer = Bytes::from(input.iter().flat_map(|v| (*v as u16).to_be_bytes()).collect::>()); - let res = Bytes::from(expected.iter().flat_map(|v| (*v as u16).to_ne_bytes()).collect::>()); - println!("buffer: {:?}", &buffer[..]); - assert_eq!(unpredict_hdiff(buffer, &predictor_info, x).unwrap()[..], res[..]); + let mut res = input.iter().flat_map(|v| (*v as u16).to_be_bytes()).collect::>(); + let exp = expected.iter().flat_map(|v| (*v as u16).to_ne_bytes()).collect::>(); + println!("buffer: {:?}", &res); + unpredict_hdiff(&mut res, &predictor_info, x).unwrap(); + assert_eq!(res, exp); assert_eq!(-1i32 as u32, u32::MAX); println!("testing u32"); predictor_info.bits_per_sample = 32; - let buffer = Bytes::from(input.iter().flat_map(|v| (*v as u32).to_be_bytes()).collect::>()); - let res = Bytes::from(expected.iter().flat_map(|v| (*v as u32).to_ne_bytes()).collect::>()); - assert_eq!(unpredict_hdiff(buffer, &predictor_info, x).unwrap(), res); + let mut res = input.iter().flat_map(|v| (*v as u32).to_be_bytes()).collect::>(); + let exp = expected.iter().flat_map(|v| (*v as u32).to_ne_bytes()).collect::>(); + unpredict_hdiff(&mut res, &predictor_info, x).unwrap(); + assert_eq!(res, exp); assert_eq!(-1i32 as u64, u64::MAX); println!("testing u64"); predictor_info.bits_per_sample = 64; - let buffer = Bytes::from(input.iter().flat_map(|v| (*v as u64).to_be_bytes()).collect::>()); - let res = Bytes::from(expected.iter().flat_map(|v| (*v as u64).to_ne_bytes()).collect::>()); - assert_eq!(unpredict_hdiff(buffer, &predictor_info, x).unwrap(), res); + let mut res = input.iter().flat_map(|v| (*v as u64).to_be_bytes()).collect::>(); + let exp = expected.iter().flat_map(|v| (*v as u64).to_ne_bytes()).collect::>(); + unpredict_hdiff(&mut res, &predictor_info, x).unwrap(); + assert_eq!(res, exp); println!("ints bigendian"); predictor_info.bits_per_sample = 8; assert_eq!(-1i32 as u8, 255); println!("testing i8"); - let buffer = Bytes::from(input.iter().flat_map(|v| (*v as i8).to_be_bytes()).collect::>()); - let res = Bytes::from(expected.clone()); - assert_eq!(unpredict_hdiff(buffer, &predictor_info, x).unwrap(), res); + let mut res = input.iter().flat_map(|v| (*v as i8).to_be_bytes()).collect::>(); + unpredict_hdiff(&mut res, &predictor_info, x).unwrap(); + assert_eq!(res, expected); assert_eq!(-1i32 as u16, u16::MAX); println!("testing i16"); predictor_info.bits_per_sample = 16; - let buffer = Bytes::from(input.iter().flat_map(|v| (*v as i16).to_be_bytes()).collect::>()); - let res = Bytes::from(expected.iter().flat_map(|v| (*v as i16).to_ne_bytes()).collect::>()); - assert_eq!(unpredict_hdiff(buffer, &predictor_info, x).unwrap(), res); + let mut res = input.iter().flat_map(|v| (*v as i16).to_be_bytes()).collect::>(); + let exp = expected.iter().flat_map(|v| (*v as i16).to_ne_bytes()).collect::>(); + unpredict_hdiff(&mut res, &predictor_info, x).unwrap(); + assert_eq!(res, exp); assert_eq!(-1i32 as u32, u32::MAX); println!("testing i32"); predictor_info.bits_per_sample = 32; - let buffer = Bytes::from(input.iter().flat_map(|v| v.to_be_bytes()).collect::>()); - let res = Bytes::from(expected.iter().flat_map(|v| (*v as i32).to_ne_bytes()).collect::>()); - assert_eq!(unpredict_hdiff(buffer, &predictor_info, x).unwrap(), res); + let mut res = input.iter().flat_map(|v| v.to_be_bytes()).collect::>(); + let exp = expected.iter().flat_map(|v| (*v as i32).to_ne_bytes()).collect::>(); + unpredict_hdiff(&mut res, &predictor_info, x).unwrap(); + assert_eq!(res, exp); assert_eq!(-1i32 as u64, u64::MAX); println!("testing i64"); predictor_info.bits_per_sample = 64; - let buffer = Bytes::from(input.iter().flat_map(|v| (*v as i64).to_be_bytes()).collect::>()); - let res = Bytes::from(expected.iter().flat_map(|v| (*v as i64).to_ne_bytes()).collect::>()); - assert_eq!(unpredict_hdiff(buffer, &predictor_info, x).unwrap(), res); + let mut res = input.iter().flat_map(|v| (*v as i64).to_be_bytes()).collect::>(); + let exp = expected.iter().flat_map(|v| (*v as i64).to_ne_bytes()).collect::>(); + unpredict_hdiff(&mut res, &predictor_info, x).unwrap(); + assert_eq!(res, exp); } } @@ -647,7 +662,7 @@ mod test { // 0 1 // 0 1 let _shuffled = [0,2,4,6,1,3,5,7u8]; - let diffed = [0,2,2,2,251,2,2,2]; + let mut diffed = [0,2,2,2,251,2,2,2]; let info = PredictorInfo { endianness: Endianness::LittleEndian, image_width: 4+4, @@ -656,13 +671,13 @@ mod test { chunk_height: 4, bits_per_sample: 16, samples_per_pixel: 1, + sample_format: SampleFormat::IEEEFP, planar_configuration: PlanarConfiguration::Chunky, }; - let input = Bytes::from_owner(diffed); - assert_eq!( - &unpredict_float(input, &info, 1, 1).unwrap()[..], - &expect_le[..] - ) + let mut res = [0u8;8]; + + unpredict_float(&mut diffed, &mut res, &info, 1).unwrap(); + assert_eq!(res,expect_le); } #[rustfmt::skip] @@ -676,7 +691,7 @@ mod test { // 0 1 // 0 1 let _shuffled = [0,2,0,0,1,3,0,0u8]; - let diffed = [0,2,254,0,1,2,253,0]; + let mut diffed = [0,2,254,0,1,2,253,0]; let info = PredictorInfo { endianness: Endianness::LittleEndian, image_width: 4+2, @@ -685,25 +700,26 @@ mod test { chunk_height: 4, bits_per_sample: 16, samples_per_pixel: 1, + sample_format: SampleFormat::IEEEFP, planar_configuration: PlanarConfiguration::Chunky, }; - let input = Bytes::from_owner(diffed); - assert_eq!( - &unpredict_float(input, &info, 1, 1).unwrap()[..], - &expect_le[..] - ) + // let input = Bytes::from_owner(diffed); + let mut res = [0u8; 4]; + + unpredict_float(&mut diffed, &mut res, &info, 1).unwrap(); + assert_eq!(res, expect_le); } #[rustfmt::skip] #[test] fn test_fpredict_f32() { // let's take this 2-value image where we only look at bytes - let expect_le = [3,2, 1,0, 7,6, 5,4]; + let expect_le = [3,2, 1,0, 7,6, 5,4u8]; let _expected = [0,1, 2,3, 4,5, 6,7u8]; // 0 1 2 3 \_ de-shuffling indices // 0 1 2 3 / (the one the function uses) let _shuffled = [0,4, 1,5, 2,6, 3,7u8]; - let diffed = [0,4,253,4,253,4,253,4u8]; + let mut diffed = [0,4,253,4,253,4,253,4u8]; println!("expected: {expect_le:?}"); let info = PredictorInfo { endianness: Endianness::LittleEndian, @@ -713,13 +729,12 @@ mod test { chunk_height: 2, bits_per_sample: 32, samples_per_pixel: 1, + sample_format: SampleFormat::IEEEFP, planar_configuration: PlanarConfiguration::Chunky, }; - let input = Bytes::from_owner(diffed); - assert_eq!( - &unpredict_float(input.clone(), &info, 0, 1).unwrap()[..], - &expect_le - ); + let mut res = [0u8; 8]; + unpredict_float(&mut diffed, &mut res, &info, 0).unwrap(); + assert_eq!(res, expect_le); } #[rustfmt::skip] @@ -727,12 +742,12 @@ mod test { fn test_fpredict_f64() { assert_eq!(f64::from_le_bytes([7,6,5,4,3,2,1,0]), f64::from_bits(0x00_01_02_03_04_05_06_07)); // let's take this 2-value image - let expect_be = [7,6,5,4,3, 2,1, 0,15,14,13,12,11,10,9,8]; + let expect_le = [7,6,5,4,3, 2,1, 0,15,14,13,12,11,10,9,8u8]; let _expected = [0,1,2,3,4, 5,6, 7,8, 9,10,11,12,13,14,15u8]; // 0 1 2 3 4 5 6 7 // 0 1 2 3 4 5 6 7 let _shuffled = [0,8,1,9,2,10,3,11,4,12, 5,13, 6,14, 7,15u8]; - let diffed = [0,8,249,8,249,8,249,8,249,8,249,8,249,8,249,8u8]; + let mut diffed = [0,8,249,8,249,8,249,8,249,8,249,8,249,8,249,8u8]; let info = PredictorInfo { endianness: Endianness::LittleEndian, image_width: 2, @@ -741,13 +756,11 @@ mod test { chunk_height: 2, bits_per_sample: 64, samples_per_pixel: 1, + sample_format: SampleFormat::IEEEFP, planar_configuration: PlanarConfiguration::Chunky, }; - let input = Bytes::from_owner(diffed); - assert_eq!( - &unpredict_float(input, &info, 0, 1) - .unwrap()[..], - &expect_be[..] - ); + let mut res = [0u8;16]; + unpredict_float(&mut diffed, &mut res, &info, 0).unwrap(); + assert_eq!(res, expect_le); } } diff --git a/src/tiff/error.rs b/src/tiff/error.rs index 28127a3..d2a5c00 100644 --- a/src/tiff/error.rs +++ b/src/tiff/error.rs @@ -159,7 +159,7 @@ pub enum TiffUnsupportedError { UnsupportedSampleDepth(u8), UnsupportedSampleFormat(Vec), // UnsupportedColorType(ColorType), - UnsupportedBitsPerChannel(u8), + UnsupportedBitsPerChannel(u16), UnsupportedPlanarConfig(Option), UnsupportedDataType, UnsupportedInterpretation(PhotometricInterpretation), diff --git a/src/tile.rs b/src/tile.rs index 3f52b55..17d696f 100644 --- a/src/tile.rs +++ b/src/tile.rs @@ -5,6 +5,7 @@ use crate::error::AsyncTiffResult; use crate::predictor::{fix_endianness, unpredict_float, unpredict_hdiff, PredictorInfo}; use crate::tiff::tags::{CompressionMethod, PhotometricInterpretation, Predictor}; use crate::tiff::{TiffError, TiffUnsupportedError}; +use crate::DecodingResult; /// A TIFF Tile response. /// @@ -65,7 +66,22 @@ impl Tile { /// /// Decoding is separate from fetching so that sync and async operations do not block the same /// runtime. - pub fn decode(self, decoder_registry: &DecoderRegistry) -> AsyncTiffResult { + pub fn decode(self, decoder_registry: &DecoderRegistry) -> AsyncTiffResult { + let mut res = DecodingResult::from_predictor_info(self.predictor_info, self.x, self.y)?; + self.decode_into(decoder_registry, res.as_mut_u8_buf())?; + Ok(res) + } + + /// decode this tile into a **properly sized** buffer. + /// + /// This is an advanced API that _may_ **panic** if the buffer is not + /// properly sized, at different places depending on the compression and + /// predictor. + pub fn decode_into( + &self, + decoder_registry: &DecoderRegistry, + result_buffer: &mut [u8], + ) -> AsyncTiffResult<()> { let decoder = decoder_registry .as_ref() .get(&self.compression_method) @@ -73,23 +89,44 @@ impl Tile { TiffUnsupportedError::UnsupportedCompressionMethod(self.compression_method), ))?; - let decoded_tile = decoder.decode_tile( - self.compressed_bytes.clone(), - self.photometric_interpretation, - self.jpeg_tables.as_deref(), - )?; - match self.predictor { - Predictor::None => Ok(fix_endianness( - decoded_tile, - self.predictor_info.endianness(), - self.predictor_info.bits_per_sample(), - )), + Predictor::None => { + decoder.decode_tile( + self.compressed_bytes.clone(), + result_buffer, + self.photometric_interpretation, + self.jpeg_tables.as_deref(), + )?; + fix_endianness( + result_buffer, + self.predictor_info.endianness(), + self.predictor_info.bits_per_sample(), + ); + Ok(()) + } Predictor::Horizontal => { - unpredict_hdiff(decoded_tile, &self.predictor_info, self.x as _) + decoder.decode_tile( + self.compressed_bytes.clone(), + result_buffer, + self.photometric_interpretation, + self.jpeg_tables.as_deref(), + )?; + unpredict_hdiff(result_buffer, &self.predictor_info, self.x as _) } Predictor::FloatingPoint => { - unpredict_float(decoded_tile, &self.predictor_info, self.x as _, self.y as _) + let mut temp_buf = vec![0u8; result_buffer.len()]; + decoder.decode_tile( + self.compressed_bytes.clone(), + &mut temp_buf, + self.photometric_interpretation, + self.jpeg_tables.as_deref(), + )?; + unpredict_float( + &mut temp_buf, + result_buffer, + &self.predictor_info, + self.x as _, + ) } } } From 53d10d9e9b6aaa35928a7fe8066430fcd264c0f3 Mon Sep 17 00:00:00 2001 From: Fee Fladder Date: Thu, 12 Jun 2025 16:28:04 +0200 Subject: [PATCH 2/3] improved LZW decoding --- src/decoder.rs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/decoder.rs b/src/decoder.rs index ef7927f..cacc1a7 100644 --- a/src/decoder.rs +++ b/src/decoder.rs @@ -7,7 +7,7 @@ use std::io::{Cursor, Read}; use bytes::Bytes; use flate2::bufread::ZlibDecoder; -use crate::error::AsyncTiffResult; +use crate::error::{AsyncTiffResult, AsyncTiffError}; use crate::tiff::tags::{CompressionMethod, PhotometricInterpretation}; use crate::tiff::{TiffError, TiffUnsupportedError}; @@ -114,11 +114,15 @@ impl Decoder for LZWDecoder { ) -> AsyncTiffResult<()> { // https://github.com/image-rs/image-tiff/blob/90ae5b8e54356a35e266fb24e969aafbcb26e990/src/decoder/stream.rs#L147 let mut decoder = weezl::decode::Decoder::with_tiff_size_switch(weezl::BitOrder::Msb, 8); - let decoded = decoder - .decode(&compressed_buffer) - .expect("failed to decode LZW data"); - result_buffer.copy_from_slice(&decoded); - Ok(()) + let buf_res = decoder + .decode_bytes(&compressed_buffer, result_buffer); + match buf_res.status { + Err(e) => Err(AsyncTiffError::External(Box::new(e))), + Ok(lzw_status) => match lzw_status { + weezl::LzwStatus::Ok | weezl::LzwStatus::Done => Ok(()), + weezl::LzwStatus::NoProgress => Err(AsyncTiffError::General("Internal LZW decoder reported no progress".into())) + } + } } } @@ -135,6 +139,7 @@ impl Decoder for UncompressedDecoder { _jpeg_tables: Option<&[u8]>, ) -> AsyncTiffResult<()> { assert_eq!(compressed_buffer.len(), result_buffer.len()); + // we still need to copy into the typed array result_buffer.copy_from_slice(&compressed_buffer); Ok(()) } @@ -192,6 +197,7 @@ fn decode_modern_jpeg( } let data = decoder.decode()?; + // jpeg decoder doesn't support decoding into a buffer -> copy result_buffer.copy_from_slice(&data); Ok(()) } From d533d7873b73b3e20568e0e622e25b69f152c0f7 Mon Sep 17 00:00:00 2001 From: Fee Fladder Date: Thu, 12 Jun 2025 16:38:16 +0200 Subject: [PATCH 3/3] cargo fmt --- src/decoder.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/decoder.rs b/src/decoder.rs index cacc1a7..4b2813f 100644 --- a/src/decoder.rs +++ b/src/decoder.rs @@ -7,7 +7,7 @@ use std::io::{Cursor, Read}; use bytes::Bytes; use flate2::bufread::ZlibDecoder; -use crate::error::{AsyncTiffResult, AsyncTiffError}; +use crate::error::{AsyncTiffError, AsyncTiffResult}; use crate::tiff::tags::{CompressionMethod, PhotometricInterpretation}; use crate::tiff::{TiffError, TiffUnsupportedError}; @@ -114,14 +114,15 @@ impl Decoder for LZWDecoder { ) -> AsyncTiffResult<()> { // https://github.com/image-rs/image-tiff/blob/90ae5b8e54356a35e266fb24e969aafbcb26e990/src/decoder/stream.rs#L147 let mut decoder = weezl::decode::Decoder::with_tiff_size_switch(weezl::BitOrder::Msb, 8); - let buf_res = decoder - .decode_bytes(&compressed_buffer, result_buffer); + let buf_res = decoder.decode_bytes(&compressed_buffer, result_buffer); match buf_res.status { Err(e) => Err(AsyncTiffError::External(Box::new(e))), Ok(lzw_status) => match lzw_status { weezl::LzwStatus::Ok | weezl::LzwStatus::Done => Ok(()), - weezl::LzwStatus::NoProgress => Err(AsyncTiffError::General("Internal LZW decoder reported no progress".into())) - } + weezl::LzwStatus::NoProgress => Err(AsyncTiffError::General( + "Internal LZW decoder reported no progress".into(), + )), + }, } } }