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