From d49342ca9da26e9c12f971976efad01586f302fb Mon Sep 17 00:00:00 2001 From: Fee Fladder Date: Wed, 2 Apr 2025 03:57:03 +0200 Subject: [PATCH 01/23] added predictors --- src/cog.rs | 4 +- src/error.rs | 4 + src/ifd.rs | 40 +++- src/lib.rs | 1 + src/metadata/reader.rs | 9 +- src/predictor.rs | 507 +++++++++++++++++++++++++++++++++++++++++ src/tiff/error.rs | 5 + src/tile.rs | 173 +++++++++++++- 8 files changed, 728 insertions(+), 15 deletions(-) create mode 100644 src/predictor.rs diff --git a/src/cog.rs b/src/cog.rs index aee53a0..72ca619 100644 --- a/src/cog.rs +++ b/src/cog.rs @@ -25,6 +25,7 @@ mod test { use crate::decoder::DecoderRegistry; use crate::metadata::{PrefetchBuffer, TiffMetadataReader}; + use crate::predictor::RevPredictorRegistry; use crate::reader::{AsyncFileReader, ObjectReader}; use super::*; @@ -52,8 +53,9 @@ mod test { let ifd = &tiff.ifds[1]; let decoder_registry = DecoderRegistry::default(); + let predictor_registry = RevPredictorRegistry::default(); let tile = ifd.fetch_tile(0, 0, reader.as_ref()).await.unwrap(); - let tile = tile.decode(&decoder_registry).unwrap(); + let tile = tile.decode(&decoder_registry, &predictor_registry).unwrap(); std::fs::write("img.buf", tile).unwrap(); } diff --git a/src/error.rs b/src/error.rs index ddbb27e..b1b2204 100644 --- a/src/error.rs +++ b/src/error.rs @@ -15,6 +15,10 @@ pub enum AsyncTiffError { #[error("General error: {0}")] General(String), + /// Tile index error + #[error("Tile index out of bounds: {0}, {1}")] + TileIndexError(u32, u32), + /// IO Error. #[error(transparent)] IOError(#[from] std::io::Error), diff --git a/src/ifd.rs b/src/ifd.rs index ef91ed5..d0047bd 100644 --- a/src/ifd.rs +++ b/src/ifd.rs @@ -6,13 +6,13 @@ use num_enum::TryFromPrimitive; use crate::error::{AsyncTiffError, AsyncTiffResult}; use crate::geo::{GeoKeyDirectory, GeoKeyTag}; -use crate::reader::AsyncFileReader; +use crate::reader::{AsyncFileReader, Endianness}; use crate::tiff::tags::{ CompressionMethod, PhotometricInterpretation, PlanarConfiguration, Predictor, ResolutionUnit, SampleFormat, Tag, }; use crate::tiff::{TiffError, Value}; -use crate::tile::Tile; +use crate::tile::{PredictorInfo, Tile}; const DOCUMENT_NAME: u16 = 269; @@ -21,6 +21,8 @@ const DOCUMENT_NAME: u16 = 269; #[allow(dead_code)] #[derive(Debug, Clone)] pub struct ImageFileDirectory { + pub(crate) endianness: Endianness, + pub(crate) new_subfile_type: Option, /// The number of columns in the image, i.e., the number of pixels per row. @@ -143,7 +145,10 @@ pub struct ImageFileDirectory { impl ImageFileDirectory { /// Create a new ImageFileDirectory from tag data - pub fn from_tags(tag_data: HashMap) -> AsyncTiffResult { + pub fn from_tags( + tag_data: HashMap, + endianness: Endianness, + ) -> AsyncTiffResult { let mut new_subfile_type = None; let mut image_width = None; let mut image_height = None; @@ -349,6 +354,7 @@ impl ImageFileDirectory { PlanarConfiguration::Chunky }; Ok(Self { + endianness, new_subfile_type, image_width: image_width.expect("image_width not found"), image_height: image_height.expect("image_height not found"), @@ -675,6 +681,30 @@ impl ImageFileDirectory { Some(offset as _..(offset + byte_count) as _) } + fn get_predictor_info(&self) -> PredictorInfo { + PredictorInfo { + endianness: self.endianness, + image_width: self.image_width, + image_height: self.image_height, + chunk_width: if self.tile_width.is_none() { + // we are stripped => image_width + self.image_width + } else { + self.tile_width.unwrap() + }, + chunk_height: if self.tile_height.is_none() { + self.rows_per_strip + .expect("no tile height and no rows_per_strip") + } else { + self.tile_height.unwrap() + }, + bits_per_sample: &self.bits_per_sample, + samples_per_pixel: self.samples_per_pixel, + sample_format: &self.sample_format, + planar_configuration: self.planar_configuration, + } + } + /// Fetch the tile located at `x` column and `y` row using the provided reader. pub async fn fetch_tile( &self, @@ -689,6 +719,8 @@ impl ImageFileDirectory { Ok(Tile { x, y, + predictor: self.predictor.unwrap_or(Predictor::None), + predictor_info: self.get_predictor_info(), compressed_bytes, compression_method: self.compression, photometric_interpretation: self.photometric_interpretation, @@ -724,6 +756,8 @@ impl ImageFileDirectory { let tile = Tile { x, y, + predictor: self.predictor.unwrap_or(Predictor::None), + predictor_info: self.get_predictor_info(), compressed_bytes, compression_method: self.compression, photometric_interpretation: self.photometric_interpretation, diff --git a/src/lib.rs b/src/lib.rs index 76c94bc..6c9d7de 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,6 +9,7 @@ pub mod error; pub mod geo; mod ifd; pub mod metadata; +pub mod predictor; pub mod tiff; mod tile; diff --git a/src/metadata/reader.rs b/src/metadata/reader.rs index 97c86d6..760df97 100644 --- a/src/metadata/reader.rs +++ b/src/metadata/reader.rs @@ -226,7 +226,7 @@ impl ImageFileDirectoryReader { let (tag, value) = self.read_tag(fetch, tag_idx).await?; tags.insert(tag, value); } - ImageFileDirectory::from_tags(tags) + ImageFileDirectory::from_tags(tags, self.endianness) } /// Finish this reader, reading the byte offset of the next IFD @@ -623,11 +623,14 @@ async fn read_tag_value( #[cfg(test)] mod test { + use crate::{ + metadata::{reader::read_tag, MetadataFetch}, + reader::Endianness, + tiff::{tags::Tag, Value}, + }; use bytes::Bytes; use futures::FutureExt; - use super::*; - impl MetadataFetch for Bytes { fn fetch( &self, diff --git a/src/predictor.rs b/src/predictor.rs new file mode 100644 index 0000000..1188735 --- /dev/null +++ b/src/predictor.rs @@ -0,0 +1,507 @@ +use std::collections::HashMap; +use std::fmt::Debug; + +use byteorder::NativeEndian; +use bytes::{Bytes, BytesMut}; +// use tiff::decoder::DecodingResult; + +use crate::{ + error::AsyncTiffResult, reader::Endianness, tiff::tags::Predictor, tile::PredictorInfo, +}; + +/// A registry for reverse predictors +/// +/// Reverse predictors, because they perform the inverse (decoding) operation of prediction +/// +/// +/// +pub struct RevPredictorRegistry(HashMap>); + +impl RevPredictorRegistry { + /// create a new predictor registry with no predictors registered + pub fn new() -> Self { + Self(HashMap::new()) + } +} + +impl AsRef>> for RevPredictorRegistry { + fn as_ref(&self) -> &HashMap> { + &self.0 + } +} + +impl Default for RevPredictorRegistry { + fn default() -> Self { + let mut hmap = HashMap::new(); + hmap.insert(Predictor::None, Box::new(NoPredictor) as _); + hmap.insert(Predictor::Horizontal, Box::new(RevHorizontalPredictor) as _); + hmap.insert( + Predictor::FloatingPoint, + Box::new(RevFloatingPointPredictor) as _, + ); + Self(hmap) + } +} + +/// Trait for reverse predictors to implement +/// +/// +pub trait RevPredictor: Debug + Send + Sync { + /// reverse predict the decompressed bytes and fix endianness on the output + /// + /// + fn rev_predict_fix_endianness( + &self, + buffer: Bytes, + predictor_info: &PredictorInfo, + tile_x: u32, + tile_y: u32, + ) -> AsyncTiffResult; // having this Bytes will give alignment issues later on +} + +/// no predictor +#[derive(Debug)] +pub struct NoPredictor; + +impl RevPredictor for NoPredictor { + fn rev_predict_fix_endianness( + &self, + buffer: Bytes, + predictor_info: &PredictorInfo, + _: u32, + _: u32, + ) -> AsyncTiffResult { + let mut res = BytesMut::from(buffer); + fix_endianness( + &mut res[..], + predictor_info.endianness, + predictor_info.bits_per_sample[0], + ); + Ok(res.into()) + } +} + +/// reverse horizontal predictor +#[derive(Debug)] +pub struct RevHorizontalPredictor; + +impl RevPredictor for RevHorizontalPredictor { + fn rev_predict_fix_endianness( + &self, + buffer: Bytes, + predictor_info: &PredictorInfo, + tile_x: u32, + _: u32, + ) -> 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[0]; + + let mut res = BytesMut::from(buffer); + for buf in res.chunks_mut(output_row_stride) { + // this is rev_hpredict_nsamp from image-tiff + rev_hpredict_nsamp(buf, bit_depth, samples); + // end rev_hpredict_nsamp + } + fix_endianness(&mut res[..], predictor_info.endianness, bit_depth); + Ok(res.into()) + } +} + +/// Reverse predictor convenienve function for horizontal differencing +/// +/// From image-tiff +pub fn rev_hpredict_nsamp(buf: &mut [u8], bit_depth: u16, 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 - 2 * 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 - 4 * 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 - 8 * 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.") + } + } +} + +/// Fix endianness. If `byte_order` matches the host, then conversion is a no-op. +/// +/// from image-tiff +pub fn fix_endianness(buf: &mut [u8], byte_order: Endianness, bit_depth: u16) { + match byte_order { + Endianness::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()) + }), + }, + Endianness::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()) + }), + }, + }; +} + +/// Floating point predictor +#[derive(Debug)] +pub struct RevFloatingPointPredictor; + +impl RevPredictor for RevFloatingPointPredictor { + fn rev_predict_fix_endianness( + &self, + buffer: Bytes, + predictor_info: &PredictorInfo, + tile_x: u32, + tile_y: u32, + ) -> AsyncTiffResult { + let output_row_stride = predictor_info.output_row_stride(tile_x)?; + let mut res: BytesMut = BytesMut::zeroed( + output_row_stride * predictor_info.chunk_height_pixels(tile_y)? as usize, + ); + let bit_depth = predictor_info.bits_per_sample[0] as usize; + 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 + .chunks_mut(output_row_stride) + .zip(res.chunks_mut(output_row_stride)) + { + match bit_depth { + 16 => rev_predict_f16(in_buf, out_buf, predictor_info.samples_per_pixel as _), + 32 => rev_predict_f32(in_buf, out_buf, predictor_info.samples_per_pixel as _), + 64 => rev_predict_f64(in_buf, out_buf, predictor_info.samples_per_pixel as _), + _ => panic!("thou shalt not predict with float16"), + } + } + } 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_pixel() / 8; + for (in_buf, out_buf) in input + .chunks_mut(input_row_stride) + .zip(res.chunks_mut(output_row_stride)) + { + let mut out_row = BytesMut::zeroed(input_row_stride); + match bit_depth { + 16 => rev_predict_f16(in_buf, out_buf, predictor_info.samples_per_pixel as _), + 32 => { + rev_predict_f32(in_buf, &mut out_row, predictor_info.samples_per_pixel as _) + } + 64 => { + rev_predict_f64(in_buf, &mut out_row, predictor_info.samples_per_pixel as _) + } + _ => panic!("thou shalt not predict f16"), + } + out_buf.copy_from_slice(&out_row[..output_row_stride]); + } + } + Ok(res.into()) + } +} + +/// Reverse floating point prediction +/// +/// floating point prediction first shuffles the bytes and then uses horizontal +/// differencing +/// also performs byte-order conversion if needed. +/// +pub fn rev_predict_f16(input: &mut [u8], output: &mut [u8], samples: usize) { + // reverse horizontal differencing + for i in samples..input.len() { + input[i] = input[i].wrapping_add(input[i - samples]); + } + // reverse byte shuffle and fix endianness + for (i, chunk) in output.chunks_mut(2).enumerate() { + chunk.copy_from_slice(&u16::to_ne_bytes( + // convert to native-endian + // preserve original byte-order + u16::from_be_bytes([input[i], input[input.len() / 2 + i]]), + )); + } +} + +/// Reverse floating point prediction +/// +/// floating point prediction first shuffles the bytes and then uses horizontal +/// differencing +/// also performs byte-order conversion if needed. +/// +pub fn rev_predict_f32(input: &mut [u8], output: &mut [u8], samples: usize) { + // reverse horizontal differencing + for i in samples..input.len() { + input[i] = input[i].wrapping_add(input[i - samples]); + } + println!("output: {output:?}, {:?}", output.len()); + // reverse byte shuffle and fix endianness + for (i, chunk) in output.chunks_mut(4).enumerate() { + println!("i:{i:?}"); + chunk.copy_from_slice(&u32::to_ne_bytes( + // convert to native-endian + // preserve original byte-order + u32::from_be_bytes([ + input[i], + input[input.len() / 4 + i], + input[input.len() / 4 * 2 + i], + input[input.len() / 4 * 3 + i], + ]), + )); + } +} + +/// Reverse floating point prediction +/// +/// floating point prediction first shuffles the bytes and then uses horizontal +/// differencing +/// Also fixes byte order if needed (tiff's->native) +pub fn rev_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], + 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], + ]))); + } +} + +#[cfg(test)] +mod test { + use bytes::Bytes; + + use crate::{ + predictor::RevFloatingPointPredictor, + reader::Endianness, + tiff::tags::{PlanarConfiguration, SampleFormat}, + tile::PredictorInfo, + }; + + use super::{NoPredictor, RevHorizontalPredictor, RevPredictor}; + + const PRED_INFO: PredictorInfo = PredictorInfo { + endianness: Endianness::LittleEndian, + image_width: 15, + image_height: 15, + chunk_width: 8, + chunk_height: 8, + bits_per_sample: &[8], + samples_per_pixel: 1, + sample_format: &[SampleFormat::Uint], + planar_configuration: crate::tiff::tags::PlanarConfiguration::Chunky, + }; + #[rustfmt::skip] + const RES: [u8;64] = [ + 0,1,2,3, 4,5,6,7, + 1,0,1,2, 3,4,5,6, + 2,1,0,1, 2,3,4,5, + 3,2,1,0, 1,2,3,4, + + 4,3,2,1, 0,1,2,3, + 5,4,3,2, 1,0,1,2, + 6,5,4,3, 2,1,0,1, + 7,6,5,4, 3,2,1,0, + ]; + #[rustfmt::skip] + const RES_RIGHT: [u8;56] = [ + 0,1,2,3, 4,5,6, + 1,0,1,2, 3,4,5, + 2,1,0,1, 2,3,4, + 3,2,1,0, 1,2,3, + + 4,3,2,1, 0,1,2, + 5,4,3,2, 1,0,1, + 6,5,4,3, 2,1,0, + 7,6,5,4, 3,2,1, + ]; + #[rustfmt::skip] + const RES_BOT: [u8;56] = [ + 0,1,2,3, 4,5,6,7, + 1,0,1,2, 3,4,5,6, + 2,1,0,1, 2,3,4,5, + 3,2,1,0, 1,2,3,4, + + 4,3,2,1, 0,1,2,3, + 5,4,3,2, 1,0,1,2, + 6,5,4,3, 2,1,0,1, + ]; + #[rustfmt::skip] + const RES_BOT_RIGHT: [u8;49] = [ + 0,1,2,3, 4,5,6, + 1,0,1,2, 3,4,5, + 2,1,0,1, 2,3,4, + 3,2,1,0, 1,2,3, + + 4,3,2,1, 0,1,2, + 5,4,3,2, 1,0,1, + 6,5,4,3, 2,1,0, + ]; + + #[rustfmt::skip] + #[test] + fn test_no_predict() { + let p = NoPredictor; + let cases = [ + (0,0, Bytes::from_static(&RES[..]), Bytes::from_static(&RES[..]) ), + (0,1, Bytes::from_static(&RES_BOT[..]), Bytes::from_static(&RES_BOT[..]) ), + (1,0, Bytes::from_static(&RES_RIGHT[..]), Bytes::from_static(&RES_RIGHT[..]) ), + (1,1, Bytes::from_static(&RES_BOT_RIGHT[..]), Bytes::from_static(&RES_BOT_RIGHT[..])) + ]; + for (x,y, input, expected) in cases { + assert_eq!(p.rev_predict_fix_endianness(input, &PRED_INFO, x, y).unwrap(), expected); + } + } + + #[rustfmt::skip] + #[test] + fn test_hpredict() { + let p = RevHorizontalPredictor; + let cases = [ + (0,0, Bytes::from_static(&[ + 0, 1, 1, 1, 1, 1, 1, 1, + 1,255, 1, 1, 1, 1, 1, 1, + 2,255,255, 1, 1, 1, 1, 1, + 3,255,255,255, 1, 1, 1, 1, + + 4,255,255,255, 255, 1, 1, 1, + 5,255,255,255, 255,255, 1, 1, + 6,255,255,255, 255,255,255, 1, + 7,255,255,255, 255,255,255,255, + ]), Bytes::from_static(&RES[..]) ), + (0,1, Bytes::from_static(&[ + 0, 1, 1, 1, 1, 1, 1, 1, + 1,255, 1, 1, 1, 1, 1, 1, + 2,255,255, 1, 1, 1, 1, 1, + 3,255,255,255, 1, 1, 1, 1, + + 4,255,255,255, 255, 1, 1, 1, + 5,255,255,255, 255,255, 1, 1, + 6,255,255,255, 255,255,255, 1, + ]), Bytes::from_static(&RES_BOT[..]) ), + (1,0, Bytes::from_static(&[ + 0, 1, 1, 1, 1, 1, 1, + 1,255, 1, 1, 1, 1, 1, + 2,255,255, 1, 1, 1, 1, + 3,255,255,255, 1, 1, 1, + + 4,255,255,255, 255, 1, 1, + 5,255,255,255, 255,255, 1, + 6,255,255,255, 255,255,255, + 7,255,255,255, 255,255,255, + ]), Bytes::from_static(&RES_RIGHT[..]) ), + (1,1, Bytes::from_static(&[ + 0, 1, 1, 1, 1, 1, 1, + 1,255, 1, 1, 1, 1, 1, + 2,255,255, 1, 1, 1, 1, + 3,255,255,255, 1, 1, 1, + + 4,255,255,255, 255, 1, 1, + 5,255,255,255, 255,255, 1, + 6,255,255,255, 255,255,255, + ]), Bytes::from_static(&RES_BOT_RIGHT[..])) + ]; + for (x,y, input, expected) in cases { + let res = p.rev_predict_fix_endianness(input.clone(), &PRED_INFO, x, y).unwrap(); + println!("testing ({x},{y}): {:?}?={:?}", &res[..], &expected[..]); + assert_eq!(res, expected); + + // also errors shouldn't crash + // p.rev_predict_fix_endianness(input, &PRED_INFO, x+1, y+1).unwrap_err(); + } + } + + // #[rustfmt::skip] + #[test] + fn test_fpredict_f32() { + // let's take this 2-value image + let expected: Vec = [42.0f32, 43.0].iter().flat_map(|f| f.to_le_bytes()).collect(); + assert_eq!(expected, vec![0x0,0x0,0x28,0x42,0x0,0x0,0x2c,0x42]); + let info = PredictorInfo { + endianness: Endianness::LittleEndian, + image_width: 2, + image_height: 2 + 1, + chunk_width: 2, + chunk_height: 2, + bits_per_sample: &[32], + samples_per_pixel: 1, + sample_format: &[SampleFormat::IEEEFP], + planar_configuration: PlanarConfiguration::Chunky, + }; + let input = Bytes::from_static(&[0x42u8, 0, 230, 4, 212, 0, 0, 0]); + assert_eq!( + RevFloatingPointPredictor + .rev_predict_fix_endianness(input, &info, 0, 1) + .unwrap(), + expected + ); + } + + // #[test] + // fn test_fpredict_f64() { + // // let's take this 2-value image + // let expected: Vec = [42.0f64, 43.0].iter().flat_map(|f| f.to_le_bytes()).collect(); + // assert_eq!(expected, vec![0,0,0,0,0,0,69,64,0,0,0,0,0,128,69,64]); + // let info = PredictorInfo { + // endianness: Endianness::LittleEndian, + // image_width: 2, + // image_height: 2 + 1, + // chunk_width: 2, + // chunk_height: 2, + // bits_per_sample: &[64], + // samples_per_pixel: 1, + // sample_format: &[SampleFormat::IEEEFP], + // planar_configuration: PlanarConfiguration::Chunky, + // }; + // let input = Bytes::from_static(&[0x42u8, 0, 230, 4, 212, 0, 0, 0]); + // assert_eq!( + // RevFloatingPointPredictor + // .rev_predict_fix_endianness(input, &info, 0, 1) + // .unwrap(), + // expected + // ); + // } +} diff --git a/src/tiff/error.rs b/src/tiff/error.rs index 2fd70d2..47a6744 100644 --- a/src/tiff/error.rs +++ b/src/tiff/error.rs @@ -9,6 +9,7 @@ use std::sync::Arc; use jpeg::UnsupportedFeature; use super::ifd::Value; +use super::tags::Predictor; use super::tags::{ CompressionMethod, PhotometricInterpretation, PlanarConfiguration, SampleFormat, Tag, }; @@ -152,6 +153,7 @@ pub enum TiffUnsupportedError { UnknownInterpretation, UnknownCompressionMethod, UnsupportedCompressionMethod(CompressionMethod), + UnsupportedPredictor(Predictor), UnsupportedSampleDepth(u8), UnsupportedSampleFormat(Vec), // UnsupportedColorType(ColorType), @@ -193,6 +195,9 @@ impl fmt::Display for TiffUnsupportedError { UnsupportedCompressionMethod(method) => { write!(fmt, "Compression method {:?} is unsupported", method) } + UnsupportedPredictor(p) => { + write!(fmt, "Predictor {p:?} is not supported") + } UnsupportedSampleDepth(samples) => { write!(fmt, "{} samples per pixel is unsupported.", samples) } diff --git a/src/tile.rs b/src/tile.rs index 4b18c80..3eb46cf 100644 --- a/src/tile.rs +++ b/src/tile.rs @@ -2,9 +2,113 @@ use bytes::Bytes; use crate::decoder::DecoderRegistry; use crate::error::AsyncTiffResult; -use crate::tiff::tags::{CompressionMethod, PhotometricInterpretation}; +use crate::predictor::RevPredictorRegistry; +use crate::reader::Endianness; +use crate::tiff::tags::{ + CompressionMethod, PhotometricInterpretation, PlanarConfiguration, Predictor, SampleFormat, +}; use crate::tiff::{TiffError, TiffUnsupportedError}; +/// All info that may be used by a predictor +/// +/// Most of this is used by the floating point predictor +/// since that intermixes padding into the decompressed output +/// +/// Also provides convenience functions +/// +#[derive(Debug, Clone, Copy)] +pub struct PredictorInfo<'a> { + pub endianness: Endianness, + pub image_width: u32, + pub image_height: u32, + pub chunk_width: u32, + pub chunk_height: u32, + pub bits_per_sample: &'a [u16], // maybe say that we only support a single bits_per_sample? + pub samples_per_pixel: u16, + pub sample_format: &'a [SampleFormat], // and a single sample_format? + pub planar_configuration: PlanarConfiguration, +} + +impl PredictorInfo<'_> { + /// chunk height in pixels, taking padding into account + /// + /// strips are considered image-width chunks + /// + /// # Example + /// + /// ```rust + /// let info = PredictorInfo { + /// # endianness: Endianness::LittleEndian, + /// image_width: 15, + /// image_height: 15, + /// chunk_width: 8, + /// chunk_height: 8, + /// # bits_per_sample: &[32], + /// # samples_per_pixel: 1, + /// # sample_format: &[SampleFormat::IEEEFP], + /// # planar_configuration: PlanarConfiguration::Chunky, + /// } + /// + /// assert_eq!(info.chunk_width_pixels(1).unwrap(), (7)) + /// info.chunk_width_pixels(2).unwrap_err() + /// ``` + pub fn chunk_width_pixels(&self, x: u32) -> AsyncTiffResult { + if x >= self.chunks_across() { + return Err(crate::error::AsyncTiffError::TileIndexError( + x, + self.chunks_across(), + )); + } else if x == self.chunks_across() - 1 { + // last chunk + Ok(self.image_width - self.chunk_width * x) + } else { + Ok(self.chunk_width) + } + } + + pub fn chunk_height_pixels(&self, y: u32) -> AsyncTiffResult { + if y >= self.chunks_down() { + Err(crate::error::AsyncTiffError::TileIndexError( + y, + self.chunks_down(), + )) + } else if y == self.chunks_down() - 1 { + // last chunk + Ok(self.image_height - self.chunk_height * y) + } else { + Ok(self.chunk_height) + } + } + + /// get the output row stride in bytes, taking padding into account + pub fn output_row_stride(&self, x: u32) -> AsyncTiffResult { + Ok((self.chunk_width_pixels(x)? as usize).saturating_mul(self.bits_per_pixel()) / 8) + } + + /// The total number of bits per pixel, taking into account possible different sample sizes + /// + /// Technically bits_per_sample.len() should be *equal* to samples, but libtiff also allows + /// it to be a single value that applies to all samples. + /// + /// Libtiff and image-tiff do not support mixed bits per sample, but we give the possibility + pub fn bits_per_pixel(&self) -> usize { + if self.bits_per_sample.len() == 1 { + self.samples_per_pixel as usize * self.bits_per_sample[0] as usize + } else { + assert_eq!(self.samples_per_pixel as usize, self.bits_per_sample.len()); + self.bits_per_sample.iter().map(|v| *v as usize).product() + } + } + + pub fn chunks_across(&self) -> u32 { + self.image_width.div_ceil(self.chunk_width) + } + + pub fn chunks_down(&self) -> u32 { + self.image_height.div_ceil(self.chunk_height) + } +} + /// A TIFF Tile response. /// /// This contains the required information to decode the tile. Decoding is separated from fetching @@ -12,16 +116,18 @@ use crate::tiff::{TiffError, TiffUnsupportedError}; /// /// This is returned by `fetch_tile`. #[derive(Debug)] -pub struct Tile { +pub struct Tile<'a> { pub(crate) x: usize, pub(crate) y: usize, + pub(crate) predictor: Predictor, + pub(crate) predictor_info: PredictorInfo<'a>, pub(crate) compressed_bytes: Bytes, pub(crate) compression_method: CompressionMethod, pub(crate) photometric_interpretation: PhotometricInterpretation, pub(crate) jpeg_tables: Option, } -impl Tile { +impl Tile<'_> { /// The column index of this tile. pub fn x(&self) -> usize { self.x @@ -60,7 +166,11 @@ 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, + predictor_registry: &RevPredictorRegistry, + ) -> AsyncTiffResult { let decoder = decoder_registry .as_ref() .get(&self.compression_method) @@ -68,10 +178,57 @@ impl Tile { TiffUnsupportedError::UnsupportedCompressionMethod(self.compression_method), ))?; - decoder.decode_tile( - self.compressed_bytes.clone(), - self.photometric_interpretation, - self.jpeg_tables.as_deref(), + let predictor = + predictor_registry + .as_ref() + .get(&self.predictor) + .ok_or(TiffError::UnsupportedError( + TiffUnsupportedError::UnsupportedPredictor(self.predictor), + ))?; + + predictor.rev_predict_fix_endianness( + decoder.decode_tile( + self.compressed_bytes.clone(), + self.photometric_interpretation, + self.jpeg_tables.as_deref(), + )?, + &self.predictor_info, + self.x as _, + self.y as _, ) } } + +#[cfg(test)] +mod test { + use crate::{ + reader::Endianness, + tiff::tags::{PlanarConfiguration, SampleFormat}, + }; + + use super::PredictorInfo; + + #[test] + fn test_chunk_width_pixels() { + let info = PredictorInfo { + endianness: Endianness::LittleEndian, + image_width: 15, + image_height: 17, + chunk_width: 8, + chunk_height: 8, + bits_per_sample: &[8], + samples_per_pixel: 1, + sample_format: &[SampleFormat::Uint], + planar_configuration: PlanarConfiguration::Chunky, + }; + assert_eq!(info.bits_per_pixel(), 8); + assert_eq!(info.chunks_across(), 2); + assert_eq!(info.chunks_down(), 3); + assert_eq!(info.chunk_width_pixels(0).unwrap(), info.chunk_width); + assert_eq!(info.chunk_width_pixels(1).unwrap(), 7); + info.chunk_width_pixels(2).unwrap_err(); + assert_eq!(info.chunk_height_pixels(0).unwrap(), info.chunk_height); + assert_eq!(info.chunk_height_pixels(2).unwrap(), 1); + info.chunk_height_pixels(3).unwrap_err(); + } +} From df407babcd2e414bd677b72a031a2458972eb11a Mon Sep 17 00:00:00 2001 From: Fee Fladder Date: Wed, 2 Apr 2025 12:52:40 +0200 Subject: [PATCH 02/23] improved tests, fixed some bugs --- src/predictor.rs | 268 +++++++++++++++++++++++++++++------------------ 1 file changed, 167 insertions(+), 101 deletions(-) diff --git a/src/predictor.rs b/src/predictor.rs index 1188735..3767a46 100644 --- a/src/predictor.rs +++ b/src/predictor.rs @@ -15,7 +15,7 @@ use crate::{ /// /// /// -pub struct RevPredictorRegistry(HashMap>); +pub struct RevPredictorRegistry(HashMap>); impl RevPredictorRegistry { /// create a new predictor registry with no predictors registered @@ -24,8 +24,8 @@ impl RevPredictorRegistry { } } -impl AsRef>> for RevPredictorRegistry { - fn as_ref(&self) -> &HashMap> { +impl AsRef>> for RevPredictorRegistry { + fn as_ref(&self) -> &HashMap> { &self.0 } } @@ -46,7 +46,7 @@ impl Default for RevPredictorRegistry { /// Trait for reverse predictors to implement /// /// -pub trait RevPredictor: Debug + Send + Sync { +pub trait RevPredict: Debug + Send + Sync { /// reverse predict the decompressed bytes and fix endianness on the output /// /// @@ -63,7 +63,7 @@ pub trait RevPredictor: Debug + Send + Sync { #[derive(Debug)] pub struct NoPredictor; -impl RevPredictor for NoPredictor { +impl RevPredict for NoPredictor { fn rev_predict_fix_endianness( &self, buffer: Bytes, @@ -85,7 +85,7 @@ impl RevPredictor for NoPredictor { #[derive(Debug)] pub struct RevHorizontalPredictor; -impl RevPredictor for RevHorizontalPredictor { +impl RevPredict for RevHorizontalPredictor { fn rev_predict_fix_endianness( &self, buffer: Bytes, @@ -98,12 +98,10 @@ impl RevPredictor for RevHorizontalPredictor { let bit_depth = predictor_info.bits_per_sample[0]; let mut res = BytesMut::from(buffer); + fix_endianness(&mut res[..], predictor_info.endianness, bit_depth); for buf in res.chunks_mut(output_row_stride) { - // this is rev_hpredict_nsamp from image-tiff rev_hpredict_nsamp(buf, bit_depth, samples); - // end rev_hpredict_nsamp } - fix_endianness(&mut res[..], predictor_info.endianness, bit_depth); Ok(res.into()) } } @@ -181,7 +179,7 @@ pub fn fix_endianness(buf: &mut [u8], byte_order: Endianness, bit_depth: u16) { #[derive(Debug)] pub struct RevFloatingPointPredictor; -impl RevPredictor for RevFloatingPointPredictor { +impl RevPredict for RevFloatingPointPredictor { fn rev_predict_fix_endianness( &self, buffer: Bytes, @@ -312,6 +310,8 @@ pub fn rev_predict_f64(input: &mut [u8], output: &mut [u8], samples: usize) { #[cfg(test)] mod test { + use std::vec; + use bytes::Bytes; use crate::{ @@ -321,64 +321,48 @@ mod test { tile::PredictorInfo, }; - use super::{NoPredictor, RevHorizontalPredictor, RevPredictor}; + use super::{NoPredictor, RevHorizontalPredictor, RevPredict}; const PRED_INFO: PredictorInfo = PredictorInfo { endianness: Endianness::LittleEndian, - image_width: 15, - image_height: 15, - chunk_width: 8, - chunk_height: 8, + image_width: 7, + image_height: 7, + chunk_width: 4, + chunk_height: 4, bits_per_sample: &[8], samples_per_pixel: 1, sample_format: &[SampleFormat::Uint], planar_configuration: crate::tiff::tags::PlanarConfiguration::Chunky, }; #[rustfmt::skip] - const RES: [u8;64] = [ - 0,1,2,3, 4,5,6,7, - 1,0,1,2, 3,4,5,6, - 2,1,0,1, 2,3,4,5, - 3,2,1,0, 1,2,3,4, - - 4,3,2,1, 0,1,2,3, - 5,4,3,2, 1,0,1,2, - 6,5,4,3, 2,1,0,1, - 7,6,5,4, 3,2,1,0, + const RES: [u8;16] = [ + 0,1, 2,3, + 1,0, 1,2, + + 2,1, 0,1, + 3,2, 1,0, ]; #[rustfmt::skip] - const RES_RIGHT: [u8;56] = [ - 0,1,2,3, 4,5,6, - 1,0,1,2, 3,4,5, - 2,1,0,1, 2,3,4, - 3,2,1,0, 1,2,3, - - 4,3,2,1, 0,1,2, - 5,4,3,2, 1,0,1, - 6,5,4,3, 2,1,0, - 7,6,5,4, 3,2,1, + const RES_RIGHT: [u8;12] = [ + 0,1, 2, + 1,0, 1, + + 2,1, 0, + 3,2, 1, ]; #[rustfmt::skip] - const RES_BOT: [u8;56] = [ - 0,1,2,3, 4,5,6,7, - 1,0,1,2, 3,4,5,6, - 2,1,0,1, 2,3,4,5, - 3,2,1,0, 1,2,3,4, - - 4,3,2,1, 0,1,2,3, - 5,4,3,2, 1,0,1,2, - 6,5,4,3, 2,1,0,1, + const RES_BOT: [u8;12] = [ + 0,1,2, 3, + 1,0,1, 2, + + 2,1,0, 1, ]; #[rustfmt::skip] - const RES_BOT_RIGHT: [u8;49] = [ - 0,1,2,3, 4,5,6, - 1,0,1,2, 3,4,5, - 2,1,0,1, 2,3,4, - 3,2,1,0, 1,2,3, - - 4,3,2,1, 0,1,2, - 5,4,3,2, 1,0,1, - 6,5,4,3, 2,1,0, + const RES_BOT_RIGHT: [u8;9] = [ + 0,1, 2, + 1,0, 1, + + 2,1, 0, ]; #[rustfmt::skip] @@ -400,57 +384,139 @@ mod test { #[test] fn test_hpredict() { let p = RevHorizontalPredictor; + let mut predictor_info = PRED_INFO.clone(); let cases = [ - (0,0, Bytes::from_static(&[ - 0, 1, 1, 1, 1, 1, 1, 1, - 1,255, 1, 1, 1, 1, 1, 1, - 2,255,255, 1, 1, 1, 1, 1, - 3,255,255,255, 1, 1, 1, 1, - - 4,255,255,255, 255, 1, 1, 1, - 5,255,255,255, 255,255, 1, 1, - 6,255,255,255, 255,255,255, 1, - 7,255,255,255, 255,255,255,255, - ]), Bytes::from_static(&RES[..]) ), - (0,1, Bytes::from_static(&[ - 0, 1, 1, 1, 1, 1, 1, 1, - 1,255, 1, 1, 1, 1, 1, 1, - 2,255,255, 1, 1, 1, 1, 1, - 3,255,255,255, 1, 1, 1, 1, - - 4,255,255,255, 255, 1, 1, 1, - 5,255,255,255, 255,255, 1, 1, - 6,255,255,255, 255,255,255, 1, - ]), Bytes::from_static(&RES_BOT[..]) ), - (1,0, Bytes::from_static(&[ - 0, 1, 1, 1, 1, 1, 1, - 1,255, 1, 1, 1, 1, 1, - 2,255,255, 1, 1, 1, 1, - 3,255,255,255, 1, 1, 1, - - 4,255,255,255, 255, 1, 1, - 5,255,255,255, 255,255, 1, - 6,255,255,255, 255,255,255, - 7,255,255,255, 255,255,255, - ]), Bytes::from_static(&RES_RIGHT[..]) ), - (1,1, Bytes::from_static(&[ - 0, 1, 1, 1, 1, 1, 1, - 1,255, 1, 1, 1, 1, 1, - 2,255,255, 1, 1, 1, 1, - 3,255,255,255, 1, 1, 1, - - 4,255,255,255, 255, 1, 1, - 5,255,255,255, 255,255, 1, - 6,255,255,255, 255,255,255, - ]), Bytes::from_static(&RES_BOT_RIGHT[..])) + (0,0, vec![ + 0i32, 1, 1, 1, + 1,-1, 1, 1, + 2,-1,-1, 1, + 3,-1,-1,-1, + ], Vec::from(&RES[..])), + (0,1, vec![ + 0, 1, 1, 1, + 1,-1, 1, 1, + 2,-1,-1, 1, + ], Vec::from(&RES_BOT[..])), + (1,0, vec![ + 0, 1, 1, + 1,-1, 1, + 2,-1,-1, + 3,-1,-1, + ], Vec::from(&RES_RIGHT[..])), + (1,1, vec![ + 0, 1, 1, + 1,-1, 1, + 2,-1,-1, + ], Vec::from(&RES_BOT_RIGHT[..])), ]; for (x,y, input, expected) in cases { - let res = p.rev_predict_fix_endianness(input.clone(), &PRED_INFO, x, y).unwrap(); - println!("testing ({x},{y}): {:?}?={:?}", &res[..], &expected[..]); - assert_eq!(res, expected); - - // also errors shouldn't crash - // p.rev_predict_fix_endianness(input, &PRED_INFO, x+1, y+1).unwrap_err(); + println!("uints littleendian"); + predictor_info.endianness = Endianness::LittleEndian; + predictor_info.sample_format = &[SampleFormat::Uint]; + 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!(p.rev_predict_fix_endianness(buffer, &predictor_info, x, y).unwrap(), res); + 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!(p.rev_predict_fix_endianness(buffer, &predictor_info, x, y).unwrap(), res); + 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!(p.rev_predict_fix_endianness(buffer, &predictor_info, x, y).unwrap(), res); + 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!(p.rev_predict_fix_endianness(buffer, &predictor_info, x, y).unwrap(), res); + + println!("ints littleendian"); + predictor_info.sample_format = &[SampleFormat::Int]; + 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!(p.rev_predict_fix_endianness(buffer, &predictor_info, x, y).unwrap()[..], res[..]); + 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!(p.rev_predict_fix_endianness(buffer, &predictor_info, x, y).unwrap(), res); + println!("testing i32"); + predictor_info.bits_per_sample = &[32]; + let buffer = Bytes::from(input.iter().flat_map(|v| (*v as i32).to_le_bytes()).collect::>()); + let res = Bytes::from(expected.iter().flat_map(|v| (*v as i32).to_ne_bytes()).collect::>()); + assert_eq!(p.rev_predict_fix_endianness(buffer, &predictor_info, x, y).unwrap(), res); + 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!(p.rev_predict_fix_endianness(buffer, &predictor_info, x, y).unwrap(), res); + + println!("uints bigendian"); + predictor_info.endianness = Endianness::BigEndian; + predictor_info.sample_format = &[SampleFormat::Uint]; + 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!(p.rev_predict_fix_endianness(buffer, &predictor_info, x, y).unwrap(), res); + 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!(p.rev_predict_fix_endianness(buffer, &predictor_info, x, y).unwrap()[..], res[..]); + 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!(p.rev_predict_fix_endianness(buffer, &predictor_info, x, y).unwrap(), res); + 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!(p.rev_predict_fix_endianness(buffer, &predictor_info, x, y).unwrap(), res); + + println!("ints bigendian"); + predictor_info.sample_format = &[SampleFormat::Int]; + 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!(p.rev_predict_fix_endianness(buffer, &predictor_info, x, y).unwrap(), res); + 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!(p.rev_predict_fix_endianness(buffer, &predictor_info, x, y).unwrap(), res); + 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 as i32).to_be_bytes()).collect::>()); + let res = Bytes::from(expected.iter().flat_map(|v| (*v as i32).to_ne_bytes()).collect::>()); + assert_eq!(p.rev_predict_fix_endianness(buffer, &predictor_info, x, y).unwrap(), res); + 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!(p.rev_predict_fix_endianness(buffer, &predictor_info, x, y).unwrap(), res); } } From c37e067ec92671d1759b4807acf3d29f531dfcbf Mon Sep 17 00:00:00 2001 From: Fee Fladder Date: Wed, 2 Apr 2025 13:11:16 +0200 Subject: [PATCH 03/23] added docs+doctests --- src/lib.rs | 2 +- src/predictor.rs | 9 ++++-- src/tile.rs | 71 ++++++++++++++++++++++++++++++++++++++++++------ 3 files changed, 69 insertions(+), 13 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 6c9d7de..8b1aca0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,4 +15,4 @@ mod tile; pub use cog::TIFF; pub use ifd::ImageFileDirectory; -pub use tile::Tile; +pub use tile::{PredictorInfo, Tile}; diff --git a/src/predictor.rs b/src/predictor.rs index 3767a46..9e21a68 100644 --- a/src/predictor.rs +++ b/src/predictor.rs @@ -1,7 +1,7 @@ +//! Predictors for no predictor, horizontal and floating-point use std::collections::HashMap; use std::fmt::Debug; -use byteorder::NativeEndian; use bytes::{Bytes, BytesMut}; // use tiff::decoder::DecodingResult; @@ -524,8 +524,11 @@ mod test { #[test] fn test_fpredict_f32() { // let's take this 2-value image - let expected: Vec = [42.0f32, 43.0].iter().flat_map(|f| f.to_le_bytes()).collect(); - assert_eq!(expected, vec![0x0,0x0,0x28,0x42,0x0,0x0,0x2c,0x42]); + let expected: Vec = [42.0f32, 43.0] + .iter() + .flat_map(|f| f.to_le_bytes()) + .collect(); + assert_eq!(expected, vec![0x0, 0x0, 0x28, 0x42, 0x0, 0x0, 0x2c, 0x42]); let info = PredictorInfo { endianness: Endianness::LittleEndian, image_width: 2, diff --git a/src/tile.rs b/src/tile.rs index 3eb46cf..a880128 100644 --- a/src/tile.rs +++ b/src/tile.rs @@ -18,25 +18,45 @@ use crate::tiff::{TiffError, TiffUnsupportedError}; /// #[derive(Debug, Clone, Copy)] pub struct PredictorInfo<'a> { + /// endianness pub endianness: Endianness, + /// width of the image in pixels pub image_width: u32, + /// height of the image in pixels pub image_height: u32, + /// chunk width in pixels + /// + /// If this is a stripped tiff, `chunk_width=image_width` pub chunk_width: u32, + /// chunk height in pixels pub chunk_height: u32, + /// bits per sample, as an array + /// + /// Can also be a single value, in which case it applies to all samples pub bits_per_sample: &'a [u16], // maybe say that we only support a single bits_per_sample? + /// number of samples per pixel pub samples_per_pixel: u16, + /// sample format for each sample + /// + /// There is no decoding implementation in this crate (or libtiff) for mixed sample formats pub sample_format: &'a [SampleFormat], // and a single sample_format? + /// planar configuration + /// + /// determines the bits per pixel pub planar_configuration: PlanarConfiguration, } impl PredictorInfo<'_> { - /// chunk height in pixels, taking padding into account + /// chunk width in pixels, taking padding into account /// /// strips are considered image-width chunks /// /// # Example /// /// ```rust + /// # use async_tiff::tiff::tags::{SampleFormat, PlanarConfiguration}; + /// # use async_tiff::reader::Endianness; + /// # use async_tiff::PredictorInfo; /// let info = PredictorInfo { /// # endianness: Endianness::LittleEndian, /// image_width: 15, @@ -47,10 +67,10 @@ impl PredictorInfo<'_> { /// # samples_per_pixel: 1, /// # sample_format: &[SampleFormat::IEEEFP], /// # planar_configuration: PlanarConfiguration::Chunky, - /// } + /// }; /// - /// assert_eq!(info.chunk_width_pixels(1).unwrap(), (7)) - /// info.chunk_width_pixels(2).unwrap_err() + /// assert_eq!(info.chunk_width_pixels(1).unwrap(), (7)); + /// info.chunk_width_pixels(2).unwrap_err(); /// ``` pub fn chunk_width_pixels(&self, x: u32) -> AsyncTiffResult { if x >= self.chunks_across() { @@ -66,6 +86,31 @@ impl PredictorInfo<'_> { } } + /// chunk height in pixels, taking padding into account + /// + /// strips are considered image-width chunks + /// + /// # Example + /// + /// ```rust + /// # use async_tiff::tiff::tags::{SampleFormat, PlanarConfiguration}; + /// # use async_tiff::reader::Endianness; + /// # use async_tiff::PredictorInfo; + /// let info = PredictorInfo { + /// # endianness: Endianness::LittleEndian, + /// image_width: 15, + /// image_height: 15, + /// chunk_width: 8, + /// chunk_height: 8, + /// # bits_per_sample: &[32], + /// # samples_per_pixel: 1, + /// # sample_format: &[SampleFormat::IEEEFP], + /// # planar_configuration: PlanarConfiguration::Chunky, + /// }; + /// + /// assert_eq!(info.chunk_height_pixels(1).unwrap(), (7)); + /// info.chunk_height_pixels(2).unwrap_err(); + /// ``` pub fn chunk_height_pixels(&self, y: u32) -> AsyncTiffResult { if y >= self.chunks_down() { Err(crate::error::AsyncTiffError::TileIndexError( @@ -91,19 +136,27 @@ impl PredictorInfo<'_> { /// it to be a single value that applies to all samples. /// /// Libtiff and image-tiff do not support mixed bits per sample, but we give the possibility + /// unless you also have PlanarConfiguration::Planar, at which point the first is taken pub fn bits_per_pixel(&self) -> usize { - if self.bits_per_sample.len() == 1 { - self.samples_per_pixel as usize * self.bits_per_sample[0] as usize - } else { - assert_eq!(self.samples_per_pixel as usize, self.bits_per_sample.len()); - self.bits_per_sample.iter().map(|v| *v as usize).product() + match self.planar_configuration { + PlanarConfiguration::Chunky => { + if self.bits_per_sample.len() == 1 { + self.samples_per_pixel as usize * self.bits_per_sample[0] as usize + } else { + assert_eq!(self.samples_per_pixel as usize, self.bits_per_sample.len()); + self.bits_per_sample.iter().map(|v| *v as usize).product() + } + } + PlanarConfiguration::Planar => self.bits_per_sample[0] as usize, } } + /// The number of chunks in the horizontal (x) direction pub fn chunks_across(&self) -> u32 { self.image_width.div_ceil(self.chunk_width) } + /// The number of chunks in the vertical (y) direction pub fn chunks_down(&self) -> u32 { self.image_height.div_ceil(self.chunk_height) } From 9f704d8b8a445dbb276c27c559daef45953fc000 Mon Sep 17 00:00:00 2001 From: Fee Fladder Date: Wed, 2 Apr 2025 14:04:35 +0200 Subject: [PATCH 04/23] fixed clippy, doctest improvements --- src/tile.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/tile.rs b/src/tile.rs index a880128..f54630c 100644 --- a/src/tile.rs +++ b/src/tile.rs @@ -69,15 +69,16 @@ impl PredictorInfo<'_> { /// # planar_configuration: PlanarConfiguration::Chunky, /// }; /// + /// assert_eq!(info.chunk_width_pixels(0).unwrap(), (8)); /// assert_eq!(info.chunk_width_pixels(1).unwrap(), (7)); /// info.chunk_width_pixels(2).unwrap_err(); /// ``` pub fn chunk_width_pixels(&self, x: u32) -> AsyncTiffResult { if x >= self.chunks_across() { - return Err(crate::error::AsyncTiffError::TileIndexError( + Err(crate::error::AsyncTiffError::TileIndexError( x, self.chunks_across(), - )); + )) } else if x == self.chunks_across() - 1 { // last chunk Ok(self.image_width - self.chunk_width * x) @@ -108,6 +109,7 @@ impl PredictorInfo<'_> { /// # planar_configuration: PlanarConfiguration::Chunky, /// }; /// + /// assert_eq!(info.chunk_height_pixels(0).unwrap(), (8)); /// assert_eq!(info.chunk_height_pixels(1).unwrap(), (7)); /// info.chunk_height_pixels(2).unwrap_err(); /// ``` From 6c9a8cf191f812f64944ea5cd5fd4fddc87e5188 Mon Sep 17 00:00:00 2001 From: Fee Fladder Date: Wed, 2 Apr 2025 16:20:23 +0200 Subject: [PATCH 05/23] added padding test, fixed corresponding bugs, removed printlns --- src/predictor.rs | 211 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 147 insertions(+), 64 deletions(-) diff --git a/src/predictor.rs b/src/predictor.rs index 9e21a68..f52ad49 100644 --- a/src/predictor.rs +++ b/src/predictor.rs @@ -109,6 +109,8 @@ impl RevPredict for RevHorizontalPredictor { /// Reverse predictor convenienve function for horizontal differencing /// /// From image-tiff +/// +/// This should be used _after_ endianness fixing pub fn rev_hpredict_nsamp(buf: &mut [u8], bit_depth: u16, samples: usize) { match bit_depth { 0..=8 => { @@ -203,7 +205,7 @@ impl RevPredict for RevFloatingPointPredictor { 16 => rev_predict_f16(in_buf, out_buf, predictor_info.samples_per_pixel as _), 32 => rev_predict_f32(in_buf, out_buf, predictor_info.samples_per_pixel as _), 64 => rev_predict_f64(in_buf, out_buf, predictor_info.samples_per_pixel as _), - _ => panic!("thou shalt not predict with float16"), + _ => panic!("thou shalt not predict f24"), } } } else { @@ -219,15 +221,18 @@ impl RevPredict for RevFloatingPointPredictor { { let mut out_row = BytesMut::zeroed(input_row_stride); match bit_depth { - 16 => rev_predict_f16(in_buf, out_buf, predictor_info.samples_per_pixel as _), + 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 _) } 64 => { rev_predict_f64(in_buf, &mut out_row, predictor_info.samples_per_pixel as _) } - _ => panic!("thou shalt not predict f16"), + _ => panic!("thou shalt not predict f24"), } + // remove the padding bytes out_buf.copy_from_slice(&out_row[..output_row_stride]); } } @@ -250,7 +255,7 @@ pub fn rev_predict_f16(input: &mut [u8], output: &mut [u8], samples: usize) { for (i, chunk) in output.chunks_mut(2).enumerate() { chunk.copy_from_slice(&u16::to_ne_bytes( // convert to native-endian - // preserve original byte-order + // floating predictor is be-like u16::from_be_bytes([input[i], input[input.len() / 2 + i]]), )); } @@ -267,20 +272,20 @@ pub fn rev_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]); } - println!("output: {output:?}, {:?}", output.len()); // reverse byte shuffle and fix endianness for (i, chunk) in output.chunks_mut(4).enumerate() { - println!("i:{i:?}"); - chunk.copy_from_slice(&u32::to_ne_bytes( + chunk.copy_from_slice( // convert to native-endian - // preserve original byte-order - u32::from_be_bytes([ - input[i], - input[input.len() / 4 + i], - input[input.len() / 4 * 2 + i], - input[input.len() / 4 * 3 + i], - ]), - )); + &u32::to_ne_bytes( + // floating predictor is be-like + u32::from_be_bytes([ + input[i], + input[input.len() / 4 + i], + input[input.len() / 4 * 2 + i], + input[input.len() / 4 * 3 + i], + ]), + ), + ); } } @@ -295,16 +300,22 @@ pub fn rev_predict_f64(input: &mut [u8], output: &mut [u8], samples: usize) { } 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], - 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], - ]))); + chunk.copy_from_slice( + // convert to native-endian + &u64::to_ne_bytes( + // floating predictor is be-like + u64::from_be_bytes([ + input[i], + input[input.len() / 8 + 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], + ]), + ), + ); } } @@ -384,7 +395,7 @@ mod test { #[test] fn test_hpredict() { let p = RevHorizontalPredictor; - let mut predictor_info = PRED_INFO.clone(); + let mut predictor_info = PRED_INFO; let cases = [ (0,0, vec![ 0i32, 1, 1, 1, @@ -453,7 +464,7 @@ mod test { assert_eq!(p.rev_predict_fix_endianness(buffer, &predictor_info, x, y).unwrap(), res); println!("testing i32"); predictor_info.bits_per_sample = &[32]; - let buffer = Bytes::from(input.iter().flat_map(|v| (*v as i32).to_le_bytes()).collect::>()); + 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!(p.rev_predict_fix_endianness(buffer, &predictor_info, x, y).unwrap(), res); println!("testing i64"); @@ -508,7 +519,7 @@ mod test { 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 as i32).to_be_bytes()).collect::>()); + 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!(p.rev_predict_fix_endianness(buffer, &predictor_info, x, y).unwrap(), res); assert_eq!(-1i32 as u64, u64::MAX); @@ -520,57 +531,129 @@ mod test { } } - // #[rustfmt::skip] + #[rustfmt::skip] + #[test] + fn test_predict_f16() { + // take a 4-value image + let expect_le = [1,0,3,2,5,4,7,6u8]; + let _expected = [0,1,2,3,4,5,6,7u8]; + // 0 1 + // 0 1 + // 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 info = PredictorInfo { + endianness: Endianness::LittleEndian, + image_width: 4+4, + image_height: 4+1, + chunk_width: 4, + 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!( + &RevFloatingPointPredictor.rev_predict_fix_endianness(input, &info, 1, 1).unwrap()[..], + &expect_le[..] + ) + } + + #[rustfmt::skip] + #[test] + fn test_predict_f16_padding() { + // take a 4-pixel image with 2 padding pixels + let expect_le = [1,0,3,2u8]; // no padding + let _expected = [0,1,2,3,0,0,0,0u8]; //padding added + // 0 1 + // 0 1 + // 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 info = PredictorInfo { + endianness: Endianness::LittleEndian, + image_width: 4+2, + image_height: 4+1, + chunk_width: 4, + 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!( + &RevFloatingPointPredictor.rev_predict_fix_endianness(input, &info, 1, 1).unwrap()[..], + &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 _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]; + println!("expected: {expect_le:?}"); + let mut info = PredictorInfo { + endianness: Endianness::LittleEndian, + image_width: 2, + image_height: 2 + 1, + chunk_width: 2, + 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!( + &RevFloatingPointPredictor + .rev_predict_fix_endianness(input.clone(), &info, 0, 1).unwrap()[..], + &expect_le + ); + info.endianness = Endianness::BigEndian; + assert_eq!( + &RevFloatingPointPredictor.rev_predict_fix_endianness(input, &info, 0, 1).unwrap()[..], + &expect_le + ) + } + + #[rustfmt::skip] + #[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 expected: Vec = [42.0f32, 43.0] - .iter() - .flat_map(|f| f.to_le_bytes()) - .collect(); - assert_eq!(expected, vec![0x0, 0x0, 0x28, 0x42, 0x0, 0x0, 0x2c, 0x42]); + let expect_be = [7,6,5,4,3, 2,1, 0,15,14,13,12,11,10,9,8]; + 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 info = PredictorInfo { endianness: Endianness::LittleEndian, image_width: 2, image_height: 2 + 1, chunk_width: 2, chunk_height: 2, - bits_per_sample: &[32], + bits_per_sample: &[64], samples_per_pixel: 1, sample_format: &[SampleFormat::IEEEFP], planar_configuration: PlanarConfiguration::Chunky, }; - let input = Bytes::from_static(&[0x42u8, 0, 230, 4, 212, 0, 0, 0]); + let input = Bytes::from_owner(diffed); assert_eq!( - RevFloatingPointPredictor + &RevFloatingPointPredictor .rev_predict_fix_endianness(input, &info, 0, 1) - .unwrap(), - expected + .unwrap()[..], + &expect_be[..] ); } - - // #[test] - // fn test_fpredict_f64() { - // // let's take this 2-value image - // let expected: Vec = [42.0f64, 43.0].iter().flat_map(|f| f.to_le_bytes()).collect(); - // assert_eq!(expected, vec![0,0,0,0,0,0,69,64,0,0,0,0,0,128,69,64]); - // let info = PredictorInfo { - // endianness: Endianness::LittleEndian, - // image_width: 2, - // image_height: 2 + 1, - // chunk_width: 2, - // chunk_height: 2, - // bits_per_sample: &[64], - // samples_per_pixel: 1, - // sample_format: &[SampleFormat::IEEEFP], - // planar_configuration: PlanarConfiguration::Chunky, - // }; - // let input = Bytes::from_static(&[0x42u8, 0, 230, 4, 212, 0, 0, 0]); - // assert_eq!( - // RevFloatingPointPredictor - // .rev_predict_fix_endianness(input, &info, 0, 1) - // .unwrap(), - // expected - // ); - // } } From a3291b02ba45c69962e4bc13dcc69f9e51b48685 Mon Sep 17 00:00:00 2001 From: Kyle Barron Date: Wed, 2 Apr 2025 15:16:01 -0400 Subject: [PATCH 06/23] Remove registry --- src/cog.rs | 6 +--- src/predictor.rs | 75 +++++++++++------------------------------------- src/tile.rs | 50 +++++++++++++++++--------------- 3 files changed, 45 insertions(+), 86 deletions(-) diff --git a/src/cog.rs b/src/cog.rs index 72ca619..99a0f14 100644 --- a/src/cog.rs +++ b/src/cog.rs @@ -23,9 +23,7 @@ mod test { use std::io::BufReader; use std::sync::Arc; - use crate::decoder::DecoderRegistry; use crate::metadata::{PrefetchBuffer, TiffMetadataReader}; - use crate::predictor::RevPredictorRegistry; use crate::reader::{AsyncFileReader, ObjectReader}; use super::*; @@ -52,10 +50,8 @@ mod test { let tiff = TIFF::new(ifds); let ifd = &tiff.ifds[1]; - let decoder_registry = DecoderRegistry::default(); - let predictor_registry = RevPredictorRegistry::default(); let tile = ifd.fetch_tile(0, 0, reader.as_ref()).await.unwrap(); - let tile = tile.decode(&decoder_registry, &predictor_registry).unwrap(); + let tile = tile.decode(&Default::default()).unwrap(); std::fs::write("img.buf", tile).unwrap(); } diff --git a/src/predictor.rs b/src/predictor.rs index f52ad49..2fbd697 100644 --- a/src/predictor.rs +++ b/src/predictor.rs @@ -1,55 +1,14 @@ //! Predictors for no predictor, horizontal and floating-point -use std::collections::HashMap; use std::fmt::Debug; use bytes::{Bytes, BytesMut}; // use tiff::decoder::DecodingResult; -use crate::{ - error::AsyncTiffResult, reader::Endianness, tiff::tags::Predictor, tile::PredictorInfo, -}; - -/// A registry for reverse predictors -/// -/// Reverse predictors, because they perform the inverse (decoding) operation of prediction -/// -/// -/// -pub struct RevPredictorRegistry(HashMap>); - -impl RevPredictorRegistry { - /// create a new predictor registry with no predictors registered - pub fn new() -> Self { - Self(HashMap::new()) - } -} - -impl AsRef>> for RevPredictorRegistry { - fn as_ref(&self) -> &HashMap> { - &self.0 - } -} - -impl Default for RevPredictorRegistry { - fn default() -> Self { - let mut hmap = HashMap::new(); - hmap.insert(Predictor::None, Box::new(NoPredictor) as _); - hmap.insert(Predictor::Horizontal, Box::new(RevHorizontalPredictor) as _); - hmap.insert( - Predictor::FloatingPoint, - Box::new(RevFloatingPointPredictor) as _, - ); - Self(hmap) - } -} +use crate::{error::AsyncTiffResult, reader::Endianness, tile::PredictorInfo}; /// Trait for reverse predictors to implement -/// -/// pub trait RevPredict: Debug + Send + Sync { /// reverse predict the decompressed bytes and fix endianness on the output - /// - /// fn rev_predict_fix_endianness( &self, buffer: Bytes, @@ -83,9 +42,9 @@ impl RevPredict for NoPredictor { /// reverse horizontal predictor #[derive(Debug)] -pub struct RevHorizontalPredictor; +pub struct HorizontalPredictor; -impl RevPredict for RevHorizontalPredictor { +impl RevPredict for HorizontalPredictor { fn rev_predict_fix_endianness( &self, buffer: Bytes, @@ -179,9 +138,9 @@ pub fn fix_endianness(buf: &mut [u8], byte_order: Endianness, bit_depth: u16) { /// Floating point predictor #[derive(Debug)] -pub struct RevFloatingPointPredictor; +pub struct FloatingPointPredictor; -impl RevPredict for RevFloatingPointPredictor { +impl RevPredict for FloatingPointPredictor { fn rev_predict_fix_endianness( &self, buffer: Bytes, @@ -243,7 +202,7 @@ impl RevPredict for RevFloatingPointPredictor { /// Reverse floating point prediction /// /// floating point prediction first shuffles the bytes and then uses horizontal -/// differencing +/// differencing /// also performs byte-order conversion if needed. /// pub fn rev_predict_f16(input: &mut [u8], output: &mut [u8], samples: usize) { @@ -264,7 +223,7 @@ pub fn rev_predict_f16(input: &mut [u8], output: &mut [u8], samples: usize) { /// Reverse floating point prediction /// /// floating point prediction first shuffles the bytes and then uses horizontal -/// differencing +/// differencing /// also performs byte-order conversion if needed. /// pub fn rev_predict_f32(input: &mut [u8], output: &mut [u8], samples: usize) { @@ -292,7 +251,7 @@ pub fn rev_predict_f32(input: &mut [u8], output: &mut [u8], samples: usize) { /// Reverse floating point prediction /// /// floating point prediction first shuffles the bytes and then uses horizontal -/// differencing +/// differencing /// Also fixes byte order if needed (tiff's->native) pub fn rev_predict_f64(input: &mut [u8], output: &mut [u8], samples: usize) { for i in samples..input.len() { @@ -326,13 +285,13 @@ mod test { use bytes::Bytes; use crate::{ - predictor::RevFloatingPointPredictor, + predictor::FloatingPointPredictor, reader::Endianness, tiff::tags::{PlanarConfiguration, SampleFormat}, tile::PredictorInfo, }; - use super::{NoPredictor, RevHorizontalPredictor, RevPredict}; + use super::{HorizontalPredictor, NoPredictor, RevPredict}; const PRED_INFO: PredictorInfo = PredictorInfo { endianness: Endianness::LittleEndian, @@ -394,7 +353,7 @@ mod test { #[rustfmt::skip] #[test] fn test_hpredict() { - let p = RevHorizontalPredictor; + let p = HorizontalPredictor; let mut predictor_info = PRED_INFO; let cases = [ (0,0, vec![ @@ -541,7 +500,7 @@ mod test { // 0 1 // 0 1 // 0 1 - let _shuffled = [0,2,4,6,1,3,5,7u8]; + let _shuffled = [0,2,4,6,1,3,5,7u8]; let diffed = [0,2,2,2,251,2,2,2]; let info = PredictorInfo { endianness: Endianness::LittleEndian, @@ -556,7 +515,7 @@ mod test { }; let input = Bytes::from_owner(diffed); assert_eq!( - &RevFloatingPointPredictor.rev_predict_fix_endianness(input, &info, 1, 1).unwrap()[..], + &FloatingPointPredictor.rev_predict_fix_endianness(input, &info, 1, 1).unwrap()[..], &expect_le[..] ) } @@ -586,7 +545,7 @@ mod test { }; let input = Bytes::from_owner(diffed); assert_eq!( - &RevFloatingPointPredictor.rev_predict_fix_endianness(input, &info, 1, 1).unwrap()[..], + &FloatingPointPredictor.rev_predict_fix_endianness(input, &info, 1, 1).unwrap()[..], &expect_le[..] ) } @@ -615,13 +574,13 @@ mod test { }; let input = Bytes::from_owner(diffed); assert_eq!( - &RevFloatingPointPredictor + &FloatingPointPredictor .rev_predict_fix_endianness(input.clone(), &info, 0, 1).unwrap()[..], &expect_le ); info.endianness = Endianness::BigEndian; assert_eq!( - &RevFloatingPointPredictor.rev_predict_fix_endianness(input, &info, 0, 1).unwrap()[..], + &FloatingPointPredictor.rev_predict_fix_endianness(input, &info, 0, 1).unwrap()[..], &expect_le ) } @@ -650,7 +609,7 @@ mod test { }; let input = Bytes::from_owner(diffed); assert_eq!( - &RevFloatingPointPredictor + &FloatingPointPredictor .rev_predict_fix_endianness(input, &info, 0, 1) .unwrap()[..], &expect_be[..] diff --git a/src/tile.rs b/src/tile.rs index f54630c..95d628d 100644 --- a/src/tile.rs +++ b/src/tile.rs @@ -2,7 +2,7 @@ use bytes::Bytes; use crate::decoder::DecoderRegistry; use crate::error::AsyncTiffResult; -use crate::predictor::RevPredictorRegistry; +use crate::predictor::{FloatingPointPredictor, HorizontalPredictor, NoPredictor, RevPredict}; use crate::reader::Endianness; use crate::tiff::tags::{ CompressionMethod, PhotometricInterpretation, PlanarConfiguration, Predictor, SampleFormat, @@ -221,11 +221,7 @@ 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, - predictor_registry: &RevPredictorRegistry, - ) -> AsyncTiffResult { + pub fn decode(self, decoder_registry: &DecoderRegistry) -> AsyncTiffResult { let decoder = decoder_registry .as_ref() .get(&self.compression_method) @@ -233,24 +229,32 @@ impl Tile<'_> { TiffUnsupportedError::UnsupportedCompressionMethod(self.compression_method), ))?; - let predictor = - predictor_registry - .as_ref() - .get(&self.predictor) - .ok_or(TiffError::UnsupportedError( - TiffUnsupportedError::UnsupportedPredictor(self.predictor), - ))?; + let decoded_tile = decoder.decode_tile( + self.compressed_bytes.clone(), + self.photometric_interpretation, + self.jpeg_tables.as_deref(), + )?; - predictor.rev_predict_fix_endianness( - decoder.decode_tile( - self.compressed_bytes.clone(), - self.photometric_interpretation, - self.jpeg_tables.as_deref(), - )?, - &self.predictor_info, - self.x as _, - self.y as _, - ) + match self.predictor { + Predictor::None => NoPredictor.rev_predict_fix_endianness( + decoded_tile, + &self.predictor_info, + self.x as _, + self.y as _, + ), + Predictor::Horizontal => HorizontalPredictor.rev_predict_fix_endianness( + decoded_tile, + &self.predictor_info, + self.x as _, + self.y as _, + ), + Predictor::FloatingPoint => FloatingPointPredictor.rev_predict_fix_endianness( + decoded_tile, + &self.predictor_info, + self.x as _, + self.y as _, + ), + } } } From b3640c0a72ec588939771c1f235b43eb64f8480f Mon Sep 17 00:00:00 2001 From: Kyle Barron Date: Wed, 2 Apr 2025 15:56:29 -0400 Subject: [PATCH 07/23] Rename to Unpredict --- src/predictor.rs | 63 ++++++++++++++++++++++++------------------------ src/tile.rs | 8 +++--- 2 files changed, 35 insertions(+), 36 deletions(-) diff --git a/src/predictor.rs b/src/predictor.rs index 2fbd697..57ecbfa 100644 --- a/src/predictor.rs +++ b/src/predictor.rs @@ -7,9 +7,9 @@ use bytes::{Bytes, BytesMut}; use crate::{error::AsyncTiffResult, reader::Endianness, tile::PredictorInfo}; /// Trait for reverse predictors to implement -pub trait RevPredict: Debug + Send + Sync { +pub trait Unpredict: Debug + Send + Sync { /// reverse predict the decompressed bytes and fix endianness on the output - fn rev_predict_fix_endianness( + fn fix_endianness_and_unpredict( &self, buffer: Bytes, predictor_info: &PredictorInfo, @@ -22,8 +22,8 @@ pub trait RevPredict: Debug + Send + Sync { #[derive(Debug)] pub struct NoPredictor; -impl RevPredict for NoPredictor { - fn rev_predict_fix_endianness( +impl Unpredict for NoPredictor { + fn fix_endianness_and_unpredict( &self, buffer: Bytes, predictor_info: &PredictorInfo, @@ -44,8 +44,8 @@ impl RevPredict for NoPredictor { #[derive(Debug)] pub struct HorizontalPredictor; -impl RevPredict for HorizontalPredictor { - fn rev_predict_fix_endianness( +impl Unpredict for HorizontalPredictor { + fn fix_endianness_and_unpredict( &self, buffer: Bytes, predictor_info: &PredictorInfo, @@ -140,8 +140,8 @@ pub fn fix_endianness(buf: &mut [u8], byte_order: Endianness, bit_depth: u16) { #[derive(Debug)] pub struct FloatingPointPredictor; -impl RevPredict for FloatingPointPredictor { - fn rev_predict_fix_endianness( +impl Unpredict for FloatingPointPredictor { + fn fix_endianness_and_unpredict( &self, buffer: Bytes, predictor_info: &PredictorInfo, @@ -291,7 +291,7 @@ mod test { tile::PredictorInfo, }; - use super::{HorizontalPredictor, NoPredictor, RevPredict}; + use super::{HorizontalPredictor, NoPredictor, Unpredict}; const PRED_INFO: PredictorInfo = PredictorInfo { endianness: Endianness::LittleEndian, @@ -346,7 +346,7 @@ mod test { (1,1, Bytes::from_static(&RES_BOT_RIGHT[..]), Bytes::from_static(&RES_BOT_RIGHT[..])) ]; for (x,y, input, expected) in cases { - assert_eq!(p.rev_predict_fix_endianness(input, &PRED_INFO, x, y).unwrap(), expected); + assert_eq!(p.fix_endianness_and_unpredict(input, &PRED_INFO, x, y).unwrap(), expected); } } @@ -388,25 +388,25 @@ mod test { println!("testing u8"); let buffer = Bytes::from(input.iter().map(|v| *v as u8).collect::>()); let res = Bytes::from(expected.clone()); - assert_eq!(p.rev_predict_fix_endianness(buffer, &predictor_info, x, y).unwrap(), res); + assert_eq!(p.fix_endianness_and_unpredict(buffer, &predictor_info, x, y).unwrap(), res); 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!(p.rev_predict_fix_endianness(buffer, &predictor_info, x, y).unwrap(), res); + assert_eq!(p.fix_endianness_and_unpredict(buffer, &predictor_info, x, y).unwrap(), res); 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!(p.rev_predict_fix_endianness(buffer, &predictor_info, x, y).unwrap(), res); + assert_eq!(p.fix_endianness_and_unpredict(buffer, &predictor_info, x, y).unwrap(), res); 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!(p.rev_predict_fix_endianness(buffer, &predictor_info, x, y).unwrap(), res); + assert_eq!(p.fix_endianness_and_unpredict(buffer, &predictor_info, x, y).unwrap(), res); println!("ints littleendian"); predictor_info.sample_format = &[SampleFormat::Int]; @@ -415,22 +415,22 @@ mod test { 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!(p.rev_predict_fix_endianness(buffer, &predictor_info, x, y).unwrap()[..], res[..]); + assert_eq!(p.fix_endianness_and_unpredict(buffer, &predictor_info, x, y).unwrap()[..], res[..]); 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!(p.rev_predict_fix_endianness(buffer, &predictor_info, x, y).unwrap(), res); + assert_eq!(p.fix_endianness_and_unpredict(buffer, &predictor_info, x, y).unwrap(), res); 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!(p.rev_predict_fix_endianness(buffer, &predictor_info, x, y).unwrap(), res); + assert_eq!(p.fix_endianness_and_unpredict(buffer, &predictor_info, x, y).unwrap(), res); 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!(p.rev_predict_fix_endianness(buffer, &predictor_info, x, y).unwrap(), res); + assert_eq!(p.fix_endianness_and_unpredict(buffer, &predictor_info, x, y).unwrap(), res); println!("uints bigendian"); predictor_info.endianness = Endianness::BigEndian; @@ -440,53 +440,52 @@ mod test { println!("testing u8"); let buffer = Bytes::from(input.iter().map(|v| *v as u8).collect::>()); let res = Bytes::from(expected.clone()); - assert_eq!(p.rev_predict_fix_endianness(buffer, &predictor_info, x, y).unwrap(), res); + assert_eq!(p.fix_endianness_and_unpredict(buffer, &predictor_info, x, y).unwrap(), res); 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!(p.rev_predict_fix_endianness(buffer, &predictor_info, x, y).unwrap()[..], res[..]); + assert_eq!(p.fix_endianness_and_unpredict(buffer, &predictor_info, x, y).unwrap()[..], res[..]); 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!(p.rev_predict_fix_endianness(buffer, &predictor_info, x, y).unwrap(), res); + assert_eq!(p.fix_endianness_and_unpredict(buffer, &predictor_info, x, y).unwrap(), res); 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!(p.rev_predict_fix_endianness(buffer, &predictor_info, x, y).unwrap(), res); + assert_eq!(p.fix_endianness_and_unpredict(buffer, &predictor_info, x, y).unwrap(), res); println!("ints bigendian"); - predictor_info.sample_format = &[SampleFormat::Int]; 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!(p.rev_predict_fix_endianness(buffer, &predictor_info, x, y).unwrap(), res); + assert_eq!(p.fix_endianness_and_unpredict(buffer, &predictor_info, x, y).unwrap(), res); 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!(p.rev_predict_fix_endianness(buffer, &predictor_info, x, y).unwrap(), res); + assert_eq!(p.fix_endianness_and_unpredict(buffer, &predictor_info, x, y).unwrap(), res); 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!(p.rev_predict_fix_endianness(buffer, &predictor_info, x, y).unwrap(), res); + assert_eq!(p.fix_endianness_and_unpredict(buffer, &predictor_info, x, y).unwrap(), res); 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!(p.rev_predict_fix_endianness(buffer, &predictor_info, x, y).unwrap(), res); + assert_eq!(p.fix_endianness_and_unpredict(buffer, &predictor_info, x, y).unwrap(), res); } } @@ -515,7 +514,7 @@ mod test { }; let input = Bytes::from_owner(diffed); assert_eq!( - &FloatingPointPredictor.rev_predict_fix_endianness(input, &info, 1, 1).unwrap()[..], + &FloatingPointPredictor.fix_endianness_and_unpredict(input, &info, 1, 1).unwrap()[..], &expect_le[..] ) } @@ -545,7 +544,7 @@ mod test { }; let input = Bytes::from_owner(diffed); assert_eq!( - &FloatingPointPredictor.rev_predict_fix_endianness(input, &info, 1, 1).unwrap()[..], + &FloatingPointPredictor.fix_endianness_and_unpredict(input, &info, 1, 1).unwrap()[..], &expect_le[..] ) } @@ -575,12 +574,12 @@ mod test { let input = Bytes::from_owner(diffed); assert_eq!( &FloatingPointPredictor - .rev_predict_fix_endianness(input.clone(), &info, 0, 1).unwrap()[..], + .fix_endianness_and_unpredict(input.clone(), &info, 0, 1).unwrap()[..], &expect_le ); info.endianness = Endianness::BigEndian; assert_eq!( - &FloatingPointPredictor.rev_predict_fix_endianness(input, &info, 0, 1).unwrap()[..], + &FloatingPointPredictor.fix_endianness_and_unpredict(input, &info, 0, 1).unwrap()[..], &expect_le ) } @@ -610,7 +609,7 @@ mod test { let input = Bytes::from_owner(diffed); assert_eq!( &FloatingPointPredictor - .rev_predict_fix_endianness(input, &info, 0, 1) + .fix_endianness_and_unpredict(input, &info, 0, 1) .unwrap()[..], &expect_be[..] ); diff --git a/src/tile.rs b/src/tile.rs index 95d628d..29b8f13 100644 --- a/src/tile.rs +++ b/src/tile.rs @@ -2,7 +2,7 @@ use bytes::Bytes; use crate::decoder::DecoderRegistry; use crate::error::AsyncTiffResult; -use crate::predictor::{FloatingPointPredictor, HorizontalPredictor, NoPredictor, RevPredict}; +use crate::predictor::{FloatingPointPredictor, HorizontalPredictor, NoPredictor, Unpredict}; use crate::reader::Endianness; use crate::tiff::tags::{ CompressionMethod, PhotometricInterpretation, PlanarConfiguration, Predictor, SampleFormat, @@ -236,19 +236,19 @@ impl Tile<'_> { )?; match self.predictor { - Predictor::None => NoPredictor.rev_predict_fix_endianness( + Predictor::None => NoPredictor.fix_endianness_and_unpredict( decoded_tile, &self.predictor_info, self.x as _, self.y as _, ), - Predictor::Horizontal => HorizontalPredictor.rev_predict_fix_endianness( + Predictor::Horizontal => HorizontalPredictor.fix_endianness_and_unpredict( decoded_tile, &self.predictor_info, self.x as _, self.y as _, ), - Predictor::FloatingPoint => FloatingPointPredictor.rev_predict_fix_endianness( + Predictor::FloatingPoint => FloatingPointPredictor.fix_endianness_and_unpredict( decoded_tile, &self.predictor_info, self.x as _, From 67eca9c4c0ec1e9473ff1a4ae911a67342f4a0bc Mon Sep 17 00:00:00 2001 From: Kyle Barron Date: Wed, 2 Apr 2025 15:56:52 -0400 Subject: [PATCH 08/23] Change to pub(crate) fields --- src/ifd.rs | 1 - src/predictor.rs | 12 +----------- src/tile.rs | 30 +++++++++++------------------- 3 files changed, 12 insertions(+), 31 deletions(-) diff --git a/src/ifd.rs b/src/ifd.rs index d0047bd..2512a55 100644 --- a/src/ifd.rs +++ b/src/ifd.rs @@ -700,7 +700,6 @@ impl ImageFileDirectory { }, bits_per_sample: &self.bits_per_sample, samples_per_pixel: self.samples_per_pixel, - sample_format: &self.sample_format, planar_configuration: self.planar_configuration, } } diff --git a/src/predictor.rs b/src/predictor.rs index 57ecbfa..70c2008 100644 --- a/src/predictor.rs +++ b/src/predictor.rs @@ -285,9 +285,7 @@ mod test { use bytes::Bytes; use crate::{ - predictor::FloatingPointPredictor, - reader::Endianness, - tiff::tags::{PlanarConfiguration, SampleFormat}, + predictor::FloatingPointPredictor, reader::Endianness, tiff::tags::PlanarConfiguration, tile::PredictorInfo, }; @@ -301,7 +299,6 @@ mod test { chunk_height: 4, bits_per_sample: &[8], samples_per_pixel: 1, - sample_format: &[SampleFormat::Uint], planar_configuration: crate::tiff::tags::PlanarConfiguration::Chunky, }; #[rustfmt::skip] @@ -382,7 +379,6 @@ mod test { for (x,y, input, expected) in cases { println!("uints littleendian"); predictor_info.endianness = Endianness::LittleEndian; - predictor_info.sample_format = &[SampleFormat::Uint]; predictor_info.bits_per_sample = &[8]; assert_eq!(-1i32 as u8, 255); println!("testing u8"); @@ -409,7 +405,6 @@ mod test { assert_eq!(p.fix_endianness_and_unpredict(buffer, &predictor_info, x, y).unwrap(), res); println!("ints littleendian"); - predictor_info.sample_format = &[SampleFormat::Int]; 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::>()); @@ -434,7 +429,6 @@ mod test { println!("uints bigendian"); predictor_info.endianness = Endianness::BigEndian; - predictor_info.sample_format = &[SampleFormat::Uint]; predictor_info.bits_per_sample = &[8]; assert_eq!(-1i32 as u8, 255); println!("testing u8"); @@ -509,7 +503,6 @@ 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); @@ -539,7 +532,6 @@ 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); @@ -568,7 +560,6 @@ 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); @@ -603,7 +594,6 @@ 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); diff --git a/src/tile.rs b/src/tile.rs index 29b8f13..be351f1 100644 --- a/src/tile.rs +++ b/src/tile.rs @@ -5,7 +5,7 @@ use crate::error::AsyncTiffResult; use crate::predictor::{FloatingPointPredictor, HorizontalPredictor, NoPredictor, Unpredict}; use crate::reader::Endianness; use crate::tiff::tags::{ - CompressionMethod, PhotometricInterpretation, PlanarConfiguration, Predictor, SampleFormat, + CompressionMethod, PhotometricInterpretation, PlanarConfiguration, Predictor, }; use crate::tiff::{TiffError, TiffUnsupportedError}; @@ -17,33 +17,29 @@ use crate::tiff::{TiffError, TiffUnsupportedError}; /// Also provides convenience functions /// #[derive(Debug, Clone, Copy)] -pub struct PredictorInfo<'a> { +pub(crate) struct PredictorInfo<'a> { /// endianness - pub endianness: Endianness, + pub(crate) endianness: Endianness, /// width of the image in pixels - pub image_width: u32, + pub(crate) image_width: u32, /// height of the image in pixels - pub image_height: u32, + pub(crate) image_height: u32, /// chunk width in pixels /// /// If this is a stripped tiff, `chunk_width=image_width` - pub chunk_width: u32, + pub(crate) chunk_width: u32, /// chunk height in pixels - pub chunk_height: u32, + pub(crate) chunk_height: u32, /// bits per sample, as an array /// /// Can also be a single value, in which case it applies to all samples - pub bits_per_sample: &'a [u16], // maybe say that we only support a single bits_per_sample? + pub(crate) bits_per_sample: &'a [u16], // maybe say that we only support a single bits_per_sample? /// number of samples per pixel - pub samples_per_pixel: u16, - /// sample format for each sample - /// - /// There is no decoding implementation in this crate (or libtiff) for mixed sample formats - pub sample_format: &'a [SampleFormat], // and a single sample_format? + pub(crate) samples_per_pixel: u16, /// planar configuration /// /// determines the bits per pixel - pub planar_configuration: PlanarConfiguration, + pub(crate) planar_configuration: PlanarConfiguration, } impl PredictorInfo<'_> { @@ -260,10 +256,7 @@ impl Tile<'_> { #[cfg(test)] mod test { - use crate::{ - reader::Endianness, - tiff::tags::{PlanarConfiguration, SampleFormat}, - }; + use crate::{reader::Endianness, tiff::tags::PlanarConfiguration}; use super::PredictorInfo; @@ -277,7 +270,6 @@ mod test { chunk_height: 8, bits_per_sample: &[8], samples_per_pixel: 1, - sample_format: &[SampleFormat::Uint], planar_configuration: PlanarConfiguration::Chunky, }; assert_eq!(info.bits_per_pixel(), 8); From a50fb81bb5b601e2ae0db306b4e85d698fa0b14c Mon Sep 17 00:00:00 2001 From: Kyle Barron Date: Wed, 2 Apr 2025 15:57:24 -0400 Subject: [PATCH 09/23] Change to pub(crate) --- src/lib.rs | 2 +- src/predictor.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 8b1aca0..6c9d7de 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,4 +15,4 @@ mod tile; pub use cog::TIFF; pub use ifd::ImageFileDirectory; -pub use tile::{PredictorInfo, Tile}; +pub use tile::Tile; diff --git a/src/predictor.rs b/src/predictor.rs index 70c2008..90f0d0b 100644 --- a/src/predictor.rs +++ b/src/predictor.rs @@ -7,7 +7,7 @@ use bytes::{Bytes, BytesMut}; use crate::{error::AsyncTiffResult, reader::Endianness, tile::PredictorInfo}; /// Trait for reverse predictors to implement -pub trait Unpredict: Debug + Send + Sync { +pub(crate) trait Unpredict: Debug + Send + Sync { /// reverse predict the decompressed bytes and fix endianness on the output fn fix_endianness_and_unpredict( &self, From 2b3fe038ba3568d9a3d9b9a96da9c8915a0ca24c Mon Sep 17 00:00:00 2001 From: Kyle Barron Date: Wed, 2 Apr 2025 16:05:10 -0400 Subject: [PATCH 10/23] Remove lifetime and store a single element for bits_per_pixel --- src/ifd.rs | 6 ++++- src/predictor.rs | 50 +++++++++++++++++++------------------- src/tile.rs | 62 ++++++++++++++++++++++++------------------------ 3 files changed, 61 insertions(+), 57 deletions(-) diff --git a/src/ifd.rs b/src/ifd.rs index 2512a55..5a29758 100644 --- a/src/ifd.rs +++ b/src/ifd.rs @@ -682,6 +682,10 @@ impl ImageFileDirectory { } fn get_predictor_info(&self) -> PredictorInfo { + if !self.bits_per_sample.windows(2).all(|w| w[0] == w[1]) { + panic!("bits_per_sample should be the same for all channels"); + } + PredictorInfo { endianness: self.endianness, image_width: self.image_width, @@ -698,7 +702,7 @@ impl ImageFileDirectory { } else { self.tile_height.unwrap() }, - bits_per_sample: &self.bits_per_sample, + bits_per_sample: self.bits_per_sample[0], samples_per_pixel: self.samples_per_pixel, planar_configuration: self.planar_configuration, } diff --git a/src/predictor.rs b/src/predictor.rs index 90f0d0b..79c340d 100644 --- a/src/predictor.rs +++ b/src/predictor.rs @@ -34,7 +34,7 @@ impl Unpredict for NoPredictor { fix_endianness( &mut res[..], predictor_info.endianness, - predictor_info.bits_per_sample[0], + predictor_info.bits_per_sample, ); Ok(res.into()) } @@ -54,7 +54,7 @@ impl Unpredict for HorizontalPredictor { ) -> 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[0]; + let bit_depth = predictor_info.bits_per_sample; let mut res = BytesMut::from(buffer); fix_endianness(&mut res[..], predictor_info.endianness, bit_depth); @@ -152,7 +152,7 @@ impl Unpredict for FloatingPointPredictor { let mut res: BytesMut = BytesMut::zeroed( output_row_stride * predictor_info.chunk_height_pixels(tile_y)? as usize, ); - let bit_depth = predictor_info.bits_per_sample[0] as usize; + 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); @@ -173,7 +173,7 @@ impl Unpredict for FloatingPointPredictor { let mut input = BytesMut::from(buffer); let input_row_stride = - predictor_info.chunk_width as usize * predictor_info.bits_per_pixel() / 8; + predictor_info.chunk_width as usize * predictor_info.bits_per_sample as usize / 8; for (in_buf, out_buf) in input .chunks_mut(input_row_stride) .zip(res.chunks_mut(output_row_stride)) @@ -297,7 +297,7 @@ mod test { image_height: 7, chunk_width: 4, chunk_height: 4, - bits_per_sample: &[8], + bits_per_sample: 8, samples_per_pixel: 1, planar_configuration: crate::tiff::tags::PlanarConfiguration::Chunky, }; @@ -379,7 +379,7 @@ mod test { for (x,y, input, expected) in cases { println!("uints littleendian"); predictor_info.endianness = Endianness::LittleEndian; - predictor_info.bits_per_sample = &[8]; + 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::>()); @@ -387,49 +387,49 @@ mod test { assert_eq!(p.fix_endianness_and_unpredict(buffer, &predictor_info, x, y).unwrap(), res); assert_eq!(-1i32 as u16, u16::MAX); println!("testing u16"); - predictor_info.bits_per_sample = &[16]; + 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!(p.fix_endianness_and_unpredict(buffer, &predictor_info, x, y).unwrap(), res); assert_eq!(-1i32 as u32, u32::MAX); println!("testing u32"); - predictor_info.bits_per_sample = &[32]; + 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!(p.fix_endianness_and_unpredict(buffer, &predictor_info, x, y).unwrap(), res); assert_eq!(-1i32 as u64, u64::MAX); println!("testing u64"); - predictor_info.bits_per_sample = &[64]; + 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!(p.fix_endianness_and_unpredict(buffer, &predictor_info, x, y).unwrap(), res); println!("ints littleendian"); - predictor_info.bits_per_sample = &[8]; + 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!(p.fix_endianness_and_unpredict(buffer, &predictor_info, x, y).unwrap()[..], res[..]); println!("testing i16"); - predictor_info.bits_per_sample = &[16]; + 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!(p.fix_endianness_and_unpredict(buffer, &predictor_info, x, y).unwrap(), res); println!("testing i32"); - predictor_info.bits_per_sample = &[32]; + 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!(p.fix_endianness_and_unpredict(buffer, &predictor_info, x, y).unwrap(), res); println!("testing i64"); - predictor_info.bits_per_sample = &[64]; + 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!(p.fix_endianness_and_unpredict(buffer, &predictor_info, x, y).unwrap(), res); println!("uints bigendian"); predictor_info.endianness = Endianness::BigEndian; - predictor_info.bits_per_sample = &[8]; + 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::>()); @@ -437,26 +437,26 @@ mod test { assert_eq!(p.fix_endianness_and_unpredict(buffer, &predictor_info, x, y).unwrap(), res); assert_eq!(-1i32 as u16, u16::MAX); println!("testing u16"); - predictor_info.bits_per_sample = &[16]; + 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!(p.fix_endianness_and_unpredict(buffer, &predictor_info, x, y).unwrap()[..], res[..]); assert_eq!(-1i32 as u32, u32::MAX); println!("testing u32"); - predictor_info.bits_per_sample = &[32]; + 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!(p.fix_endianness_and_unpredict(buffer, &predictor_info, x, y).unwrap(), res); assert_eq!(-1i32 as u64, u64::MAX); println!("testing u64"); - predictor_info.bits_per_sample = &[64]; + 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!(p.fix_endianness_and_unpredict(buffer, &predictor_info, x, y).unwrap(), res); println!("ints bigendian"); - predictor_info.bits_per_sample = &[8]; + 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::>()); @@ -464,19 +464,19 @@ mod test { assert_eq!(p.fix_endianness_and_unpredict(buffer, &predictor_info, x, y).unwrap(), res); assert_eq!(-1i32 as u16, u16::MAX); println!("testing i16"); - predictor_info.bits_per_sample = &[16]; + 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!(p.fix_endianness_and_unpredict(buffer, &predictor_info, x, y).unwrap(), res); assert_eq!(-1i32 as u32, u32::MAX); println!("testing i32"); - predictor_info.bits_per_sample = &[32]; + 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!(p.fix_endianness_and_unpredict(buffer, &predictor_info, x, y).unwrap(), res); assert_eq!(-1i32 as u64, u64::MAX); println!("testing i64"); - predictor_info.bits_per_sample = &[64]; + 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!(p.fix_endianness_and_unpredict(buffer, &predictor_info, x, y).unwrap(), res); @@ -501,7 +501,7 @@ mod test { image_height: 4+1, chunk_width: 4, chunk_height: 4, - bits_per_sample: &[16], + bits_per_sample: 16, samples_per_pixel: 1, planar_configuration: PlanarConfiguration::Chunky, }; @@ -530,7 +530,7 @@ mod test { image_height: 4+1, chunk_width: 4, chunk_height: 4, - bits_per_sample: &[16], + bits_per_sample: 16, samples_per_pixel: 1, planar_configuration: PlanarConfiguration::Chunky, }; @@ -558,7 +558,7 @@ mod test { image_height: 2 + 1, chunk_width: 2, chunk_height: 2, - bits_per_sample: &[32], + bits_per_sample: 32, samples_per_pixel: 1, planar_configuration: PlanarConfiguration::Chunky, }; @@ -592,7 +592,7 @@ mod test { image_height: 2 + 1, chunk_width: 2, chunk_height: 2, - bits_per_sample: &[64], + bits_per_sample: 64, samples_per_pixel: 1, planar_configuration: PlanarConfiguration::Chunky, }; diff --git a/src/tile.rs b/src/tile.rs index be351f1..f94e677 100644 --- a/src/tile.rs +++ b/src/tile.rs @@ -17,7 +17,7 @@ use crate::tiff::{TiffError, TiffUnsupportedError}; /// Also provides convenience functions /// #[derive(Debug, Clone, Copy)] -pub(crate) struct PredictorInfo<'a> { +pub(crate) struct PredictorInfo { /// endianness pub(crate) endianness: Endianness, /// width of the image in pixels @@ -30,10 +30,10 @@ pub(crate) struct PredictorInfo<'a> { pub(crate) chunk_width: u32, /// chunk height in pixels pub(crate) chunk_height: u32, - /// bits per sample, as an array + /// bits per sample /// - /// Can also be a single value, in which case it applies to all samples - pub(crate) bits_per_sample: &'a [u16], // maybe say that we only support a single bits_per_sample? + /// We only support a single bits_per_sample across all samples + pub(crate) bits_per_sample: u16, /// number of samples per pixel pub(crate) samples_per_pixel: u16, /// planar configuration @@ -42,7 +42,7 @@ pub(crate) struct PredictorInfo<'a> { pub(crate) planar_configuration: PlanarConfiguration, } -impl PredictorInfo<'_> { +impl PredictorInfo { /// chunk width in pixels, taking padding into account /// /// strips are considered image-width chunks @@ -125,29 +125,30 @@ impl PredictorInfo<'_> { /// get the output row stride in bytes, taking padding into account pub fn output_row_stride(&self, x: u32) -> AsyncTiffResult { - Ok((self.chunk_width_pixels(x)? as usize).saturating_mul(self.bits_per_pixel()) / 8) + Ok((self.chunk_width_pixels(x)? as usize).saturating_mul(self.bits_per_sample as _) / 8) } - /// The total number of bits per pixel, taking into account possible different sample sizes - /// - /// Technically bits_per_sample.len() should be *equal* to samples, but libtiff also allows - /// it to be a single value that applies to all samples. - /// - /// Libtiff and image-tiff do not support mixed bits per sample, but we give the possibility - /// unless you also have PlanarConfiguration::Planar, at which point the first is taken - pub fn bits_per_pixel(&self) -> usize { - match self.planar_configuration { - PlanarConfiguration::Chunky => { - if self.bits_per_sample.len() == 1 { - self.samples_per_pixel as usize * self.bits_per_sample[0] as usize - } else { - assert_eq!(self.samples_per_pixel as usize, self.bits_per_sample.len()); - self.bits_per_sample.iter().map(|v| *v as usize).product() - } - } - PlanarConfiguration::Planar => self.bits_per_sample[0] as usize, - } - } + // /// The total number of bits per pixel, taking into account possible different sample sizes + // /// + // /// Technically bits_per_sample.len() should be *equal* to samples, but libtiff also allows + // /// it to be a single value that applies to all samples. + // /// + // /// Libtiff and image-tiff do not support mixed bits per sample, but we give the possibility + // /// unless you also have PlanarConfiguration::Planar, at which point the first is taken + // pub fn bits_per_pixel(&self) -> usize { + // self.bits_per_sample + // match self.planar_configuration { + // PlanarConfiguration::Chunky => { + // if self.bits_per_sample.len() == 1 { + // self.samples_per_pixel as usize * self.bits_per_sample[0] as usize + // } else { + // assert_eq!(self.samples_per_pixel as usize, self.bits_per_sample.len()); + // self.bits_per_sample.iter().map(|v| *v as usize).product() + // } + // } + // PlanarConfiguration::Planar => self.bits_per_sample[0] as usize, + // } + // } /// The number of chunks in the horizontal (x) direction pub fn chunks_across(&self) -> u32 { @@ -167,18 +168,18 @@ impl PredictorInfo<'_> { /// /// This is returned by `fetch_tile`. #[derive(Debug)] -pub struct Tile<'a> { +pub struct Tile { pub(crate) x: usize, pub(crate) y: usize, pub(crate) predictor: Predictor, - pub(crate) predictor_info: PredictorInfo<'a>, + pub(crate) predictor_info: PredictorInfo, pub(crate) compressed_bytes: Bytes, pub(crate) compression_method: CompressionMethod, pub(crate) photometric_interpretation: PhotometricInterpretation, pub(crate) jpeg_tables: Option, } -impl Tile<'_> { +impl Tile { /// The column index of this tile. pub fn x(&self) -> usize { self.x @@ -268,11 +269,10 @@ mod test { image_height: 17, chunk_width: 8, chunk_height: 8, - bits_per_sample: &[8], + bits_per_sample: 8, samples_per_pixel: 1, planar_configuration: PlanarConfiguration::Chunky, }; - assert_eq!(info.bits_per_pixel(), 8); assert_eq!(info.chunks_across(), 2); assert_eq!(info.chunks_down(), 3); assert_eq!(info.chunk_width_pixels(0).unwrap(), info.chunk_width); From d754e140ced3f38f770a115f2b6918a96fb5e428 Mon Sep 17 00:00:00 2001 From: Kyle Barron Date: Wed, 2 Apr 2025 16:07:45 -0400 Subject: [PATCH 11/23] Remove unused planar configuration --- src/ifd.rs | 1 - src/predictor.rs | 10 +--------- src/tile.rs | 11 ++--------- 3 files changed, 3 insertions(+), 19 deletions(-) diff --git a/src/ifd.rs b/src/ifd.rs index 5a29758..c6176b6 100644 --- a/src/ifd.rs +++ b/src/ifd.rs @@ -704,7 +704,6 @@ impl ImageFileDirectory { }, bits_per_sample: self.bits_per_sample[0], samples_per_pixel: self.samples_per_pixel, - planar_configuration: self.planar_configuration, } } diff --git a/src/predictor.rs b/src/predictor.rs index 79c340d..0489437 100644 --- a/src/predictor.rs +++ b/src/predictor.rs @@ -284,10 +284,7 @@ mod test { use bytes::Bytes; - use crate::{ - predictor::FloatingPointPredictor, reader::Endianness, tiff::tags::PlanarConfiguration, - tile::PredictorInfo, - }; + use crate::{predictor::FloatingPointPredictor, reader::Endianness, tile::PredictorInfo}; use super::{HorizontalPredictor, NoPredictor, Unpredict}; @@ -299,7 +296,6 @@ mod test { chunk_height: 4, bits_per_sample: 8, samples_per_pixel: 1, - planar_configuration: crate::tiff::tags::PlanarConfiguration::Chunky, }; #[rustfmt::skip] const RES: [u8;16] = [ @@ -503,7 +499,6 @@ mod test { chunk_height: 4, bits_per_sample: 16, samples_per_pixel: 1, - planar_configuration: PlanarConfiguration::Chunky, }; let input = Bytes::from_owner(diffed); assert_eq!( @@ -532,7 +527,6 @@ mod test { chunk_height: 4, bits_per_sample: 16, samples_per_pixel: 1, - planar_configuration: PlanarConfiguration::Chunky, }; let input = Bytes::from_owner(diffed); assert_eq!( @@ -560,7 +554,6 @@ mod test { chunk_height: 2, bits_per_sample: 32, samples_per_pixel: 1, - planar_configuration: PlanarConfiguration::Chunky, }; let input = Bytes::from_owner(diffed); assert_eq!( @@ -594,7 +587,6 @@ mod test { chunk_height: 2, bits_per_sample: 64, samples_per_pixel: 1, - planar_configuration: PlanarConfiguration::Chunky, }; let input = Bytes::from_owner(diffed); assert_eq!( diff --git a/src/tile.rs b/src/tile.rs index f94e677..45d39a3 100644 --- a/src/tile.rs +++ b/src/tile.rs @@ -4,9 +4,7 @@ use crate::decoder::DecoderRegistry; use crate::error::AsyncTiffResult; use crate::predictor::{FloatingPointPredictor, HorizontalPredictor, NoPredictor, Unpredict}; use crate::reader::Endianness; -use crate::tiff::tags::{ - CompressionMethod, PhotometricInterpretation, PlanarConfiguration, Predictor, -}; +use crate::tiff::tags::{CompressionMethod, PhotometricInterpretation, Predictor}; use crate::tiff::{TiffError, TiffUnsupportedError}; /// All info that may be used by a predictor @@ -36,10 +34,6 @@ pub(crate) struct PredictorInfo { pub(crate) bits_per_sample: u16, /// number of samples per pixel pub(crate) samples_per_pixel: u16, - /// planar configuration - /// - /// determines the bits per pixel - pub(crate) planar_configuration: PlanarConfiguration, } impl PredictorInfo { @@ -257,7 +251,7 @@ impl Tile { #[cfg(test)] mod test { - use crate::{reader::Endianness, tiff::tags::PlanarConfiguration}; + use crate::reader::Endianness; use super::PredictorInfo; @@ -271,7 +265,6 @@ mod test { chunk_height: 8, bits_per_sample: 8, samples_per_pixel: 1, - planar_configuration: PlanarConfiguration::Chunky, }; assert_eq!(info.chunks_across(), 2); assert_eq!(info.chunks_down(), 3); From cda2bcca8bda1891dbc710419c2bc4cdf99cac04 Mon Sep 17 00:00:00 2001 From: Kyle Barron Date: Wed, 2 Apr 2025 16:08:29 -0400 Subject: [PATCH 12/23] chunk_width and chunk_height without unwrap --- src/ifd.rs | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/ifd.rs b/src/ifd.rs index c6176b6..784438c 100644 --- a/src/ifd.rs +++ b/src/ifd.rs @@ -686,22 +686,25 @@ impl ImageFileDirectory { panic!("bits_per_sample should be the same for all channels"); } + let chunk_width = if let Some(tile_width) = self.tile_width { + tile_width + } else { + self.image_width + }; + let chunk_height = if let Some(tile_height) = self.tile_height { + tile_height + } else { + self.rows_per_strip + .expect("no tile height and no rows_per_strip") + }; + PredictorInfo { endianness: self.endianness, image_width: self.image_width, image_height: self.image_height, - chunk_width: if self.tile_width.is_none() { - // we are stripped => image_width - self.image_width - } else { - self.tile_width.unwrap() - }, - chunk_height: if self.tile_height.is_none() { - self.rows_per_strip - .expect("no tile height and no rows_per_strip") - } else { - self.tile_height.unwrap() - }, + chunk_width, + chunk_height, + // TODO: validate this? Restore handling for different PlanarConfiguration? bits_per_sample: self.bits_per_sample[0], samples_per_pixel: self.samples_per_pixel, } From 478798394980f4296e4bcd32581d54c6bb6f7354 Mon Sep 17 00:00:00 2001 From: Kyle Barron Date: Wed, 2 Apr 2025 16:28:39 -0400 Subject: [PATCH 13/23] Move PredictorInfo into predictor.rs --- src/ifd.rs | 38 ++------- src/predictor.rs | 205 ++++++++++++++++++++++++++++++++++++++++++++++- src/tile.rs | 181 +---------------------------------------- 3 files changed, 211 insertions(+), 213 deletions(-) diff --git a/src/ifd.rs b/src/ifd.rs index 784438c..f0da9c1 100644 --- a/src/ifd.rs +++ b/src/ifd.rs @@ -6,13 +6,14 @@ use num_enum::TryFromPrimitive; use crate::error::{AsyncTiffError, AsyncTiffResult}; use crate::geo::{GeoKeyDirectory, GeoKeyTag}; +use crate::predictor::PredictorInfo; use crate::reader::{AsyncFileReader, Endianness}; use crate::tiff::tags::{ CompressionMethod, PhotometricInterpretation, PlanarConfiguration, Predictor, ResolutionUnit, SampleFormat, Tag, }; use crate::tiff::{TiffError, Value}; -use crate::tile::{PredictorInfo, Tile}; +use crate::tile::Tile; const DOCUMENT_NAME: u16 = 269; @@ -681,35 +682,6 @@ impl ImageFileDirectory { Some(offset as _..(offset + byte_count) as _) } - fn get_predictor_info(&self) -> PredictorInfo { - if !self.bits_per_sample.windows(2).all(|w| w[0] == w[1]) { - panic!("bits_per_sample should be the same for all channels"); - } - - let chunk_width = if let Some(tile_width) = self.tile_width { - tile_width - } else { - self.image_width - }; - let chunk_height = if let Some(tile_height) = self.tile_height { - tile_height - } else { - self.rows_per_strip - .expect("no tile height and no rows_per_strip") - }; - - PredictorInfo { - endianness: self.endianness, - image_width: self.image_width, - image_height: self.image_height, - chunk_width, - chunk_height, - // TODO: validate this? Restore handling for different PlanarConfiguration? - bits_per_sample: self.bits_per_sample[0], - samples_per_pixel: self.samples_per_pixel, - } - } - /// Fetch the tile located at `x` column and `y` row using the provided reader. pub async fn fetch_tile( &self, @@ -725,7 +697,7 @@ impl ImageFileDirectory { x, y, predictor: self.predictor.unwrap_or(Predictor::None), - predictor_info: self.get_predictor_info(), + predictor_info: PredictorInfo::from_ifd(self), compressed_bytes, compression_method: self.compression, photometric_interpretation: self.photometric_interpretation, @@ -742,6 +714,8 @@ impl ImageFileDirectory { ) -> AsyncTiffResult> { assert_eq!(x.len(), y.len(), "x and y should have same len"); + let predictor_info = PredictorInfo::from_ifd(self); + // 1: Get all the byte ranges for all tiles let byte_ranges = x .iter() @@ -762,7 +736,7 @@ impl ImageFileDirectory { x, y, predictor: self.predictor.unwrap_or(Predictor::None), - predictor_info: self.get_predictor_info(), + predictor_info, compressed_bytes, compression_method: self.compression, photometric_interpretation: self.photometric_interpretation, diff --git a/src/predictor.rs b/src/predictor.rs index 0489437..f405517 100644 --- a/src/predictor.rs +++ b/src/predictor.rs @@ -4,7 +4,185 @@ use std::fmt::Debug; use bytes::{Bytes, BytesMut}; // use tiff::decoder::DecodingResult; -use crate::{error::AsyncTiffResult, reader::Endianness, tile::PredictorInfo}; +use crate::ImageFileDirectory; +use crate::{error::AsyncTiffResult, reader::Endianness}; + +/// All info that may be used by a predictor +/// +/// Most of this is used by the floating point predictor +/// since that intermixes padding into the decompressed output +/// +/// Also provides convenience functions +/// +#[derive(Debug, Clone, Copy)] +pub(crate) struct PredictorInfo { + /// endianness + endianness: Endianness, + /// width of the image in pixels + image_width: u32, + /// height of the image in pixels + image_height: u32, + /// chunk width in pixels + /// + /// If this is a stripped tiff, `chunk_width=image_width` + chunk_width: u32, + /// chunk height in pixels + chunk_height: u32, + /// bits per sample + /// + /// We only support a single bits_per_sample across all samples + bits_per_sample: u16, + /// number of samples per pixel + samples_per_pixel: u16, +} + +impl PredictorInfo { + pub(crate) fn from_ifd(ifd: &ImageFileDirectory) -> Self { + if !ifd.bits_per_sample.windows(2).all(|w| w[0] == w[1]) { + panic!("bits_per_sample should be the same for all channels"); + } + + let chunk_width = if let Some(tile_width) = ifd.tile_width { + tile_width + } else { + ifd.image_width + }; + let chunk_height = if let Some(tile_height) = ifd.tile_height { + tile_height + } else { + ifd.rows_per_strip + .expect("no tile height and no rows_per_strip") + }; + + PredictorInfo { + endianness: ifd.endianness, + image_width: ifd.image_width, + image_height: ifd.image_height, + chunk_width, + chunk_height, + // TODO: validate this? Restore handling for different PlanarConfiguration? + bits_per_sample: ifd.bits_per_sample[0], + samples_per_pixel: ifd.samples_per_pixel, + } + } + + /// chunk width in pixels, taking padding into account + /// + /// strips are considered image-width chunks + /// + /// # Example + /// + /// ```rust + /// # use async_tiff::tiff::tags::{SampleFormat, PlanarConfiguration}; + /// # use async_tiff::reader::Endianness; + /// # use async_tiff::PredictorInfo; + /// let info = PredictorInfo { + /// # endianness: Endianness::LittleEndian, + /// image_width: 15, + /// image_height: 15, + /// chunk_width: 8, + /// chunk_height: 8, + /// # bits_per_sample: &[32], + /// # samples_per_pixel: 1, + /// # sample_format: &[SampleFormat::IEEEFP], + /// # planar_configuration: PlanarConfiguration::Chunky, + /// }; + /// + /// assert_eq!(info.chunk_width_pixels(0).unwrap(), (8)); + /// assert_eq!(info.chunk_width_pixels(1).unwrap(), (7)); + /// info.chunk_width_pixels(2).unwrap_err(); + /// ``` + fn chunk_width_pixels(&self, x: u32) -> AsyncTiffResult { + if x >= self.chunks_across() { + Err(crate::error::AsyncTiffError::TileIndexError( + x, + self.chunks_across(), + )) + } else if x == self.chunks_across() - 1 { + // last chunk + Ok(self.image_width - self.chunk_width * x) + } else { + Ok(self.chunk_width) + } + } + + /// chunk height in pixels, taking padding into account + /// + /// strips are considered image-width chunks + /// + /// # Example + /// + /// ```rust + /// # use async_tiff::tiff::tags::{SampleFormat, PlanarConfiguration}; + /// # use async_tiff::reader::Endianness; + /// # use async_tiff::PredictorInfo; + /// let info = PredictorInfo { + /// # endianness: Endianness::LittleEndian, + /// image_width: 15, + /// image_height: 15, + /// chunk_width: 8, + /// chunk_height: 8, + /// # bits_per_sample: &[32], + /// # samples_per_pixel: 1, + /// # sample_format: &[SampleFormat::IEEEFP], + /// # planar_configuration: PlanarConfiguration::Chunky, + /// }; + /// + /// assert_eq!(info.chunk_height_pixels(0).unwrap(), (8)); + /// assert_eq!(info.chunk_height_pixels(1).unwrap(), (7)); + /// info.chunk_height_pixels(2).unwrap_err(); + /// ``` + pub fn chunk_height_pixels(&self, y: u32) -> AsyncTiffResult { + if y >= self.chunks_down() { + Err(crate::error::AsyncTiffError::TileIndexError( + y, + self.chunks_down(), + )) + } else if y == self.chunks_down() - 1 { + // last chunk + Ok(self.image_height - self.chunk_height * y) + } else { + Ok(self.chunk_height) + } + } + + /// get the output row stride in bytes, taking padding into account + pub fn output_row_stride(&self, x: u32) -> AsyncTiffResult { + Ok((self.chunk_width_pixels(x)? as usize).saturating_mul(self.bits_per_sample as _) / 8) + } + + // /// The total number of bits per pixel, taking into account possible different sample sizes + // /// + // /// Technically bits_per_sample.len() should be *equal* to samples, but libtiff also allows + // /// it to be a single value that applies to all samples. + // /// + // /// Libtiff and image-tiff do not support mixed bits per sample, but we give the possibility + // /// unless you also have PlanarConfiguration::Planar, at which point the first is taken + // pub fn bits_per_pixel(&self) -> usize { + // self.bits_per_sample + // match self.planar_configuration { + // PlanarConfiguration::Chunky => { + // if self.bits_per_sample.len() == 1 { + // self.samples_per_pixel as usize * self.bits_per_sample[0] as usize + // } else { + // assert_eq!(self.samples_per_pixel as usize, self.bits_per_sample.len()); + // self.bits_per_sample.iter().map(|v| *v as usize).product() + // } + // } + // PlanarConfiguration::Planar => self.bits_per_sample[0] as usize, + // } + // } + + /// The number of chunks in the horizontal (x) direction + pub fn chunks_across(&self) -> u32 { + self.image_width.div_ceil(self.chunk_width) + } + + /// The number of chunks in the vertical (y) direction + pub fn chunks_down(&self) -> u32 { + self.image_height.div_ceil(self.chunk_height) + } +} /// Trait for reverse predictors to implement pub(crate) trait Unpredict: Debug + Send + Sync { @@ -284,9 +462,9 @@ mod test { use bytes::Bytes; - use crate::{predictor::FloatingPointPredictor, reader::Endianness, tile::PredictorInfo}; + use crate::{predictor::FloatingPointPredictor, reader::Endianness}; - use super::{HorizontalPredictor, NoPredictor, Unpredict}; + use super::*; const PRED_INFO: PredictorInfo = PredictorInfo { endianness: Endianness::LittleEndian, @@ -328,6 +506,27 @@ mod test { 2,1, 0, ]; + #[test] + fn test_chunk_width_pixels() { + let info = PredictorInfo { + endianness: Endianness::LittleEndian, + image_width: 15, + image_height: 17, + chunk_width: 8, + chunk_height: 8, + bits_per_sample: 8, + samples_per_pixel: 1, + }; + assert_eq!(info.chunks_across(), 2); + assert_eq!(info.chunks_down(), 3); + assert_eq!(info.chunk_width_pixels(0).unwrap(), info.chunk_width); + assert_eq!(info.chunk_width_pixels(1).unwrap(), 7); + info.chunk_width_pixels(2).unwrap_err(); + assert_eq!(info.chunk_height_pixels(0).unwrap(), info.chunk_height); + assert_eq!(info.chunk_height_pixels(2).unwrap(), 1); + info.chunk_height_pixels(3).unwrap_err(); + } + #[rustfmt::skip] #[test] fn test_no_predict() { diff --git a/src/tile.rs b/src/tile.rs index 45d39a3..f6c50ec 100644 --- a/src/tile.rs +++ b/src/tile.rs @@ -2,159 +2,12 @@ use bytes::Bytes; use crate::decoder::DecoderRegistry; use crate::error::AsyncTiffResult; -use crate::predictor::{FloatingPointPredictor, HorizontalPredictor, NoPredictor, Unpredict}; -use crate::reader::Endianness; +use crate::predictor::{ + FloatingPointPredictor, HorizontalPredictor, NoPredictor, PredictorInfo, Unpredict, +}; use crate::tiff::tags::{CompressionMethod, PhotometricInterpretation, Predictor}; use crate::tiff::{TiffError, TiffUnsupportedError}; -/// All info that may be used by a predictor -/// -/// Most of this is used by the floating point predictor -/// since that intermixes padding into the decompressed output -/// -/// Also provides convenience functions -/// -#[derive(Debug, Clone, Copy)] -pub(crate) struct PredictorInfo { - /// endianness - pub(crate) endianness: Endianness, - /// width of the image in pixels - pub(crate) image_width: u32, - /// height of the image in pixels - pub(crate) image_height: u32, - /// chunk width in pixels - /// - /// If this is a stripped tiff, `chunk_width=image_width` - pub(crate) chunk_width: u32, - /// chunk height in pixels - pub(crate) chunk_height: u32, - /// bits per sample - /// - /// We only support a single bits_per_sample across all samples - pub(crate) bits_per_sample: u16, - /// number of samples per pixel - pub(crate) samples_per_pixel: u16, -} - -impl PredictorInfo { - /// chunk width in pixels, taking padding into account - /// - /// strips are considered image-width chunks - /// - /// # Example - /// - /// ```rust - /// # use async_tiff::tiff::tags::{SampleFormat, PlanarConfiguration}; - /// # use async_tiff::reader::Endianness; - /// # use async_tiff::PredictorInfo; - /// let info = PredictorInfo { - /// # endianness: Endianness::LittleEndian, - /// image_width: 15, - /// image_height: 15, - /// chunk_width: 8, - /// chunk_height: 8, - /// # bits_per_sample: &[32], - /// # samples_per_pixel: 1, - /// # sample_format: &[SampleFormat::IEEEFP], - /// # planar_configuration: PlanarConfiguration::Chunky, - /// }; - /// - /// assert_eq!(info.chunk_width_pixels(0).unwrap(), (8)); - /// assert_eq!(info.chunk_width_pixels(1).unwrap(), (7)); - /// info.chunk_width_pixels(2).unwrap_err(); - /// ``` - pub fn chunk_width_pixels(&self, x: u32) -> AsyncTiffResult { - if x >= self.chunks_across() { - Err(crate::error::AsyncTiffError::TileIndexError( - x, - self.chunks_across(), - )) - } else if x == self.chunks_across() - 1 { - // last chunk - Ok(self.image_width - self.chunk_width * x) - } else { - Ok(self.chunk_width) - } - } - - /// chunk height in pixels, taking padding into account - /// - /// strips are considered image-width chunks - /// - /// # Example - /// - /// ```rust - /// # use async_tiff::tiff::tags::{SampleFormat, PlanarConfiguration}; - /// # use async_tiff::reader::Endianness; - /// # use async_tiff::PredictorInfo; - /// let info = PredictorInfo { - /// # endianness: Endianness::LittleEndian, - /// image_width: 15, - /// image_height: 15, - /// chunk_width: 8, - /// chunk_height: 8, - /// # bits_per_sample: &[32], - /// # samples_per_pixel: 1, - /// # sample_format: &[SampleFormat::IEEEFP], - /// # planar_configuration: PlanarConfiguration::Chunky, - /// }; - /// - /// assert_eq!(info.chunk_height_pixels(0).unwrap(), (8)); - /// assert_eq!(info.chunk_height_pixels(1).unwrap(), (7)); - /// info.chunk_height_pixels(2).unwrap_err(); - /// ``` - pub fn chunk_height_pixels(&self, y: u32) -> AsyncTiffResult { - if y >= self.chunks_down() { - Err(crate::error::AsyncTiffError::TileIndexError( - y, - self.chunks_down(), - )) - } else if y == self.chunks_down() - 1 { - // last chunk - Ok(self.image_height - self.chunk_height * y) - } else { - Ok(self.chunk_height) - } - } - - /// get the output row stride in bytes, taking padding into account - pub fn output_row_stride(&self, x: u32) -> AsyncTiffResult { - Ok((self.chunk_width_pixels(x)? as usize).saturating_mul(self.bits_per_sample as _) / 8) - } - - // /// The total number of bits per pixel, taking into account possible different sample sizes - // /// - // /// Technically bits_per_sample.len() should be *equal* to samples, but libtiff also allows - // /// it to be a single value that applies to all samples. - // /// - // /// Libtiff and image-tiff do not support mixed bits per sample, but we give the possibility - // /// unless you also have PlanarConfiguration::Planar, at which point the first is taken - // pub fn bits_per_pixel(&self) -> usize { - // self.bits_per_sample - // match self.planar_configuration { - // PlanarConfiguration::Chunky => { - // if self.bits_per_sample.len() == 1 { - // self.samples_per_pixel as usize * self.bits_per_sample[0] as usize - // } else { - // assert_eq!(self.samples_per_pixel as usize, self.bits_per_sample.len()); - // self.bits_per_sample.iter().map(|v| *v as usize).product() - // } - // } - // PlanarConfiguration::Planar => self.bits_per_sample[0] as usize, - // } - // } - - /// The number of chunks in the horizontal (x) direction - pub fn chunks_across(&self) -> u32 { - self.image_width.div_ceil(self.chunk_width) - } - - /// The number of chunks in the vertical (y) direction - pub fn chunks_down(&self) -> u32 { - self.image_height.div_ceil(self.chunk_height) - } -} - /// A TIFF Tile response. /// /// This contains the required information to decode the tile. Decoding is separated from fetching @@ -248,31 +101,3 @@ impl Tile { } } } - -#[cfg(test)] -mod test { - use crate::reader::Endianness; - - use super::PredictorInfo; - - #[test] - fn test_chunk_width_pixels() { - let info = PredictorInfo { - endianness: Endianness::LittleEndian, - image_width: 15, - image_height: 17, - chunk_width: 8, - chunk_height: 8, - bits_per_sample: 8, - samples_per_pixel: 1, - }; - assert_eq!(info.chunks_across(), 2); - assert_eq!(info.chunks_down(), 3); - assert_eq!(info.chunk_width_pixels(0).unwrap(), info.chunk_width); - assert_eq!(info.chunk_width_pixels(1).unwrap(), 7); - info.chunk_width_pixels(2).unwrap_err(); - assert_eq!(info.chunk_height_pixels(0).unwrap(), info.chunk_height); - assert_eq!(info.chunk_height_pixels(2).unwrap(), 1); - info.chunk_height_pixels(3).unwrap_err(); - } -} From 931f58dc4ff65f2144cd6c111c4a2fb3b8757f4f Mon Sep 17 00:00:00 2001 From: Kyle Barron Date: Wed, 2 Apr 2025 16:32:20 -0400 Subject: [PATCH 14/23] Remove unnecessary doctests and unused code --- src/predictor.rs | 83 +++++++----------------------------------------- 1 file changed, 12 insertions(+), 71 deletions(-) diff --git a/src/predictor.rs b/src/predictor.rs index f405517..7095e5b 100644 --- a/src/predictor.rs +++ b/src/predictor.rs @@ -61,6 +61,14 @@ impl PredictorInfo { chunk_width, chunk_height, // TODO: validate this? Restore handling for different PlanarConfiguration? + // PlanarConfiguration::Chunky => { + // if self.bits_per_sample.len() == 1 { + // self.samples_per_pixel as usize * self.bits_per_sample[0] as usize + // } else { + // assert_eq!(self.samples_per_pixel as usize, self.bits_per_sample.len()); + // self.bits_per_sample.iter().map(|v| *v as usize).product() + // } + // } bits_per_sample: ifd.bits_per_sample[0], samples_per_pixel: ifd.samples_per_pixel, } @@ -69,29 +77,6 @@ impl PredictorInfo { /// chunk width in pixels, taking padding into account /// /// strips are considered image-width chunks - /// - /// # Example - /// - /// ```rust - /// # use async_tiff::tiff::tags::{SampleFormat, PlanarConfiguration}; - /// # use async_tiff::reader::Endianness; - /// # use async_tiff::PredictorInfo; - /// let info = PredictorInfo { - /// # endianness: Endianness::LittleEndian, - /// image_width: 15, - /// image_height: 15, - /// chunk_width: 8, - /// chunk_height: 8, - /// # bits_per_sample: &[32], - /// # samples_per_pixel: 1, - /// # sample_format: &[SampleFormat::IEEEFP], - /// # planar_configuration: PlanarConfiguration::Chunky, - /// }; - /// - /// assert_eq!(info.chunk_width_pixels(0).unwrap(), (8)); - /// assert_eq!(info.chunk_width_pixels(1).unwrap(), (7)); - /// info.chunk_width_pixels(2).unwrap_err(); - /// ``` fn chunk_width_pixels(&self, x: u32) -> AsyncTiffResult { if x >= self.chunks_across() { Err(crate::error::AsyncTiffError::TileIndexError( @@ -110,29 +95,7 @@ impl PredictorInfo { /// /// strips are considered image-width chunks /// - /// # Example - /// - /// ```rust - /// # use async_tiff::tiff::tags::{SampleFormat, PlanarConfiguration}; - /// # use async_tiff::reader::Endianness; - /// # use async_tiff::PredictorInfo; - /// let info = PredictorInfo { - /// # endianness: Endianness::LittleEndian, - /// image_width: 15, - /// image_height: 15, - /// chunk_width: 8, - /// chunk_height: 8, - /// # bits_per_sample: &[32], - /// # samples_per_pixel: 1, - /// # sample_format: &[SampleFormat::IEEEFP], - /// # planar_configuration: PlanarConfiguration::Chunky, - /// }; - /// - /// assert_eq!(info.chunk_height_pixels(0).unwrap(), (8)); - /// assert_eq!(info.chunk_height_pixels(1).unwrap(), (7)); - /// info.chunk_height_pixels(2).unwrap_err(); - /// ``` - pub fn chunk_height_pixels(&self, y: u32) -> AsyncTiffResult { + fn chunk_height_pixels(&self, y: u32) -> AsyncTiffResult { if y >= self.chunks_down() { Err(crate::error::AsyncTiffError::TileIndexError( y, @@ -147,39 +110,17 @@ impl PredictorInfo { } /// get the output row stride in bytes, taking padding into account - pub fn output_row_stride(&self, x: u32) -> AsyncTiffResult { + fn output_row_stride(&self, x: u32) -> AsyncTiffResult { Ok((self.chunk_width_pixels(x)? as usize).saturating_mul(self.bits_per_sample as _) / 8) } - // /// The total number of bits per pixel, taking into account possible different sample sizes - // /// - // /// Technically bits_per_sample.len() should be *equal* to samples, but libtiff also allows - // /// it to be a single value that applies to all samples. - // /// - // /// Libtiff and image-tiff do not support mixed bits per sample, but we give the possibility - // /// unless you also have PlanarConfiguration::Planar, at which point the first is taken - // pub fn bits_per_pixel(&self) -> usize { - // self.bits_per_sample - // match self.planar_configuration { - // PlanarConfiguration::Chunky => { - // if self.bits_per_sample.len() == 1 { - // self.samples_per_pixel as usize * self.bits_per_sample[0] as usize - // } else { - // assert_eq!(self.samples_per_pixel as usize, self.bits_per_sample.len()); - // self.bits_per_sample.iter().map(|v| *v as usize).product() - // } - // } - // PlanarConfiguration::Planar => self.bits_per_sample[0] as usize, - // } - // } - /// The number of chunks in the horizontal (x) direction - pub fn chunks_across(&self) -> u32 { + fn chunks_across(&self) -> u32 { self.image_width.div_ceil(self.chunk_width) } /// The number of chunks in the vertical (y) direction - pub fn chunks_down(&self) -> u32 { + fn chunks_down(&self) -> u32 { self.image_height.div_ceil(self.chunk_height) } } From 2ac868af11151c8ec2ebb0dfd6cea6ed3329fff2 Mon Sep 17 00:00:00 2001 From: Kyle Barron Date: Wed, 2 Apr 2025 16:35:46 -0400 Subject: [PATCH 15/23] Only call chunks_* once --- src/predictor.rs | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/src/predictor.rs b/src/predictor.rs index 7095e5b..aaaeb29 100644 --- a/src/predictor.rs +++ b/src/predictor.rs @@ -2,8 +2,8 @@ use std::fmt::Debug; use bytes::{Bytes, BytesMut}; -// use tiff::decoder::DecodingResult; +use crate::error::AsyncTiffError; use crate::ImageFileDirectory; use crate::{error::AsyncTiffResult, reader::Endianness}; @@ -78,12 +78,10 @@ impl PredictorInfo { /// /// strips are considered image-width chunks fn chunk_width_pixels(&self, x: u32) -> AsyncTiffResult { - if x >= self.chunks_across() { - Err(crate::error::AsyncTiffError::TileIndexError( - x, - self.chunks_across(), - )) - } else if x == self.chunks_across() - 1 { + let chunks_across = self.chunks_across(); + if x >= chunks_across { + Err(AsyncTiffError::TileIndexError(x, chunks_across)) + } else if x == chunks_across - 1 { // last chunk Ok(self.image_width - self.chunk_width * x) } else { @@ -94,14 +92,11 @@ impl PredictorInfo { /// chunk height in pixels, taking padding into account /// /// strips are considered image-width chunks - /// fn chunk_height_pixels(&self, y: u32) -> AsyncTiffResult { - if y >= self.chunks_down() { - Err(crate::error::AsyncTiffError::TileIndexError( - y, - self.chunks_down(), - )) - } else if y == self.chunks_down() - 1 { + let chunks_down = self.chunks_down(); + if y >= chunks_down { + Err(AsyncTiffError::TileIndexError(y, chunks_down)) + } else if y == chunks_down - 1 { // last chunk Ok(self.image_height - self.chunk_height * y) } else { From be74506bf2df9b6b4b24d2d7e8811c838492ace6 Mon Sep 17 00:00:00 2001 From: Kyle Barron Date: Wed, 2 Apr 2025 16:45:54 -0400 Subject: [PATCH 16/23] Ensure no copies when endianness matches system endianness --- src/predictor.rs | 78 +++++++++++++++++++++++++++--------------------- 1 file changed, 44 insertions(+), 34 deletions(-) diff --git a/src/predictor.rs b/src/predictor.rs index aaaeb29..e91b4cf 100644 --- a/src/predictor.rs +++ b/src/predictor.rs @@ -144,13 +144,11 @@ impl Unpredict for NoPredictor { _: u32, _: u32, ) -> AsyncTiffResult { - let mut res = BytesMut::from(buffer); - fix_endianness( - &mut res[..], + Ok(fix_endianness( + buffer, predictor_info.endianness, predictor_info.bits_per_sample, - ); - Ok(res.into()) + )) } } @@ -170,8 +168,8 @@ impl Unpredict for HorizontalPredictor { 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); - fix_endianness(&mut res[..], predictor_info.endianness, bit_depth); for buf in res.chunks_mut(output_row_stride) { rev_hpredict_nsamp(buf, bit_depth, samples); } @@ -221,33 +219,47 @@ 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. /// /// from image-tiff -pub fn fix_endianness(buf: &mut [u8], byte_order: Endianness, bit_depth: u16) { +pub fn fix_endianness(buffer: Bytes, byte_order: Endianness, bit_depth: u16) -> Bytes { match byte_order { - Endianness::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()) - }), - }, - Endianness::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()) - }), - }, - }; + #[cfg(target_endian = "little")] + Endianness::LittleEndian => buffer, + #[cfg(target_endian = "big")] + 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() + } + #[cfg(target_endian = "little")] + Endianness::BigEndian => { + 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_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()) + }), + }; + buf.freeze() + } + #[cfg(target_endian = "big")] + Endianness::BigEndian => buffer, + } } /// Floating point predictor @@ -318,7 +330,6 @@ impl Unpredict for FloatingPointPredictor { /// floating point prediction first shuffles the bytes and then uses horizontal /// differencing /// also performs byte-order conversion if needed. -/// pub fn rev_predict_f16(input: &mut [u8], output: &mut [u8], samples: usize) { // reverse horizontal differencing for i in samples..input.len() { @@ -339,7 +350,6 @@ pub fn rev_predict_f16(input: &mut [u8], output: &mut [u8], samples: usize) { /// floating point prediction first shuffles the bytes and then uses horizontal /// differencing /// also performs byte-order conversion if needed. -/// pub fn rev_predict_f32(input: &mut [u8], output: &mut [u8], samples: usize) { // reverse horizontal differencing for i in samples..input.len() { From a0ff0337352dd26788a4db51d0a910460bf06ec0 Mon Sep 17 00:00:00 2001 From: Fee Fladder Date: Thu, 3 Apr 2025 01:59:39 +0200 Subject: [PATCH 17/23] added planar_configuration back, updated bits_per_pixel and added tests --- src/predictor.rs | 92 ++++++++++++++++++++++++++++++++++-------------- 1 file changed, 66 insertions(+), 26 deletions(-) diff --git a/src/predictor.rs b/src/predictor.rs index e91b4cf..6086009 100644 --- a/src/predictor.rs +++ b/src/predictor.rs @@ -4,6 +4,7 @@ use std::fmt::Debug; use bytes::{Bytes, BytesMut}; use crate::error::AsyncTiffError; +use crate::tiff::tags::PlanarConfiguration; use crate::ImageFileDirectory; use crate::{error::AsyncTiffResult, reader::Endianness}; @@ -34,6 +35,8 @@ pub(crate) struct PredictorInfo { bits_per_sample: u16, /// number of samples per pixel samples_per_pixel: u16, + + planar_configuration: PlanarConfiguration, } impl PredictorInfo { @@ -60,15 +63,7 @@ impl PredictorInfo { image_height: ifd.image_height, chunk_width, chunk_height, - // TODO: validate this? Restore handling for different PlanarConfiguration? - // PlanarConfiguration::Chunky => { - // if self.bits_per_sample.len() == 1 { - // self.samples_per_pixel as usize * self.bits_per_sample[0] as usize - // } else { - // assert_eq!(self.samples_per_pixel as usize, self.bits_per_sample.len()); - // self.bits_per_sample.iter().map(|v| *v as usize).product() - // } - // } + planar_configuration: ifd.planar_configuration, bits_per_sample: ifd.bits_per_sample[0], samples_per_pixel: ifd.samples_per_pixel, } @@ -106,7 +101,27 @@ impl PredictorInfo { /// get the output row stride in bytes, taking padding into account fn output_row_stride(&self, x: u32) -> AsyncTiffResult { - Ok((self.chunk_width_pixels(x)? as usize).saturating_mul(self.bits_per_sample as _) / 8) + Ok((self.chunk_width_pixels(x)? as usize).saturating_mul(self.bits_per_pixel()) / 8) + } + + /// the number of rows the output has, taking padding and PlanarConfiguration into account. + fn output_rows(&self, y: u32) -> AsyncTiffResult { + match self.planar_configuration { + PlanarConfiguration::Chunky => Ok(self.chunk_height_pixels(y)? as usize), + PlanarConfiguration::Planar => { + Ok((self.chunk_height_pixels(y)? as usize) + .saturating_mul(self.samples_per_pixel as _)) + } + } + } + + fn bits_per_pixel(&self) -> usize { + match self.planar_configuration { + PlanarConfiguration::Chunky => { + self.bits_per_sample as usize * self.samples_per_pixel as usize + } + PlanarConfiguration::Planar => self.bits_per_sample as usize, + } } /// The number of chunks in the horizontal (x) direction @@ -275,9 +290,8 @@ impl Unpredict for FloatingPointPredictor { tile_y: u32, ) -> AsyncTiffResult { let output_row_stride = predictor_info.output_row_stride(tile_x)?; - let mut res: BytesMut = BytesMut::zeroed( - output_row_stride * predictor_info.chunk_height_pixels(tile_y)? as usize, - ); + let mut res: BytesMut = + BytesMut::zeroed(output_row_stride * predictor_info.output_rows(tile_y)? as usize); let bit_depth = predictor_info.bits_per_sample; if predictor_info.chunk_width_pixels(tile_x)? == predictor_info.chunk_width { // no special padding handling @@ -420,6 +434,7 @@ mod test { chunk_height: 4, bits_per_sample: 8, samples_per_pixel: 1, + planar_configuration: PlanarConfiguration::Chunky, }; #[rustfmt::skip] const RES: [u8;16] = [ @@ -454,23 +469,44 @@ mod test { #[test] fn test_chunk_width_pixels() { - let info = PredictorInfo { - endianness: Endianness::LittleEndian, - image_width: 15, - image_height: 17, - chunk_width: 8, - chunk_height: 8, - bits_per_sample: 8, - samples_per_pixel: 1, - }; + let info = PRED_INFO; assert_eq!(info.chunks_across(), 2); - assert_eq!(info.chunks_down(), 3); + assert_eq!(info.chunks_down(), 2); assert_eq!(info.chunk_width_pixels(0).unwrap(), info.chunk_width); - assert_eq!(info.chunk_width_pixels(1).unwrap(), 7); + assert_eq!(info.chunk_width_pixels(1).unwrap(), 3); info.chunk_width_pixels(2).unwrap_err(); assert_eq!(info.chunk_height_pixels(0).unwrap(), info.chunk_height); - assert_eq!(info.chunk_height_pixels(2).unwrap(), 1); - info.chunk_height_pixels(3).unwrap_err(); + assert_eq!(info.chunk_height_pixels(1).unwrap(), 3); + info.chunk_height_pixels(2).unwrap_err(); + } + + #[test] + fn test_output_row_stride() { + let mut info = PRED_INFO; + assert_eq!(info.output_row_stride(0).unwrap(), 4); + assert_eq!(info.output_row_stride(1).unwrap(), 3); + info.output_row_stride(2).unwrap_err(); + info.samples_per_pixel = 2; + assert_eq!(info.output_row_stride(0).unwrap(), 8); + assert_eq!(info.output_row_stride(1).unwrap(), 6); + info.bits_per_sample = 16; + assert_eq!(info.output_row_stride(0).unwrap(), 16); + assert_eq!(info.output_row_stride(1).unwrap(), 12); + info.planar_configuration = PlanarConfiguration::Planar; + assert_eq!(info.output_row_stride(0).unwrap(), 8); + assert_eq!(info.output_row_stride(1).unwrap(), 6); + } + + #[test] + fn test_output_rows() { + let mut info = PRED_INFO; + info.samples_per_pixel = 2; + assert_eq!(info.output_rows(0).unwrap(), 4); + assert_eq!(info.output_rows(1).unwrap(), 3); + info.output_rows(2).unwrap_err(); + info.planar_configuration = PlanarConfiguration::Planar; + assert_eq!(info.output_rows(0).unwrap(), 8); + assert_eq!(info.output_rows(1).unwrap(), 6); } #[rustfmt::skip] @@ -644,6 +680,7 @@ mod test { chunk_height: 4, bits_per_sample: 16, samples_per_pixel: 1, + planar_configuration: PlanarConfiguration::Chunky, }; let input = Bytes::from_owner(diffed); assert_eq!( @@ -672,6 +709,7 @@ mod test { chunk_height: 4, bits_per_sample: 16, samples_per_pixel: 1, + planar_configuration: PlanarConfiguration::Chunky, }; let input = Bytes::from_owner(diffed); assert_eq!( @@ -699,6 +737,7 @@ mod test { chunk_height: 2, bits_per_sample: 32, samples_per_pixel: 1, + planar_configuration: PlanarConfiguration::Chunky, }; let input = Bytes::from_owner(diffed); assert_eq!( @@ -732,6 +771,7 @@ mod test { chunk_height: 2, bits_per_sample: 64, samples_per_pixel: 1, + planar_configuration: PlanarConfiguration::Chunky, }; let input = Bytes::from_owner(diffed); assert_eq!( From 26176f96955536d3ed7c801cf59bc5b480105641 Mon Sep 17 00:00:00 2001 From: Fee Fladder Date: Thu, 3 Apr 2025 02:08:15 +0200 Subject: [PATCH 18/23] silly clippy things --- src/predictor.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/predictor.rs b/src/predictor.rs index 6086009..b422e27 100644 --- a/src/predictor.rs +++ b/src/predictor.rs @@ -291,7 +291,7 @@ impl Unpredict for FloatingPointPredictor { ) -> 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)? as usize); + 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 From 111630b45567dcbb2cf3eb033739de2e4fe766fa Mon Sep 17 00:00:00 2001 From: Fee Fladder Date: Thu, 3 Apr 2025 06:36:18 +0200 Subject: [PATCH 19/23] removed UnPredict trait in favour of separate functions; small change to fix_endianness --- src/predictor.rs | 289 +++++++++++++++++++++-------------------------- src/tile.rs | 31 ++--- 2 files changed, 140 insertions(+), 180 deletions(-) diff --git a/src/predictor.rs b/src/predictor.rs index b422e27..64672bf 100644 --- a/src/predictor.rs +++ b/src/predictor.rs @@ -40,6 +40,14 @@ pub(crate) struct PredictorInfo { } impl PredictorInfo { + pub(crate) fn endianness(&self) -> Endianness { + self.endianness + } + + pub(crate) fn bits_per_sample(&self) -> u16 { + self.bits_per_sample + } + pub(crate) fn from_ifd(ifd: &ImageFileDirectory) -> Self { if !ifd.bits_per_sample.windows(2).all(|w| w[0] == w[1]) { panic!("bits_per_sample should be the same for all channels"); @@ -135,64 +143,27 @@ impl PredictorInfo { } } -/// Trait for reverse predictors to implement -pub(crate) trait Unpredict: Debug + Send + Sync { - /// reverse predict the decompressed bytes and fix endianness on the output - fn fix_endianness_and_unpredict( - &self, - buffer: Bytes, - predictor_info: &PredictorInfo, - tile_x: u32, - tile_y: u32, - ) -> AsyncTiffResult; // having this Bytes will give alignment issues later on -} - -/// no predictor -#[derive(Debug)] -pub struct NoPredictor; - -impl Unpredict for NoPredictor { - fn fix_endianness_and_unpredict( - &self, - buffer: Bytes, - predictor_info: &PredictorInfo, - _: u32, - _: u32, - ) -> AsyncTiffResult { - Ok(fix_endianness( - buffer, - predictor_info.endianness, - predictor_info.bits_per_sample, - )) - } -} - /// reverse horizontal predictor -#[derive(Debug)] -pub struct HorizontalPredictor; - -impl Unpredict for HorizontalPredictor { - fn fix_endianness_and_unpredict( - &self, - buffer: Bytes, - predictor_info: &PredictorInfo, - tile_x: u32, - _: u32, - ) -> 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) { - rev_hpredict_nsamp(buf, bit_depth, samples); - } - Ok(res.into()) +/// +/// fixes byte order before reversing differencing +pub(crate) fn unpredict_hdiff( + buffer: Bytes, + predictor_info: &PredictorInfo, + tile_x: u32, +) -> 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) { + rev_hpredict_nsamp(buf, bit_depth, samples); } + Ok(res.into()) } -/// Reverse predictor convenienve function for horizontal differencing +/// Reverse predictor convenience function for horizontal differencing /// /// From image-tiff /// @@ -235,10 +206,16 @@ pub fn rev_hpredict_nsamp(buf: &mut [u8], bit_depth: u16, samples: usize) { /// /// from image-tiff pub fn fix_endianness(buffer: Bytes, byte_order: Endianness, bit_depth: u16) -> Bytes { + #[cfg(target_endian = "little")] + if let Endianness::LittleEndian = byte_order { + return buffer; + } + #[cfg(target_endian = "big")] + if let Endianness::BigEndian = byte_order { + return buffer; + } + match byte_order { - #[cfg(target_endian = "little")] - Endianness::LittleEndian => buffer, - #[cfg(target_endian = "big")] Endianness::LittleEndian => { let mut buf = BytesMut::from(buffer); match bit_depth { @@ -255,7 +232,6 @@ pub fn fix_endianness(buffer: Bytes, byte_order: Endianness, bit_depth: u16) -> } buf.freeze() } - #[cfg(target_endian = "little")] Endianness::BigEndian => { let mut buf = BytesMut::from(buffer); match bit_depth { @@ -272,71 +248,70 @@ pub fn fix_endianness(buffer: Bytes, byte_order: Endianness, bit_depth: u16) -> }; buf.freeze() } - #[cfg(target_endian = "big")] - Endianness::BigEndian => buffer, } } -/// Floating point predictor -#[derive(Debug)] -pub struct FloatingPointPredictor; - -impl Unpredict for FloatingPointPredictor { - fn fix_endianness_and_unpredict( - &self, - buffer: Bytes, - predictor_info: &PredictorInfo, - tile_x: u32, - tile_y: u32, - ) -> 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 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 - .chunks_mut(output_row_stride) - .zip(res.chunks_mut(output_row_stride)) - { - match bit_depth { - 16 => rev_predict_f16(in_buf, out_buf, predictor_info.samples_per_pixel as _), - 32 => rev_predict_f32(in_buf, out_buf, predictor_info.samples_per_pixel as _), - 64 => rev_predict_f64(in_buf, out_buf, predictor_info.samples_per_pixel as _), - _ => panic!("thou shalt not predict f24"), +/// Reverse a floating-point prediction +/// +/// According to [the spec](http://chriscox.org/TIFFTN3d1.pdf), no external +/// byte-ordering should be done. +/// +/// If the tile has horizontal padding, it will shorten the output. +pub(crate) fn unpredict_float( + buffer: Bytes, + predictor_info: &PredictorInfo, + tile_x: u32, + tile_y: u32, +) -> 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 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 + .chunks_mut(output_row_stride) + .zip(res.chunks_mut(output_row_stride)) + { + match bit_depth { + 16 => rev_predict_f16(in_buf, out_buf, predictor_info.samples_per_pixel as _), + 32 => rev_predict_f32(in_buf, out_buf, predictor_info.samples_per_pixel as _), + 64 => rev_predict_f64(in_buf, out_buf, predictor_info.samples_per_pixel as _), + _ => { + return Err(AsyncTiffError::General(format!( + "thou shalt not predict f{bit_depth:?}" + ))) } } - } 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 - .chunks_mut(input_row_stride) - .zip(res.chunks_mut(output_row_stride)) - { - let mut out_row = BytesMut::zeroed(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 _) - } - 64 => { - rev_predict_f64(in_buf, &mut out_row, predictor_info.samples_per_pixel as _) - } - _ => panic!("thou shalt not predict f24"), + } + } 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 + .chunks_mut(input_row_stride) + .zip(res.chunks_mut(output_row_stride)) + { + let mut out_row = BytesMut::zeroed(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 _), + 64 => rev_predict_f64(in_buf, &mut out_row, predictor_info.samples_per_pixel as _), + _ => { + return Err(AsyncTiffError::General(format!( + "thou shalt not predict f{bit_depth:?}" + ))) } - // remove the padding bytes - out_buf.copy_from_slice(&out_row[..output_row_stride]); } + // remove the padding bytes + out_buf.copy_from_slice(&out_row[..output_row_stride]); } - Ok(res.into()) } + Ok(res.into()) } /// Reverse floating point prediction @@ -422,7 +397,10 @@ mod test { use bytes::Bytes; - use crate::{predictor::FloatingPointPredictor, reader::Endianness}; + use crate::{ + predictor::{unpredict_float, unpredict_hdiff}, + reader::Endianness, + }; use super::*; @@ -509,25 +487,23 @@ mod test { assert_eq!(info.output_rows(1).unwrap(), 6); } - #[rustfmt::skip] - #[test] - fn test_no_predict() { - let p = NoPredictor; - let cases = [ - (0,0, Bytes::from_static(&RES[..]), Bytes::from_static(&RES[..]) ), - (0,1, Bytes::from_static(&RES_BOT[..]), Bytes::from_static(&RES_BOT[..]) ), - (1,0, Bytes::from_static(&RES_RIGHT[..]), Bytes::from_static(&RES_RIGHT[..]) ), - (1,1, Bytes::from_static(&RES_BOT_RIGHT[..]), Bytes::from_static(&RES_BOT_RIGHT[..])) - ]; - for (x,y, input, expected) in cases { - assert_eq!(p.fix_endianness_and_unpredict(input, &PRED_INFO, x, y).unwrap(), expected); - } - } + // #[rustfmt::skip] + // #[test] + // fn test_no_predict() { + // let cases = [ + // (0,0, Bytes::from_static(&RES[..]), Bytes::from_static(&RES[..]) ), + // (0,1, Bytes::from_static(&RES_BOT[..]), Bytes::from_static(&RES_BOT[..]) ), + // (1,0, Bytes::from_static(&RES_RIGHT[..]), Bytes::from_static(&RES_RIGHT[..]) ), + // (1,1, Bytes::from_static(&RES_BOT_RIGHT[..]), Bytes::from_static(&RES_BOT_RIGHT[..])) + // ]; + // for (x,y, input, expected) in cases { + // assert_eq!(fix_endianness(input, &PRED_INFO, x, y).unwrap(), expected); + // } + // } #[rustfmt::skip] #[test] - fn test_hpredict() { - let p = HorizontalPredictor; + fn test_hdiff_unpredict() { let mut predictor_info = PRED_INFO; let cases = [ (0,0, vec![ @@ -553,7 +529,7 @@ mod test { 2,-1,-1, ], Vec::from(&RES_BOT_RIGHT[..])), ]; - for (x,y, input, expected) in cases { + for (x,_y, input, expected) in cases { println!("uints littleendian"); predictor_info.endianness = Endianness::LittleEndian; predictor_info.bits_per_sample = 8; @@ -561,25 +537,25 @@ mod test { println!("testing u8"); let buffer = Bytes::from(input.iter().map(|v| *v as u8).collect::>()); let res = Bytes::from(expected.clone()); - assert_eq!(p.fix_endianness_and_unpredict(buffer, &predictor_info, x, y).unwrap(), res); + assert_eq!(unpredict_hdiff(buffer, &predictor_info, x).unwrap(), res); 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!(p.fix_endianness_and_unpredict(buffer, &predictor_info, x, y).unwrap(), res); + assert_eq!(unpredict_hdiff(buffer, &predictor_info, x).unwrap(), res); 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!(p.fix_endianness_and_unpredict(buffer, &predictor_info, x, y).unwrap(), res); + assert_eq!(unpredict_hdiff(buffer, &predictor_info, x).unwrap(), res); 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!(p.fix_endianness_and_unpredict(buffer, &predictor_info, x, y).unwrap(), res); + assert_eq!(unpredict_hdiff(buffer, &predictor_info, x).unwrap(), res); println!("ints littleendian"); predictor_info.bits_per_sample = 8; @@ -587,22 +563,22 @@ mod test { 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!(p.fix_endianness_and_unpredict(buffer, &predictor_info, x, y).unwrap()[..], res[..]); + assert_eq!(unpredict_hdiff(buffer, &predictor_info, x).unwrap()[..], res[..]); 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!(p.fix_endianness_and_unpredict(buffer, &predictor_info, x, y).unwrap(), res); + assert_eq!(unpredict_hdiff(buffer, &predictor_info, x).unwrap(), res); 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!(p.fix_endianness_and_unpredict(buffer, &predictor_info, x, y).unwrap(), res); + assert_eq!(unpredict_hdiff(buffer, &predictor_info, x).unwrap(), res); 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!(p.fix_endianness_and_unpredict(buffer, &predictor_info, x, y).unwrap(), res); + assert_eq!(unpredict_hdiff(buffer, &predictor_info, x).unwrap(), res); println!("uints bigendian"); predictor_info.endianness = Endianness::BigEndian; @@ -611,26 +587,26 @@ mod test { println!("testing u8"); let buffer = Bytes::from(input.iter().map(|v| *v as u8).collect::>()); let res = Bytes::from(expected.clone()); - assert_eq!(p.fix_endianness_and_unpredict(buffer, &predictor_info, x, y).unwrap(), res); + assert_eq!(unpredict_hdiff(buffer, &predictor_info, x).unwrap(), res); 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!(p.fix_endianness_and_unpredict(buffer, &predictor_info, x, y).unwrap()[..], res[..]); + assert_eq!(unpredict_hdiff(buffer, &predictor_info, x).unwrap()[..], res[..]); 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!(p.fix_endianness_and_unpredict(buffer, &predictor_info, x, y).unwrap(), res); + assert_eq!(unpredict_hdiff(buffer, &predictor_info, x).unwrap(), res); 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!(p.fix_endianness_and_unpredict(buffer, &predictor_info, x, y).unwrap(), res); + assert_eq!(unpredict_hdiff(buffer, &predictor_info, x).unwrap(), res); println!("ints bigendian"); predictor_info.bits_per_sample = 8; @@ -638,25 +614,25 @@ mod test { 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!(p.fix_endianness_and_unpredict(buffer, &predictor_info, x, y).unwrap(), res); + assert_eq!(unpredict_hdiff(buffer, &predictor_info, x).unwrap(), res); 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!(p.fix_endianness_and_unpredict(buffer, &predictor_info, x, y).unwrap(), res); + assert_eq!(unpredict_hdiff(buffer, &predictor_info, x).unwrap(), res); 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!(p.fix_endianness_and_unpredict(buffer, &predictor_info, x, y).unwrap(), res); + assert_eq!(unpredict_hdiff(buffer, &predictor_info, x).unwrap(), res); 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!(p.fix_endianness_and_unpredict(buffer, &predictor_info, x, y).unwrap(), res); + assert_eq!(unpredict_hdiff(buffer, &predictor_info, x).unwrap(), res); } } @@ -684,7 +660,7 @@ mod test { }; let input = Bytes::from_owner(diffed); assert_eq!( - &FloatingPointPredictor.fix_endianness_and_unpredict(input, &info, 1, 1).unwrap()[..], + &unpredict_float(input, &info, 1, 1).unwrap()[..], &expect_le[..] ) } @@ -713,7 +689,7 @@ mod test { }; let input = Bytes::from_owner(diffed); assert_eq!( - &FloatingPointPredictor.fix_endianness_and_unpredict(input, &info, 1, 1).unwrap()[..], + &unpredict_float(input, &info, 1, 1).unwrap()[..], &expect_le[..] ) } @@ -729,7 +705,7 @@ mod test { let _shuffled = [0,4, 1,5, 2,6, 3,7u8]; let diffed = [0,4,253,4,253,4,253,4u8]; println!("expected: {expect_le:?}"); - let mut info = PredictorInfo { + let info = PredictorInfo { endianness: Endianness::LittleEndian, image_width: 2, image_height: 2 + 1, @@ -741,15 +717,9 @@ mod test { }; let input = Bytes::from_owner(diffed); assert_eq!( - &FloatingPointPredictor - .fix_endianness_and_unpredict(input.clone(), &info, 0, 1).unwrap()[..], + &unpredict_float(input.clone(), &info, 0, 1).unwrap()[..], &expect_le ); - info.endianness = Endianness::BigEndian; - assert_eq!( - &FloatingPointPredictor.fix_endianness_and_unpredict(input, &info, 0, 1).unwrap()[..], - &expect_le - ) } #[rustfmt::skip] @@ -775,8 +745,7 @@ mod test { }; let input = Bytes::from_owner(diffed); assert_eq!( - &FloatingPointPredictor - .fix_endianness_and_unpredict(input, &info, 0, 1) + &unpredict_float(input, &info, 0, 1) .unwrap()[..], &expect_be[..] ); diff --git a/src/tile.rs b/src/tile.rs index f6c50ec..402cf5a 100644 --- a/src/tile.rs +++ b/src/tile.rs @@ -2,9 +2,7 @@ use bytes::Bytes; use crate::decoder::DecoderRegistry; use crate::error::AsyncTiffResult; -use crate::predictor::{ - FloatingPointPredictor, HorizontalPredictor, NoPredictor, PredictorInfo, Unpredict, -}; +use crate::predictor::{fix_endianness, unpredict_float, unpredict_hdiff, PredictorInfo}; use crate::tiff::tags::{CompressionMethod, PhotometricInterpretation, Predictor}; use crate::tiff::{TiffError, TiffUnsupportedError}; @@ -80,24 +78,17 @@ impl Tile { )?; match self.predictor { - Predictor::None => NoPredictor.fix_endianness_and_unpredict( + Predictor::None => Ok(fix_endianness( decoded_tile, - &self.predictor_info, - self.x as _, - self.y as _, - ), - Predictor::Horizontal => HorizontalPredictor.fix_endianness_and_unpredict( - decoded_tile, - &self.predictor_info, - self.x as _, - self.y as _, - ), - Predictor::FloatingPoint => FloatingPointPredictor.fix_endianness_and_unpredict( - decoded_tile, - &self.predictor_info, - self.x as _, - self.y as _, - ), + self.predictor_info.endianness(), + self.predictor_info.bits_per_sample(), + )), + Predictor::Horizontal => { + unpredict_hdiff(decoded_tile, &self.predictor_info, self.x as _) + } + Predictor::FloatingPoint => { + unpredict_float(decoded_tile, &self.predictor_info, self.x as _, self.y as _) + } } } } From 73d4894e113e3f009fad90be482a14c3625062b4 Mon Sep 17 00:00:00 2001 From: Fee Fladder Date: Thu, 3 Apr 2025 15:17:45 +0200 Subject: [PATCH 20/23] added doc comment clarifying that strips are also tiles --- src/tile.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/tile.rs b/src/tile.rs index 402cf5a..3f52b55 100644 --- a/src/tile.rs +++ b/src/tile.rs @@ -12,6 +12,8 @@ use crate::tiff::{TiffError, TiffUnsupportedError}; /// so that sync and async operations can be separated and non-blocking. /// /// This is returned by `fetch_tile`. +/// +/// A strip of a stripped tiff is an image-width, rows-per-strip tile. #[derive(Debug)] pub struct Tile { pub(crate) x: usize, From 5286e171c11561543e114aa04a0ca13ec9386932 Mon Sep 17 00:00:00 2001 From: Fee Fladder <33122845+feefladder@users.noreply.github.com> Date: Tue, 10 Jun 2025 12:56:30 +0200 Subject: [PATCH 21/23] Update src/tiff/error.rs Co-authored-by: Wei Ji <23487320+weiji14@users.noreply.github.com> --- src/tiff/error.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tiff/error.rs b/src/tiff/error.rs index 47a6744..d8ab21d 100644 --- a/src/tiff/error.rs +++ b/src/tiff/error.rs @@ -196,7 +196,7 @@ impl fmt::Display for TiffUnsupportedError { write!(fmt, "Compression method {:?} is unsupported", method) } UnsupportedPredictor(p) => { - write!(fmt, "Predictor {p:?} is not supported") + write!(fmt, "Predictor {p:?} is unsupported") } UnsupportedSampleDepth(samples) => { write!(fmt, "{} samples per pixel is unsupported.", samples) From dc46f8a5a4ffa3fe13f2411500a8272a5aded588 Mon Sep 17 00:00:00 2001 From: Fee Fladder <33122845+feefladder@users.noreply.github.com> Date: Tue, 10 Jun 2025 13:01:37 +0200 Subject: [PATCH 22/23] Apply suggestions from @weji14 Co-authored-by: Wei Ji <23487320+weiji14@users.noreply.github.com> --- src/predictor.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/predictor.rs b/src/predictor.rs index 64672bf..18a732d 100644 --- a/src/predictor.rs +++ b/src/predictor.rs @@ -165,7 +165,7 @@ pub(crate) fn unpredict_hdiff( /// Reverse predictor convenience function for horizontal differencing /// -/// From image-tiff +// From image-tiff /// /// This should be used _after_ endianness fixing pub fn rev_hpredict_nsamp(buf: &mut [u8], bit_depth: u16, samples: usize) { @@ -204,7 +204,7 @@ 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. /// -/// from image-tiff +// from image-tiff pub fn fix_endianness(buffer: Bytes, byte_order: Endianness, bit_depth: u16) -> Bytes { #[cfg(target_endian = "little")] if let Endianness::LittleEndian = byte_order { From 9b3afc8a682dfc9bb6b6507874caa7ff053bd8e4 Mon Sep 17 00:00:00 2001 From: Fee Fladder Date: Tue, 10 Jun 2025 13:14:18 +0200 Subject: [PATCH 23/23] made floating point predictor(s) use --- src/predictor.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/predictor.rs b/src/predictor.rs index 18a732d..f897b80 100644 --- a/src/predictor.rs +++ b/src/predictor.rs @@ -325,7 +325,7 @@ pub fn rev_predict_f16(input: &mut [u8], output: &mut [u8], samples: usize) { input[i] = input[i].wrapping_add(input[i - samples]); } // reverse byte shuffle and fix endianness - for (i, chunk) in output.chunks_mut(2).enumerate() { + for (i, chunk) in output.chunks_exact_mut(2).enumerate() { chunk.copy_from_slice(&u16::to_ne_bytes( // convert to native-endian // floating predictor is be-like @@ -345,7 +345,7 @@ pub fn rev_predict_f32(input: &mut [u8], output: &mut [u8], samples: usize) { input[i] = input[i].wrapping_add(input[i - samples]); } // reverse byte shuffle and fix endianness - for (i, chunk) in output.chunks_mut(4).enumerate() { + for (i, chunk) in output.chunks_exact_mut(4).enumerate() { chunk.copy_from_slice( // convert to native-endian &u32::to_ne_bytes( @@ -371,7 +371,7 @@ pub fn rev_predict_f64(input: &mut [u8], output: &mut [u8], samples: usize) { input[i] = input[i].wrapping_add(input[i - samples]); } - for (i, chunk) in output.chunks_mut(8).enumerate() { + for (i, chunk) in output.chunks_exact_mut(8).enumerate() { chunk.copy_from_slice( // convert to native-endian &u64::to_ne_bytes(