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