Coverage Report

Created: 2025-12-14 07:56

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