/rust/registry/src/index.crates.io-1949cf8c6b5b557f/tiff-0.10.3/src/decoder/image.rs
Line | Count | Source |
1 | | use super::ifd::Value; |
2 | | use super::stream::PackBitsReader; |
3 | | use super::tag_reader::TagReader; |
4 | | use super::ChunkType; |
5 | | use super::{predict_f16, predict_f32, predict_f64, ValueReader}; |
6 | | use crate::tags::{ |
7 | | CompressionMethod, PhotometricInterpretation, PlanarConfiguration, Predictor, SampleFormat, Tag, |
8 | | }; |
9 | | use crate::{ |
10 | | ColorType, Directory, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, UsageError, |
11 | | }; |
12 | | |
13 | | use std::io::{self, Cursor, Read, Seek}; |
14 | | use std::num::NonZeroUsize; |
15 | | use std::sync::Arc; |
16 | | |
17 | | #[derive(Debug)] |
18 | | pub(crate) struct StripDecodeState { |
19 | | pub rows_per_strip: u32, |
20 | | } |
21 | | |
22 | | #[derive(Debug)] |
23 | | /// Computed values useful for tile decoding |
24 | | pub(crate) struct TileAttributes { |
25 | | pub image_width: usize, |
26 | | pub image_height: usize, |
27 | | |
28 | | pub tile_width: usize, |
29 | | pub tile_length: usize, |
30 | | } |
31 | | |
32 | | impl TileAttributes { |
33 | 39.9k | pub fn tiles_across(&self) -> usize { |
34 | 39.9k | self.image_width.div_ceil(self.tile_width) |
35 | 39.9k | } |
36 | 13.6k | pub fn tiles_down(&self) -> usize { |
37 | 13.6k | self.image_height.div_ceil(self.tile_length) |
38 | 13.6k | } |
39 | 714 | fn padding_right(&self) -> usize { |
40 | 714 | (self.tile_width - self.image_width % self.tile_width) % self.tile_width |
41 | 714 | } |
42 | 507 | fn padding_down(&self) -> usize { |
43 | 507 | (self.tile_length - self.image_height % self.tile_length) % self.tile_length |
44 | 507 | } |
45 | 13.1k | pub fn get_padding(&self, tile: usize) -> (usize, usize) { |
46 | 13.1k | let row = tile / self.tiles_across(); |
47 | 13.1k | let column = tile % self.tiles_across(); |
48 | | |
49 | 13.1k | let padding_right = if column == self.tiles_across() - 1 { |
50 | 714 | self.padding_right() |
51 | | } else { |
52 | 12.4k | 0 |
53 | | }; |
54 | | |
55 | 13.1k | let padding_down = if row == self.tiles_down() - 1 { |
56 | 507 | self.padding_down() |
57 | | } else { |
58 | 12.6k | 0 |
59 | | }; |
60 | | |
61 | 13.1k | (padding_right, padding_down) |
62 | 13.1k | } |
63 | | } |
64 | | |
65 | | #[derive(Debug)] |
66 | | pub(crate) struct Image { |
67 | | pub ifd: Option<Directory>, |
68 | | pub width: u32, |
69 | | pub height: u32, |
70 | | pub bits_per_sample: u8, |
71 | | pub samples: u16, |
72 | | pub sample_format: SampleFormat, |
73 | | pub photometric_interpretation: PhotometricInterpretation, |
74 | | pub compression_method: CompressionMethod, |
75 | | pub predictor: Predictor, |
76 | | pub jpeg_tables: Option<Arc<Vec<u8>>>, |
77 | | pub chunk_type: ChunkType, |
78 | | pub planar_config: PlanarConfiguration, |
79 | | pub strip_decoder: Option<StripDecodeState>, |
80 | | pub tile_attributes: Option<TileAttributes>, |
81 | | pub chunk_offsets: Vec<u64>, |
82 | | pub chunk_bytes: Vec<u64>, |
83 | | } |
84 | | |
85 | | impl Image { |
86 | 4.24k | pub fn from_reader<R: Read + Seek>( |
87 | 4.24k | decoder: &mut ValueReader<R>, |
88 | 4.24k | ifd: Directory, |
89 | 4.24k | ) -> TiffResult<Image> { |
90 | 4.24k | let mut tag_reader = TagReader { decoder, ifd: &ifd }; |
91 | | |
92 | 4.24k | let width = tag_reader.require_tag(Tag::ImageWidth)?.into_u32()?; |
93 | 4.17k | let height = tag_reader.require_tag(Tag::ImageLength)?.into_u32()?; |
94 | 4.16k | if width == 0 || height == 0 { |
95 | 1 | return Err(TiffError::FormatError(TiffFormatError::InvalidDimensions( |
96 | 1 | width, height, |
97 | 1 | ))); |
98 | 4.16k | } |
99 | | |
100 | 4.16k | let photometric_interpretation = tag_reader |
101 | 4.16k | .find_tag(Tag::PhotometricInterpretation)? |
102 | 4.16k | .map(Value::into_u16) |
103 | 4.16k | .transpose()? |
104 | 4.16k | .and_then(PhotometricInterpretation::from_u16) |
105 | 4.16k | .ok_or(TiffUnsupportedError::UnknownInterpretation)?; |
106 | | |
107 | | // Try to parse both the compression method and the number, format, and bits of the included samples. |
108 | | // If they are not explicitly specified, those tags are reset to their default values and not carried from previous images. |
109 | 4.16k | let compression_method = match tag_reader.find_tag(Tag::Compression)? { |
110 | 4.09k | Some(val) => CompressionMethod::from_u16_exhaustive(val.into_u16()?), |
111 | 70 | None => CompressionMethod::None, |
112 | | }; |
113 | | |
114 | 4.16k | let jpeg_tables = if compression_method == CompressionMethod::ModernJPEG |
115 | 3.28k | && ifd.contains(Tag::JPEGTables) |
116 | | { |
117 | 1.22k | let vec = tag_reader |
118 | 1.22k | .find_tag(Tag::JPEGTables)? |
119 | 1.22k | .unwrap() |
120 | 1.22k | .into_u8_vec()?; |
121 | 1.22k | if vec.len() < 2 { |
122 | 0 | return Err(TiffError::FormatError( |
123 | 0 | TiffFormatError::InvalidTagValueType(Tag::JPEGTables), |
124 | 0 | )); |
125 | 1.22k | } |
126 | | |
127 | 1.22k | Some(Arc::new(vec)) |
128 | | } else { |
129 | 2.93k | None |
130 | | }; |
131 | | |
132 | 4.15k | let samples: u16 = tag_reader |
133 | 4.15k | .find_tag(Tag::SamplesPerPixel)? |
134 | 4.15k | .map(Value::into_u16) |
135 | 4.15k | .transpose()? |
136 | 4.15k | .unwrap_or(1); |
137 | 4.15k | if samples == 0 { |
138 | 0 | return Err(TiffFormatError::SamplesPerPixelIsZero.into()); |
139 | 4.15k | } |
140 | | |
141 | 4.15k | let sample_format = match tag_reader.find_tag_uint_vec(Tag::SampleFormat)? { |
142 | 295 | Some(vals) => { |
143 | 295 | let sample_format: Vec<_> = vals |
144 | 295 | .into_iter() |
145 | 295 | .map(SampleFormat::from_u16_exhaustive) |
146 | 295 | .collect(); |
147 | | |
148 | | // TODO: for now, only homogenous formats across samples are supported. |
149 | 371 | if !sample_format.windows(2).all(|s| s[0] == s[1]) {<tiff::decoder::image::Image>::from_reader::<std::io::cursor::Cursor<&[u8]>>::{closure#0}Line | Count | Source | 149 | 371 | if !sample_format.windows(2).all(|s| s[0] == s[1]) { |
Unexecuted instantiation: <tiff::decoder::image::Image>::from_reader::<_>::{closure#0} |
150 | 19 | return Err(TiffUnsupportedError::UnsupportedSampleFormat(sample_format).into()); |
151 | 276 | } |
152 | | |
153 | 276 | sample_format[0] |
154 | | } |
155 | 3.85k | None => SampleFormat::Uint, |
156 | | }; |
157 | | |
158 | 4.13k | let bits_per_sample: Vec<u8> = tag_reader |
159 | 4.13k | .find_tag_uint_vec(Tag::BitsPerSample)? |
160 | 4.10k | .unwrap_or_else(|| vec![1]); <tiff::decoder::image::Image>::from_reader::<std::io::cursor::Cursor<&[u8]>>::{closure#1}Line | Count | Source | 160 | 524 | .unwrap_or_else(|| vec![1]); |
Unexecuted instantiation: <tiff::decoder::image::Image>::from_reader::<_>::{closure#1} |
161 | | |
162 | | // Technically bits_per_sample.len() should be *equal* to samples, but libtiff also allows |
163 | | // it to be a single value that applies to all samples. |
164 | 4.10k | if bits_per_sample.len() != samples.into() && bits_per_sample.len() != 1 { |
165 | 4 | return Err(TiffError::FormatError( |
166 | 4 | TiffFormatError::InconsistentSizesEncountered, |
167 | 4 | )); |
168 | 4.10k | } |
169 | | |
170 | | // This library (and libtiff) do not support mixed sample formats and zero bits per sample |
171 | | // doesn't make sense. |
172 | 4.56k | if bits_per_sample.iter().any(|&b| b != bits_per_sample[0]) || bits_per_sample[0] == 0 {<tiff::decoder::image::Image>::from_reader::<std::io::cursor::Cursor<&[u8]>>::{closure#2}Line | Count | Source | 172 | 4.56k | if bits_per_sample.iter().any(|&b| b != bits_per_sample[0]) || bits_per_sample[0] == 0 { |
Unexecuted instantiation: <tiff::decoder::image::Image>::from_reader::<_>::{closure#2} |
173 | 1 | return Err(TiffUnsupportedError::InconsistentBitsPerSample(bits_per_sample).into()); |
174 | 4.10k | } |
175 | | |
176 | 4.10k | let predictor = tag_reader |
177 | 4.10k | .find_tag(Tag::Predictor)? |
178 | 4.10k | .map(Value::into_u16) |
179 | 4.10k | .transpose()? |
180 | 4.10k | .map(|p| { |
181 | 355 | Predictor::from_u16(p) |
182 | 355 | .ok_or(TiffError::FormatError(TiffFormatError::UnknownPredictor(p))) |
183 | 355 | }) <tiff::decoder::image::Image>::from_reader::<std::io::cursor::Cursor<&[u8]>>::{closure#3}Line | Count | Source | 180 | 355 | .map(|p| { | 181 | 355 | Predictor::from_u16(p) | 182 | 355 | .ok_or(TiffError::FormatError(TiffFormatError::UnknownPredictor(p))) | 183 | 355 | }) |
Unexecuted instantiation: <tiff::decoder::image::Image>::from_reader::<_>::{closure#3} |
184 | 4.10k | .transpose()? |
185 | 4.10k | .unwrap_or(Predictor::None); |
186 | | |
187 | 4.10k | let planar_config = tag_reader |
188 | 4.10k | .find_tag(Tag::PlanarConfiguration)? |
189 | 4.09k | .map(Value::into_u16) |
190 | 4.09k | .transpose()? |
191 | 4.09k | .map(|p| { |
192 | 518 | PlanarConfiguration::from_u16(p).ok_or(TiffError::FormatError( |
193 | 518 | TiffFormatError::UnknownPlanarConfiguration(p), |
194 | 518 | )) |
195 | 518 | }) <tiff::decoder::image::Image>::from_reader::<std::io::cursor::Cursor<&[u8]>>::{closure#4}Line | Count | Source | 191 | 518 | .map(|p| { | 192 | 518 | PlanarConfiguration::from_u16(p).ok_or(TiffError::FormatError( | 193 | 518 | TiffFormatError::UnknownPlanarConfiguration(p), | 194 | 518 | )) | 195 | 518 | }) |
Unexecuted instantiation: <tiff::decoder::image::Image>::from_reader::<_>::{closure#4} |
196 | 4.09k | .transpose()? |
197 | 4.09k | .unwrap_or(PlanarConfiguration::Chunky); |
198 | | |
199 | 4.09k | let planes = match planar_config { |
200 | 4.09k | PlanarConfiguration::Chunky => 1, |
201 | 1 | PlanarConfiguration::Planar => samples, |
202 | | }; |
203 | | |
204 | | let chunk_type; |
205 | | let chunk_offsets; |
206 | | let chunk_bytes; |
207 | | let strip_decoder; |
208 | | let tile_attributes; |
209 | 4.09k | match ( |
210 | 4.09k | ifd.contains(Tag::StripByteCounts), |
211 | 4.09k | ifd.contains(Tag::StripOffsets), |
212 | 4.09k | ifd.contains(Tag::TileByteCounts), |
213 | 4.09k | ifd.contains(Tag::TileOffsets), |
214 | 4.09k | ) { |
215 | | (true, true, false, false) => { |
216 | 3.60k | chunk_type = ChunkType::Strip; |
217 | | |
218 | 3.60k | chunk_offsets = tag_reader |
219 | 3.60k | .find_tag(Tag::StripOffsets)? |
220 | 3.60k | .unwrap() |
221 | 3.60k | .into_u64_vec()?; |
222 | 3.60k | chunk_bytes = tag_reader |
223 | 3.60k | .find_tag(Tag::StripByteCounts)? |
224 | 3.59k | .unwrap() |
225 | 3.59k | .into_u64_vec()?; |
226 | 3.59k | let rows_per_strip = tag_reader |
227 | 3.59k | .find_tag(Tag::RowsPerStrip)? |
228 | 3.59k | .map(Value::into_u32) |
229 | 3.59k | .transpose()? |
230 | 3.59k | .unwrap_or(height); |
231 | 3.59k | strip_decoder = Some(StripDecodeState { rows_per_strip }); |
232 | 3.59k | tile_attributes = None; |
233 | | |
234 | 3.59k | if chunk_offsets.len() != chunk_bytes.len() |
235 | 3.58k | || rows_per_strip == 0 |
236 | 3.58k | || u32::try_from(chunk_offsets.len())? |
237 | 3.58k | != (height.saturating_sub(1) / rows_per_strip + 1) * planes as u32 |
238 | | { |
239 | 20 | return Err(TiffError::FormatError( |
240 | 20 | TiffFormatError::InconsistentSizesEncountered, |
241 | 20 | )); |
242 | 3.57k | } |
243 | | } |
244 | | (false, false, true, true) => { |
245 | 485 | chunk_type = ChunkType::Tile; |
246 | | |
247 | 485 | let tile_width = |
248 | 485 | usize::try_from(tag_reader.require_tag(Tag::TileWidth)?.into_u32()?)?; |
249 | 485 | let tile_length = |
250 | 485 | usize::try_from(tag_reader.require_tag(Tag::TileLength)?.into_u32()?)?; |
251 | | |
252 | 485 | if tile_width == 0 { |
253 | 0 | return Err(TiffFormatError::InvalidTagValueType(Tag::TileWidth).into()); |
254 | 485 | } else if tile_length == 0 { |
255 | 0 | return Err(TiffFormatError::InvalidTagValueType(Tag::TileLength).into()); |
256 | 485 | } |
257 | | |
258 | 485 | strip_decoder = None; |
259 | | tile_attributes = Some(TileAttributes { |
260 | 485 | image_width: usize::try_from(width)?, |
261 | 485 | image_height: usize::try_from(height)?, |
262 | 485 | tile_width, |
263 | 485 | tile_length, |
264 | | }); |
265 | 485 | chunk_offsets = tag_reader |
266 | 485 | .find_tag(Tag::TileOffsets)? |
267 | 485 | .unwrap() |
268 | 485 | .into_u64_vec()?; |
269 | 485 | chunk_bytes = tag_reader |
270 | 485 | .find_tag(Tag::TileByteCounts)? |
271 | 484 | .unwrap() |
272 | 484 | .into_u64_vec()?; |
273 | | |
274 | 484 | let tile = tile_attributes.as_ref().unwrap(); |
275 | 484 | if chunk_offsets.len() != chunk_bytes.len() |
276 | 483 | || chunk_offsets.len() |
277 | 483 | != tile.tiles_down() * tile.tiles_across() * planes as usize |
278 | | { |
279 | 1 | return Err(TiffError::FormatError( |
280 | 1 | TiffFormatError::InconsistentSizesEncountered, |
281 | 1 | )); |
282 | 483 | } |
283 | | } |
284 | | (_, _, _, _) => { |
285 | 2 | return Err(TiffError::FormatError( |
286 | 2 | TiffFormatError::StripTileTagConflict, |
287 | 2 | )) |
288 | | } |
289 | | }; |
290 | | |
291 | 4.05k | Ok(Image { |
292 | 4.05k | ifd: Some(ifd), |
293 | 4.05k | width, |
294 | 4.05k | height, |
295 | 4.05k | bits_per_sample: bits_per_sample[0], |
296 | 4.05k | samples, |
297 | 4.05k | sample_format, |
298 | 4.05k | photometric_interpretation, |
299 | 4.05k | compression_method, |
300 | 4.05k | jpeg_tables, |
301 | 4.05k | predictor, |
302 | 4.05k | chunk_type, |
303 | 4.05k | planar_config, |
304 | 4.05k | strip_decoder, |
305 | 4.05k | tile_attributes, |
306 | 4.05k | chunk_offsets, |
307 | 4.05k | chunk_bytes, |
308 | 4.05k | }) |
309 | 4.24k | } <tiff::decoder::image::Image>::from_reader::<std::io::cursor::Cursor<&[u8]>> Line | Count | Source | 86 | 4.24k | pub fn from_reader<R: Read + Seek>( | 87 | 4.24k | decoder: &mut ValueReader<R>, | 88 | 4.24k | ifd: Directory, | 89 | 4.24k | ) -> TiffResult<Image> { | 90 | 4.24k | let mut tag_reader = TagReader { decoder, ifd: &ifd }; | 91 | | | 92 | 4.24k | let width = tag_reader.require_tag(Tag::ImageWidth)?.into_u32()?; | 93 | 4.17k | let height = tag_reader.require_tag(Tag::ImageLength)?.into_u32()?; | 94 | 4.16k | if width == 0 || height == 0 { | 95 | 1 | return Err(TiffError::FormatError(TiffFormatError::InvalidDimensions( | 96 | 1 | width, height, | 97 | 1 | ))); | 98 | 4.16k | } | 99 | | | 100 | 4.16k | let photometric_interpretation = tag_reader | 101 | 4.16k | .find_tag(Tag::PhotometricInterpretation)? | 102 | 4.16k | .map(Value::into_u16) | 103 | 4.16k | .transpose()? | 104 | 4.16k | .and_then(PhotometricInterpretation::from_u16) | 105 | 4.16k | .ok_or(TiffUnsupportedError::UnknownInterpretation)?; | 106 | | | 107 | | // Try to parse both the compression method and the number, format, and bits of the included samples. | 108 | | // If they are not explicitly specified, those tags are reset to their default values and not carried from previous images. | 109 | 4.16k | let compression_method = match tag_reader.find_tag(Tag::Compression)? { | 110 | 4.09k | Some(val) => CompressionMethod::from_u16_exhaustive(val.into_u16()?), | 111 | 70 | None => CompressionMethod::None, | 112 | | }; | 113 | | | 114 | 4.16k | let jpeg_tables = if compression_method == CompressionMethod::ModernJPEG | 115 | 3.28k | && ifd.contains(Tag::JPEGTables) | 116 | | { | 117 | 1.22k | let vec = tag_reader | 118 | 1.22k | .find_tag(Tag::JPEGTables)? | 119 | 1.22k | .unwrap() | 120 | 1.22k | .into_u8_vec()?; | 121 | 1.22k | if vec.len() < 2 { | 122 | 0 | return Err(TiffError::FormatError( | 123 | 0 | TiffFormatError::InvalidTagValueType(Tag::JPEGTables), | 124 | 0 | )); | 125 | 1.22k | } | 126 | | | 127 | 1.22k | Some(Arc::new(vec)) | 128 | | } else { | 129 | 2.93k | None | 130 | | }; | 131 | | | 132 | 4.15k | let samples: u16 = tag_reader | 133 | 4.15k | .find_tag(Tag::SamplesPerPixel)? | 134 | 4.15k | .map(Value::into_u16) | 135 | 4.15k | .transpose()? | 136 | 4.15k | .unwrap_or(1); | 137 | 4.15k | if samples == 0 { | 138 | 0 | return Err(TiffFormatError::SamplesPerPixelIsZero.into()); | 139 | 4.15k | } | 140 | | | 141 | 4.15k | let sample_format = match tag_reader.find_tag_uint_vec(Tag::SampleFormat)? { | 142 | 295 | Some(vals) => { | 143 | 295 | let sample_format: Vec<_> = vals | 144 | 295 | .into_iter() | 145 | 295 | .map(SampleFormat::from_u16_exhaustive) | 146 | 295 | .collect(); | 147 | | | 148 | | // TODO: for now, only homogenous formats across samples are supported. | 149 | 295 | if !sample_format.windows(2).all(|s| s[0] == s[1]) { | 150 | 19 | return Err(TiffUnsupportedError::UnsupportedSampleFormat(sample_format).into()); | 151 | 276 | } | 152 | | | 153 | 276 | sample_format[0] | 154 | | } | 155 | 3.85k | None => SampleFormat::Uint, | 156 | | }; | 157 | | | 158 | 4.13k | let bits_per_sample: Vec<u8> = tag_reader | 159 | 4.13k | .find_tag_uint_vec(Tag::BitsPerSample)? | 160 | 4.10k | .unwrap_or_else(|| vec![1]); | 161 | | | 162 | | // Technically bits_per_sample.len() should be *equal* to samples, but libtiff also allows | 163 | | // it to be a single value that applies to all samples. | 164 | 4.10k | if bits_per_sample.len() != samples.into() && bits_per_sample.len() != 1 { | 165 | 4 | return Err(TiffError::FormatError( | 166 | 4 | TiffFormatError::InconsistentSizesEncountered, | 167 | 4 | )); | 168 | 4.10k | } | 169 | | | 170 | | // This library (and libtiff) do not support mixed sample formats and zero bits per sample | 171 | | // doesn't make sense. | 172 | 4.10k | if bits_per_sample.iter().any(|&b| b != bits_per_sample[0]) || bits_per_sample[0] == 0 { | 173 | 1 | return Err(TiffUnsupportedError::InconsistentBitsPerSample(bits_per_sample).into()); | 174 | 4.10k | } | 175 | | | 176 | 4.10k | let predictor = tag_reader | 177 | 4.10k | .find_tag(Tag::Predictor)? | 178 | 4.10k | .map(Value::into_u16) | 179 | 4.10k | .transpose()? | 180 | 4.10k | .map(|p| { | 181 | | Predictor::from_u16(p) | 182 | | .ok_or(TiffError::FormatError(TiffFormatError::UnknownPredictor(p))) | 183 | | }) | 184 | 4.10k | .transpose()? | 185 | 4.10k | .unwrap_or(Predictor::None); | 186 | | | 187 | 4.10k | let planar_config = tag_reader | 188 | 4.10k | .find_tag(Tag::PlanarConfiguration)? | 189 | 4.09k | .map(Value::into_u16) | 190 | 4.09k | .transpose()? | 191 | 4.09k | .map(|p| { | 192 | | PlanarConfiguration::from_u16(p).ok_or(TiffError::FormatError( | 193 | | TiffFormatError::UnknownPlanarConfiguration(p), | 194 | | )) | 195 | | }) | 196 | 4.09k | .transpose()? | 197 | 4.09k | .unwrap_or(PlanarConfiguration::Chunky); | 198 | | | 199 | 4.09k | let planes = match planar_config { | 200 | 4.09k | PlanarConfiguration::Chunky => 1, | 201 | 1 | PlanarConfiguration::Planar => samples, | 202 | | }; | 203 | | | 204 | | let chunk_type; | 205 | | let chunk_offsets; | 206 | | let chunk_bytes; | 207 | | let strip_decoder; | 208 | | let tile_attributes; | 209 | 4.09k | match ( | 210 | 4.09k | ifd.contains(Tag::StripByteCounts), | 211 | 4.09k | ifd.contains(Tag::StripOffsets), | 212 | 4.09k | ifd.contains(Tag::TileByteCounts), | 213 | 4.09k | ifd.contains(Tag::TileOffsets), | 214 | 4.09k | ) { | 215 | | (true, true, false, false) => { | 216 | 3.60k | chunk_type = ChunkType::Strip; | 217 | | | 218 | 3.60k | chunk_offsets = tag_reader | 219 | 3.60k | .find_tag(Tag::StripOffsets)? | 220 | 3.60k | .unwrap() | 221 | 3.60k | .into_u64_vec()?; | 222 | 3.60k | chunk_bytes = tag_reader | 223 | 3.60k | .find_tag(Tag::StripByteCounts)? | 224 | 3.59k | .unwrap() | 225 | 3.59k | .into_u64_vec()?; | 226 | 3.59k | let rows_per_strip = tag_reader | 227 | 3.59k | .find_tag(Tag::RowsPerStrip)? | 228 | 3.59k | .map(Value::into_u32) | 229 | 3.59k | .transpose()? | 230 | 3.59k | .unwrap_or(height); | 231 | 3.59k | strip_decoder = Some(StripDecodeState { rows_per_strip }); | 232 | 3.59k | tile_attributes = None; | 233 | | | 234 | 3.59k | if chunk_offsets.len() != chunk_bytes.len() | 235 | 3.58k | || rows_per_strip == 0 | 236 | 3.58k | || u32::try_from(chunk_offsets.len())? | 237 | 3.58k | != (height.saturating_sub(1) / rows_per_strip + 1) * planes as u32 | 238 | | { | 239 | 20 | return Err(TiffError::FormatError( | 240 | 20 | TiffFormatError::InconsistentSizesEncountered, | 241 | 20 | )); | 242 | 3.57k | } | 243 | | } | 244 | | (false, false, true, true) => { | 245 | 485 | chunk_type = ChunkType::Tile; | 246 | | | 247 | 485 | let tile_width = | 248 | 485 | usize::try_from(tag_reader.require_tag(Tag::TileWidth)?.into_u32()?)?; | 249 | 485 | let tile_length = | 250 | 485 | usize::try_from(tag_reader.require_tag(Tag::TileLength)?.into_u32()?)?; | 251 | | | 252 | 485 | if tile_width == 0 { | 253 | 0 | return Err(TiffFormatError::InvalidTagValueType(Tag::TileWidth).into()); | 254 | 485 | } else if tile_length == 0 { | 255 | 0 | return Err(TiffFormatError::InvalidTagValueType(Tag::TileLength).into()); | 256 | 485 | } | 257 | | | 258 | 485 | strip_decoder = None; | 259 | | tile_attributes = Some(TileAttributes { | 260 | 485 | image_width: usize::try_from(width)?, | 261 | 485 | image_height: usize::try_from(height)?, | 262 | 485 | tile_width, | 263 | 485 | tile_length, | 264 | | }); | 265 | 485 | chunk_offsets = tag_reader | 266 | 485 | .find_tag(Tag::TileOffsets)? | 267 | 485 | .unwrap() | 268 | 485 | .into_u64_vec()?; | 269 | 485 | chunk_bytes = tag_reader | 270 | 485 | .find_tag(Tag::TileByteCounts)? | 271 | 484 | .unwrap() | 272 | 484 | .into_u64_vec()?; | 273 | | | 274 | 484 | let tile = tile_attributes.as_ref().unwrap(); | 275 | 484 | if chunk_offsets.len() != chunk_bytes.len() | 276 | 483 | || chunk_offsets.len() | 277 | 483 | != tile.tiles_down() * tile.tiles_across() * planes as usize | 278 | | { | 279 | 1 | return Err(TiffError::FormatError( | 280 | 1 | TiffFormatError::InconsistentSizesEncountered, | 281 | 1 | )); | 282 | 483 | } | 283 | | } | 284 | | (_, _, _, _) => { | 285 | 2 | return Err(TiffError::FormatError( | 286 | 2 | TiffFormatError::StripTileTagConflict, | 287 | 2 | )) | 288 | | } | 289 | | }; | 290 | | | 291 | 4.05k | Ok(Image { | 292 | 4.05k | ifd: Some(ifd), | 293 | 4.05k | width, | 294 | 4.05k | height, | 295 | 4.05k | bits_per_sample: bits_per_sample[0], | 296 | 4.05k | samples, | 297 | 4.05k | sample_format, | 298 | 4.05k | photometric_interpretation, | 299 | 4.05k | compression_method, | 300 | 4.05k | jpeg_tables, | 301 | 4.05k | predictor, | 302 | 4.05k | chunk_type, | 303 | 4.05k | planar_config, | 304 | 4.05k | strip_decoder, | 305 | 4.05k | tile_attributes, | 306 | 4.05k | chunk_offsets, | 307 | 4.05k | chunk_bytes, | 308 | 4.05k | }) | 309 | 4.24k | } |
Unexecuted instantiation: <tiff::decoder::image::Image>::from_reader::<_> |
310 | | |
311 | 21.6k | pub(crate) fn colortype(&self) -> TiffResult<ColorType> { |
312 | 21.6k | match self.photometric_interpretation { |
313 | 765 | PhotometricInterpretation::RGB => match self.samples { |
314 | 617 | 3 => Ok(ColorType::RGB(self.bits_per_sample)), |
315 | 148 | 4 => Ok(ColorType::RGBA(self.bits_per_sample)), |
316 | | // FIXME: We should _ignore_ other components. In particular: |
317 | | // > Beware of extra components. Some TIFF files may have more components per pixel |
318 | | // than you think. A Baseline TIFF reader must skip over them gracefully,using the |
319 | | // values of the SamplesPerPixel and BitsPerSample fields. |
320 | | // > -- TIFF 6.0 Specification, Section 7, Additional Baseline requirements. |
321 | 0 | _ => Err(TiffError::UnsupportedError( |
322 | 0 | TiffUnsupportedError::InterpretationWithBits( |
323 | 0 | self.photometric_interpretation, |
324 | 0 | vec![self.bits_per_sample; self.samples as usize], |
325 | 0 | ), |
326 | 0 | )), |
327 | | }, |
328 | 110 | PhotometricInterpretation::CMYK => match self.samples { |
329 | 110 | 4 => Ok(ColorType::CMYK(self.bits_per_sample)), |
330 | 0 | 5 => Ok(ColorType::CMYKA(self.bits_per_sample)), |
331 | 0 | _ => Err(TiffError::UnsupportedError( |
332 | 0 | TiffUnsupportedError::InterpretationWithBits( |
333 | 0 | self.photometric_interpretation, |
334 | 0 | vec![self.bits_per_sample; self.samples as usize], |
335 | 0 | ), |
336 | 0 | )), |
337 | | }, |
338 | 0 | PhotometricInterpretation::YCbCr => match self.samples { |
339 | 0 | 3 => Ok(ColorType::YCbCr(self.bits_per_sample)), |
340 | 0 | _ => Err(TiffError::UnsupportedError( |
341 | 0 | TiffUnsupportedError::InterpretationWithBits( |
342 | 0 | self.photometric_interpretation, |
343 | 0 | vec![self.bits_per_sample; self.samples as usize], |
344 | 0 | ), |
345 | 0 | )), |
346 | | }, |
347 | | // TODO: treatment of WhiteIsZero is not quite consistent with `invert_colors` that is |
348 | | // later called when that interpretation is read. That function does not support |
349 | | // Multiband as a color type and will error. It's unclear how to resolve that exactly. |
350 | | PhotometricInterpretation::BlackIsZero | PhotometricInterpretation::WhiteIsZero => { |
351 | 20.7k | match self.samples { |
352 | 20.7k | 1 => Ok(ColorType::Gray(self.bits_per_sample)), |
353 | 3 | _ => Ok(ColorType::Multiband { |
354 | 3 | bit_depth: self.bits_per_sample, |
355 | 3 | num_samples: self.samples, |
356 | 3 | }), |
357 | | } |
358 | | } |
359 | | // TODO: this is bad we should not fail at this point |
360 | | PhotometricInterpretation::RGBPalette |
361 | | | PhotometricInterpretation::TransparencyMask |
362 | 0 | | PhotometricInterpretation::CIELab => Err(TiffError::UnsupportedError( |
363 | 0 | TiffUnsupportedError::InterpretationWithBits( |
364 | 0 | self.photometric_interpretation, |
365 | 0 | vec![self.bits_per_sample; self.samples as usize], |
366 | 0 | ), |
367 | 0 | )), |
368 | | } |
369 | 21.6k | } |
370 | | |
371 | 0 | pub(crate) fn minimum_row_stride(&self, dims: (u32, u32)) -> Option<NonZeroUsize> { |
372 | 0 | let (width, height) = dims; |
373 | | |
374 | 0 | let row_stride = u64::from(width) |
375 | 0 | .saturating_mul(self.samples_per_pixel() as u64) |
376 | 0 | .saturating_mul(self.bits_per_sample as u64) |
377 | 0 | .div_ceil(8); |
378 | | |
379 | | // Note: row stride should be smaller than the len if we have an actual buffer. If there |
380 | | // are no pixels in the buffer (height _or_ width is 0) then the stride is not well defined |
381 | | // and we return `None`. |
382 | 0 | (height > 0) |
383 | 0 | .then_some(row_stride as usize) |
384 | 0 | .and_then(NonZeroUsize::new) |
385 | 0 | } |
386 | | |
387 | 17.5k | fn create_reader<'r, R: 'r + Read>( |
388 | 17.5k | reader: R, |
389 | 17.5k | compression_method: CompressionMethod, |
390 | 17.5k | compressed_length: u64, |
391 | 17.5k | // FIXME: these should be `expect` attributes or we choose another way of passing them. |
392 | 17.5k | #[cfg_attr(not(feature = "jpeg"), allow(unused_variables))] jpeg_tables: Option<&[u8]>, |
393 | 17.5k | #[cfg_attr(not(feature = "fax"), allow(unused_variables))] dimensions: (u32, u32), |
394 | 17.5k | ) -> TiffResult<Box<dyn Read + 'r>> { |
395 | 17.5k | Ok(match compression_method { |
396 | 12.7k | CompressionMethod::None => Box::new(reader), |
397 | | #[cfg(feature = "lzw")] |
398 | 559 | CompressionMethod::LZW => Box::new(super::stream::LZWReader::new( |
399 | 559 | reader, |
400 | 559 | usize::try_from(compressed_length)?, |
401 | | )), |
402 | | #[cfg(feature = "zstd")] |
403 | | CompressionMethod::ZSTD => Box::new(zstd::Decoder::new(reader)?), |
404 | 51 | CompressionMethod::PackBits => Box::new(PackBitsReader::new(reader, compressed_length)), |
405 | | #[cfg(feature = "deflate")] |
406 | | CompressionMethod::Deflate | CompressionMethod::OldDeflate => { |
407 | 627 | Box::new(super::stream::DeflateReader::new(reader)) |
408 | | } |
409 | | #[cfg(feature = "jpeg")] |
410 | | CompressionMethod::ModernJPEG => { |
411 | | use zune_jpeg::zune_core; |
412 | | |
413 | 3.52k | if jpeg_tables.is_some() && compressed_length < 2 { |
414 | 0 | return Err(TiffError::FormatError( |
415 | 0 | TiffFormatError::InvalidTagValueType(Tag::JPEGTables), |
416 | 0 | )); |
417 | 3.52k | } |
418 | | |
419 | | // Construct new jpeg_reader wrapping a SmartReader. |
420 | | // |
421 | | // JPEG compression in TIFF allows saving quantization and/or huffman tables in one |
422 | | // central location. These `jpeg_tables` are simply prepended to the remaining jpeg image data. |
423 | | // Because these `jpeg_tables` start with a `SOI` (HEX: `0xFFD8`) or __start of image__ marker |
424 | | // which is also at the beginning of the remaining JPEG image data and would |
425 | | // confuse the JPEG renderer, one of these has to be taken off. In this case the first two |
426 | | // bytes of the remaining JPEG data is removed because it follows `jpeg_tables`. |
427 | | // Similary, `jpeg_tables` ends with a `EOI` (HEX: `0xFFD9`) or __end of image__ marker, |
428 | | // this has to be removed as well (last two bytes of `jpeg_tables`). |
429 | 3.52k | let mut jpeg_reader = match jpeg_tables { |
430 | 1.49k | Some(jpeg_tables) => { |
431 | 1.49k | let mut reader = reader.take(compressed_length); |
432 | 1.49k | reader.read_exact(&mut [0; 2])?; |
433 | | |
434 | 1.32k | Box::new( |
435 | 1.32k | Cursor::new(&jpeg_tables[..jpeg_tables.len() - 2]) |
436 | 1.32k | .chain(reader.take(compressed_length)), |
437 | 1.32k | ) as Box<dyn Read> |
438 | | } |
439 | 2.02k | None => Box::new(reader.take(compressed_length)), |
440 | | }; |
441 | | |
442 | 3.35k | let mut jpeg_data = Vec::new(); |
443 | 3.35k | jpeg_reader.read_to_end(&mut jpeg_data)?; |
444 | | |
445 | 3.35k | let mut decoder = zune_jpeg::JpegDecoder::new(jpeg_data); |
446 | 3.35k | let mut options: zune_core::options::DecoderOptions = Default::default(); |
447 | | |
448 | | // Disable color conversion by setting the output colorspace to the input |
449 | | // colorspace. |
450 | 3.35k | decoder.decode_headers()?; |
451 | 2.86k | if let Some(colorspace) = decoder.get_input_colorspace() { |
452 | 2.86k | options = options.jpeg_set_out_colorspace(colorspace); |
453 | 2.86k | } |
454 | | |
455 | 2.86k | decoder.set_options(options); |
456 | | |
457 | 2.86k | let data = decoder.decode()?; |
458 | | |
459 | 514 | Box::new(Cursor::new(data)) |
460 | | } |
461 | | #[cfg(feature = "fax")] |
462 | 77 | CompressionMethod::Fax4 => Box::new(super::stream::Group4Reader::new( |
463 | 77 | dimensions, |
464 | 77 | reader, |
465 | 77 | compressed_length, |
466 | 0 | )?), |
467 | 2 | method => { |
468 | 2 | return Err(TiffError::UnsupportedError( |
469 | 2 | TiffUnsupportedError::UnsupportedCompressionMethod(method), |
470 | 2 | )) |
471 | | } |
472 | | }) |
473 | 17.5k | } <tiff::decoder::image::Image>::create_reader::<&mut std::io::cursor::Cursor<&[u8]>> Line | Count | Source | 387 | 17.5k | fn create_reader<'r, R: 'r + Read>( | 388 | 17.5k | reader: R, | 389 | 17.5k | compression_method: CompressionMethod, | 390 | 17.5k | compressed_length: u64, | 391 | 17.5k | // FIXME: these should be `expect` attributes or we choose another way of passing them. | 392 | 17.5k | #[cfg_attr(not(feature = "jpeg"), allow(unused_variables))] jpeg_tables: Option<&[u8]>, | 393 | 17.5k | #[cfg_attr(not(feature = "fax"), allow(unused_variables))] dimensions: (u32, u32), | 394 | 17.5k | ) -> TiffResult<Box<dyn Read + 'r>> { | 395 | 17.5k | Ok(match compression_method { | 396 | 12.7k | CompressionMethod::None => Box::new(reader), | 397 | | #[cfg(feature = "lzw")] | 398 | 559 | CompressionMethod::LZW => Box::new(super::stream::LZWReader::new( | 399 | 559 | reader, | 400 | 559 | usize::try_from(compressed_length)?, | 401 | | )), | 402 | | #[cfg(feature = "zstd")] | 403 | | CompressionMethod::ZSTD => Box::new(zstd::Decoder::new(reader)?), | 404 | 51 | CompressionMethod::PackBits => Box::new(PackBitsReader::new(reader, compressed_length)), | 405 | | #[cfg(feature = "deflate")] | 406 | | CompressionMethod::Deflate | CompressionMethod::OldDeflate => { | 407 | 627 | Box::new(super::stream::DeflateReader::new(reader)) | 408 | | } | 409 | | #[cfg(feature = "jpeg")] | 410 | | CompressionMethod::ModernJPEG => { | 411 | | use zune_jpeg::zune_core; | 412 | | | 413 | 3.52k | if jpeg_tables.is_some() && compressed_length < 2 { | 414 | 0 | return Err(TiffError::FormatError( | 415 | 0 | TiffFormatError::InvalidTagValueType(Tag::JPEGTables), | 416 | 0 | )); | 417 | 3.52k | } | 418 | | | 419 | | // Construct new jpeg_reader wrapping a SmartReader. | 420 | | // | 421 | | // JPEG compression in TIFF allows saving quantization and/or huffman tables in one | 422 | | // central location. These `jpeg_tables` are simply prepended to the remaining jpeg image data. | 423 | | // Because these `jpeg_tables` start with a `SOI` (HEX: `0xFFD8`) or __start of image__ marker | 424 | | // which is also at the beginning of the remaining JPEG image data and would | 425 | | // confuse the JPEG renderer, one of these has to be taken off. In this case the first two | 426 | | // bytes of the remaining JPEG data is removed because it follows `jpeg_tables`. | 427 | | // Similary, `jpeg_tables` ends with a `EOI` (HEX: `0xFFD9`) or __end of image__ marker, | 428 | | // this has to be removed as well (last two bytes of `jpeg_tables`). | 429 | 3.52k | let mut jpeg_reader = match jpeg_tables { | 430 | 1.49k | Some(jpeg_tables) => { | 431 | 1.49k | let mut reader = reader.take(compressed_length); | 432 | 1.49k | reader.read_exact(&mut [0; 2])?; | 433 | | | 434 | 1.32k | Box::new( | 435 | 1.32k | Cursor::new(&jpeg_tables[..jpeg_tables.len() - 2]) | 436 | 1.32k | .chain(reader.take(compressed_length)), | 437 | 1.32k | ) as Box<dyn Read> | 438 | | } | 439 | 2.02k | None => Box::new(reader.take(compressed_length)), | 440 | | }; | 441 | | | 442 | 3.35k | let mut jpeg_data = Vec::new(); | 443 | 3.35k | jpeg_reader.read_to_end(&mut jpeg_data)?; | 444 | | | 445 | 3.35k | let mut decoder = zune_jpeg::JpegDecoder::new(jpeg_data); | 446 | 3.35k | let mut options: zune_core::options::DecoderOptions = Default::default(); | 447 | | | 448 | | // Disable color conversion by setting the output colorspace to the input | 449 | | // colorspace. | 450 | 3.35k | decoder.decode_headers()?; | 451 | 2.86k | if let Some(colorspace) = decoder.get_input_colorspace() { | 452 | 2.86k | options = options.jpeg_set_out_colorspace(colorspace); | 453 | 2.86k | } | 454 | | | 455 | 2.86k | decoder.set_options(options); | 456 | | | 457 | 2.86k | let data = decoder.decode()?; | 458 | | | 459 | 514 | Box::new(Cursor::new(data)) | 460 | | } | 461 | | #[cfg(feature = "fax")] | 462 | 77 | CompressionMethod::Fax4 => Box::new(super::stream::Group4Reader::new( | 463 | 77 | dimensions, | 464 | 77 | reader, | 465 | 77 | compressed_length, | 466 | 0 | )?), | 467 | 2 | method => { | 468 | 2 | return Err(TiffError::UnsupportedError( | 469 | 2 | TiffUnsupportedError::UnsupportedCompressionMethod(method), | 470 | 2 | )) | 471 | | } | 472 | | }) | 473 | 17.5k | } |
Unexecuted instantiation: <tiff::decoder::image::Image>::create_reader::<_> |
474 | | |
475 | | /// Samples per pixel within chunk. |
476 | | /// |
477 | | /// In planar config, samples are stored in separate strips/chunks, also called bands. |
478 | | /// |
479 | | /// Example with `bits_per_sample = [8, 8, 8]` and `PhotometricInterpretation::RGB`: |
480 | | /// * `PlanarConfiguration::Chunky` -> 3 (RGBRGBRGB...) |
481 | | /// * `PlanarConfiguration::Planar` -> 1 (RRR...) (GGG...) (BBB...) |
482 | 29.7k | pub(crate) fn samples_per_pixel(&self) -> usize { |
483 | 29.7k | match self.planar_config { |
484 | 29.7k | PlanarConfiguration::Chunky => self.samples.into(), |
485 | 0 | PlanarConfiguration::Planar => 1, |
486 | | } |
487 | 29.7k | } |
488 | | |
489 | | /// Number of strips per pixel. |
490 | 4.04k | pub(crate) fn strips_per_pixel(&self) -> usize { |
491 | 4.04k | match self.planar_config { |
492 | 4.04k | PlanarConfiguration::Chunky => 1, |
493 | 0 | PlanarConfiguration::Planar => self.samples.into(), |
494 | | } |
495 | 4.04k | } |
496 | | |
497 | 0 | pub(crate) fn chunk_file_range(&self, chunk: u32) -> TiffResult<(u64, u64)> { |
498 | 0 | let file_offset = self |
499 | 0 | .chunk_offsets |
500 | 0 | .get(chunk as usize) |
501 | 0 | .ok_or(TiffError::FormatError( |
502 | 0 | TiffFormatError::InconsistentSizesEncountered, |
503 | 0 | ))?; |
504 | | |
505 | 0 | let compressed_bytes = |
506 | 0 | self.chunk_bytes |
507 | 0 | .get(chunk as usize) |
508 | 0 | .ok_or(TiffError::FormatError( |
509 | 0 | TiffFormatError::InconsistentSizesEncountered, |
510 | 0 | ))?; |
511 | | |
512 | 0 | Ok((*file_offset, *compressed_bytes)) |
513 | 0 | } |
514 | | |
515 | 39.2k | pub(crate) fn chunk_dimensions(&self) -> TiffResult<(u32, u32)> { |
516 | 39.2k | match self.chunk_type { |
517 | | ChunkType::Strip => { |
518 | 12.4k | let strip_attrs = self.strip_decoder.as_ref().unwrap(); |
519 | 12.4k | Ok((self.width, strip_attrs.rows_per_strip)) |
520 | | } |
521 | | ChunkType::Tile => { |
522 | 26.8k | let tile_attrs = self.tile_attributes.as_ref().unwrap(); |
523 | | Ok(( |
524 | 26.8k | u32::try_from(tile_attrs.tile_width)?, |
525 | 26.8k | u32::try_from(tile_attrs.tile_length)?, |
526 | | )) |
527 | | } |
528 | | } |
529 | 39.2k | } |
530 | | |
531 | 17.5k | pub(crate) fn chunk_data_dimensions(&self, chunk_index: u32) -> TiffResult<(u32, u32)> { |
532 | 17.5k | let dims = self.chunk_dimensions()?; |
533 | | |
534 | 17.5k | match self.chunk_type { |
535 | | ChunkType::Strip => { |
536 | 4.42k | let strip_attrs = self.strip_decoder.as_ref().unwrap(); |
537 | 4.42k | let strips_per_band = |
538 | 4.42k | self.height.saturating_sub(1) / strip_attrs.rows_per_strip + 1; |
539 | 4.42k | let strip_height_without_padding = (chunk_index % strips_per_band) |
540 | 4.42k | .checked_mul(dims.1) |
541 | 4.42k | .and_then(|x| self.height.checked_sub(x)) |
542 | 4.42k | .ok_or(TiffError::UsageError(UsageError::InvalidChunkIndex( |
543 | 4.42k | chunk_index, |
544 | 4.42k | )))?; |
545 | | |
546 | | // Ignore potential vertical padding on the bottommost strip |
547 | 4.42k | let strip_height = dims.1.min(strip_height_without_padding); |
548 | | |
549 | 4.42k | Ok((dims.0, strip_height)) |
550 | | } |
551 | | ChunkType::Tile => { |
552 | 13.1k | let tile_attrs = self.tile_attributes.as_ref().unwrap(); |
553 | 13.1k | let (padding_right, padding_down) = tile_attrs.get_padding(chunk_index as usize); |
554 | | |
555 | 13.1k | let tile_width = tile_attrs.tile_width - padding_right; |
556 | 13.1k | let tile_length = tile_attrs.tile_length - padding_down; |
557 | | |
558 | 13.1k | Ok((u32::try_from(tile_width)?, u32::try_from(tile_length)?)) |
559 | | } |
560 | | } |
561 | 17.5k | } |
562 | | |
563 | 17.5k | pub(crate) fn expand_chunk( |
564 | 17.5k | &self, |
565 | 17.5k | reader: &mut ValueReader<impl Read>, |
566 | 17.5k | buf: &mut [u8], |
567 | 17.5k | output_row_stride: usize, |
568 | 17.5k | chunk_index: u32, |
569 | 17.5k | ) -> TiffResult<()> { |
570 | | let ValueReader { |
571 | 17.5k | reader, |
572 | | bigtiff: _, |
573 | 17.5k | limits, |
574 | 17.5k | } = reader; |
575 | | |
576 | 17.5k | let byte_order = reader.byte_order; |
577 | 17.5k | let reader = reader.inner(); |
578 | | |
579 | | // Validate that the color type is supported. |
580 | 17.5k | let color_type = self.colortype()?; |
581 | 12.8k | match color_type { |
582 | 438 | ColorType::RGB(n) |
583 | 126 | | ColorType::RGBA(n) |
584 | 98 | | ColorType::CMYK(n) |
585 | 0 | | ColorType::CMYKA(n) |
586 | 0 | | ColorType::YCbCr(n) |
587 | 4.11k | | ColorType::Gray(n) |
588 | | | ColorType::Multiband { |
589 | 0 | bit_depth: n, |
590 | | num_samples: _, |
591 | 16.9k | } if n == 8 || n == 16 || n == 32 || n == 64 => {} |
592 | 12.8k | ColorType::Gray(n) |
593 | | | ColorType::Multiband { |
594 | 0 | bit_depth: n, |
595 | | num_samples: _, |
596 | 12.8k | } if n < 8 => match self.predictor { |
597 | 12.8k | Predictor::None => {} |
598 | | Predictor::Horizontal => { |
599 | 0 | return Err(TiffError::UnsupportedError( |
600 | 0 | TiffUnsupportedError::HorizontalPredictor(color_type), |
601 | 0 | )); |
602 | | } |
603 | | Predictor::FloatingPoint => { |
604 | 0 | return Err(TiffError::UnsupportedError( |
605 | 0 | TiffUnsupportedError::FloatingPointPredictor(color_type), |
606 | 0 | )); |
607 | | } |
608 | | }, |
609 | 0 | type_ => { |
610 | 0 | return Err(TiffError::UnsupportedError( |
611 | 0 | TiffUnsupportedError::UnsupportedColorType(type_), |
612 | 0 | )); |
613 | | } |
614 | | } |
615 | | |
616 | | // Validate that the predictor is supported for the sample type. |
617 | 17.5k | match (self.predictor, self.sample_format) { |
618 | | ( |
619 | | Predictor::Horizontal, |
620 | | SampleFormat::Int | SampleFormat::Uint | SampleFormat::IEEEFP, |
621 | 405 | ) => {} |
622 | | (Predictor::Horizontal, _) => { |
623 | 0 | return Err(TiffError::UnsupportedError( |
624 | 0 | TiffUnsupportedError::HorizontalPredictor(color_type), |
625 | 0 | )); |
626 | | } |
627 | 0 | (Predictor::FloatingPoint, SampleFormat::IEEEFP) => {} |
628 | | (Predictor::FloatingPoint, _) => { |
629 | 1 | return Err(TiffError::UnsupportedError( |
630 | 1 | TiffUnsupportedError::FloatingPointPredictor(color_type), |
631 | 1 | )); |
632 | | } |
633 | 17.1k | _ => {} |
634 | | } |
635 | | |
636 | 17.5k | let compressed_bytes = |
637 | 17.5k | self.chunk_bytes |
638 | 17.5k | .get(chunk_index as usize) |
639 | 17.5k | .ok_or(TiffError::FormatError( |
640 | 17.5k | TiffFormatError::InconsistentSizesEncountered, |
641 | 17.5k | ))?; |
642 | 17.5k | if *compressed_bytes > limits.intermediate_buffer_size as u64 { |
643 | 14 | return Err(TiffError::LimitsExceeded); |
644 | 17.5k | } |
645 | | |
646 | 17.5k | let compression_method = self.compression_method; |
647 | 17.5k | let photometric_interpretation = self.photometric_interpretation; |
648 | 17.5k | let predictor = self.predictor; |
649 | 17.5k | let samples = self.samples_per_pixel(); |
650 | | |
651 | 17.5k | let chunk_dims = self.chunk_dimensions()?; |
652 | 17.5k | let data_dims = self.chunk_data_dimensions(chunk_index)?; |
653 | | |
654 | 17.5k | let chunk_row_bits = (u64::from(chunk_dims.0) * u64::from(self.bits_per_sample)) |
655 | 17.5k | .checked_mul(samples as u64) |
656 | 17.5k | .ok_or(TiffError::LimitsExceeded)?; |
657 | 17.5k | let chunk_row_bytes: usize = chunk_row_bits.div_ceil(8).try_into()?; |
658 | | |
659 | 17.5k | let data_row_bits = (u64::from(data_dims.0) * u64::from(self.bits_per_sample)) |
660 | 17.5k | .checked_mul(samples as u64) |
661 | 17.5k | .ok_or(TiffError::LimitsExceeded)?; |
662 | 17.5k | let data_row_bytes: usize = data_row_bits.div_ceil(8).try_into()?; |
663 | | |
664 | | // TODO: Should these return errors instead? |
665 | 17.5k | assert!(output_row_stride >= data_row_bytes); |
666 | 17.5k | assert!(buf.len() >= output_row_stride * (data_dims.1 as usize - 1) + data_row_bytes); |
667 | | |
668 | 17.5k | let mut reader = Self::create_reader( |
669 | 17.5k | reader, |
670 | 17.5k | compression_method, |
671 | 17.5k | *compressed_bytes, |
672 | 17.5k | self.jpeg_tables.as_deref().map(|a| &**a), <tiff::decoder::image::Image>::expand_chunk::<std::io::cursor::Cursor<&[u8]>>::{closure#0}Line | Count | Source | 672 | 1.49k | self.jpeg_tables.as_deref().map(|a| &**a), |
Unexecuted instantiation: <tiff::decoder::image::Image>::expand_chunk::<_>::{closure#0} |
673 | 17.5k | chunk_dims, |
674 | 3.00k | )?; |
675 | | |
676 | 14.5k | if output_row_stride == chunk_row_bytes { |
677 | 1.45k | let tile = &mut buf[..chunk_row_bytes * data_dims.1 as usize]; |
678 | 1.45k | reader.read_exact(tile)?; |
679 | | |
680 | 739k | for row in tile.chunks_mut(chunk_row_bytes) { |
681 | 739k | super::fix_endianness_and_predict( |
682 | 739k | row, |
683 | 739k | color_type.bit_depth(), |
684 | 739k | samples, |
685 | 739k | byte_order, |
686 | 739k | predictor, |
687 | 739k | ); |
688 | 739k | } |
689 | 994 | if photometric_interpretation == PhotometricInterpretation::WhiteIsZero { |
690 | 166 | super::invert_colors(tile, color_type, self.sample_format)?; |
691 | 828 | } |
692 | 13.1k | } else if chunk_row_bytes > data_row_bytes && self.predictor == Predictor::FloatingPoint { |
693 | | // The floating point predictor shuffles the padding bytes into the encoded output, so |
694 | | // this case is handled specially when needed. |
695 | 0 | let mut encoded = vec![0u8; chunk_row_bytes]; |
696 | 0 | for row in buf.chunks_mut(output_row_stride).take(data_dims.1 as usize) { |
697 | 0 | reader.read_exact(&mut encoded)?; |
698 | | |
699 | 0 | let row = &mut row[..data_row_bytes]; |
700 | 0 | match color_type.bit_depth() { |
701 | 0 | 16 => predict_f16(&mut encoded, row, samples), |
702 | 0 | 32 => predict_f32(&mut encoded, row, samples), |
703 | 0 | 64 => predict_f64(&mut encoded, row, samples), |
704 | 0 | _ => unreachable!(), |
705 | | } |
706 | 0 | if photometric_interpretation == PhotometricInterpretation::WhiteIsZero { |
707 | 0 | super::invert_colors(row, color_type, self.sample_format)?; |
708 | 0 | } |
709 | | } |
710 | | } else { |
711 | 3.22M | for row in buf.chunks_mut(output_row_stride).take(data_dims.1 as usize) { |
712 | 3.22M | let row = &mut row[..data_row_bytes]; |
713 | 3.22M | reader.read_exact(row)?; |
714 | | |
715 | | // Skip horizontal padding |
716 | 3.21M | if chunk_row_bytes > data_row_bytes { |
717 | 119k | let len = u64::try_from(chunk_row_bytes - data_row_bytes)?; |
718 | 119k | io::copy(&mut reader.by_ref().take(len), &mut io::sink())?; |
719 | 3.10M | } |
720 | | |
721 | 3.21M | super::fix_endianness_and_predict( |
722 | 3.21M | row, |
723 | 3.21M | color_type.bit_depth(), |
724 | 3.21M | samples, |
725 | 3.21M | byte_order, |
726 | 3.21M | predictor, |
727 | | ); |
728 | 3.21M | if photometric_interpretation == PhotometricInterpretation::WhiteIsZero { |
729 | 165k | super::invert_colors(row, color_type, self.sample_format)?; |
730 | 3.05M | } |
731 | | } |
732 | | } |
733 | | |
734 | 13.6k | Ok(()) |
735 | 17.5k | } <tiff::decoder::image::Image>::expand_chunk::<std::io::cursor::Cursor<&[u8]>> Line | Count | Source | 563 | 17.5k | pub(crate) fn expand_chunk( | 564 | 17.5k | &self, | 565 | 17.5k | reader: &mut ValueReader<impl Read>, | 566 | 17.5k | buf: &mut [u8], | 567 | 17.5k | output_row_stride: usize, | 568 | 17.5k | chunk_index: u32, | 569 | 17.5k | ) -> TiffResult<()> { | 570 | | let ValueReader { | 571 | 17.5k | reader, | 572 | | bigtiff: _, | 573 | 17.5k | limits, | 574 | 17.5k | } = reader; | 575 | | | 576 | 17.5k | let byte_order = reader.byte_order; | 577 | 17.5k | let reader = reader.inner(); | 578 | | | 579 | | // Validate that the color type is supported. | 580 | 17.5k | let color_type = self.colortype()?; | 581 | 12.8k | match color_type { | 582 | 438 | ColorType::RGB(n) | 583 | 126 | | ColorType::RGBA(n) | 584 | 98 | | ColorType::CMYK(n) | 585 | 0 | | ColorType::CMYKA(n) | 586 | 0 | | ColorType::YCbCr(n) | 587 | 4.11k | | ColorType::Gray(n) | 588 | | | ColorType::Multiband { | 589 | 0 | bit_depth: n, | 590 | | num_samples: _, | 591 | 16.9k | } if n == 8 || n == 16 || n == 32 || n == 64 => {} | 592 | 12.8k | ColorType::Gray(n) | 593 | | | ColorType::Multiband { | 594 | 0 | bit_depth: n, | 595 | | num_samples: _, | 596 | 12.8k | } if n < 8 => match self.predictor { | 597 | 12.8k | Predictor::None => {} | 598 | | Predictor::Horizontal => { | 599 | 0 | return Err(TiffError::UnsupportedError( | 600 | 0 | TiffUnsupportedError::HorizontalPredictor(color_type), | 601 | 0 | )); | 602 | | } | 603 | | Predictor::FloatingPoint => { | 604 | 0 | return Err(TiffError::UnsupportedError( | 605 | 0 | TiffUnsupportedError::FloatingPointPredictor(color_type), | 606 | 0 | )); | 607 | | } | 608 | | }, | 609 | 0 | type_ => { | 610 | 0 | return Err(TiffError::UnsupportedError( | 611 | 0 | TiffUnsupportedError::UnsupportedColorType(type_), | 612 | 0 | )); | 613 | | } | 614 | | } | 615 | | | 616 | | // Validate that the predictor is supported for the sample type. | 617 | 17.5k | match (self.predictor, self.sample_format) { | 618 | | ( | 619 | | Predictor::Horizontal, | 620 | | SampleFormat::Int | SampleFormat::Uint | SampleFormat::IEEEFP, | 621 | 405 | ) => {} | 622 | | (Predictor::Horizontal, _) => { | 623 | 0 | return Err(TiffError::UnsupportedError( | 624 | 0 | TiffUnsupportedError::HorizontalPredictor(color_type), | 625 | 0 | )); | 626 | | } | 627 | 0 | (Predictor::FloatingPoint, SampleFormat::IEEEFP) => {} | 628 | | (Predictor::FloatingPoint, _) => { | 629 | 1 | return Err(TiffError::UnsupportedError( | 630 | 1 | TiffUnsupportedError::FloatingPointPredictor(color_type), | 631 | 1 | )); | 632 | | } | 633 | 17.1k | _ => {} | 634 | | } | 635 | | | 636 | 17.5k | let compressed_bytes = | 637 | 17.5k | self.chunk_bytes | 638 | 17.5k | .get(chunk_index as usize) | 639 | 17.5k | .ok_or(TiffError::FormatError( | 640 | 17.5k | TiffFormatError::InconsistentSizesEncountered, | 641 | 17.5k | ))?; | 642 | 17.5k | if *compressed_bytes > limits.intermediate_buffer_size as u64 { | 643 | 14 | return Err(TiffError::LimitsExceeded); | 644 | 17.5k | } | 645 | | | 646 | 17.5k | let compression_method = self.compression_method; | 647 | 17.5k | let photometric_interpretation = self.photometric_interpretation; | 648 | 17.5k | let predictor = self.predictor; | 649 | 17.5k | let samples = self.samples_per_pixel(); | 650 | | | 651 | 17.5k | let chunk_dims = self.chunk_dimensions()?; | 652 | 17.5k | let data_dims = self.chunk_data_dimensions(chunk_index)?; | 653 | | | 654 | 17.5k | let chunk_row_bits = (u64::from(chunk_dims.0) * u64::from(self.bits_per_sample)) | 655 | 17.5k | .checked_mul(samples as u64) | 656 | 17.5k | .ok_or(TiffError::LimitsExceeded)?; | 657 | 17.5k | let chunk_row_bytes: usize = chunk_row_bits.div_ceil(8).try_into()?; | 658 | | | 659 | 17.5k | let data_row_bits = (u64::from(data_dims.0) * u64::from(self.bits_per_sample)) | 660 | 17.5k | .checked_mul(samples as u64) | 661 | 17.5k | .ok_or(TiffError::LimitsExceeded)?; | 662 | 17.5k | let data_row_bytes: usize = data_row_bits.div_ceil(8).try_into()?; | 663 | | | 664 | | // TODO: Should these return errors instead? | 665 | 17.5k | assert!(output_row_stride >= data_row_bytes); | 666 | 17.5k | assert!(buf.len() >= output_row_stride * (data_dims.1 as usize - 1) + data_row_bytes); | 667 | | | 668 | 17.5k | let mut reader = Self::create_reader( | 669 | 17.5k | reader, | 670 | 17.5k | compression_method, | 671 | 17.5k | *compressed_bytes, | 672 | 17.5k | self.jpeg_tables.as_deref().map(|a| &**a), | 673 | 17.5k | chunk_dims, | 674 | 3.00k | )?; | 675 | | | 676 | 14.5k | if output_row_stride == chunk_row_bytes { | 677 | 1.45k | let tile = &mut buf[..chunk_row_bytes * data_dims.1 as usize]; | 678 | 1.45k | reader.read_exact(tile)?; | 679 | | | 680 | 739k | for row in tile.chunks_mut(chunk_row_bytes) { | 681 | 739k | super::fix_endianness_and_predict( | 682 | 739k | row, | 683 | 739k | color_type.bit_depth(), | 684 | 739k | samples, | 685 | 739k | byte_order, | 686 | 739k | predictor, | 687 | 739k | ); | 688 | 739k | } | 689 | 994 | if photometric_interpretation == PhotometricInterpretation::WhiteIsZero { | 690 | 166 | super::invert_colors(tile, color_type, self.sample_format)?; | 691 | 828 | } | 692 | 13.1k | } else if chunk_row_bytes > data_row_bytes && self.predictor == Predictor::FloatingPoint { | 693 | | // The floating point predictor shuffles the padding bytes into the encoded output, so | 694 | | // this case is handled specially when needed. | 695 | 0 | let mut encoded = vec![0u8; chunk_row_bytes]; | 696 | 0 | for row in buf.chunks_mut(output_row_stride).take(data_dims.1 as usize) { | 697 | 0 | reader.read_exact(&mut encoded)?; | 698 | | | 699 | 0 | let row = &mut row[..data_row_bytes]; | 700 | 0 | match color_type.bit_depth() { | 701 | 0 | 16 => predict_f16(&mut encoded, row, samples), | 702 | 0 | 32 => predict_f32(&mut encoded, row, samples), | 703 | 0 | 64 => predict_f64(&mut encoded, row, samples), | 704 | 0 | _ => unreachable!(), | 705 | | } | 706 | 0 | if photometric_interpretation == PhotometricInterpretation::WhiteIsZero { | 707 | 0 | super::invert_colors(row, color_type, self.sample_format)?; | 708 | 0 | } | 709 | | } | 710 | | } else { | 711 | 3.22M | for row in buf.chunks_mut(output_row_stride).take(data_dims.1 as usize) { | 712 | 3.22M | let row = &mut row[..data_row_bytes]; | 713 | 3.22M | reader.read_exact(row)?; | 714 | | | 715 | | // Skip horizontal padding | 716 | 3.21M | if chunk_row_bytes > data_row_bytes { | 717 | 119k | let len = u64::try_from(chunk_row_bytes - data_row_bytes)?; | 718 | 119k | io::copy(&mut reader.by_ref().take(len), &mut io::sink())?; | 719 | 3.10M | } | 720 | | | 721 | 3.21M | super::fix_endianness_and_predict( | 722 | 3.21M | row, | 723 | 3.21M | color_type.bit_depth(), | 724 | 3.21M | samples, | 725 | 3.21M | byte_order, | 726 | 3.21M | predictor, | 727 | | ); | 728 | 3.21M | if photometric_interpretation == PhotometricInterpretation::WhiteIsZero { | 729 | 165k | super::invert_colors(row, color_type, self.sample_format)?; | 730 | 3.05M | } | 731 | | } | 732 | | } | 733 | | | 734 | 13.6k | Ok(()) | 735 | 17.5k | } |
Unexecuted instantiation: <tiff::decoder::image::Image>::expand_chunk::<_> |
736 | | } |