/src/image-png/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 | 1.99M | fn new_u32(kind: U32ValueKind) -> Self { |
74 | 1.99M | Self::U32 { |
75 | 1.99M | kind, |
76 | 1.99M | bytes: [0; 4], |
77 | 1.99M | accumulated_count: 0, |
78 | 1.99M | } |
79 | 1.99M | } |
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 had the wrong number of bytes. |
274 | | ChunkLengthWrong { |
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 | ChunkLengthWrong { kind } => { |
419 | 0 | write!(fmt, "Chunk length wrong: {:?}", 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 | 125 | fn from(err: io::Error) -> DecodingError { |
431 | 125 | DecodingError::IoError(err) |
432 | 125 | } |
433 | | } |
434 | | |
435 | | impl From<FormatError> for DecodingError { |
436 | 822 | fn from(err: FormatError) -> DecodingError { |
437 | 822 | DecodingError::Format(err) |
438 | 822 | } |
439 | | } |
440 | | |
441 | | impl From<FormatErrorInner> for FormatError { |
442 | 318M | fn from(inner: FormatErrorInner) -> Self { |
443 | 318M | FormatError { inner } |
444 | 318M | } |
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 | 27.7k | fn from(tbe: TextDecodingError) -> Self { |
458 | 27.7k | DecodingError::Format(FormatError { |
459 | 27.7k | inner: FormatErrorInner::BadTextEncoding(tbe), |
460 | 27.7k | }) |
461 | 27.7k | } |
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 | 41.5k | fn default() -> Self { |
476 | 41.5k | Self { |
477 | 41.5k | ignore_adler32: true, |
478 | 41.5k | ignore_crc: false, |
479 | 41.5k | ignore_text_chunk: false, |
480 | 41.5k | ignore_iccp_chunk: false, |
481 | 41.5k | skip_ancillary_crc_failures: true, |
482 | 41.5k | } |
483 | 41.5k | } |
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 | 0 | pub fn set_ignore_text_chunk(&mut self, ignore_text_chunk: bool) { |
512 | 0 | self.ignore_text_chunk = ignore_text_chunk; |
513 | 0 | } |
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 | | /// Whether this chunk should be skipped or decoded. |
575 | | action: ChunkAction, |
576 | | } |
577 | | |
578 | | #[derive(Debug, PartialEq)] |
579 | | enum ChunkAction { |
580 | | Process, |
581 | | Skip, |
582 | | Reject, |
583 | | } |
584 | | |
585 | | impl StreamingDecoder { |
586 | | /// Creates a new StreamingDecoder |
587 | | /// |
588 | | /// Allocates the internal buffers. |
589 | 41.5k | pub fn new() -> StreamingDecoder { |
590 | 41.5k | StreamingDecoder::new_with_options(DecodeOptions::default()) |
591 | 41.5k | } |
592 | | |
593 | 41.5k | pub fn new_with_options(decode_options: DecodeOptions) -> StreamingDecoder { |
594 | 41.5k | let mut inflater = ZlibStream::new(); |
595 | 41.5k | inflater.set_ignore_adler32(decode_options.ignore_adler32); |
596 | | |
597 | 41.5k | StreamingDecoder { |
598 | 41.5k | state: Some(State::new_u32(U32ValueKind::Signature1stU32)), |
599 | 41.5k | current_chunk: ChunkState { |
600 | 41.5k | type_: ChunkType([0; 4]), |
601 | 41.5k | crc: Crc32::new(), |
602 | 41.5k | remaining: 0, |
603 | 41.5k | raw_bytes: Vec::with_capacity(CHUNK_BUFFER_SIZE), |
604 | 41.5k | action: ChunkAction::Process, |
605 | 41.5k | }, |
606 | 41.5k | inflater, |
607 | 41.5k | info: None, |
608 | 41.5k | current_seq_no: None, |
609 | 41.5k | have_idat: false, |
610 | 41.5k | have_iccp: false, |
611 | 41.5k | ready_for_idat_chunks: true, |
612 | 41.5k | ready_for_fdat_chunks: false, |
613 | 41.5k | decode_options, |
614 | 41.5k | limits: Limits { bytes: usize::MAX }, |
615 | 41.5k | } |
616 | 41.5k | } |
617 | | |
618 | | /// Resets the StreamingDecoder |
619 | 0 | pub fn reset(&mut self) { |
620 | 0 | self.state = Some(State::new_u32(U32ValueKind::Signature1stU32)); |
621 | 0 | self.current_chunk.crc = Crc32::new(); |
622 | 0 | self.current_chunk.remaining = 0; |
623 | 0 | self.current_chunk.raw_bytes.clear(); |
624 | 0 | self.inflater.reset(); |
625 | 0 | self.info = None; |
626 | 0 | self.current_seq_no = None; |
627 | 0 | self.have_idat = false; |
628 | 0 | } |
629 | | |
630 | | /// Provides access to the inner `info` field |
631 | 0 | pub fn info(&self) -> Option<&Info<'static>> { |
632 | 0 | self.info.as_ref() |
633 | 0 | } |
634 | | |
635 | 0 | pub fn set_ignore_text_chunk(&mut self, ignore_text_chunk: bool) { |
636 | 0 | self.decode_options.set_ignore_text_chunk(ignore_text_chunk); |
637 | 0 | } |
638 | | |
639 | 0 | pub fn set_ignore_iccp_chunk(&mut self, ignore_iccp_chunk: bool) { |
640 | 0 | self.decode_options.set_ignore_iccp_chunk(ignore_iccp_chunk); |
641 | 0 | } |
642 | | |
643 | | /// Return whether the decoder is set to ignore the Adler-32 checksum. |
644 | 0 | pub fn ignore_adler32(&self) -> bool { |
645 | 0 | self.inflater.ignore_adler32() |
646 | 0 | } |
647 | | |
648 | | /// Set whether to compute and verify the Adler-32 checksum during |
649 | | /// decompression. Return `true` if the flag was successfully set. |
650 | | /// |
651 | | /// The decoder defaults to `true`. |
652 | | /// |
653 | | /// This flag cannot be modified after decompression has started until the |
654 | | /// [`StreamingDecoder`] is reset. |
655 | 0 | pub fn set_ignore_adler32(&mut self, ignore_adler32: bool) -> bool { |
656 | 0 | self.inflater.set_ignore_adler32(ignore_adler32) |
657 | 0 | } |
658 | | |
659 | | /// Set whether to compute and verify the Adler-32 checksum during |
660 | | /// decompression. |
661 | | /// |
662 | | /// The decoder defaults to `false`. |
663 | 0 | pub fn set_ignore_crc(&mut self, ignore_crc: bool) { |
664 | 0 | self.decode_options.set_ignore_crc(ignore_crc) |
665 | 0 | } |
666 | | |
667 | | /// Ignore ancillary chunks if CRC fails |
668 | | /// |
669 | | /// Defaults to `true` |
670 | 0 | pub fn set_skip_ancillary_crc_failures(&mut self, skip_ancillary_crc_failures: bool) { |
671 | 0 | self.decode_options |
672 | 0 | .set_skip_ancillary_crc_failures(skip_ancillary_crc_failures) |
673 | 0 | } |
674 | | |
675 | | /// Low level StreamingDecoder interface. |
676 | | /// |
677 | | /// Allows to stream partial data to the encoder. Returns a tuple containing the bytes that have |
678 | | /// been consumed from the input buffer and the current decoding result. If the decoded chunk |
679 | | /// was an image data chunk, it also appends the read data to `image_data`. |
680 | 27.1M | pub fn update( |
681 | 27.1M | &mut self, |
682 | 27.1M | mut buf: &[u8], |
683 | 27.1M | mut image_data: Option<&mut UnfilterBuf<'_>>, |
684 | 27.1M | ) -> Result<(usize, Decoded), DecodingError> { |
685 | 27.1M | if self.state.is_none() { |
686 | 0 | return Err(DecodingError::Parameter( |
687 | 0 | ParameterErrorKind::PolledAfterFatalError.into(), |
688 | 0 | )); |
689 | 27.1M | } |
690 | | |
691 | 27.1M | let len = buf.len(); |
692 | 39.2M | while !buf.is_empty() { |
693 | 27.7M | let image_data = image_data.as_deref_mut(); |
694 | | |
695 | 27.7M | match self.next_state(buf, image_data) { |
696 | 12.0M | Ok((bytes, Decoded::Nothing)) => buf = &buf[bytes..], |
697 | 15.6M | Ok((bytes, result)) => { |
698 | 15.6M | buf = &buf[bytes..]; |
699 | 15.6M | return Ok((len - buf.len(), result)); |
700 | | } |
701 | 7.39k | Err(err) => { |
702 | 7.39k | debug_assert!(self.state.is_none()); |
703 | 7.39k | return Err(err); |
704 | | } |
705 | | } |
706 | | } |
707 | 11.4M | Ok((len - buf.len(), Decoded::Nothing)) |
708 | 27.1M | } |
709 | | |
710 | 27.7M | fn next_state( |
711 | 27.7M | &mut self, |
712 | 27.7M | buf: &[u8], |
713 | 27.7M | image_data: Option<&mut UnfilterBuf<'_>>, |
714 | 27.7M | ) -> Result<(usize, Decoded), DecodingError> { |
715 | | use self::State::*; |
716 | | |
717 | | // Driver should ensure that state is never None |
718 | 27.7M | let state = self.state.take().unwrap(); |
719 | | |
720 | 27.7M | match state { |
721 | | U32 { |
722 | 5.18M | kind, |
723 | 5.18M | mut bytes, |
724 | 5.18M | mut accumulated_count, |
725 | | } => { |
726 | 5.18M | debug_assert!(accumulated_count <= 4); |
727 | 5.18M | if accumulated_count == 0 && buf.len() >= 4 { |
728 | | // Handling these `accumulated_count` and `buf.len()` values in a separate `if` |
729 | | // branch is not strictly necessary - the `else` statement below is already |
730 | | // capable of handling these values. The main reason for special-casing these |
731 | | // values is that they occur fairly frequently and special-casing them results |
732 | | // in performance gains. |
733 | | const CONSUMED_BYTES: usize = 4; |
734 | 924k | self.parse_u32(kind, &buf[0..4], image_data, CONSUMED_BYTES) |
735 | | } else { |
736 | 4.26M | let remaining_count = 4 - accumulated_count; |
737 | 4.26M | let consumed_bytes = { |
738 | 4.26M | let available_count = min(remaining_count, buf.len()); |
739 | 4.26M | bytes[accumulated_count..accumulated_count + available_count] |
740 | 4.26M | .copy_from_slice(&buf[0..available_count]); |
741 | 4.26M | accumulated_count += available_count; |
742 | 4.26M | available_count |
743 | | }; |
744 | | |
745 | 4.26M | if accumulated_count < 4 { |
746 | 3.19M | self.state = Some(U32 { |
747 | 3.19M | kind, |
748 | 3.19M | bytes, |
749 | 3.19M | accumulated_count, |
750 | 3.19M | }); |
751 | 3.19M | Ok((consumed_bytes, Decoded::Nothing)) |
752 | | } else { |
753 | 1.06M | debug_assert_eq!(accumulated_count, 4); |
754 | 1.06M | self.parse_u32(kind, &bytes, image_data, consumed_bytes) |
755 | | } |
756 | | } |
757 | | } |
758 | 8.15M | ReadChunkData(type_str) => { |
759 | 8.15M | debug_assert!(type_str != IDAT && type_str != chunk::fdAT); |
760 | 8.15M | if self.current_chunk.remaining == 0 { |
761 | 19.1k | self.state = Some(State::new_u32(U32ValueKind::Crc(type_str))); |
762 | 19.1k | Ok((0, Decoded::Nothing)) |
763 | | } else { |
764 | | let ChunkState { |
765 | 8.13M | crc, |
766 | 8.13M | remaining, |
767 | 8.13M | raw_bytes, |
768 | | type_: _, |
769 | 8.13M | action, |
770 | 8.13M | } = &mut self.current_chunk; |
771 | | |
772 | 8.13M | let buf_avail = raw_bytes.capacity() - raw_bytes.len(); |
773 | 8.13M | let bytes_avail = min(buf.len(), buf_avail); |
774 | 8.13M | let n = min(*remaining, bytes_avail as u32); |
775 | 8.13M | let buf = &buf[..n as usize]; |
776 | | |
777 | 8.13M | if !self.decode_options.ignore_crc { |
778 | 8.13M | crc.update(buf); |
779 | 8.13M | } |
780 | | |
781 | 8.13M | if *action == ChunkAction::Process { |
782 | 7.55M | if raw_bytes.len() == raw_bytes.capacity() { |
783 | 7.87k | if self.limits.bytes == 0 { |
784 | 26 | return Err(DecodingError::LimitsExceeded); |
785 | 7.85k | } |
786 | | |
787 | | // Double the size of the Vec, but not beyond the allocation limit. |
788 | 7.85k | debug_assert!(raw_bytes.capacity() > 0); |
789 | 7.85k | let reserve_size = raw_bytes.capacity().min(self.limits.bytes); |
790 | | |
791 | 7.85k | self.limits.reserve_bytes(reserve_size)?; |
792 | 7.85k | raw_bytes.reserve_exact(reserve_size); |
793 | 7.55M | } |
794 | 7.55M | raw_bytes.extend_from_slice(buf); |
795 | 581k | } |
796 | | |
797 | 8.13M | *remaining -= n; |
798 | 8.13M | if *remaining == 0 { |
799 | 569k | debug_assert!(type_str != IDAT && type_str != chunk::fdAT); |
800 | 569k | self.state = Some(State::new_u32(U32ValueKind::Crc(type_str))); |
801 | 7.56M | } else { |
802 | 7.56M | self.state = Some(ReadChunkData(type_str)); |
803 | 7.56M | } |
804 | 8.13M | Ok((n as usize, Decoded::Nothing)) |
805 | | } |
806 | | } |
807 | 14.4M | ImageData(type_str) => { |
808 | 14.4M | debug_assert!(type_str == IDAT || type_str == chunk::fdAT); |
809 | 14.4M | let len = std::cmp::min(buf.len(), self.current_chunk.remaining as usize); |
810 | 14.4M | let buf = &buf[..len]; |
811 | | |
812 | 14.4M | let consumed = if let Some(image_data) = image_data { |
813 | 11.1M | self.inflater.decompress(buf, image_data)? |
814 | | } else { |
815 | 3.22M | len |
816 | | }; |
817 | | |
818 | 14.4M | if !self.decode_options.ignore_crc { |
819 | 14.4M | self.current_chunk.crc.update(&buf[..consumed]); |
820 | 14.4M | } |
821 | | |
822 | 14.4M | self.current_chunk.remaining -= consumed as u32; |
823 | 14.4M | if self.current_chunk.remaining == 0 { |
824 | 23.8k | self.state = Some(State::new_u32(U32ValueKind::Crc(type_str))); |
825 | 14.3M | } else { |
826 | 14.3M | self.state = Some(ImageData(type_str)); |
827 | 14.3M | } |
828 | 14.4M | Ok((consumed, Decoded::ImageData)) |
829 | | } |
830 | | } |
831 | 27.7M | } |
832 | | |
833 | 1.99M | fn parse_u32( |
834 | 1.99M | &mut self, |
835 | 1.99M | kind: U32ValueKind, |
836 | 1.99M | u32_be_bytes: &[u8], |
837 | 1.99M | image_data: Option<&mut UnfilterBuf<'_>>, |
838 | 1.99M | consumed_bytes: usize, |
839 | 1.99M | ) -> Result<(usize, Decoded), DecodingError> { |
840 | 1.99M | debug_assert_eq!(u32_be_bytes.len(), 4); |
841 | 1.99M | let bytes = u32_be_bytes.try_into().unwrap(); |
842 | 1.99M | let val = u32::from_be_bytes(bytes); |
843 | | |
844 | 1.99M | match kind { |
845 | | U32ValueKind::Signature1stU32 => { |
846 | 41.4k | if bytes == [137, 80, 78, 71] { |
847 | 41.1k | self.state = Some(State::new_u32(U32ValueKind::Signature2ndU32)); |
848 | 41.1k | Ok((consumed_bytes, Decoded::Nothing)) |
849 | | } else { |
850 | 309 | Err(DecodingError::Format( |
851 | 309 | FormatErrorInner::InvalidSignature.into(), |
852 | 309 | )) |
853 | | } |
854 | | } |
855 | | U32ValueKind::Signature2ndU32 => { |
856 | 41.1k | if bytes == [13, 10, 26, 10] { |
857 | 40.8k | self.state = Some(State::new_u32(U32ValueKind::Length)); |
858 | 40.8k | Ok((consumed_bytes, Decoded::Nothing)) |
859 | | } else { |
860 | 221 | Err(DecodingError::Format( |
861 | 221 | FormatErrorInner::InvalidSignature.into(), |
862 | 221 | )) |
863 | | } |
864 | | } |
865 | | U32ValueKind::Length => { |
866 | 644k | self.state = Some(State::new_u32(U32ValueKind::Type { length: val })); |
867 | 644k | Ok((consumed_bytes, Decoded::Nothing)) |
868 | | } |
869 | 648k | U32ValueKind::Type { length } => { |
870 | 648k | let type_str = ChunkType(bytes); |
871 | 648k | if self.info.is_none() && type_str != IHDR { |
872 | 230 | return Err(DecodingError::Format( |
873 | 230 | FormatErrorInner::ChunkBeforeIhdr { kind: type_str }.into(), |
874 | 230 | )); |
875 | 648k | } |
876 | 648k | if type_str != self.current_chunk.type_ |
877 | 146k | && (self.current_chunk.type_ == IDAT || self.current_chunk.type_ == chunk::fdAT) |
878 | | { |
879 | 14.1k | let finished = match image_data { |
880 | 750 | Some(image_data) => self.inflater.finish(image_data)?, |
881 | 13.3k | None => true, |
882 | | }; |
883 | | |
884 | | // We ended up handling IDAT/fdAT data rather than the chunk |
885 | | // type header atually received. Thus rewind `self.state` to |
886 | | // what it was before this function was called. |
887 | 13.7k | self.state = Some(State::U32 { |
888 | 13.7k | kind, |
889 | 13.7k | bytes, |
890 | 13.7k | accumulated_count: 4 - consumed_bytes, |
891 | 13.7k | }); |
892 | | |
893 | 13.7k | if finished { |
894 | | // We've processed all the image data necessary. Update |
895 | | // `current_chunk.type_`so this codepath isn't taken |
896 | | // again next time. |
897 | 13.6k | self.current_chunk.type_ = type_str; |
898 | 13.6k | self.ready_for_idat_chunks = false; |
899 | 13.6k | self.ready_for_fdat_chunks = false; |
900 | 13.6k | return Ok((0, Decoded::ImageDataFlushed)); |
901 | | } else { |
902 | | // Report that we processed some image data without |
903 | | // consuming any input. This gives the caller a chance |
904 | | // to grow the output buffer and call us again. |
905 | 77 | return Ok((0, Decoded::ImageData)); |
906 | | } |
907 | 634k | } |
908 | | |
909 | 634k | self.current_chunk.type_ = type_str; |
910 | 634k | if !self.decode_options.ignore_crc { |
911 | 634k | self.current_chunk.crc.reset(); |
912 | 634k | self.current_chunk.crc.update(&type_str.0); |
913 | 634k | } |
914 | 634k | self.current_chunk.remaining = length; |
915 | 634k | self.current_chunk.raw_bytes.clear(); |
916 | | |
917 | 634k | self.state = match type_str { |
918 | | chunk::fdAT => { |
919 | 2.37k | if !self.ready_for_fdat_chunks { |
920 | 7 | return Err(DecodingError::Format( |
921 | 7 | FormatErrorInner::UnexpectedRestartOfDataChunkSequence { |
922 | 7 | kind: chunk::fdAT, |
923 | 7 | } |
924 | 7 | .into(), |
925 | 7 | )); |
926 | 2.36k | } |
927 | 2.36k | if length < 4 { |
928 | 6 | return Err(DecodingError::Format( |
929 | 6 | FormatErrorInner::FdatShorterThanFourBytes.into(), |
930 | 6 | )); |
931 | 2.35k | } |
932 | 2.35k | self.current_chunk.action = ChunkAction::Process; |
933 | 2.35k | Some(State::new_u32(U32ValueKind::ApngSequenceNumber)) |
934 | | } |
935 | | IDAT => { |
936 | 37.1k | if !self.ready_for_idat_chunks { |
937 | 4 | return Err(DecodingError::Format( |
938 | 4 | FormatErrorInner::UnexpectedRestartOfDataChunkSequence { |
939 | 4 | kind: IDAT, |
940 | 4 | } |
941 | 4 | .into(), |
942 | 4 | )); |
943 | 37.1k | } |
944 | 37.1k | self.have_idat = true; |
945 | 37.1k | self.current_chunk.action = ChunkAction::Process; |
946 | 37.1k | Some(State::ImageData(type_str)) |
947 | | } |
948 | 594k | _ => Some(self.start_chunk(type_str, length)?), |
949 | | }; |
950 | 632k | Ok((consumed_bytes, Decoded::ChunkBegin(length, type_str))) |
951 | | } |
952 | 612k | U32ValueKind::Crc(type_str) => { |
953 | | // If ignore_crc is set, do not calculate CRC. We set |
954 | | // sum=val so that it short-circuits to true in the next |
955 | | // if-statement block |
956 | 612k | let sum = if self.decode_options.ignore_crc { |
957 | 0 | val |
958 | | } else { |
959 | 612k | self.current_chunk.crc.clone().finalize() |
960 | | }; |
961 | | |
962 | 612k | if val == sum || CHECKSUM_DISABLED { |
963 | 612k | match self.current_chunk.action { |
964 | | ChunkAction::Process => { |
965 | | // A fatal error in chunk parsing leaves the decoder in state 'None' to enforce |
966 | | // that parsing can't continue after an error. |
967 | 565k | debug_assert!(self.state.is_none()); |
968 | 565k | let decoded = self.parse_chunk(type_str)?; |
969 | | |
970 | 564k | if type_str != IEND { |
971 | 564k | self.state = Some(State::new_u32(U32ValueKind::Length)); |
972 | 564k | } |
973 | 564k | Ok((consumed_bytes, decoded)) |
974 | | } |
975 | | ChunkAction::Skip => { |
976 | 41.5k | self.state = Some(State::new_u32(U32ValueKind::Length)); |
977 | 41.5k | Ok(( |
978 | 41.5k | consumed_bytes, |
979 | 41.5k | Decoded::SkippedAncillaryChunk(self.current_chunk.type_), |
980 | 41.5k | )) |
981 | | } |
982 | | ChunkAction::Reject => { |
983 | 4.62k | self.state = Some(State::new_u32(U32ValueKind::Length)); |
984 | 4.62k | Ok((consumed_bytes, Decoded::BadAncillaryChunk(type_str))) |
985 | | } |
986 | | } |
987 | 0 | } else if self.decode_options.skip_ancillary_crc_failures |
988 | 0 | && !chunk::is_critical(type_str) |
989 | | { |
990 | | // Ignore ancillary chunk with invalid CRC |
991 | 0 | self.state = Some(State::new_u32(U32ValueKind::Length)); |
992 | 0 | Ok((consumed_bytes, Decoded::BadAncillaryChunk(type_str))) |
993 | | } else { |
994 | 0 | Err(DecodingError::Format( |
995 | 0 | FormatErrorInner::CrcMismatch { |
996 | 0 | crc_val: val, |
997 | 0 | crc_sum: sum, |
998 | 0 | chunk: type_str, |
999 | 0 | } |
1000 | 0 | .into(), |
1001 | 0 | )) |
1002 | | } |
1003 | | } |
1004 | | U32ValueKind::ApngSequenceNumber => { |
1005 | 2.33k | debug_assert_eq!(self.current_chunk.type_, chunk::fdAT); |
1006 | 2.33k | let next_seq_no = val; |
1007 | | |
1008 | | // Should be verified by the FdatShorterThanFourBytes check earlier. |
1009 | 2.33k | debug_assert!(self.current_chunk.remaining >= 4); |
1010 | 2.33k | self.current_chunk.remaining -= 4; |
1011 | | |
1012 | 2.33k | if let Some(seq_no) = self.current_seq_no { |
1013 | 2.33k | if next_seq_no != seq_no + 1 { |
1014 | 204 | return Err(DecodingError::Format( |
1015 | 204 | FormatErrorInner::ApngOrder { |
1016 | 204 | present: next_seq_no, |
1017 | 204 | expected: seq_no + 1, |
1018 | 204 | } |
1019 | 204 | .into(), |
1020 | 204 | )); |
1021 | 2.13k | } |
1022 | 2.13k | self.current_seq_no = Some(next_seq_no); |
1023 | | } else { |
1024 | 0 | return Err(DecodingError::Format(FormatErrorInner::MissingFctl.into())); |
1025 | | } |
1026 | | |
1027 | 2.13k | if !self.decode_options.ignore_crc { |
1028 | 2.13k | let data = next_seq_no.to_be_bytes(); |
1029 | 2.13k | self.current_chunk.crc.update(&data); |
1030 | 2.13k | } |
1031 | | |
1032 | 2.13k | self.state = Some(State::ImageData(chunk::fdAT)); |
1033 | 2.13k | Ok((consumed_bytes, Decoded::Nothing)) |
1034 | | } |
1035 | | } |
1036 | 1.99M | } |
1037 | | |
1038 | 594k | fn start_chunk(&mut self, type_str: ChunkType, length: u32) -> Result<State, DecodingError> { |
1039 | 548k | let target_length = match type_str { |
1040 | 40.6k | IHDR => 13..=13, |
1041 | 1.28k | chunk::PLTE => 3..=768, |
1042 | 11 | chunk::IEND => 0..=0, |
1043 | 8.34k | chunk::sBIT => 1..=4, |
1044 | 4.17k | chunk::tRNS => 1..=256, |
1045 | 2.01k | chunk::pHYs => 9..=9, |
1046 | 4.76k | chunk::gAMA => 4..=4, |
1047 | 5.16k | chunk::acTL => 8..=8, |
1048 | 4.76k | chunk::fcTL => 26..=26, |
1049 | 3.32k | chunk::cHRM => 32..=32, |
1050 | 3.84k | chunk::sRGB => 1..=1, |
1051 | 2.57k | chunk::cICP => 4..=4, |
1052 | 2.42k | chunk::mDCV => 24..=24, |
1053 | 1.26k | chunk::cLLI => 8..=8, |
1054 | 8.90k | chunk::bKGD => 1..=6, |
1055 | | |
1056 | | // Unbounded size chunks |
1057 | 1.12k | chunk::eXIf => 0..=u32::MAX >> 1, // TODO: allow skipping. |
1058 | 7.77k | chunk::iCCP if !self.decode_options.ignore_iccp_chunk => 0..=u32::MAX >> 1, |
1059 | 323k | chunk::tEXt if !self.decode_options.ignore_text_chunk => 0..=u32::MAX >> 1, |
1060 | 43.1k | chunk::zTXt if !self.decode_options.ignore_text_chunk => 0..=u32::MAX >> 1, |
1061 | 79.5k | chunk::iTXt if !self.decode_options.ignore_text_chunk => 0..=u32::MAX >> 1, |
1062 | | |
1063 | 0 | chunk::IDAT | chunk::fdAT => unreachable!(), |
1064 | | |
1065 | 46.2k | _ if is_critical(type_str) => { |
1066 | 1.14k | return Err(DecodingError::Format( |
1067 | 1.14k | FormatErrorInner::UnrecognizedCriticalChunk { type_str }.into(), |
1068 | 1.14k | )); |
1069 | | } |
1070 | | _ => { |
1071 | 45.0k | self.current_chunk.action = ChunkAction::Skip; |
1072 | 45.0k | return Ok(State::ReadChunkData(type_str)); |
1073 | | } |
1074 | | }; |
1075 | | |
1076 | 548k | if !target_length.contains(&length) { |
1077 | | // Uncomment to detect unexpected chunk lengths during testing. |
1078 | | // panic!("chunk type_str={type_str:?} has length={length}, target_length={target_length:?}"); |
1079 | 5.17k | match type_str { |
1080 | | IHDR | chunk::PLTE | chunk::IEND | chunk::fcTL => { |
1081 | 218 | return Err(DecodingError::Format( |
1082 | 218 | FormatErrorInner::ChunkLengthWrong { kind: type_str }.into(), |
1083 | 218 | )); |
1084 | | } |
1085 | 4.96k | _ => { |
1086 | 4.96k | self.current_chunk.action = ChunkAction::Reject; |
1087 | 4.96k | } |
1088 | | } |
1089 | 543k | } else { |
1090 | 543k | self.current_chunk.action = ChunkAction::Process; |
1091 | 543k | } |
1092 | | |
1093 | 548k | Ok(State::ReadChunkData(type_str)) |
1094 | 594k | } |
1095 | | |
1096 | 565k | fn parse_chunk(&mut self, type_str: ChunkType) -> Result<Decoded, DecodingError> { |
1097 | 565k | let mut parse_result = match type_str { |
1098 | | // Critical non-data chunks. |
1099 | 40.3k | IHDR => self.parse_ihdr(), |
1100 | 1.26k | chunk::PLTE => self.parse_plte(), |
1101 | 4 | chunk::IEND => Ok(()), // TODO: Check chunk size. |
1102 | | |
1103 | | // Data chunks handled separately. |
1104 | 21.6k | chunk::IDAT => Ok(()), |
1105 | 2.02k | chunk::fdAT => Ok(()), |
1106 | | |
1107 | | // Recognized bounded-size ancillary chunks. |
1108 | 7.53k | chunk::sBIT => self.parse_sbit(), |
1109 | 3.64k | chunk::tRNS => self.parse_trns(), |
1110 | 1.22k | chunk::pHYs => self.parse_phys(), |
1111 | 4.31k | chunk::gAMA => self.parse_gama(), |
1112 | 4.86k | chunk::acTL => self.parse_actl(), |
1113 | 4.60k | chunk::fcTL => self.parse_fctl(), |
1114 | 2.77k | chunk::cHRM => self.parse_chrm(), |
1115 | 3.60k | chunk::sRGB => self.parse_srgb(), |
1116 | 2.14k | chunk::cICP => self.parse_cicp(), |
1117 | 2.00k | chunk::mDCV => self.parse_mdcv(), |
1118 | 865 | chunk::cLLI => self.parse_clli(), |
1119 | 8.69k | chunk::bKGD => self.parse_bkgd(), |
1120 | | |
1121 | | // Ancillary chunks with unbounded size. |
1122 | 1.07k | chunk::eXIf => self.parse_exif(), |
1123 | 7.70k | chunk::iCCP => self.parse_iccp(), |
1124 | 323k | chunk::tEXt => self.parse_text(), |
1125 | 43.0k | chunk::zTXt => self.parse_ztxt(), |
1126 | 79.3k | chunk::iTXt => self.parse_itxt(), |
1127 | | |
1128 | | // Unrecognized chunks. |
1129 | 0 | _ => unreachable!( |
1130 | | "Unrecognized chunk {type_str:?} should have been caught in start_chunk" |
1131 | | ), |
1132 | | }; |
1133 | | |
1134 | 565k | parse_result = parse_result.map_err(|e| { |
1135 | 49 | match e { |
1136 | | // `parse_chunk` is invoked after gathering **all** bytes of a chunk, so |
1137 | | // `UnexpectedEof` from something like `read_be` is permanent and indicates an |
1138 | | // invalid PNG that should be represented as a `FormatError`, rather than as a |
1139 | | // (potentially recoverable) `IoError` / `UnexpectedEof`. |
1140 | 49 | DecodingError::IoError(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => { |
1141 | 0 | let fmt_err: FormatError = |
1142 | 0 | FormatErrorInner::ChunkLengthWrong { kind: type_str }.into(); |
1143 | 0 | fmt_err.into() |
1144 | | } |
1145 | 74.2k | e => e, |
1146 | | } |
1147 | 74.2k | }); |
1148 | | |
1149 | 74.1k | match parse_result { |
1150 | 491k | Ok(()) => Ok(Decoded::ChunkComplete(type_str)), |
1151 | | Err(DecodingError::Format(_)) |
1152 | 74.1k | if type_str != chunk::fcTL && !chunk::is_critical(type_str) => |
1153 | | { |
1154 | | // Ignore benign errors in most auxiliary chunks. `LimitsExceeded`, `Parameter` and |
1155 | | // other error kinds are *not* treated as benign. We don't ignore errors in `fcTL` |
1156 | | // chunks because the fallback to the static/non-animated image has to be |
1157 | | // implemented *on top* of the `StreamingDecoder` API. |
1158 | | // |
1159 | | // TODO: Consider supporting a strict mode where even benign errors are reported up. |
1160 | | // See https://github.com/image-rs/image-png/pull/569#issuecomment-2642062285 |
1161 | 72.4k | Ok(Decoded::BadAncillaryChunk(type_str)) |
1162 | | } |
1163 | 1.73k | Err(e) => Err(e), |
1164 | | } |
1165 | 565k | } |
1166 | | |
1167 | 4.60k | fn parse_fctl(&mut self) -> Result<(), DecodingError> { |
1168 | 4.60k | let mut buf = &self.current_chunk.raw_bytes[..]; |
1169 | 4.60k | let next_seq_no = buf.read_be()?; |
1170 | | |
1171 | | // Assuming that fcTL is required before *every* fdAT-sequence |
1172 | 4.60k | self.current_seq_no = Some(if let Some(seq_no) = self.current_seq_no { |
1173 | 2.60k | if next_seq_no != seq_no + 1 { |
1174 | 195 | return Err(DecodingError::Format( |
1175 | 195 | FormatErrorInner::ApngOrder { |
1176 | 195 | expected: seq_no + 1, |
1177 | 195 | present: next_seq_no, |
1178 | 195 | } |
1179 | 195 | .into(), |
1180 | 195 | )); |
1181 | 2.41k | } |
1182 | 2.41k | next_seq_no |
1183 | | } else { |
1184 | 1.99k | if next_seq_no != 0 { |
1185 | 119 | return Err(DecodingError::Format( |
1186 | 119 | FormatErrorInner::ApngOrder { |
1187 | 119 | expected: 0, |
1188 | 119 | present: next_seq_no, |
1189 | 119 | } |
1190 | 119 | .into(), |
1191 | 119 | )); |
1192 | 1.88k | } |
1193 | 1.88k | 0 |
1194 | | }); |
1195 | 4.29k | self.inflater.reset(); |
1196 | 4.29k | self.ready_for_fdat_chunks = self.have_idat; |
1197 | 4.25k | let fc = FrameControl { |
1198 | 4.29k | sequence_number: next_seq_no, |
1199 | 4.29k | width: buf.read_be()?, |
1200 | 4.29k | height: buf.read_be()?, |
1201 | 4.29k | x_offset: buf.read_be()?, |
1202 | 4.29k | y_offset: buf.read_be()?, |
1203 | 4.29k | delay_num: buf.read_be()?, |
1204 | 4.29k | delay_den: buf.read_be()?, |
1205 | | dispose_op: { |
1206 | 4.29k | let dispose_op = buf.read_be()?; |
1207 | 4.29k | match DisposeOp::from_u8(dispose_op) { |
1208 | 4.26k | Some(dispose_op) => dispose_op, |
1209 | | None => { |
1210 | 24 | return Err(DecodingError::Format( |
1211 | 24 | FormatErrorInner::InvalidDisposeOp(dispose_op).into(), |
1212 | 24 | )) |
1213 | | } |
1214 | | } |
1215 | | }, |
1216 | | blend_op: { |
1217 | 4.26k | let blend_op = buf.read_be()?; |
1218 | 4.26k | match BlendOp::from_u8(blend_op) { |
1219 | 4.25k | Some(blend_op) => blend_op, |
1220 | | None => { |
1221 | 16 | return Err(DecodingError::Format( |
1222 | 16 | FormatErrorInner::InvalidBlendOp(blend_op).into(), |
1223 | 16 | )) |
1224 | | } |
1225 | | } |
1226 | | }, |
1227 | | }; |
1228 | 4.25k | self.info.as_ref().unwrap().validate(&fc)?; |
1229 | 3.86k | if !self.have_idat { |
1230 | 1.49k | self.info.as_ref().unwrap().validate_default_image(&fc)?; |
1231 | 2.36k | } |
1232 | 3.24k | self.info.as_mut().unwrap().frame_control = Some(fc); |
1233 | 3.24k | Ok(()) |
1234 | 4.60k | } |
1235 | | |
1236 | 4.86k | fn parse_actl(&mut self) -> Result<(), DecodingError> { |
1237 | 4.86k | let info = self.info.as_mut().unwrap(); |
1238 | 4.86k | if self.have_idat { |
1239 | 392 | Err(DecodingError::Format( |
1240 | 392 | FormatErrorInner::AfterIdat { kind: chunk::acTL }.into(), |
1241 | 392 | )) |
1242 | 4.47k | } else if info.animation_control.is_some() { |
1243 | 1.93k | Err(DecodingError::Format( |
1244 | 1.93k | FormatErrorInner::DuplicateChunk { kind: chunk::acTL }.into(), |
1245 | 1.93k | )) |
1246 | | } else { |
1247 | 2.54k | let mut buf = &self.current_chunk.raw_bytes[..]; |
1248 | 2.54k | let actl = AnimationControl { |
1249 | 2.54k | num_frames: buf.read_be()?, |
1250 | 2.54k | num_plays: buf.read_be()?, |
1251 | | }; |
1252 | | // The spec says that "0 is not a valid value" for `num_frames`. |
1253 | | // So let's ignore such malformed `acTL` chunks. |
1254 | 2.54k | if actl.num_frames == 0 { |
1255 | 274 | return Ok(()); |
1256 | 2.26k | } |
1257 | | |
1258 | | // The spec also says that the number of frames and number of plays should be limited |
1259 | | // to (2^31)-1. Same as the other condition we enforce it by ignoring the chunk. |
1260 | | // Another option may be saturation which would lose us some frames but encourage |
1261 | | // rather dubious handling. |
1262 | 2.26k | if actl.num_frames > 0x7FFFFFFF || actl.num_plays > 0x7FFFFFFF { |
1263 | 1.05k | return Ok(()); |
1264 | 1.21k | } |
1265 | | |
1266 | 1.21k | info.animation_control = Some(actl); |
1267 | 1.21k | Ok(()) |
1268 | | } |
1269 | 4.86k | } |
1270 | | |
1271 | 1.26k | fn parse_plte(&mut self) -> Result<(), DecodingError> { |
1272 | 1.26k | let info = self.info.as_mut().unwrap(); |
1273 | 1.26k | if info.palette.is_some() { |
1274 | | // Only one palette is allowed |
1275 | 17 | Err(DecodingError::Format( |
1276 | 17 | FormatErrorInner::DuplicateChunk { kind: chunk::PLTE }.into(), |
1277 | 17 | )) |
1278 | | } else { |
1279 | 1.24k | info.palette = Some(Cow::Owned(self.current_chunk.raw_bytes.clone())); |
1280 | 1.24k | Ok(()) |
1281 | | } |
1282 | 1.26k | } |
1283 | | |
1284 | 7.53k | fn parse_sbit(&mut self) -> Result<(), DecodingError> { |
1285 | 7.53k | let info = self.info.as_mut().unwrap(); |
1286 | 7.53k | if info.palette.is_some() { |
1287 | 423 | return Err(DecodingError::Format( |
1288 | 423 | FormatErrorInner::AfterPlte { kind: chunk::sBIT }.into(), |
1289 | 423 | )); |
1290 | 7.11k | } |
1291 | | |
1292 | 7.11k | if self.have_idat { |
1293 | 986 | return Err(DecodingError::Format( |
1294 | 986 | FormatErrorInner::AfterIdat { kind: chunk::sBIT }.into(), |
1295 | 986 | )); |
1296 | 6.12k | } |
1297 | | |
1298 | 6.12k | if info.sbit.is_some() { |
1299 | 1.54k | return Err(DecodingError::Format( |
1300 | 1.54k | FormatErrorInner::DuplicateChunk { kind: chunk::sBIT }.into(), |
1301 | 1.54k | )); |
1302 | 4.57k | } |
1303 | | |
1304 | 4.57k | let (color_type, bit_depth) = { (info.color_type, info.bit_depth) }; |
1305 | | // The sample depth for color type 3 is fixed at eight bits. |
1306 | 4.57k | let sample_depth = if color_type == ColorType::Indexed { |
1307 | 1.45k | BitDepth::Eight |
1308 | | } else { |
1309 | 3.12k | bit_depth |
1310 | | }; |
1311 | 4.57k | let vec = self.current_chunk.raw_bytes.clone(); |
1312 | 4.57k | let len = vec.len(); |
1313 | | |
1314 | | // expected lenth of the chunk |
1315 | 4.57k | let expected = match color_type { |
1316 | 1.34k | ColorType::Grayscale => 1, |
1317 | 1.47k | ColorType::Rgb | ColorType::Indexed => 3, |
1318 | 8 | ColorType::GrayscaleAlpha => 2, |
1319 | 1.74k | ColorType::Rgba => 4, |
1320 | | }; |
1321 | | |
1322 | | // Check if the sbit chunk size is valid. |
1323 | 4.57k | if expected != len { |
1324 | 1.46k | return Err(DecodingError::Format( |
1325 | 1.46k | FormatErrorInner::InvalidSbitChunkSize { |
1326 | 1.46k | color_type, |
1327 | 1.46k | expected, |
1328 | 1.46k | len, |
1329 | 1.46k | } |
1330 | 1.46k | .into(), |
1331 | 1.46k | )); |
1332 | 3.11k | } |
1333 | | |
1334 | 7.09k | for sbit in &vec { |
1335 | 6.96k | if *sbit < 1 || *sbit > sample_depth as u8 { |
1336 | 2.98k | return Err(DecodingError::Format( |
1337 | 2.98k | FormatErrorInner::InvalidSbit { |
1338 | 2.98k | sample_depth, |
1339 | 2.98k | sbit: *sbit, |
1340 | 2.98k | } |
1341 | 2.98k | .into(), |
1342 | 2.98k | )); |
1343 | 3.97k | } |
1344 | | } |
1345 | 128 | info.sbit = Some(Cow::Owned(vec)); |
1346 | 128 | Ok(()) |
1347 | 7.53k | } |
1348 | | |
1349 | 3.64k | fn parse_trns(&mut self) -> Result<(), DecodingError> { |
1350 | 3.64k | let info = self.info.as_mut().unwrap(); |
1351 | 3.64k | if info.trns.is_some() { |
1352 | 419 | return Err(DecodingError::Format( |
1353 | 419 | FormatErrorInner::DuplicateChunk { kind: chunk::PLTE }.into(), |
1354 | 419 | )); |
1355 | 3.22k | } |
1356 | 3.22k | let (color_type, bit_depth) = { (info.color_type, info.bit_depth as u8) }; |
1357 | 3.22k | let mut vec = self.current_chunk.raw_bytes.clone(); |
1358 | 3.22k | let len = vec.len(); |
1359 | 3.22k | match color_type { |
1360 | | ColorType::Grayscale => { |
1361 | 522 | if len < 2 { |
1362 | 403 | return Err(DecodingError::Format( |
1363 | 403 | FormatErrorInner::ShortPalette { expected: 2, len }.into(), |
1364 | 403 | )); |
1365 | 119 | } |
1366 | 119 | if bit_depth < 16 { |
1367 | 78 | vec[0] = vec[1]; |
1368 | 78 | vec.truncate(1); |
1369 | 78 | } |
1370 | 119 | info.trns = Some(Cow::Owned(vec)); |
1371 | 119 | Ok(()) |
1372 | | } |
1373 | | ColorType::Rgb => { |
1374 | 1.02k | if len < 6 { |
1375 | 946 | return Err(DecodingError::Format( |
1376 | 946 | FormatErrorInner::ShortPalette { expected: 6, len }.into(), |
1377 | 946 | )); |
1378 | 81 | } |
1379 | 81 | if bit_depth < 16 { |
1380 | 71 | vec[0] = vec[1]; |
1381 | 71 | vec[1] = vec[3]; |
1382 | 71 | vec[2] = vec[5]; |
1383 | 71 | vec.truncate(3); |
1384 | 71 | } |
1385 | 81 | info.trns = Some(Cow::Owned(vec)); |
1386 | 81 | Ok(()) |
1387 | | } |
1388 | | ColorType::Indexed => { |
1389 | | // The transparency chunk must be after the palette chunk and |
1390 | | // before the data chunk. |
1391 | 641 | if info.palette.is_none() { |
1392 | 408 | return Err(DecodingError::Format( |
1393 | 408 | FormatErrorInner::BeforePlte { kind: chunk::tRNS }.into(), |
1394 | 408 | )); |
1395 | 233 | } else if self.have_idat { |
1396 | 147 | return Err(DecodingError::Format( |
1397 | 147 | FormatErrorInner::OutsidePlteIdat { kind: chunk::tRNS }.into(), |
1398 | 147 | )); |
1399 | 86 | } |
1400 | | |
1401 | 86 | info.trns = Some(Cow::Owned(vec)); |
1402 | 86 | Ok(()) |
1403 | | } |
1404 | 1.03k | c => Err(DecodingError::Format( |
1405 | 1.03k | FormatErrorInner::ColorWithBadTrns(c).into(), |
1406 | 1.03k | )), |
1407 | | } |
1408 | 3.64k | } |
1409 | | |
1410 | 1.22k | fn parse_phys(&mut self) -> Result<(), DecodingError> { |
1411 | 1.22k | let info = self.info.as_mut().unwrap(); |
1412 | 1.22k | if self.have_idat { |
1413 | 134 | Err(DecodingError::Format( |
1414 | 134 | FormatErrorInner::AfterIdat { kind: chunk::pHYs }.into(), |
1415 | 134 | )) |
1416 | 1.09k | } else if info.pixel_dims.is_some() { |
1417 | 722 | Err(DecodingError::Format( |
1418 | 722 | FormatErrorInner::DuplicateChunk { kind: chunk::pHYs }.into(), |
1419 | 722 | )) |
1420 | | } else { |
1421 | 373 | let mut buf = &self.current_chunk.raw_bytes[..]; |
1422 | 373 | let xppu = buf.read_be()?; |
1423 | 373 | let yppu = buf.read_be()?; |
1424 | 373 | let unit = buf.read_be()?; |
1425 | 373 | let unit = match Unit::from_u8(unit) { |
1426 | 70 | Some(unit) => unit, |
1427 | | None => { |
1428 | 303 | return Err(DecodingError::Format( |
1429 | 303 | FormatErrorInner::InvalidUnit(unit).into(), |
1430 | 303 | )) |
1431 | | } |
1432 | | }; |
1433 | 70 | let pixel_dims = PixelDimensions { xppu, yppu, unit }; |
1434 | 70 | info.pixel_dims = Some(pixel_dims); |
1435 | 70 | Ok(()) |
1436 | | } |
1437 | 1.22k | } |
1438 | | |
1439 | 2.77k | fn parse_chrm(&mut self) -> Result<(), DecodingError> { |
1440 | 2.77k | let info = self.info.as_mut().unwrap(); |
1441 | 2.77k | if self.have_idat { |
1442 | 393 | Err(DecodingError::Format( |
1443 | 393 | FormatErrorInner::AfterIdat { kind: chunk::cHRM }.into(), |
1444 | 393 | )) |
1445 | 2.37k | } else if info.chrm_chunk.is_some() { |
1446 | 2.28k | Err(DecodingError::Format( |
1447 | 2.28k | FormatErrorInner::DuplicateChunk { kind: chunk::cHRM }.into(), |
1448 | 2.28k | )) |
1449 | | } else { |
1450 | 91 | let mut buf = &self.current_chunk.raw_bytes[..]; |
1451 | 91 | let white_x: u32 = buf.read_be()?; |
1452 | 91 | let white_y: u32 = buf.read_be()?; |
1453 | 91 | let red_x: u32 = buf.read_be()?; |
1454 | 91 | let red_y: u32 = buf.read_be()?; |
1455 | 91 | let green_x: u32 = buf.read_be()?; |
1456 | 91 | let green_y: u32 = buf.read_be()?; |
1457 | 91 | let blue_x: u32 = buf.read_be()?; |
1458 | 91 | let blue_y: u32 = buf.read_be()?; |
1459 | | |
1460 | 91 | let source_chromaticities = SourceChromaticities { |
1461 | 91 | white: ( |
1462 | 91 | ScaledFloat::from_scaled(white_x), |
1463 | 91 | ScaledFloat::from_scaled(white_y), |
1464 | 91 | ), |
1465 | 91 | red: ( |
1466 | 91 | ScaledFloat::from_scaled(red_x), |
1467 | 91 | ScaledFloat::from_scaled(red_y), |
1468 | 91 | ), |
1469 | 91 | green: ( |
1470 | 91 | ScaledFloat::from_scaled(green_x), |
1471 | 91 | ScaledFloat::from_scaled(green_y), |
1472 | 91 | ), |
1473 | 91 | blue: ( |
1474 | 91 | ScaledFloat::from_scaled(blue_x), |
1475 | 91 | ScaledFloat::from_scaled(blue_y), |
1476 | 91 | ), |
1477 | 91 | }; |
1478 | | |
1479 | 91 | info.chrm_chunk = Some(source_chromaticities); |
1480 | 91 | Ok(()) |
1481 | | } |
1482 | 2.77k | } |
1483 | | |
1484 | 4.31k | fn parse_gama(&mut self) -> Result<(), DecodingError> { |
1485 | 4.31k | let info = self.info.as_mut().unwrap(); |
1486 | 4.31k | if self.have_idat { |
1487 | 645 | Err(DecodingError::Format( |
1488 | 645 | FormatErrorInner::AfterIdat { kind: chunk::gAMA }.into(), |
1489 | 645 | )) |
1490 | 3.67k | } else if info.gama_chunk.is_some() { |
1491 | 3.21k | Err(DecodingError::Format( |
1492 | 3.21k | FormatErrorInner::DuplicateChunk { kind: chunk::gAMA }.into(), |
1493 | 3.21k | )) |
1494 | | } else { |
1495 | 464 | let mut buf = &self.current_chunk.raw_bytes[..]; |
1496 | 464 | let source_gamma: u32 = buf.read_be()?; |
1497 | 464 | if source_gamma == 0 { |
1498 | 138 | return Err(DecodingError::Format( |
1499 | 138 | FormatErrorInner::BadGammaValue.into(), |
1500 | 138 | )); |
1501 | 326 | } |
1502 | | |
1503 | 326 | let source_gamma = ScaledFloat::from_scaled(source_gamma); |
1504 | 326 | info.gama_chunk = Some(source_gamma); |
1505 | 326 | Ok(()) |
1506 | | } |
1507 | 4.31k | } |
1508 | | |
1509 | 3.60k | fn parse_srgb(&mut self) -> Result<(), DecodingError> { |
1510 | 3.60k | let info = self.info.as_mut().unwrap(); |
1511 | 3.60k | if self.have_idat { |
1512 | 411 | Err(DecodingError::Format( |
1513 | 411 | FormatErrorInner::AfterIdat { kind: chunk::sRGB }.into(), |
1514 | 411 | )) |
1515 | 3.19k | } else if info.srgb.is_some() { |
1516 | 2.28k | Err(DecodingError::Format( |
1517 | 2.28k | FormatErrorInner::DuplicateChunk { kind: chunk::sRGB }.into(), |
1518 | 2.28k | )) |
1519 | | } else { |
1520 | 913 | let mut buf = &self.current_chunk.raw_bytes[..]; |
1521 | 913 | let raw: u8 = buf.read_be()?; // BE is is nonsense for single bytes, but this way the size is checked. |
1522 | 913 | let rendering_intent = crate::SrgbRenderingIntent::from_raw(raw).ok_or_else(|| { |
1523 | 822 | FormatError::from(FormatErrorInner::InvalidSrgbRenderingIntent(raw)) |
1524 | 822 | })?; |
1525 | | |
1526 | | // Set srgb and override source gamma and chromaticities. |
1527 | 91 | info.srgb = Some(rendering_intent); |
1528 | 91 | Ok(()) |
1529 | | } |
1530 | 3.60k | } |
1531 | | |
1532 | 2.14k | fn parse_cicp(&mut self) -> Result<(), DecodingError> { |
1533 | 2.14k | let info = self.info.as_mut().unwrap(); |
1534 | | |
1535 | | // The spec requires that the cICP chunk MUST come before the PLTE and IDAT chunks. |
1536 | 2.14k | if info.coding_independent_code_points.is_some() { |
1537 | 1.51k | return Err(DecodingError::Format( |
1538 | 1.51k | FormatErrorInner::DuplicateChunk { kind: chunk::cICP }.into(), |
1539 | 1.51k | )); |
1540 | 631 | } else if info.palette.is_some() { |
1541 | 267 | return Err(DecodingError::Format( |
1542 | 267 | FormatErrorInner::AfterPlte { kind: chunk::cICP }.into(), |
1543 | 267 | )); |
1544 | 364 | } else if self.have_idat { |
1545 | 267 | return Err(DecodingError::Format( |
1546 | 267 | FormatErrorInner::AfterIdat { kind: chunk::cICP }.into(), |
1547 | 267 | )); |
1548 | 97 | } |
1549 | | |
1550 | 97 | let mut buf = &*self.current_chunk.raw_bytes; |
1551 | 97 | let color_primaries: u8 = buf.read_be()?; |
1552 | 97 | let transfer_function: u8 = buf.read_be()?; |
1553 | 97 | let matrix_coefficients: u8 = buf.read_be()?; |
1554 | 57 | let is_video_full_range_image = { |
1555 | 97 | let flag: u8 = buf.read_be()?; |
1556 | 97 | match flag { |
1557 | 54 | 0 => false, |
1558 | 3 | 1 => true, |
1559 | | _ => { |
1560 | 40 | return Err(DecodingError::IoError( |
1561 | 40 | std::io::ErrorKind::InvalidData.into(), |
1562 | 40 | )); |
1563 | | } |
1564 | | } |
1565 | | }; |
1566 | | |
1567 | | // RGB is currently the only supported color model in PNG, and as |
1568 | | // such Matrix Coefficients shall be set to 0. |
1569 | 57 | if matrix_coefficients != 0 { |
1570 | 4 | return Err(DecodingError::IoError( |
1571 | 4 | std::io::ErrorKind::InvalidData.into(), |
1572 | 4 | )); |
1573 | 53 | } |
1574 | | |
1575 | 53 | if !buf.is_empty() { |
1576 | 0 | return Err(DecodingError::IoError( |
1577 | 0 | std::io::ErrorKind::InvalidData.into(), |
1578 | 0 | )); |
1579 | 53 | } |
1580 | | |
1581 | 53 | info.coding_independent_code_points = Some(CodingIndependentCodePoints { |
1582 | 53 | color_primaries, |
1583 | 53 | transfer_function, |
1584 | 53 | matrix_coefficients, |
1585 | 53 | is_video_full_range_image, |
1586 | 53 | }); |
1587 | | |
1588 | 53 | Ok(()) |
1589 | 2.14k | } |
1590 | | |
1591 | 2.00k | fn parse_mdcv(&mut self) -> Result<(), DecodingError> { |
1592 | 2.00k | let info = self.info.as_mut().unwrap(); |
1593 | | |
1594 | | // The spec requires that the mDCV chunk MUST come before the PLTE and IDAT chunks. |
1595 | 2.00k | if info.mastering_display_color_volume.is_some() { |
1596 | 1.38k | return Err(DecodingError::Format( |
1597 | 1.38k | FormatErrorInner::DuplicateChunk { kind: chunk::mDCV }.into(), |
1598 | 1.38k | )); |
1599 | 615 | } else if info.palette.is_some() { |
1600 | 282 | return Err(DecodingError::Format( |
1601 | 282 | FormatErrorInner::AfterPlte { kind: chunk::mDCV }.into(), |
1602 | 282 | )); |
1603 | 333 | } else if self.have_idat { |
1604 | 277 | return Err(DecodingError::Format( |
1605 | 277 | FormatErrorInner::AfterIdat { kind: chunk::mDCV }.into(), |
1606 | 277 | )); |
1607 | 56 | } |
1608 | | |
1609 | 56 | let mut buf = &*self.current_chunk.raw_bytes; |
1610 | 56 | let red_x: u16 = buf.read_be()?; |
1611 | 56 | let red_y: u16 = buf.read_be()?; |
1612 | 56 | let green_x: u16 = buf.read_be()?; |
1613 | 56 | let green_y: u16 = buf.read_be()?; |
1614 | 56 | let blue_x: u16 = buf.read_be()?; |
1615 | 56 | let blue_y: u16 = buf.read_be()?; |
1616 | 56 | let white_x: u16 = buf.read_be()?; |
1617 | 56 | let white_y: u16 = buf.read_be()?; |
1618 | 448 | fn scale(chunk: u16) -> ScaledFloat { |
1619 | | // `ScaledFloat::SCALING` is hardcoded to 100_000, which works |
1620 | | // well for the `cHRM` chunk where the spec says that "a value |
1621 | | // of 0.3127 would be stored as the integer 31270". In the |
1622 | | // `mDCV` chunk the spec says that "0.708, 0.292)" is stored as |
1623 | | // "{ 35400, 14600 }", using a scaling factor of 50_000, so we |
1624 | | // multiply by 2 before converting. |
1625 | 448 | ScaledFloat::from_scaled((chunk as u32) * 2) |
1626 | 448 | } |
1627 | 56 | let chromaticities = SourceChromaticities { |
1628 | 56 | white: (scale(white_x), scale(white_y)), |
1629 | 56 | red: (scale(red_x), scale(red_y)), |
1630 | 56 | green: (scale(green_x), scale(green_y)), |
1631 | 56 | blue: (scale(blue_x), scale(blue_y)), |
1632 | 56 | }; |
1633 | 56 | let max_luminance: u32 = buf.read_be()?; |
1634 | 56 | let min_luminance: u32 = buf.read_be()?; |
1635 | 56 | if !buf.is_empty() { |
1636 | 0 | return Err(DecodingError::IoError( |
1637 | 0 | std::io::ErrorKind::InvalidData.into(), |
1638 | 0 | )); |
1639 | 56 | } |
1640 | 56 | info.mastering_display_color_volume = Some(MasteringDisplayColorVolume { |
1641 | 56 | chromaticities, |
1642 | 56 | max_luminance, |
1643 | 56 | min_luminance, |
1644 | 56 | }); |
1645 | | |
1646 | 56 | Ok(()) |
1647 | 2.00k | } |
1648 | | |
1649 | 865 | fn parse_clli(&mut self) -> Result<(), DecodingError> { |
1650 | 865 | let info = self.info.as_mut().unwrap(); |
1651 | 865 | if info.content_light_level.is_some() { |
1652 | 807 | return Err(DecodingError::Format( |
1653 | 807 | FormatErrorInner::DuplicateChunk { kind: chunk::cLLI }.into(), |
1654 | 807 | )); |
1655 | 58 | } |
1656 | | |
1657 | 58 | let mut buf = &*self.current_chunk.raw_bytes; |
1658 | 58 | let max_content_light_level: u32 = buf.read_be()?; |
1659 | 58 | let max_frame_average_light_level: u32 = buf.read_be()?; |
1660 | 58 | if !buf.is_empty() { |
1661 | 0 | return Err(DecodingError::IoError( |
1662 | 0 | std::io::ErrorKind::InvalidData.into(), |
1663 | 0 | )); |
1664 | 58 | } |
1665 | 58 | info.content_light_level = Some(ContentLightLevelInfo { |
1666 | 58 | max_content_light_level, |
1667 | 58 | max_frame_average_light_level, |
1668 | 58 | }); |
1669 | | |
1670 | 58 | Ok(()) |
1671 | 865 | } |
1672 | | |
1673 | 1.07k | fn parse_exif(&mut self) -> Result<(), DecodingError> { |
1674 | 1.07k | let info = self.info.as_mut().unwrap(); |
1675 | 1.07k | if info.exif_metadata.is_some() { |
1676 | 852 | return Err(DecodingError::Format( |
1677 | 852 | FormatErrorInner::DuplicateChunk { kind: chunk::eXIf }.into(), |
1678 | 852 | )); |
1679 | 222 | } |
1680 | | |
1681 | 222 | info.exif_metadata = Some(self.current_chunk.raw_bytes.clone().into()); |
1682 | 222 | Ok(()) |
1683 | 1.07k | } |
1684 | | |
1685 | 7.70k | fn parse_iccp(&mut self) -> Result<(), DecodingError> { |
1686 | 7.70k | if self.have_idat { |
1687 | 588 | Err(DecodingError::Format( |
1688 | 588 | FormatErrorInner::AfterIdat { kind: chunk::iCCP }.into(), |
1689 | 588 | )) |
1690 | 7.11k | } else if self.have_iccp { |
1691 | 5.51k | Err(DecodingError::Format( |
1692 | 5.51k | FormatErrorInner::DuplicateChunk { kind: chunk::iCCP }.into(), |
1693 | 5.51k | )) |
1694 | | } else { |
1695 | 1.59k | self.have_iccp = true; |
1696 | 1.59k | let _ = self.parse_iccp_raw(); |
1697 | 1.59k | Ok(()) |
1698 | | } |
1699 | 7.70k | } |
1700 | | |
1701 | 1.59k | fn parse_iccp_raw(&mut self) -> Result<(), DecodingError> { |
1702 | 1.59k | let info = self.info.as_mut().unwrap(); |
1703 | 1.59k | let mut buf = &self.current_chunk.raw_bytes[..]; |
1704 | | |
1705 | | // read profile name |
1706 | 7.29k | for len in 0..=80 { |
1707 | 7.29k | let raw: u8 = buf.read_be()?; |
1708 | 7.18k | if (raw == 0 && len == 0) || (raw != 0 && len == 80) { |
1709 | 56 | return Err(DecodingError::from(TextDecodingError::InvalidKeywordSize)); |
1710 | 7.12k | } |
1711 | 7.12k | if raw == 0 { |
1712 | 1.43k | break; |
1713 | 5.69k | } |
1714 | | } |
1715 | | |
1716 | 1.43k | match buf.read_be()? { |
1717 | | // compression method |
1718 | 1.35k | 0u8 => (), |
1719 | 60 | n => { |
1720 | 60 | return Err(DecodingError::Format( |
1721 | 60 | FormatErrorInner::UnknownCompressionMethod(n).into(), |
1722 | 60 | )) |
1723 | | } |
1724 | | } |
1725 | | |
1726 | 1.35k | match fdeflate::decompress_to_vec_bounded(buf, self.limits.bytes) { |
1727 | 610 | Ok(profile) => { |
1728 | 610 | self.limits.reserve_bytes(profile.len())?; |
1729 | 610 | info.icc_profile = Some(Cow::Owned(profile)); |
1730 | | } |
1731 | 525 | Err(fdeflate::BoundedDecompressionError::DecompressionError { inner: err }) => { |
1732 | 525 | return Err(DecodingError::Format( |
1733 | 525 | FormatErrorInner::CorruptFlateStream { err }.into(), |
1734 | 525 | )) |
1735 | | } |
1736 | | Err(fdeflate::BoundedDecompressionError::OutputTooLarge { .. }) => { |
1737 | 222 | return Err(DecodingError::LimitsExceeded); |
1738 | | } |
1739 | | } |
1740 | | |
1741 | 610 | Ok(()) |
1742 | 1.59k | } |
1743 | | |
1744 | 40.3k | fn parse_ihdr(&mut self) -> Result<(), DecodingError> { |
1745 | 40.3k | if self.info.is_some() { |
1746 | 14 | return Err(DecodingError::Format( |
1747 | 14 | FormatErrorInner::DuplicateChunk { kind: IHDR }.into(), |
1748 | 14 | )); |
1749 | 40.3k | } |
1750 | 40.3k | let mut buf = &self.current_chunk.raw_bytes[..]; |
1751 | 40.3k | let width = buf.read_be()?; |
1752 | 40.3k | let height = buf.read_be()?; |
1753 | 40.3k | if width == 0 || height == 0 { |
1754 | 10 | return Err(DecodingError::Format( |
1755 | 10 | FormatErrorInner::InvalidDimensions.into(), |
1756 | 10 | )); |
1757 | 40.3k | } |
1758 | 40.3k | let bit_depth = buf.read_be()?; |
1759 | 40.3k | let bit_depth = match BitDepth::from_u8(bit_depth) { |
1760 | 40.2k | Some(bits) => bits, |
1761 | | None => { |
1762 | 110 | return Err(DecodingError::Format( |
1763 | 110 | FormatErrorInner::InvalidBitDepth(bit_depth).into(), |
1764 | 110 | )) |
1765 | | } |
1766 | | }; |
1767 | 40.2k | let color_type = buf.read_be()?; |
1768 | 40.2k | let color_type = match ColorType::from_u8(color_type) { |
1769 | 40.2k | Some(color_type) => { |
1770 | 40.2k | if color_type.is_combination_invalid(bit_depth) { |
1771 | 16 | return Err(DecodingError::Format( |
1772 | 16 | FormatErrorInner::InvalidColorBitDepth { |
1773 | 16 | color_type, |
1774 | 16 | bit_depth, |
1775 | 16 | } |
1776 | 16 | .into(), |
1777 | 16 | )); |
1778 | | } else { |
1779 | 40.2k | color_type |
1780 | | } |
1781 | | } |
1782 | | None => { |
1783 | 29 | return Err(DecodingError::Format( |
1784 | 29 | FormatErrorInner::InvalidColorType(color_type).into(), |
1785 | 29 | )) |
1786 | | } |
1787 | | }; |
1788 | 40.2k | match buf.read_be()? { |
1789 | | // compression method |
1790 | 40.1k | 0u8 => (), |
1791 | 42 | n => { |
1792 | 42 | return Err(DecodingError::Format( |
1793 | 42 | FormatErrorInner::UnknownCompressionMethod(n).into(), |
1794 | 42 | )) |
1795 | | } |
1796 | | } |
1797 | 40.1k | match buf.read_be()? { |
1798 | | // filter method |
1799 | 40.1k | 0u8 => (), |
1800 | 35 | n => { |
1801 | 35 | return Err(DecodingError::Format( |
1802 | 35 | FormatErrorInner::UnknownFilterMethod(n).into(), |
1803 | 35 | )) |
1804 | | } |
1805 | | } |
1806 | 40.1k | let interlaced = match buf.read_be()? { |
1807 | 28.5k | 0u8 => false, |
1808 | 11.5k | 1 => true, |
1809 | 29 | n => { |
1810 | 29 | return Err(DecodingError::Format( |
1811 | 29 | FormatErrorInner::UnknownInterlaceMethod(n).into(), |
1812 | 29 | )) |
1813 | | } |
1814 | | }; |
1815 | | |
1816 | 40.1k | self.info = Some(Info { |
1817 | 40.1k | width, |
1818 | 40.1k | height, |
1819 | 40.1k | bit_depth, |
1820 | 40.1k | color_type, |
1821 | 40.1k | interlaced, |
1822 | 40.1k | ..Default::default() |
1823 | 40.1k | }); |
1824 | | |
1825 | 40.1k | Ok(()) |
1826 | 40.3k | } |
1827 | | |
1828 | 445k | fn split_keyword(buf: &[u8]) -> Result<(&[u8], &[u8]), DecodingError> { |
1829 | 445k | let null_byte_index = buf |
1830 | 445k | .iter() |
1831 | 1.88M | .position(|&b| b == 0) |
1832 | 445k | .ok_or_else(|| DecodingError::from(TextDecodingError::MissingNullSeparator))?; |
1833 | | |
1834 | 433k | if null_byte_index == 0 || null_byte_index > 79 { |
1835 | 4.96k | return Err(DecodingError::from(TextDecodingError::InvalidKeywordSize)); |
1836 | 428k | } |
1837 | | |
1838 | 428k | Ok((&buf[..null_byte_index], &buf[null_byte_index + 1..])) |
1839 | 445k | } |
1840 | | |
1841 | 323k | fn parse_text(&mut self) -> Result<(), DecodingError> { |
1842 | 323k | let buf = &self.current_chunk.raw_bytes[..]; |
1843 | 323k | self.limits.reserve_bytes(buf.len())?; |
1844 | | |
1845 | 323k | let (keyword_slice, value_slice) = Self::split_keyword(buf)?; |
1846 | | |
1847 | 311k | self.info |
1848 | 311k | .as_mut() |
1849 | 311k | .unwrap() |
1850 | 311k | .uncompressed_latin1_text |
1851 | 311k | .push(TEXtChunk::decode(keyword_slice, value_slice).map_err(DecodingError::from)?); |
1852 | | |
1853 | 311k | Ok(()) |
1854 | 323k | } |
1855 | | |
1856 | 43.0k | fn parse_ztxt(&mut self) -> Result<(), DecodingError> { |
1857 | 43.0k | let buf = &self.current_chunk.raw_bytes[..]; |
1858 | 43.0k | self.limits.reserve_bytes(buf.len())?; |
1859 | | |
1860 | 43.0k | let (keyword_slice, value_slice) = Self::split_keyword(buf)?; |
1861 | | |
1862 | 40.3k | let compression_method = *value_slice |
1863 | 40.3k | .first() |
1864 | 40.3k | .ok_or_else(|| DecodingError::from(TextDecodingError::InvalidCompressionMethod))?; |
1865 | | |
1866 | 40.0k | let text_slice = &value_slice[1..]; |
1867 | | |
1868 | 40.0k | self.info.as_mut().unwrap().compressed_latin1_text.push( |
1869 | 40.0k | ZTXtChunk::decode(keyword_slice, compression_method, text_slice) |
1870 | 40.0k | .map_err(DecodingError::from)?, |
1871 | | ); |
1872 | | |
1873 | 38.6k | Ok(()) |
1874 | 43.0k | } |
1875 | | |
1876 | 79.3k | fn parse_itxt(&mut self) -> Result<(), DecodingError> { |
1877 | 79.3k | let buf = &self.current_chunk.raw_bytes[..]; |
1878 | 79.3k | self.limits.reserve_bytes(buf.len())?; |
1879 | | |
1880 | 79.3k | let (keyword_slice, value_slice) = Self::split_keyword(buf)?; |
1881 | | |
1882 | 75.8k | let compression_flag = *value_slice |
1883 | 75.8k | .first() |
1884 | 75.8k | .ok_or_else(|| DecodingError::from(TextDecodingError::MissingCompressionFlag))?; |
1885 | | |
1886 | 75.5k | let compression_method = *value_slice |
1887 | 75.5k | .get(1) |
1888 | 75.5k | .ok_or_else(|| DecodingError::from(TextDecodingError::InvalidCompressionMethod))?; |
1889 | | |
1890 | 75.0k | let second_null_byte_index = value_slice[2..] |
1891 | 75.0k | .iter() |
1892 | 855k | .position(|&b| b == 0) |
1893 | 75.0k | .ok_or_else(|| DecodingError::from(TextDecodingError::MissingNullSeparator))? |
1894 | | + 2; |
1895 | | |
1896 | 73.8k | let language_tag_slice = &value_slice[2..second_null_byte_index]; |
1897 | | |
1898 | 73.8k | let third_null_byte_index = value_slice[second_null_byte_index + 1..] |
1899 | 73.8k | .iter() |
1900 | 349k | .position(|&b| b == 0) |
1901 | 73.8k | .ok_or_else(|| DecodingError::from(TextDecodingError::MissingNullSeparator))? |
1902 | 71.7k | + (second_null_byte_index + 1); |
1903 | | |
1904 | 71.7k | let translated_keyword_slice = |
1905 | 71.7k | &value_slice[second_null_byte_index + 1..third_null_byte_index]; |
1906 | | |
1907 | 71.7k | let text_slice = &value_slice[third_null_byte_index + 1..]; |
1908 | | |
1909 | 71.7k | self.info.as_mut().unwrap().utf8_text.push( |
1910 | 71.7k | ITXtChunk::decode( |
1911 | 71.7k | keyword_slice, |
1912 | 71.7k | compression_flag, |
1913 | 71.7k | compression_method, |
1914 | 71.7k | language_tag_slice, |
1915 | 71.7k | translated_keyword_slice, |
1916 | 71.7k | text_slice, |
1917 | | ) |
1918 | 71.7k | .map_err(DecodingError::from)?, |
1919 | | ); |
1920 | | |
1921 | 67.1k | Ok(()) |
1922 | 79.3k | } |
1923 | | |
1924 | 8.69k | fn parse_bkgd(&mut self) -> Result<(), DecodingError> { |
1925 | 8.69k | let info = self.info.as_mut().unwrap(); |
1926 | 8.69k | if info.bkgd.is_some() { |
1927 | | // Only one bKGD chunk is allowed |
1928 | 1.58k | return Err(DecodingError::Format( |
1929 | 1.58k | FormatErrorInner::DuplicateChunk { kind: chunk::bKGD }.into(), |
1930 | 1.58k | )); |
1931 | 7.11k | } else if self.have_idat { |
1932 | 1.39k | return Err(DecodingError::Format( |
1933 | 1.39k | FormatErrorInner::AfterIdat { kind: chunk::bKGD }.into(), |
1934 | 1.39k | )); |
1935 | 5.72k | } |
1936 | | |
1937 | 5.72k | let expected = match info.color_type { |
1938 | | ColorType::Indexed => { |
1939 | 531 | if info.palette.is_none() { |
1940 | 5 | return Err(DecodingError::IoError( |
1941 | 5 | std::io::ErrorKind::InvalidData.into(), |
1942 | 5 | )); |
1943 | 526 | }; |
1944 | 526 | 1 |
1945 | | } |
1946 | 2.98k | ColorType::Grayscale | ColorType::GrayscaleAlpha => 2, |
1947 | 2.20k | ColorType::Rgb | ColorType::Rgba => 6, |
1948 | | }; |
1949 | 5.71k | let vec = self.current_chunk.raw_bytes.clone(); |
1950 | 5.71k | if vec.len() != expected { |
1951 | 5.60k | return Err(DecodingError::Format( |
1952 | 5.60k | FormatErrorInner::ChunkLengthWrong { kind: chunk::bKGD }.into(), |
1953 | 5.60k | )); |
1954 | 111 | } |
1955 | | |
1956 | 111 | info.bkgd = Some(Cow::Owned(vec)); |
1957 | 111 | Ok(()) |
1958 | 8.69k | } |
1959 | | } |
1960 | | |
1961 | | impl Info<'_> { |
1962 | 1.49k | fn validate_default_image(&self, fc: &FrameControl) -> Result<(), DecodingError> { |
1963 | | // https://www.w3.org/TR/png-3/#fcTL-chunk says that: |
1964 | | // |
1965 | | // > The fcTL chunk corresponding to the default image, if it exists, has these |
1966 | | // > restrictions: |
1967 | | // > |
1968 | | // > * The x_offset and y_offset fields must be 0. |
1969 | | // > * The width and height fields must equal |
1970 | | // > the corresponding fields from the IHDR chunk. |
1971 | 1.49k | if fc.x_offset != 0 |
1972 | 1.30k | || fc.y_offset != 0 |
1973 | 1.14k | || fc.width != self.width |
1974 | 1.00k | || fc.height != self.height |
1975 | | { |
1976 | 618 | return Err(DecodingError::Format( |
1977 | 618 | FormatErrorInner::BadSubFrameBounds {}.into(), |
1978 | 618 | )); |
1979 | 880 | } |
1980 | 880 | Ok(()) |
1981 | 1.49k | } |
1982 | | |
1983 | 4.25k | fn validate(&self, fc: &FrameControl) -> Result<(), DecodingError> { |
1984 | 4.25k | if fc.width == 0 || fc.height == 0 { |
1985 | 17 | return Err(DecodingError::Format( |
1986 | 17 | FormatErrorInner::InvalidDimensions.into(), |
1987 | 17 | )); |
1988 | 4.23k | } |
1989 | | |
1990 | | // Validate mathematically: fc.width + fc.x_offset <= self.width |
1991 | 4.23k | let in_x_bounds = Some(fc.width) <= self.width.checked_sub(fc.x_offset); |
1992 | | // Validate mathematically: fc.height + fc.y_offset <= self.height |
1993 | 4.23k | let in_y_bounds = Some(fc.height) <= self.height.checked_sub(fc.y_offset); |
1994 | | |
1995 | 4.23k | if !in_x_bounds || !in_y_bounds { |
1996 | 373 | return Err(DecodingError::Format( |
1997 | 373 | // TODO: do we want to display the bad bounds? |
1998 | 373 | FormatErrorInner::BadSubFrameBounds {}.into(), |
1999 | 373 | )); |
2000 | 3.86k | } |
2001 | | |
2002 | 3.86k | Ok(()) |
2003 | 4.25k | } |
2004 | | } |
2005 | | |
2006 | | impl Default for StreamingDecoder { |
2007 | 0 | fn default() -> Self { |
2008 | 0 | Self::new() |
2009 | 0 | } |
2010 | | } |
2011 | | |
2012 | | #[cfg(test)] |
2013 | | mod tests { |
2014 | | use super::ScaledFloat; |
2015 | | use super::SourceChromaticities; |
2016 | | use crate::test_utils::*; |
2017 | | use crate::{Decoder, DecodingError, Reader, SrgbRenderingIntent, Unit}; |
2018 | | use approx::assert_relative_eq; |
2019 | | use byteorder::WriteBytesExt; |
2020 | | use std::borrow::Cow; |
2021 | | use std::cell::RefCell; |
2022 | | |
2023 | | use std::fs::File; |
2024 | | use std::io::BufRead; |
2025 | | use std::io::Cursor; |
2026 | | use std::io::Seek; |
2027 | | use std::io::{BufReader, ErrorKind, Read, Write}; |
2028 | | use std::rc::Rc; |
2029 | | |
2030 | | #[test] |
2031 | | fn image_gamma() -> Result<(), ()> { |
2032 | | fn trial(path: &str, expected: Option<ScaledFloat>) { |
2033 | | let decoder = crate::Decoder::new(BufReader::new(File::open(path).unwrap())); |
2034 | | let reader = decoder.read_info().unwrap(); |
2035 | | let actual: Option<ScaledFloat> = reader.info().gamma(); |
2036 | | assert!(actual == expected); |
2037 | | } |
2038 | | trial("tests/pngsuite/f00n0g08.png", None); |
2039 | | trial("tests/pngsuite/f00n2c08.png", None); |
2040 | | trial("tests/pngsuite/f01n0g08.png", None); |
2041 | | trial("tests/pngsuite/f01n2c08.png", None); |
2042 | | trial("tests/pngsuite/f02n0g08.png", None); |
2043 | | trial("tests/pngsuite/f02n2c08.png", None); |
2044 | | trial("tests/pngsuite/f03n0g08.png", None); |
2045 | | trial("tests/pngsuite/f03n2c08.png", None); |
2046 | | trial("tests/pngsuite/f04n0g08.png", None); |
2047 | | trial("tests/pngsuite/f04n2c08.png", None); |
2048 | | trial("tests/pngsuite/f99n0g04.png", None); |
2049 | | trial("tests/pngsuite/tm3n3p02.png", None); |
2050 | | trial("tests/pngsuite/g03n0g16.png", Some(ScaledFloat::new(0.35))); |
2051 | | trial("tests/pngsuite/g03n2c08.png", Some(ScaledFloat::new(0.35))); |
2052 | | trial("tests/pngsuite/g03n3p04.png", Some(ScaledFloat::new(0.35))); |
2053 | | trial("tests/pngsuite/g04n0g16.png", Some(ScaledFloat::new(0.45))); |
2054 | | trial("tests/pngsuite/g04n2c08.png", Some(ScaledFloat::new(0.45))); |
2055 | | trial("tests/pngsuite/g04n3p04.png", Some(ScaledFloat::new(0.45))); |
2056 | | trial("tests/pngsuite/g05n0g16.png", Some(ScaledFloat::new(0.55))); |
2057 | | trial("tests/pngsuite/g05n2c08.png", Some(ScaledFloat::new(0.55))); |
2058 | | trial("tests/pngsuite/g05n3p04.png", Some(ScaledFloat::new(0.55))); |
2059 | | trial("tests/pngsuite/g07n0g16.png", Some(ScaledFloat::new(0.7))); |
2060 | | trial("tests/pngsuite/g07n2c08.png", Some(ScaledFloat::new(0.7))); |
2061 | | trial("tests/pngsuite/g07n3p04.png", Some(ScaledFloat::new(0.7))); |
2062 | | trial("tests/pngsuite/g10n0g16.png", Some(ScaledFloat::new(1.0))); |
2063 | | trial("tests/pngsuite/g10n2c08.png", Some(ScaledFloat::new(1.0))); |
2064 | | trial("tests/pngsuite/g10n3p04.png", Some(ScaledFloat::new(1.0))); |
2065 | | trial("tests/pngsuite/g25n0g16.png", Some(ScaledFloat::new(2.5))); |
2066 | | trial("tests/pngsuite/g25n2c08.png", Some(ScaledFloat::new(2.5))); |
2067 | | trial("tests/pngsuite/g25n3p04.png", Some(ScaledFloat::new(2.5))); |
2068 | | Ok(()) |
2069 | | } |
2070 | | |
2071 | | #[test] |
2072 | | fn image_source_chromaticities() -> Result<(), ()> { |
2073 | | fn trial(path: &str, expected: Option<SourceChromaticities>) { |
2074 | | let decoder = crate::Decoder::new(BufReader::new(File::open(path).unwrap())); |
2075 | | let reader = decoder.read_info().unwrap(); |
2076 | | let actual: Option<SourceChromaticities> = reader.info().chromaticities(); |
2077 | | assert!(actual == expected); |
2078 | | } |
2079 | | trial( |
2080 | | "tests/pngsuite/ccwn2c08.png", |
2081 | | Some(SourceChromaticities::new( |
2082 | | (0.3127, 0.3290), |
2083 | | (0.64, 0.33), |
2084 | | (0.30, 0.60), |
2085 | | (0.15, 0.06), |
2086 | | )), |
2087 | | ); |
2088 | | trial( |
2089 | | "tests/pngsuite/ccwn3p08.png", |
2090 | | Some(SourceChromaticities::new( |
2091 | | (0.3127, 0.3290), |
2092 | | (0.64, 0.33), |
2093 | | (0.30, 0.60), |
2094 | | (0.15, 0.06), |
2095 | | )), |
2096 | | ); |
2097 | | trial("tests/pngsuite/basi0g01.png", None); |
2098 | | trial("tests/pngsuite/basi0g02.png", None); |
2099 | | trial("tests/pngsuite/basi0g04.png", None); |
2100 | | trial("tests/pngsuite/basi0g08.png", None); |
2101 | | trial("tests/pngsuite/basi0g16.png", None); |
2102 | | trial("tests/pngsuite/basi2c08.png", None); |
2103 | | trial("tests/pngsuite/basi2c16.png", None); |
2104 | | trial("tests/pngsuite/basi3p01.png", None); |
2105 | | trial("tests/pngsuite/basi3p02.png", None); |
2106 | | trial("tests/pngsuite/basi3p04.png", None); |
2107 | | trial("tests/pngsuite/basi3p08.png", None); |
2108 | | trial("tests/pngsuite/basi4a08.png", None); |
2109 | | trial("tests/pngsuite/basi4a16.png", None); |
2110 | | trial("tests/pngsuite/basi6a08.png", None); |
2111 | | trial("tests/pngsuite/basi6a16.png", None); |
2112 | | trial("tests/pngsuite/basn0g01.png", None); |
2113 | | trial("tests/pngsuite/basn0g02.png", None); |
2114 | | trial("tests/pngsuite/basn0g04.png", None); |
2115 | | trial("tests/pngsuite/basn0g08.png", None); |
2116 | | trial("tests/pngsuite/basn0g16.png", None); |
2117 | | trial("tests/pngsuite/basn2c08.png", None); |
2118 | | trial("tests/pngsuite/basn2c16.png", None); |
2119 | | trial("tests/pngsuite/basn3p01.png", None); |
2120 | | trial("tests/pngsuite/basn3p02.png", None); |
2121 | | trial("tests/pngsuite/basn3p04.png", None); |
2122 | | trial("tests/pngsuite/basn3p08.png", None); |
2123 | | trial("tests/pngsuite/basn4a08.png", None); |
2124 | | trial("tests/pngsuite/basn4a16.png", None); |
2125 | | trial("tests/pngsuite/basn6a08.png", None); |
2126 | | trial("tests/pngsuite/basn6a16.png", None); |
2127 | | trial("tests/pngsuite/bgai4a08.png", None); |
2128 | | trial("tests/pngsuite/bgai4a16.png", None); |
2129 | | trial("tests/pngsuite/bgan6a08.png", None); |
2130 | | trial("tests/pngsuite/bgan6a16.png", None); |
2131 | | trial("tests/pngsuite/bgbn4a08.png", None); |
2132 | | trial("tests/pngsuite/bggn4a16.png", None); |
2133 | | trial("tests/pngsuite/bgwn6a08.png", None); |
2134 | | trial("tests/pngsuite/bgyn6a16.png", None); |
2135 | | trial("tests/pngsuite/cdfn2c08.png", None); |
2136 | | trial("tests/pngsuite/cdhn2c08.png", None); |
2137 | | trial("tests/pngsuite/cdsn2c08.png", None); |
2138 | | trial("tests/pngsuite/cdun2c08.png", None); |
2139 | | trial("tests/pngsuite/ch1n3p04.png", None); |
2140 | | trial("tests/pngsuite/ch2n3p08.png", None); |
2141 | | trial("tests/pngsuite/cm0n0g04.png", None); |
2142 | | trial("tests/pngsuite/cm7n0g04.png", None); |
2143 | | trial("tests/pngsuite/cm9n0g04.png", None); |
2144 | | trial("tests/pngsuite/cs3n2c16.png", None); |
2145 | | trial("tests/pngsuite/cs3n3p08.png", None); |
2146 | | trial("tests/pngsuite/cs5n2c08.png", None); |
2147 | | trial("tests/pngsuite/cs5n3p08.png", None); |
2148 | | trial("tests/pngsuite/cs8n2c08.png", None); |
2149 | | trial("tests/pngsuite/cs8n3p08.png", None); |
2150 | | trial("tests/pngsuite/ct0n0g04.png", None); |
2151 | | trial("tests/pngsuite/ct1n0g04.png", None); |
2152 | | trial("tests/pngsuite/cten0g04.png", None); |
2153 | | trial("tests/pngsuite/ctfn0g04.png", None); |
2154 | | trial("tests/pngsuite/ctgn0g04.png", None); |
2155 | | trial("tests/pngsuite/cthn0g04.png", None); |
2156 | | trial("tests/pngsuite/ctjn0g04.png", None); |
2157 | | trial("tests/pngsuite/ctzn0g04.png", None); |
2158 | | trial("tests/pngsuite/f00n0g08.png", None); |
2159 | | trial("tests/pngsuite/f00n2c08.png", None); |
2160 | | trial("tests/pngsuite/f01n0g08.png", None); |
2161 | | trial("tests/pngsuite/f01n2c08.png", None); |
2162 | | trial("tests/pngsuite/f02n0g08.png", None); |
2163 | | trial("tests/pngsuite/f02n2c08.png", None); |
2164 | | trial("tests/pngsuite/f03n0g08.png", None); |
2165 | | trial("tests/pngsuite/f03n2c08.png", None); |
2166 | | trial("tests/pngsuite/f04n0g08.png", None); |
2167 | | trial("tests/pngsuite/f04n2c08.png", None); |
2168 | | trial("tests/pngsuite/f99n0g04.png", None); |
2169 | | trial("tests/pngsuite/g03n0g16.png", None); |
2170 | | trial("tests/pngsuite/g03n2c08.png", None); |
2171 | | trial("tests/pngsuite/g03n3p04.png", None); |
2172 | | trial("tests/pngsuite/g04n0g16.png", None); |
2173 | | trial("tests/pngsuite/g04n2c08.png", None); |
2174 | | trial("tests/pngsuite/g04n3p04.png", None); |
2175 | | trial("tests/pngsuite/g05n0g16.png", None); |
2176 | | trial("tests/pngsuite/g05n2c08.png", None); |
2177 | | trial("tests/pngsuite/g05n3p04.png", None); |
2178 | | trial("tests/pngsuite/g07n0g16.png", None); |
2179 | | trial("tests/pngsuite/g07n2c08.png", None); |
2180 | | trial("tests/pngsuite/g07n3p04.png", None); |
2181 | | trial("tests/pngsuite/g10n0g16.png", None); |
2182 | | trial("tests/pngsuite/g10n2c08.png", None); |
2183 | | trial("tests/pngsuite/g10n3p04.png", None); |
2184 | | trial("tests/pngsuite/g25n0g16.png", None); |
2185 | | trial("tests/pngsuite/g25n2c08.png", None); |
2186 | | trial("tests/pngsuite/g25n3p04.png", None); |
2187 | | trial("tests/pngsuite/oi1n0g16.png", None); |
2188 | | trial("tests/pngsuite/oi1n2c16.png", None); |
2189 | | trial("tests/pngsuite/oi2n0g16.png", None); |
2190 | | trial("tests/pngsuite/oi2n2c16.png", None); |
2191 | | trial("tests/pngsuite/oi4n0g16.png", None); |
2192 | | trial("tests/pngsuite/oi4n2c16.png", None); |
2193 | | trial("tests/pngsuite/oi9n0g16.png", None); |
2194 | | trial("tests/pngsuite/oi9n2c16.png", None); |
2195 | | trial("tests/pngsuite/PngSuite.png", None); |
2196 | | trial("tests/pngsuite/pp0n2c16.png", None); |
2197 | | trial("tests/pngsuite/pp0n6a08.png", None); |
2198 | | trial("tests/pngsuite/ps1n0g08.png", None); |
2199 | | trial("tests/pngsuite/ps1n2c16.png", None); |
2200 | | trial("tests/pngsuite/ps2n0g08.png", None); |
2201 | | trial("tests/pngsuite/ps2n2c16.png", None); |
2202 | | trial("tests/pngsuite/s01i3p01.png", None); |
2203 | | trial("tests/pngsuite/s01n3p01.png", None); |
2204 | | trial("tests/pngsuite/s02i3p01.png", None); |
2205 | | trial("tests/pngsuite/s02n3p01.png", None); |
2206 | | trial("tests/pngsuite/s03i3p01.png", None); |
2207 | | trial("tests/pngsuite/s03n3p01.png", None); |
2208 | | trial("tests/pngsuite/s04i3p01.png", None); |
2209 | | trial("tests/pngsuite/s04n3p01.png", None); |
2210 | | trial("tests/pngsuite/s05i3p02.png", None); |
2211 | | trial("tests/pngsuite/s05n3p02.png", None); |
2212 | | trial("tests/pngsuite/s06i3p02.png", None); |
2213 | | trial("tests/pngsuite/s06n3p02.png", None); |
2214 | | trial("tests/pngsuite/s07i3p02.png", None); |
2215 | | trial("tests/pngsuite/s07n3p02.png", None); |
2216 | | trial("tests/pngsuite/s08i3p02.png", None); |
2217 | | trial("tests/pngsuite/s08n3p02.png", None); |
2218 | | trial("tests/pngsuite/s09i3p02.png", None); |
2219 | | trial("tests/pngsuite/s09n3p02.png", None); |
2220 | | trial("tests/pngsuite/s32i3p04.png", None); |
2221 | | trial("tests/pngsuite/s32n3p04.png", None); |
2222 | | trial("tests/pngsuite/s33i3p04.png", None); |
2223 | | trial("tests/pngsuite/s33n3p04.png", None); |
2224 | | trial("tests/pngsuite/s34i3p04.png", None); |
2225 | | trial("tests/pngsuite/s34n3p04.png", None); |
2226 | | trial("tests/pngsuite/s35i3p04.png", None); |
2227 | | trial("tests/pngsuite/s35n3p04.png", None); |
2228 | | trial("tests/pngsuite/s36i3p04.png", None); |
2229 | | trial("tests/pngsuite/s36n3p04.png", None); |
2230 | | trial("tests/pngsuite/s37i3p04.png", None); |
2231 | | trial("tests/pngsuite/s37n3p04.png", None); |
2232 | | trial("tests/pngsuite/s38i3p04.png", None); |
2233 | | trial("tests/pngsuite/s38n3p04.png", None); |
2234 | | trial("tests/pngsuite/s39i3p04.png", None); |
2235 | | trial("tests/pngsuite/s39n3p04.png", None); |
2236 | | trial("tests/pngsuite/s40i3p04.png", None); |
2237 | | trial("tests/pngsuite/s40n3p04.png", None); |
2238 | | trial("tests/pngsuite/tbbn0g04.png", None); |
2239 | | trial("tests/pngsuite/tbbn2c16.png", None); |
2240 | | trial("tests/pngsuite/tbbn3p08.png", None); |
2241 | | trial("tests/pngsuite/tbgn2c16.png", None); |
2242 | | trial("tests/pngsuite/tbgn3p08.png", None); |
2243 | | trial("tests/pngsuite/tbrn2c08.png", None); |
2244 | | trial("tests/pngsuite/tbwn0g16.png", None); |
2245 | | trial("tests/pngsuite/tbwn3p08.png", None); |
2246 | | trial("tests/pngsuite/tbyn3p08.png", None); |
2247 | | trial("tests/pngsuite/tm3n3p02.png", None); |
2248 | | trial("tests/pngsuite/tp0n0g08.png", None); |
2249 | | trial("tests/pngsuite/tp0n2c08.png", None); |
2250 | | trial("tests/pngsuite/tp0n3p08.png", None); |
2251 | | trial("tests/pngsuite/tp1n3p08.png", None); |
2252 | | trial("tests/pngsuite/z00n2c08.png", None); |
2253 | | trial("tests/pngsuite/z03n2c08.png", None); |
2254 | | trial("tests/pngsuite/z06n2c08.png", None); |
2255 | | Ok(()) |
2256 | | } |
2257 | | |
2258 | | #[test] |
2259 | | fn image_source_sbit() { |
2260 | | fn trial(path: &str, expected: Option<Cow<[u8]>>) { |
2261 | | let decoder = crate::Decoder::new(BufReader::new(File::open(path).unwrap())); |
2262 | | let reader = decoder.read_info().unwrap(); |
2263 | | let actual: Option<Cow<[u8]>> = reader.info().sbit.clone(); |
2264 | | assert!(actual == expected); |
2265 | | } |
2266 | | |
2267 | | trial("tests/sbit/g.png", Some(Cow::Owned(vec![5u8]))); |
2268 | | trial("tests/sbit/ga.png", Some(Cow::Owned(vec![5u8, 3u8]))); |
2269 | | trial( |
2270 | | "tests/sbit/indexed.png", |
2271 | | Some(Cow::Owned(vec![5u8, 6u8, 5u8])), |
2272 | | ); |
2273 | | trial("tests/sbit/rgb.png", Some(Cow::Owned(vec![5u8, 6u8, 5u8]))); |
2274 | | trial( |
2275 | | "tests/sbit/rgba.png", |
2276 | | Some(Cow::Owned(vec![5u8, 6u8, 5u8, 8u8])), |
2277 | | ); |
2278 | | } |
2279 | | |
2280 | | /// Test handling of a PNG file that contains *two* iCCP chunks. |
2281 | | /// This is a regression test for https://github.com/image-rs/image/issues/1825. |
2282 | | #[test] |
2283 | | fn test_two_iccp_chunks() { |
2284 | | // The test file has been taken from |
2285 | | // https://github.com/image-rs/image/issues/1825#issuecomment-1321798639, |
2286 | | // but the 2nd iCCP chunk has been altered manually (see the 2nd comment below for more |
2287 | | // details). |
2288 | | let decoder = crate::Decoder::new(BufReader::new( |
2289 | | File::open("tests/bugfixes/issue#1825.png").unwrap(), |
2290 | | )); |
2291 | | let reader = decoder.read_info().unwrap(); |
2292 | | let icc_profile = reader.info().icc_profile.clone().unwrap().into_owned(); |
2293 | | |
2294 | | // Assert that the contents of the *first* iCCP chunk are returned. |
2295 | | // |
2296 | | // Note that the 2nd chunk in the test file has been manually altered to have a different |
2297 | | // content (`b"test iccp contents"`) which would have a different CRC (797351983). |
2298 | | assert_eq!(4070462061, crc32fast::hash(&icc_profile)); |
2299 | | } |
2300 | | |
2301 | | #[test] |
2302 | | fn test_iccp_roundtrip() { |
2303 | | let dummy_icc = b"I'm a profile"; |
2304 | | |
2305 | | let mut info = crate::Info::with_size(1, 1); |
2306 | | info.icc_profile = Some(dummy_icc.into()); |
2307 | | let mut encoded_image = Vec::new(); |
2308 | | let enc = crate::Encoder::with_info(&mut encoded_image, info).unwrap(); |
2309 | | let mut enc = enc.write_header().unwrap(); |
2310 | | enc.write_image_data(&[0]).unwrap(); |
2311 | | enc.finish().unwrap(); |
2312 | | |
2313 | | let dec = crate::Decoder::new(Cursor::new(&encoded_image)); |
2314 | | let dec = dec.read_info().unwrap(); |
2315 | | assert_eq!(dummy_icc, &**dec.info().icc_profile.as_ref().unwrap()); |
2316 | | } |
2317 | | |
2318 | | #[test] |
2319 | | fn test_phys_roundtrip() { |
2320 | | let mut info = crate::Info::with_size(1, 1); |
2321 | | info.pixel_dims = Some(crate::PixelDimensions { |
2322 | | xppu: 12, |
2323 | | yppu: 34, |
2324 | | unit: Unit::Meter, |
2325 | | }); |
2326 | | let mut encoded_image = Vec::new(); |
2327 | | let enc = crate::Encoder::with_info(&mut encoded_image, info).unwrap(); |
2328 | | let mut enc = enc.write_header().unwrap(); |
2329 | | enc.write_image_data(&[0]).unwrap(); |
2330 | | enc.finish().unwrap(); |
2331 | | |
2332 | | let dec = crate::Decoder::new(Cursor::new(&encoded_image)); |
2333 | | let dec = dec.read_info().unwrap(); |
2334 | | let phys = dec.info().pixel_dims.as_ref().unwrap(); |
2335 | | assert_eq!(phys.xppu, 12); |
2336 | | assert_eq!(phys.yppu, 34); |
2337 | | assert_eq!(phys.unit, Unit::Meter); |
2338 | | } |
2339 | | |
2340 | | #[test] |
2341 | | fn test_srgb_roundtrip() { |
2342 | | let mut info = crate::Info::with_size(1, 1); |
2343 | | info.srgb = Some(SrgbRenderingIntent::Saturation); |
2344 | | let mut encoded_image = Vec::new(); |
2345 | | let enc = crate::Encoder::with_info(&mut encoded_image, info).unwrap(); |
2346 | | let mut enc = enc.write_header().unwrap(); |
2347 | | enc.write_image_data(&[0]).unwrap(); |
2348 | | enc.finish().unwrap(); |
2349 | | |
2350 | | let dec = crate::Decoder::new(Cursor::new(&encoded_image)); |
2351 | | let dec = dec.read_info().unwrap(); |
2352 | | assert_eq!(dec.info().srgb.unwrap(), SrgbRenderingIntent::Saturation); |
2353 | | } |
2354 | | |
2355 | | #[test] |
2356 | | fn test_png_with_broken_iccp() { |
2357 | | let decoder = crate::Decoder::new(BufReader::new( |
2358 | | File::open("tests/iccp/broken_iccp.png").unwrap(), |
2359 | | )); |
2360 | | assert!(decoder.read_info().is_ok()); |
2361 | | let mut decoder = crate::Decoder::new(BufReader::new( |
2362 | | File::open("tests/iccp/broken_iccp.png").unwrap(), |
2363 | | )); |
2364 | | decoder.set_ignore_iccp_chunk(true); |
2365 | | assert!(decoder.read_info().is_ok()); |
2366 | | } |
2367 | | |
2368 | | /// Test handling of `cICP`, `mDCV`, and `cLLI` chunks. |
2369 | | #[test] |
2370 | | fn test_cicp_mdcv_and_clli_chunks() { |
2371 | | let mut decoder = crate::Decoder::new(BufReader::new( |
2372 | | File::open("tests/bugfixes/cicp_pq.png").unwrap(), |
2373 | | )); |
2374 | | decoder.ignore_checksums(true); |
2375 | | let reader = decoder.read_info().unwrap(); |
2376 | | let info = reader.info(); |
2377 | | |
2378 | | let cicp = info.coding_independent_code_points.unwrap(); |
2379 | | assert_eq!(cicp.color_primaries, 9); |
2380 | | assert_eq!(cicp.transfer_function, 16); |
2381 | | assert_eq!(cicp.matrix_coefficients, 0); |
2382 | | assert!(cicp.is_video_full_range_image); |
2383 | | |
2384 | | let mdcv = info.mastering_display_color_volume.unwrap(); |
2385 | | assert_relative_eq!(mdcv.chromaticities.red.0.into_value(), 0.680); |
2386 | | assert_relative_eq!(mdcv.chromaticities.red.1.into_value(), 0.320); |
2387 | | assert_relative_eq!(mdcv.chromaticities.green.0.into_value(), 0.265); |
2388 | | assert_relative_eq!(mdcv.chromaticities.green.1.into_value(), 0.690); |
2389 | | assert_relative_eq!(mdcv.chromaticities.blue.0.into_value(), 0.150); |
2390 | | assert_relative_eq!(mdcv.chromaticities.blue.1.into_value(), 0.060); |
2391 | | assert_relative_eq!(mdcv.chromaticities.white.0.into_value(), 0.3127); |
2392 | | assert_relative_eq!(mdcv.chromaticities.white.1.into_value(), 0.3290); |
2393 | | assert_relative_eq!(mdcv.min_luminance as f32 / 10_000.0, 0.01); |
2394 | | assert_relative_eq!(mdcv.max_luminance as f32 / 10_000.0, 5000.0); |
2395 | | |
2396 | | let clli = info.content_light_level.unwrap(); |
2397 | | assert_relative_eq!(clli.max_content_light_level as f32 / 10_000.0, 4000.0); |
2398 | | assert_relative_eq!(clli.max_frame_average_light_level as f32 / 10_000.0, 2627.0); |
2399 | | } |
2400 | | |
2401 | | /// Test handling of `eXIf` chunk. |
2402 | | #[test] |
2403 | | fn test_exif_chunk() { |
2404 | | let decoder = crate::Decoder::new(BufReader::new( |
2405 | | File::open("tests/bugfixes/F-exif-chunk-early.png").unwrap(), |
2406 | | )); |
2407 | | let reader = decoder.read_info().unwrap(); |
2408 | | let info = reader.info(); |
2409 | | let exif = info.exif_metadata.as_ref().unwrap().as_ref(); |
2410 | | assert_eq!(exif.len(), 90); |
2411 | | } |
2412 | | |
2413 | | /// Tests what happens then [`Reader.finish`] is called twice. |
2414 | | #[test] |
2415 | | fn test_finishing_twice() { |
2416 | | let mut png = Vec::new(); |
2417 | | write_noncompressed_png(&mut png, 16, 1024); |
2418 | | let decoder = Decoder::new(Cursor::new(&png)); |
2419 | | let mut reader = decoder.read_info().unwrap(); |
2420 | | |
2421 | | // First call to `finish` - expecting success. |
2422 | | reader.finish().unwrap(); |
2423 | | |
2424 | | // Second call to `finish` - expecting an error. |
2425 | | let err = reader.finish().unwrap_err(); |
2426 | | assert!(matches!(&err, DecodingError::Parameter(_))); |
2427 | | assert_eq!("End of image has been reached", format!("{err}")); |
2428 | | } |
2429 | | |
2430 | | /// Writes an acTL chunk. |
2431 | | /// See https://wiki.mozilla.org/APNG_Specification#.60acTL.60:_The_Animation_Control_Chunk |
2432 | | fn write_actl(w: &mut impl Write, animation: &crate::AnimationControl) { |
2433 | | let mut data = Vec::new(); |
2434 | | data.write_u32::<byteorder::BigEndian>(animation.num_frames) |
2435 | | .unwrap(); |
2436 | | data.write_u32::<byteorder::BigEndian>(animation.num_plays) |
2437 | | .unwrap(); |
2438 | | write_chunk(w, b"acTL", &data); |
2439 | | } |
2440 | | |
2441 | | /// Writes an fcTL chunk. |
2442 | | /// See https://wiki.mozilla.org/APNG_Specification#.60fcTL.60:_The_Frame_Control_Chunk |
2443 | | fn write_fctl(w: &mut impl Write, frame: &crate::FrameControl) { |
2444 | | let mut data = Vec::new(); |
2445 | | data.write_u32::<byteorder::BigEndian>(frame.sequence_number) |
2446 | | .unwrap(); |
2447 | | data.write_u32::<byteorder::BigEndian>(frame.width).unwrap(); |
2448 | | data.write_u32::<byteorder::BigEndian>(frame.height) |
2449 | | .unwrap(); |
2450 | | data.write_u32::<byteorder::BigEndian>(frame.x_offset) |
2451 | | .unwrap(); |
2452 | | data.write_u32::<byteorder::BigEndian>(frame.y_offset) |
2453 | | .unwrap(); |
2454 | | data.write_u16::<byteorder::BigEndian>(frame.delay_num) |
2455 | | .unwrap(); |
2456 | | data.write_u16::<byteorder::BigEndian>(frame.delay_den) |
2457 | | .unwrap(); |
2458 | | data.write_u8(frame.dispose_op as u8).unwrap(); |
2459 | | data.write_u8(frame.blend_op as u8).unwrap(); |
2460 | | write_chunk(w, b"fcTL", &data); |
2461 | | } |
2462 | | |
2463 | | /// Writes an fdAT chunk. |
2464 | | /// See https://wiki.mozilla.org/APNG_Specification#.60fdAT.60:_The_Frame_Data_Chunk |
2465 | | fn write_fdat(w: &mut impl Write, sequence_number: u32, image_data: &[u8]) { |
2466 | | let mut data = Vec::new(); |
2467 | | data.write_u32::<byteorder::BigEndian>(sequence_number) |
2468 | | .unwrap(); |
2469 | | data.write_all(image_data).unwrap(); |
2470 | | write_chunk(w, b"fdAT", &data); |
2471 | | } |
2472 | | |
2473 | | /// Writes PNG signature and chunks that can precede an fdAT chunk that is expected |
2474 | | /// to have |
2475 | | /// - `sequence_number` set to 0 |
2476 | | /// - image data with rgba8 pixels in a `width` by `width` image |
2477 | | fn write_fdat_prefix(w: &mut impl Write, num_frames: u32, width: u32) { |
2478 | | write_png_sig(w); |
2479 | | write_rgba8_ihdr_with_width(w, width); |
2480 | | write_actl( |
2481 | | w, |
2482 | | &crate::AnimationControl { |
2483 | | num_frames, |
2484 | | num_plays: 0, |
2485 | | }, |
2486 | | ); |
2487 | | |
2488 | | let mut fctl = crate::FrameControl { |
2489 | | width, |
2490 | | height: width, |
2491 | | ..Default::default() |
2492 | | }; |
2493 | | write_fctl(w, &fctl); |
2494 | | write_rgba8_idats(w, width, 0x7fffffff); |
2495 | | |
2496 | | fctl.sequence_number += 1; |
2497 | | write_fctl(w, &fctl); |
2498 | | } |
2499 | | |
2500 | | #[test] |
2501 | | fn test_fdat_chunk_without_idat() { |
2502 | | let png = { |
2503 | | let width = 1; |
2504 | | let mut png = Vec::new(); |
2505 | | write_png_sig(&mut png); |
2506 | | write_rgba8_ihdr_with_width(&mut png, width); |
2507 | | let image_data = generate_rgba8_with_width_and_height(width, width); |
2508 | | write_actl( |
2509 | | &mut png, |
2510 | | &crate::AnimationControl { |
2511 | | num_frames: 2, |
2512 | | num_plays: 1, |
2513 | | }, |
2514 | | ); |
2515 | | let mut fctl = crate::FrameControl { |
2516 | | sequence_number: 0, |
2517 | | width, |
2518 | | height: width, |
2519 | | ..Default::default() |
2520 | | }; |
2521 | | write_fctl(&mut png, &fctl); |
2522 | | fctl.sequence_number = 1; |
2523 | | write_fctl(&mut png, &fctl); |
2524 | | write_fdat(&mut png, 1, &image_data[..]); |
2525 | | write_iend(&mut png); |
2526 | | png |
2527 | | }; |
2528 | | let decoder = Decoder::new(Cursor::new(&png)); |
2529 | | let Err(err) = decoder.read_info() else { |
2530 | | panic!("Expected an error") |
2531 | | }; |
2532 | | assert!(matches!(&err, DecodingError::Format(_))); |
2533 | | assert_eq!( |
2534 | | "Unexpected restart of ChunkType { type: fdAT, \ |
2535 | | critical: false, \ |
2536 | | private: true, \ |
2537 | | reserved: false, \ |
2538 | | safecopy: false \ |
2539 | | } chunk sequence", |
2540 | | format!("{err}"), |
2541 | | ); |
2542 | | } |
2543 | | |
2544 | | #[test] |
2545 | | fn test_fdat_chunk_payload_length_0() { |
2546 | | let mut png = Vec::new(); |
2547 | | write_fdat_prefix(&mut png, 2, 8); |
2548 | | write_chunk(&mut png, b"fdAT", &[]); |
2549 | | |
2550 | | let decoder = Decoder::new(Cursor::new(&png)); |
2551 | | let mut reader = decoder.read_info().unwrap(); |
2552 | | let mut buf = vec![0; reader.output_buffer_size().unwrap()]; |
2553 | | reader.next_frame(&mut buf).unwrap(); |
2554 | | |
2555 | | // 0-length fdAT should result in an error. |
2556 | | let err = reader.next_frame(&mut buf).unwrap_err(); |
2557 | | assert!(matches!(&err, DecodingError::Format(_))); |
2558 | | assert_eq!("fdAT chunk shorter than 4 bytes", format!("{err}")); |
2559 | | |
2560 | | // Calling `next_frame` again should return an error. Same error as above would be nice, |
2561 | | // but it is probably unnecessary and infeasible (`DecodingError` can't derive `Clone` |
2562 | | // because `std::io::Error` doesn't implement `Clone`).. But it definitely shouldn't enter |
2563 | | // an infinite loop. |
2564 | | let err2 = reader.next_frame(&mut buf).unwrap_err(); |
2565 | | assert!(matches!(&err2, DecodingError::Parameter(_))); |
2566 | | assert_eq!( |
2567 | | "A fatal decoding error has been encounted earlier", |
2568 | | format!("{err2}") |
2569 | | ); |
2570 | | } |
2571 | | |
2572 | | #[test] |
2573 | | fn test_fdat_chunk_payload_length_3() { |
2574 | | let mut png = Vec::new(); |
2575 | | write_fdat_prefix(&mut png, 2, 8); |
2576 | | write_chunk(&mut png, b"fdAT", &[1, 0, 0]); |
2577 | | |
2578 | | let decoder = Decoder::new(Cursor::new(&png)); |
2579 | | let mut reader = decoder.read_info().unwrap(); |
2580 | | let mut buf = vec![0; reader.output_buffer_size().unwrap()]; |
2581 | | reader.next_frame(&mut buf).unwrap(); |
2582 | | |
2583 | | // 3-bytes-long fdAT should result in an error. |
2584 | | let err = reader.next_frame(&mut buf).unwrap_err(); |
2585 | | assert!(matches!(&err, DecodingError::Format(_))); |
2586 | | assert_eq!("fdAT chunk shorter than 4 bytes", format!("{err}")); |
2587 | | } |
2588 | | |
2589 | | #[test] |
2590 | | fn test_frame_split_across_two_fdat_chunks() { |
2591 | | // Generate test data where the 2nd animation frame is split across 2 fdAT chunks. |
2592 | | // |
2593 | | // This is similar to the example given in |
2594 | | // https://wiki.mozilla.org/APNG_Specification#Chunk_Sequence_Numbers: |
2595 | | // |
2596 | | // ``` |
2597 | | // Sequence number Chunk |
2598 | | // (none) `acTL` |
2599 | | // 0 `fcTL` first frame |
2600 | | // (none) `IDAT` first frame / default image |
2601 | | // 1 `fcTL` second frame |
2602 | | // 2 first `fdAT` for second frame |
2603 | | // 3 second `fdAT` for second frame |
2604 | | // ``` |
2605 | | let png = { |
2606 | | let mut png = Vec::new(); |
2607 | | write_fdat_prefix(&mut png, 2, 8); |
2608 | | let image_data = generate_rgba8_with_width_and_height(8, 8); |
2609 | | write_fdat(&mut png, 2, &image_data[..30]); |
2610 | | write_fdat(&mut png, 3, &image_data[30..]); |
2611 | | write_iend(&mut png); |
2612 | | png |
2613 | | }; |
2614 | | |
2615 | | // Start decoding. |
2616 | | let decoder = Decoder::new(Cursor::new(&png)); |
2617 | | let mut reader = decoder.read_info().unwrap(); |
2618 | | let mut buf = vec![0; reader.output_buffer_size().unwrap()]; |
2619 | | let Some(animation_control) = reader.info().animation_control else { |
2620 | | panic!("No acTL"); |
2621 | | }; |
2622 | | assert_eq!(animation_control.num_frames, 2); |
2623 | | |
2624 | | // Process the 1st animation frame. |
2625 | | let first_frame: Vec<u8>; |
2626 | | { |
2627 | | reader.next_frame(&mut buf).unwrap(); |
2628 | | first_frame = buf.clone(); |
2629 | | |
2630 | | // Note that the doc comment of `Reader::next_frame` says that "[...] |
2631 | | // can be checked afterwards by calling `info` **after** a successful call and |
2632 | | // inspecting the `frame_control` data.". (Note the **emphasis** on "after".) |
2633 | | let Some(frame_control) = reader.info().frame_control else { |
2634 | | panic!("No fcTL (1st frame)"); |
2635 | | }; |
2636 | | // The sequence number is taken from the `fcTL` chunk that comes before the `IDAT` |
2637 | | // chunk. |
2638 | | assert_eq!(frame_control.sequence_number, 0); |
2639 | | } |
2640 | | |
2641 | | // Process the 2nd animation frame. |
2642 | | let second_frame: Vec<u8>; |
2643 | | { |
2644 | | reader.next_frame(&mut buf).unwrap(); |
2645 | | second_frame = buf.clone(); |
2646 | | |
2647 | | // Same as above - updated `frame_control` is available *after* the `next_frame` call. |
2648 | | let Some(frame_control) = reader.info().frame_control else { |
2649 | | panic!("No fcTL (2nd frame)"); |
2650 | | }; |
2651 | | // The sequence number is taken from the `fcTL` chunk that comes before the two `fdAT` |
2652 | | // chunks. Note that sequence numbers inside `fdAT` chunks are not publicly exposed |
2653 | | // (but they are still checked when decoding to verify that they are sequential). |
2654 | | assert_eq!(frame_control.sequence_number, 1); |
2655 | | } |
2656 | | |
2657 | | assert_eq!(first_frame, second_frame); |
2658 | | } |
2659 | | |
2660 | | #[test] |
2661 | | fn test_idat_bigger_than_image_size_from_ihdr() { |
2662 | | let png = { |
2663 | | let mut png = Vec::new(); |
2664 | | write_png_sig(&mut png); |
2665 | | write_rgba8_ihdr_with_width(&mut png, 8); |
2666 | | |
2667 | | // Here we want to test an invalid image where the `IDAT` chunk contains more data |
2668 | | // (data for 8x256 image) than declared in the `IHDR` chunk (which only describes an |
2669 | | // 8x8 image). |
2670 | | write_chunk( |
2671 | | &mut png, |
2672 | | b"IDAT", |
2673 | | &generate_rgba8_with_width_and_height(8, 256), |
2674 | | ); |
2675 | | |
2676 | | write_iend(&mut png); |
2677 | | png |
2678 | | }; |
2679 | | let decoder = Decoder::new(Cursor::new(&png)); |
2680 | | let mut reader = decoder.read_info().unwrap(); |
2681 | | let mut buf = vec![0; reader.output_buffer_size().unwrap()]; |
2682 | | |
2683 | | // TODO: Should this return an error instead? For now let's just have test assertions for |
2684 | | // the current behavior. |
2685 | | reader.next_frame(&mut buf).unwrap(); |
2686 | | assert_eq!(3093270825, crc32fast::hash(&buf)); |
2687 | | } |
2688 | | |
2689 | | #[test] |
2690 | | fn test_only_idat_chunk_in_input_stream() { |
2691 | | let png = { |
2692 | | let mut png = Vec::new(); |
2693 | | write_png_sig(&mut png); |
2694 | | write_chunk(&mut png, b"IDAT", &[]); |
2695 | | png |
2696 | | }; |
2697 | | let decoder = Decoder::new(Cursor::new(&png)); |
2698 | | let Err(err) = decoder.read_info() else { |
2699 | | panic!("Expected an error") |
2700 | | }; |
2701 | | assert!(matches!(&err, DecodingError::Format(_))); |
2702 | | assert_eq!( |
2703 | | "ChunkType { type: IDAT, \ |
2704 | | critical: true, \ |
2705 | | private: false, \ |
2706 | | reserved: false, \ |
2707 | | safecopy: false \ |
2708 | | } chunk appeared before IHDR chunk", |
2709 | | format!("{err}"), |
2710 | | ); |
2711 | | } |
2712 | | |
2713 | | /// `StreamingInput` can be used by tests to simulate a streaming input |
2714 | | /// (e.g. a slow http response, where all bytes are not immediately available). |
2715 | | #[derive(Clone)] |
2716 | | struct StreamingInput { |
2717 | | full_input: Vec<u8>, |
2718 | | state: Rc<RefCell<StreamingInputState>>, |
2719 | | } |
2720 | | |
2721 | | struct StreamingInputState { |
2722 | | current_pos: usize, |
2723 | | available_len: usize, |
2724 | | } |
2725 | | |
2726 | | impl StreamingInput { |
2727 | | fn new(full_input: Vec<u8>) -> Self { |
2728 | | Self { |
2729 | | full_input, |
2730 | | state: Rc::new(RefCell::new(StreamingInputState { |
2731 | | current_pos: 0, |
2732 | | available_len: 0, |
2733 | | })), |
2734 | | } |
2735 | | } |
2736 | | |
2737 | | fn with_noncompressed_png(width: u32, idat_size: usize) -> Self { |
2738 | | let mut png = Vec::new(); |
2739 | | write_noncompressed_png(&mut png, width, idat_size); |
2740 | | Self::new(png) |
2741 | | } |
2742 | | |
2743 | | fn expose_next_byte(&self) { |
2744 | | let mut state = self.state.borrow_mut(); |
2745 | | assert!(state.available_len < self.full_input.len()); |
2746 | | state.available_len += 1; |
2747 | | } |
2748 | | |
2749 | | fn stream_input_until_reader_is_available(&self) -> Reader<StreamingInput> { |
2750 | | loop { |
2751 | | self.state.borrow_mut().current_pos = 0; |
2752 | | match Decoder::new(self.clone()).read_info() { |
2753 | | Ok(reader) => { |
2754 | | break reader; |
2755 | | } |
2756 | | Err(DecodingError::IoError(e)) if e.kind() == ErrorKind::UnexpectedEof => { |
2757 | | self.expose_next_byte(); |
2758 | | } |
2759 | | _ => panic!("Unexpected error"), |
2760 | | } |
2761 | | } |
2762 | | } |
2763 | | |
2764 | | fn decode_full_input<F, R>(&self, f: F) -> R |
2765 | | where |
2766 | | F: FnOnce(Reader<Cursor<&[u8]>>) -> R, |
2767 | | { |
2768 | | let decoder = Decoder::new(Cursor::new(&*self.full_input)); |
2769 | | f(decoder.read_info().unwrap()) |
2770 | | } |
2771 | | } |
2772 | | |
2773 | | impl Read for StreamingInput { |
2774 | | fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> { |
2775 | | let mut state = self.state.borrow_mut(); |
2776 | | let mut available_bytes = &self.full_input[state.current_pos..state.available_len]; |
2777 | | let number_of_read_bytes = available_bytes.read(buf)?; |
2778 | | state.current_pos += number_of_read_bytes; |
2779 | | assert!(state.current_pos <= state.available_len); |
2780 | | Ok(number_of_read_bytes) |
2781 | | } |
2782 | | } |
2783 | | impl BufRead for StreamingInput { |
2784 | | fn fill_buf(&mut self) -> std::io::Result<&[u8]> { |
2785 | | let state = self.state.borrow(); |
2786 | | Ok(&self.full_input[state.current_pos..state.available_len]) |
2787 | | } |
2788 | | |
2789 | | fn consume(&mut self, amt: usize) { |
2790 | | let mut state = self.state.borrow_mut(); |
2791 | | state.current_pos += amt; |
2792 | | assert!(state.current_pos <= state.available_len); |
2793 | | } |
2794 | | } |
2795 | | impl Seek for StreamingInput { |
2796 | | fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> { |
2797 | | let mut state = self.state.borrow_mut(); |
2798 | | state.current_pos = match pos { |
2799 | | std::io::SeekFrom::Start(n) => n as usize, |
2800 | | std::io::SeekFrom::End(n) => (self.full_input.len() as i64 + n) as usize, |
2801 | | std::io::SeekFrom::Current(n) => (state.current_pos as i64 + n) as usize, |
2802 | | } as usize; |
2803 | | Ok(state.current_pos as u64) |
2804 | | } |
2805 | | fn stream_position(&mut self) -> std::io::Result<u64> { |
2806 | | Ok(self.state.borrow().current_pos as u64) |
2807 | | } |
2808 | | } |
2809 | | |
2810 | | /// Test resuming/retrying `Reader.next_frame` after `UnexpectedEof`. |
2811 | | #[test] |
2812 | | fn test_streaming_input_and_decoding_via_next_frame() { |
2813 | | const WIDTH: u32 = 16; |
2814 | | const IDAT_SIZE: usize = 512; |
2815 | | let streaming_input = StreamingInput::with_noncompressed_png(WIDTH, IDAT_SIZE); |
2816 | | |
2817 | | let (whole_output_info, decoded_from_whole_input) = |
2818 | | streaming_input.decode_full_input(|mut r| { |
2819 | | let mut buf = vec![0; r.output_buffer_size().unwrap()]; |
2820 | | let output_info = r.next_frame(&mut buf).unwrap(); |
2821 | | (output_info, buf) |
2822 | | }); |
2823 | | |
2824 | | let mut png_reader = streaming_input.stream_input_until_reader_is_available(); |
2825 | | let mut decoded_from_streaming_input = vec![0; png_reader.output_buffer_size().unwrap()]; |
2826 | | let streaming_output_info = loop { |
2827 | | match png_reader.next_frame(decoded_from_streaming_input.as_mut_slice()) { |
2828 | | Ok(output_info) => break output_info, |
2829 | | Err(DecodingError::IoError(e)) if e.kind() == ErrorKind::UnexpectedEof => { |
2830 | | streaming_input.expose_next_byte() |
2831 | | } |
2832 | | e => panic!("Unexpected error: {:?}", e), |
2833 | | } |
2834 | | }; |
2835 | | assert_eq!(whole_output_info, streaming_output_info); |
2836 | | assert_eq!( |
2837 | | crc32fast::hash(&decoded_from_whole_input), |
2838 | | crc32fast::hash(&decoded_from_streaming_input) |
2839 | | ); |
2840 | | } |
2841 | | |
2842 | | /// Test resuming/retrying `Reader.next_row` after `UnexpectedEof`. |
2843 | | #[test] |
2844 | | fn test_streaming_input_and_decoding_via_next_row() { |
2845 | | const WIDTH: u32 = 16; |
2846 | | const IDAT_SIZE: usize = 512; |
2847 | | let streaming_input = StreamingInput::with_noncompressed_png(WIDTH, IDAT_SIZE); |
2848 | | |
2849 | | let decoded_from_whole_input = streaming_input.decode_full_input(|mut r| { |
2850 | | let mut buf = vec![0; r.output_buffer_size().unwrap()]; |
2851 | | r.next_frame(&mut buf).unwrap(); |
2852 | | buf |
2853 | | }); |
2854 | | |
2855 | | let mut png_reader = streaming_input.stream_input_until_reader_is_available(); |
2856 | | let mut decoded_from_streaming_input = Vec::new(); |
2857 | | loop { |
2858 | | match png_reader.next_row() { |
2859 | | Ok(None) => break, |
2860 | | Ok(Some(row)) => decoded_from_streaming_input.extend_from_slice(row.data()), |
2861 | | Err(DecodingError::IoError(e)) if e.kind() == ErrorKind::UnexpectedEof => { |
2862 | | streaming_input.expose_next_byte() |
2863 | | } |
2864 | | e => panic!("Unexpected error: {:?}", e), |
2865 | | } |
2866 | | } |
2867 | | assert_eq!( |
2868 | | crc32fast::hash(&decoded_from_whole_input), |
2869 | | crc32fast::hash(&decoded_from_streaming_input) |
2870 | | ); |
2871 | | } |
2872 | | |
2873 | | /// Test resuming/retrying `Decoder.read_header_info` after `UnexpectedEof`. |
2874 | | #[test] |
2875 | | fn test_streaming_input_and_reading_header_info() { |
2876 | | const WIDTH: u32 = 16; |
2877 | | const IDAT_SIZE: usize = 512; |
2878 | | let streaming_input = StreamingInput::with_noncompressed_png(WIDTH, IDAT_SIZE); |
2879 | | |
2880 | | let info_from_whole_input = streaming_input.decode_full_input(|r| r.info().clone()); |
2881 | | |
2882 | | let mut decoder = Decoder::new(streaming_input.clone()); |
2883 | | let info_from_streaming_input = loop { |
2884 | | match decoder.read_header_info() { |
2885 | | Ok(info) => break info.clone(), |
2886 | | Err(DecodingError::IoError(e)) if e.kind() == ErrorKind::UnexpectedEof => { |
2887 | | streaming_input.expose_next_byte() |
2888 | | } |
2889 | | e => panic!("Unexpected error: {:?}", e), |
2890 | | } |
2891 | | }; |
2892 | | |
2893 | | assert_eq!(info_from_whole_input.width, info_from_streaming_input.width); |
2894 | | assert_eq!( |
2895 | | info_from_whole_input.height, |
2896 | | info_from_streaming_input.height |
2897 | | ); |
2898 | | assert_eq!( |
2899 | | info_from_whole_input.bit_depth, |
2900 | | info_from_streaming_input.bit_depth |
2901 | | ); |
2902 | | assert_eq!( |
2903 | | info_from_whole_input.color_type, |
2904 | | info_from_streaming_input.color_type |
2905 | | ); |
2906 | | assert_eq!( |
2907 | | info_from_whole_input.interlaced, |
2908 | | info_from_streaming_input.interlaced |
2909 | | ); |
2910 | | } |
2911 | | |
2912 | | /// Creates a ready-to-test [`Reader`] which decodes a PNG that contains: |
2913 | | /// IHDR, IDAT, IEND. |
2914 | | fn create_reader_of_ihdr_idat() -> Reader<Cursor<Vec<u8>>> { |
2915 | | let mut png = Vec::new(); |
2916 | | write_noncompressed_png(&mut png, /* width = */ 16, /* idat_size = */ 1024); |
2917 | | Decoder::new(Cursor::new(png)).read_info().unwrap() |
2918 | | } |
2919 | | |
2920 | | /// Creates a ready-to-test [`Reader`] which decodes an animated PNG that contains: |
2921 | | /// IHDR, acTL, fcTL, IDAT, fcTL, fdAT, IEND. (i.e. IDAT is part of the animation) |
2922 | | fn create_reader_of_ihdr_actl_fctl_idat_fctl_fdat() -> Reader<Cursor<Vec<u8>>> { |
2923 | | let width = 16; |
2924 | | let mut fctl = crate::FrameControl { |
2925 | | width, |
2926 | | height: width, |
2927 | | ..Default::default() |
2928 | | }; |
2929 | | |
2930 | | let mut png = Vec::new(); |
2931 | | write_png_sig(&mut png); |
2932 | | write_rgba8_ihdr_with_width(&mut png, width); |
2933 | | write_actl( |
2934 | | &mut png, |
2935 | | &crate::AnimationControl { |
2936 | | num_frames: 2, |
2937 | | num_plays: 0, |
2938 | | }, |
2939 | | ); |
2940 | | fctl.sequence_number = 0; |
2941 | | write_fctl(&mut png, &fctl); |
2942 | | // Using `fctl.height + 1` means that the `IDAT` will have "left-over" data after |
2943 | | // processing. This helps to verify that `Reader.read_until_image_data` discards the |
2944 | | // left-over data when resetting `UnfilteredRowsBuffer`. |
2945 | | let idat_data = generate_rgba8_with_width_and_height(fctl.width, fctl.height + 1); |
2946 | | write_chunk(&mut png, b"IDAT", &idat_data); |
2947 | | |
2948 | | let fdat_width = 10; |
2949 | | fctl.sequence_number = 1; |
2950 | | // Using different width in `IDAT` and `fDAT` frames helps to catch problems that |
2951 | | // may arise when `Reader.read_until_image_data` doesn't properly reset |
2952 | | // `UnfilteredRowsBuffer`. |
2953 | | fctl.width = fdat_width; |
2954 | | write_fctl(&mut png, &fctl); |
2955 | | let fdat_data = generate_rgba8_with_width_and_height(fctl.width, fctl.height); |
2956 | | write_fdat(&mut png, 2, &fdat_data); |
2957 | | write_iend(&mut png); |
2958 | | |
2959 | | Decoder::new(Cursor::new(png)).read_info().unwrap() |
2960 | | } |
2961 | | |
2962 | | /// Creates a ready-to-test [`Reader`] which decodes an animated PNG that contains: IHDR, acTL, |
2963 | | /// IDAT, fcTL, fdAT, fcTL, fdAT, IEND. (i.e. IDAT is *not* part of the animation) |
2964 | | fn create_reader_of_ihdr_actl_idat_fctl_fdat_fctl_fdat() -> Reader<Cursor<Vec<u8>>> { |
2965 | | let width = 16; |
2966 | | let frame_data = generate_rgba8_with_width_and_height(width, width); |
2967 | | let mut fctl = crate::FrameControl { |
2968 | | width, |
2969 | | height: width, |
2970 | | ..Default::default() |
2971 | | }; |
2972 | | |
2973 | | let mut png = Vec::new(); |
2974 | | write_png_sig(&mut png); |
2975 | | write_rgba8_ihdr_with_width(&mut png, width); |
2976 | | write_actl( |
2977 | | &mut png, |
2978 | | &crate::AnimationControl { |
2979 | | num_frames: 2, |
2980 | | num_plays: 0, |
2981 | | }, |
2982 | | ); |
2983 | | write_chunk(&mut png, b"IDAT", &frame_data); |
2984 | | fctl.sequence_number = 0; |
2985 | | write_fctl(&mut png, &fctl); |
2986 | | write_fdat(&mut png, 1, &frame_data); |
2987 | | fctl.sequence_number = 2; |
2988 | | write_fctl(&mut png, &fctl); |
2989 | | write_fdat(&mut png, 3, &frame_data); |
2990 | | write_iend(&mut png); |
2991 | | |
2992 | | Decoder::new(Cursor::new(png)).read_info().unwrap() |
2993 | | } |
2994 | | |
2995 | | fn get_fctl_sequence_number(reader: &Reader<impl BufRead + Seek>) -> u32 { |
2996 | | reader |
2997 | | .info() |
2998 | | .frame_control |
2999 | | .as_ref() |
3000 | | .unwrap() |
3001 | | .sequence_number |
3002 | | } |
3003 | | |
3004 | | /// Tests that [`Reader.next_frame`] will report a `PolledAfterEndOfImage` error when called |
3005 | | /// after already decoding a single frame in a non-animated PNG. |
3006 | | #[test] |
3007 | | fn test_next_frame_polling_after_end_non_animated() { |
3008 | | let mut reader = create_reader_of_ihdr_idat(); |
3009 | | let mut buf = vec![0; reader.output_buffer_size().unwrap()]; |
3010 | | reader |
3011 | | .next_frame(&mut buf) |
3012 | | .expect("Expecting no error for IDAT frame"); |
3013 | | |
3014 | | let err = reader |
3015 | | .next_frame(&mut buf) |
3016 | | .expect_err("Main test - expecting error"); |
3017 | | assert!( |
3018 | | matches!(&err, DecodingError::Parameter(_)), |
3019 | | "Unexpected kind of error: {:?}", |
3020 | | &err, |
3021 | | ); |
3022 | | } |
3023 | | |
3024 | | /// Tests that [`Reader.next_frame_info`] will report a `PolledAfterEndOfImage` error when |
3025 | | /// called when decoding a PNG that only contains a single frame. |
3026 | | #[test] |
3027 | | fn test_next_frame_info_polling_after_end_non_animated() { |
3028 | | let mut reader = create_reader_of_ihdr_idat(); |
3029 | | |
3030 | | let err = reader |
3031 | | .next_frame_info() |
3032 | | .expect_err("Main test - expecting error"); |
3033 | | assert!( |
3034 | | matches!(&err, DecodingError::Parameter(_)), |
3035 | | "Unexpected kind of error: {:?}", |
3036 | | &err, |
3037 | | ); |
3038 | | } |
3039 | | |
3040 | | /// Tests that [`Reader.next_frame`] will report a `PolledAfterEndOfImage` error when called |
3041 | | /// after already decoding a single frame in an animated PNG where IDAT is part of the |
3042 | | /// animation. |
3043 | | #[test] |
3044 | | fn test_next_frame_polling_after_end_idat_part_of_animation() { |
3045 | | let mut reader = create_reader_of_ihdr_actl_fctl_idat_fctl_fdat(); |
3046 | | let mut buf = vec![0; reader.output_buffer_size().unwrap()]; |
3047 | | |
3048 | | assert_eq!(get_fctl_sequence_number(&reader), 0); |
3049 | | reader |
3050 | | .next_frame(&mut buf) |
3051 | | .expect("Expecting no error for IDAT frame"); |
3052 | | |
3053 | | // `next_frame` doesn't advance to the next `fcTL`. |
3054 | | assert_eq!(get_fctl_sequence_number(&reader), 0); |
3055 | | |
3056 | | reader |
3057 | | .next_frame(&mut buf) |
3058 | | .expect("Expecting no error for fdAT frame"); |
3059 | | assert_eq!(get_fctl_sequence_number(&reader), 1); |
3060 | | |
3061 | | let err = reader |
3062 | | .next_frame(&mut buf) |
3063 | | .expect_err("Main test - expecting error"); |
3064 | | assert!( |
3065 | | matches!(&err, DecodingError::Parameter(_)), |
3066 | | "Unexpected kind of error: {:?}", |
3067 | | &err, |
3068 | | ); |
3069 | | } |
3070 | | |
3071 | | /// Tests that [`Reader.next_frame`] will report a `PolledAfterEndOfImage` error when called |
3072 | | /// after already decoding a single frame in an animated PNG where IDAT is *not* part of the |
3073 | | /// animation. |
3074 | | #[test] |
3075 | | fn test_next_frame_polling_after_end_idat_not_part_of_animation() { |
3076 | | let mut reader = create_reader_of_ihdr_actl_idat_fctl_fdat_fctl_fdat(); |
3077 | | let mut buf = vec![0; reader.output_buffer_size().unwrap()]; |
3078 | | |
3079 | | assert!(reader.info().frame_control.is_none()); |
3080 | | reader |
3081 | | .next_frame(&mut buf) |
3082 | | .expect("Expecting no error for IDAT frame"); |
3083 | | |
3084 | | // `next_frame` doesn't advance to the next `fcTL`. |
3085 | | assert!(reader.info().frame_control.is_none()); |
3086 | | |
3087 | | reader |
3088 | | .next_frame(&mut buf) |
3089 | | .expect("Expecting no error for 1st fdAT frame"); |
3090 | | assert_eq!(get_fctl_sequence_number(&reader), 0); |
3091 | | |
3092 | | reader |
3093 | | .next_frame(&mut buf) |
3094 | | .expect("Expecting no error for 2nd fdAT frame"); |
3095 | | assert_eq!(get_fctl_sequence_number(&reader), 2); |
3096 | | |
3097 | | let err = reader |
3098 | | .next_frame(&mut buf) |
3099 | | .expect_err("Main test - expecting error"); |
3100 | | assert!( |
3101 | | matches!(&err, DecodingError::Parameter(_)), |
3102 | | "Unexpected kind of error: {:?}", |
3103 | | &err, |
3104 | | ); |
3105 | | } |
3106 | | |
3107 | | /// Tests that after decoding a whole frame via [`Reader.next_row`] the call to |
3108 | | /// [`Reader.next_frame`] will decode the **next** frame. |
3109 | | #[test] |
3110 | | fn test_row_by_row_then_next_frame() { |
3111 | | let mut reader = create_reader_of_ihdr_actl_fctl_idat_fctl_fdat(); |
3112 | | let mut buf = vec![0; reader.output_buffer_size().unwrap()]; |
3113 | | |
3114 | | assert_eq!(get_fctl_sequence_number(&reader), 0); |
3115 | | while let Some(_) = reader.next_row().unwrap() {} |
3116 | | assert_eq!(get_fctl_sequence_number(&reader), 0); |
3117 | | |
3118 | | buf.fill(0x0f); |
3119 | | reader |
3120 | | .next_frame(&mut buf) |
3121 | | .expect("Expecting no error from next_frame call"); |
3122 | | |
3123 | | // Verify if we have read the next `fcTL` chunk + repopulated `buf`: |
3124 | | assert_eq!(get_fctl_sequence_number(&reader), 1); |
3125 | | assert!(buf.iter().any(|byte| *byte != 0x0f)); |
3126 | | } |
3127 | | |
3128 | | /// Tests that after decoding a whole frame via [`Reader.next_row`] it is possible |
3129 | | /// to use [`Reader.next_row`] to decode the next frame (by using the `next_frame_info` API to |
3130 | | /// advance to the next frame when `next_row` returns `None`). |
3131 | | #[test] |
3132 | | fn test_row_by_row_of_two_frames() { |
3133 | | let mut reader = create_reader_of_ihdr_actl_fctl_idat_fctl_fdat(); |
3134 | | |
3135 | | let mut rows_of_frame1 = 0; |
3136 | | assert_eq!(get_fctl_sequence_number(&reader), 0); |
3137 | | while let Some(_) = reader.next_row().unwrap() { |
3138 | | rows_of_frame1 += 1; |
3139 | | } |
3140 | | assert_eq!(rows_of_frame1, 16); |
3141 | | assert_eq!(get_fctl_sequence_number(&reader), 0); |
3142 | | |
3143 | | let mut rows_of_frame2 = 0; |
3144 | | assert_eq!(reader.next_frame_info().unwrap().sequence_number, 1); |
3145 | | assert_eq!(get_fctl_sequence_number(&reader), 1); |
3146 | | while let Some(_) = reader.next_row().unwrap() { |
3147 | | rows_of_frame2 += 1; |
3148 | | } |
3149 | | assert_eq!(rows_of_frame2, 16); |
3150 | | assert_eq!(get_fctl_sequence_number(&reader), 1); |
3151 | | |
3152 | | let err = reader |
3153 | | .next_frame_info() |
3154 | | .expect_err("No more frames - expecting error"); |
3155 | | assert!( |
3156 | | matches!(&err, DecodingError::Parameter(_)), |
3157 | | "Unexpected kind of error: {:?}", |
3158 | | &err, |
3159 | | ); |
3160 | | } |
3161 | | |
3162 | | /// This test is similar to `test_next_frame_polling_after_end_idat_part_of_animation`, but it |
3163 | | /// uses `next_frame_info` calls to read to the next `fcTL` earlier - before the next call to |
3164 | | /// `next_frame` (knowing `fcTL` before calling `next_frame` may be helpful to determine the |
3165 | | /// size of the output buffer and/or to prepare the buffer based on the `DisposeOp` of the |
3166 | | /// previous frames). |
3167 | | #[test] |
3168 | | fn test_next_frame_info_after_next_frame() { |
3169 | | let mut reader = create_reader_of_ihdr_actl_fctl_idat_fctl_fdat(); |
3170 | | let mut buf = vec![0; reader.output_buffer_size().unwrap()]; |
3171 | | |
3172 | | assert_eq!(get_fctl_sequence_number(&reader), 0); |
3173 | | reader |
3174 | | .next_frame(&mut buf) |
3175 | | .expect("Expecting no error for IDAT frame"); |
3176 | | |
3177 | | // `next_frame` doesn't advance to the next `fcTL`. |
3178 | | assert_eq!(get_fctl_sequence_number(&reader), 0); |
3179 | | |
3180 | | // But `next_frame_info` can be used to go to the next `fcTL`. |
3181 | | assert_eq!(reader.next_frame_info().unwrap().sequence_number, 1); |
3182 | | assert_eq!(get_fctl_sequence_number(&reader), 1); |
3183 | | |
3184 | | reader |
3185 | | .next_frame(&mut buf) |
3186 | | .expect("Expecting no error for fdAT frame"); |
3187 | | assert_eq!(get_fctl_sequence_number(&reader), 1); |
3188 | | |
3189 | | let err = reader |
3190 | | .next_frame_info() |
3191 | | .expect_err("Main test - expecting error"); |
3192 | | assert!( |
3193 | | matches!(&err, DecodingError::Parameter(_)), |
3194 | | "Unexpected kind of error: {:?}", |
3195 | | &err, |
3196 | | ); |
3197 | | } |
3198 | | |
3199 | | /// This test is similar to `test_next_frame_polling_after_end_idat_not_part_of_animation`, but |
3200 | | /// it uses `next_frame_info` to skip the `IDAT` frame entirely + to move between frames. |
3201 | | #[test] |
3202 | | fn test_next_frame_info_to_skip_first_frame() { |
3203 | | let mut reader = create_reader_of_ihdr_actl_idat_fctl_fdat_fctl_fdat(); |
3204 | | let mut buf = vec![0; reader.output_buffer_size().unwrap()]; |
3205 | | |
3206 | | // First (IDAT) frame doesn't have frame control info, which means |
3207 | | // that it is not part of the animation. |
3208 | | assert!(reader.info().frame_control.is_none()); |
3209 | | |
3210 | | // `next_frame_info` can be used to skip the IDAT frame (without first having to separately |
3211 | | // discard the image data - e.g. by also calling `next_frame` first). |
3212 | | assert_eq!(reader.next_frame_info().unwrap().sequence_number, 0); |
3213 | | assert_eq!(get_fctl_sequence_number(&reader), 0); |
3214 | | reader |
3215 | | .next_frame(&mut buf) |
3216 | | .expect("Expecting no error for 1st fdAT frame"); |
3217 | | assert_eq!(get_fctl_sequence_number(&reader), 0); |
3218 | | |
3219 | | // Get the `fcTL` for the 2nd frame. |
3220 | | assert_eq!(reader.next_frame_info().unwrap().sequence_number, 2); |
3221 | | reader |
3222 | | .next_frame(&mut buf) |
3223 | | .expect("Expecting no error for 2nd fdAT frame"); |
3224 | | assert_eq!(get_fctl_sequence_number(&reader), 2); |
3225 | | |
3226 | | let err = reader |
3227 | | .next_frame_info() |
3228 | | .expect_err("Main test - expecting error"); |
3229 | | assert!( |
3230 | | matches!(&err, DecodingError::Parameter(_)), |
3231 | | "Unexpected kind of error: {:?}", |
3232 | | &err, |
3233 | | ); |
3234 | | } |
3235 | | |
3236 | | #[test] |
3237 | | fn test_incorrect_trns_chunk_is_ignored() { |
3238 | | let png = { |
3239 | | let mut png = Vec::new(); |
3240 | | write_png_sig(&mut png); |
3241 | | write_rgba8_ihdr_with_width(&mut png, 8); |
3242 | | write_chunk(&mut png, b"tRNS", &[12, 34, 56]); |
3243 | | write_chunk( |
3244 | | &mut png, |
3245 | | b"IDAT", |
3246 | | &generate_rgba8_with_width_and_height(8, 8), |
3247 | | ); |
3248 | | write_iend(&mut png); |
3249 | | png |
3250 | | }; |
3251 | | let decoder = Decoder::new(Cursor::new(&png)); |
3252 | | let mut reader = decoder.read_info().unwrap(); |
3253 | | let mut buf = vec![0; reader.output_buffer_size().unwrap()]; |
3254 | | assert!(reader.info().trns.is_none()); |
3255 | | reader.next_frame(&mut buf).unwrap(); |
3256 | | assert_eq!(3093270825, crc32fast::hash(&buf)); |
3257 | | assert!(reader.info().trns.is_none()); |
3258 | | } |
3259 | | |
3260 | | /// This is a regression test for https://crbug.com/422421347 |
3261 | | #[test] |
3262 | | fn test_actl_num_frames_zero() { |
3263 | | let width = 16; |
3264 | | let frame_data = generate_rgba8_with_width_and_height(width, width); |
3265 | | |
3266 | | let mut png = Vec::new(); |
3267 | | write_png_sig(&mut png); |
3268 | | write_rgba8_ihdr_with_width(&mut png, width); |
3269 | | write_actl( |
3270 | | &mut png, |
3271 | | &crate::AnimationControl { |
3272 | | num_frames: 0, // <= spec violation needed by this test |
3273 | | num_plays: 0, |
3274 | | }, |
3275 | | ); |
3276 | | // Presence of an `fcTL` chunk will prevent incrementing |
3277 | | // `num_frames` when calculating `remaining_frames` in |
3278 | | // `Decoder::read_info`. So the test writes an `fcTL` chunk |
3279 | | // to end up with `remaining_frames == 0` if `parse_actl` allows |
3280 | | // `num_frames == 0`. |
3281 | | write_fctl( |
3282 | | &mut png, |
3283 | | &crate::FrameControl { |
3284 | | width, |
3285 | | height: width, |
3286 | | ..Default::default() |
3287 | | }, |
3288 | | ); |
3289 | | write_chunk(&mut png, b"IDAT", &frame_data); |
3290 | | write_iend(&mut png); |
3291 | | |
3292 | | let mut reader = Decoder::new(Cursor::new(png)).read_info().unwrap(); |
3293 | | |
3294 | | // Using `next_interlaced_row` in the test, because it doesn't check |
3295 | | // `Reader::remaining_frames` (unlike `next_frame`), because it assumes that either |
3296 | | // `read_info` or `next_frame` leave `Reader` in a valid state. |
3297 | | // |
3298 | | // The test passes if these `next_interlaced_row` calls don't hit any `assert!` failures. |
3299 | | while let Some(_row) = reader.next_interlaced_row().unwrap() {} |
3300 | | } |
3301 | | |
3302 | | #[test] |
3303 | | fn test_small_fctl() { |
3304 | | const FCTL_SIZE: u32 = 30; |
3305 | | const IHDR_SIZE: u32 = 50; |
3306 | | let mut png = Vec::new(); |
3307 | | write_png_sig(&mut png); |
3308 | | write_rgba8_ihdr_with_width(&mut png, IHDR_SIZE); |
3309 | | write_actl( |
3310 | | &mut png, |
3311 | | &crate::AnimationControl { |
3312 | | num_frames: 1, |
3313 | | num_plays: 1, |
3314 | | }, |
3315 | | ); |
3316 | | write_fctl( |
3317 | | &mut png, |
3318 | | &crate::FrameControl { |
3319 | | width: FCTL_SIZE, |
3320 | | height: FCTL_SIZE, |
3321 | | x_offset: 10, |
3322 | | y_offset: 10, |
3323 | | sequence_number: 0, |
3324 | | ..Default::default() |
3325 | | }, |
3326 | | ); |
3327 | | write_chunk( |
3328 | | &mut png, |
3329 | | b"IDAT", |
3330 | | &generate_rgba8_with_width_and_height(IHDR_SIZE, IHDR_SIZE), |
3331 | | ); |
3332 | | write_iend(&mut png); |
3333 | | |
3334 | | let reader = Decoder::new(Cursor::new(png)).read_info(); |
3335 | | let err = reader.err().unwrap(); |
3336 | | assert!(matches!(&err, DecodingError::Format(_))); |
3337 | | assert_eq!("Sub frame is out-of-bounds.", format!("{err}")); |
3338 | | } |
3339 | | |
3340 | | #[test] |
3341 | | fn test_invalid_text_chunk() { |
3342 | | // The spec requires a NUL character (separating keyword from text) within the first 80 |
3343 | | // bytes of the chunk. Here there is no NUL character in the first 100 bytes, so this |
3344 | | // chunk is invalid and should trigger an error in `parse_text`. |
3345 | | let invalid_text_chunk = vec![b'A'; 100]; |
3346 | | |
3347 | | const SIZE: u32 = 20; |
3348 | | let mut png = Vec::new(); |
3349 | | write_png_sig(&mut png); |
3350 | | write_rgba8_ihdr_with_width(&mut png, SIZE); |
3351 | | write_chunk(&mut png, b"tEXt", invalid_text_chunk.as_slice()); |
3352 | | write_chunk( |
3353 | | &mut png, |
3354 | | b"IDAT", |
3355 | | &generate_rgba8_with_width_and_height(SIZE, SIZE), |
3356 | | ); |
3357 | | write_iend(&mut png); |
3358 | | |
3359 | | let reader = Decoder::new(Cursor::new(png)).read_info().unwrap(); |
3360 | | let info = reader.info(); |
3361 | | assert_eq!(info.width, SIZE); |
3362 | | assert_eq!(info.uncompressed_latin1_text.len(), 0); |
3363 | | } |
3364 | | |
3365 | | /// This is a regression test for https://crbug.com/451710590. |
3366 | | #[test] |
3367 | | fn test_duplicate_actl_chunk() { |
3368 | | let width = 16; |
3369 | | let frame_data = generate_rgba8_with_width_and_height(width, width); |
3370 | | |
3371 | | let mut png = Vec::new(); |
3372 | | write_png_sig(&mut png); |
3373 | | write_rgba8_ihdr_with_width(&mut png, width); |
3374 | | write_actl( |
3375 | | &mut png, |
3376 | | &crate::AnimationControl { |
3377 | | num_frames: 2, |
3378 | | num_plays: 123, |
3379 | | }, |
3380 | | ); |
3381 | | write_actl( |
3382 | | &mut png, |
3383 | | &crate::AnimationControl { |
3384 | | num_frames: 1, // <- should be ignored |
3385 | | num_plays: 456, |
3386 | | }, |
3387 | | ); |
3388 | | write_chunk(&mut png, b"IDAT", &frame_data); |
3389 | | write_iend(&mut png); |
3390 | | |
3391 | | let reader = Decoder::new(Cursor::new(png)).read_info().unwrap(); |
3392 | | let Some(actl) = reader.info().animation_control.as_ref() else { |
3393 | | panic!("No `animation_control`?") |
3394 | | }; |
3395 | | assert_eq!(actl.num_frames, 2); |
3396 | | assert_eq!(actl.num_plays, 123); |
3397 | | } |
3398 | | } |