/rust/registry/src/index.crates.io-1949cf8c6b5b557f/tiff-0.11.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, ExtraSamples, PhotometricInterpretation, PlanarConfiguration, Predictor, |
8 | | SampleFormat, Tag, |
9 | | }; |
10 | | use crate::{ |
11 | | ColorType, Directory, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, UsageError, |
12 | | }; |
13 | | |
14 | | use std::io::{self, Cursor, Read, Seek}; |
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 | 0 | pub fn tiles_across(&self) -> usize { |
34 | 0 | self.image_width.div_ceil(self.tile_width) |
35 | 0 | } |
36 | 0 | pub fn tiles_down(&self) -> usize { |
37 | 0 | self.image_height.div_ceil(self.tile_length) |
38 | 0 | } |
39 | 0 | fn padding_right(&self) -> usize { |
40 | 0 | (self.tile_width - self.image_width % self.tile_width) % self.tile_width |
41 | 0 | } |
42 | 0 | fn padding_down(&self) -> usize { |
43 | 0 | (self.tile_length - self.image_height % self.tile_length) % self.tile_length |
44 | 0 | } |
45 | 0 | pub fn get_padding(&self, tile: usize) -> (usize, usize) { |
46 | 0 | let row = tile / self.tiles_across(); |
47 | 0 | let column = tile % self.tiles_across(); |
48 | | |
49 | 0 | let padding_right = if column == self.tiles_across() - 1 { |
50 | 0 | self.padding_right() |
51 | | } else { |
52 | 0 | 0 |
53 | | }; |
54 | | |
55 | 0 | let padding_down = if row == self.tiles_down() - 1 { |
56 | 0 | self.padding_down() |
57 | | } else { |
58 | 0 | 0 |
59 | | }; |
60 | | |
61 | 0 | (padding_right, padding_down) |
62 | 0 | } |
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 | | /// The `ExtraSamples`, defaulting to empty if not given. |
73 | | pub extra_samples: Vec<ExtraSamples>, |
74 | | /// Number of samples that belong to the photometric interpretation, samples except |
75 | | /// `ExtraSamples` (338, 0x0152) tag. |
76 | | pub photometric_samples: u16, |
77 | | pub sample_format: SampleFormat, |
78 | | pub photometric_interpretation: PhotometricInterpretation, |
79 | | pub compression_method: CompressionMethod, |
80 | | pub predictor: Predictor, |
81 | | pub jpeg_tables: Option<Arc<Vec<u8>>>, |
82 | | pub chunk_type: ChunkType, |
83 | | pub planar_config: PlanarConfiguration, |
84 | | pub strip_decoder: Option<StripDecodeState>, |
85 | | pub tile_attributes: Option<TileAttributes>, |
86 | | pub chunk_offsets: Vec<u64>, |
87 | | pub chunk_bytes: Vec<u64>, |
88 | | pub chroma_subsampling: (u16, u16), |
89 | | } |
90 | | |
91 | | /// Describes how to read a tile-aligned portion of the image. |
92 | | #[derive(Clone)] |
93 | | pub(crate) struct ReadoutLayout { |
94 | | /// The planar configuration, which applies to both the underlying image and the output buffer. |
95 | | /// This may be relaxed if we find a clean enough way to provide it. |
96 | | pub planar_config: PlanarConfiguration, |
97 | | |
98 | | /// The sample interpretation (interpret with planar_config). |
99 | | /// |
100 | | /// FIXME: we should not require this here. The ability to turn out the raw bytes from the |
101 | | /// sample arrays is very different from turning out interpretable color. Firstly we can always |
102 | | /// readout `Multiband` but currently only use that ColorType in special circumstances (it must |
103 | | /// not overlap cases where actually want to use a ColorType). |
104 | | /// |
105 | | /// And then we have CIE Lab, which uses a tuple of `(u8, i8, i8)`, that is still filterable |
106 | | /// but still not represented by any of our `DecoderResult` variants. Other color variants |
107 | | /// depend on extra tags (YCbCrCoefficients/0x0211) and we don't have a good side channel to |
108 | | /// tag the output with all that TIFF specific information, so arguably we should process and |
109 | | /// apply those to the data so it becomes a self-contained representation. |
110 | | /// |
111 | | /// This should be computed at a higher level, in `Decoder`, instead. |
112 | | pub color: ColorType, |
113 | | /// The number of bytes from one row to another. |
114 | | pub minimum_row_stride: usize, |
115 | | /// The format of samples (assumed uniform for now, same with depth of `ColorType`). |
116 | | pub sample_format: SampleFormat, |
117 | | |
118 | | /// Number of bytes to advance in output per row. |
119 | | pub row_stride: usize, |
120 | | /// Number of bytes to advance in output per chunk in width. |
121 | | pub chunk_row_stride: usize, |
122 | | /// Number of bytes to advance in output per chunk in height. |
123 | | pub chunk_col_stride: usize, |
124 | | /// Number of bytes in output from one plane to another. |
125 | | pub plane_stride: usize, |
126 | | |
127 | | /// Bits per sample in the encoded data. |
128 | | pub tiff_bits_per_sample: u8, |
129 | | /// Number of samples in the encoded data. |
130 | | pub tiff_samples: u16, |
131 | | /// Dimensions of the underlying rectangular chunks (tile or strips). |
132 | | pub tiff_chunk_dimensions: (u32, u32), |
133 | | /// Number of bytes in the underlying data with all samples per row of chunks. |
134 | | pub tiff_row_bytes: usize, |
135 | | |
136 | | /// Chunks until wrapping to the next row of chunks. |
137 | | pub chunks_across: u32, |
138 | | /// Chunks to advance to get to the next plane of chunks. |
139 | | pub chunks_per_plane: u32, |
140 | | } |
141 | | |
142 | | impl Image { |
143 | 0 | pub fn from_reader<R: Read + Seek>( |
144 | 0 | decoder: &mut ValueReader<R>, |
145 | 0 | ifd: Directory, |
146 | 0 | ) -> TiffResult<Image> { |
147 | 0 | let mut tag_reader = TagReader { decoder, ifd: &ifd }; |
148 | | |
149 | 0 | let width = tag_reader.require_tag(Tag::ImageWidth)?.into_u32()?; |
150 | 0 | let height = tag_reader.require_tag(Tag::ImageLength)?.into_u32()?; |
151 | 0 | if width == 0 || height == 0 { |
152 | 0 | return Err(TiffError::FormatError(TiffFormatError::InvalidDimensions( |
153 | 0 | width, height, |
154 | 0 | ))); |
155 | 0 | } |
156 | | |
157 | 0 | let photometric_interpretation = tag_reader |
158 | 0 | .find_tag(Tag::PhotometricInterpretation)? |
159 | 0 | .map(Value::into_u16) |
160 | 0 | .transpose()? |
161 | 0 | .and_then(PhotometricInterpretation::from_u16) |
162 | 0 | .ok_or(TiffUnsupportedError::UnknownInterpretation)?; |
163 | | |
164 | | // Try to parse both the compression method and the number, format, and bits of the included samples. |
165 | | // If they are not explicitly specified, those tags are reset to their default values and not carried from previous images. |
166 | 0 | let compression_method = match tag_reader.find_tag(Tag::Compression)? { |
167 | 0 | Some(val) => CompressionMethod::from_u16_exhaustive(val.into_u16()?), |
168 | 0 | None => CompressionMethod::None, |
169 | | }; |
170 | | |
171 | 0 | let jpeg_tables = if compression_method == CompressionMethod::ModernJPEG |
172 | 0 | && ifd.contains(Tag::JPEGTables) |
173 | | { |
174 | 0 | let vec = tag_reader |
175 | 0 | .find_tag(Tag::JPEGTables)? |
176 | 0 | .unwrap() |
177 | 0 | .into_u8_vec()?; |
178 | 0 | if vec.len() < 2 { |
179 | 0 | return Err(TiffError::FormatError( |
180 | 0 | TiffFormatError::InvalidTagValueType(Tag::JPEGTables), |
181 | 0 | )); |
182 | 0 | } |
183 | | |
184 | 0 | Some(Arc::new(vec)) |
185 | | } else { |
186 | 0 | None |
187 | | }; |
188 | | |
189 | 0 | let samples: u16 = tag_reader |
190 | 0 | .find_tag(Tag::SamplesPerPixel)? |
191 | 0 | .map(Value::into_u16) |
192 | 0 | .transpose()? |
193 | 0 | .unwrap_or(1); |
194 | | |
195 | 0 | if samples == 0 { |
196 | 0 | return Err(TiffFormatError::SamplesPerPixelIsZero.into()); |
197 | 0 | } |
198 | | |
199 | 0 | let extra_samples = match tag_reader.find_tag(Tag::ExtraSamples)? { |
200 | 0 | Some(n) => n.into_u16_vec()?, |
201 | 0 | None => vec![], |
202 | | }; |
203 | | |
204 | 0 | let extra_samples = extra_samples |
205 | 0 | .into_iter() |
206 | 0 | .map(|x| ExtraSamples::from_u16(x).unwrap_or(ExtraSamples::Unspecified)) Unexecuted instantiation: <tiff::decoder::image::Image>::from_reader::<std::io::cursor::Cursor<&[u8]>>::{closure#0}Unexecuted instantiation: <tiff::decoder::image::Image>::from_reader::<_>::{closure#0} |
207 | 0 | .collect::<Vec<_>>(); |
208 | | |
209 | 0 | let photometric_samples = match usize::from(samples).checked_sub(extra_samples.len()) { |
210 | | None => { |
211 | 0 | return Err(TiffError::FormatError( |
212 | 0 | TiffFormatError::InconsistentSizesEncountered, |
213 | 0 | )); |
214 | | } |
215 | 0 | Some(n) => n as u16, |
216 | | }; |
217 | | |
218 | 0 | let sample_format = match tag_reader.find_tag_uint_vec(Tag::SampleFormat)? { |
219 | 0 | Some(vals) => { |
220 | 0 | let sample_format: Vec<_> = vals |
221 | 0 | .into_iter() |
222 | 0 | .map(SampleFormat::from_u16_exhaustive) |
223 | 0 | .collect(); |
224 | | |
225 | 0 | let Some(format) = sample_format.first().copied() else { |
226 | | // Reject empty sample formats |
227 | 0 | return Err(TiffFormatError::InvalidTagValueType(Tag::SampleFormat).into()); |
228 | | }; |
229 | | // TODO: for now, only homogenous formats across samples are supported. |
230 | 0 | if !sample_format.iter().all(|&s| s == format) {Unexecuted instantiation: <tiff::decoder::image::Image>::from_reader::<std::io::cursor::Cursor<&[u8]>>::{closure#1}Unexecuted instantiation: <tiff::decoder::image::Image>::from_reader::<_>::{closure#1} |
231 | 0 | return Err(TiffUnsupportedError::UnsupportedSampleFormat(sample_format).into()); |
232 | 0 | } |
233 | 0 | format |
234 | | } |
235 | 0 | None => SampleFormat::Uint, |
236 | | }; |
237 | | |
238 | 0 | let bits_per_sample: Vec<u8> = tag_reader |
239 | 0 | .find_tag_uint_vec(Tag::BitsPerSample)? |
240 | 0 | .unwrap_or_else(|| vec![1]); Unexecuted instantiation: <tiff::decoder::image::Image>::from_reader::<std::io::cursor::Cursor<&[u8]>>::{closure#2}Unexecuted instantiation: <tiff::decoder::image::Image>::from_reader::<_>::{closure#2} |
241 | | |
242 | | // Technically bits_per_sample.len() should be *equal* to samples, but libtiff also allows |
243 | | // it to be a single value that applies to all samples. |
244 | 0 | if bits_per_sample.len() != usize::from(samples) && bits_per_sample.len() != 1 { |
245 | 0 | return Err(TiffError::FormatError( |
246 | 0 | TiffFormatError::InconsistentSizesEncountered, |
247 | 0 | )); |
248 | 0 | } |
249 | | |
250 | | // This library (and libtiff) do not support mixed sample formats and zero bits per sample |
251 | | // doesn't make sense. |
252 | 0 | 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::<std::io::cursor::Cursor<&[u8]>>::{closure#3}Unexecuted instantiation: <tiff::decoder::image::Image>::from_reader::<_>::{closure#3} |
253 | 0 | return Err(TiffUnsupportedError::InconsistentBitsPerSample(bits_per_sample).into()); |
254 | 0 | } |
255 | | |
256 | 0 | let predictor = tag_reader |
257 | 0 | .find_tag(Tag::Predictor)? |
258 | 0 | .map(Value::into_u16) |
259 | 0 | .transpose()? |
260 | 0 | .map(|p| { |
261 | 0 | Predictor::from_u16(p) |
262 | 0 | .ok_or(TiffError::FormatError(TiffFormatError::UnknownPredictor(p))) |
263 | 0 | }) Unexecuted instantiation: <tiff::decoder::image::Image>::from_reader::<std::io::cursor::Cursor<&[u8]>>::{closure#4}Unexecuted instantiation: <tiff::decoder::image::Image>::from_reader::<_>::{closure#4} |
264 | 0 | .transpose()? |
265 | 0 | .unwrap_or(Predictor::None); |
266 | | |
267 | 0 | let planar_config = tag_reader |
268 | 0 | .find_tag(Tag::PlanarConfiguration)? |
269 | 0 | .map(Value::into_u16) |
270 | 0 | .transpose()? |
271 | 0 | .map(|p| { |
272 | 0 | PlanarConfiguration::from_u16(p).ok_or(TiffError::FormatError( |
273 | 0 | TiffFormatError::UnknownPlanarConfiguration(p), |
274 | 0 | )) |
275 | 0 | }) Unexecuted instantiation: <tiff::decoder::image::Image>::from_reader::<std::io::cursor::Cursor<&[u8]>>::{closure#5}Unexecuted instantiation: <tiff::decoder::image::Image>::from_reader::<_>::{closure#5} |
276 | 0 | .transpose()? |
277 | 0 | .unwrap_or(PlanarConfiguration::Chunky); |
278 | | |
279 | 0 | let ycbcr_subsampling = tag_reader.find_tag_uint_vec::<u16>(Tag::ChromaSubsampling)?; |
280 | | |
281 | 0 | let chroma_subsampling = if let Some(subsamples) = &ycbcr_subsampling { |
282 | 0 | let [a, b] = subsamples.as_slice() else { |
283 | 0 | return Err(TiffError::FormatError(TiffFormatError::InvalidCountForTag( |
284 | 0 | Tag::ChromaSubsampling, |
285 | 0 | subsamples.len(), |
286 | 0 | ))); |
287 | | }; |
288 | | |
289 | | // ImageWidth and ImageLength are constrained to be integer multiples of |
290 | | // YCbCrSubsampleHoriz and YCbCrSubsampleVert respectively. TileWidth and TileLength |
291 | | // have the same constraints. RowsPerStrip must be an integer multiple of |
292 | | // YCbCrSubsampleVert. |
293 | 0 | (*a, *b) |
294 | | } else { |
295 | 0 | (2, 2) |
296 | | }; |
297 | | |
298 | 0 | let planes = match planar_config { |
299 | 0 | PlanarConfiguration::Chunky => 1, |
300 | 0 | PlanarConfiguration::Planar => samples, |
301 | | }; |
302 | | |
303 | | let chunk_type; |
304 | | let chunk_offsets; |
305 | | let chunk_bytes; |
306 | | let strip_decoder; |
307 | | let tile_attributes; |
308 | 0 | match ( |
309 | 0 | ifd.contains(Tag::StripByteCounts), |
310 | 0 | ifd.contains(Tag::StripOffsets), |
311 | 0 | ifd.contains(Tag::TileByteCounts), |
312 | 0 | ifd.contains(Tag::TileOffsets), |
313 | 0 | ) { |
314 | | (true, true, false, false) => { |
315 | 0 | chunk_type = ChunkType::Strip; |
316 | | |
317 | 0 | chunk_offsets = tag_reader |
318 | 0 | .find_tag(Tag::StripOffsets)? |
319 | 0 | .unwrap() |
320 | 0 | .into_u64_vec()?; |
321 | 0 | chunk_bytes = tag_reader |
322 | 0 | .find_tag(Tag::StripByteCounts)? |
323 | 0 | .unwrap() |
324 | 0 | .into_u64_vec()?; |
325 | 0 | let rows_per_strip = tag_reader |
326 | 0 | .find_tag(Tag::RowsPerStrip)? |
327 | 0 | .map(Value::into_u32) |
328 | 0 | .transpose()? |
329 | 0 | .unwrap_or(height); |
330 | 0 | strip_decoder = Some(StripDecodeState { rows_per_strip }); |
331 | 0 | tile_attributes = None; |
332 | | |
333 | 0 | if chunk_offsets.len() != chunk_bytes.len() |
334 | 0 | || rows_per_strip == 0 |
335 | 0 | || u32::try_from(chunk_offsets.len())? |
336 | 0 | != (height.saturating_sub(1) / rows_per_strip + 1) * planes as u32 |
337 | | { |
338 | 0 | return Err(TiffError::FormatError( |
339 | 0 | TiffFormatError::InconsistentSizesEncountered, |
340 | 0 | )); |
341 | 0 | } |
342 | | } |
343 | | (false, false, true, true) => { |
344 | 0 | chunk_type = ChunkType::Tile; |
345 | | |
346 | 0 | let tile_width = |
347 | 0 | usize::try_from(tag_reader.require_tag(Tag::TileWidth)?.into_u32()?)?; |
348 | 0 | let tile_length = |
349 | 0 | usize::try_from(tag_reader.require_tag(Tag::TileLength)?.into_u32()?)?; |
350 | | |
351 | 0 | if tile_width == 0 { |
352 | 0 | return Err(TiffFormatError::InvalidTagValueType(Tag::TileWidth).into()); |
353 | 0 | } else if tile_length == 0 { |
354 | 0 | return Err(TiffFormatError::InvalidTagValueType(Tag::TileLength).into()); |
355 | 0 | } |
356 | | |
357 | 0 | strip_decoder = None; |
358 | | tile_attributes = Some(TileAttributes { |
359 | 0 | image_width: usize::try_from(width)?, |
360 | 0 | image_height: usize::try_from(height)?, |
361 | 0 | tile_width, |
362 | 0 | tile_length, |
363 | | }); |
364 | 0 | chunk_offsets = tag_reader |
365 | 0 | .find_tag(Tag::TileOffsets)? |
366 | 0 | .unwrap() |
367 | 0 | .into_u64_vec()?; |
368 | 0 | chunk_bytes = tag_reader |
369 | 0 | .find_tag(Tag::TileByteCounts)? |
370 | 0 | .unwrap() |
371 | 0 | .into_u64_vec()?; |
372 | | |
373 | 0 | let tile = tile_attributes.as_ref().unwrap(); |
374 | 0 | if chunk_offsets.len() != chunk_bytes.len() |
375 | 0 | || chunk_offsets.len() |
376 | 0 | != tile.tiles_down() * tile.tiles_across() * planes as usize |
377 | | { |
378 | 0 | return Err(TiffError::FormatError( |
379 | 0 | TiffFormatError::InconsistentSizesEncountered, |
380 | 0 | )); |
381 | 0 | } |
382 | | } |
383 | | (_, _, _, _) => { |
384 | 0 | return Err(TiffError::FormatError( |
385 | 0 | TiffFormatError::StripTileTagConflict, |
386 | 0 | )) |
387 | | } |
388 | | }; |
389 | | |
390 | 0 | Ok(Image { |
391 | 0 | ifd: Some(ifd), |
392 | 0 | width, |
393 | 0 | height, |
394 | 0 | bits_per_sample: bits_per_sample[0], |
395 | 0 | samples, |
396 | 0 | extra_samples, |
397 | 0 | photometric_samples, |
398 | 0 | sample_format, |
399 | 0 | photometric_interpretation, |
400 | 0 | compression_method, |
401 | 0 | jpeg_tables, |
402 | 0 | predictor, |
403 | 0 | chunk_type, |
404 | 0 | planar_config, |
405 | 0 | strip_decoder, |
406 | 0 | tile_attributes, |
407 | 0 | chunk_offsets, |
408 | 0 | chunk_bytes, |
409 | 0 | chroma_subsampling, |
410 | 0 | }) |
411 | 0 | } Unexecuted instantiation: <tiff::decoder::image::Image>::from_reader::<std::io::cursor::Cursor<&[u8]>> Unexecuted instantiation: <tiff::decoder::image::Image>::from_reader::<_> |
412 | | |
413 | 0 | pub(crate) fn colortype(&self) -> TiffResult<ColorType> { |
414 | 0 | let is_alpha_extra_samples = matches!( |
415 | 0 | self.extra_samples.as_slice(), |
416 | 0 | [ExtraSamples::AssociatedAlpha, ..] | [ExtraSamples::UnassociatedAlpha, ..] |
417 | | ); |
418 | | |
419 | 0 | match self.photometric_interpretation { |
420 | 0 | PhotometricInterpretation::RGB => match self.photometric_samples { |
421 | 0 | 3 => Ok(if is_alpha_extra_samples { |
422 | 0 | ColorType::RGBA(self.bits_per_sample) |
423 | | } else { |
424 | 0 | ColorType::RGB(self.bits_per_sample) |
425 | | }), |
426 | 0 | 4 => Ok(ColorType::RGBA(self.bits_per_sample)), |
427 | 0 | _ => Err(TiffError::UnsupportedError( |
428 | 0 | TiffUnsupportedError::InterpretationWithBits( |
429 | 0 | self.photometric_interpretation, |
430 | 0 | vec![self.bits_per_sample; self.samples as usize], |
431 | 0 | ), |
432 | 0 | )), |
433 | | }, |
434 | 0 | PhotometricInterpretation::CMYK => match self.photometric_samples { |
435 | 0 | 4 => Ok(if is_alpha_extra_samples { |
436 | 0 | ColorType::CMYKA(self.bits_per_sample) |
437 | | } else { |
438 | 0 | ColorType::CMYK(self.bits_per_sample) |
439 | | }), |
440 | 0 | 5 => Ok(ColorType::CMYKA(self.bits_per_sample)), |
441 | 0 | _ => Err(TiffError::UnsupportedError( |
442 | 0 | TiffUnsupportedError::InterpretationWithBits( |
443 | 0 | self.photometric_interpretation, |
444 | 0 | vec![self.bits_per_sample; self.samples as usize], |
445 | 0 | ), |
446 | 0 | )), |
447 | | }, |
448 | 0 | PhotometricInterpretation::YCbCr => match self.photometric_samples { |
449 | 0 | 3 => Ok(ColorType::YCbCr(self.bits_per_sample)), |
450 | 0 | _ => Err(TiffError::UnsupportedError( |
451 | 0 | TiffUnsupportedError::InterpretationWithBits( |
452 | 0 | self.photometric_interpretation, |
453 | 0 | vec![self.bits_per_sample; self.samples as usize], |
454 | 0 | ), |
455 | 0 | )), |
456 | | }, |
457 | | // TODO: treatment of WhiteIsZero is not quite consistent with `invert_colors` that is |
458 | | // later called when that interpretation is read. That function does not support |
459 | | // Multiband as a color type and will error. It's unclear how to resolve that exactly. |
460 | | PhotometricInterpretation::BlackIsZero | PhotometricInterpretation::WhiteIsZero => { |
461 | | // Note: compatibility with previous implementation requires us to return extra |
462 | | // samples as `Multiband`. For gray images however the better choice would be |
463 | | // returning a `Gray` color, i.e. matching on `photometric_samples` instead. |
464 | 0 | match self.samples { |
465 | 0 | 1 => Ok(ColorType::Gray(self.bits_per_sample)), |
466 | 0 | _ => Ok(ColorType::Multiband { |
467 | 0 | bit_depth: self.bits_per_sample, |
468 | 0 | num_samples: self.samples, |
469 | 0 | }), |
470 | | } |
471 | | } |
472 | | // ``` |
473 | | // struct IccLab /* Interpretation 9* { |
474 | | // pub L: u8, // SampleFormat::Uint |
475 | | // pub a: u8, // SampleFormat::Uint, defined as TiffLab::a + 128 |
476 | | // pub b: u8, // SampleFormat::Uint, defined as TiffLab::b + 128 |
477 | | // } |
478 | | // ``` |
479 | 0 | PhotometricInterpretation::IccLab => match self.photometric_samples { |
480 | 0 | 3 if matches!(self.sample_format, SampleFormat::Uint) => { |
481 | 0 | Ok(ColorType::Lab(self.bits_per_sample)) |
482 | | } |
483 | 0 | _ => Err(TiffError::UnsupportedError( |
484 | 0 | TiffUnsupportedError::InterpretationWithBits( |
485 | 0 | self.photometric_interpretation, |
486 | 0 | vec![self.bits_per_sample; self.samples as usize], |
487 | 0 | ), |
488 | 0 | )), |
489 | | }, |
490 | | // Unsupported due to inherently heterogeneous sample types. This is represented as: |
491 | | // ``` |
492 | | // struct TiffLab /* Interpretation 8* { |
493 | | // pub L: u8, // SampleFormat::Uint |
494 | | // pub a: i8, // SampleFormat::Int |
495 | | // pub b: i8, // SampleFormat::Int |
496 | | // } |
497 | | // ``` |
498 | 0 | PhotometricInterpretation::CIELab => Err(TiffError::UnsupportedError( |
499 | 0 | TiffUnsupportedError::InterpretationWithBits( |
500 | 0 | PhotometricInterpretation::CIELab, |
501 | 0 | vec![self.bits_per_sample; self.samples as usize], |
502 | 0 | ), |
503 | 0 | )), |
504 | | // Unsupported due to extra unfiltering and conversion steps. We need to find the |
505 | | // Decode tag (SRATIONAL; 2 * SamplesPerPixel) and apply the following conversion: |
506 | | // |
507 | | // L* = Decode[0] + Lsample x (Decode[1] - Decode[0]) / (2^n -1) |
508 | | // … |
509 | | // |
510 | | // So we'll have a larger depth in the output and either worry about reducing fractions |
511 | | // or turn everything into floats. That's a lot of decisions. |
512 | 0 | PhotometricInterpretation::ItuLab => Err(TiffError::UnsupportedError( |
513 | 0 | TiffUnsupportedError::InterpretationWithBits( |
514 | 0 | PhotometricInterpretation::CIELab, |
515 | 0 | vec![self.bits_per_sample; self.samples as usize], |
516 | 0 | ), |
517 | 0 | )), |
518 | | PhotometricInterpretation::RGBPalette | PhotometricInterpretation::TransparencyMask => { |
519 | 0 | Err(TiffError::UnsupportedError( |
520 | 0 | TiffUnsupportedError::InterpretationWithBits( |
521 | 0 | self.photometric_interpretation, |
522 | 0 | vec![self.bits_per_sample; self.samples as usize], |
523 | 0 | ), |
524 | 0 | )) |
525 | | } |
526 | | } |
527 | 0 | } |
528 | | |
529 | 0 | fn create_reader<'r, R: 'r + Read + Seek>( |
530 | 0 | reader: R, |
531 | 0 | compression_method: CompressionMethod, |
532 | 0 | compressed_length: u64, |
533 | 0 | // FIXME: these should be `expect` attributes or we choose another way of passing them. |
534 | 0 | #[cfg_attr(not(feature = "jpeg"), allow(unused_variables))] jpeg_tables: Option<&[u8]>, |
535 | 0 | #[cfg_attr(not(feature = "fax"), allow(unused_variables))] dimensions: (u32, u32), |
536 | 0 | #[cfg_attr(not(feature = "webp"), allow(unused_variables))] samples: u16, |
537 | 0 | ) -> TiffResult<Box<dyn Read + 'r>> { |
538 | 0 | Ok(match compression_method { |
539 | 0 | CompressionMethod::None => Box::new(reader), |
540 | | #[cfg(feature = "lzw")] |
541 | 0 | CompressionMethod::LZW => Box::new(super::stream::LZWReader::new( |
542 | 0 | reader, |
543 | 0 | usize::try_from(compressed_length)?, |
544 | | )), |
545 | | #[cfg(feature = "zstd")] |
546 | | CompressionMethod::ZSTD => Box::new(zstd::Decoder::new(reader)?), |
547 | 0 | CompressionMethod::PackBits => Box::new(PackBitsReader::new(reader, compressed_length)), |
548 | | #[cfg(feature = "deflate")] |
549 | | CompressionMethod::Deflate | CompressionMethod::OldDeflate => { |
550 | 0 | Box::new(super::stream::DeflateReader::new(reader)) |
551 | | } |
552 | | #[cfg(feature = "jpeg")] |
553 | | CompressionMethod::ModernJPEG => { |
554 | | use zune_jpeg::zune_core; |
555 | | |
556 | 0 | if jpeg_tables.is_some() && compressed_length < 2 { |
557 | 0 | return Err(TiffError::FormatError( |
558 | 0 | TiffFormatError::InvalidTagValueType(Tag::JPEGTables), |
559 | 0 | )); |
560 | 0 | } |
561 | | |
562 | | // Construct new jpeg_reader wrapping a SmartReader. |
563 | | // |
564 | | // JPEG compression in TIFF allows saving quantization and/or huffman tables in one |
565 | | // central location. These `jpeg_tables` are simply prepended to the remaining jpeg image data. |
566 | | // Because these `jpeg_tables` start with a `SOI` (HEX: `0xFFD8`) or __start of image__ marker |
567 | | // which is also at the beginning of the remaining JPEG image data and would |
568 | | // confuse the JPEG renderer, one of these has to be taken off. In this case the first two |
569 | | // bytes of the remaining JPEG data is removed because it follows `jpeg_tables`. |
570 | | // Similary, `jpeg_tables` ends with a `EOI` (HEX: `0xFFD9`) or __end of image__ marker, |
571 | | // this has to be removed as well (last two bytes of `jpeg_tables`). |
572 | 0 | let mut jpeg_reader = match jpeg_tables { |
573 | 0 | Some(jpeg_tables) => { |
574 | 0 | let mut reader = reader.take(compressed_length); |
575 | 0 | reader.read_exact(&mut [0; 2])?; |
576 | | |
577 | 0 | Box::new( |
578 | 0 | Cursor::new(&jpeg_tables[..jpeg_tables.len() - 2]) |
579 | 0 | .chain(reader.take(compressed_length)), |
580 | 0 | ) as Box<dyn Read> |
581 | | } |
582 | 0 | None => Box::new(reader.take(compressed_length)), |
583 | | }; |
584 | | |
585 | 0 | let mut jpeg_data = Vec::new(); |
586 | 0 | jpeg_reader.read_to_end(&mut jpeg_data)?; |
587 | | |
588 | 0 | let mut decoder = |
589 | 0 | zune_jpeg::JpegDecoder::new(zune_core::bytestream::ZCursor::new(jpeg_data)); |
590 | 0 | let mut options: zune_core::options::DecoderOptions = Default::default(); |
591 | | |
592 | | // Disable color conversion by setting the output colorspace to the input |
593 | | // colorspace. |
594 | 0 | decoder.decode_headers()?; |
595 | 0 | if let Some(colorspace) = decoder.input_colorspace() { |
596 | 0 | options = options.jpeg_set_out_colorspace(colorspace); |
597 | 0 | } |
598 | | |
599 | 0 | decoder.set_options(options); |
600 | | |
601 | 0 | let data = decoder.decode()?; |
602 | | |
603 | 0 | Box::new(Cursor::new(data)) |
604 | | } |
605 | | #[cfg(feature = "fax")] |
606 | 0 | CompressionMethod::Fax4 => Box::new(super::stream::Group4Reader::new( |
607 | 0 | dimensions, |
608 | 0 | reader, |
609 | 0 | compressed_length, |
610 | 0 | )?), |
611 | | #[cfg(feature = "webp")] |
612 | | CompressionMethod::WebP => Box::new(super::stream::WebPReader::new( |
613 | | reader, |
614 | | compressed_length, |
615 | | samples, |
616 | | )?), |
617 | | |
618 | 0 | method => { |
619 | 0 | return Err(TiffError::UnsupportedError( |
620 | 0 | TiffUnsupportedError::UnsupportedCompressionMethod(method), |
621 | 0 | )) |
622 | | } |
623 | | }) |
624 | 0 | } Unexecuted instantiation: <tiff::decoder::image::Image>::create_reader::<&mut std::io::cursor::Cursor<&[u8]>> Unexecuted instantiation: <tiff::decoder::image::Image>::create_reader::<_> |
625 | | |
626 | | /// Samples per pixel within chunk. |
627 | | /// |
628 | | /// In planar config, samples are stored in separate strips/chunks, also called bands. |
629 | | /// |
630 | | /// Example with `bits_per_sample = [8, 8, 8]` and `PhotometricInterpretation::RGB`: |
631 | | /// * `PlanarConfiguration::Chunky` -> 3 (RGBRGBRGB...) |
632 | | /// * `PlanarConfiguration::Planar` -> 1 (RRR...) (GGG...) (BBB...) |
633 | 0 | pub(crate) fn samples_per_pixel(&self) -> u16 { |
634 | 0 | match self.planar_config { |
635 | 0 | PlanarConfiguration::Chunky => self.samples, |
636 | 0 | PlanarConfiguration::Planar => 1, |
637 | | } |
638 | 0 | } |
639 | | |
640 | 0 | pub(crate) fn samples_per_out_texel(&self, color: ColorType) -> u16 { |
641 | 0 | match self.planar_config { |
642 | 0 | PlanarConfiguration::Chunky => color.num_samples(), |
643 | 0 | PlanarConfiguration::Planar => 1, |
644 | | } |
645 | 0 | } |
646 | | |
647 | | /// Number of strips per pixel. |
648 | 0 | pub(crate) fn strips_per_pixel(&self) -> u16 { |
649 | 0 | match self.planar_config { |
650 | 0 | PlanarConfiguration::Chunky => 1, |
651 | 0 | PlanarConfiguration::Planar => self.samples, |
652 | | } |
653 | 0 | } |
654 | | |
655 | 0 | pub(crate) fn chunk_file_range(&self, chunk: u32) -> TiffResult<(u64, u64)> { |
656 | 0 | let file_offset = self |
657 | 0 | .chunk_offsets |
658 | 0 | .get(chunk as usize) |
659 | 0 | .ok_or(TiffError::FormatError( |
660 | 0 | TiffFormatError::InconsistentSizesEncountered, |
661 | 0 | ))?; |
662 | | |
663 | 0 | let compressed_bytes = |
664 | 0 | self.chunk_bytes |
665 | 0 | .get(chunk as usize) |
666 | 0 | .ok_or(TiffError::FormatError( |
667 | 0 | TiffFormatError::InconsistentSizesEncountered, |
668 | 0 | ))?; |
669 | | |
670 | 0 | Ok((*file_offset, *compressed_bytes)) |
671 | 0 | } |
672 | | |
673 | 0 | pub(crate) fn chunk_dimensions(&self) -> TiffResult<(u32, u32)> { |
674 | 0 | match self.chunk_type { |
675 | | ChunkType::Strip => { |
676 | 0 | let strip_attrs = self.strip_decoder.as_ref().unwrap(); |
677 | 0 | Ok((self.width, strip_attrs.rows_per_strip)) |
678 | | } |
679 | | ChunkType::Tile => { |
680 | 0 | let tile_attrs = self.tile_attributes.as_ref().unwrap(); |
681 | | Ok(( |
682 | 0 | u32::try_from(tile_attrs.tile_width)?, |
683 | 0 | u32::try_from(tile_attrs.tile_length)?, |
684 | | )) |
685 | | } |
686 | | } |
687 | 0 | } |
688 | | |
689 | 0 | pub(crate) fn readout_for_image(&self) -> TiffResult<ReadoutLayout> { |
690 | 0 | let Image { width, height, .. } = *self; |
691 | 0 | self.readout_for_size(width, height) |
692 | 0 | } |
693 | | |
694 | | /// Get the layout for reading out a tile-aligned portion of the image. |
695 | | /// |
696 | | /// The provided width and height should be less than or equal to the image dimensions. |
697 | 0 | pub(crate) fn readout_for_size(&self, width: u32, height: u32) -> TiffResult<ReadoutLayout> { |
698 | 0 | let color = self.colortype()?; |
699 | | |
700 | 0 | let tiff_samples = self.samples_per_pixel(); |
701 | 0 | let tiff_bits_per_sample = self.bits_per_sample; |
702 | 0 | let data_samples = self.samples_per_out_texel(color); |
703 | 0 | let tiff_chunk_dimensions = self.chunk_dimensions()?; |
704 | 0 | let strips_per_pixel = self.strips_per_pixel(); |
705 | | |
706 | 0 | let data_dimensions = (width, height); |
707 | | |
708 | 0 | let tiff_row_bits = (u64::from(tiff_chunk_dimensions.0) * u64::from(tiff_bits_per_sample)) |
709 | 0 | .checked_mul(u64::from(tiff_samples)) |
710 | 0 | .ok_or(TiffError::LimitsExceeded)?; |
711 | 0 | let tiff_row_bytes: usize = tiff_row_bits.div_ceil(8).try_into()?; |
712 | | |
713 | 0 | let chunk_row_bits = (u64::from(tiff_chunk_dimensions.0) * u64::from(tiff_bits_per_sample)) |
714 | 0 | .checked_mul(u64::from(data_samples)) |
715 | 0 | .ok_or(TiffError::LimitsExceeded)?; |
716 | 0 | let chunk_row_bytes: usize = chunk_row_bits.div_ceil(8).try_into()?; |
717 | | |
718 | 0 | let data_row_bits = (u64::from(data_dimensions.0) * u64::from(tiff_bits_per_sample)) |
719 | 0 | .checked_mul(u64::from(data_samples)) |
720 | 0 | .ok_or(TiffError::LimitsExceeded)?; |
721 | 0 | let data_row_bytes: usize = data_row_bits.div_ceil(8).try_into()?; |
722 | | |
723 | 0 | let chunk_col_stride: usize = data_row_bits |
724 | 0 | .div_ceil(8) |
725 | 0 | .checked_mul(u64::from(tiff_chunk_dimensions.1)) |
726 | 0 | .ok_or(TiffError::LimitsExceeded)? |
727 | 0 | .try_into()?; |
728 | | |
729 | 0 | let plane_stride: usize = data_row_bits |
730 | 0 | .div_ceil(8) |
731 | 0 | .checked_mul(u64::from(data_dimensions.1)) |
732 | 0 | .ok_or(TiffError::LimitsExceeded)? |
733 | 0 | .try_into()?; |
734 | | |
735 | 0 | let minimum_row_stride = data_row_bytes; |
736 | | |
737 | 0 | let chunks_across: u32 = data_dimensions.0.div_ceil(tiff_chunk_dimensions.0); |
738 | 0 | let chunks_per_plane = (self.chunk_offsets.len() as u32) / u32::from(strips_per_pixel); |
739 | | |
740 | | // We would not get an offset in byte units, sorry, no bit interleaving in the output. |
741 | 0 | if chunks_across > 1 && chunk_row_bits % 8 != 0 { |
742 | 0 | return Err(TiffError::UnsupportedError( |
743 | 0 | TiffUnsupportedError::MisalignedTileBoundaries, |
744 | 0 | )); |
745 | 0 | } |
746 | | |
747 | | // Only this color type interprets the tag, which is defined with a default of (2, 2) |
748 | 0 | if matches!(color, ColorType::YCbCr(_)) && self.chroma_subsampling != (1, 1) { |
749 | | // The JPEG library does upsampling for us and defines its buffers correctly |
750 | | // (presumably). All other compression schemes are not supported.. |
751 | | // |
752 | | // NOTE: as explained in <fa225e820b96bef35f01bf4685654beeb4a8df0c> we may be better |
753 | | // off supporting this tag by consistently upsampling, not by adjusting the buffer |
754 | | // size. At least as a default this makes more sense and is much more permissive in |
755 | | // case the compression stream disagrees with the tags (we would not have enough / or |
756 | | // the wrong buffer layout if we only asked for subsampled planes in a planar layout). |
757 | 0 | if !matches!(self.compression_method, CompressionMethod::ModernJPEG) { |
758 | 0 | return Err(TiffError::UnsupportedError( |
759 | 0 | TiffUnsupportedError::ChromaSubsampling, |
760 | 0 | )); |
761 | 0 | } |
762 | 0 | } |
763 | | |
764 | 0 | Ok(ReadoutLayout { |
765 | 0 | planar_config: self.planar_config, |
766 | 0 | color, |
767 | 0 | minimum_row_stride, |
768 | 0 | sample_format: self.sample_format, |
769 | 0 | row_stride: data_row_bytes, |
770 | 0 | chunk_row_stride: chunk_row_bytes, |
771 | 0 | chunk_col_stride, |
772 | 0 | plane_stride, |
773 | 0 | tiff_bits_per_sample, |
774 | 0 | tiff_samples, |
775 | 0 | tiff_chunk_dimensions, |
776 | 0 | tiff_row_bytes, |
777 | 0 | chunks_across, |
778 | 0 | chunks_per_plane, |
779 | 0 | }) |
780 | 0 | } |
781 | | |
782 | 0 | pub(crate) fn chunk_data_dimensions(&self, chunk_index: u32) -> TiffResult<(u32, u32)> { |
783 | 0 | let dims = self.chunk_dimensions()?; |
784 | | |
785 | 0 | match self.chunk_type { |
786 | | ChunkType::Strip => { |
787 | 0 | let rows_per_strip = dims.1; |
788 | 0 | let strips_per_band = self.height.div_ceil(rows_per_strip); |
789 | | |
790 | 0 | let strip_height_without_padding = (chunk_index % strips_per_band) |
791 | 0 | .checked_mul(dims.1) |
792 | 0 | .and_then(|x| self.height.checked_sub(x)) |
793 | 0 | .ok_or(TiffError::UsageError(UsageError::InvalidChunkIndex( |
794 | 0 | chunk_index, |
795 | 0 | )))?; |
796 | | |
797 | | // Ignore potential vertical padding on the bottommost strip |
798 | 0 | let strip_height = dims.1.min(strip_height_without_padding); |
799 | | |
800 | 0 | Ok((dims.0, strip_height)) |
801 | | } |
802 | | ChunkType::Tile => { |
803 | 0 | let tile_attrs = self.tile_attributes.as_ref().unwrap(); |
804 | 0 | let (padding_right, padding_down) = tile_attrs.get_padding(chunk_index as usize); |
805 | | |
806 | 0 | let tile_width = tile_attrs.tile_width - padding_right; |
807 | 0 | let tile_length = tile_attrs.tile_length - padding_down; |
808 | | |
809 | 0 | Ok((u32::try_from(tile_width)?, u32::try_from(tile_length)?)) |
810 | | } |
811 | | } |
812 | 0 | } |
813 | | |
814 | 0 | pub(crate) fn expand_chunk( |
815 | 0 | &self, |
816 | 0 | reader: &mut ValueReader<impl Read + Seek>, |
817 | 0 | buf: &mut [u8], |
818 | 0 | layout: &ReadoutLayout, |
819 | 0 | chunk_index: u32, |
820 | 0 | ) -> TiffResult<()> { |
821 | | let ValueReader { |
822 | 0 | reader, |
823 | | bigtiff: _, |
824 | 0 | limits, |
825 | 0 | } = reader; |
826 | | |
827 | 0 | let byte_order = reader.byte_order; |
828 | | |
829 | | // Validate that the color type is supported. |
830 | 0 | let color_type = layout.color; |
831 | | |
832 | 0 | match color_type { |
833 | 0 | ColorType::RGB(n) |
834 | 0 | | ColorType::RGBA(n) |
835 | 0 | | ColorType::CMYK(n) |
836 | 0 | | ColorType::CMYKA(n) |
837 | 0 | | ColorType::YCbCr(n) |
838 | 0 | | ColorType::Gray(n) |
839 | | | ColorType::Multiband { |
840 | 0 | bit_depth: n, |
841 | | num_samples: _, |
842 | 0 | } if n == 8 || n == 16 || n == 32 || n == 64 => {} |
843 | 0 | ColorType::Gray(n) |
844 | | | ColorType::Multiband { |
845 | 0 | bit_depth: n, |
846 | | num_samples: _, |
847 | 0 | } if n < 8 => match self.predictor { |
848 | 0 | Predictor::None => {} |
849 | | Predictor::Horizontal => { |
850 | 0 | return Err(TiffError::UnsupportedError( |
851 | 0 | TiffUnsupportedError::HorizontalPredictor(color_type), |
852 | 0 | )); |
853 | | } |
854 | | Predictor::FloatingPoint => { |
855 | 0 | return Err(TiffError::UnsupportedError( |
856 | 0 | TiffUnsupportedError::FloatingPointPredictor(color_type), |
857 | 0 | )); |
858 | | } |
859 | | }, |
860 | 0 | type_ => { |
861 | 0 | return Err(TiffError::UnsupportedError( |
862 | 0 | TiffUnsupportedError::UnsupportedColorType(type_), |
863 | 0 | )); |
864 | | } |
865 | | } |
866 | | |
867 | | // Validate that the predictor is supported for the sample type. |
868 | 0 | match (self.predictor, self.sample_format) { |
869 | | ( |
870 | | Predictor::Horizontal, |
871 | | SampleFormat::Int | SampleFormat::Uint | SampleFormat::IEEEFP, |
872 | 0 | ) => {} |
873 | | (Predictor::Horizontal, _) => { |
874 | 0 | return Err(TiffError::UnsupportedError( |
875 | 0 | TiffUnsupportedError::HorizontalPredictor(color_type), |
876 | 0 | )); |
877 | | } |
878 | 0 | (Predictor::FloatingPoint, SampleFormat::IEEEFP) => {} |
879 | | (Predictor::FloatingPoint, _) => { |
880 | 0 | return Err(TiffError::UnsupportedError( |
881 | 0 | TiffUnsupportedError::FloatingPointPredictor(color_type), |
882 | 0 | )); |
883 | | } |
884 | 0 | _ => {} |
885 | | } |
886 | | |
887 | 0 | let compressed_bytes = |
888 | 0 | self.chunk_bytes |
889 | 0 | .get(chunk_index as usize) |
890 | 0 | .ok_or(TiffError::FormatError( |
891 | 0 | TiffFormatError::InconsistentSizesEncountered, |
892 | 0 | ))?; |
893 | | |
894 | 0 | if *compressed_bytes > limits.intermediate_buffer_size as u64 { |
895 | 0 | return Err(TiffError::LimitsExceeded); |
896 | 0 | } |
897 | | |
898 | 0 | let compression_method = self.compression_method; |
899 | 0 | let photometric_interpretation = self.photometric_interpretation; |
900 | 0 | let predictor = self.predictor; |
901 | | |
902 | 0 | let samples = layout.tiff_samples; |
903 | 0 | let data_samples = layout.samples_per_out_texel(); |
904 | | |
905 | | // We have two dimensions: the 2d rectangle of encoded data and the 2d rectangle this |
906 | | // takes up in the output. Each has an associated count of bits per pixel. The first |
907 | | // dimension, i.e. a ''row'', is the number of pixels that are encoded with bit packing |
908 | | // while the second is the byte-padded array of each so encoded slices. |
909 | | // |
910 | | // During decoding we map the relevant bits from one to the other. |
911 | 0 | let chunk_dims = self.chunk_dimensions()?; |
912 | 0 | let data_dims = self.chunk_data_dimensions(chunk_index)?; |
913 | | |
914 | 0 | let chunk_row_bytes: usize = layout.tiff_row_bytes; |
915 | 0 | let data_row_bytes: usize = layout.chunk_row_bytes(data_dims.0)?; |
916 | | |
917 | | // TODO: Should these return errors instead? |
918 | 0 | assert!(layout.minimum_row_stride >= data_row_bytes); |
919 | 0 | assert!(buf.len() >= layout.row_stride * (data_dims.1 as usize - 1) + data_row_bytes); |
920 | | |
921 | 0 | let is_all_bits = samples == data_samples; |
922 | 0 | let is_output_chunk_rows = layout.row_stride == chunk_row_bytes; |
923 | | |
924 | 0 | let mut reader = Self::create_reader( |
925 | 0 | reader.inner(), |
926 | 0 | compression_method, |
927 | 0 | *compressed_bytes, |
928 | 0 | self.jpeg_tables.as_deref().map(|a| &**a), Unexecuted instantiation: <tiff::decoder::image::Image>::expand_chunk::<std::io::cursor::Cursor<&[u8]>>::{closure#0}Unexecuted instantiation: <tiff::decoder::image::Image>::expand_chunk::<_>::{closure#0} |
929 | 0 | chunk_dims, |
930 | 0 | self.samples, |
931 | 0 | )?; |
932 | | |
933 | 0 | if is_output_chunk_rows && is_all_bits { |
934 | | // Here we can read directly into the output buffer itself. |
935 | 0 | let tile = &mut buf[..chunk_row_bytes * data_dims.1 as usize]; |
936 | 0 | reader.read_exact(tile)?; |
937 | | |
938 | 0 | for row in tile.chunks_mut(chunk_row_bytes) { |
939 | 0 | super::fix_endianness_and_predict( |
940 | 0 | row, |
941 | 0 | color_type.bit_depth(), |
942 | 0 | samples, |
943 | 0 | byte_order, |
944 | 0 | predictor, |
945 | 0 | ); |
946 | 0 | } |
947 | | |
948 | 0 | if photometric_interpretation == PhotometricInterpretation::WhiteIsZero { |
949 | 0 | super::invert_colors(tile, color_type, self.sample_format)?; |
950 | 0 | } |
951 | 0 | } else if chunk_row_bytes > data_row_bytes && self.predictor == Predictor::FloatingPoint { |
952 | | // The floating point predictor shuffles the padding bytes into the encoded output, so |
953 | | // this case is handled specially when needed. |
954 | 0 | let mut encoded = vec![0u8; chunk_row_bytes]; |
955 | 0 | for row in buf.chunks_mut(layout.row_stride).take(data_dims.1 as usize) { |
956 | 0 | reader.read_exact(&mut encoded)?; |
957 | | |
958 | 0 | let row = &mut row[..data_row_bytes]; |
959 | 0 | match color_type.bit_depth() { |
960 | 0 | 16 => predict_f16(&mut encoded, row, samples), |
961 | 0 | 32 => predict_f32(&mut encoded, row, samples), |
962 | 0 | 64 => predict_f64(&mut encoded, row, samples), |
963 | 0 | _ => unreachable!(), |
964 | | } |
965 | 0 | if photometric_interpretation == PhotometricInterpretation::WhiteIsZero { |
966 | 0 | super::invert_colors(row, color_type, self.sample_format)?; |
967 | 0 | } |
968 | | } |
969 | 0 | } else if is_all_bits { |
970 | | // We read row-by-row but each row fits in its output buffer. |
971 | 0 | for row in buf.chunks_mut(layout.row_stride).take(data_dims.1 as usize) { |
972 | 0 | let row = &mut row[..data_row_bytes]; |
973 | 0 | let used = data_row_bytes.min(chunk_row_bytes); |
974 | | |
975 | | // Two ways how we get here: we have more bytes in our chunk data than in the image |
976 | | // we are to read. Then we need to skip the rest of the data. Or we have a bigger |
977 | | // row stride than the chunk contains data, then we need to fill only the front. |
978 | 0 | reader.read_exact(&mut row[..used])?; |
979 | | // Skip horizontal padding |
980 | 0 | if chunk_row_bytes > data_row_bytes { |
981 | 0 | let len = u64::try_from(chunk_row_bytes - data_row_bytes)?; |
982 | 0 | io::copy(&mut reader.by_ref().take(len), &mut io::sink())?; |
983 | 0 | } |
984 | | |
985 | 0 | super::fix_endianness_and_predict( |
986 | 0 | row, |
987 | 0 | color_type.bit_depth(), |
988 | 0 | samples, |
989 | 0 | byte_order, |
990 | 0 | predictor, |
991 | | ); |
992 | | |
993 | 0 | if photometric_interpretation == PhotometricInterpretation::WhiteIsZero { |
994 | 0 | super::invert_colors(row, color_type, self.sample_format)?; |
995 | 0 | } |
996 | | } |
997 | | } else { |
998 | | // The encoded data potentially takes up more space than the output data so we must be |
999 | | // prepared to discard some of it. That decision is bit-by-bit. |
1000 | 0 | let bits_per_pixel = u32::from(self.bits_per_sample) * u32::from(self.samples); |
1001 | | // Assumes the photometric samples are always the start.. This is slightly problematic. |
1002 | | // To expand spport we should instead have different methods of transforming the read |
1003 | | // buffer data, not only the `compact_photometric_bytes` method below and then choose |
1004 | | // from the right one with supplied parameters. Then we can also bit-for-bit copy with |
1005 | | // a selection for better performance. |
1006 | 0 | let photometric_bit_end = u32::from(self.bits_per_sample) * data_samples as u32; |
1007 | | |
1008 | 0 | debug_assert!(bits_per_pixel >= photometric_bit_end); |
1009 | | |
1010 | 0 | if bits_per_pixel % 8 != 0 || photometric_bit_end % 8 != 0 { |
1011 | 0 | return Err(TiffError::UnsupportedError( |
1012 | 0 | TiffUnsupportedError::InterpretationWithBits( |
1013 | 0 | self.photometric_interpretation, |
1014 | 0 | vec![self.bits_per_sample; self.samples as usize], |
1015 | 0 | ), |
1016 | 0 | )); |
1017 | 0 | } |
1018 | | |
1019 | 0 | let photo_range = photometric_bit_end / 8..bits_per_pixel / 8; |
1020 | 0 | let mut encoded = vec![0u8; chunk_row_bytes]; |
1021 | 0 | for row in buf.chunks_mut(layout.row_stride).take(data_dims.1 as usize) { |
1022 | 0 | reader.read_exact(&mut encoded)?; |
1023 | | |
1024 | 0 | Self::compact_photometric_bytes(&mut encoded, row, &photo_range); |
1025 | | |
1026 | 0 | super::fix_endianness_and_predict( |
1027 | 0 | row, |
1028 | 0 | color_type.bit_depth(), |
1029 | 0 | samples, |
1030 | 0 | byte_order, |
1031 | 0 | predictor, |
1032 | | ); |
1033 | | |
1034 | 0 | if photometric_interpretation == PhotometricInterpretation::WhiteIsZero { |
1035 | 0 | super::invert_colors(row, color_type, self.sample_format)?; |
1036 | 0 | } |
1037 | | } |
1038 | | } |
1039 | | |
1040 | 0 | Ok(()) |
1041 | 0 | } Unexecuted instantiation: <tiff::decoder::image::Image>::expand_chunk::<std::io::cursor::Cursor<&[u8]>> Unexecuted instantiation: <tiff::decoder::image::Image>::expand_chunk::<_> |
1042 | | |
1043 | | /// Turn a contiguous buffer of a whole number of raw sample arrays into a whole number of |
1044 | | /// photometric sample arrays by removing the extra samples in-between. |
1045 | 0 | fn compact_photometric_bytes( |
1046 | 0 | raw: &mut [u8], |
1047 | 0 | row: &mut [u8], |
1048 | 0 | photo_range: &std::ops::Range<u32>, |
1049 | 0 | ) { |
1050 | 0 | raw.chunks_exact_mut(photo_range.end as usize) |
1051 | 0 | .zip(row.chunks_exact_mut(photo_range.start as usize)) |
1052 | 0 | .for_each(|(src, dst)| { |
1053 | 0 | dst.copy_from_slice(&src[..photo_range.start as usize]); |
1054 | 0 | }); |
1055 | 0 | } |
1056 | | } |
1057 | | |
1058 | | impl ReadoutLayout { |
1059 | 0 | pub(crate) fn samples_per_out_texel(&self) -> u16 { |
1060 | 0 | match self.planar_config { |
1061 | 0 | PlanarConfiguration::Chunky => self.color.num_samples(), |
1062 | 0 | PlanarConfiguration::Planar => 1, |
1063 | | } |
1064 | 0 | } |
1065 | | |
1066 | | // For a concrete chunk, which may be a partial border chunk, the byte length of one row of its |
1067 | | // pixel data. |
1068 | 0 | pub(crate) fn chunk_row_bytes(&self, width: u32) -> TiffResult<usize> { |
1069 | 0 | let data_samples = self.samples_per_out_texel(); |
1070 | 0 | let data_row_bits = (u64::from(width) * u64::from(self.tiff_bits_per_sample)) |
1071 | 0 | .checked_mul(u64::from(data_samples)) |
1072 | 0 | .ok_or(TiffError::LimitsExceeded)?; |
1073 | 0 | Ok(data_row_bits.div_ceil(8).try_into()?) |
1074 | 0 | } |
1075 | | |
1076 | 0 | pub(crate) fn set_row_stride(&mut self, row_stride: usize) -> Result<(), TiffError> { |
1077 | 0 | if row_stride < self.minimum_row_stride { |
1078 | 0 | return Err(TiffError::UsageError( |
1079 | 0 | UsageError::InsufficientOutputRowStride { |
1080 | 0 | needed: self.minimum_row_stride, |
1081 | 0 | requested: row_stride, |
1082 | 0 | }, |
1083 | 0 | )); |
1084 | 0 | } |
1085 | | |
1086 | 0 | let data_row_bytes = u64::try_from(row_stride)?; |
1087 | | |
1088 | 0 | let chunk_col_stride = data_row_bytes |
1089 | 0 | .checked_mul(u64::from(self.tiff_chunk_dimensions.1)) |
1090 | 0 | .ok_or(TiffError::LimitsExceeded)? |
1091 | 0 | .try_into()?; |
1092 | | |
1093 | 0 | let height = self.plane_stride.checked_div(self.row_stride); |
1094 | | |
1095 | 0 | let plane_stride = height |
1096 | 0 | .and_then(|h| data_row_bytes.checked_mul(h as u64)) |
1097 | | // If height was zero, or the previous stride was zero, there are no bytes in a plane |
1098 | 0 | .unwrap_or(0) |
1099 | 0 | .try_into()?; |
1100 | | |
1101 | 0 | self.row_stride = row_stride; |
1102 | 0 | self.chunk_col_stride = chunk_col_stride; |
1103 | 0 | self.plane_stride = plane_stride; |
1104 | | |
1105 | 0 | Ok(()) |
1106 | 0 | } |
1107 | | |
1108 | | /// Reduce this down to the layout of the output planes. |
1109 | 0 | pub(crate) fn to_plane_layout(&self) -> Result<PlaneLayout, TiffError> { |
1110 | 0 | let num_planes = self.color.num_samples() / self.samples_per_out_texel(); |
1111 | | |
1112 | | // Using the standard range iterator as checked_add on steroids. |
1113 | | // |
1114 | | // Note: for supporting subsampling, adjust as required. |
1115 | 0 | let mut offset = (0..=usize::MAX).step_by(self.plane_stride); |
1116 | | |
1117 | 0 | let plane_offsets = offset.by_ref().take(usize::from(num_planes)).collect(); |
1118 | | |
1119 | | // Get the past-the-end of the last plane. |
1120 | | // |
1121 | | // This also verifies the `take` above was not short. |
1122 | 0 | let Some(total_bytes) = offset.next() else { |
1123 | 0 | return Err(TiffError::LimitsExceeded); |
1124 | | }; |
1125 | | |
1126 | 0 | Ok(PlaneLayout { |
1127 | 0 | plane_offsets, |
1128 | 0 | total_bytes, |
1129 | 0 | readout: self.clone(), |
1130 | 0 | }) |
1131 | 0 | } |
1132 | | } |
1133 | | |
1134 | | /// A `ReadoutLayout` with pre-calculated plane information. |
1135 | | pub(crate) struct PlaneLayout { |
1136 | | /// The underlying readout layout. |
1137 | | pub readout: ReadoutLayout, |
1138 | | /// Buffer offset from one plane of output to the next. |
1139 | | pub plane_offsets: Vec<usize>, |
1140 | | /// Total number of bytes for all planes in given order. |
1141 | | pub total_bytes: usize, |
1142 | | } |
1143 | | |
1144 | | impl PlaneLayout { |
1145 | | /// Return the number of planes to extract into the provided buffer. |
1146 | 0 | pub(crate) fn used_planes(&self, buffer: &[impl Sized]) -> TiffResult<u16> { |
1147 | 0 | self.readout.assert_min_layout(buffer)?; |
1148 | 0 | let buffer_len = core::mem::size_of_val(buffer); |
1149 | | |
1150 | | // Note: with differently sized planes this is dependent on the plane. |
1151 | 0 | let last_plane_start = buffer_len.checked_sub(self.readout.plane_stride); |
1152 | | |
1153 | | // Find how many planes fit into the output buffer. |
1154 | 0 | let used_plane_offsets = self |
1155 | 0 | .plane_offsets |
1156 | 0 | .iter() |
1157 | 0 | .enumerate() |
1158 | | // Find the first plane that would not fit completely at its offset. |
1159 | 0 | .skip_while(|(_, &offset)| last_plane_start >= Some(offset)) Unexecuted instantiation: <tiff::decoder::image::PlaneLayout>::used_planes::<u8>::{closure#0}Unexecuted instantiation: <tiff::decoder::image::PlaneLayout>::used_planes::<_>::{closure#0} |
1160 | 0 | .nth(0) |
1161 | | // If all planes fit, use all of them. |
1162 | 0 | .map_or(self.plane_offsets.len(), |(idx, _)| idx); |
1163 | | |
1164 | 0 | debug_assert!( |
1165 | 0 | used_plane_offsets <= usize::from(u16::MAX), |
1166 | 0 | "Planes limited by number of samples, which is encoded as u16" |
1167 | | ); |
1168 | | |
1169 | 0 | Ok(used_plane_offsets as u16) |
1170 | 0 | } Unexecuted instantiation: <tiff::decoder::image::PlaneLayout>::used_planes::<u8> Unexecuted instantiation: <tiff::decoder::image::PlaneLayout>::used_planes::<_> |
1171 | | } |