Coverage Report

Created: 2026-08-14 08:22

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