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}
685