Coverage Report

Created: 2025-07-01 06:50

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