tiff/decoder/
image.rs

1use super::ifd::Value;
2use super::stream::PackBitsReader;
3use super::tag_reader::TagReader;
4use super::ChunkType;
5use super::{predict_f16, predict_f32, predict_f64, ValueReader};
6use crate::tags::{
7    CompressionMethod, PhotometricInterpretation, PlanarConfiguration, Predictor, SampleFormat, Tag,
8};
9use crate::{
10    ColorType, Directory, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, UsageError,
11};
12
13use std::io::{self, Cursor, Read, Seek};
14use std::num::NonZeroUsize;
15use std::sync::Arc;
16
17#[derive(Debug)]
18pub(crate) struct StripDecodeState {
19    pub rows_per_strip: u32,
20}
21
22#[derive(Debug)]
23/// Computed values useful for tile decoding
24pub(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
32impl TileAttributes {
33    pub fn tiles_across(&self) -> usize {
34        self.image_width.div_ceil(self.tile_width)
35    }
36    pub fn tiles_down(&self) -> usize {
37        self.image_height.div_ceil(self.tile_length)
38    }
39    fn padding_right(&self) -> usize {
40        (self.tile_width - self.image_width % self.tile_width) % self.tile_width
41    }
42    fn padding_down(&self) -> usize {
43        (self.tile_length - self.image_height % self.tile_length) % self.tile_length
44    }
45    pub fn get_padding(&self, tile: usize) -> (usize, usize) {
46        let row = tile / self.tiles_across();
47        let column = tile % self.tiles_across();
48
49        let padding_right = if column == self.tiles_across() - 1 {
50            self.padding_right()
51        } else {
52            0
53        };
54
55        let padding_down = if row == self.tiles_down() - 1 {
56            self.padding_down()
57        } else {
58            0
59        };
60
61        (padding_right, padding_down)
62    }
63}
64
65#[derive(Debug)]
66pub(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
85impl Image {
86    pub fn from_reader<R: Read + Seek>(
87        decoder: &mut ValueReader<R>,
88        ifd: Directory,
89    ) -> TiffResult<Image> {
90        let mut tag_reader = TagReader { decoder, ifd: &ifd };
91
92        let width = tag_reader.require_tag(Tag::ImageWidth)?.into_u32()?;
93        let height = tag_reader.require_tag(Tag::ImageLength)?.into_u32()?;
94        if width == 0 || height == 0 {
95            return Err(TiffError::FormatError(TiffFormatError::InvalidDimensions(
96                width, height,
97            )));
98        }
99
100        let photometric_interpretation = tag_reader
101            .find_tag(Tag::PhotometricInterpretation)?
102            .map(Value::into_u16)
103            .transpose()?
104            .and_then(PhotometricInterpretation::from_u16)
105            .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        let compression_method = match tag_reader.find_tag(Tag::Compression)? {
110            Some(val) => CompressionMethod::from_u16_exhaustive(val.into_u16()?),
111            None => CompressionMethod::None,
112        };
113
114        let jpeg_tables = if compression_method == CompressionMethod::ModernJPEG
115            && ifd.contains(Tag::JPEGTables)
116        {
117            let vec = tag_reader
118                .find_tag(Tag::JPEGTables)?
119                .unwrap()
120                .into_u8_vec()?;
121            if vec.len() < 2 {
122                return Err(TiffError::FormatError(
123                    TiffFormatError::InvalidTagValueType(Tag::JPEGTables),
124                ));
125            }
126
127            Some(Arc::new(vec))
128        } else {
129            None
130        };
131
132        let samples: u16 = tag_reader
133            .find_tag(Tag::SamplesPerPixel)?
134            .map(Value::into_u16)
135            .transpose()?
136            .unwrap_or(1);
137        if samples == 0 {
138            return Err(TiffFormatError::SamplesPerPixelIsZero.into());
139        }
140
141        let sample_format = match tag_reader.find_tag_uint_vec(Tag::SampleFormat)? {
142            Some(vals) => {
143                let sample_format: Vec<_> = vals
144                    .into_iter()
145                    .map(SampleFormat::from_u16_exhaustive)
146                    .collect();
147
148                // TODO: for now, only homogenous formats across samples are supported.
149                if !sample_format.windows(2).all(|s| s[0] == s[1]) {
150                    return Err(TiffUnsupportedError::UnsupportedSampleFormat(sample_format).into());
151                }
152
153                sample_format[0]
154            }
155            None => SampleFormat::Uint,
156        };
157
158        let bits_per_sample: Vec<u8> = tag_reader
159            .find_tag_uint_vec(Tag::BitsPerSample)?
160            .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        if bits_per_sample.len() != samples.into() && bits_per_sample.len() != 1 {
165            return Err(TiffError::FormatError(
166                TiffFormatError::InconsistentSizesEncountered,
167            ));
168        }
169
170        // This library (and libtiff) do not support mixed sample formats and zero bits per sample
171        // doesn't make sense.
172        if bits_per_sample.iter().any(|&b| b != bits_per_sample[0]) || bits_per_sample[0] == 0 {
173            return Err(TiffUnsupportedError::InconsistentBitsPerSample(bits_per_sample).into());
174        }
175
176        let predictor = tag_reader
177            .find_tag(Tag::Predictor)?
178            .map(Value::into_u16)
179            .transpose()?
180            .map(|p| {
181                Predictor::from_u16(p)
182                    .ok_or(TiffError::FormatError(TiffFormatError::UnknownPredictor(p)))
183            })
184            .transpose()?
185            .unwrap_or(Predictor::None);
186
187        let planar_config = tag_reader
188            .find_tag(Tag::PlanarConfiguration)?
189            .map(Value::into_u16)
190            .transpose()?
191            .map(|p| {
192                PlanarConfiguration::from_u16(p).ok_or(TiffError::FormatError(
193                    TiffFormatError::UnknownPlanarConfiguration(p),
194                ))
195            })
196            .transpose()?
197            .unwrap_or(PlanarConfiguration::Chunky);
198
199        let planes = match planar_config {
200            PlanarConfiguration::Chunky => 1,
201            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        match (
210            ifd.contains(Tag::StripByteCounts),
211            ifd.contains(Tag::StripOffsets),
212            ifd.contains(Tag::TileByteCounts),
213            ifd.contains(Tag::TileOffsets),
214        ) {
215            (true, true, false, false) => {
216                chunk_type = ChunkType::Strip;
217
218                chunk_offsets = tag_reader
219                    .find_tag(Tag::StripOffsets)?
220                    .unwrap()
221                    .into_u64_vec()?;
222                chunk_bytes = tag_reader
223                    .find_tag(Tag::StripByteCounts)?
224                    .unwrap()
225                    .into_u64_vec()?;
226                let rows_per_strip = tag_reader
227                    .find_tag(Tag::RowsPerStrip)?
228                    .map(Value::into_u32)
229                    .transpose()?
230                    .unwrap_or(height);
231                strip_decoder = Some(StripDecodeState { rows_per_strip });
232                tile_attributes = None;
233
234                if chunk_offsets.len() != chunk_bytes.len()
235                    || rows_per_strip == 0
236                    || u32::try_from(chunk_offsets.len())?
237                        != (height.saturating_sub(1) / rows_per_strip + 1) * planes as u32
238                {
239                    return Err(TiffError::FormatError(
240                        TiffFormatError::InconsistentSizesEncountered,
241                    ));
242                }
243            }
244            (false, false, true, true) => {
245                chunk_type = ChunkType::Tile;
246
247                let tile_width =
248                    usize::try_from(tag_reader.require_tag(Tag::TileWidth)?.into_u32()?)?;
249                let tile_length =
250                    usize::try_from(tag_reader.require_tag(Tag::TileLength)?.into_u32()?)?;
251
252                if tile_width == 0 {
253                    return Err(TiffFormatError::InvalidTagValueType(Tag::TileWidth).into());
254                } else if tile_length == 0 {
255                    return Err(TiffFormatError::InvalidTagValueType(Tag::TileLength).into());
256                }
257
258                strip_decoder = None;
259                tile_attributes = Some(TileAttributes {
260                    image_width: usize::try_from(width)?,
261                    image_height: usize::try_from(height)?,
262                    tile_width,
263                    tile_length,
264                });
265                chunk_offsets = tag_reader
266                    .find_tag(Tag::TileOffsets)?
267                    .unwrap()
268                    .into_u64_vec()?;
269                chunk_bytes = tag_reader
270                    .find_tag(Tag::TileByteCounts)?
271                    .unwrap()
272                    .into_u64_vec()?;
273
274                let tile = tile_attributes.as_ref().unwrap();
275                if chunk_offsets.len() != chunk_bytes.len()
276                    || chunk_offsets.len()
277                        != tile.tiles_down() * tile.tiles_across() * planes as usize
278                {
279                    return Err(TiffError::FormatError(
280                        TiffFormatError::InconsistentSizesEncountered,
281                    ));
282                }
283            }
284            (_, _, _, _) => {
285                return Err(TiffError::FormatError(
286                    TiffFormatError::StripTileTagConflict,
287                ))
288            }
289        };
290
291        Ok(Image {
292            ifd: Some(ifd),
293            width,
294            height,
295            bits_per_sample: bits_per_sample[0],
296            samples,
297            sample_format,
298            photometric_interpretation,
299            compression_method,
300            jpeg_tables,
301            predictor,
302            chunk_type,
303            planar_config,
304            strip_decoder,
305            tile_attributes,
306            chunk_offsets,
307            chunk_bytes,
308        })
309    }
310
311    pub(crate) fn colortype(&self) -> TiffResult<ColorType> {
312        match self.photometric_interpretation {
313            PhotometricInterpretation::RGB => match self.samples {
314                3 => Ok(ColorType::RGB(self.bits_per_sample)),
315                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                _ => Err(TiffError::UnsupportedError(
322                    TiffUnsupportedError::InterpretationWithBits(
323                        self.photometric_interpretation,
324                        vec![self.bits_per_sample; self.samples as usize],
325                    ),
326                )),
327            },
328            PhotometricInterpretation::CMYK => match self.samples {
329                4 => Ok(ColorType::CMYK(self.bits_per_sample)),
330                5 => Ok(ColorType::CMYKA(self.bits_per_sample)),
331                _ => Err(TiffError::UnsupportedError(
332                    TiffUnsupportedError::InterpretationWithBits(
333                        self.photometric_interpretation,
334                        vec![self.bits_per_sample; self.samples as usize],
335                    ),
336                )),
337            },
338            PhotometricInterpretation::YCbCr => match self.samples {
339                3 => Ok(ColorType::YCbCr(self.bits_per_sample)),
340                _ => Err(TiffError::UnsupportedError(
341                    TiffUnsupportedError::InterpretationWithBits(
342                        self.photometric_interpretation,
343                        vec![self.bits_per_sample; self.samples as usize],
344                    ),
345                )),
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                match self.samples {
352                    1 => Ok(ColorType::Gray(self.bits_per_sample)),
353                    _ => Ok(ColorType::Multiband {
354                        bit_depth: self.bits_per_sample,
355                        num_samples: self.samples,
356                    }),
357                }
358            }
359            // TODO: this is bad we should not fail at this point
360            PhotometricInterpretation::RGBPalette
361            | PhotometricInterpretation::TransparencyMask
362            | PhotometricInterpretation::CIELab => Err(TiffError::UnsupportedError(
363                TiffUnsupportedError::InterpretationWithBits(
364                    self.photometric_interpretation,
365                    vec![self.bits_per_sample; self.samples as usize],
366                ),
367            )),
368        }
369    }
370
371    pub(crate) fn minimum_row_stride(&self, dims: (u32, u32)) -> Option<NonZeroUsize> {
372        let (width, height) = dims;
373
374        let row_stride = u64::from(width)
375            .saturating_mul(self.samples_per_pixel() as u64)
376            .saturating_mul(self.bits_per_sample as u64)
377            .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        (height > 0)
383            .then_some(row_stride as usize)
384            .and_then(NonZeroUsize::new)
385    }
386
387    fn create_reader<'r, R: 'r + Read>(
388        reader: R,
389        compression_method: CompressionMethod,
390        compressed_length: u64,
391        // FIXME: these should be `expect` attributes or we choose another way of passing them.
392        #[cfg_attr(not(feature = "jpeg"), allow(unused_variables))] jpeg_tables: Option<&[u8]>,
393        #[cfg_attr(not(feature = "fax"), allow(unused_variables))] dimensions: (u32, u32),
394    ) -> TiffResult<Box<dyn Read + 'r>> {
395        Ok(match compression_method {
396            CompressionMethod::None => Box::new(reader),
397            #[cfg(feature = "lzw")]
398            CompressionMethod::LZW => Box::new(super::stream::LZWReader::new(
399                reader,
400                usize::try_from(compressed_length)?,
401            )),
402            #[cfg(feature = "zstd")]
403            CompressionMethod::ZSTD => Box::new(zstd::Decoder::new(reader)?),
404            CompressionMethod::PackBits => Box::new(PackBitsReader::new(reader, compressed_length)),
405            #[cfg(feature = "deflate")]
406            CompressionMethod::Deflate | CompressionMethod::OldDeflate => {
407                Box::new(super::stream::DeflateReader::new(reader))
408            }
409            #[cfg(feature = "jpeg")]
410            CompressionMethod::ModernJPEG => {
411                use zune_jpeg::zune_core;
412
413                if jpeg_tables.is_some() && compressed_length < 2 {
414                    return Err(TiffError::FormatError(
415                        TiffFormatError::InvalidTagValueType(Tag::JPEGTables),
416                    ));
417                }
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                let mut jpeg_reader = match jpeg_tables {
430                    Some(jpeg_tables) => {
431                        let mut reader = reader.take(compressed_length);
432                        reader.read_exact(&mut [0; 2])?;
433
434                        Box::new(
435                            Cursor::new(&jpeg_tables[..jpeg_tables.len() - 2])
436                                .chain(reader.take(compressed_length)),
437                        ) as Box<dyn Read>
438                    }
439                    None => Box::new(reader.take(compressed_length)),
440                };
441
442                let mut jpeg_data = Vec::new();
443                jpeg_reader.read_to_end(&mut jpeg_data)?;
444
445                let mut decoder = zune_jpeg::JpegDecoder::new(jpeg_data);
446                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                decoder.decode_headers()?;
451                if let Some(colorspace) = decoder.get_input_colorspace() {
452                    options = options.jpeg_set_out_colorspace(colorspace);
453                }
454
455                decoder.set_options(options);
456
457                let data = decoder.decode()?;
458
459                Box::new(Cursor::new(data))
460            }
461            #[cfg(feature = "fax")]
462            CompressionMethod::Fax4 => Box::new(super::stream::Group4Reader::new(
463                dimensions,
464                reader,
465                compressed_length,
466            )?),
467            method => {
468                return Err(TiffError::UnsupportedError(
469                    TiffUnsupportedError::UnsupportedCompressionMethod(method),
470                ))
471            }
472        })
473    }
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    pub(crate) fn samples_per_pixel(&self) -> usize {
483        match self.planar_config {
484            PlanarConfiguration::Chunky => self.samples.into(),
485            PlanarConfiguration::Planar => 1,
486        }
487    }
488
489    /// Number of strips per pixel.
490    pub(crate) fn strips_per_pixel(&self) -> usize {
491        match self.planar_config {
492            PlanarConfiguration::Chunky => 1,
493            PlanarConfiguration::Planar => self.samples.into(),
494        }
495    }
496
497    pub(crate) fn chunk_file_range(&self, chunk: u32) -> TiffResult<(u64, u64)> {
498        let file_offset = self
499            .chunk_offsets
500            .get(chunk as usize)
501            .ok_or(TiffError::FormatError(
502                TiffFormatError::InconsistentSizesEncountered,
503            ))?;
504
505        let compressed_bytes =
506            self.chunk_bytes
507                .get(chunk as usize)
508                .ok_or(TiffError::FormatError(
509                    TiffFormatError::InconsistentSizesEncountered,
510                ))?;
511
512        Ok((*file_offset, *compressed_bytes))
513    }
514
515    pub(crate) fn chunk_dimensions(&self) -> TiffResult<(u32, u32)> {
516        match self.chunk_type {
517            ChunkType::Strip => {
518                let strip_attrs = self.strip_decoder.as_ref().unwrap();
519                Ok((self.width, strip_attrs.rows_per_strip))
520            }
521            ChunkType::Tile => {
522                let tile_attrs = self.tile_attributes.as_ref().unwrap();
523                Ok((
524                    u32::try_from(tile_attrs.tile_width)?,
525                    u32::try_from(tile_attrs.tile_length)?,
526                ))
527            }
528        }
529    }
530
531    pub(crate) fn chunk_data_dimensions(&self, chunk_index: u32) -> TiffResult<(u32, u32)> {
532        let dims = self.chunk_dimensions()?;
533
534        match self.chunk_type {
535            ChunkType::Strip => {
536                let strip_attrs = self.strip_decoder.as_ref().unwrap();
537                let strips_per_band =
538                    self.height.saturating_sub(1) / strip_attrs.rows_per_strip + 1;
539                let strip_height_without_padding = (chunk_index % strips_per_band)
540                    .checked_mul(dims.1)
541                    .and_then(|x| self.height.checked_sub(x))
542                    .ok_or(TiffError::UsageError(UsageError::InvalidChunkIndex(
543                        chunk_index,
544                    )))?;
545
546                // Ignore potential vertical padding on the bottommost strip
547                let strip_height = dims.1.min(strip_height_without_padding);
548
549                Ok((dims.0, strip_height))
550            }
551            ChunkType::Tile => {
552                let tile_attrs = self.tile_attributes.as_ref().unwrap();
553                let (padding_right, padding_down) = tile_attrs.get_padding(chunk_index as usize);
554
555                let tile_width = tile_attrs.tile_width - padding_right;
556                let tile_length = tile_attrs.tile_length - padding_down;
557
558                Ok((u32::try_from(tile_width)?, u32::try_from(tile_length)?))
559            }
560        }
561    }
562
563    pub(crate) fn expand_chunk(
564        &self,
565        reader: &mut ValueReader<impl Read>,
566        buf: &mut [u8],
567        output_row_stride: usize,
568        chunk_index: u32,
569    ) -> TiffResult<()> {
570        let ValueReader {
571            reader,
572            bigtiff: _,
573            limits,
574        } = reader;
575
576        let byte_order = reader.byte_order;
577        let reader = reader.inner();
578
579        // Validate that the color type is supported.
580        let color_type = self.colortype()?;
581        match color_type {
582            ColorType::RGB(n)
583            | ColorType::RGBA(n)
584            | ColorType::CMYK(n)
585            | ColorType::CMYKA(n)
586            | ColorType::YCbCr(n)
587            | ColorType::Gray(n)
588            | ColorType::Multiband {
589                bit_depth: n,
590                num_samples: _,
591            } if n == 8 || n == 16 || n == 32 || n == 64 => {}
592            ColorType::Gray(n)
593            | ColorType::Multiband {
594                bit_depth: n,
595                num_samples: _,
596            } if n < 8 => match self.predictor {
597                Predictor::None => {}
598                Predictor::Horizontal => {
599                    return Err(TiffError::UnsupportedError(
600                        TiffUnsupportedError::HorizontalPredictor(color_type),
601                    ));
602                }
603                Predictor::FloatingPoint => {
604                    return Err(TiffError::UnsupportedError(
605                        TiffUnsupportedError::FloatingPointPredictor(color_type),
606                    ));
607                }
608            },
609            type_ => {
610                return Err(TiffError::UnsupportedError(
611                    TiffUnsupportedError::UnsupportedColorType(type_),
612                ));
613            }
614        }
615
616        // Validate that the predictor is supported for the sample type.
617        match (self.predictor, self.sample_format) {
618            (
619                Predictor::Horizontal,
620                SampleFormat::Int | SampleFormat::Uint | SampleFormat::IEEEFP,
621            ) => {}
622            (Predictor::Horizontal, _) => {
623                return Err(TiffError::UnsupportedError(
624                    TiffUnsupportedError::HorizontalPredictor(color_type),
625                ));
626            }
627            (Predictor::FloatingPoint, SampleFormat::IEEEFP) => {}
628            (Predictor::FloatingPoint, _) => {
629                return Err(TiffError::UnsupportedError(
630                    TiffUnsupportedError::FloatingPointPredictor(color_type),
631                ));
632            }
633            _ => {}
634        }
635
636        let compressed_bytes =
637            self.chunk_bytes
638                .get(chunk_index as usize)
639                .ok_or(TiffError::FormatError(
640                    TiffFormatError::InconsistentSizesEncountered,
641                ))?;
642        if *compressed_bytes > limits.intermediate_buffer_size as u64 {
643            return Err(TiffError::LimitsExceeded);
644        }
645
646        let compression_method = self.compression_method;
647        let photometric_interpretation = self.photometric_interpretation;
648        let predictor = self.predictor;
649        let samples = self.samples_per_pixel();
650
651        let chunk_dims = self.chunk_dimensions()?;
652        let data_dims = self.chunk_data_dimensions(chunk_index)?;
653
654        let chunk_row_bits = (u64::from(chunk_dims.0) * u64::from(self.bits_per_sample))
655            .checked_mul(samples as u64)
656            .ok_or(TiffError::LimitsExceeded)?;
657        let chunk_row_bytes: usize = chunk_row_bits.div_ceil(8).try_into()?;
658
659        let data_row_bits = (u64::from(data_dims.0) * u64::from(self.bits_per_sample))
660            .checked_mul(samples as u64)
661            .ok_or(TiffError::LimitsExceeded)?;
662        let data_row_bytes: usize = data_row_bits.div_ceil(8).try_into()?;
663
664        // TODO: Should these return errors instead?
665        assert!(output_row_stride >= data_row_bytes);
666        assert!(buf.len() >= output_row_stride * (data_dims.1 as usize - 1) + data_row_bytes);
667
668        let mut reader = Self::create_reader(
669            reader,
670            compression_method,
671            *compressed_bytes,
672            self.jpeg_tables.as_deref().map(|a| &**a),
673            chunk_dims,
674        )?;
675
676        if output_row_stride == chunk_row_bytes {
677            let tile = &mut buf[..chunk_row_bytes * data_dims.1 as usize];
678            reader.read_exact(tile)?;
679
680            for row in tile.chunks_mut(chunk_row_bytes) {
681                super::fix_endianness_and_predict(
682                    row,
683                    color_type.bit_depth(),
684                    samples,
685                    byte_order,
686                    predictor,
687                );
688            }
689            if photometric_interpretation == PhotometricInterpretation::WhiteIsZero {
690                super::invert_colors(tile, color_type, self.sample_format)?;
691            }
692        } 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            let mut encoded = vec![0u8; chunk_row_bytes];
696            for row in buf.chunks_mut(output_row_stride).take(data_dims.1 as usize) {
697                reader.read_exact(&mut encoded)?;
698
699                let row = &mut row[..data_row_bytes];
700                match color_type.bit_depth() {
701                    16 => predict_f16(&mut encoded, row, samples),
702                    32 => predict_f32(&mut encoded, row, samples),
703                    64 => predict_f64(&mut encoded, row, samples),
704                    _ => unreachable!(),
705                }
706                if photometric_interpretation == PhotometricInterpretation::WhiteIsZero {
707                    super::invert_colors(row, color_type, self.sample_format)?;
708                }
709            }
710        } else {
711            for row in buf.chunks_mut(output_row_stride).take(data_dims.1 as usize) {
712                let row = &mut row[..data_row_bytes];
713                reader.read_exact(row)?;
714
715                // Skip horizontal padding
716                if chunk_row_bytes > data_row_bytes {
717                    let len = u64::try_from(chunk_row_bytes - data_row_bytes)?;
718                    io::copy(&mut reader.by_ref().take(len), &mut io::sink())?;
719                }
720
721                super::fix_endianness_and_predict(
722                    row,
723                    color_type.bit_depth(),
724                    samples,
725                    byte_order,
726                    predictor,
727                );
728                if photometric_interpretation == PhotometricInterpretation::WhiteIsZero {
729                    super::invert_colors(row, color_type, self.sample_format)?;
730                }
731            }
732        }
733
734        Ok(())
735    }
736}