Skip to content

Commit 4787983

Browse files
committed
Move PredictorInfo into predictor.rs
1 parent cda2bcc commit 4787983

File tree

3 files changed

+211
-213
lines changed

3 files changed

+211
-213
lines changed

src/ifd.rs

Lines changed: 6 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@ use num_enum::TryFromPrimitive;
66

77
use crate::error::{AsyncTiffError, AsyncTiffResult};
88
use crate::geo::{GeoKeyDirectory, GeoKeyTag};
9+
use crate::predictor::PredictorInfo;
910
use crate::reader::{AsyncFileReader, Endianness};
1011
use crate::tiff::tags::{
1112
CompressionMethod, PhotometricInterpretation, PlanarConfiguration, Predictor, ResolutionUnit,
1213
SampleFormat, Tag,
1314
};
1415
use crate::tiff::{TiffError, Value};
15-
use crate::tile::{PredictorInfo, Tile};
16+
use crate::tile::Tile;
1617

1718
const DOCUMENT_NAME: u16 = 269;
1819

@@ -681,35 +682,6 @@ impl ImageFileDirectory {
681682
Some(offset as _..(offset + byte_count) as _)
682683
}
683684

684-
fn get_predictor_info(&self) -> PredictorInfo {
685-
if !self.bits_per_sample.windows(2).all(|w| w[0] == w[1]) {
686-
panic!("bits_per_sample should be the same for all channels");
687-
}
688-
689-
let chunk_width = if let Some(tile_width) = self.tile_width {
690-
tile_width
691-
} else {
692-
self.image_width
693-
};
694-
let chunk_height = if let Some(tile_height) = self.tile_height {
695-
tile_height
696-
} else {
697-
self.rows_per_strip
698-
.expect("no tile height and no rows_per_strip")
699-
};
700-
701-
PredictorInfo {
702-
endianness: self.endianness,
703-
image_width: self.image_width,
704-
image_height: self.image_height,
705-
chunk_width,
706-
chunk_height,
707-
// TODO: validate this? Restore handling for different PlanarConfiguration?
708-
bits_per_sample: self.bits_per_sample[0],
709-
samples_per_pixel: self.samples_per_pixel,
710-
}
711-
}
712-
713685
/// Fetch the tile located at `x` column and `y` row using the provided reader.
714686
pub async fn fetch_tile(
715687
&self,
@@ -725,7 +697,7 @@ impl ImageFileDirectory {
725697
x,
726698
y,
727699
predictor: self.predictor.unwrap_or(Predictor::None),
728-
predictor_info: self.get_predictor_info(),
700+
predictor_info: PredictorInfo::from_ifd(self),
729701
compressed_bytes,
730702
compression_method: self.compression,
731703
photometric_interpretation: self.photometric_interpretation,
@@ -742,6 +714,8 @@ impl ImageFileDirectory {
742714
) -> AsyncTiffResult<Vec<Tile>> {
743715
assert_eq!(x.len(), y.len(), "x and y should have same len");
744716

717+
let predictor_info = PredictorInfo::from_ifd(self);
718+
745719
// 1: Get all the byte ranges for all tiles
746720
let byte_ranges = x
747721
.iter()
@@ -762,7 +736,7 @@ impl ImageFileDirectory {
762736
x,
763737
y,
764738
predictor: self.predictor.unwrap_or(Predictor::None),
765-
predictor_info: self.get_predictor_info(),
739+
predictor_info,
766740
compressed_bytes,
767741
compression_method: self.compression,
768742
photometric_interpretation: self.photometric_interpretation,

src/predictor.rs

Lines changed: 202 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,185 @@ use std::fmt::Debug;
44
use 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
10188
pub(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

Comments
 (0)