tiff/decoder/
image.rs

1use super::ifd::{Directory, Value};
2use super::stream::{ByteOrder, DeflateReader, LZWReader, PackBitsReader};
3use super::tag_reader::TagReader;
4use super::{fp_predict_f32, fp_predict_f64, DecodingBuffer, Limits};
5use super::{stream::SmartReader, ChunkType};
6use crate::tags::{
7    CompressionMethod, PhotometricInterpretation, PlanarConfiguration, Predictor, SampleFormat, Tag,
8};
9use crate::{ColorType, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, UsageError};
10use std::convert::TryFrom;
11use std::io::{self, Cursor, Read, Seek};
12use std::sync::Arc;
13
14#[derive(Debug)]
15pub(crate) struct StripDecodeState {
16    pub rows_per_strip: u32,
17}
18
19#[derive(Debug)]
20/// Computed values useful for tile decoding
21pub(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
29impl 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)]
63pub(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
83impl 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}