@@ -4,7 +4,185 @@ use std::fmt::Debug;
44use bytes:: { Bytes , BytesMut } ;
55// use tiff::decoder::DecodingResult;
66
7- use crate :: { error:: AsyncTiffResult , reader:: Endianness , tile:: PredictorInfo } ;
7+ use crate :: ImageFileDirectory ;
8+ use crate :: { error:: AsyncTiffResult , reader:: Endianness } ;
9+
10+ /// All info that may be used by a predictor
11+ ///
12+ /// Most of this is used by the floating point predictor
13+ /// since that intermixes padding into the decompressed output
14+ ///
15+ /// Also provides convenience functions
16+ ///
17+ #[ derive( Debug , Clone , Copy ) ]
18+ pub ( crate ) struct PredictorInfo {
19+ /// endianness
20+ endianness : Endianness ,
21+ /// width of the image in pixels
22+ image_width : u32 ,
23+ /// height of the image in pixels
24+ image_height : u32 ,
25+ /// chunk width in pixels
26+ ///
27+ /// If this is a stripped tiff, `chunk_width=image_width`
28+ chunk_width : u32 ,
29+ /// chunk height in pixels
30+ chunk_height : u32 ,
31+ /// bits per sample
32+ ///
33+ /// We only support a single bits_per_sample across all samples
34+ bits_per_sample : u16 ,
35+ /// number of samples per pixel
36+ samples_per_pixel : u16 ,
37+ }
38+
39+ impl PredictorInfo {
40+ pub ( crate ) fn from_ifd ( ifd : & ImageFileDirectory ) -> Self {
41+ if !ifd. bits_per_sample . windows ( 2 ) . all ( |w| w[ 0 ] == w[ 1 ] ) {
42+ panic ! ( "bits_per_sample should be the same for all channels" ) ;
43+ }
44+
45+ let chunk_width = if let Some ( tile_width) = ifd. tile_width {
46+ tile_width
47+ } else {
48+ ifd. image_width
49+ } ;
50+ let chunk_height = if let Some ( tile_height) = ifd. tile_height {
51+ tile_height
52+ } else {
53+ ifd. rows_per_strip
54+ . expect ( "no tile height and no rows_per_strip" )
55+ } ;
56+
57+ PredictorInfo {
58+ endianness : ifd. endianness ,
59+ image_width : ifd. image_width ,
60+ image_height : ifd. image_height ,
61+ chunk_width,
62+ chunk_height,
63+ // TODO: validate this? Restore handling for different PlanarConfiguration?
64+ bits_per_sample : ifd. bits_per_sample [ 0 ] ,
65+ samples_per_pixel : ifd. samples_per_pixel ,
66+ }
67+ }
68+
69+ /// chunk width in pixels, taking padding into account
70+ ///
71+ /// strips are considered image-width chunks
72+ ///
73+ /// # Example
74+ ///
75+ /// ```rust
76+ /// # use async_tiff::tiff::tags::{SampleFormat, PlanarConfiguration};
77+ /// # use async_tiff::reader::Endianness;
78+ /// # use async_tiff::PredictorInfo;
79+ /// let info = PredictorInfo {
80+ /// # endianness: Endianness::LittleEndian,
81+ /// image_width: 15,
82+ /// image_height: 15,
83+ /// chunk_width: 8,
84+ /// chunk_height: 8,
85+ /// # bits_per_sample: &[32],
86+ /// # samples_per_pixel: 1,
87+ /// # sample_format: &[SampleFormat::IEEEFP],
88+ /// # planar_configuration: PlanarConfiguration::Chunky,
89+ /// };
90+ ///
91+ /// assert_eq!(info.chunk_width_pixels(0).unwrap(), (8));
92+ /// assert_eq!(info.chunk_width_pixels(1).unwrap(), (7));
93+ /// info.chunk_width_pixels(2).unwrap_err();
94+ /// ```
95+ fn chunk_width_pixels ( & self , x : u32 ) -> AsyncTiffResult < u32 > {
96+ if x >= self . chunks_across ( ) {
97+ Err ( crate :: error:: AsyncTiffError :: TileIndexError (
98+ x,
99+ self . chunks_across ( ) ,
100+ ) )
101+ } else if x == self . chunks_across ( ) - 1 {
102+ // last chunk
103+ Ok ( self . image_width - self . chunk_width * x)
104+ } else {
105+ Ok ( self . chunk_width )
106+ }
107+ }
108+
109+ /// chunk height in pixels, taking padding into account
110+ ///
111+ /// strips are considered image-width chunks
112+ ///
113+ /// # Example
114+ ///
115+ /// ```rust
116+ /// # use async_tiff::tiff::tags::{SampleFormat, PlanarConfiguration};
117+ /// # use async_tiff::reader::Endianness;
118+ /// # use async_tiff::PredictorInfo;
119+ /// let info = PredictorInfo {
120+ /// # endianness: Endianness::LittleEndian,
121+ /// image_width: 15,
122+ /// image_height: 15,
123+ /// chunk_width: 8,
124+ /// chunk_height: 8,
125+ /// # bits_per_sample: &[32],
126+ /// # samples_per_pixel: 1,
127+ /// # sample_format: &[SampleFormat::IEEEFP],
128+ /// # planar_configuration: PlanarConfiguration::Chunky,
129+ /// };
130+ ///
131+ /// assert_eq!(info.chunk_height_pixels(0).unwrap(), (8));
132+ /// assert_eq!(info.chunk_height_pixels(1).unwrap(), (7));
133+ /// info.chunk_height_pixels(2).unwrap_err();
134+ /// ```
135+ pub fn chunk_height_pixels ( & self , y : u32 ) -> AsyncTiffResult < u32 > {
136+ if y >= self . chunks_down ( ) {
137+ Err ( crate :: error:: AsyncTiffError :: TileIndexError (
138+ y,
139+ self . chunks_down ( ) ,
140+ ) )
141+ } else if y == self . chunks_down ( ) - 1 {
142+ // last chunk
143+ Ok ( self . image_height - self . chunk_height * y)
144+ } else {
145+ Ok ( self . chunk_height )
146+ }
147+ }
148+
149+ /// get the output row stride in bytes, taking padding into account
150+ pub fn output_row_stride ( & self , x : u32 ) -> AsyncTiffResult < usize > {
151+ Ok ( ( self . chunk_width_pixels ( x) ? as usize ) . saturating_mul ( self . bits_per_sample as _ ) / 8 )
152+ }
153+
154+ // /// The total number of bits per pixel, taking into account possible different sample sizes
155+ // ///
156+ // /// Technically bits_per_sample.len() should be *equal* to samples, but libtiff also allows
157+ // /// it to be a single value that applies to all samples.
158+ // ///
159+ // /// Libtiff and image-tiff do not support mixed bits per sample, but we give the possibility
160+ // /// unless you also have PlanarConfiguration::Planar, at which point the first is taken
161+ // pub fn bits_per_pixel(&self) -> usize {
162+ // self.bits_per_sample
163+ // match self.planar_configuration {
164+ // PlanarConfiguration::Chunky => {
165+ // if self.bits_per_sample.len() == 1 {
166+ // self.samples_per_pixel as usize * self.bits_per_sample[0] as usize
167+ // } else {
168+ // assert_eq!(self.samples_per_pixel as usize, self.bits_per_sample.len());
169+ // self.bits_per_sample.iter().map(|v| *v as usize).product()
170+ // }
171+ // }
172+ // PlanarConfiguration::Planar => self.bits_per_sample[0] as usize,
173+ // }
174+ // }
175+
176+ /// The number of chunks in the horizontal (x) direction
177+ pub fn chunks_across ( & self ) -> u32 {
178+ self . image_width . div_ceil ( self . chunk_width )
179+ }
180+
181+ /// The number of chunks in the vertical (y) direction
182+ pub fn chunks_down ( & self ) -> u32 {
183+ self . image_height . div_ceil ( self . chunk_height )
184+ }
185+ }
8186
9187/// Trait for reverse predictors to implement
10188pub ( crate ) trait Unpredict : Debug + Send + Sync {
@@ -284,9 +462,9 @@ mod test {
284462
285463 use bytes:: Bytes ;
286464
287- use crate :: { predictor:: FloatingPointPredictor , reader:: Endianness , tile :: PredictorInfo } ;
465+ use crate :: { predictor:: FloatingPointPredictor , reader:: Endianness } ;
288466
289- use super :: { HorizontalPredictor , NoPredictor , Unpredict } ;
467+ use super :: * ;
290468
291469 const PRED_INFO : PredictorInfo = PredictorInfo {
292470 endianness : Endianness :: LittleEndian ,
@@ -328,6 +506,27 @@ mod test {
328506 2 , 1 , 0 ,
329507 ] ;
330508
509+ #[ test]
510+ fn test_chunk_width_pixels ( ) {
511+ let info = PredictorInfo {
512+ endianness : Endianness :: LittleEndian ,
513+ image_width : 15 ,
514+ image_height : 17 ,
515+ chunk_width : 8 ,
516+ chunk_height : 8 ,
517+ bits_per_sample : 8 ,
518+ samples_per_pixel : 1 ,
519+ } ;
520+ assert_eq ! ( info. chunks_across( ) , 2 ) ;
521+ assert_eq ! ( info. chunks_down( ) , 3 ) ;
522+ assert_eq ! ( info. chunk_width_pixels( 0 ) . unwrap( ) , info. chunk_width) ;
523+ assert_eq ! ( info. chunk_width_pixels( 1 ) . unwrap( ) , 7 ) ;
524+ info. chunk_width_pixels ( 2 ) . unwrap_err ( ) ;
525+ assert_eq ! ( info. chunk_height_pixels( 0 ) . unwrap( ) , info. chunk_height) ;
526+ assert_eq ! ( info. chunk_height_pixels( 2 ) . unwrap( ) , 1 ) ;
527+ info. chunk_height_pixels ( 3 ) . unwrap_err ( ) ;
528+ }
529+
331530 #[ rustfmt:: skip]
332531 #[ test]
333532 fn test_no_predict ( ) {
0 commit comments