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