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