/rust/registry/src/index.crates.io-6f17d22bba15001f/png-0.17.16/src/decoder/stream.rs
Line | Count | Source (jump to first uncovered line) |
1 | | use std::convert::TryInto; |
2 | | use std::error; |
3 | | use std::fmt; |
4 | | use std::io; |
5 | | use std::{borrow::Cow, cmp::min}; |
6 | | |
7 | | use crc32fast::Hasher as Crc32; |
8 | | |
9 | | use super::zlib::ZlibStream; |
10 | | use crate::chunk::{self, ChunkType, IDAT, IEND, IHDR}; |
11 | | use crate::common::{ |
12 | | AnimationControl, BitDepth, BlendOp, ColorType, ContentLightLevelInfo, DisposeOp, FrameControl, |
13 | | Info, MasteringDisplayColorVolume, ParameterError, ParameterErrorKind, PixelDimensions, |
14 | | ScaledFloat, SourceChromaticities, Unit, |
15 | | }; |
16 | | use crate::text_metadata::{ITXtChunk, TEXtChunk, TextDecodingError, ZTXtChunk}; |
17 | | use crate::traits::ReadBytesExt; |
18 | | use crate::{CodingIndependentCodePoints, Limits}; |
19 | | |
20 | | /// TODO check if these size are reasonable |
21 | | pub const CHUNK_BUFFER_SIZE: usize = 32 * 1024; |
22 | | |
23 | | /// Determines if checksum checks should be disabled globally. |
24 | | /// |
25 | | /// This is used only in fuzzing. `afl` automatically adds `--cfg fuzzing` to RUSTFLAGS which can |
26 | | /// be used to detect that build. |
27 | | const CHECKSUM_DISABLED: bool = cfg!(fuzzing); |
28 | | |
29 | | /// Kind of `u32` value that is being read via `State::U32`. |
30 | | #[derive(Debug)] |
31 | | enum U32ValueKind { |
32 | | /// First 4 bytes of the PNG signature - see |
33 | | /// http://www.libpng.org/pub/png/spec/1.2/PNG-Structure.html#PNG-file-signature |
34 | | Signature1stU32, |
35 | | /// Second 4 bytes of the PNG signature - see |
36 | | /// http://www.libpng.org/pub/png/spec/1.2/PNG-Structure.html#PNG-file-signature |
37 | | Signature2ndU32, |
38 | | /// Chunk length - see |
39 | | /// http://www.libpng.org/pub/png/spec/1.2/PNG-Structure.html#Chunk-layout |
40 | | Length, |
41 | | /// Chunk type - see |
42 | | /// http://www.libpng.org/pub/png/spec/1.2/PNG-Structure.html#Chunk-layout |
43 | | Type { length: u32 }, |
44 | | /// Chunk checksum - see |
45 | | /// http://www.libpng.org/pub/png/spec/1.2/PNG-Structure.html#Chunk-layout |
46 | | Crc(ChunkType), |
47 | | /// Sequence number from an `fdAT` chunk - see |
48 | | /// https://wiki.mozilla.org/APNG_Specification#.60fdAT.60:_The_Frame_Data_Chunk |
49 | | ApngSequenceNumber, |
50 | | } |
51 | | |
52 | | #[derive(Debug)] |
53 | | enum State { |
54 | | /// In this state we are reading a u32 value from external input. We start with |
55 | | /// `accumulated_count` set to `0`. After reading or accumulating the required 4 bytes we will |
56 | | /// call `parse_32` which will then move onto the next state. |
57 | | U32 { |
58 | | kind: U32ValueKind, |
59 | | bytes: [u8; 4], |
60 | | accumulated_count: usize, |
61 | | }, |
62 | | /// In this state we are reading chunk data from external input, and appending it to |
63 | | /// `ChunkState::raw_bytes`. |
64 | | ReadChunkData(ChunkType), |
65 | | /// In this state we check if all chunk data has been already read into `ChunkState::raw_bytes` |
66 | | /// and if so then we parse the chunk. Otherwise, we go back to the `ReadChunkData` state. |
67 | | ParseChunkData(ChunkType), |
68 | | /// In this state we are reading image data from external input and feeding it directly into |
69 | | /// `StreamingDecoder::inflater`. |
70 | | ImageData(ChunkType), |
71 | | } |
72 | | |
73 | | impl State { |
74 | 820k | fn new_u32(kind: U32ValueKind) -> Self { |
75 | 820k | Self::U32 { |
76 | 820k | kind, |
77 | 820k | bytes: [0; 4], |
78 | 820k | accumulated_count: 0, |
79 | 820k | } |
80 | 820k | } |
81 | | } |
82 | | |
83 | | #[derive(Debug)] |
84 | | /// Result of the decoding process |
85 | | pub enum Decoded { |
86 | | /// Nothing decoded yet |
87 | | Nothing, |
88 | | Header(u32, u32, BitDepth, ColorType, bool), |
89 | | ChunkBegin(u32, ChunkType), |
90 | | ChunkComplete(u32, ChunkType), |
91 | | PixelDimensions(PixelDimensions), |
92 | | AnimationControl(AnimationControl), |
93 | | FrameControl(FrameControl), |
94 | | /// Decoded raw image data. |
95 | | ImageData, |
96 | | /// The last of a consecutive chunk of IDAT was done. |
97 | | /// This is distinct from ChunkComplete which only marks that some IDAT chunk was completed but |
98 | | /// not that no additional IDAT chunk follows. |
99 | | ImageDataFlushed, |
100 | | PartialChunk(ChunkType), |
101 | | ImageEnd, |
102 | | } |
103 | | |
104 | | /// Any kind of error during PNG decoding. |
105 | | /// |
106 | | /// This enumeration provides a very rough analysis on the origin of the failure. That is, each |
107 | | /// variant corresponds to one kind of actor causing the error. It should not be understood as a |
108 | | /// direct blame but can inform the search for a root cause or if such a search is required. |
109 | | #[derive(Debug)] |
110 | | pub enum DecodingError { |
111 | | /// An error in IO of the underlying reader. |
112 | | /// |
113 | | /// Note that some IO errors may be recoverable - decoding may be retried after the |
114 | | /// error is resolved. For example, decoding from a slow stream of data (e.g. decoding from a |
115 | | /// network stream) may occasionally result in [std::io::ErrorKind::UnexpectedEof] kind of |
116 | | /// error, but decoding can resume when more data becomes available. |
117 | | IoError(io::Error), |
118 | | /// The input image was not a valid PNG. |
119 | | /// |
120 | | /// There isn't a lot that can be done here, except if the program itself was responsible for |
121 | | /// creating this image then investigate the generator. This is internally implemented with a |
122 | | /// large Enum. If You are interested in accessing some of the more exact information on the |
123 | | /// variant then we can discuss in an issue. |
124 | | Format(FormatError), |
125 | | /// An interface was used incorrectly. |
126 | | /// |
127 | | /// This is used in cases where it's expected that the programmer might trip up and stability |
128 | | /// could be affected. For example when: |
129 | | /// |
130 | | /// * The decoder is polled for more animation frames despite being done (or not being animated |
131 | | /// in the first place). |
132 | | /// * The output buffer does not have the required size. |
133 | | /// |
134 | | /// As a rough guideline for introducing new variants parts of the requirements are dynamically |
135 | | /// derived from the (untrusted) input data while the other half is from the caller. In the |
136 | | /// above cases the number of frames respectively the size is determined by the file while the |
137 | | /// number of calls |
138 | | /// |
139 | | /// If you're an application you might want to signal that a bug report is appreciated. |
140 | | Parameter(ParameterError), |
141 | | /// The image would have required exceeding the limits configured with the decoder. |
142 | | /// |
143 | | /// Note that Your allocations, e.g. when reading into a pre-allocated buffer, is __NOT__ |
144 | | /// considered part of the limits. Nevertheless, required intermediate buffers such as for |
145 | | /// singular lines is checked against the limit. |
146 | | /// |
147 | | /// Note that this is a best-effort basis. |
148 | | LimitsExceeded, |
149 | | } |
150 | | |
151 | | #[derive(Debug)] |
152 | | pub struct FormatError { |
153 | | inner: FormatErrorInner, |
154 | | } |
155 | | |
156 | | #[derive(Debug)] |
157 | | pub(crate) enum FormatErrorInner { |
158 | | /// Bad framing. |
159 | | CrcMismatch { |
160 | | /// Stored CRC32 value |
161 | | crc_val: u32, |
162 | | /// Calculated CRC32 sum |
163 | | crc_sum: u32, |
164 | | /// The chunk type that has the CRC mismatch. |
165 | | chunk: ChunkType, |
166 | | }, |
167 | | /// Not a PNG, the magic signature is missing. |
168 | | InvalidSignature, |
169 | | // Errors of chunk level ordering, missing etc. |
170 | | /// Fctl must occur if an animated chunk occurs. |
171 | | MissingFctl, |
172 | | /// Image data that was indicated in IHDR or acTL is missing. |
173 | | MissingImageData, |
174 | | /// 4.3., Must be first. |
175 | | ChunkBeforeIhdr { |
176 | | kind: ChunkType, |
177 | | }, |
178 | | /// 4.3., some chunks must be before IDAT. |
179 | | AfterIdat { |
180 | | kind: ChunkType, |
181 | | }, |
182 | | // 4.3., Some chunks must be after PLTE. |
183 | | BeforePlte { |
184 | | kind: ChunkType, |
185 | | }, |
186 | | /// 4.3., some chunks must be before PLTE. |
187 | | AfterPlte { |
188 | | kind: ChunkType, |
189 | | }, |
190 | | /// 4.3., some chunks must be between PLTE and IDAT. |
191 | | OutsidePlteIdat { |
192 | | kind: ChunkType, |
193 | | }, |
194 | | /// 4.3., some chunks must be unique. |
195 | | DuplicateChunk { |
196 | | kind: ChunkType, |
197 | | }, |
198 | | /// Specifically for fdat there is an embedded sequence number for chunks. |
199 | | ApngOrder { |
200 | | /// The sequence number in the chunk. |
201 | | present: u32, |
202 | | /// The one that should have been present. |
203 | | expected: u32, |
204 | | }, |
205 | | // Errors specific to particular chunk data to be validated. |
206 | | /// The palette did not even contain a single pixel data. |
207 | | ShortPalette { |
208 | | expected: usize, |
209 | | len: usize, |
210 | | }, |
211 | | /// sBIT chunk size based on color type. |
212 | | InvalidSbitChunkSize { |
213 | | color_type: ColorType, |
214 | | expected: usize, |
215 | | len: usize, |
216 | | }, |
217 | | InvalidSbit { |
218 | | sample_depth: BitDepth, |
219 | | sbit: u8, |
220 | | }, |
221 | | /// A palletized image did not have a palette. |
222 | | PaletteRequired, |
223 | | /// The color-depth combination is not valid according to Table 11.1. |
224 | | InvalidColorBitDepth { |
225 | | color_type: ColorType, |
226 | | bit_depth: BitDepth, |
227 | | }, |
228 | | ColorWithBadTrns(ColorType), |
229 | | /// The image width or height is zero. |
230 | | InvalidDimensions, |
231 | | InvalidBitDepth(u8), |
232 | | InvalidColorType(u8), |
233 | | InvalidDisposeOp(u8), |
234 | | InvalidBlendOp(u8), |
235 | | InvalidUnit(u8), |
236 | | /// The rendering intent of the sRGB chunk is invalid. |
237 | | InvalidSrgbRenderingIntent(u8), |
238 | | UnknownCompressionMethod(u8), |
239 | | UnknownFilterMethod(u8), |
240 | | UnknownInterlaceMethod(u8), |
241 | | /// The subframe is not in bounds of the image. |
242 | | /// TODO: fields with relevant data. |
243 | | BadSubFrameBounds {}, |
244 | | // Errors specific to the IDAT/fdAT chunks. |
245 | | /// The compression of the data stream was faulty. |
246 | | CorruptFlateStream { |
247 | | err: fdeflate::DecompressionError, |
248 | | }, |
249 | | /// The image data chunk was too short for the expected pixel count. |
250 | | NoMoreImageData, |
251 | | /// Bad text encoding |
252 | | BadTextEncoding(TextDecodingError), |
253 | | /// fdAT shorter than 4 bytes |
254 | | FdatShorterThanFourBytes, |
255 | | /// "11.2.4 IDAT Image data" section of the PNG spec says: There may be multiple IDAT chunks; |
256 | | /// if so, they shall appear consecutively with no other intervening chunks. |
257 | | /// `UnexpectedRestartOfDataChunkSequence{kind: IDAT}` indicates that there were "intervening |
258 | | /// chunks". |
259 | | /// |
260 | | /// The APNG spec doesn't directly describe an error similar to `CantInterleaveIdatChunks`, |
261 | | /// but we require that a new sequence of consecutive `fdAT` chunks cannot appear unless we've |
262 | | /// seen an `fcTL` chunk. |
263 | | UnexpectedRestartOfDataChunkSequence { |
264 | | kind: ChunkType, |
265 | | }, |
266 | | /// Failure to parse a chunk, because the chunk didn't contain enough bytes. |
267 | | ChunkTooShort { |
268 | | kind: ChunkType, |
269 | | }, |
270 | | } |
271 | | |
272 | | impl error::Error for DecodingError { |
273 | 0 | fn cause(&self) -> Option<&(dyn error::Error + 'static)> { |
274 | 0 | match self { |
275 | 0 | DecodingError::IoError(err) => Some(err), |
276 | 0 | _ => None, |
277 | | } |
278 | 0 | } |
279 | | } |
280 | | |
281 | | impl fmt::Display for DecodingError { |
282 | 0 | fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { |
283 | | use self::DecodingError::*; |
284 | 0 | match self { |
285 | 0 | IoError(err) => write!(fmt, "{}", err), |
286 | 0 | Parameter(desc) => write!(fmt, "{}", &desc), |
287 | 0 | Format(desc) => write!(fmt, "{}", desc), |
288 | 0 | LimitsExceeded => write!(fmt, "limits are exceeded"), |
289 | | } |
290 | 0 | } |
291 | | } |
292 | | |
293 | | impl fmt::Display for FormatError { |
294 | 0 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { |
295 | | use FormatErrorInner::*; |
296 | 0 | match &self.inner { |
297 | | CrcMismatch { |
298 | 0 | crc_val, |
299 | 0 | crc_sum, |
300 | 0 | chunk, |
301 | 0 | .. |
302 | 0 | } => write!( |
303 | 0 | fmt, |
304 | 0 | "CRC error: expected 0x{:x} have 0x{:x} while decoding {:?} chunk.", |
305 | 0 | crc_val, crc_sum, chunk |
306 | 0 | ), |
307 | 0 | MissingFctl => write!(fmt, "fcTL chunk missing before fdAT chunk."), |
308 | 0 | MissingImageData => write!(fmt, "IDAT or fdAT chunk is missing."), |
309 | 0 | ChunkBeforeIhdr { kind } => write!(fmt, "{:?} chunk appeared before IHDR chunk", kind), |
310 | 0 | AfterIdat { kind } => write!(fmt, "Chunk {:?} is invalid after IDAT chunk.", kind), |
311 | 0 | BeforePlte { kind } => write!(fmt, "Chunk {:?} is invalid before PLTE chunk.", kind), |
312 | 0 | AfterPlte { kind } => write!(fmt, "Chunk {:?} is invalid after PLTE chunk.", kind), |
313 | 0 | OutsidePlteIdat { kind } => write!( |
314 | 0 | fmt, |
315 | 0 | "Chunk {:?} must appear between PLTE and IDAT chunks.", |
316 | 0 | kind |
317 | 0 | ), |
318 | 0 | DuplicateChunk { kind } => write!(fmt, "Chunk {:?} must appear at most once.", kind), |
319 | 0 | ApngOrder { present, expected } => write!( |
320 | 0 | fmt, |
321 | 0 | "Sequence is not in order, expected #{} got #{}.", |
322 | 0 | expected, present, |
323 | 0 | ), |
324 | 0 | ShortPalette { expected, len } => write!( |
325 | 0 | fmt, |
326 | 0 | "Not enough palette entries, expect {} got {}.", |
327 | 0 | expected, len |
328 | 0 | ), |
329 | 0 | InvalidSbitChunkSize {color_type, expected, len} => write!( |
330 | 0 | fmt, |
331 | 0 | "The size of the sBIT chunk should be {} byte(s), but {} byte(s) were provided for the {:?} color type.", |
332 | 0 | expected, len, color_type |
333 | 0 | ), |
334 | 0 | InvalidSbit {sample_depth, sbit} => write!( |
335 | 0 | fmt, |
336 | 0 | "Invalid sBIT value {}. It must be greater than zero and less than the sample depth {:?}.", |
337 | 0 | sbit, sample_depth |
338 | 0 | ), |
339 | 0 | PaletteRequired => write!(fmt, "Missing palette of indexed image."), |
340 | 0 | InvalidDimensions => write!(fmt, "Invalid image dimensions"), |
341 | | InvalidColorBitDepth { |
342 | 0 | color_type, |
343 | 0 | bit_depth, |
344 | 0 | } => write!( |
345 | 0 | fmt, |
346 | 0 | "Invalid color/depth combination in header: {:?}/{:?}", |
347 | 0 | color_type, bit_depth, |
348 | 0 | ), |
349 | 0 | ColorWithBadTrns(color_type) => write!( |
350 | 0 | fmt, |
351 | 0 | "Transparency chunk found for color type {:?}.", |
352 | 0 | color_type |
353 | 0 | ), |
354 | 0 | InvalidBitDepth(nr) => write!(fmt, "Invalid bit depth {}.", nr), |
355 | 0 | InvalidColorType(nr) => write!(fmt, "Invalid color type {}.", nr), |
356 | 0 | InvalidDisposeOp(nr) => write!(fmt, "Invalid dispose op {}.", nr), |
357 | 0 | InvalidBlendOp(nr) => write!(fmt, "Invalid blend op {}.", nr), |
358 | 0 | InvalidUnit(nr) => write!(fmt, "Invalid physical pixel size unit {}.", nr), |
359 | 0 | InvalidSrgbRenderingIntent(nr) => write!(fmt, "Invalid sRGB rendering intent {}.", nr), |
360 | 0 | UnknownCompressionMethod(nr) => write!(fmt, "Unknown compression method {}.", nr), |
361 | 0 | UnknownFilterMethod(nr) => write!(fmt, "Unknown filter method {}.", nr), |
362 | 0 | UnknownInterlaceMethod(nr) => write!(fmt, "Unknown interlace method {}.", nr), |
363 | 0 | BadSubFrameBounds {} => write!(fmt, "Sub frame is out-of-bounds."), |
364 | 0 | InvalidSignature => write!(fmt, "Invalid PNG signature."), |
365 | 0 | NoMoreImageData => write!( |
366 | 0 | fmt, |
367 | 0 | "IDAT or fDAT chunk does not have enough data for image." |
368 | 0 | ), |
369 | 0 | CorruptFlateStream { err } => { |
370 | 0 | write!(fmt, "Corrupt deflate stream. ")?; |
371 | 0 | write!(fmt, "{:?}", err) |
372 | | } |
373 | | // TODO: Wrap more info in the enum variant |
374 | 0 | BadTextEncoding(tde) => { |
375 | 0 | match tde { |
376 | | TextDecodingError::Unrepresentable => { |
377 | 0 | write!(fmt, "Unrepresentable data in tEXt chunk.") |
378 | | } |
379 | | TextDecodingError::InvalidKeywordSize => { |
380 | 0 | write!(fmt, "Keyword empty or longer than 79 bytes.") |
381 | | } |
382 | | TextDecodingError::MissingNullSeparator => { |
383 | 0 | write!(fmt, "No null separator in tEXt chunk.") |
384 | | } |
385 | | TextDecodingError::InflationError => { |
386 | 0 | write!(fmt, "Invalid compressed text data.") |
387 | | } |
388 | | TextDecodingError::OutOfDecompressionSpace => { |
389 | 0 | write!(fmt, "Out of decompression space. Try with a larger limit.") |
390 | | } |
391 | | TextDecodingError::InvalidCompressionMethod => { |
392 | 0 | write!(fmt, "Using an unrecognized byte as compression method.") |
393 | | } |
394 | | TextDecodingError::InvalidCompressionFlag => { |
395 | 0 | write!(fmt, "Using a flag that is not 0 or 255 as a compression flag for iTXt chunk.") |
396 | | } |
397 | | TextDecodingError::MissingCompressionFlag => { |
398 | 0 | write!(fmt, "No compression flag in the iTXt chunk.") |
399 | | } |
400 | | } |
401 | | } |
402 | 0 | FdatShorterThanFourBytes => write!(fmt, "fdAT chunk shorter than 4 bytes"), |
403 | 0 | UnexpectedRestartOfDataChunkSequence { kind } => { |
404 | 0 | write!(fmt, "Unexpected restart of {:?} chunk sequence", kind) |
405 | | } |
406 | 0 | ChunkTooShort { kind } => { |
407 | 0 | write!(fmt, "Chunk is too short: {:?}", kind) |
408 | | } |
409 | | } |
410 | 0 | } |
411 | | } |
412 | | |
413 | | impl From<io::Error> for DecodingError { |
414 | 95 | fn from(err: io::Error) -> DecodingError { |
415 | 95 | DecodingError::IoError(err) |
416 | 95 | } |
417 | | } |
418 | | |
419 | | impl From<FormatError> for DecodingError { |
420 | 88 | fn from(err: FormatError) -> DecodingError { |
421 | 88 | DecodingError::Format(err) |
422 | 88 | } |
423 | | } |
424 | | |
425 | | impl From<FormatErrorInner> for FormatError { |
426 | 70.3k | fn from(inner: FormatErrorInner) -> Self { |
427 | 70.3k | FormatError { inner } |
428 | 70.3k | } |
429 | | } |
430 | | |
431 | | impl From<DecodingError> for io::Error { |
432 | 0 | fn from(err: DecodingError) -> io::Error { |
433 | 0 | match err { |
434 | 0 | DecodingError::IoError(err) => err, |
435 | 0 | err => io::Error::new(io::ErrorKind::Other, err.to_string()), |
436 | | } |
437 | 0 | } |
438 | | } |
439 | | |
440 | | impl From<TextDecodingError> for DecodingError { |
441 | 3 | fn from(tbe: TextDecodingError) -> Self { |
442 | 3 | DecodingError::Format(FormatError { |
443 | 3 | inner: FormatErrorInner::BadTextEncoding(tbe), |
444 | 3 | }) |
445 | 3 | } |
446 | | } |
447 | | |
448 | | /// Decoder configuration options |
449 | | #[derive(Clone)] |
450 | | pub struct DecodeOptions { |
451 | | ignore_adler32: bool, |
452 | | ignore_crc: bool, |
453 | | ignore_text_chunk: bool, |
454 | | ignore_iccp_chunk: bool, |
455 | | skip_ancillary_crc_failures: bool, |
456 | | } |
457 | | |
458 | | impl Default for DecodeOptions { |
459 | 4.54k | fn default() -> Self { |
460 | 4.54k | Self { |
461 | 4.54k | ignore_adler32: true, |
462 | 4.54k | ignore_crc: false, |
463 | 4.54k | ignore_text_chunk: false, |
464 | 4.54k | ignore_iccp_chunk: false, |
465 | 4.54k | skip_ancillary_crc_failures: true, |
466 | 4.54k | } |
467 | 4.54k | } |
468 | | } |
469 | | |
470 | | impl DecodeOptions { |
471 | | /// When set, the decoder will not compute and verify the Adler-32 checksum. |
472 | | /// |
473 | | /// Defaults to `true`. |
474 | 0 | pub fn set_ignore_adler32(&mut self, ignore_adler32: bool) { |
475 | 0 | self.ignore_adler32 = ignore_adler32; |
476 | 0 | } |
477 | | |
478 | | /// When set, the decoder will not compute and verify the CRC code. |
479 | | /// |
480 | | /// Defaults to `false`. |
481 | 0 | pub fn set_ignore_crc(&mut self, ignore_crc: bool) { |
482 | 0 | self.ignore_crc = ignore_crc; |
483 | 0 | } |
484 | | |
485 | | /// Flag to ignore computing and verifying the Adler-32 checksum and CRC |
486 | | /// code. |
487 | 0 | pub fn set_ignore_checksums(&mut self, ignore_checksums: bool) { |
488 | 0 | self.ignore_adler32 = ignore_checksums; |
489 | 0 | self.ignore_crc = ignore_checksums; |
490 | 0 | } |
491 | | |
492 | | /// Ignore text chunks while decoding. |
493 | | /// |
494 | | /// Defaults to `false`. |
495 | 4.54k | pub fn set_ignore_text_chunk(&mut self, ignore_text_chunk: bool) { |
496 | 4.54k | self.ignore_text_chunk = ignore_text_chunk; |
497 | 4.54k | } |
498 | | |
499 | | /// Ignore ICCP chunks while decoding. |
500 | | /// |
501 | | /// Defaults to `false`. |
502 | 0 | pub fn set_ignore_iccp_chunk(&mut self, ignore_iccp_chunk: bool) { |
503 | 0 | self.ignore_iccp_chunk = ignore_iccp_chunk; |
504 | 0 | } |
505 | | |
506 | | /// Ignore ancillary chunks if CRC fails |
507 | | /// |
508 | | /// Defaults to `true` |
509 | 0 | pub fn set_skip_ancillary_crc_failures(&mut self, skip_ancillary_crc_failures: bool) { |
510 | 0 | self.skip_ancillary_crc_failures = skip_ancillary_crc_failures; |
511 | 0 | } |
512 | | } |
513 | | |
514 | | /// PNG StreamingDecoder (low-level interface) |
515 | | /// |
516 | | /// By default, the decoder does not verify Adler-32 checksum computation. To |
517 | | /// enable checksum verification, set it with [`StreamingDecoder::set_ignore_adler32`] |
518 | | /// before starting decompression. |
519 | | pub struct StreamingDecoder { |
520 | | state: Option<State>, |
521 | | current_chunk: ChunkState, |
522 | | /// The inflater state handling consecutive `IDAT` and `fdAT` chunks. |
523 | | inflater: ZlibStream, |
524 | | /// The complete image info read from all prior chunks. |
525 | | pub(crate) info: Option<Info<'static>>, |
526 | | /// The animation chunk sequence number. |
527 | | current_seq_no: Option<u32>, |
528 | | /// Whether we have already seen a start of an IDAT chunk. (Used to validate chunk ordering - |
529 | | /// some chunk types can only appear before or after an IDAT chunk.) |
530 | | have_idat: bool, |
531 | | /// Whether we are ready for a start of an `IDAT` chunk sequence. Initially `true` and set to |
532 | | /// `false` when the first sequence of consecutive `IDAT` chunks ends. |
533 | | ready_for_idat_chunks: bool, |
534 | | /// Whether we are ready for a start of an `fdAT` chunk sequence. Initially `false`. Set to |
535 | | /// `true` after encountering an `fcTL` chunk. Set to `false` when a sequence of consecutive |
536 | | /// `fdAT` chunks ends. |
537 | | ready_for_fdat_chunks: bool, |
538 | | /// Whether we have already seen an iCCP chunk. Used to prevent parsing of duplicate iCCP chunks. |
539 | | have_iccp: bool, |
540 | | decode_options: DecodeOptions, |
541 | | pub(crate) limits: Limits, |
542 | | } |
543 | | |
544 | | struct ChunkState { |
545 | | /// The type of the current chunk. |
546 | | /// Relevant for `IDAT` and `fdAT` which aggregate consecutive chunks of their own type. |
547 | | type_: ChunkType, |
548 | | |
549 | | /// Partial crc until now. |
550 | | crc: Crc32, |
551 | | |
552 | | /// Remaining bytes to be read. |
553 | | remaining: u32, |
554 | | |
555 | | /// Non-decoded bytes in the chunk. |
556 | | raw_bytes: Vec<u8>, |
557 | | } |
558 | | |
559 | | impl StreamingDecoder { |
560 | | /// Creates a new StreamingDecoder |
561 | | /// |
562 | | /// Allocates the internal buffers. |
563 | 4.54k | pub fn new() -> StreamingDecoder { |
564 | 4.54k | StreamingDecoder::new_with_options(DecodeOptions::default()) |
565 | 4.54k | } |
566 | | |
567 | 4.54k | pub fn new_with_options(decode_options: DecodeOptions) -> StreamingDecoder { |
568 | 4.54k | let mut inflater = ZlibStream::new(); |
569 | 4.54k | inflater.set_ignore_adler32(decode_options.ignore_adler32); |
570 | 4.54k | |
571 | 4.54k | StreamingDecoder { |
572 | 4.54k | state: Some(State::new_u32(U32ValueKind::Signature1stU32)), |
573 | 4.54k | current_chunk: ChunkState::default(), |
574 | 4.54k | inflater, |
575 | 4.54k | info: None, |
576 | 4.54k | current_seq_no: None, |
577 | 4.54k | have_idat: false, |
578 | 4.54k | have_iccp: false, |
579 | 4.54k | ready_for_idat_chunks: true, |
580 | 4.54k | ready_for_fdat_chunks: false, |
581 | 4.54k | decode_options, |
582 | 4.54k | limits: Limits { bytes: usize::MAX }, |
583 | 4.54k | } |
584 | 4.54k | } |
585 | | |
586 | | /// Resets the StreamingDecoder |
587 | 0 | pub fn reset(&mut self) { |
588 | 0 | self.state = Some(State::new_u32(U32ValueKind::Signature1stU32)); |
589 | 0 | self.current_chunk.crc = Crc32::new(); |
590 | 0 | self.current_chunk.remaining = 0; |
591 | 0 | self.current_chunk.raw_bytes.clear(); |
592 | 0 | self.inflater.reset(); |
593 | 0 | self.info = None; |
594 | 0 | self.current_seq_no = None; |
595 | 0 | self.have_idat = false; |
596 | 0 | } |
597 | | |
598 | | /// Provides access to the inner `info` field |
599 | 0 | pub fn info(&self) -> Option<&Info<'static>> { |
600 | 0 | self.info.as_ref() |
601 | 0 | } |
602 | | |
603 | 4.54k | pub fn set_ignore_text_chunk(&mut self, ignore_text_chunk: bool) { |
604 | 4.54k | self.decode_options.set_ignore_text_chunk(ignore_text_chunk); |
605 | 4.54k | } |
606 | | |
607 | 0 | pub fn set_ignore_iccp_chunk(&mut self, ignore_iccp_chunk: bool) { |
608 | 0 | self.decode_options.set_ignore_iccp_chunk(ignore_iccp_chunk); |
609 | 0 | } |
610 | | |
611 | | /// Return whether the decoder is set to ignore the Adler-32 checksum. |
612 | 0 | pub fn ignore_adler32(&self) -> bool { |
613 | 0 | self.inflater.ignore_adler32() |
614 | 0 | } |
615 | | |
616 | | /// Set whether to compute and verify the Adler-32 checksum during |
617 | | /// decompression. Return `true` if the flag was successfully set. |
618 | | /// |
619 | | /// The decoder defaults to `true`. |
620 | | /// |
621 | | /// This flag cannot be modified after decompression has started until the |
622 | | /// [`StreamingDecoder`] is reset. |
623 | 0 | pub fn set_ignore_adler32(&mut self, ignore_adler32: bool) -> bool { |
624 | 0 | self.inflater.set_ignore_adler32(ignore_adler32) |
625 | 0 | } |
626 | | |
627 | | /// Set whether to compute and verify the Adler-32 checksum during |
628 | | /// decompression. |
629 | | /// |
630 | | /// The decoder defaults to `false`. |
631 | 0 | pub fn set_ignore_crc(&mut self, ignore_crc: bool) { |
632 | 0 | self.decode_options.set_ignore_crc(ignore_crc) |
633 | 0 | } |
634 | | |
635 | | /// Ignore ancillary chunks if CRC fails |
636 | | /// |
637 | | /// Defaults to `true` |
638 | 0 | pub fn set_skip_ancillary_crc_failures(&mut self, skip_ancillary_crc_failures: bool) { |
639 | 0 | self.decode_options |
640 | 0 | .set_skip_ancillary_crc_failures(skip_ancillary_crc_failures) |
641 | 0 | } |
642 | | |
643 | | /// Low level StreamingDecoder interface. |
644 | | /// |
645 | | /// Allows to stream partial data to the encoder. Returns a tuple containing the bytes that have |
646 | | /// been consumed from the input buffer and the current decoding result. If the decoded chunk |
647 | | /// was an image data chunk, it also appends the read data to `image_data`. |
648 | 563k | pub fn update( |
649 | 563k | &mut self, |
650 | 563k | mut buf: &[u8], |
651 | 563k | image_data: &mut Vec<u8>, |
652 | 563k | ) -> Result<(usize, Decoded), DecodingError> { |
653 | 563k | if self.state.is_none() { |
654 | 0 | return Err(DecodingError::Parameter( |
655 | 0 | ParameterErrorKind::PolledAfterFatalError.into(), |
656 | 0 | )); |
657 | 563k | } |
658 | 563k | |
659 | 563k | let len = buf.len(); |
660 | 1.11M | while !buf.is_empty() { |
661 | 1.11M | match self.next_state(buf, image_data) { |
662 | 553k | Ok((bytes, Decoded::Nothing)) => buf = &buf[bytes..], |
663 | 559k | Ok((bytes, result)) => { |
664 | 559k | buf = &buf[bytes..]; |
665 | 559k | return Ok((len - buf.len(), result)); |
666 | | } |
667 | 1.00k | Err(err) => { |
668 | 1.00k | debug_assert!(self.state.is_none()); |
669 | 1.00k | return Err(err); |
670 | | } |
671 | | } |
672 | | } |
673 | 2.24k | Ok((len - buf.len(), Decoded::Nothing)) |
674 | 563k | } |
675 | | |
676 | 1.11M | fn next_state( |
677 | 1.11M | &mut self, |
678 | 1.11M | buf: &[u8], |
679 | 1.11M | image_data: &mut Vec<u8>, |
680 | 1.11M | ) -> Result<(usize, Decoded), DecodingError> { |
681 | | use self::State::*; |
682 | | |
683 | | // Driver should ensure that state is never None |
684 | 1.11M | let state = self.state.take().unwrap(); |
685 | 1.11M | |
686 | 1.11M | match state { |
687 | | U32 { |
688 | 820k | kind, |
689 | 820k | mut bytes, |
690 | 820k | mut accumulated_count, |
691 | 820k | } => { |
692 | 820k | debug_assert!(accumulated_count <= 4); |
693 | 820k | if accumulated_count == 0 && buf.len() >= 4 { |
694 | | // Handling these `accumulated_count` and `buf.len()` values in a separate `if` |
695 | | // branch is not strictly necessary - the `else` statement below is already |
696 | | // capable of handling these values. The main reason for special-casing these |
697 | | // values is that they occur fairly frequently and special-casing them results |
698 | | // in performance gains. |
699 | | const CONSUMED_BYTES: usize = 4; |
700 | 818k | self.parse_u32(kind, &buf[0..4], image_data) |
701 | 818k | .map(|decoded| (CONSUMED_BYTES, decoded)) |
702 | | } else { |
703 | 1.23k | let remaining_count = 4 - accumulated_count; |
704 | 1.23k | let consumed_bytes = { |
705 | 1.23k | let available_count = min(remaining_count, buf.len()); |
706 | 1.23k | bytes[accumulated_count..accumulated_count + available_count] |
707 | 1.23k | .copy_from_slice(&buf[0..available_count]); |
708 | 1.23k | accumulated_count += available_count; |
709 | 1.23k | available_count |
710 | 1.23k | }; |
711 | 1.23k | |
712 | 1.23k | if accumulated_count < 4 { |
713 | 1.14k | self.state = Some(U32 { |
714 | 1.14k | kind, |
715 | 1.14k | bytes, |
716 | 1.14k | accumulated_count, |
717 | 1.14k | }); |
718 | 1.14k | Ok((consumed_bytes, Decoded::Nothing)) |
719 | | } else { |
720 | 94 | debug_assert_eq!(accumulated_count, 4); |
721 | 94 | self.parse_u32(kind, &bytes, image_data) |
722 | 94 | .map(|decoded| (consumed_bytes, decoded)) |
723 | | } |
724 | | } |
725 | | } |
726 | 17.5k | ParseChunkData(type_str) => { |
727 | 17.5k | debug_assert!(type_str != IDAT && type_str != chunk::fdAT); |
728 | 17.5k | if self.current_chunk.remaining == 0 { |
729 | | // Got complete chunk. |
730 | 17.2k | Ok((0, self.parse_chunk(type_str)?)) |
731 | | } else { |
732 | | // Make sure we have room to read more of the chunk. |
733 | | // We need it fully before parsing. |
734 | 312 | self.reserve_current_chunk()?; |
735 | | |
736 | 312 | self.state = Some(ReadChunkData(type_str)); |
737 | 312 | Ok((0, Decoded::PartialChunk(type_str))) |
738 | | } |
739 | | } |
740 | 266k | ReadChunkData(type_str) => { |
741 | 266k | debug_assert!(type_str != IDAT && type_str != chunk::fdAT); |
742 | 266k | if self.current_chunk.remaining == 0 { |
743 | 248k | self.state = Some(State::new_u32(U32ValueKind::Crc(type_str))); |
744 | 248k | Ok((0, Decoded::Nothing)) |
745 | | } else { |
746 | | let ChunkState { |
747 | 18.9k | crc, |
748 | 18.9k | remaining, |
749 | 18.9k | raw_bytes, |
750 | 18.9k | type_: _, |
751 | 18.9k | } = &mut self.current_chunk; |
752 | 18.9k | |
753 | 18.9k | let buf_avail = raw_bytes.capacity() - raw_bytes.len(); |
754 | 18.9k | let bytes_avail = min(buf.len(), buf_avail); |
755 | 18.9k | let n = min(*remaining, bytes_avail as u32); |
756 | 18.9k | if buf_avail == 0 { |
757 | 312 | self.state = Some(ParseChunkData(type_str)); |
758 | 312 | Ok((0, Decoded::Nothing)) |
759 | | } else { |
760 | 18.5k | let buf = &buf[..n as usize]; |
761 | 18.5k | if !self.decode_options.ignore_crc { |
762 | 18.5k | crc.update(buf); |
763 | 18.5k | } |
764 | 18.5k | raw_bytes.extend_from_slice(buf); |
765 | 18.5k | |
766 | 18.5k | *remaining -= n; |
767 | 18.5k | if *remaining == 0 { |
768 | 17.2k | self.state = Some(ParseChunkData(type_str)); |
769 | 17.2k | } else { |
770 | 1.37k | self.state = Some(ReadChunkData(type_str)); |
771 | 1.37k | } |
772 | 18.5k | Ok((n as usize, Decoded::Nothing)) |
773 | | } |
774 | | } |
775 | | } |
776 | 9.29k | ImageData(type_str) => { |
777 | 9.29k | debug_assert!(type_str == IDAT || type_str == chunk::fdAT); |
778 | 9.29k | let len = std::cmp::min(buf.len(), self.current_chunk.remaining as usize); |
779 | 9.29k | let buf = &buf[..len]; |
780 | 9.29k | let consumed = self.inflater.decompress(buf, image_data)?; |
781 | 8.80k | self.current_chunk.crc.update(&buf[..consumed]); |
782 | 8.80k | self.current_chunk.remaining -= consumed as u32; |
783 | 8.80k | if self.current_chunk.remaining == 0 { |
784 | 2.99k | self.state = Some(State::new_u32(U32ValueKind::Crc(type_str))); |
785 | 5.81k | } else { |
786 | 5.81k | self.state = Some(ImageData(type_str)); |
787 | 5.81k | } |
788 | 8.80k | Ok((consumed, Decoded::ImageData)) |
789 | | } |
790 | | } |
791 | 1.11M | } |
792 | | |
793 | 818k | fn parse_u32( |
794 | 818k | &mut self, |
795 | 818k | kind: U32ValueKind, |
796 | 818k | u32_be_bytes: &[u8], |
797 | 818k | image_data: &mut Vec<u8>, |
798 | 818k | ) -> Result<Decoded, DecodingError> { |
799 | 818k | debug_assert_eq!(u32_be_bytes.len(), 4); |
800 | 818k | let bytes = u32_be_bytes.try_into().unwrap(); |
801 | 818k | let val = u32::from_be_bytes(bytes); |
802 | 818k | |
803 | 818k | match kind { |
804 | | U32ValueKind::Signature1stU32 => { |
805 | 4.54k | if bytes == [137, 80, 78, 71] { |
806 | 4.54k | self.state = Some(State::new_u32(U32ValueKind::Signature2ndU32)); |
807 | 4.54k | Ok(Decoded::Nothing) |
808 | | } else { |
809 | 0 | Err(DecodingError::Format( |
810 | 0 | FormatErrorInner::InvalidSignature.into(), |
811 | 0 | )) |
812 | | } |
813 | | } |
814 | | U32ValueKind::Signature2ndU32 => { |
815 | 4.54k | if bytes == [13, 10, 26, 10] { |
816 | 4.54k | self.state = Some(State::new_u32(U32ValueKind::Length)); |
817 | 4.54k | Ok(Decoded::Nothing) |
818 | | } else { |
819 | 0 | Err(DecodingError::Format( |
820 | 0 | FormatErrorInner::InvalidSignature.into(), |
821 | 0 | )) |
822 | | } |
823 | | } |
824 | | U32ValueKind::Length => { |
825 | 271k | self.state = Some(State::new_u32(U32ValueKind::Type { length: val })); |
826 | 271k | Ok(Decoded::Nothing) |
827 | | } |
828 | 271k | U32ValueKind::Type { length } => { |
829 | 271k | let type_str = ChunkType(bytes); |
830 | 271k | if self.info.is_none() && type_str != IHDR { |
831 | 62 | return Err(DecodingError::Format( |
832 | 62 | FormatErrorInner::ChunkBeforeIhdr { kind: type_str }.into(), |
833 | 62 | )); |
834 | 271k | } |
835 | 271k | if type_str != self.current_chunk.type_ |
836 | 16.0k | && (self.current_chunk.type_ == IDAT || self.current_chunk.type_ == chunk::fdAT) |
837 | | { |
838 | 234 | self.current_chunk.type_ = type_str; |
839 | 234 | self.inflater.finish_compressed_chunks(image_data)?; |
840 | 50 | self.inflater.reset(); |
841 | 50 | self.ready_for_idat_chunks = false; |
842 | 50 | self.ready_for_fdat_chunks = false; |
843 | 50 | self.state = Some(State::U32 { |
844 | 50 | kind, |
845 | 50 | bytes, |
846 | 50 | accumulated_count: 4, |
847 | 50 | }); |
848 | 50 | return Ok(Decoded::ImageDataFlushed); |
849 | 271k | } |
850 | 271k | self.state = match type_str { |
851 | | chunk::fdAT => { |
852 | 35 | if !self.ready_for_fdat_chunks { |
853 | 1 | return Err(DecodingError::Format( |
854 | 1 | FormatErrorInner::UnexpectedRestartOfDataChunkSequence { |
855 | 1 | kind: chunk::fdAT, |
856 | 1 | } |
857 | 1 | .into(), |
858 | 1 | )); |
859 | 34 | } |
860 | 34 | if length < 4 { |
861 | 2 | return Err(DecodingError::Format( |
862 | 2 | FormatErrorInner::FdatShorterThanFourBytes.into(), |
863 | 2 | )); |
864 | 32 | } |
865 | 32 | Some(State::new_u32(U32ValueKind::ApngSequenceNumber)) |
866 | | } |
867 | | IDAT => { |
868 | 5.15k | if !self.ready_for_idat_chunks { |
869 | 0 | return Err(DecodingError::Format( |
870 | 0 | FormatErrorInner::UnexpectedRestartOfDataChunkSequence { |
871 | 0 | kind: IDAT, |
872 | 0 | } |
873 | 0 | .into(), |
874 | 0 | )); |
875 | 5.15k | } |
876 | 5.15k | self.have_idat = true; |
877 | 5.15k | Some(State::ImageData(type_str)) |
878 | | } |
879 | 265k | _ => Some(State::ReadChunkData(type_str)), |
880 | | }; |
881 | 271k | self.current_chunk.type_ = type_str; |
882 | 271k | if !self.decode_options.ignore_crc { |
883 | 271k | self.current_chunk.crc.reset(); |
884 | 271k | self.current_chunk.crc.update(&type_str.0); |
885 | 271k | } |
886 | 271k | self.current_chunk.remaining = length; |
887 | 271k | self.current_chunk.raw_bytes.clear(); |
888 | 271k | Ok(Decoded::ChunkBegin(length, type_str)) |
889 | | } |
890 | 267k | U32ValueKind::Crc(type_str) => { |
891 | | // If ignore_crc is set, do not calculate CRC. We set |
892 | | // sum=val so that it short-circuits to true in the next |
893 | | // if-statement block |
894 | 267k | let sum = if self.decode_options.ignore_crc { |
895 | 0 | val |
896 | | } else { |
897 | 267k | self.current_chunk.crc.clone().finalize() |
898 | | }; |
899 | | |
900 | 267k | if val == sum || CHECKSUM_DISABLED { |
901 | 267k | if type_str == IEND { |
902 | 1 | debug_assert!(self.state.is_none()); |
903 | 1 | Ok(Decoded::ImageEnd) |
904 | | } else { |
905 | 267k | self.state = Some(State::new_u32(U32ValueKind::Length)); |
906 | 267k | Ok(Decoded::ChunkComplete(val, type_str)) |
907 | | } |
908 | 0 | } else if self.decode_options.skip_ancillary_crc_failures |
909 | 0 | && !chunk::is_critical(type_str) |
910 | | { |
911 | | // Ignore ancillary chunk with invalid CRC |
912 | 0 | self.state = Some(State::new_u32(U32ValueKind::Length)); |
913 | 0 | Ok(Decoded::Nothing) |
914 | | } else { |
915 | 0 | Err(DecodingError::Format( |
916 | 0 | FormatErrorInner::CrcMismatch { |
917 | 0 | crc_val: val, |
918 | 0 | crc_sum: sum, |
919 | 0 | chunk: type_str, |
920 | 0 | } |
921 | 0 | .into(), |
922 | 0 | )) |
923 | | } |
924 | | } |
925 | | U32ValueKind::ApngSequenceNumber => { |
926 | 0 | debug_assert_eq!(self.current_chunk.type_, chunk::fdAT); |
927 | 0 | let next_seq_no = val; |
928 | 0 |
|
929 | 0 | // Should be verified by the FdatShorterThanFourBytes check earlier. |
930 | 0 | debug_assert!(self.current_chunk.remaining >= 4); |
931 | 0 | self.current_chunk.remaining -= 4; |
932 | | |
933 | 0 | if let Some(seq_no) = self.current_seq_no { |
934 | 0 | if next_seq_no != seq_no + 1 { |
935 | 0 | return Err(DecodingError::Format( |
936 | 0 | FormatErrorInner::ApngOrder { |
937 | 0 | present: next_seq_no, |
938 | 0 | expected: seq_no + 1, |
939 | 0 | } |
940 | 0 | .into(), |
941 | 0 | )); |
942 | 0 | } |
943 | 0 | self.current_seq_no = Some(next_seq_no); |
944 | | } else { |
945 | 0 | return Err(DecodingError::Format(FormatErrorInner::MissingFctl.into())); |
946 | | } |
947 | | |
948 | 0 | if !self.decode_options.ignore_crc { |
949 | 0 | let data = next_seq_no.to_be_bytes(); |
950 | 0 | self.current_chunk.crc.update(&data); |
951 | 0 | } |
952 | | |
953 | 0 | self.state = Some(State::ImageData(chunk::fdAT)); |
954 | 0 | Ok(Decoded::PartialChunk(chunk::fdAT)) |
955 | | } |
956 | | } |
957 | 818k | } |
958 | | |
959 | 312 | fn reserve_current_chunk(&mut self) -> Result<(), DecodingError> { |
960 | 312 | let max = self.limits.bytes; |
961 | 312 | let buffer = &mut self.current_chunk.raw_bytes; |
962 | 312 | |
963 | 312 | // Double if necessary, but no more than until the limit is reached. |
964 | 312 | let reserve_size = max.saturating_sub(buffer.capacity()).min(buffer.len()); |
965 | 312 | self.limits.reserve_bytes(reserve_size)?; |
966 | 312 | buffer.reserve_exact(reserve_size); |
967 | 312 | |
968 | 312 | if buffer.capacity() == buffer.len() { |
969 | 0 | Err(DecodingError::LimitsExceeded) |
970 | | } else { |
971 | 312 | Ok(()) |
972 | | } |
973 | 312 | } |
974 | | |
975 | 17.2k | fn parse_chunk(&mut self, type_str: ChunkType) -> Result<Decoded, DecodingError> { |
976 | 17.2k | self.state = Some(State::new_u32(U32ValueKind::Crc(type_str))); |
977 | 17.2k | let parse_result = match type_str { |
978 | 4.31k | IHDR => self.parse_ihdr(), |
979 | 1.65k | chunk::sBIT => self.parse_sbit(), |
980 | 63 | chunk::PLTE => self.parse_plte(), |
981 | 248 | chunk::tRNS => self.parse_trns(), |
982 | 7 | chunk::pHYs => self.parse_phys(), |
983 | 7 | chunk::gAMA => self.parse_gama(), |
984 | 216 | chunk::acTL => self.parse_actl(), |
985 | 216 | chunk::fcTL => self.parse_fctl(), |
986 | 15 | chunk::cHRM => self.parse_chrm(), |
987 | 7 | chunk::sRGB => self.parse_srgb(), |
988 | 87 | chunk::cICP => Ok(self.parse_cicp()), |
989 | 350 | chunk::mDCV => Ok(self.parse_mdcv()), |
990 | 60 | chunk::cLLI => Ok(self.parse_clli()), |
991 | 1.22k | chunk::bKGD => Ok(self.parse_bkgd()), |
992 | 753 | chunk::iCCP if !self.decode_options.ignore_iccp_chunk => self.parse_iccp(), |
993 | 0 | chunk::tEXt if !self.decode_options.ignore_text_chunk => self.parse_text(), |
994 | 0 | chunk::zTXt if !self.decode_options.ignore_text_chunk => self.parse_ztxt(), |
995 | 0 | chunk::iTXt if !self.decode_options.ignore_text_chunk => self.parse_itxt(), |
996 | 7.99k | _ => Ok(Decoded::PartialChunk(type_str)), |
997 | | }; |
998 | | |
999 | 17.2k | parse_result.map_err(|e| { |
1000 | 267 | self.state = None; |
1001 | 87 | match e { |
1002 | | // `parse_chunk` is invoked after gathering **all** bytes of a chunk, so |
1003 | | // `UnexpectedEof` from something like `read_be` is permanent and indicates an |
1004 | | // invalid PNG that should be represented as a `FormatError`, rather than as a |
1005 | | // (potentially recoverable) `IoError` / `UnexpectedEof`. |
1006 | 87 | DecodingError::IoError(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => { |
1007 | 87 | let fmt_err: FormatError = |
1008 | 87 | FormatErrorInner::ChunkTooShort { kind: type_str }.into(); |
1009 | 87 | fmt_err.into() |
1010 | | } |
1011 | 180 | e => e, |
1012 | | } |
1013 | 17.2k | }) |
1014 | 17.2k | } |
1015 | | |
1016 | 216 | fn parse_fctl(&mut self) -> Result<Decoded, DecodingError> { |
1017 | 216 | let mut buf = &self.current_chunk.raw_bytes[..]; |
1018 | 216 | let next_seq_no = buf.read_be()?; |
1019 | | |
1020 | | // Assuming that fcTL is required before *every* fdAT-sequence |
1021 | 214 | self.current_seq_no = Some(if let Some(seq_no) = self.current_seq_no { |
1022 | 38 | if next_seq_no != seq_no + 1 { |
1023 | 22 | return Err(DecodingError::Format( |
1024 | 22 | FormatErrorInner::ApngOrder { |
1025 | 22 | expected: seq_no + 1, |
1026 | 22 | present: next_seq_no, |
1027 | 22 | } |
1028 | 22 | .into(), |
1029 | 22 | )); |
1030 | 16 | } |
1031 | 16 | next_seq_no |
1032 | | } else { |
1033 | 176 | if next_seq_no != 0 { |
1034 | 3 | return Err(DecodingError::Format( |
1035 | 3 | FormatErrorInner::ApngOrder { |
1036 | 3 | expected: 0, |
1037 | 3 | present: next_seq_no, |
1038 | 3 | } |
1039 | 3 | .into(), |
1040 | 3 | )); |
1041 | 173 | } |
1042 | 173 | 0 |
1043 | | }); |
1044 | 189 | self.inflater.reset(); |
1045 | 189 | self.ready_for_fdat_chunks = true; |
1046 | 168 | let fc = FrameControl { |
1047 | 189 | sequence_number: next_seq_no, |
1048 | 189 | width: buf.read_be()?, |
1049 | 185 | height: buf.read_be()?, |
1050 | 183 | x_offset: buf.read_be()?, |
1051 | 181 | y_offset: buf.read_be()?, |
1052 | 179 | delay_num: buf.read_be()?, |
1053 | 178 | delay_den: buf.read_be()?, |
1054 | | dispose_op: { |
1055 | 176 | let dispose_op = buf.read_be()?; |
1056 | 175 | match DisposeOp::from_u8(dispose_op) { |
1057 | 174 | Some(dispose_op) => dispose_op, |
1058 | | None => { |
1059 | 1 | return Err(DecodingError::Format( |
1060 | 1 | FormatErrorInner::InvalidDisposeOp(dispose_op).into(), |
1061 | 1 | )) |
1062 | | } |
1063 | | } |
1064 | | }, |
1065 | | blend_op: { |
1066 | 174 | let blend_op = buf.read_be()?; |
1067 | 173 | match BlendOp::from_u8(blend_op) { |
1068 | 168 | Some(blend_op) => blend_op, |
1069 | | None => { |
1070 | 5 | return Err(DecodingError::Format( |
1071 | 5 | FormatErrorInner::InvalidBlendOp(blend_op).into(), |
1072 | 5 | )) |
1073 | | } |
1074 | | } |
1075 | | }, |
1076 | | }; |
1077 | 168 | self.info.as_ref().unwrap().validate(&fc)?; |
1078 | 82 | self.info.as_mut().unwrap().frame_control = Some(fc); |
1079 | 82 | Ok(Decoded::FrameControl(fc)) |
1080 | 216 | } |
1081 | | |
1082 | 216 | fn parse_actl(&mut self) -> Result<Decoded, DecodingError> { |
1083 | 216 | if self.have_idat { |
1084 | 0 | Err(DecodingError::Format( |
1085 | 0 | FormatErrorInner::AfterIdat { kind: chunk::acTL }.into(), |
1086 | 0 | )) |
1087 | | } else { |
1088 | 216 | let mut buf = &self.current_chunk.raw_bytes[..]; |
1089 | 210 | let actl = AnimationControl { |
1090 | 216 | num_frames: buf.read_be()?, |
1091 | 211 | num_plays: buf.read_be()?, |
1092 | | }; |
1093 | 210 | self.info.as_mut().unwrap().animation_control = Some(actl); |
1094 | 210 | Ok(Decoded::AnimationControl(actl)) |
1095 | | } |
1096 | 216 | } |
1097 | | |
1098 | 63 | fn parse_plte(&mut self) -> Result<Decoded, DecodingError> { |
1099 | 63 | let info = self.info.as_mut().unwrap(); |
1100 | 63 | if info.palette.is_some() { |
1101 | | // Only one palette is allowed |
1102 | 2 | Err(DecodingError::Format( |
1103 | 2 | FormatErrorInner::DuplicateChunk { kind: chunk::PLTE }.into(), |
1104 | 2 | )) |
1105 | | } else { |
1106 | 61 | self.limits |
1107 | 61 | .reserve_bytes(self.current_chunk.raw_bytes.len())?; |
1108 | 61 | info.palette = Some(Cow::Owned(self.current_chunk.raw_bytes.clone())); |
1109 | 61 | Ok(Decoded::Nothing) |
1110 | | } |
1111 | 63 | } |
1112 | | |
1113 | 1.65k | fn parse_sbit(&mut self) -> Result<Decoded, DecodingError> { |
1114 | 1.65k | let mut parse = || { |
1115 | 1.65k | let info = self.info.as_mut().unwrap(); |
1116 | 1.65k | if info.palette.is_some() { |
1117 | 274 | return Err(DecodingError::Format( |
1118 | 274 | FormatErrorInner::AfterPlte { kind: chunk::sBIT }.into(), |
1119 | 274 | )); |
1120 | 1.38k | } |
1121 | 1.38k | |
1122 | 1.38k | if self.have_idat { |
1123 | 0 | return Err(DecodingError::Format( |
1124 | 0 | FormatErrorInner::AfterIdat { kind: chunk::sBIT }.into(), |
1125 | 0 | )); |
1126 | 1.38k | } |
1127 | 1.38k | |
1128 | 1.38k | if info.sbit.is_some() { |
1129 | 202 | return Err(DecodingError::Format( |
1130 | 202 | FormatErrorInner::DuplicateChunk { kind: chunk::sBIT }.into(), |
1131 | 202 | )); |
1132 | 1.18k | } |
1133 | 1.18k | |
1134 | 1.18k | let (color_type, bit_depth) = { (info.color_type, info.bit_depth) }; |
1135 | | // The sample depth for color type 3 is fixed at eight bits. |
1136 | 1.18k | let sample_depth = if color_type == ColorType::Indexed { |
1137 | 293 | BitDepth::Eight |
1138 | | } else { |
1139 | 888 | bit_depth |
1140 | | }; |
1141 | 1.18k | self.limits |
1142 | 1.18k | .reserve_bytes(self.current_chunk.raw_bytes.len())?; |
1143 | 1.18k | let vec = self.current_chunk.raw_bytes.clone(); |
1144 | 1.18k | let len = vec.len(); |
1145 | | |
1146 | | // expected lenth of the chunk |
1147 | 1.18k | let expected = match color_type { |
1148 | 415 | ColorType::Grayscale => 1, |
1149 | 311 | ColorType::Rgb | ColorType::Indexed => 3, |
1150 | 0 | ColorType::GrayscaleAlpha => 2, |
1151 | 455 | ColorType::Rgba => 4, |
1152 | | }; |
1153 | | |
1154 | | // Check if the sbit chunk size is valid. |
1155 | 1.18k | if expected != len { |
1156 | 568 | return Err(DecodingError::Format( |
1157 | 568 | FormatErrorInner::InvalidSbitChunkSize { |
1158 | 568 | color_type, |
1159 | 568 | expected, |
1160 | 568 | len, |
1161 | 568 | } |
1162 | 568 | .into(), |
1163 | 568 | )); |
1164 | 613 | } |
1165 | | |
1166 | 1.42k | for sbit in &vec { |
1167 | 1.41k | if *sbit < 1 || *sbit > sample_depth as u8 { |
1168 | 598 | return Err(DecodingError::Format( |
1169 | 598 | FormatErrorInner::InvalidSbit { |
1170 | 598 | sample_depth, |
1171 | 598 | sbit: *sbit, |
1172 | 598 | } |
1173 | 598 | .into(), |
1174 | 598 | )); |
1175 | 813 | } |
1176 | | } |
1177 | 15 | info.sbit = Some(Cow::Owned(vec)); |
1178 | 15 | Ok(Decoded::Nothing) |
1179 | 1.65k | }; |
1180 | | |
1181 | 1.65k | parse().ok(); |
1182 | 1.65k | Ok(Decoded::Nothing) |
1183 | 1.65k | } |
1184 | | |
1185 | 248 | fn parse_trns(&mut self) -> Result<Decoded, DecodingError> { |
1186 | 248 | let info = self.info.as_mut().unwrap(); |
1187 | 248 | if info.trns.is_some() { |
1188 | 6 | return Err(DecodingError::Format( |
1189 | 6 | FormatErrorInner::DuplicateChunk { kind: chunk::PLTE }.into(), |
1190 | 6 | )); |
1191 | 242 | } |
1192 | 242 | let (color_type, bit_depth) = { (info.color_type, info.bit_depth as u8) }; |
1193 | 242 | self.limits |
1194 | 242 | .reserve_bytes(self.current_chunk.raw_bytes.len())?; |
1195 | 242 | let mut vec = self.current_chunk.raw_bytes.clone(); |
1196 | 242 | let len = vec.len(); |
1197 | 242 | match color_type { |
1198 | | ColorType::Grayscale => { |
1199 | 21 | if len < 2 { |
1200 | 1 | return Err(DecodingError::Format( |
1201 | 1 | FormatErrorInner::ShortPalette { expected: 2, len }.into(), |
1202 | 1 | )); |
1203 | 20 | } |
1204 | 20 | if bit_depth < 16 { |
1205 | 19 | vec[0] = vec[1]; |
1206 | 19 | vec.truncate(1); |
1207 | 19 | } |
1208 | 20 | info.trns = Some(Cow::Owned(vec)); |
1209 | 20 | Ok(Decoded::Nothing) |
1210 | | } |
1211 | | ColorType::Rgb => { |
1212 | 211 | if len < 6 { |
1213 | 1 | return Err(DecodingError::Format( |
1214 | 1 | FormatErrorInner::ShortPalette { expected: 6, len }.into(), |
1215 | 1 | )); |
1216 | 210 | } |
1217 | 210 | if bit_depth < 16 { |
1218 | 208 | vec[0] = vec[1]; |
1219 | 208 | vec[1] = vec[3]; |
1220 | 208 | vec[2] = vec[5]; |
1221 | 208 | vec.truncate(3); |
1222 | 208 | } |
1223 | 210 | info.trns = Some(Cow::Owned(vec)); |
1224 | 210 | Ok(Decoded::Nothing) |
1225 | | } |
1226 | | ColorType::Indexed => { |
1227 | | // The transparency chunk must be after the palette chunk and |
1228 | | // before the data chunk. |
1229 | 9 | if info.palette.is_none() { |
1230 | 1 | return Err(DecodingError::Format( |
1231 | 1 | FormatErrorInner::BeforePlte { kind: chunk::tRNS }.into(), |
1232 | 1 | )); |
1233 | 8 | } else if self.have_idat { |
1234 | 0 | return Err(DecodingError::Format( |
1235 | 0 | FormatErrorInner::OutsidePlteIdat { kind: chunk::tRNS }.into(), |
1236 | 0 | )); |
1237 | 8 | } |
1238 | 8 | |
1239 | 8 | info.trns = Some(Cow::Owned(vec)); |
1240 | 8 | Ok(Decoded::Nothing) |
1241 | | } |
1242 | 1 | c => Err(DecodingError::Format( |
1243 | 1 | FormatErrorInner::ColorWithBadTrns(c).into(), |
1244 | 1 | )), |
1245 | | } |
1246 | 248 | } |
1247 | | |
1248 | 7 | fn parse_phys(&mut self) -> Result<Decoded, DecodingError> { |
1249 | 7 | let info = self.info.as_mut().unwrap(); |
1250 | 7 | if self.have_idat { |
1251 | 0 | Err(DecodingError::Format( |
1252 | 0 | FormatErrorInner::AfterIdat { kind: chunk::pHYs }.into(), |
1253 | 0 | )) |
1254 | 7 | } else if info.pixel_dims.is_some() { |
1255 | 1 | Err(DecodingError::Format( |
1256 | 1 | FormatErrorInner::DuplicateChunk { kind: chunk::pHYs }.into(), |
1257 | 1 | )) |
1258 | | } else { |
1259 | 6 | let mut buf = &self.current_chunk.raw_bytes[..]; |
1260 | 6 | let xppu = buf.read_be()?; |
1261 | 5 | let yppu = buf.read_be()?; |
1262 | 4 | let unit = buf.read_be()?; |
1263 | 3 | let unit = match Unit::from_u8(unit) { |
1264 | 2 | Some(unit) => unit, |
1265 | | None => { |
1266 | 1 | return Err(DecodingError::Format( |
1267 | 1 | FormatErrorInner::InvalidUnit(unit).into(), |
1268 | 1 | )) |
1269 | | } |
1270 | | }; |
1271 | 2 | let pixel_dims = PixelDimensions { xppu, yppu, unit }; |
1272 | 2 | info.pixel_dims = Some(pixel_dims); |
1273 | 2 | Ok(Decoded::PixelDimensions(pixel_dims)) |
1274 | | } |
1275 | 7 | } |
1276 | | |
1277 | 15 | fn parse_chrm(&mut self) -> Result<Decoded, DecodingError> { |
1278 | 15 | let info = self.info.as_mut().unwrap(); |
1279 | 15 | if self.have_idat { |
1280 | 0 | Err(DecodingError::Format( |
1281 | 0 | FormatErrorInner::AfterIdat { kind: chunk::cHRM }.into(), |
1282 | 0 | )) |
1283 | 15 | } else if info.chrm_chunk.is_some() { |
1284 | 1 | Err(DecodingError::Format( |
1285 | 1 | FormatErrorInner::DuplicateChunk { kind: chunk::cHRM }.into(), |
1286 | 1 | )) |
1287 | | } else { |
1288 | 14 | let mut buf = &self.current_chunk.raw_bytes[..]; |
1289 | 14 | let white_x: u32 = buf.read_be()?; |
1290 | 13 | let white_y: u32 = buf.read_be()?; |
1291 | 12 | let red_x: u32 = buf.read_be()?; |
1292 | 11 | let red_y: u32 = buf.read_be()?; |
1293 | 10 | let green_x: u32 = buf.read_be()?; |
1294 | 9 | let green_y: u32 = buf.read_be()?; |
1295 | 8 | let blue_x: u32 = buf.read_be()?; |
1296 | 7 | let blue_y: u32 = buf.read_be()?; |
1297 | | |
1298 | 6 | let source_chromaticities = SourceChromaticities { |
1299 | 6 | white: ( |
1300 | 6 | ScaledFloat::from_scaled(white_x), |
1301 | 6 | ScaledFloat::from_scaled(white_y), |
1302 | 6 | ), |
1303 | 6 | red: ( |
1304 | 6 | ScaledFloat::from_scaled(red_x), |
1305 | 6 | ScaledFloat::from_scaled(red_y), |
1306 | 6 | ), |
1307 | 6 | green: ( |
1308 | 6 | ScaledFloat::from_scaled(green_x), |
1309 | 6 | ScaledFloat::from_scaled(green_y), |
1310 | 6 | ), |
1311 | 6 | blue: ( |
1312 | 6 | ScaledFloat::from_scaled(blue_x), |
1313 | 6 | ScaledFloat::from_scaled(blue_y), |
1314 | 6 | ), |
1315 | 6 | }; |
1316 | 6 | |
1317 | 6 | info.chrm_chunk = Some(source_chromaticities); |
1318 | 6 | // Ignore chromaticities if sRGB profile is used. |
1319 | 6 | if info.srgb.is_none() { |
1320 | 5 | info.source_chromaticities = Some(source_chromaticities); |
1321 | 5 | } |
1322 | | |
1323 | 6 | Ok(Decoded::Nothing) |
1324 | | } |
1325 | 15 | } |
1326 | | |
1327 | 7 | fn parse_gama(&mut self) -> Result<Decoded, DecodingError> { |
1328 | 7 | let info = self.info.as_mut().unwrap(); |
1329 | 7 | if self.have_idat { |
1330 | 0 | Err(DecodingError::Format( |
1331 | 0 | FormatErrorInner::AfterIdat { kind: chunk::gAMA }.into(), |
1332 | 0 | )) |
1333 | 7 | } else if info.gama_chunk.is_some() { |
1334 | 1 | Err(DecodingError::Format( |
1335 | 1 | FormatErrorInner::DuplicateChunk { kind: chunk::gAMA }.into(), |
1336 | 1 | )) |
1337 | | } else { |
1338 | 6 | let mut buf = &self.current_chunk.raw_bytes[..]; |
1339 | 6 | let source_gamma: u32 = buf.read_be()?; |
1340 | 5 | let source_gamma = ScaledFloat::from_scaled(source_gamma); |
1341 | 5 | |
1342 | 5 | info.gama_chunk = Some(source_gamma); |
1343 | 5 | // Ignore chromaticities if sRGB profile is used. |
1344 | 5 | if info.srgb.is_none() { |
1345 | 3 | info.source_gamma = Some(source_gamma); |
1346 | 3 | } |
1347 | | |
1348 | 5 | Ok(Decoded::Nothing) |
1349 | | } |
1350 | 7 | } |
1351 | | |
1352 | 7 | fn parse_srgb(&mut self) -> Result<Decoded, DecodingError> { |
1353 | 7 | let info = self.info.as_mut().unwrap(); |
1354 | 7 | if self.have_idat { |
1355 | 0 | Err(DecodingError::Format( |
1356 | 0 | FormatErrorInner::AfterIdat { kind: chunk::acTL }.into(), |
1357 | 0 | )) |
1358 | 7 | } else if info.srgb.is_some() { |
1359 | 1 | Err(DecodingError::Format( |
1360 | 1 | FormatErrorInner::DuplicateChunk { kind: chunk::sRGB }.into(), |
1361 | 1 | )) |
1362 | | } else { |
1363 | 6 | let mut buf = &self.current_chunk.raw_bytes[..]; |
1364 | 6 | let raw: u8 = buf.read_be()?; // BE is is nonsense for single bytes, but this way the size is checked. |
1365 | 6 | let rendering_intent = crate::SrgbRenderingIntent::from_raw(raw).ok_or_else(|| { |
1366 | 1 | FormatError::from(FormatErrorInner::InvalidSrgbRenderingIntent(raw)) |
1367 | 6 | })?; |
1368 | | |
1369 | | // Set srgb and override source gamma and chromaticities. |
1370 | 5 | info.srgb = Some(rendering_intent); |
1371 | 5 | info.source_gamma = Some(crate::srgb::substitute_gamma()); |
1372 | 5 | info.source_chromaticities = Some(crate::srgb::substitute_chromaticities()); |
1373 | 5 | Ok(Decoded::Nothing) |
1374 | | } |
1375 | 7 | } |
1376 | | |
1377 | | // NOTE: This function cannot return `DecodingError` and handles parsing |
1378 | | // errors or spec violations as-if the chunk was missing. See |
1379 | | // https://github.com/image-rs/image-png/issues/525 for more discussion. |
1380 | 87 | fn parse_cicp(&mut self) -> Decoded { |
1381 | 53 | fn parse(mut buf: &[u8]) -> Result<CodingIndependentCodePoints, std::io::Error> { |
1382 | 53 | let color_primaries: u8 = buf.read_be()?; |
1383 | 53 | let transfer_function: u8 = buf.read_be()?; |
1384 | 52 | let matrix_coefficients: u8 = buf.read_be()?; |
1385 | 30 | let is_video_full_range_image = { |
1386 | 51 | let flag: u8 = buf.read_be()?; |
1387 | 51 | match flag { |
1388 | 30 | 0 => false, |
1389 | 0 | 1 => true, |
1390 | | _ => { |
1391 | 21 | return Err(std::io::ErrorKind::InvalidData.into()); |
1392 | | } |
1393 | | } |
1394 | | }; |
1395 | | |
1396 | | // RGB is currently the only supported color model in PNG, and as |
1397 | | // such Matrix Coefficients shall be set to 0. |
1398 | 30 | if matrix_coefficients != 0 { |
1399 | 29 | return Err(std::io::ErrorKind::InvalidData.into()); |
1400 | 1 | } |
1401 | 1 | |
1402 | 1 | if !buf.is_empty() { |
1403 | 0 | return Err(std::io::ErrorKind::InvalidData.into()); |
1404 | 1 | } |
1405 | 1 | |
1406 | 1 | Ok(CodingIndependentCodePoints { |
1407 | 1 | color_primaries, |
1408 | 1 | transfer_function, |
1409 | 1 | matrix_coefficients, |
1410 | 1 | is_video_full_range_image, |
1411 | 1 | }) |
1412 | 53 | } |
1413 | | |
1414 | | // The spec requires that the cICP chunk MUST come before the PLTE and IDAT chunks. |
1415 | | // Additionally, we ignore a second, duplicated cICP chunk (if any). |
1416 | 87 | let info = self.info.as_mut().unwrap(); |
1417 | 87 | let is_before_plte_and_idat = !self.have_idat && info.palette.is_none(); |
1418 | 87 | if is_before_plte_and_idat && info.coding_independent_code_points.is_none() { |
1419 | 53 | info.coding_independent_code_points = parse(&self.current_chunk.raw_bytes[..]).ok(); |
1420 | 53 | } |
1421 | | |
1422 | 87 | Decoded::Nothing |
1423 | 87 | } |
1424 | | |
1425 | | // NOTE: This function cannot return `DecodingError` and handles parsing |
1426 | | // errors or spec violations as-if the chunk was missing. See |
1427 | | // https://github.com/image-rs/image-png/issues/525 for more discussion. |
1428 | 350 | fn parse_mdcv(&mut self) -> Decoded { |
1429 | 250 | fn parse(mut buf: &[u8]) -> Result<MasteringDisplayColorVolume, std::io::Error> { |
1430 | 250 | let red_x: u16 = buf.read_be()?; |
1431 | 247 | let red_y: u16 = buf.read_be()?; |
1432 | 236 | let green_x: u16 = buf.read_be()?; |
1433 | 230 | let green_y: u16 = buf.read_be()?; |
1434 | 229 | let blue_x: u16 = buf.read_be()?; |
1435 | 225 | let blue_y: u16 = buf.read_be()?; |
1436 | 225 | let white_x: u16 = buf.read_be()?; |
1437 | 89 | let white_y: u16 = buf.read_be()?; |
1438 | 704 | fn scale(chunk: u16) -> ScaledFloat { |
1439 | 704 | // `ScaledFloat::SCALING` is hardcoded to 100_000, which works |
1440 | 704 | // well for the `cHRM` chunk where the spec says that "a value |
1441 | 704 | // of 0.3127 would be stored as the integer 31270". In the |
1442 | 704 | // `mDCV` chunk the spec says that "0.708, 0.292)" is stored as |
1443 | 704 | // "{ 35400, 14600 }", using a scaling factor of 50_000, so we |
1444 | 704 | // multiply by 2 before converting. |
1445 | 704 | ScaledFloat::from_scaled((chunk as u32) * 2) |
1446 | 704 | } |
1447 | 88 | let chromaticities = SourceChromaticities { |
1448 | 88 | white: (scale(white_x), scale(white_y)), |
1449 | 88 | red: (scale(red_x), scale(red_y)), |
1450 | 88 | green: (scale(green_x), scale(green_y)), |
1451 | 88 | blue: (scale(blue_x), scale(blue_y)), |
1452 | 88 | }; |
1453 | 88 | let max_luminance: u32 = buf.read_be()?; |
1454 | 78 | let min_luminance: u32 = buf.read_be()?; |
1455 | 22 | if !buf.is_empty() { |
1456 | 15 | return Err(std::io::ErrorKind::InvalidData.into()); |
1457 | 7 | } |
1458 | 7 | Ok(MasteringDisplayColorVolume { |
1459 | 7 | chromaticities, |
1460 | 7 | max_luminance, |
1461 | 7 | min_luminance, |
1462 | 7 | }) |
1463 | 250 | } |
1464 | | |
1465 | | // The spec requires that the mDCV chunk MUST come before the PLTE and IDAT chunks. |
1466 | | // Additionally, we ignore a second, duplicated mDCV chunk (if any). |
1467 | 350 | let info = self.info.as_mut().unwrap(); |
1468 | 350 | let is_before_plte_and_idat = !self.have_idat && info.palette.is_none(); |
1469 | 350 | if is_before_plte_and_idat && info.mastering_display_color_volume.is_none() { |
1470 | 250 | info.mastering_display_color_volume = parse(&self.current_chunk.raw_bytes[..]).ok(); |
1471 | 250 | } |
1472 | | |
1473 | 350 | Decoded::Nothing |
1474 | 350 | } |
1475 | | |
1476 | | // NOTE: This function cannot return `DecodingError` and handles parsing |
1477 | | // errors or spec violations as-if the chunk was missing. See |
1478 | | // https://github.com/image-rs/image-png/issues/525 for more discussion. |
1479 | 60 | fn parse_clli(&mut self) -> Decoded { |
1480 | 50 | fn parse(mut buf: &[u8]) -> Result<ContentLightLevelInfo, std::io::Error> { |
1481 | 50 | let max_content_light_level: u32 = buf.read_be()?; |
1482 | 49 | let max_frame_average_light_level: u32 = buf.read_be()?; |
1483 | 39 | if !buf.is_empty() { |
1484 | 34 | return Err(std::io::ErrorKind::InvalidData.into()); |
1485 | 5 | } |
1486 | 5 | Ok(ContentLightLevelInfo { |
1487 | 5 | max_content_light_level, |
1488 | 5 | max_frame_average_light_level, |
1489 | 5 | }) |
1490 | 50 | } |
1491 | | |
1492 | | // We ignore a second, duplicated cLLI chunk (if any). |
1493 | 60 | let info = self.info.as_mut().unwrap(); |
1494 | 60 | if info.content_light_level.is_none() { |
1495 | 50 | info.content_light_level = parse(&self.current_chunk.raw_bytes[..]).ok(); |
1496 | 50 | } |
1497 | | |
1498 | 60 | Decoded::Nothing |
1499 | 60 | } |
1500 | | |
1501 | 753 | fn parse_iccp(&mut self) -> Result<Decoded, DecodingError> { |
1502 | 753 | if self.have_idat { |
1503 | 0 | Err(DecodingError::Format( |
1504 | 0 | FormatErrorInner::AfterIdat { kind: chunk::iCCP }.into(), |
1505 | 0 | )) |
1506 | 753 | } else if self.have_iccp { |
1507 | | // We have already encountered an iCCP chunk before. |
1508 | | // |
1509 | | // Section "4.2.2.4. iCCP Embedded ICC profile" of the spec says: |
1510 | | // > A file should contain at most one embedded profile, |
1511 | | // > whether explicit like iCCP or implicit like sRGB. |
1512 | | // but |
1513 | | // * This is a "should", not a "must" |
1514 | | // * The spec also says that "All ancillary chunks are optional, in the sense that |
1515 | | // [...] decoders can ignore them." |
1516 | | // * The reference implementation (libpng) ignores the subsequent iCCP chunks |
1517 | | // (treating them as a benign error). |
1518 | 413 | Ok(Decoded::Nothing) |
1519 | | } else { |
1520 | 340 | self.have_iccp = true; |
1521 | 340 | let _ = self.parse_iccp_raw(); |
1522 | 340 | Ok(Decoded::Nothing) |
1523 | | } |
1524 | 753 | } |
1525 | | |
1526 | 340 | fn parse_iccp_raw(&mut self) -> Result<(), DecodingError> { |
1527 | 340 | let info = self.info.as_mut().unwrap(); |
1528 | 340 | let mut buf = &self.current_chunk.raw_bytes[..]; |
1529 | | |
1530 | | // read profile name |
1531 | 2.93k | for len in 0..=80 { |
1532 | 2.93k | let raw: u8 = buf.read_be()?; |
1533 | 2.92k | if (raw == 0 && len == 0) || (raw != 0 && len == 80) { |
1534 | 3 | return Err(DecodingError::from(TextDecodingError::InvalidKeywordSize)); |
1535 | 2.92k | } |
1536 | 2.92k | if raw == 0 { |
1537 | 332 | break; |
1538 | 2.59k | } |
1539 | | } |
1540 | | |
1541 | 332 | match buf.read_be()? { |
1542 | | // compression method |
1543 | 324 | 0u8 => (), |
1544 | 5 | n => { |
1545 | 5 | return Err(DecodingError::Format( |
1546 | 5 | FormatErrorInner::UnknownCompressionMethod(n).into(), |
1547 | 5 | )) |
1548 | | } |
1549 | | } |
1550 | | |
1551 | 324 | match fdeflate::decompress_to_vec_bounded(buf, self.limits.bytes) { |
1552 | 111 | Ok(profile) => { |
1553 | 111 | self.limits.reserve_bytes(profile.len())?; |
1554 | 111 | info.icc_profile = Some(Cow::Owned(profile)); |
1555 | | } |
1556 | 213 | Err(fdeflate::BoundedDecompressionError::DecompressionError { inner: err }) => { |
1557 | 213 | return Err(DecodingError::Format( |
1558 | 213 | FormatErrorInner::CorruptFlateStream { err }.into(), |
1559 | 213 | )) |
1560 | | } |
1561 | | Err(fdeflate::BoundedDecompressionError::OutputTooLarge { .. }) => { |
1562 | 0 | return Err(DecodingError::LimitsExceeded); |
1563 | | } |
1564 | | } |
1565 | | |
1566 | 111 | Ok(()) |
1567 | 340 | } |
1568 | | |
1569 | 4.31k | fn parse_ihdr(&mut self) -> Result<Decoded, DecodingError> { |
1570 | 4.31k | if self.info.is_some() { |
1571 | 1 | return Err(DecodingError::Format( |
1572 | 1 | FormatErrorInner::DuplicateChunk { kind: IHDR }.into(), |
1573 | 1 | )); |
1574 | 4.31k | } |
1575 | 4.31k | let mut buf = &self.current_chunk.raw_bytes[..]; |
1576 | 4.31k | let width = buf.read_be()?; |
1577 | 4.31k | let height = buf.read_be()?; |
1578 | 4.30k | if width == 0 || height == 0 { |
1579 | 2 | return Err(DecodingError::Format( |
1580 | 2 | FormatErrorInner::InvalidDimensions.into(), |
1581 | 2 | )); |
1582 | 4.30k | } |
1583 | 4.30k | let bit_depth = buf.read_be()?; |
1584 | 4.27k | let bit_depth = match BitDepth::from_u8(bit_depth) { |
1585 | 4.25k | Some(bits) => bits, |
1586 | | None => { |
1587 | 20 | return Err(DecodingError::Format( |
1588 | 20 | FormatErrorInner::InvalidBitDepth(bit_depth).into(), |
1589 | 20 | )) |
1590 | | } |
1591 | | }; |
1592 | 4.25k | let color_type = buf.read_be()?; |
1593 | 4.25k | let color_type = match ColorType::from_u8(color_type) { |
1594 | 4.25k | Some(color_type) => { |
1595 | 4.25k | if color_type.is_combination_invalid(bit_depth) { |
1596 | 3 | return Err(DecodingError::Format( |
1597 | 3 | FormatErrorInner::InvalidColorBitDepth { |
1598 | 3 | color_type, |
1599 | 3 | bit_depth, |
1600 | 3 | } |
1601 | 3 | .into(), |
1602 | 3 | )); |
1603 | | } else { |
1604 | 4.24k | color_type |
1605 | | } |
1606 | | } |
1607 | | None => { |
1608 | 2 | return Err(DecodingError::Format( |
1609 | 2 | FormatErrorInner::InvalidColorType(color_type).into(), |
1610 | 2 | )) |
1611 | | } |
1612 | | }; |
1613 | 4.24k | match buf.read_be()? { |
1614 | | // compression method |
1615 | 4.23k | 0u8 => (), |
1616 | 8 | n => { |
1617 | 8 | return Err(DecodingError::Format( |
1618 | 8 | FormatErrorInner::UnknownCompressionMethod(n).into(), |
1619 | 8 | )) |
1620 | | } |
1621 | | } |
1622 | 4.23k | match buf.read_be()? { |
1623 | | // filter method |
1624 | 4.22k | 0u8 => (), |
1625 | 8 | n => { |
1626 | 8 | return Err(DecodingError::Format( |
1627 | 8 | FormatErrorInner::UnknownFilterMethod(n).into(), |
1628 | 8 | )) |
1629 | | } |
1630 | | } |
1631 | 4.22k | let interlaced = match buf.read_be()? { |
1632 | 2.55k | 0u8 => false, |
1633 | 1.66k | 1 => true, |
1634 | 1 | n => { |
1635 | 1 | return Err(DecodingError::Format( |
1636 | 1 | FormatErrorInner::UnknownInterlaceMethod(n).into(), |
1637 | 1 | )) |
1638 | | } |
1639 | | }; |
1640 | | |
1641 | 4.22k | if let Some(mut raw_row_len) = color_type.checked_raw_row_length(bit_depth, width) { |
1642 | 4.22k | if interlaced { |
1643 | 1.66k | // This overshoots, but overestimating should be fine. |
1644 | 1.66k | // TODO: Calculate **exact** IDAT size for interlaced images. |
1645 | 1.66k | raw_row_len = raw_row_len.saturating_mul(2); |
1646 | 2.55k | } |
1647 | 4.22k | self.inflater |
1648 | 4.22k | .set_max_total_output((height as usize).saturating_mul(raw_row_len)); |
1649 | 0 | } |
1650 | | |
1651 | 4.22k | self.info = Some(Info { |
1652 | 4.22k | width, |
1653 | 4.22k | height, |
1654 | 4.22k | bit_depth, |
1655 | 4.22k | color_type, |
1656 | 4.22k | interlaced, |
1657 | 4.22k | ..Default::default() |
1658 | 4.22k | }); |
1659 | 4.22k | |
1660 | 4.22k | Ok(Decoded::Header( |
1661 | 4.22k | width, height, bit_depth, color_type, interlaced, |
1662 | 4.22k | )) |
1663 | 4.31k | } |
1664 | | |
1665 | 0 | fn split_keyword(buf: &[u8]) -> Result<(&[u8], &[u8]), DecodingError> { |
1666 | 0 | let null_byte_index = buf |
1667 | 0 | .iter() |
1668 | 0 | .position(|&b| b == 0) |
1669 | 0 | .ok_or_else(|| DecodingError::from(TextDecodingError::MissingNullSeparator))?; |
1670 | | |
1671 | 0 | if null_byte_index == 0 || null_byte_index > 79 { |
1672 | 0 | return Err(DecodingError::from(TextDecodingError::InvalidKeywordSize)); |
1673 | 0 | } |
1674 | 0 |
|
1675 | 0 | Ok((&buf[..null_byte_index], &buf[null_byte_index + 1..])) |
1676 | 0 | } |
1677 | | |
1678 | 0 | fn parse_text(&mut self) -> Result<Decoded, DecodingError> { |
1679 | 0 | let buf = &self.current_chunk.raw_bytes[..]; |
1680 | 0 | self.limits.reserve_bytes(buf.len())?; |
1681 | | |
1682 | 0 | let (keyword_slice, value_slice) = Self::split_keyword(buf)?; |
1683 | | |
1684 | 0 | self.info |
1685 | 0 | .as_mut() |
1686 | 0 | .unwrap() |
1687 | 0 | .uncompressed_latin1_text |
1688 | 0 | .push(TEXtChunk::decode(keyword_slice, value_slice).map_err(DecodingError::from)?); |
1689 | | |
1690 | 0 | Ok(Decoded::Nothing) |
1691 | 0 | } |
1692 | | |
1693 | 0 | fn parse_ztxt(&mut self) -> Result<Decoded, DecodingError> { |
1694 | 0 | let buf = &self.current_chunk.raw_bytes[..]; |
1695 | 0 | self.limits.reserve_bytes(buf.len())?; |
1696 | | |
1697 | 0 | let (keyword_slice, value_slice) = Self::split_keyword(buf)?; |
1698 | | |
1699 | 0 | let compression_method = *value_slice |
1700 | 0 | .first() |
1701 | 0 | .ok_or_else(|| DecodingError::from(TextDecodingError::InvalidCompressionMethod))?; |
1702 | | |
1703 | 0 | let text_slice = &value_slice[1..]; |
1704 | 0 |
|
1705 | 0 | self.info.as_mut().unwrap().compressed_latin1_text.push( |
1706 | 0 | ZTXtChunk::decode(keyword_slice, compression_method, text_slice) |
1707 | 0 | .map_err(DecodingError::from)?, |
1708 | | ); |
1709 | | |
1710 | 0 | Ok(Decoded::Nothing) |
1711 | 0 | } |
1712 | | |
1713 | 0 | fn parse_itxt(&mut self) -> Result<Decoded, DecodingError> { |
1714 | 0 | let buf = &self.current_chunk.raw_bytes[..]; |
1715 | 0 | self.limits.reserve_bytes(buf.len())?; |
1716 | | |
1717 | 0 | let (keyword_slice, value_slice) = Self::split_keyword(buf)?; |
1718 | | |
1719 | 0 | let compression_flag = *value_slice |
1720 | 0 | .first() |
1721 | 0 | .ok_or_else(|| DecodingError::from(TextDecodingError::MissingCompressionFlag))?; |
1722 | | |
1723 | 0 | let compression_method = *value_slice |
1724 | 0 | .get(1) |
1725 | 0 | .ok_or_else(|| DecodingError::from(TextDecodingError::InvalidCompressionMethod))?; |
1726 | | |
1727 | 0 | let second_null_byte_index = value_slice[2..] |
1728 | 0 | .iter() |
1729 | 0 | .position(|&b| b == 0) |
1730 | 0 | .ok_or_else(|| DecodingError::from(TextDecodingError::MissingNullSeparator))? |
1731 | | + 2; |
1732 | | |
1733 | 0 | let language_tag_slice = &value_slice[2..second_null_byte_index]; |
1734 | | |
1735 | 0 | let third_null_byte_index = value_slice[second_null_byte_index + 1..] |
1736 | 0 | .iter() |
1737 | 0 | .position(|&b| b == 0) |
1738 | 0 | .ok_or_else(|| DecodingError::from(TextDecodingError::MissingNullSeparator))? |
1739 | 0 | + (second_null_byte_index + 1); |
1740 | 0 |
|
1741 | 0 | let translated_keyword_slice = |
1742 | 0 | &value_slice[second_null_byte_index + 1..third_null_byte_index]; |
1743 | 0 |
|
1744 | 0 | let text_slice = &value_slice[third_null_byte_index + 1..]; |
1745 | 0 |
|
1746 | 0 | self.info.as_mut().unwrap().utf8_text.push( |
1747 | 0 | ITXtChunk::decode( |
1748 | 0 | keyword_slice, |
1749 | 0 | compression_flag, |
1750 | 0 | compression_method, |
1751 | 0 | language_tag_slice, |
1752 | 0 | translated_keyword_slice, |
1753 | 0 | text_slice, |
1754 | 0 | ) |
1755 | 0 | .map_err(DecodingError::from)?, |
1756 | | ); |
1757 | | |
1758 | 0 | Ok(Decoded::Nothing) |
1759 | 0 | } |
1760 | | |
1761 | | // NOTE: This function cannot return `DecodingError` and handles parsing |
1762 | | // errors or spec violations as-if the chunk was missing. See |
1763 | | // https://github.com/image-rs/image-png/issues/525 for more discussion. |
1764 | 1.22k | fn parse_bkgd(&mut self) -> Decoded { |
1765 | 1.22k | let info = self.info.as_mut().unwrap(); |
1766 | 1.22k | if info.bkgd.is_none() && !self.have_idat { |
1767 | 1.02k | let expected = match info.color_type { |
1768 | | ColorType::Indexed => { |
1769 | 585 | if info.palette.is_none() { |
1770 | 381 | return Decoded::Nothing; |
1771 | 204 | }; |
1772 | 204 | 1 |
1773 | | } |
1774 | 47 | ColorType::Grayscale | ColorType::GrayscaleAlpha => 2, |
1775 | 394 | ColorType::Rgb | ColorType::Rgba => 6, |
1776 | | }; |
1777 | 645 | let vec = self.current_chunk.raw_bytes.clone(); |
1778 | 645 | let len = vec.len(); |
1779 | 645 | if len == expected { |
1780 | 11 | info.bkgd = Some(Cow::Owned(vec)); |
1781 | 634 | } |
1782 | 194 | } |
1783 | | |
1784 | 839 | Decoded::Nothing |
1785 | 1.22k | } |
1786 | | } |
1787 | | |
1788 | | impl Info<'_> { |
1789 | 168 | fn validate(&self, fc: &FrameControl) -> Result<(), DecodingError> { |
1790 | 168 | if fc.width == 0 || fc.height == 0 { |
1791 | 1 | return Err(DecodingError::Format( |
1792 | 1 | FormatErrorInner::InvalidDimensions.into(), |
1793 | 1 | )); |
1794 | 167 | } |
1795 | 167 | |
1796 | 167 | // Validate mathematically: fc.width + fc.x_offset <= self.width |
1797 | 167 | let in_x_bounds = Some(fc.width) <= self.width.checked_sub(fc.x_offset); |
1798 | 167 | // Validate mathematically: fc.height + fc.y_offset <= self.height |
1799 | 167 | let in_y_bounds = Some(fc.height) <= self.height.checked_sub(fc.y_offset); |
1800 | 167 | |
1801 | 167 | if !in_x_bounds || !in_y_bounds { |
1802 | 85 | return Err(DecodingError::Format( |
1803 | 85 | // TODO: do we want to display the bad bounds? |
1804 | 85 | FormatErrorInner::BadSubFrameBounds {}.into(), |
1805 | 85 | )); |
1806 | 82 | } |
1807 | 82 | |
1808 | 82 | Ok(()) |
1809 | 168 | } |
1810 | | } |
1811 | | |
1812 | | impl Default for StreamingDecoder { |
1813 | 0 | fn default() -> Self { |
1814 | 0 | Self::new() |
1815 | 0 | } |
1816 | | } |
1817 | | |
1818 | | impl Default for ChunkState { |
1819 | 4.54k | fn default() -> Self { |
1820 | 4.54k | ChunkState { |
1821 | 4.54k | type_: ChunkType([0; 4]), |
1822 | 4.54k | crc: Crc32::new(), |
1823 | 4.54k | remaining: 0, |
1824 | 4.54k | raw_bytes: Vec::with_capacity(CHUNK_BUFFER_SIZE), |
1825 | 4.54k | } |
1826 | 4.54k | } |
1827 | | } |
1828 | | |
1829 | | #[cfg(test)] |
1830 | | mod tests { |
1831 | | use super::ScaledFloat; |
1832 | | use super::SourceChromaticities; |
1833 | | use crate::test_utils::*; |
1834 | | use crate::{Decoder, DecodingError, Reader}; |
1835 | | use approx::assert_relative_eq; |
1836 | | use byteorder::WriteBytesExt; |
1837 | | use std::borrow::Cow; |
1838 | | use std::cell::RefCell; |
1839 | | use std::collections::VecDeque; |
1840 | | use std::fs::File; |
1841 | | use std::io::{ErrorKind, Read, Write}; |
1842 | | use std::rc::Rc; |
1843 | | |
1844 | | #[test] |
1845 | | fn image_gamma() -> Result<(), ()> { |
1846 | | fn trial(path: &str, expected: Option<ScaledFloat>) { |
1847 | | let decoder = crate::Decoder::new(File::open(path).unwrap()); |
1848 | | let reader = decoder.read_info().unwrap(); |
1849 | | let actual: Option<ScaledFloat> = reader.info().source_gamma; |
1850 | | assert!(actual == expected); |
1851 | | } |
1852 | | trial("tests/pngsuite/f00n0g08.png", None); |
1853 | | trial("tests/pngsuite/f00n2c08.png", None); |
1854 | | trial("tests/pngsuite/f01n0g08.png", None); |
1855 | | trial("tests/pngsuite/f01n2c08.png", None); |
1856 | | trial("tests/pngsuite/f02n0g08.png", None); |
1857 | | trial("tests/pngsuite/f02n2c08.png", None); |
1858 | | trial("tests/pngsuite/f03n0g08.png", None); |
1859 | | trial("tests/pngsuite/f03n2c08.png", None); |
1860 | | trial("tests/pngsuite/f04n0g08.png", None); |
1861 | | trial("tests/pngsuite/f04n2c08.png", None); |
1862 | | trial("tests/pngsuite/f99n0g04.png", None); |
1863 | | trial("tests/pngsuite/tm3n3p02.png", None); |
1864 | | trial("tests/pngsuite/g03n0g16.png", Some(ScaledFloat::new(0.35))); |
1865 | | trial("tests/pngsuite/g03n2c08.png", Some(ScaledFloat::new(0.35))); |
1866 | | trial("tests/pngsuite/g03n3p04.png", Some(ScaledFloat::new(0.35))); |
1867 | | trial("tests/pngsuite/g04n0g16.png", Some(ScaledFloat::new(0.45))); |
1868 | | trial("tests/pngsuite/g04n2c08.png", Some(ScaledFloat::new(0.45))); |
1869 | | trial("tests/pngsuite/g04n3p04.png", Some(ScaledFloat::new(0.45))); |
1870 | | trial("tests/pngsuite/g05n0g16.png", Some(ScaledFloat::new(0.55))); |
1871 | | trial("tests/pngsuite/g05n2c08.png", Some(ScaledFloat::new(0.55))); |
1872 | | trial("tests/pngsuite/g05n3p04.png", Some(ScaledFloat::new(0.55))); |
1873 | | trial("tests/pngsuite/g07n0g16.png", Some(ScaledFloat::new(0.7))); |
1874 | | trial("tests/pngsuite/g07n2c08.png", Some(ScaledFloat::new(0.7))); |
1875 | | trial("tests/pngsuite/g07n3p04.png", Some(ScaledFloat::new(0.7))); |
1876 | | trial("tests/pngsuite/g10n0g16.png", Some(ScaledFloat::new(1.0))); |
1877 | | trial("tests/pngsuite/g10n2c08.png", Some(ScaledFloat::new(1.0))); |
1878 | | trial("tests/pngsuite/g10n3p04.png", Some(ScaledFloat::new(1.0))); |
1879 | | trial("tests/pngsuite/g25n0g16.png", Some(ScaledFloat::new(2.5))); |
1880 | | trial("tests/pngsuite/g25n2c08.png", Some(ScaledFloat::new(2.5))); |
1881 | | trial("tests/pngsuite/g25n3p04.png", Some(ScaledFloat::new(2.5))); |
1882 | | Ok(()) |
1883 | | } |
1884 | | |
1885 | | #[test] |
1886 | | fn image_source_chromaticities() -> Result<(), ()> { |
1887 | | fn trial(path: &str, expected: Option<SourceChromaticities>) { |
1888 | | let decoder = crate::Decoder::new(File::open(path).unwrap()); |
1889 | | let reader = decoder.read_info().unwrap(); |
1890 | | let actual: Option<SourceChromaticities> = reader.info().source_chromaticities; |
1891 | | assert!(actual == expected); |
1892 | | } |
1893 | | trial( |
1894 | | "tests/pngsuite/ccwn2c08.png", |
1895 | | Some(SourceChromaticities::new( |
1896 | | (0.3127, 0.3290), |
1897 | | (0.64, 0.33), |
1898 | | (0.30, 0.60), |
1899 | | (0.15, 0.06), |
1900 | | )), |
1901 | | ); |
1902 | | trial( |
1903 | | "tests/pngsuite/ccwn3p08.png", |
1904 | | Some(SourceChromaticities::new( |
1905 | | (0.3127, 0.3290), |
1906 | | (0.64, 0.33), |
1907 | | (0.30, 0.60), |
1908 | | (0.15, 0.06), |
1909 | | )), |
1910 | | ); |
1911 | | trial("tests/pngsuite/basi0g01.png", None); |
1912 | | trial("tests/pngsuite/basi0g02.png", None); |
1913 | | trial("tests/pngsuite/basi0g04.png", None); |
1914 | | trial("tests/pngsuite/basi0g08.png", None); |
1915 | | trial("tests/pngsuite/basi0g16.png", None); |
1916 | | trial("tests/pngsuite/basi2c08.png", None); |
1917 | | trial("tests/pngsuite/basi2c16.png", None); |
1918 | | trial("tests/pngsuite/basi3p01.png", None); |
1919 | | trial("tests/pngsuite/basi3p02.png", None); |
1920 | | trial("tests/pngsuite/basi3p04.png", None); |
1921 | | trial("tests/pngsuite/basi3p08.png", None); |
1922 | | trial("tests/pngsuite/basi4a08.png", None); |
1923 | | trial("tests/pngsuite/basi4a16.png", None); |
1924 | | trial("tests/pngsuite/basi6a08.png", None); |
1925 | | trial("tests/pngsuite/basi6a16.png", None); |
1926 | | trial("tests/pngsuite/basn0g01.png", None); |
1927 | | trial("tests/pngsuite/basn0g02.png", None); |
1928 | | trial("tests/pngsuite/basn0g04.png", None); |
1929 | | trial("tests/pngsuite/basn0g08.png", None); |
1930 | | trial("tests/pngsuite/basn0g16.png", None); |
1931 | | trial("tests/pngsuite/basn2c08.png", None); |
1932 | | trial("tests/pngsuite/basn2c16.png", None); |
1933 | | trial("tests/pngsuite/basn3p01.png", None); |
1934 | | trial("tests/pngsuite/basn3p02.png", None); |
1935 | | trial("tests/pngsuite/basn3p04.png", None); |
1936 | | trial("tests/pngsuite/basn3p08.png", None); |
1937 | | trial("tests/pngsuite/basn4a08.png", None); |
1938 | | trial("tests/pngsuite/basn4a16.png", None); |
1939 | | trial("tests/pngsuite/basn6a08.png", None); |
1940 | | trial("tests/pngsuite/basn6a16.png", None); |
1941 | | trial("tests/pngsuite/bgai4a08.png", None); |
1942 | | trial("tests/pngsuite/bgai4a16.png", None); |
1943 | | trial("tests/pngsuite/bgan6a08.png", None); |
1944 | | trial("tests/pngsuite/bgan6a16.png", None); |
1945 | | trial("tests/pngsuite/bgbn4a08.png", None); |
1946 | | trial("tests/pngsuite/bggn4a16.png", None); |
1947 | | trial("tests/pngsuite/bgwn6a08.png", None); |
1948 | | trial("tests/pngsuite/bgyn6a16.png", None); |
1949 | | trial("tests/pngsuite/cdfn2c08.png", None); |
1950 | | trial("tests/pngsuite/cdhn2c08.png", None); |
1951 | | trial("tests/pngsuite/cdsn2c08.png", None); |
1952 | | trial("tests/pngsuite/cdun2c08.png", None); |
1953 | | trial("tests/pngsuite/ch1n3p04.png", None); |
1954 | | trial("tests/pngsuite/ch2n3p08.png", None); |
1955 | | trial("tests/pngsuite/cm0n0g04.png", None); |
1956 | | trial("tests/pngsuite/cm7n0g04.png", None); |
1957 | | trial("tests/pngsuite/cm9n0g04.png", None); |
1958 | | trial("tests/pngsuite/cs3n2c16.png", None); |
1959 | | trial("tests/pngsuite/cs3n3p08.png", None); |
1960 | | trial("tests/pngsuite/cs5n2c08.png", None); |
1961 | | trial("tests/pngsuite/cs5n3p08.png", None); |
1962 | | trial("tests/pngsuite/cs8n2c08.png", None); |
1963 | | trial("tests/pngsuite/cs8n3p08.png", None); |
1964 | | trial("tests/pngsuite/ct0n0g04.png", None); |
1965 | | trial("tests/pngsuite/ct1n0g04.png", None); |
1966 | | trial("tests/pngsuite/cten0g04.png", None); |
1967 | | trial("tests/pngsuite/ctfn0g04.png", None); |
1968 | | trial("tests/pngsuite/ctgn0g04.png", None); |
1969 | | trial("tests/pngsuite/cthn0g04.png", None); |
1970 | | trial("tests/pngsuite/ctjn0g04.png", None); |
1971 | | trial("tests/pngsuite/ctzn0g04.png", None); |
1972 | | trial("tests/pngsuite/f00n0g08.png", None); |
1973 | | trial("tests/pngsuite/f00n2c08.png", None); |
1974 | | trial("tests/pngsuite/f01n0g08.png", None); |
1975 | | trial("tests/pngsuite/f01n2c08.png", None); |
1976 | | trial("tests/pngsuite/f02n0g08.png", None); |
1977 | | trial("tests/pngsuite/f02n2c08.png", None); |
1978 | | trial("tests/pngsuite/f03n0g08.png", None); |
1979 | | trial("tests/pngsuite/f03n2c08.png", None); |
1980 | | trial("tests/pngsuite/f04n0g08.png", None); |
1981 | | trial("tests/pngsuite/f04n2c08.png", None); |
1982 | | trial("tests/pngsuite/f99n0g04.png", None); |
1983 | | trial("tests/pngsuite/g03n0g16.png", None); |
1984 | | trial("tests/pngsuite/g03n2c08.png", None); |
1985 | | trial("tests/pngsuite/g03n3p04.png", None); |
1986 | | trial("tests/pngsuite/g04n0g16.png", None); |
1987 | | trial("tests/pngsuite/g04n2c08.png", None); |
1988 | | trial("tests/pngsuite/g04n3p04.png", None); |
1989 | | trial("tests/pngsuite/g05n0g16.png", None); |
1990 | | trial("tests/pngsuite/g05n2c08.png", None); |
1991 | | trial("tests/pngsuite/g05n3p04.png", None); |
1992 | | trial("tests/pngsuite/g07n0g16.png", None); |
1993 | | trial("tests/pngsuite/g07n2c08.png", None); |
1994 | | trial("tests/pngsuite/g07n3p04.png", None); |
1995 | | trial("tests/pngsuite/g10n0g16.png", None); |
1996 | | trial("tests/pngsuite/g10n2c08.png", None); |
1997 | | trial("tests/pngsuite/g10n3p04.png", None); |
1998 | | trial("tests/pngsuite/g25n0g16.png", None); |
1999 | | trial("tests/pngsuite/g25n2c08.png", None); |
2000 | | trial("tests/pngsuite/g25n3p04.png", None); |
2001 | | trial("tests/pngsuite/oi1n0g16.png", None); |
2002 | | trial("tests/pngsuite/oi1n2c16.png", None); |
2003 | | trial("tests/pngsuite/oi2n0g16.png", None); |
2004 | | trial("tests/pngsuite/oi2n2c16.png", None); |
2005 | | trial("tests/pngsuite/oi4n0g16.png", None); |
2006 | | trial("tests/pngsuite/oi4n2c16.png", None); |
2007 | | trial("tests/pngsuite/oi9n0g16.png", None); |
2008 | | trial("tests/pngsuite/oi9n2c16.png", None); |
2009 | | trial("tests/pngsuite/PngSuite.png", None); |
2010 | | trial("tests/pngsuite/pp0n2c16.png", None); |
2011 | | trial("tests/pngsuite/pp0n6a08.png", None); |
2012 | | trial("tests/pngsuite/ps1n0g08.png", None); |
2013 | | trial("tests/pngsuite/ps1n2c16.png", None); |
2014 | | trial("tests/pngsuite/ps2n0g08.png", None); |
2015 | | trial("tests/pngsuite/ps2n2c16.png", None); |
2016 | | trial("tests/pngsuite/s01i3p01.png", None); |
2017 | | trial("tests/pngsuite/s01n3p01.png", None); |
2018 | | trial("tests/pngsuite/s02i3p01.png", None); |
2019 | | trial("tests/pngsuite/s02n3p01.png", None); |
2020 | | trial("tests/pngsuite/s03i3p01.png", None); |
2021 | | trial("tests/pngsuite/s03n3p01.png", None); |
2022 | | trial("tests/pngsuite/s04i3p01.png", None); |
2023 | | trial("tests/pngsuite/s04n3p01.png", None); |
2024 | | trial("tests/pngsuite/s05i3p02.png", None); |
2025 | | trial("tests/pngsuite/s05n3p02.png", None); |
2026 | | trial("tests/pngsuite/s06i3p02.png", None); |
2027 | | trial("tests/pngsuite/s06n3p02.png", None); |
2028 | | trial("tests/pngsuite/s07i3p02.png", None); |
2029 | | trial("tests/pngsuite/s07n3p02.png", None); |
2030 | | trial("tests/pngsuite/s08i3p02.png", None); |
2031 | | trial("tests/pngsuite/s08n3p02.png", None); |
2032 | | trial("tests/pngsuite/s09i3p02.png", None); |
2033 | | trial("tests/pngsuite/s09n3p02.png", None); |
2034 | | trial("tests/pngsuite/s32i3p04.png", None); |
2035 | | trial("tests/pngsuite/s32n3p04.png", None); |
2036 | | trial("tests/pngsuite/s33i3p04.png", None); |
2037 | | trial("tests/pngsuite/s33n3p04.png", None); |
2038 | | trial("tests/pngsuite/s34i3p04.png", None); |
2039 | | trial("tests/pngsuite/s34n3p04.png", None); |
2040 | | trial("tests/pngsuite/s35i3p04.png", None); |
2041 | | trial("tests/pngsuite/s35n3p04.png", None); |
2042 | | trial("tests/pngsuite/s36i3p04.png", None); |
2043 | | trial("tests/pngsuite/s36n3p04.png", None); |
2044 | | trial("tests/pngsuite/s37i3p04.png", None); |
2045 | | trial("tests/pngsuite/s37n3p04.png", None); |
2046 | | trial("tests/pngsuite/s38i3p04.png", None); |
2047 | | trial("tests/pngsuite/s38n3p04.png", None); |
2048 | | trial("tests/pngsuite/s39i3p04.png", None); |
2049 | | trial("tests/pngsuite/s39n3p04.png", None); |
2050 | | trial("tests/pngsuite/s40i3p04.png", None); |
2051 | | trial("tests/pngsuite/s40n3p04.png", None); |
2052 | | trial("tests/pngsuite/tbbn0g04.png", None); |
2053 | | trial("tests/pngsuite/tbbn2c16.png", None); |
2054 | | trial("tests/pngsuite/tbbn3p08.png", None); |
2055 | | trial("tests/pngsuite/tbgn2c16.png", None); |
2056 | | trial("tests/pngsuite/tbgn3p08.png", None); |
2057 | | trial("tests/pngsuite/tbrn2c08.png", None); |
2058 | | trial("tests/pngsuite/tbwn0g16.png", None); |
2059 | | trial("tests/pngsuite/tbwn3p08.png", None); |
2060 | | trial("tests/pngsuite/tbyn3p08.png", None); |
2061 | | trial("tests/pngsuite/tm3n3p02.png", None); |
2062 | | trial("tests/pngsuite/tp0n0g08.png", None); |
2063 | | trial("tests/pngsuite/tp0n2c08.png", None); |
2064 | | trial("tests/pngsuite/tp0n3p08.png", None); |
2065 | | trial("tests/pngsuite/tp1n3p08.png", None); |
2066 | | trial("tests/pngsuite/z00n2c08.png", None); |
2067 | | trial("tests/pngsuite/z03n2c08.png", None); |
2068 | | trial("tests/pngsuite/z06n2c08.png", None); |
2069 | | Ok(()) |
2070 | | } |
2071 | | |
2072 | | #[test] |
2073 | | fn image_source_sbit() { |
2074 | | fn trial(path: &str, expected: Option<Cow<[u8]>>) { |
2075 | | let decoder = crate::Decoder::new(File::open(path).unwrap()); |
2076 | | let reader = decoder.read_info().unwrap(); |
2077 | | let actual: Option<Cow<[u8]>> = reader.info().sbit.clone(); |
2078 | | assert!(actual == expected); |
2079 | | } |
2080 | | |
2081 | | trial("tests/sbit/g.png", Some(Cow::Owned(vec![5u8]))); |
2082 | | trial("tests/sbit/ga.png", Some(Cow::Owned(vec![5u8, 3u8]))); |
2083 | | trial( |
2084 | | "tests/sbit/indexed.png", |
2085 | | Some(Cow::Owned(vec![5u8, 6u8, 5u8])), |
2086 | | ); |
2087 | | trial("tests/sbit/rgb.png", Some(Cow::Owned(vec![5u8, 6u8, 5u8]))); |
2088 | | trial( |
2089 | | "tests/sbit/rgba.png", |
2090 | | Some(Cow::Owned(vec![5u8, 6u8, 5u8, 8u8])), |
2091 | | ); |
2092 | | } |
2093 | | |
2094 | | /// Test handling of a PNG file that contains *two* iCCP chunks. |
2095 | | /// This is a regression test for https://github.com/image-rs/image/issues/1825. |
2096 | | #[test] |
2097 | | fn test_two_iccp_chunks() { |
2098 | | // The test file has been taken from |
2099 | | // https://github.com/image-rs/image/issues/1825#issuecomment-1321798639, |
2100 | | // but the 2nd iCCP chunk has been altered manually (see the 2nd comment below for more |
2101 | | // details). |
2102 | | let decoder = crate::Decoder::new(File::open("tests/bugfixes/issue#1825.png").unwrap()); |
2103 | | let reader = decoder.read_info().unwrap(); |
2104 | | let icc_profile = reader.info().icc_profile.clone().unwrap().into_owned(); |
2105 | | |
2106 | | // Assert that the contents of the *first* iCCP chunk are returned. |
2107 | | // |
2108 | | // Note that the 2nd chunk in the test file has been manually altered to have a different |
2109 | | // content (`b"test iccp contents"`) which would have a different CRC (797351983). |
2110 | | assert_eq!(4070462061, crc32fast::hash(&icc_profile)); |
2111 | | } |
2112 | | |
2113 | | #[test] |
2114 | | fn test_iccp_roundtrip() { |
2115 | | let dummy_icc = b"I'm a profile"; |
2116 | | |
2117 | | let mut info = crate::Info::with_size(1, 1); |
2118 | | info.icc_profile = Some(dummy_icc.into()); |
2119 | | let mut encoded_image = Vec::new(); |
2120 | | let enc = crate::Encoder::with_info(&mut encoded_image, info).unwrap(); |
2121 | | let mut enc = enc.write_header().unwrap(); |
2122 | | enc.write_image_data(&[0]).unwrap(); |
2123 | | enc.finish().unwrap(); |
2124 | | |
2125 | | let dec = crate::Decoder::new(encoded_image.as_slice()); |
2126 | | let dec = dec.read_info().unwrap(); |
2127 | | assert_eq!(dummy_icc, &**dec.info().icc_profile.as_ref().unwrap()); |
2128 | | } |
2129 | | |
2130 | | #[test] |
2131 | | fn test_png_with_broken_iccp() { |
2132 | | let decoder = crate::Decoder::new(File::open("tests/iccp/broken_iccp.png").unwrap()); |
2133 | | assert!(decoder.read_info().is_ok()); |
2134 | | let mut decoder = crate::Decoder::new(File::open("tests/iccp/broken_iccp.png").unwrap()); |
2135 | | decoder.set_ignore_iccp_chunk(true); |
2136 | | assert!(decoder.read_info().is_ok()); |
2137 | | } |
2138 | | |
2139 | | /// Test handling of `mDCV` and `cLLI` chunks.` |
2140 | | #[test] |
2141 | | fn test_mdcv_and_clli_chunks() { |
2142 | | let decoder = crate::Decoder::new(File::open("tests/bugfixes/cicp_pq.png").unwrap()); |
2143 | | let reader = decoder.read_info().unwrap(); |
2144 | | let info = reader.info(); |
2145 | | |
2146 | | let cicp = info.coding_independent_code_points.unwrap(); |
2147 | | assert_eq!(cicp.color_primaries, 9); |
2148 | | assert_eq!(cicp.transfer_function, 16); |
2149 | | assert_eq!(cicp.matrix_coefficients, 0); |
2150 | | assert!(cicp.is_video_full_range_image); |
2151 | | |
2152 | | let mdcv = info.mastering_display_color_volume.unwrap(); |
2153 | | assert_relative_eq!(mdcv.chromaticities.red.0.into_value(), 0.680); |
2154 | | assert_relative_eq!(mdcv.chromaticities.red.1.into_value(), 0.320); |
2155 | | assert_relative_eq!(mdcv.chromaticities.green.0.into_value(), 0.265); |
2156 | | assert_relative_eq!(mdcv.chromaticities.green.1.into_value(), 0.690); |
2157 | | assert_relative_eq!(mdcv.chromaticities.blue.0.into_value(), 0.150); |
2158 | | assert_relative_eq!(mdcv.chromaticities.blue.1.into_value(), 0.060); |
2159 | | assert_relative_eq!(mdcv.chromaticities.white.0.into_value(), 0.3127); |
2160 | | assert_relative_eq!(mdcv.chromaticities.white.1.into_value(), 0.3290); |
2161 | | assert_relative_eq!(mdcv.min_luminance as f32 / 10_000.0, 0.01); |
2162 | | assert_relative_eq!(mdcv.max_luminance as f32 / 10_000.0, 5000.0); |
2163 | | |
2164 | | let clli = info.content_light_level.unwrap(); |
2165 | | assert_relative_eq!(clli.max_content_light_level as f32 / 10_000.0, 4000.0); |
2166 | | assert_relative_eq!(clli.max_frame_average_light_level as f32 / 10_000.0, 2627.0); |
2167 | | } |
2168 | | |
2169 | | /// Tests what happens then [`Reader.finish`] is called twice. |
2170 | | #[test] |
2171 | | fn test_finishing_twice() { |
2172 | | let mut png = Vec::new(); |
2173 | | write_noncompressed_png(&mut png, 16, 1024); |
2174 | | let decoder = Decoder::new(png.as_slice()); |
2175 | | let mut reader = decoder.read_info().unwrap(); |
2176 | | |
2177 | | // First call to `finish` - expecting success. |
2178 | | reader.finish().unwrap(); |
2179 | | |
2180 | | // Second call to `finish` - expecting an error. |
2181 | | let err = reader.finish().unwrap_err(); |
2182 | | assert!(matches!(&err, DecodingError::Parameter(_))); |
2183 | | assert_eq!("End of image has been reached", format!("{err}")); |
2184 | | } |
2185 | | |
2186 | | /// Writes an acTL chunk. |
2187 | | /// See https://wiki.mozilla.org/APNG_Specification#.60acTL.60:_The_Animation_Control_Chunk |
2188 | | fn write_actl(w: &mut impl Write, animation: &crate::AnimationControl) { |
2189 | | let mut data = Vec::new(); |
2190 | | data.write_u32::<byteorder::BigEndian>(animation.num_frames) |
2191 | | .unwrap(); |
2192 | | data.write_u32::<byteorder::BigEndian>(animation.num_plays) |
2193 | | .unwrap(); |
2194 | | write_chunk(w, b"acTL", &data); |
2195 | | } |
2196 | | |
2197 | | /// Writes an fcTL chunk. |
2198 | | /// See https://wiki.mozilla.org/APNG_Specification#.60fcTL.60:_The_Frame_Control_Chunk |
2199 | | fn write_fctl(w: &mut impl Write, frame: &crate::FrameControl) { |
2200 | | let mut data = Vec::new(); |
2201 | | data.write_u32::<byteorder::BigEndian>(frame.sequence_number) |
2202 | | .unwrap(); |
2203 | | data.write_u32::<byteorder::BigEndian>(frame.width).unwrap(); |
2204 | | data.write_u32::<byteorder::BigEndian>(frame.height) |
2205 | | .unwrap(); |
2206 | | data.write_u32::<byteorder::BigEndian>(frame.x_offset) |
2207 | | .unwrap(); |
2208 | | data.write_u32::<byteorder::BigEndian>(frame.y_offset) |
2209 | | .unwrap(); |
2210 | | data.write_u16::<byteorder::BigEndian>(frame.delay_num) |
2211 | | .unwrap(); |
2212 | | data.write_u16::<byteorder::BigEndian>(frame.delay_den) |
2213 | | .unwrap(); |
2214 | | data.write_u8(frame.dispose_op as u8).unwrap(); |
2215 | | data.write_u8(frame.blend_op as u8).unwrap(); |
2216 | | write_chunk(w, b"fcTL", &data); |
2217 | | } |
2218 | | |
2219 | | /// Writes an fdAT chunk. |
2220 | | /// See https://wiki.mozilla.org/APNG_Specification#.60fdAT.60:_The_Frame_Data_Chunk |
2221 | | fn write_fdat(w: &mut impl Write, sequence_number: u32, image_data: &[u8]) { |
2222 | | let mut data = Vec::new(); |
2223 | | data.write_u32::<byteorder::BigEndian>(sequence_number) |
2224 | | .unwrap(); |
2225 | | data.write_all(image_data).unwrap(); |
2226 | | write_chunk(w, b"fdAT", &data); |
2227 | | } |
2228 | | |
2229 | | /// Writes PNG signature and chunks that can precede an fdAT chunk that is expected |
2230 | | /// to have |
2231 | | /// - `sequence_number` set to 0 |
2232 | | /// - image data with rgba8 pixels in a `width` by `width` image |
2233 | | fn write_fdat_prefix(w: &mut impl Write, num_frames: u32, width: u32) { |
2234 | | write_png_sig(w); |
2235 | | write_rgba8_ihdr_with_width(w, width); |
2236 | | write_actl( |
2237 | | w, |
2238 | | &crate::AnimationControl { |
2239 | | num_frames, |
2240 | | num_plays: 0, |
2241 | | }, |
2242 | | ); |
2243 | | |
2244 | | let mut fctl = crate::FrameControl { |
2245 | | width, |
2246 | | height: width, |
2247 | | ..Default::default() |
2248 | | }; |
2249 | | write_fctl(w, &fctl); |
2250 | | write_rgba8_idats(w, width, 0x7fffffff); |
2251 | | |
2252 | | fctl.sequence_number += 1; |
2253 | | write_fctl(w, &fctl); |
2254 | | } |
2255 | | |
2256 | | #[test] |
2257 | | fn test_fdat_chunk_payload_length_0() { |
2258 | | let mut png = Vec::new(); |
2259 | | write_fdat_prefix(&mut png, 2, 8); |
2260 | | write_chunk(&mut png, b"fdAT", &[]); |
2261 | | |
2262 | | let decoder = Decoder::new(png.as_slice()); |
2263 | | let mut reader = decoder.read_info().unwrap(); |
2264 | | let mut buf = vec![0; reader.output_buffer_size()]; |
2265 | | reader.next_frame(&mut buf).unwrap(); |
2266 | | |
2267 | | // 0-length fdAT should result in an error. |
2268 | | let err = reader.next_frame(&mut buf).unwrap_err(); |
2269 | | assert!(matches!(&err, DecodingError::Format(_))); |
2270 | | assert_eq!("fdAT chunk shorter than 4 bytes", format!("{err}")); |
2271 | | |
2272 | | // Calling `next_frame` again should return an error. Same error as above would be nice, |
2273 | | // but it is probably unnecessary and infeasible (`DecodingError` can't derive `Clone` |
2274 | | // because `std::io::Error` doesn't implement `Clone`).. But it definitely shouldn't enter |
2275 | | // an infinite loop. |
2276 | | let err2 = reader.next_frame(&mut buf).unwrap_err(); |
2277 | | assert!(matches!(&err2, DecodingError::Parameter(_))); |
2278 | | assert_eq!( |
2279 | | "A fatal decoding error has been encounted earlier", |
2280 | | format!("{err2}") |
2281 | | ); |
2282 | | } |
2283 | | |
2284 | | #[test] |
2285 | | fn test_fdat_chunk_payload_length_3() { |
2286 | | let mut png = Vec::new(); |
2287 | | write_fdat_prefix(&mut png, 2, 8); |
2288 | | write_chunk(&mut png, b"fdAT", &[1, 0, 0]); |
2289 | | |
2290 | | let decoder = Decoder::new(png.as_slice()); |
2291 | | let mut reader = decoder.read_info().unwrap(); |
2292 | | let mut buf = vec![0; reader.output_buffer_size()]; |
2293 | | reader.next_frame(&mut buf).unwrap(); |
2294 | | |
2295 | | // 3-bytes-long fdAT should result in an error. |
2296 | | let err = reader.next_frame(&mut buf).unwrap_err(); |
2297 | | assert!(matches!(&err, DecodingError::Format(_))); |
2298 | | assert_eq!("fdAT chunk shorter than 4 bytes", format!("{err}")); |
2299 | | } |
2300 | | |
2301 | | #[test] |
2302 | | fn test_frame_split_across_two_fdat_chunks() { |
2303 | | // Generate test data where the 2nd animation frame is split across 2 fdAT chunks. |
2304 | | // |
2305 | | // This is similar to the example given in |
2306 | | // https://wiki.mozilla.org/APNG_Specification#Chunk_Sequence_Numbers: |
2307 | | // |
2308 | | // ``` |
2309 | | // Sequence number Chunk |
2310 | | // (none) `acTL` |
2311 | | // 0 `fcTL` first frame |
2312 | | // (none) `IDAT` first frame / default image |
2313 | | // 1 `fcTL` second frame |
2314 | | // 2 first `fdAT` for second frame |
2315 | | // 3 second `fdAT` for second frame |
2316 | | // ``` |
2317 | | let png = { |
2318 | | let mut png = Vec::new(); |
2319 | | write_fdat_prefix(&mut png, 2, 8); |
2320 | | let image_data = generate_rgba8_with_width_and_height(8, 8); |
2321 | | write_fdat(&mut png, 2, &image_data[..30]); |
2322 | | write_fdat(&mut png, 3, &image_data[30..]); |
2323 | | write_iend(&mut png); |
2324 | | png |
2325 | | }; |
2326 | | |
2327 | | // Start decoding. |
2328 | | let decoder = Decoder::new(png.as_slice()); |
2329 | | let mut reader = decoder.read_info().unwrap(); |
2330 | | let mut buf = vec![0; reader.output_buffer_size()]; |
2331 | | let Some(animation_control) = reader.info().animation_control else { |
2332 | | panic!("No acTL"); |
2333 | | }; |
2334 | | assert_eq!(animation_control.num_frames, 2); |
2335 | | |
2336 | | // Process the 1st animation frame. |
2337 | | let first_frame: Vec<u8>; |
2338 | | { |
2339 | | reader.next_frame(&mut buf).unwrap(); |
2340 | | first_frame = buf.clone(); |
2341 | | |
2342 | | // Note that the doc comment of `Reader::next_frame` says that "[...] |
2343 | | // can be checked afterwards by calling `info` **after** a successful call and |
2344 | | // inspecting the `frame_control` data.". (Note the **emphasis** on "after".) |
2345 | | let Some(frame_control) = reader.info().frame_control else { |
2346 | | panic!("No fcTL (1st frame)"); |
2347 | | }; |
2348 | | // The sequence number is taken from the `fcTL` chunk that comes before the `IDAT` |
2349 | | // chunk. |
2350 | | assert_eq!(frame_control.sequence_number, 0); |
2351 | | } |
2352 | | |
2353 | | // Process the 2nd animation frame. |
2354 | | let second_frame: Vec<u8>; |
2355 | | { |
2356 | | reader.next_frame(&mut buf).unwrap(); |
2357 | | second_frame = buf.clone(); |
2358 | | |
2359 | | // Same as above - updated `frame_control` is available *after* the `next_frame` call. |
2360 | | let Some(frame_control) = reader.info().frame_control else { |
2361 | | panic!("No fcTL (2nd frame)"); |
2362 | | }; |
2363 | | // The sequence number is taken from the `fcTL` chunk that comes before the two `fdAT` |
2364 | | // chunks. Note that sequence numbers inside `fdAT` chunks are not publicly exposed |
2365 | | // (but they are still checked when decoding to verify that they are sequential). |
2366 | | assert_eq!(frame_control.sequence_number, 1); |
2367 | | } |
2368 | | |
2369 | | assert_eq!(first_frame, second_frame); |
2370 | | } |
2371 | | |
2372 | | #[test] |
2373 | | fn test_idat_bigger_than_image_size_from_ihdr() { |
2374 | | let png = { |
2375 | | let mut png = Vec::new(); |
2376 | | write_png_sig(&mut png); |
2377 | | write_rgba8_ihdr_with_width(&mut png, 8); |
2378 | | |
2379 | | // Here we want to test an invalid image where the `IDAT` chunk contains more data |
2380 | | // (data for 8x256 image) than declared in the `IHDR` chunk (which only describes an |
2381 | | // 8x8 image). |
2382 | | write_chunk( |
2383 | | &mut png, |
2384 | | b"IDAT", |
2385 | | &generate_rgba8_with_width_and_height(8, 256), |
2386 | | ); |
2387 | | |
2388 | | write_iend(&mut png); |
2389 | | png |
2390 | | }; |
2391 | | let decoder = Decoder::new(png.as_slice()); |
2392 | | let mut reader = decoder.read_info().unwrap(); |
2393 | | let mut buf = vec![0; reader.output_buffer_size()]; |
2394 | | |
2395 | | // TODO: Should this return an error instead? For now let's just have test assertions for |
2396 | | // the current behavior. |
2397 | | reader.next_frame(&mut buf).unwrap(); |
2398 | | assert_eq!(3093270825, crc32fast::hash(&buf)); |
2399 | | } |
2400 | | |
2401 | | #[test] |
2402 | | fn test_only_idat_chunk_in_input_stream() { |
2403 | | let png = { |
2404 | | let mut png = Vec::new(); |
2405 | | write_png_sig(&mut png); |
2406 | | write_chunk(&mut png, b"IDAT", &[]); |
2407 | | png |
2408 | | }; |
2409 | | let decoder = Decoder::new(png.as_slice()); |
2410 | | let Err(err) = decoder.read_info() else { |
2411 | | panic!("Expected an error") |
2412 | | }; |
2413 | | assert!(matches!(&err, DecodingError::Format(_))); |
2414 | | assert_eq!( |
2415 | | "ChunkType { type: IDAT, \ |
2416 | | critical: true, \ |
2417 | | private: false, \ |
2418 | | reserved: false, \ |
2419 | | safecopy: false \ |
2420 | | } chunk appeared before IHDR chunk", |
2421 | | format!("{err}"), |
2422 | | ); |
2423 | | } |
2424 | | |
2425 | | /// `StreamingInput` can be used by tests to simulate a streaming input |
2426 | | /// (e.g. a slow http response, where all bytes are not immediately available). |
2427 | | #[derive(Clone)] |
2428 | | struct StreamingInput(Rc<RefCell<StreamingInputState>>); |
2429 | | |
2430 | | struct StreamingInputState { |
2431 | | full_input: Vec<u8>, |
2432 | | current_pos: usize, |
2433 | | available_len: usize, |
2434 | | } |
2435 | | |
2436 | | impl StreamingInput { |
2437 | | fn new(full_input: Vec<u8>) -> Self { |
2438 | | Self(Rc::new(RefCell::new(StreamingInputState { |
2439 | | full_input, |
2440 | | current_pos: 0, |
2441 | | available_len: 0, |
2442 | | }))) |
2443 | | } |
2444 | | |
2445 | | fn with_noncompressed_png(width: u32, idat_size: usize) -> Self { |
2446 | | let mut png = Vec::new(); |
2447 | | write_noncompressed_png(&mut png, width, idat_size); |
2448 | | Self::new(png) |
2449 | | } |
2450 | | |
2451 | | fn expose_next_byte(&self) { |
2452 | | let mut state = self.0.borrow_mut(); |
2453 | | assert!(state.available_len < state.full_input.len()); |
2454 | | state.available_len += 1; |
2455 | | } |
2456 | | |
2457 | | fn stream_input_until_reader_is_available(&self) -> Reader<StreamingInput> { |
2458 | | loop { |
2459 | | self.0.borrow_mut().current_pos = 0; |
2460 | | match Decoder::new(self.clone()).read_info() { |
2461 | | Ok(reader) => { |
2462 | | break reader; |
2463 | | } |
2464 | | Err(DecodingError::IoError(e)) if e.kind() == ErrorKind::UnexpectedEof => { |
2465 | | self.expose_next_byte(); |
2466 | | } |
2467 | | _ => panic!("Unexpected error"), |
2468 | | } |
2469 | | } |
2470 | | } |
2471 | | |
2472 | | fn decode_full_input<F, R>(&self, f: F) -> R |
2473 | | where |
2474 | | F: FnOnce(Reader<&[u8]>) -> R, |
2475 | | { |
2476 | | let state = self.0.borrow(); |
2477 | | let decoder = Decoder::new(state.full_input.as_slice()); |
2478 | | f(decoder.read_info().unwrap()) |
2479 | | } |
2480 | | } |
2481 | | |
2482 | | impl Read for StreamingInput { |
2483 | | fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> { |
2484 | | let mut state = self.0.borrow_mut(); |
2485 | | let mut available_bytes = &state.full_input[state.current_pos..state.available_len]; |
2486 | | let number_of_read_bytes = available_bytes.read(buf)?; |
2487 | | state.current_pos += number_of_read_bytes; |
2488 | | assert!(state.current_pos <= state.available_len); |
2489 | | Ok(number_of_read_bytes) |
2490 | | } |
2491 | | } |
2492 | | |
2493 | | /// Test resuming/retrying `Reader.next_frame` after `UnexpectedEof`. |
2494 | | #[test] |
2495 | | fn test_streaming_input_and_decoding_via_next_frame() { |
2496 | | const WIDTH: u32 = 16; |
2497 | | const IDAT_SIZE: usize = 512; |
2498 | | let streaming_input = StreamingInput::with_noncompressed_png(WIDTH, IDAT_SIZE); |
2499 | | |
2500 | | let (whole_output_info, decoded_from_whole_input) = |
2501 | | streaming_input.decode_full_input(|mut r| { |
2502 | | let mut buf = vec![0; r.output_buffer_size()]; |
2503 | | let output_info = r.next_frame(&mut buf).unwrap(); |
2504 | | (output_info, buf) |
2505 | | }); |
2506 | | |
2507 | | let mut png_reader = streaming_input.stream_input_until_reader_is_available(); |
2508 | | let mut decoded_from_streaming_input = vec![0; png_reader.output_buffer_size()]; |
2509 | | let streaming_output_info = loop { |
2510 | | match png_reader.next_frame(decoded_from_streaming_input.as_mut_slice()) { |
2511 | | Ok(output_info) => break output_info, |
2512 | | Err(DecodingError::IoError(e)) if e.kind() == ErrorKind::UnexpectedEof => { |
2513 | | streaming_input.expose_next_byte() |
2514 | | } |
2515 | | e => panic!("Unexpected error: {:?}", e), |
2516 | | } |
2517 | | }; |
2518 | | assert_eq!(whole_output_info, streaming_output_info); |
2519 | | assert_eq!( |
2520 | | crc32fast::hash(&decoded_from_whole_input), |
2521 | | crc32fast::hash(&decoded_from_streaming_input) |
2522 | | ); |
2523 | | } |
2524 | | |
2525 | | /// Test resuming/retrying `Reader.next_row` after `UnexpectedEof`. |
2526 | | #[test] |
2527 | | fn test_streaming_input_and_decoding_via_next_row() { |
2528 | | const WIDTH: u32 = 16; |
2529 | | const IDAT_SIZE: usize = 512; |
2530 | | let streaming_input = StreamingInput::with_noncompressed_png(WIDTH, IDAT_SIZE); |
2531 | | |
2532 | | let decoded_from_whole_input = streaming_input.decode_full_input(|mut r| { |
2533 | | let mut buf = vec![0; r.output_buffer_size()]; |
2534 | | r.next_frame(&mut buf).unwrap(); |
2535 | | buf |
2536 | | }); |
2537 | | |
2538 | | let mut png_reader = streaming_input.stream_input_until_reader_is_available(); |
2539 | | let mut decoded_from_streaming_input = Vec::new(); |
2540 | | loop { |
2541 | | match png_reader.next_row() { |
2542 | | Ok(None) => break, |
2543 | | Ok(Some(row)) => decoded_from_streaming_input.extend_from_slice(row.data()), |
2544 | | Err(DecodingError::IoError(e)) if e.kind() == ErrorKind::UnexpectedEof => { |
2545 | | streaming_input.expose_next_byte() |
2546 | | } |
2547 | | e => panic!("Unexpected error: {:?}", e), |
2548 | | } |
2549 | | } |
2550 | | assert_eq!( |
2551 | | crc32fast::hash(&decoded_from_whole_input), |
2552 | | crc32fast::hash(&decoded_from_streaming_input) |
2553 | | ); |
2554 | | } |
2555 | | |
2556 | | /// Test resuming/retrying `Decoder.read_header_info` after `UnexpectedEof`. |
2557 | | #[test] |
2558 | | fn test_streaming_input_and_reading_header_info() { |
2559 | | const WIDTH: u32 = 16; |
2560 | | const IDAT_SIZE: usize = 512; |
2561 | | let streaming_input = StreamingInput::with_noncompressed_png(WIDTH, IDAT_SIZE); |
2562 | | |
2563 | | let info_from_whole_input = streaming_input.decode_full_input(|r| r.info().clone()); |
2564 | | |
2565 | | let mut decoder = Decoder::new(streaming_input.clone()); |
2566 | | let info_from_streaming_input = loop { |
2567 | | match decoder.read_header_info() { |
2568 | | Ok(info) => break info.clone(), |
2569 | | Err(DecodingError::IoError(e)) if e.kind() == ErrorKind::UnexpectedEof => { |
2570 | | streaming_input.expose_next_byte() |
2571 | | } |
2572 | | e => panic!("Unexpected error: {:?}", e), |
2573 | | } |
2574 | | }; |
2575 | | |
2576 | | assert_eq!(info_from_whole_input.width, info_from_streaming_input.width); |
2577 | | assert_eq!( |
2578 | | info_from_whole_input.height, |
2579 | | info_from_streaming_input.height |
2580 | | ); |
2581 | | assert_eq!( |
2582 | | info_from_whole_input.bit_depth, |
2583 | | info_from_streaming_input.bit_depth |
2584 | | ); |
2585 | | assert_eq!( |
2586 | | info_from_whole_input.color_type, |
2587 | | info_from_streaming_input.color_type |
2588 | | ); |
2589 | | assert_eq!( |
2590 | | info_from_whole_input.interlaced, |
2591 | | info_from_streaming_input.interlaced |
2592 | | ); |
2593 | | } |
2594 | | |
2595 | | /// Creates a ready-to-test [`Reader`] which decodes a PNG that contains: |
2596 | | /// IHDR, IDAT, IEND. |
2597 | | fn create_reader_of_ihdr_idat() -> Reader<VecDeque<u8>> { |
2598 | | let mut png = VecDeque::new(); |
2599 | | write_noncompressed_png(&mut png, /* width = */ 16, /* idat_size = */ 1024); |
2600 | | Decoder::new(png).read_info().unwrap() |
2601 | | } |
2602 | | |
2603 | | /// Creates a ready-to-test [`Reader`] which decodes an animated PNG that contains: |
2604 | | /// IHDR, acTL, fcTL, IDAT, fcTL, fdAT, IEND. (i.e. IDAT is part of the animation) |
2605 | | fn create_reader_of_ihdr_actl_fctl_idat_fctl_fdat() -> Reader<VecDeque<u8>> { |
2606 | | let idat_width = 16; |
2607 | | let mut fctl = crate::FrameControl { |
2608 | | width: idat_width, |
2609 | | height: idat_width, // same height and width |
2610 | | ..Default::default() |
2611 | | }; |
2612 | | |
2613 | | let mut png = VecDeque::new(); |
2614 | | write_png_sig(&mut png); |
2615 | | write_rgba8_ihdr_with_width(&mut png, idat_width); |
2616 | | write_actl( |
2617 | | &mut png, |
2618 | | &crate::AnimationControl { |
2619 | | num_frames: 2, |
2620 | | num_plays: 0, |
2621 | | }, |
2622 | | ); |
2623 | | fctl.sequence_number = 0; |
2624 | | write_fctl(&mut png, &fctl); |
2625 | | // Using `fctl.height + 1` means that the `IDAT` will have "left-over" data after |
2626 | | // processing. This helps to verify that `Reader.read_until_image_data` discards the |
2627 | | // left-over data when resetting `UnfilteredRowsBuffer`. |
2628 | | let idat_data = generate_rgba8_with_width_and_height(fctl.width, fctl.height + 1); |
2629 | | write_chunk(&mut png, b"IDAT", &idat_data); |
2630 | | |
2631 | | let fdat_width = 10; |
2632 | | fctl.sequence_number = 1; |
2633 | | // Using different width in `IDAT` and `fDAT` frames helps to catch problems that |
2634 | | // may arise when `Reader.read_until_image_data` doesn't properly reset |
2635 | | // `UnfilteredRowsBuffer`. |
2636 | | fctl.width = fdat_width; |
2637 | | write_fctl(&mut png, &fctl); |
2638 | | let fdat_data = generate_rgba8_with_width_and_height(fctl.width, fctl.height); |
2639 | | write_fdat(&mut png, 2, &fdat_data); |
2640 | | write_iend(&mut png); |
2641 | | |
2642 | | Decoder::new(png).read_info().unwrap() |
2643 | | } |
2644 | | |
2645 | | /// Creates a ready-to-test [`Reader`] which decodes an animated PNG that contains: IHDR, acTL, |
2646 | | /// IDAT, fcTL, fdAT, fcTL, fdAT, IEND. (i.e. IDAT is *not* part of the animation) |
2647 | | fn create_reader_of_ihdr_actl_idat_fctl_fdat_fctl_fdat() -> Reader<VecDeque<u8>> { |
2648 | | let width = 16; |
2649 | | let frame_data = generate_rgba8_with_width_and_height(width, width); |
2650 | | let mut fctl = crate::FrameControl { |
2651 | | width, |
2652 | | height: width, |
2653 | | ..Default::default() |
2654 | | }; |
2655 | | |
2656 | | let mut png = VecDeque::new(); |
2657 | | write_png_sig(&mut png); |
2658 | | write_rgba8_ihdr_with_width(&mut png, width); |
2659 | | write_actl( |
2660 | | &mut png, |
2661 | | &crate::AnimationControl { |
2662 | | num_frames: 2, |
2663 | | num_plays: 0, |
2664 | | }, |
2665 | | ); |
2666 | | write_chunk(&mut png, b"IDAT", &frame_data); |
2667 | | fctl.sequence_number = 0; |
2668 | | write_fctl(&mut png, &fctl); |
2669 | | write_fdat(&mut png, 1, &frame_data); |
2670 | | fctl.sequence_number = 2; |
2671 | | write_fctl(&mut png, &fctl); |
2672 | | write_fdat(&mut png, 3, &frame_data); |
2673 | | write_iend(&mut png); |
2674 | | |
2675 | | Decoder::new(png).read_info().unwrap() |
2676 | | } |
2677 | | |
2678 | | fn get_fctl_sequence_number(reader: &Reader<impl Read>) -> u32 { |
2679 | | reader |
2680 | | .info() |
2681 | | .frame_control |
2682 | | .as_ref() |
2683 | | .unwrap() |
2684 | | .sequence_number |
2685 | | } |
2686 | | |
2687 | | /// Tests that [`Reader.next_frame`] will report a `PolledAfterEndOfImage` error when called |
2688 | | /// after already decoding a single frame in a non-animated PNG. |
2689 | | #[test] |
2690 | | fn test_next_frame_polling_after_end_non_animated() { |
2691 | | let mut reader = create_reader_of_ihdr_idat(); |
2692 | | let mut buf = vec![0; reader.output_buffer_size()]; |
2693 | | reader |
2694 | | .next_frame(&mut buf) |
2695 | | .expect("Expecting no error for IDAT frame"); |
2696 | | |
2697 | | let err = reader |
2698 | | .next_frame(&mut buf) |
2699 | | .expect_err("Main test - expecting error"); |
2700 | | assert!( |
2701 | | matches!(&err, DecodingError::Parameter(_)), |
2702 | | "Unexpected kind of error: {:?}", |
2703 | | &err, |
2704 | | ); |
2705 | | } |
2706 | | |
2707 | | /// Tests that [`Reader.next_frame_info`] will report a `PolledAfterEndOfImage` error when |
2708 | | /// called when decoding a PNG that only contains a single frame. |
2709 | | #[test] |
2710 | | fn test_next_frame_info_polling_after_end_non_animated() { |
2711 | | let mut reader = create_reader_of_ihdr_idat(); |
2712 | | |
2713 | | let err = reader |
2714 | | .next_frame_info() |
2715 | | .expect_err("Main test - expecting error"); |
2716 | | assert!( |
2717 | | matches!(&err, DecodingError::Parameter(_)), |
2718 | | "Unexpected kind of error: {:?}", |
2719 | | &err, |
2720 | | ); |
2721 | | } |
2722 | | |
2723 | | /// Tests that [`Reader.next_frame`] will report a `PolledAfterEndOfImage` error when called |
2724 | | /// after already decoding a single frame in an animated PNG where IDAT is part of the |
2725 | | /// animation. |
2726 | | #[test] |
2727 | | fn test_next_frame_polling_after_end_idat_part_of_animation() { |
2728 | | let mut reader = create_reader_of_ihdr_actl_fctl_idat_fctl_fdat(); |
2729 | | let mut buf = vec![0; reader.output_buffer_size()]; |
2730 | | |
2731 | | assert_eq!(get_fctl_sequence_number(&reader), 0); |
2732 | | reader |
2733 | | .next_frame(&mut buf) |
2734 | | .expect("Expecting no error for IDAT frame"); |
2735 | | |
2736 | | // `next_frame` doesn't advance to the next `fcTL`. |
2737 | | assert_eq!(get_fctl_sequence_number(&reader), 0); |
2738 | | |
2739 | | reader |
2740 | | .next_frame(&mut buf) |
2741 | | .expect("Expecting no error for fdAT frame"); |
2742 | | assert_eq!(get_fctl_sequence_number(&reader), 1); |
2743 | | |
2744 | | let err = reader |
2745 | | .next_frame(&mut buf) |
2746 | | .expect_err("Main test - expecting error"); |
2747 | | assert!( |
2748 | | matches!(&err, DecodingError::Parameter(_)), |
2749 | | "Unexpected kind of error: {:?}", |
2750 | | &err, |
2751 | | ); |
2752 | | } |
2753 | | |
2754 | | /// Tests that [`Reader.next_frame`] will report a `PolledAfterEndOfImage` error when called |
2755 | | /// after already decoding a single frame in an animated PNG where IDAT is *not* part of the |
2756 | | /// animation. |
2757 | | #[test] |
2758 | | fn test_next_frame_polling_after_end_idat_not_part_of_animation() { |
2759 | | let mut reader = create_reader_of_ihdr_actl_idat_fctl_fdat_fctl_fdat(); |
2760 | | let mut buf = vec![0; reader.output_buffer_size()]; |
2761 | | |
2762 | | assert!(reader.info().frame_control.is_none()); |
2763 | | reader |
2764 | | .next_frame(&mut buf) |
2765 | | .expect("Expecting no error for IDAT frame"); |
2766 | | |
2767 | | // `next_frame` doesn't advance to the next `fcTL`. |
2768 | | assert!(reader.info().frame_control.is_none()); |
2769 | | |
2770 | | reader |
2771 | | .next_frame(&mut buf) |
2772 | | .expect("Expecting no error for 1st fdAT frame"); |
2773 | | assert_eq!(get_fctl_sequence_number(&reader), 0); |
2774 | | |
2775 | | reader |
2776 | | .next_frame(&mut buf) |
2777 | | .expect("Expecting no error for 2nd fdAT frame"); |
2778 | | assert_eq!(get_fctl_sequence_number(&reader), 2); |
2779 | | |
2780 | | let err = reader |
2781 | | .next_frame(&mut buf) |
2782 | | .expect_err("Main test - expecting error"); |
2783 | | assert!( |
2784 | | matches!(&err, DecodingError::Parameter(_)), |
2785 | | "Unexpected kind of error: {:?}", |
2786 | | &err, |
2787 | | ); |
2788 | | } |
2789 | | |
2790 | | /// Tests that after decoding a whole frame via [`Reader.next_row`] the call to |
2791 | | /// [`Reader.next_frame`] will decode the **next** frame. |
2792 | | #[test] |
2793 | | fn test_row_by_row_then_next_frame() { |
2794 | | let mut reader = create_reader_of_ihdr_actl_fctl_idat_fctl_fdat(); |
2795 | | let mut buf = vec![0; reader.output_buffer_size()]; |
2796 | | |
2797 | | assert_eq!(get_fctl_sequence_number(&reader), 0); |
2798 | | while let Some(_) = reader.next_row().unwrap() {} |
2799 | | assert_eq!(get_fctl_sequence_number(&reader), 0); |
2800 | | |
2801 | | buf.fill(0x0f); |
2802 | | reader |
2803 | | .next_frame(&mut buf) |
2804 | | .expect("Expecting no error from next_frame call"); |
2805 | | |
2806 | | // Verify if we have read the next `fcTL` chunk + repopulated `buf`: |
2807 | | assert_eq!(get_fctl_sequence_number(&reader), 1); |
2808 | | assert!(buf.iter().any(|byte| *byte != 0x0f)); |
2809 | | } |
2810 | | |
2811 | | /// Tests that after decoding a whole frame via [`Reader.next_row`] it is possible |
2812 | | /// to use [`Reader.next_row`] to decode the next frame (by using the `next_frame_info` API to |
2813 | | /// advance to the next frame when `next_row` returns `None`). |
2814 | | #[test] |
2815 | | fn test_row_by_row_of_two_frames() { |
2816 | | let mut reader = create_reader_of_ihdr_actl_fctl_idat_fctl_fdat(); |
2817 | | |
2818 | | let mut rows_of_frame1 = 0; |
2819 | | assert_eq!(get_fctl_sequence_number(&reader), 0); |
2820 | | while let Some(_) = reader.next_row().unwrap() { |
2821 | | rows_of_frame1 += 1; |
2822 | | } |
2823 | | assert_eq!(rows_of_frame1, 16); |
2824 | | assert_eq!(get_fctl_sequence_number(&reader), 0); |
2825 | | |
2826 | | let mut rows_of_frame2 = 0; |
2827 | | assert_eq!(reader.next_frame_info().unwrap().sequence_number, 1); |
2828 | | assert_eq!(get_fctl_sequence_number(&reader), 1); |
2829 | | while let Some(_) = reader.next_row().unwrap() { |
2830 | | rows_of_frame2 += 1; |
2831 | | } |
2832 | | assert_eq!(rows_of_frame2, 16); |
2833 | | assert_eq!(get_fctl_sequence_number(&reader), 1); |
2834 | | |
2835 | | let err = reader |
2836 | | .next_frame_info() |
2837 | | .expect_err("No more frames - expecting error"); |
2838 | | assert!( |
2839 | | matches!(&err, DecodingError::Parameter(_)), |
2840 | | "Unexpected kind of error: {:?}", |
2841 | | &err, |
2842 | | ); |
2843 | | } |
2844 | | |
2845 | | /// This test is similar to `test_next_frame_polling_after_end_idat_part_of_animation`, but it |
2846 | | /// uses `next_frame_info` calls to read to the next `fcTL` earlier - before the next call to |
2847 | | /// `next_frame` (knowing `fcTL` before calling `next_frame` may be helpful to determine the |
2848 | | /// size of the output buffer and/or to prepare the buffer based on the `DisposeOp` of the |
2849 | | /// previous frames). |
2850 | | #[test] |
2851 | | fn test_next_frame_info_after_next_frame() { |
2852 | | let mut reader = create_reader_of_ihdr_actl_fctl_idat_fctl_fdat(); |
2853 | | let mut buf = vec![0; reader.output_buffer_size()]; |
2854 | | |
2855 | | assert_eq!(get_fctl_sequence_number(&reader), 0); |
2856 | | reader |
2857 | | .next_frame(&mut buf) |
2858 | | .expect("Expecting no error for IDAT frame"); |
2859 | | |
2860 | | // `next_frame` doesn't advance to the next `fcTL`. |
2861 | | assert_eq!(get_fctl_sequence_number(&reader), 0); |
2862 | | |
2863 | | // But `next_frame_info` can be used to go to the next `fcTL`. |
2864 | | assert_eq!(reader.next_frame_info().unwrap().sequence_number, 1); |
2865 | | assert_eq!(get_fctl_sequence_number(&reader), 1); |
2866 | | |
2867 | | reader |
2868 | | .next_frame(&mut buf) |
2869 | | .expect("Expecting no error for fdAT frame"); |
2870 | | assert_eq!(get_fctl_sequence_number(&reader), 1); |
2871 | | |
2872 | | let err = reader |
2873 | | .next_frame_info() |
2874 | | .expect_err("Main test - expecting error"); |
2875 | | assert!( |
2876 | | matches!(&err, DecodingError::Parameter(_)), |
2877 | | "Unexpected kind of error: {:?}", |
2878 | | &err, |
2879 | | ); |
2880 | | } |
2881 | | |
2882 | | /// This test is similar to `test_next_frame_polling_after_end_idat_not_part_of_animation`, but |
2883 | | /// it uses `next_frame_info` to skip the `IDAT` frame entirely + to move between frames. |
2884 | | #[test] |
2885 | | fn test_next_frame_info_to_skip_first_frame() { |
2886 | | let mut reader = create_reader_of_ihdr_actl_idat_fctl_fdat_fctl_fdat(); |
2887 | | let mut buf = vec![0; reader.output_buffer_size()]; |
2888 | | |
2889 | | // First (IDAT) frame doesn't have frame control info, which means |
2890 | | // that it is not part of the animation. |
2891 | | assert!(reader.info().frame_control.is_none()); |
2892 | | |
2893 | | // `next_frame_info` can be used to skip the IDAT frame (without first having to separately |
2894 | | // discard the image data - e.g. by also calling `next_frame` first). |
2895 | | assert_eq!(reader.next_frame_info().unwrap().sequence_number, 0); |
2896 | | assert_eq!(get_fctl_sequence_number(&reader), 0); |
2897 | | reader |
2898 | | .next_frame(&mut buf) |
2899 | | .expect("Expecting no error for 1st fdAT frame"); |
2900 | | assert_eq!(get_fctl_sequence_number(&reader), 0); |
2901 | | |
2902 | | // Get the `fcTL` for the 2nd frame. |
2903 | | assert_eq!(reader.next_frame_info().unwrap().sequence_number, 2); |
2904 | | reader |
2905 | | .next_frame(&mut buf) |
2906 | | .expect("Expecting no error for 2nd fdAT frame"); |
2907 | | assert_eq!(get_fctl_sequence_number(&reader), 2); |
2908 | | |
2909 | | let err = reader |
2910 | | .next_frame_info() |
2911 | | .expect_err("Main test - expecting error"); |
2912 | | assert!( |
2913 | | matches!(&err, DecodingError::Parameter(_)), |
2914 | | "Unexpected kind of error: {:?}", |
2915 | | &err, |
2916 | | ); |
2917 | | } |
2918 | | } |