Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_graphics/src/png.rs

71.6 KiB, 222 runs

created by r1870400018:13946, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1//! A PNG codec.
2//!
3//! PNG is a short list of length-prefixed chunks wrapped around a DEFLATE stream, so the only piece
4//! worth borrowing is the DEFLATE, which `flate2` supplies. The chunk framing, the CRC-32 each
5//! chunk carries, and the scanline filters are small enough to own.
6//!
7//! Owning the decoder is also a security position. An image decoder is the classic place a viewer
8//! is attacked from, and this one is written in a crate that forbids `unsafe`, checks every length
9//! it is told, and refuses a decompressed stream larger than the header says it should be.
10//!
11//! # What is supported
12//!
13//! The decoder reads every combination of bit depth and colour type the specification allows -- 1,
14//! 2, 4, 8 and 16 bits per channel across greyscale, truecolour, palette, greyscale with alpha and
15//! truecolour with alpha -- with or without Adam7 interlacing. The two are independent, so an
16//! interlaced 1-bit palette image and a non-interlaced 16-bit truecolour one are both read.
17//!
18//! Samples narrower than eight bits are widened so that the widest value the depth can hold becomes
19//! 255: a 1-bit sample is 0 or 255, a 2-bit one a multiple of 85, a 4-bit one a multiple of 17.
20//! Sixteen-bit samples are reduced to their high byte, which is a deliberate loss: a [`Pixmap`] is
21//! eight bits a channel, and a lossless path for 16 would be a second pixel type rather than a
22//! change here. Palette indices are never widened, since they are indices and not intensities.
23//!
24//! The `tRNS` chunk is read. It is nominally ancillary, but it is the one ancillary chunk that
25//! carries pixel data: for the three colour types without an alpha channel of their own it is where
26//! the alpha channel is written, so a decoder that skips it does not drop decoration, it reports the
27//! wrong image. Its samples are compared against the file's own samples at the file's own depth,
28//! before any widening, so the match is the one the specification describes.
29//!
30//! # What is refused, by name
31//!
32//! A colour type outside 0, 2, 3, 4 and 6; a bit depth outside 1, 2, 4, 8 and 16; a depth the
33//! declared colour type does not allow; a compression method other than DEFLATE; a filter method
34//! other than the adaptive one; an interlace method other than none or Adam7; a `tRNS` sample wider
35//! than the declared depth; a `tRNS` chunk under a colour type that already carries alpha, or out
36//! of order; a palette index beyond the palette; a chunk whose CRC does not match; and image data
37//! that decompresses to anything other than the exact size the header implies.
38//!
39//! The encoder writes one form only: eight-bit truecolour with alpha, not interlaced.
40//!
41//! [Written with AI entirely](https://need2know.ai/entirely-ai/code)\
42//! Anthropic Claude
43
44use crate::{
45 colour::Rgba,
46 pixmap::{
47 Pixmap,
48 MAX_PIXELS,
49 },
50};
51
52use oxedyne_fe2o3_core::prelude::*;
53
54use std::io::{
55 Read,
56 Write,
57};
58
59use flate2::{
60 read::ZlibDecoder,
61 write::ZlibEncoder,
62 Compression,
63};
64
65// the eight bytes that begin every PNG
66const SIG: [u8; 8] = [0x89, b'P', b'N', b'G', 0x0D, 0x0A, 0x1A, 0x0A];
67
68/// How a PNG says what each pixel carries.
69#[derive(Clone, Copy, Debug, PartialEq, Eq)]
70enum ColourType {
71 Grey, // one channel: luminance
72 Rgb, // three: red, green, blue
73 Palette, // one: an index into the palette
74 GreyAlpha, // two: luminance and alpha
75 Rgba, // four: red, green, blue, alpha
76}
77
78impl ColourType {
79
80 /// The colour type for a PNG's header byte.
81 fn from_code(code: u8) -> Outcome<Self> {
82 match code {
83 0 => Ok(Self::Grey),
84 2 => Ok(Self::Rgb),
85 3 => Ok(Self::Palette),
86 4 => Ok(Self::GreyAlpha),
87 6 => Ok(Self::Rgba),
88 _ => Err(err!(
89 "The PNG header declares colour type {}, which is not one of 0, 2, 3, 4 or 6.",
90 code;
91 Invalid, Input, Decode)),
92 }
93 }
94
95 fn channels(&self) -> usize {
96 match self {
97 Self::Grey => 1,
98 Self::Rgb => 3,
99 Self::Palette => 1,
100 Self::GreyAlpha => 2,
101 Self::Rgba => 4,
102 }
103 }
104
105 /// The bit depths the specification allows this colour type.
106 ///
107 /// Only the two one-channel types may go below eight bits, and only the types whose samples are
108 /// intensities rather than palette indices may go above it.
109 fn depths(&self) -> &'static [u8] {
110 match self {
111 Self::Grey => &[1, 2, 4, 8, 16],
112 Self::Rgb => &[8, 16],
113 Self::Palette => &[1, 2, 4, 8],
114 Self::GreyAlpha => &[8, 16],
115 Self::Rgba => &[8, 16],
116 }
117 }
118}
119
120/// What a `tRNS` chunk says, which is a different thing for each colour type that may carry one.
121///
122/// The greyscale and truecolour forms hold the sample as the file writes it, at the file's own bit
123/// depth, because that is what they are compared against.
124#[derive(Clone, Debug)]
125enum Trns {
126 Palette(Vec<u8>), // one alpha byte an entry; short of the palette means opaque
127 Grey(u16), // the one transparent luminance; every other is opaque
128 Rgb(u16, u16, u16), // the one transparent colour; every other is opaque
129}
130
131/// A PNG's image header, once believed.
132#[derive(Clone, Copy, Debug)]
133struct Header {
134 w: usize, // width in pixels
135 h: usize, // height in pixels
136 ct: ColourType, // what each pixel carries
137 depth: u8, // bits per sample: 1, 2, 4, 8 or 16
138 laced: bool, // is the image data Adam7 interlaced?
139}
140
141/// One pass of image data: where its pixels begin, how far apart they sit, and how many there are.
142///
143/// A non-interlaced image is one pass with a step of one in each direction, so the interlaced and
144/// non-interlaced cases share every line of the decoding below.
145#[derive(Clone, Copy, Debug)]
146struct Pass {
147 x0: usize, // column of the pass's first pixel
148 y0: usize, // and its row
149 dx: usize, // columns between one of the pass's pixels and the next
150 dy: usize, // rows between one of its scanlines and the next
151 w: usize, // pixels across the pass
152 h: usize, // scanlines down it
153}
154
155// The seven Adam7 passes, as the offset and step at which each lays its pixels into the image.
156const ADAM7: [(usize, usize, usize, usize); 7] = [
157 (0, 0, 8, 8),
158 (4, 0, 8, 8),
159 (0, 4, 4, 8),
160 (2, 0, 4, 4),
161 (0, 2, 2, 4),
162 (1, 0, 2, 2),
163 (0, 1, 1, 2),
164];
165
166// ┌───────────────────────────────────────────────────────────────────────────┐
167// │ CRC-32 │
168// └───────────────────────────────────────────────────────────────────────────┘
169
170/// The CRC-32 of some bytes, as PNG defines it: the ISO 3309 polynomial, reflected.
171fn crc32(bytes: &[u8]) -> u32 {
172 let mut crc = 0xFFFF_FFFFu32;
173 for b in bytes {
174 let mut c = (crc ^ (*b as u32)) & 0xFF;
175 for _ in 0..8 {
176 c = if c & 1 != 0 {
177 0xEDB8_8320 ^ (c >> 1)
178 } else {
179 c >> 1
180 };
181 }
182 crc = c ^ (crc >> 8);
183 }
184 crc ^ 0xFFFF_FFFF
185}
186
187/// Reads a PNG's size without inflating a single scanline.
188///
189/// The specification requires IHDR to be the first chunk, so this reads the signature and that one
190/// chunk and stops -- twenty-nine bytes whatever the size of the file. The counterpart to
191/// [`crate::jpeg::dimensions`], and for the same reason: a caller that only needs to know how big
192/// an image is should not pay to decompress it.
193///
194/// The header is validated exactly as [`decode`] validates it, so a size this returns is a size
195/// the decoder would also accept.
196pub fn dimensions(buf: &[u8]) -> Outcome<(usize, usize)> {
197 if buf.len() < SIG.len() || buf[..SIG.len()] != SIG {
198 return Err(err!(
199 "The bytes do not begin with the PNG signature."; Invalid, Input, Decode));
200 }
201 let pos = SIG.len();
202 if pos + 8 > buf.len() {
203 return Err(err!(
204 "A PNG chunk header needs 8 bytes at offset {}, but only {} remain.",
205 pos, buf.len() - pos;
206 Invalid, Input, Decode));
207 }
208 let len = u32::from_be_bytes([buf[pos], buf[pos + 1], buf[pos + 2], buf[pos + 3]]) as usize;
209 let kind = [buf[pos + 4], buf[pos + 5], buf[pos + 6], buf[pos + 7]];
210 if &kind != b"IHDR" {
211 return Err(err!(
212 "A PNG begins with its IHDR chunk, but this one begins with '{}'.",
213 String::from_utf8_lossy(&kind);
214 Invalid, Input, Decode, Missing));
215 }
216 let data_start = pos + 8;
217 let data_end = match data_start.checked_add(len) {
218 Some(e) => e,
219 None => return Err(err!(
220 "The PNG IHDR chunk declares a length of {}, which overflows.", len;
221 Invalid, Input, Decode, Overflow)),
222 };
223 if data_end > buf.len() {
224 return Err(err!(
225 "The PNG IHDR chunk declares {} bytes, but only {} remain.",
226 len, buf.len().saturating_sub(data_start);
227 Invalid, Input, Decode));
228 }
229 let hdr = res!(decode_header(&buf[data_start..data_end]));
230 Ok((hdr.w, hdr.h))
231}
232
233// ┌───────────────────────────────────────────────────────────────────────────┐
234// │ ENCODING │
235// └───────────────────────────────────────────────────────────────────────────┘
236
237/// Encodes a pixmap as a PNG: eight-bit truecolour with alpha, no interlacing.
238pub fn encode(pm: &Pixmap) -> Outcome<Vec<u8>> {
239 let (w, h) = (pm.width(), pm.height());
240 let mut out = Vec::with_capacity(w * h + 1024);
241 out.extend_from_slice(&SIG);
242
243 // The image header.
244 let mut ihdr = Vec::with_capacity(13);
245 ihdr.extend_from_slice(&(w as u32).to_be_bytes());
246 ihdr.extend_from_slice(&(h as u32).to_be_bytes());
247 ihdr.push(8); // Bit depth.
248 ihdr.push(6); // Colour type: truecolour with alpha.
249 ihdr.push(0); // Compression method: DEFLATE, the only one there is.
250 ihdr.push(0); // Filter method: the only one there is.
251 ihdr.push(0); // Interlace method: none.
252 write_chunk(&mut out, b"IHDR", &ihdr);
253
254 // The image data: each scanline filtered, then the lot deflated.
255 let idat = res!(deflate_region(pm, 0, 0, w, h));
256 write_chunk(&mut out, b"IDAT", &idat);
257
258 write_chunk(&mut out, b"IEND", &[]);
259 Ok(out)
260}
261
262/// Filters and deflates a rectangle of a pixmap, giving the bytes an `IDAT` or an `fdAT` carries.
263///
264/// The rectangle is the whole image for a still, and the part that changed for a frame of an
265/// animation. Filtering runs across the rectangle rather than the image, because that is what the
266/// rectangle's own scanlines are: a frame is decoded as a picture in its own right, and its left
267/// edge has no neighbour to the left of it whatever the canvas holds there.
268fn deflate_region(pm: &Pixmap, x0: usize, y0: usize, w: usize, h: usize) -> Outcome<Vec<u8>> {
269 if x0 + w > pm.width() || y0 + h > pm.height() {
270 return Err(err!(
271 "A region {} by {} at ({}, {}) runs outside a pixmap of {} by {}.",
272 w, h, x0, y0, pm.width(), pm.height();
273 Invalid, Input, Range));
274 }
275 let stride = w * 4;
276 let src = pm.width() * 4;
277 let mut raw = Vec::with_capacity(h * (stride + 1));
278 let mut prev = vec![0u8; stride];
279 for y in 0..h {
280 let from = (y0 + y) * src + x0 * 4;
281 let line = &pm.data()[from..from + stride];
282 filter_scanline(line, &prev, 4, &mut raw);
283 prev.copy_from_slice(line);
284 }
285 let mut z = ZlibEncoder::new(Vec::new(), Compression::default());
286 res!(z.write_all(&raw));
287 Ok(res!(z.finish()))
288}
289
290/// Appends a chunk: its length, its type, its data, and the CRC over type and data.
291fn write_chunk(out: &mut Vec<u8>, kind: &[u8; 4], data: &[u8]) {
292 out.extend_from_slice(&(data.len() as u32).to_be_bytes());
293 let start = out.len();
294 out.extend_from_slice(kind);
295 out.extend_from_slice(data);
296 let crc = crc32(&out[start..]);
297 out.extend_from_slice(&crc.to_be_bytes());
298}
299
300/// Filters one scanline, choosing whichever of the five filters leaves the smallest residue.
301///
302/// The heuristic is the one the PNG specification suggests: sum the absolute values of the filtered
303/// bytes, taken as signed, and keep the smallest. A filter that leaves the bytes closest to zero is
304/// the one DEFLATE will do most with.
305fn filter_scanline(line: &[u8], prev: &[u8], bpp: usize, out: &mut Vec<u8>) {
306 let n = line.len();
307 let mut best: Option<(u32, u8, Vec<u8>)> = None;
308 for ftype in 0u8..5 {
309 let mut buf = Vec::with_capacity(n);
310 for i in 0..n {
311 let a = if i >= bpp { line[i - bpp] } else { 0 }; // Left.
312 let b = prev[i]; // Above.
313 let c = if i >= bpp { prev[i - bpp] } else { 0 }; // Above left.
314 let x = line[i];
315 let v = match ftype {
316 0 => x,
317 1 => x.wrapping_sub(a),
318 2 => x.wrapping_sub(b),
319 3 => x.wrapping_sub(((a as u16 + b as u16) / 2) as u8),
320 _ => x.wrapping_sub(paeth(a, b, c)),
321 };
322 buf.push(v);
323 }
324 let score: u32 = buf.iter().map(|v| (*v as i8).unsigned_abs() as u32).sum();
325 let better = match &best {
326 None => true,
327 Some((s, _, _)) => score < *s,
328 };
329 if better {
330 best = Some((score, ftype, buf));
331 }
332 }
333 if let Some((_, ftype, buf)) = best {
334 out.push(ftype);
335 out.extend_from_slice(&buf);
336 }
337}
338
339/// The Paeth predictor: whichever of the left, above and above-left neighbours is closest to their
340/// linear estimate.
341fn paeth(a: u8, b: u8, c: u8) -> u8 {
342 let p = (a as i16) + (b as i16) - (c as i16);
343 let pa = (p - a as i16).abs();
344 let pb = (p - b as i16).abs();
345 let pc = (p - c as i16).abs();
346 if pa <= pb && pa <= pc {
347 a
348 } else if pb <= pc {
349 b
350 } else {
351 c
352 }
353}
354
355// ┌───────────────────────────────────────────────────────────────────────────┐
356// │ ANIMATION │
357// └───────────────────────────────────────────────────────────────────────────┘
358
359// The most frames one animation may hold, a ceiling against a length that is a mistake. A hundred
360// thousand frames is about fifty-five minutes at thirty a second, which is longer than anything
361// this format is the right container for.
362pub const MAX_FRAMES: u32 = 100_000;
363
364/// How long a frame is shown, as the exact rational a frame control chunk carries.
365///
366/// A rational rather than a count of milliseconds because that is what the chunk holds, and because
367/// the rates that matter divide badly: a thirtieth of a second is 1/30 exactly and 33.333
368/// milliseconds not at all, so an animation timed in milliseconds drifts and one timed in frames
369/// does not.
370#[derive(Clone, Copy, Debug, PartialEq, Eq)]
371pub struct Delay {
372 pub num: u16, // numerator of the delay in seconds
373 pub den: u16, // and its denominator
374}
375
376impl Delay {
377
378 /// One frame of a sequence played at the given rate.
379 pub fn fps(rate: u16) -> Outcome<Self> {
380 if rate == 0 {
381 return Err(err!("A frame rate of zero names no delay."; Invalid, Input));
382 }
383 Ok(Self { num: 1, den: rate })
384 }
385
386 pub fn ms(ms: u16) -> Self {
387 Self { num: ms, den: 1000 }
388 }
389
390 pub fn seconds(&self) -> f64 {
391 if self.den == 0 {
392 0.0
393 } else {
394 (self.num as f64) / (self.den as f64)
395 }
396 }
397}
398
399/// An animation, encoded as an APNG: frames pushed one at a time, and the bytes taken at the end.
400///
401/// The file an [`Animation`] writes is a PNG first and an animation second. Its default image is the
402/// first frame, so a reader that knows nothing of the animation chunks shows that frame and reports
403/// no error -- which is the whole reason the format is laid out the way it is, and the reason a
404/// caller who wants one file for both purposes need not write two.
405///
406/// # What each frame costs
407///
408/// Only the rectangle in which a frame differs from the one before it is written, so a drawing that
409/// moves a hand across a still background costs the hand. That is the difference between a usable
410/// file and an unusable one for the material this is for, where most of the canvas is unchanged for
411/// most of the run, and it is why frames are pushed through here rather than encoded separately and
412/// concatenated.
413///
414/// # What this is not
415///
416/// It is not a video codec. Every frame is compressed against its own predecessor by subtraction of
417/// a rectangle and by DEFLATE, with no motion estimation and no lossy transform, so a photographic
418/// sequence will be many times the size of the same sequence in a video container. Line drawing,
419/// flat colour and text -- which is what a vector document rasterises to -- is what it is good at.
420pub struct Animation {
421 w: usize, // canvas width in pixels
422 h: usize, // canvas height in pixels
423 plays: u32, // how many times to play; zero is forever
424 prev: Option<Pixmap>, // the frame the next one is differenced against
425 body: Vec<u8>, // the frame chunks written so far, in order
426 seq: u32, // the next APNG sequence number
427 n: u32, // how many frames have been pushed
428}
429
430impl Animation {
431
432 /// Begins an animation on a canvas of the given size, playing forever.
433 pub fn new(w: usize, h: usize) -> Outcome<Self> {
434 // A canvas is sized by the same rules a pixmap is, and refusing here rather than at the
435 // first frame tells the caller before they have rendered anything.
436 let _ = res!(Pixmap::new(w, h));
437 Ok(Self {
438 w,
439 h,
440 plays: 0,
441 prev: None,
442 body: Vec::new(),
443 seq: 0,
444 n: 0,
445 })
446 }
447
448 /// Sets how many times the animation plays, zero being forever.
449 pub fn plays(mut self, n: u32) -> Self {
450 self.plays = n;
451 self
452 }
453
454 pub fn frames(&self) -> u32 {
455 self.n
456 }
457
458 /// Adds a frame, shown for the given delay.
459 ///
460 /// The frame must be the size of the canvas. A frame of another size is refused rather than
461 /// scaled or cropped: an animation whose frames disagree about their size is one whose author
462 /// and encoder disagree about what is being drawn, and guessing which is right animates
463 /// something nobody rendered.
464 pub fn push(&mut self, pm: &Pixmap, delay: Delay) -> Outcome<()> {
465 if pm.width() != self.w || pm.height() != self.h {
466 return Err(err!(
467 "Frame {} is {} by {} pixels, but the animation's canvas is {} by {}.",
468 self.n, pm.width(), pm.height(), self.w, self.h;
469 Invalid, Input, Mismatch));
470 }
471 if delay.den == 0 {
472 return Err(err!(
473 "Frame {} is given a delay of {}/0 seconds, which names no duration.",
474 self.n, delay.num;
475 Invalid, Input));
476 }
477 if self.n >= MAX_FRAMES {
478 return Err(err!(
479 "An animation may hold {} frames, and this is frame {}.", MAX_FRAMES, self.n + 1;
480 Invalid, Input, Excessive));
481 }
482
483 // The rectangle to write: the whole canvas for the first frame, and afterwards only where
484 // this frame differs from the last. A frame identical to its predecessor still has to be
485 // written, because it carries the delay that holds the picture on the screen, so it is
486 // written as the smallest rectangle a frame control chunk permits.
487 let (x0, y0, w, h) = match &self.prev {
488 None => (0, 0, self.w, self.h),
489 Some(prev) => match difference(prev, pm) {
490 Some(r) => r,
491 None => (0, 0, 1, 1),
492 },
493 };
494
495 let mut fctl = Vec::with_capacity(26);
496 fctl.extend_from_slice(&self.seq.to_be_bytes());
497 fctl.extend_from_slice(&(w as u32).to_be_bytes());
498 fctl.extend_from_slice(&(h as u32).to_be_bytes());
499 fctl.extend_from_slice(&(x0 as u32).to_be_bytes());
500 fctl.extend_from_slice(&(y0 as u32).to_be_bytes());
501 fctl.extend_from_slice(&delay.num.to_be_bytes());
502 fctl.extend_from_slice(&delay.den.to_be_bytes());
503 fctl.push(0); // Dispose: leave the canvas as this frame left it.
504 fctl.push(0); // Blend: the frame's pixels replace what is under them, alpha included.
505 write_chunk(&mut self.body, b"fcTL", &fctl);
506 self.seq += 1;
507
508 let data = res!(deflate_region(pm, x0, y0, w, h));
509 if self.n == 0 {
510 // The first frame is the file's default image, so it is an `IDAT` and takes no
511 // sequence number of its own.
512 write_chunk(&mut self.body, b"IDAT", &data);
513 } else {
514 let mut fdat = Vec::with_capacity(data.len() + 4);
515 fdat.extend_from_slice(&self.seq.to_be_bytes());
516 fdat.extend_from_slice(&data);
517 write_chunk(&mut self.body, b"fdAT", &fdat);
518 self.seq += 1;
519 }
520
521 self.prev = Some(pm.clone());
522 self.n += 1;
523 Ok(())
524 }
525
526 pub fn finish(self) -> Outcome<Vec<u8>> {
527 if self.n == 0 {
528 return Err(err!(
529 "An animation must hold at least one frame, and none were pushed.";
530 Invalid, Input, Missing));
531 }
532 let mut out = Vec::with_capacity(self.body.len() + 128);
533 out.extend_from_slice(&SIG);
534
535 let mut ihdr = Vec::with_capacity(13);
536 ihdr.extend_from_slice(&(self.w as u32).to_be_bytes());
537 ihdr.extend_from_slice(&(self.h as u32).to_be_bytes());
538 ihdr.push(8); // Bit depth.
539 ihdr.push(6); // Colour type: truecolour with alpha.
540 ihdr.push(0); // Compression method: DEFLATE, the only one there is.
541 ihdr.push(0); // Filter method: the only one there is.
542 ihdr.push(0); // Interlace method: none.
543 write_chunk(&mut out, b"IHDR", &ihdr);
544
545 // The animation control chunk, which must precede the first `IDAT` and which is why the
546 // frames were held rather than written: it counts them.
547 let mut actl = Vec::with_capacity(8);
548 actl.extend_from_slice(&self.n.to_be_bytes());
549 actl.extend_from_slice(&self.plays.to_be_bytes());
550 write_chunk(&mut out, b"acTL", &actl);
551
552 out.extend_from_slice(&self.body);
553 write_chunk(&mut out, b"IEND", &[]);
554 Ok(out)
555 }
556}
557
558/// The smallest rectangle outside which two pixmaps of the same size hold the same pixels, as
559/// `(x, y, width, height)`, or `None` where they are identical.
560fn difference(a: &Pixmap, b: &Pixmap) -> Option<(usize, usize, usize, usize)> {
561 if a.width() != b.width() || a.height() != b.height() {
562 return Some((0, 0, b.width(), b.height()));
563 }
564 let (w, h) = (b.width(), b.height());
565 let stride = w * 4;
566 let (mut x0, mut y0, mut x1, mut y1) = (w, h, 0usize, 0usize);
567 for y in 0..h {
568 let ra = &a.data()[y * stride..(y + 1) * stride];
569 let rb = &b.data()[y * stride..(y + 1) * stride];
570 if ra == rb {
571 continue;
572 }
573 if y < y0 {
574 y0 = y;
575 }
576 y1 = y + 1;
577 for x in 0..w {
578 if ra[x * 4..x * 4 + 4] != rb[x * 4..x * 4 + 4] {
579 if x < x0 {
580 x0 = x;
581 }
582 if x + 1 > x1 {
583 x1 = x + 1;
584 }
585 }
586 }
587 }
588 if x1 <= x0 || y1 <= y0 {
589 None
590 } else {
591 Some((x0, y0, x1 - x0, y1 - y0))
592 }
593}
594
595// ┌───────────────────────────────────────────────────────────────────────────┐
596// │ DECODING │
597// └───────────────────────────────────────────────────────────────────────────┘
598
599/// The passes the image data is written in: one for a plain image, up to seven for an interlaced
600/// one.
601///
602/// An Adam7 pass whose grid falls entirely outside a small image holds no pixels and no scanlines,
603/// and contributes nothing at all to the stream -- not even a filter byte. Such passes are dropped
604/// here, so that everything downstream, the size arithmetic included, sees only passes that exist.
605fn passes_of(hdr: &Header) -> Vec<Pass> {
606 if !hdr.laced {
607 return vec![Pass { x0: 0, y0: 0, dx: 1, dy: 1, w: hdr.w, h: hdr.h }];
608 }
609 let mut out = Vec::with_capacity(ADAM7.len());
610 for (x0, y0, dx, dy) in ADAM7 {
611 // The pixels of a pass are those at x0, x0 + dx, x0 + 2dx and so on that fall inside the
612 // image, which is a count of zero once x0 reaches the width.
613 let w = if hdr.w > x0 { (hdr.w - x0 + dx - 1) / dx } else { 0 };
614 let h = if hdr.h > y0 { (hdr.h - y0 + dy - 1) / dy } else { 0 };
615 if w > 0 && h > 0 {
616 out.push(Pass { x0, y0, dx, dy, w, h });
617 }
618 }
619 out
620}
621
622/// The number of bytes one scanline of `w` pixels occupies, rounded up to a whole byte.
623fn row_bytes(ct: ColourType, depth: u8, w: usize) -> Outcome<usize> {
624 let bits = match w.checked_mul(ct.channels()).and_then(|n| n.checked_mul(depth as usize)) {
625 Some(n) => n,
626 None => return Err(err!(
627 "A PNG scanline of {} pixels at {} channels of {} bits overflows a count of bits.",
628 w, ct.channels(), depth;
629 Invalid, Input, Decode, Overflow)),
630 };
631 Ok((bits + 7) / 8)
632}
633
634/// The number of bytes a pixel occupies in a filtered scanline, which is what the filters step by.
635///
636/// The specification rounds this up to one, so that samples narrower than a byte filter against the
637/// byte beside them rather than against a fraction of one.
638fn filter_bpp(ct: ColourType, depth: u8) -> usize {
639 let bits = ct.channels() * depth as usize;
640 std::cmp::max(1, bits / 8)
641}
642
643/// The exact number of bytes the image data must decompress to.
644///
645/// Each scanline of each pass carries one filter byte ahead of its samples, so an interlaced image
646/// carries as many filter bytes as all seven passes have scanlines between them, which is more than
647/// the image has rows. Getting this wrong in either direction either refuses a sound file or lets a
648/// larger stream through the ceiling, so it is summed over the passes rather than estimated.
649fn expected_size(hdr: &Header, passes: &[Pass]) -> Outcome<usize> {
650 let mut total = 0usize;
651 for p in passes {
652 let stride = res!(row_bytes(hdr.ct, hdr.depth, p.w));
653 let pass = match (stride + 1).checked_mul(p.h) {
654 Some(n) => n,
655 None => return Err(err!(
656 "A PNG pass of {} by {} pixels overflows a count of bytes.", p.w, p.h;
657 Invalid, Input, Decode, Overflow)),
658 };
659 total = match total.checked_add(pass) {
660 Some(n) => n,
661 None => return Err(err!(
662 "A PNG of {} by {} pixels overflows a count of image bytes.", hdr.w, hdr.h;
663 Invalid, Input, Decode, Overflow)),
664 };
665 }
666 Ok(total)
667}
668
669/// Every length is checked against the bytes actually present, every chunk's CRC is verified, and
670/// the decompressed stream is refused the moment it exceeds the size the header implies, so a small
671/// file cannot expand into a large allocation.
672pub fn decode(buf: &[u8]) -> Outcome<Pixmap> {
673 if buf.len() < SIG.len() || buf[..SIG.len()] != SIG {
674 return Err(err!(
675 "The bytes do not begin with the PNG signature."; Invalid, Input, Decode));
676 }
677 let mut pos = SIG.len();
678 let mut hdr: Option<Header> = None;
679 let mut palette: Vec<Rgba> = Vec::new();
680 let mut trns: Option<Trns> = None;
681 let mut idat: Vec<u8> = Vec::new();
682 let mut ended = false;
683
684 while pos < buf.len() {
685 // Length, type, data, CRC.
686 if pos + 8 > buf.len() {
687 return Err(err!(
688 "A PNG chunk header needs 8 bytes at offset {}, but only {} remain.",
689 pos, buf.len() - pos;
690 Invalid, Input, Decode));
691 }
692 let len = u32::from_be_bytes([buf[pos], buf[pos + 1], buf[pos + 2], buf[pos + 3]]) as usize;
693 let kind = [buf[pos + 4], buf[pos + 5], buf[pos + 6], buf[pos + 7]];
694 let data_start = pos + 8;
695 let data_end = match data_start.checked_add(len) {
696 Some(e) => e,
697 None => return Err(err!(
698 "A PNG chunk at offset {} declares a length of {}, which overflows.", pos, len;
699 Invalid, Input, Decode, Overflow)),
700 };
701 if data_end + 4 > buf.len() {
702 return Err(err!(
703 "The PNG chunk '{}' at offset {} declares {} bytes, but only {} remain.",
704 String::from_utf8_lossy(&kind), pos, len, buf.len().saturating_sub(data_start);
705 Invalid, Input, Decode));
706 }
707 let data = &buf[data_start..data_end];
708 let want = u32::from_be_bytes([
709 buf[data_end],
710 buf[data_end + 1],
711 buf[data_end + 2],
712 buf[data_end + 3],
713 ]);
714 let got = crc32(&buf[pos + 4..data_end]);
715 if got != want {
716 return Err(err!(
717 "The PNG chunk '{}' at offset {} carries the CRC {:#010X}, but its bytes hash to \
718 {:#010X}.", String::from_utf8_lossy(&kind), pos, want, got;
719 Invalid, Input, Decode, Checksum));
720 }
721 pos = data_end + 4;
722
723 match &kind {
724 b"IHDR" => hdr = Some(res!(decode_header(data))),
725 b"PLTE" => palette = res!(decode_palette(data)),
726 b"tRNS" => {
727 // tRNS precedes the image data, and for a palette image follows the palette.
728 if !idat.is_empty() {
729 return Err(err!(
730 "The PNG carries a tRNS chunk after its image data, but tRNS precedes IDAT.";
731 Invalid, Input, Decode));
732 }
733 let h = match hdr {
734 Some(h) => h,
735 None => return Err(err!(
736 "The PNG carries a tRNS chunk before its IHDR chunk.";
737 Invalid, Input, Decode, Missing)),
738 };
739 trns = Some(res!(decode_transparency(data, h.ct, h.depth, &palette)));
740 },
741 b"IDAT" => idat.extend_from_slice(data),
742 b"IEND" => {
743 ended = true;
744 break;
745 },
746 _ => (), // The remaining ancillary chunks are decoration, and not our business.
747 }
748 }
749
750 if !ended {
751 return Err(err!("The PNG has no IEND chunk."; Invalid, Input, Decode, Missing));
752 }
753 let hdr = match hdr {
754 Some(h) => h,
755 None => return Err(err!("The PNG has no IHDR chunk."; Invalid, Input, Decode, Missing)),
756 };
757 if idat.is_empty() {
758 return Err(err!("The PNG has no image data."; Invalid, Input, Decode, Missing));
759 }
760 if hdr.ct == ColourType::Palette && palette.is_empty() {
761 return Err(err!(
762 "The PNG declares a palette colour type but carries no PLTE chunk.";
763 Invalid, Input, Decode, Missing));
764 }
765
766 // Inflate, refusing anything larger than the header says it should be. The buffer starts small
767 // whatever the header claims, so that a handful of bytes declaring a large image cannot make us
768 // reserve a large allocation before a single byte of it has been inflated.
769 let passes = passes_of(&hdr);
770 let expect = res!(expected_size(&hdr, &passes));
771 let mut raw = Vec::with_capacity(std::cmp::min(expect, 1 << 20));
772 let mut z = ZlibDecoder::new(&idat[..]).take((expect as u64) + 1);
773 res!(z.read_to_end(&mut raw));
774 if raw.len() != expect {
775 return Err(err!(
776 "The PNG's image data decompresses to {} bytes, but its header of {} by {} pixels at {} \
777 bits a channel{} implies {}.",
778 raw.len(), hdr.w, hdr.h, hdr.depth,
779 if hdr.laced { ", interlaced," } else { "," }, expect;
780 Invalid, Input, Decode, Mismatch));
781 }
782
783 // Unfilter each pass, then expand into RGBA. A pass's scanlines filter against each other and
784 // not against the image's rows, so `prev` restarts at each pass.
785 let bpp = filter_bpp(hdr.ct, hdr.depth);
786 let mut pm = res!(Pixmap::new(hdr.w, hdr.h));
787 let mut at = 0;
788 for p in &passes {
789 let stride = res!(row_bytes(hdr.ct, hdr.depth, p.w));
790 let mut prev = vec![0u8; stride];
791 let mut line = vec![0u8; stride];
792 for j in 0..p.h {
793 // The pass arithmetic above sized `raw` for exactly these reads, but the slice is
794 // taken through `get` all the same: an indexing panic is not a refusal.
795 let ftype = match raw.get(at) {
796 Some(b) => *b,
797 None => return Err(err!(
798 "The PNG's image data ends before the filter byte of scanline {} of a pass.", j;
799 Invalid, Input, Decode, Missing)),
800 };
801 match raw.get(at + 1..at + 1 + stride) {
802 Some(s) => line.copy_from_slice(s),
803 None => return Err(err!(
804 "The PNG's image data ends {} bytes into scanline {} of a pass, which needs {}.",
805 raw.len().saturating_sub(at + 1), j, stride;
806 Invalid, Input, Decode, Missing)),
807 }
808 at += stride + 1;
809 res!(unfilter_scanline(ftype, &mut line, &prev, bpp, j));
810 let y = p.y0 + j * p.dy;
811 for i in 0..p.w {
812 let c = res!(pixel_of(&hdr, &line, i, &palette, trns.as_ref()));
813 pm.set_pixel(p.x0 + i * p.dx, y, c);
814 }
815 prev.copy_from_slice(&line);
816 }
817 }
818 Ok(pm)
819}
820
821/// Reads the image header, and refuses by name every combination the format does not define.
822fn decode_header(data: &[u8]) -> Outcome<Header> {
823 if data.len() != 13 {
824 return Err(err!(
825 "A PNG image header is 13 bytes, but this one is {}.", data.len();
826 Invalid, Input, Decode));
827 }
828 let w = u32::from_be_bytes([data[0], data[1], data[2], data[3]]) as usize;
829 let h = u32::from_be_bytes([data[4], data[5], data[6], data[7]]) as usize;
830 let depth = data[8];
831 let ct = res!(ColourType::from_code(data[9]));
832 let comp = data[10];
833 let filt = data[11];
834 let interlace = data[12];
835
836 if w == 0 || h == 0 {
837 return Err(err!(
838 "The PNG header declares a size of {} by {} pixels.", w, h; Invalid, Input, Decode));
839 }
840 let n = match w.checked_mul(h) {
841 Some(n) => n,
842 None => return Err(err!(
843 "The PNG header declares {} by {} pixels, which overflows.", w, h;
844 Invalid, Input, Decode, Overflow)),
845 };
846 if n > MAX_PIXELS {
847 return Err(err!(
848 "The PNG header declares {} by {} pixels, over the ceiling of {}.", w, h, MAX_PIXELS;
849 Invalid, Input, Decode, Excessive));
850 }
851 if !matches!(depth, 1 | 2 | 4 | 8 | 16) {
852 return Err(err!(
853 "The PNG declares {} bits per channel, which is not one of 1, 2, 4, 8 or 16.", depth;
854 Invalid, Input, Decode));
855 }
856 if !ct.depths().contains(&depth) {
857 return Err(err!(
858 "The PNG declares {} bits per channel under colour type {:?}, which the specification \
859 allows only at {:?} bits.", depth, ct, ct.depths();
860 Invalid, Input, Decode));
861 }
862 if comp != 0 {
863 return Err(err!(
864 "The PNG declares compression method {}. DEFLATE, method 0, is the only one the \
865 specification defines.", comp;
866 Invalid, Input, Decode));
867 }
868 if filt != 0 {
869 return Err(err!(
870 "The PNG declares filter method {}. The adaptive filtering of method 0 is the only one \
871 the specification defines.", filt;
872 Invalid, Input, Decode));
873 }
874 let laced = match interlace {
875 0 => false,
876 1 => true,
877 _ => return Err(err!(
878 "The PNG declares interlace method {}, which is neither 0, no interlacing, nor 1, \
879 Adam7.", interlace;
880 Invalid, Input, Decode)),
881 };
882 Ok(Header { w, h, ct, depth, laced })
883}
884
885/// Reads a palette: three bytes a colour, opaque.
886fn decode_palette(data: &[u8]) -> Outcome<Vec<Rgba>> {
887 if data.len() % 3 != 0 {
888 return Err(err!(
889 "A PNG palette holds 3 bytes per entry, but this one is {} bytes.", data.len();
890 Invalid, Input, Decode));
891 }
892 Ok(data.chunks_exact(3).map(|c| Rgba::opaque(c[0], c[1], c[2])).collect())
893}
894
895/// Reads a transparency chunk, whose shape the colour type it accompanies decides.
896///
897/// The specification forbids `tRNS` to the two colour types that already carry an alpha channel, so
898/// its presence there is a malformed file rather than a chunk to ignore.
899fn decode_transparency(data: &[u8], ct: ColourType, depth: u8, palette: &[Rgba]) -> Outcome<Trns> {
900 match ct {
901 ColourType::Palette => {
902 if palette.is_empty() {
903 return Err(err!(
904 "The PNG carries a tRNS chunk before its PLTE chunk. In a palette image the \
905 palette comes first, because tRNS gives one alpha byte per palette entry.";
906 Invalid, Input, Decode, Order));
907 }
908 if data.len() > palette.len() {
909 return Err(err!(
910 "The PNG's tRNS chunk holds {} alpha bytes, but its palette holds only {} \
911 entries.", data.len(), palette.len();
912 Invalid, Input, Decode, Mismatch));
913 }
914 Ok(Trns::Palette(data.to_vec()))
915 },
916 ColourType::Grey => {
917 if data.len() != 2 {
918 return Err(err!(
919 "A greyscale PNG's tRNS chunk is a single 2-byte sample, but this one is {} \
920 bytes.", data.len();
921 Invalid, Input, Decode));
922 }
923 Ok(Trns::Grey(res!(trns_sample(&data[0..2], "luminance", depth))))
924 },
925 ColourType::Rgb => {
926 if data.len() != 6 {
927 return Err(err!(
928 "A truecolour PNG's tRNS chunk is three 2-byte samples, but this one is {} \
929 bytes.", data.len();
930 Invalid, Input, Decode));
931 }
932 Ok(Trns::Rgb(
933 res!(trns_sample(&data[0..2], "red", depth)),
934 res!(trns_sample(&data[2..4], "green", depth)),
935 res!(trns_sample(&data[4..6], "blue", depth)),
936 ))
937 },
938 ColourType::GreyAlpha | ColourType::Rgba => Err(err!(
939 "The PNG carries a tRNS chunk under colour type {:?}, which already has an alpha \
940 channel. The specification forbids the combination.", ct;
941 Invalid, Input, Decode)),
942 }
943}
944
945/// Reads one of `tRNS`'s samples, which the specification writes as 16 bits big-endian whatever the
946/// bit depth.
947///
948/// The value must fit the declared depth, since it is compared against samples of that width. A
949/// larger one names a sample no pixel in the file can hold, and is refused rather than truncated
950/// into a match that was never there.
951fn trns_sample(be: &[u8], name: &str, depth: u8) -> Outcome<u16> {
952 let v = u16::from_be_bytes([be[0], be[1]]);
953 let max = if depth >= 16 { u16::MAX } else { (1u16 << depth) - 1 };
954 if v > max {
955 return Err(err!(
956 "The PNG's tRNS chunk names a transparent {} of {}, but at {} bits a channel its \
957 samples run from 0 to {}.", name, v, depth, max;
958 Invalid, Input, Decode, Range));
959 }
960 Ok(v)
961}
962
963/// Reads the `i`th sample of an unfiltered scanline, at the bit depth the header declares.
964///
965/// Samples narrower than a byte are packed most significant first, and the row is padded out to a
966/// whole byte. The padding is masked away rather than merely left unread, so bits a hostile file
967/// sets there cannot reach a pixel.
968fn sample_at(line: &[u8], i: usize, depth: u8) -> Outcome<u16> {
969 match depth {
970 1 | 2 | 4 => {
971 let per = 8 / depth as usize; // Samples to the byte.
972 let byte = match line.get(i / per) {
973 Some(b) => *b,
974 None => return Err(err!(
975 "Sample {} at {} bits lies beyond a scanline of {} bytes.", i, depth, line.len();
976 Invalid, Input, Decode, Range)),
977 };
978 let shift = 8 - depth as usize * (i % per + 1);
979 Ok(((byte >> shift) as u16) & ((1u16 << depth) - 1))
980 },
981 8 => match line.get(i) {
982 Some(b) => Ok(*b as u16),
983 None => Err(err!(
984 "Sample {} at 8 bits lies beyond a scanline of {} bytes.", i, line.len();
985 Invalid, Input, Decode, Range)),
986 },
987 16 => match (line.get(2 * i), line.get(2 * i + 1)) {
988 (Some(hi), Some(lo)) => Ok(u16::from_be_bytes([*hi, *lo])),
989 _ => Err(err!(
990 "Sample {} at 16 bits lies beyond a scanline of {} bytes.", i, line.len();
991 Invalid, Input, Decode, Range)),
992 },
993 _ => Err(err!(
994 "A scanline was read at {} bits a sample, which the header should have refused.", depth;
995 Bug, Unreachable)),
996 }
997}
998
999/// Widens a sample of the declared bit depth to the eight bits a pixmap holds.
1000///
1001/// The narrow depths are scaled so that the widest value the depth can hold becomes 255, which is
1002/// what the specification's sample-depth scaling amounts to and what makes a 1-bit image black and
1003/// white rather than black and very-nearly-black.
1004///
1005/// Sixteen bits are reduced by keeping the high byte, the same reduction `libpng` performs for
1006/// `png_set_strip_16`. It is a truncation and not a rounding: a sample that is an eight-bit value
1007/// written twice, which is what almost every 16-bit file in practice holds, survives it exactly,
1008/// and anything else loses at most one part in 256. A pixmap is eight bits a channel, so some
1009/// reduction has to happen here; a lossless path would be a wider pixel type, not a change to this
1010/// function.
1011fn widen(v: u16, depth: u8) -> u8 {
1012 match depth {
1013 1 => if v == 0 { 0 } else { 255 },
1014 2 => (v as u8) * 85,
1015 4 => (v as u8) * 17,
1016 8 => v as u8,
1017 _ => (v >> 8) as u8,
1018 }
1019}
1020
1021/// Reverses one scanline's filter, in place.
1022fn unfilter_scanline(
1023 ftype: u8,
1024 line: &mut [u8],
1025 prev: &[u8],
1026 bpp: usize,
1027 y: usize,
1028)
1029 -> Outcome<()>
1030{
1031 let n = line.len();
1032 for i in 0..n {
1033 let a = if i >= bpp { line[i - bpp] } else { 0 }; // Left, already unfiltered.
1034 let b = prev[i]; // Above.
1035 let c = if i >= bpp { prev[i - bpp] } else { 0 }; // Above left.
1036 let x = line[i];
1037 line[i] = match ftype {
1038 0 => x,
1039 1 => x.wrapping_add(a),
1040 2 => x.wrapping_add(b),
1041 3 => x.wrapping_add(((a as u16 + b as u16) / 2) as u8),
1042 4 => x.wrapping_add(paeth(a, b, c)),
1043 _ => return Err(err!(
1044 "Scanline {} declares filter type {}, which is not one of 0 to 4.", y, ftype;
1045 Invalid, Input, Decode)),
1046 };
1047 }
1048 Ok(())
1049}
1050
1051/// Reads one pixel out of an unfiltered scanline, whatever the colour type and bit depth.
1052///
1053/// The three colour types that carry no alpha channel take theirs from `tRNS`, if the file gave one.
1054/// The comparison against `tRNS` happens on the raw sample, before widening, because that is the
1055/// sample the chunk names; comparing widened values would make every 1-bit black pixel match a
1056/// `tRNS` of 0 whether or not the file said so.
1057fn pixel_of(
1058 hdr: &Header,
1059 line: &[u8],
1060 x: usize,
1061 palette: &[Rgba],
1062 trns: Option<&Trns>,
1063)
1064 -> Outcome<Rgba>
1065{
1066 let d = hdr.depth;
1067 let i = x * hdr.ct.channels(); // Index of the pixel's first sample.
1068 match hdr.ct {
1069 ColourType::Grey => {
1070 let s = res!(sample_at(line, i, d));
1071 let a = match trns {
1072 Some(Trns::Grey(t)) if s == *t => 0,
1073 _ => 255,
1074 };
1075 let g = widen(s, d);
1076 Ok(Rgba::new(g, g, g, a))
1077 },
1078 ColourType::Rgb => {
1079 let r = res!(sample_at(line, i, d));
1080 let g = res!(sample_at(line, i + 1, d));
1081 let b = res!(sample_at(line, i + 2, d));
1082 let a = match trns {
1083 Some(Trns::Rgb(tr, tg, tb)) if r == *tr && g == *tg && b == *tb => 0,
1084 _ => 255,
1085 };
1086 Ok(Rgba::new(widen(r, d), widen(g, d), widen(b, d), a))
1087 },
1088 ColourType::GreyAlpha => {
1089 let g = widen(res!(sample_at(line, i, d)), d);
1090 let a = widen(res!(sample_at(line, i + 1, d)), d);
1091 Ok(Rgba::new(g, g, g, a))
1092 },
1093 ColourType::Rgba => Ok(Rgba::new(
1094 widen(res!(sample_at(line, i, d)), d),
1095 widen(res!(sample_at(line, i + 1, d)), d),
1096 widen(res!(sample_at(line, i + 2, d)), d),
1097 widen(res!(sample_at(line, i + 3, d)), d),
1098 )),
1099 ColourType::Palette => {
1100 // A palette sample is an index and not an intensity, so it is never widened.
1101 let idx = res!(sample_at(line, i, d)) as usize;
1102 match palette.get(idx) {
1103 Some(c) => {
1104 let mut c = *c;
1105 // tRNS may stop short of the palette's end, leaving the rest opaque.
1106 if let Some(Trns::Palette(alpha)) = trns {
1107 if let Some(a) = alpha.get(idx) {
1108 c.a = *a;
1109 }
1110 }
1111 Ok(c)
1112 },
1113 None => Err(err!(
1114 "A palette PNG names colour {} at pixel {}, but its palette holds {}.",
1115 idx, x, palette.len();
1116 Invalid, Input, Decode, Range)),
1117 }
1118 },
1119 }
1120}
1121
1122#[cfg(test)]
1123mod tests {
1124 use super::*;
1125 use crate::path::Bounds;
1126
1127 #[test]
1128 fn test_a_pixmap_survives_a_round_trip_00() -> Outcome<()> {
1129 let mut pm = res!(Pixmap::filled(17, 9, Rgba::new(10, 20, 30, 255)));
1130 res!(pm.fill_bounds(Bounds::new(2.0, 2.0, 8.0, 6.0), Rgba::new(200, 100, 50, 128), None));
1131 let buf = res!(encode(&pm));
1132 let back = res!(decode(&buf));
1133 assert_eq!(back.width(), 17);
1134 assert_eq!(back.height(), 9);
1135 assert_eq!(back, pm, "the decoded pixmap must equal the one encoded");
1136 Ok(())
1137 }
1138
1139 #[test]
1140 fn test_the_signature_is_checked_01() {
1141 assert!(decode(&[0u8; 8]).is_err());
1142 assert!(decode(&[]).is_err());
1143 }
1144
1145 /// The size read from the header is the size the decoder produces, and it is read from the
1146 /// first twenty-nine bytes rather than from the image data.
1147 #[test]
1148 fn test_the_size_is_read_without_decoding_02() -> Outcome<()> {
1149 let pm = res!(Pixmap::filled(37, 11, Rgba::new(1, 2, 3, 255)));
1150 let buf = res!(encode(&pm));
1151 assert_eq!((37, 11), res!(dimensions(&buf)));
1152 // Signature plus one whole IHDR chunk is 8 + 8 + 13 + 4; everything after it is data.
1153 assert_eq!((37, 11), res!(dimensions(&buf[..33])));
1154 Ok(())
1155 }
1156
1157 /// Bytes that are not a PNG, or a PNG whose first chunk is not IHDR, are refused rather than
1158 /// answered with a guess.
1159 #[test]
1160 fn test_a_size_is_refused_rather_than_guessed_03() {
1161 assert!(dimensions(&[]).is_err());
1162 assert!(dimensions(b"GIF89a\x01\x00").is_err());
1163 let mut wrong = SIG.to_vec();
1164 wrong.extend_from_slice(&13u32.to_be_bytes());
1165 wrong.extend_from_slice(b"IDAT");
1166 wrong.extend_from_slice(&[0u8; 13]);
1167 assert!(dimensions(&wrong).is_err(), "a first chunk that is not IHDR must be refused");
1168 // A header that says it is longer than the bytes present.
1169 let mut short = SIG.to_vec();
1170 short.extend_from_slice(&13u32.to_be_bytes());
1171 short.extend_from_slice(b"IHDR");
1172 short.extend_from_slice(&[0u8; 4]);
1173 assert!(dimensions(&short).is_err());
1174 }
1175
1176 #[test]
1177 fn test_a_corrupted_crc_is_caught_02() -> Outcome<()> {
1178 let pm = res!(Pixmap::filled(4, 4, Rgba::WHITE));
1179 let mut buf = res!(encode(&pm));
1180 // Flip a byte of the image data, leaving its chunk's CRC declaring the old bytes.
1181 let n = buf.len();
1182 buf[n - 20] ^= 0xFF;
1183 assert!(decode(&buf).is_err(), "a corrupted chunk must not decode");
1184 Ok(())
1185 }
1186
1187 #[test]
1188 fn test_a_truncated_file_is_caught_03() -> Outcome<()> {
1189 let pm = res!(Pixmap::filled(4, 4, Rgba::WHITE));
1190 let buf = res!(encode(&pm));
1191 for cut in [10, 20, buf.len() - 1] {
1192 assert!(decode(&buf[..cut]).is_err(), "a file cut at {} must not decode", cut);
1193 }
1194 Ok(())
1195 }
1196
1197 #[test]
1198 fn test_an_absurd_header_is_refused_04() -> Outcome<()> {
1199 // A header claiming 60000 by 60000 pixels: 3.6 billion, over the ceiling.
1200 let pm = res!(Pixmap::filled(2, 2, Rgba::WHITE));
1201 let mut buf = res!(encode(&pm));
1202 buf[16..20].copy_from_slice(&60000u32.to_be_bytes());
1203 buf[20..24].copy_from_slice(&60000u32.to_be_bytes());
1204 // Repair the CRC, so that the size and not the checksum is what refuses it. The CRC covers
1205 // the chunk's type and data: 4 + 13 bytes from offset 12.
1206 let crc = crc32(&buf[12..29]);
1207 buf[29..33].copy_from_slice(&crc.to_be_bytes());
1208 assert!(decode(&buf).is_err(), "a header over the pixel ceiling must be refused");
1209 Ok(())
1210 }
1211
1212 #[test]
1213 fn test_crc32_matches_the_known_value_05() {
1214 // The CRC-32 of "123456789" is a standard check value.
1215 assert_eq!(crc32(b"123456789"), 0xCBF4_3926);
1216 }
1217
1218 #[test]
1219 fn test_paeth_prefers_the_nearest_neighbour_06() {
1220 assert_eq!(paeth(10, 20, 10), 20); // The estimate lands on b.
1221 assert_eq!(paeth(200, 5, 5), 200); // The estimate lands on a.
1222 }
1223
1224 // ┌───────────────────────────────────────────────────────────────────────┐
1225 // │ tRNS │
1226 // └───────────────────────────────────────────────────────────────────────┘
1227 //
1228 // The encoder above writes colour type 6 and nothing else, so it can never produce a file with a
1229 // tRNS chunk in it, and a round trip through our own encoder cannot say whether tRNS is read
1230 // correctly or read at all. The three files below are therefore written out byte by byte, and
1231 // the alpha each pixel is expected to carry was taken from an independent decoder (Python's
1232 // PIL, reading these exact bytes) rather than from this one.
1233
1234 /// Colour type 3, 4 by 2. Four palette entries: red, green, blue, white. The tRNS chunk is two
1235 /// bytes long against a palette of four, so entries 2 and 3 fall beyond it and stay opaque.
1236 const PAL_TRNS: [u8; 113] = [
1237 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D,
1238 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02,
1239 0x08, 0x03, 0x00, 0x00, 0x00, 0x48, 0x76, 0x8D, 0x51, 0x00, 0x00, 0x00,
1240 0x0C, 0x50, 0x4C, 0x54, 0x45, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00,
1241 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFB, 0x00, 0x60, 0xF6, 0x00, 0x00, 0x00,
1242 0x02, 0x74, 0x52, 0x4E, 0x53, 0x00, 0x80, 0x9B, 0x2B, 0x4E, 0x18, 0x00,
1243 0x00, 0x00, 0x12, 0x49, 0x44, 0x41, 0x54, 0x78, 0xDA, 0x63, 0x60, 0x60,
1244 0x64, 0x62, 0x66, 0x60, 0x66, 0x62, 0x64, 0x00, 0x00, 0x00, 0x46, 0x00,
1245 0x0D, 0xA4, 0x00, 0x59, 0x7B, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E,
1246 0x44, 0xAE, 0x42, 0x60, 0x82,
1247 ];
1248
1249 /// Colour type 0, 4 by 2. The tRNS chunk names the single transparent luminance, 128.
1250 const GREY_TRNS: [u8; 89] = [
1251 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D,
1252 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02,
1253 0x08, 0x00, 0x00, 0x00, 0x00, 0x5A, 0xC3, 0x22, 0xBF, 0x00, 0x00, 0x00,
1254 0x02, 0x74, 0x52, 0x4E, 0x53, 0x00, 0x80, 0x9B, 0x2B, 0x4E, 0x18, 0x00,
1255 0x00, 0x00, 0x12, 0x49, 0x44, 0x41, 0x54, 0x78, 0xDA, 0x63, 0x60, 0x68,
1256 0xF8, 0xDF, 0xC0, 0xD0, 0xC0, 0xD5, 0x70, 0x02, 0x00, 0x11, 0xE9, 0x03,
1257 0xD2, 0xF6, 0xE5, 0x55, 0x6C, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E,
1258 0x44, 0xAE, 0x42, 0x60, 0x82,
1259 ];
1260
1261 /// Colour type 2, 4 by 2. The tRNS chunk names the single transparent colour, pure red. This is
1262 /// the shape of PngSuite's `tbrn2c08`, where an independent decoder finds 453 of the 1024 pixels
1263 /// fully transparent and this codec, before tRNS was read, found none.
1264 const RGB_TRNS: [u8; 102] = [
1265 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D,
1266 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02,
1267 0x08, 0x02, 0x00, 0x00, 0x00, 0xF0, 0xCA, 0xEA, 0x34, 0x00, 0x00, 0x00,
1268 0x06, 0x74, 0x52, 0x4E, 0x53, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xA4,
1269 0xC2, 0xC0, 0x1D, 0x00, 0x00, 0x00, 0x1B, 0x49, 0x44, 0x41, 0x54, 0x78,
1270 0xDA, 0x63, 0xF8, 0xCF, 0xC0, 0xC0, 0xF0, 0x1F, 0x08, 0x19, 0x18, 0x99,
1271 0x98, 0x41, 0xD4, 0x7F, 0x06, 0x46, 0xB0, 0x08, 0x03, 0x00, 0x59, 0x20,
1272 0x06, 0x02, 0x5D, 0xD3, 0x95, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45,
1273 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82,
1274 ];
1275
1276 /// Checks a decoded pixmap against every pixel an independent decoder found in it.
1277 fn expect_pixels(buf: &[u8], want: &[[(u8, u8, u8, u8); 4]; 2]) -> Outcome<()> {
1278 let pm = res!(decode(buf));
1279 for y in 0..2 {
1280 for x in 0..4 {
1281 let (r, g, b, a) = want[y][x];
1282 let got = match pm.pixel(x, y) {
1283 Some(c) => c,
1284 None => return Err(err!(
1285 "Pixel {},{} lies outside the decoded pixmap.", x, y; Invalid, Input)),
1286 };
1287 assert_eq!(
1288 got, Rgba::new(r, g, b, a),
1289 "pixel {},{} should be {:?}", x, y, Rgba::new(r, g, b, a),
1290 );
1291 }
1292 }
1293 Ok(())
1294 }
1295
1296 #[test]
1297 fn test_trns_gives_a_palette_image_its_alpha_07() -> Outcome<()> {
1298 res!(expect_pixels(&PAL_TRNS, &[
1299 [(255, 0, 0, 0), (0, 255, 0, 128), (0, 0, 255, 255), (255, 255, 255, 255)],
1300 [(255, 255, 255, 255), (0, 0, 255, 255), (0, 255, 0, 128), (255, 0, 0, 0)],
1301 ]));
1302 Ok(())
1303 }
1304
1305 #[test]
1306 fn test_trns_gives_a_greyscale_image_its_alpha_08() -> Outcome<()> {
1307 res!(expect_pixels(&GREY_TRNS, &[
1308 [(0, 0, 0, 255), (128, 128, 128, 0), (255, 255, 255, 255), (128, 128, 128, 0)],
1309 [(128, 128, 128, 0), (10, 10, 10, 255), (128, 128, 128, 0), (200, 200, 200, 255)],
1310 ]));
1311 Ok(())
1312 }
1313
1314 #[test]
1315 fn test_trns_gives_a_truecolour_image_its_alpha_09() -> Outcome<()> {
1316 // The second pixel of the second row is 255,0,1: one off the transparent colour, and so
1317 // opaque. An implementation that compared loosely would report it transparent.
1318 res!(expect_pixels(&RGB_TRNS, &[
1319 [(255, 0, 0, 0), (0, 255, 0, 255), (255, 0, 0, 0), (1, 2, 3, 255)],
1320 [(255, 0, 0, 0), (255, 0, 1, 255), (0, 0, 0, 255), (255, 0, 0, 0)],
1321 ]));
1322 Ok(())
1323 }
1324
1325 /// Assembles a PNG from the chunks given, in the order given, between a signature and an IEND.
1326 fn assemble(chunks: &[(&[u8; 4], Vec<u8>)]) -> Vec<u8> {
1327 let mut out = Vec::new();
1328 out.extend_from_slice(&SIG);
1329 for (kind, data) in chunks {
1330 write_chunk(&mut out, kind, data);
1331 }
1332 write_chunk(&mut out, b"IEND", &[]);
1333 out
1334 }
1335
1336 /// An image header, eight bits a channel and not interlaced.
1337 fn ihdr(w: u32, h: u32, ct: u8) -> Vec<u8> {
1338 ihdr_at(w, h, 8, ct, 0)
1339 }
1340
1341 /// An image header at a given bit depth and interlace method.
1342 fn ihdr_at(w: u32, h: u32, depth: u8, ct: u8, laced: u8) -> Vec<u8> {
1343 let mut v = Vec::with_capacity(13);
1344 v.extend_from_slice(&w.to_be_bytes());
1345 v.extend_from_slice(&h.to_be_bytes());
1346 v.extend_from_slice(&[depth, ct, 0, 0, laced]);
1347 v
1348 }
1349
1350 /// Deflates raw scanlines, each already carrying its filter byte, into an IDAT payload.
1351 fn idat_of(raw: &[u8]) -> Outcome<Vec<u8>> {
1352 let mut z = ZlibEncoder::new(Vec::new(), Compression::default());
1353 res!(z.write_all(raw));
1354 Ok(res!(z.finish()))
1355 }
1356
1357 #[test]
1358 fn test_trns_is_refused_where_the_specification_forbids_it_10() -> Outcome<()> {
1359 // Colour type 6 already carries an alpha channel, so tRNS has nothing to say and the
1360 // specification forbids it. A file carrying both is malformed, not merely odd.
1361 let idat = res!(idat_of(&[0, 1, 2, 3, 4]));
1362 let buf = assemble(&[
1363 (b"IHDR", ihdr(1, 1, 6)),
1364 (b"tRNS", vec![0x00, 0x80]),
1365 (b"IDAT", idat),
1366 ]);
1367 assert!(decode(&buf).is_err(), "tRNS under colour type 6 must be refused");
1368 Ok(())
1369 }
1370
1371 #[test]
1372 fn test_a_malformed_trns_is_refused_11() -> Outcome<()> {
1373 let plte = vec![255, 0, 0, 0, 255, 0]; // Two entries: red, green.
1374 let pal_idat = res!(idat_of(&[0, 0])); // Filter 0, then palette index 0.
1375 let grey_idat = res!(idat_of(&[0, 128])); // Filter 0, then the luminance 128.
1376
1377 // A sample of 256 cannot apply to an 8-bit pixel, and must be refused, not truncated to 0.
1378 let over = assemble(&[
1379 (b"IHDR", ihdr(1, 1, 0)),
1380 (b"tRNS", vec![0x01, 0x00]),
1381 (b"IDAT", res!(idat_of(&[0, 0]))),
1382 ]);
1383 assert!(decode(&over).is_err(), "a tRNS sample above 255 must be refused at 8 bits");
1384
1385 // A greyscale tRNS is exactly two bytes.
1386 let short = assemble(&[
1387 (b"IHDR", ihdr(1, 1, 0)),
1388 (b"tRNS", vec![0x80]),
1389 (b"IDAT", grey_idat.clone()),
1390 ]);
1391 assert!(decode(&short).is_err(), "a one-byte greyscale tRNS must be refused");
1392
1393 // More alpha bytes than the palette has entries.
1394 let long = assemble(&[
1395 (b"IHDR", ihdr(1, 1, 3)),
1396 (b"PLTE", plte.clone()),
1397 (b"tRNS", vec![0, 0, 0]),
1398 (b"IDAT", pal_idat.clone()),
1399 ]);
1400 assert!(decode(&long).is_err(), "a tRNS longer than the palette must be refused");
1401
1402 // tRNS gives one alpha byte per palette entry, so it cannot precede the palette.
1403 let early = assemble(&[
1404 (b"IHDR", ihdr(1, 1, 3)),
1405 (b"tRNS", vec![0]),
1406 (b"PLTE", plte.clone()),
1407 (b"IDAT", pal_idat.clone()),
1408 ]);
1409 assert!(decode(&early).is_err(), "a tRNS before the PLTE must be refused");
1410
1411 // tRNS carries pixel data, so it cannot arrive after the pixels it applies to.
1412 let late = assemble(&[
1413 (b"IHDR", ihdr(1, 1, 0)),
1414 (b"IDAT", grey_idat),
1415 (b"tRNS", vec![0x00, 0x80]),
1416 ]);
1417 assert!(decode(&late).is_err(), "a tRNS after the IDAT must be refused");
1418
1419 // The same file, with the tRNS where it belongs, decodes: it is the order that is refused
1420 // above and not the chunk.
1421 let good = assemble(&[
1422 (b"IHDR", ihdr(1, 1, 3)),
1423 (b"PLTE", plte),
1424 (b"tRNS", vec![0]),
1425 (b"IDAT", pal_idat),
1426 ]);
1427 let pm = res!(decode(&good));
1428 let got = match pm.pixel(0, 0) {
1429 Some(c) => c,
1430 None => return Err(err!("A 1 by 1 pixmap has a pixel."; Invalid, Input)),
1431 };
1432 assert_eq!(got, Rgba::new(255, 0, 0, 0), "the palette's first entry is transparent");
1433 Ok(())
1434 }
1435
1436 // ┌───────────────────────────────────────────────────────────────────────┐
1437 // │ ADAM7, BIT DEPTH, AND THE SIZES THEY IMPLY │
1438 // └───────────────────────────────────────────────────────────────────────┘
1439
1440 /// A header for the pass and size arithmetic below, which reads nothing else from it.
1441 fn hdr_of(w: usize, h: usize, ct: ColourType, depth: u8, laced: bool) -> Header {
1442 Header { w, h, ct, depth, laced }
1443 }
1444
1445 #[test]
1446 fn test_the_adam7_passes_partition_the_image_12() -> Outcome<()> {
1447 // Whatever the size, the seven passes between them name every pixel exactly once. Summing
1448 // their areas is therefore a check on all seven width and height formulae at once, and it
1449 // catches the off-by-one that a size smaller than a pass's grid invites.
1450 for w in 1..=20usize {
1451 for h in 1..=20usize {
1452 let hdr = hdr_of(w, h, ColourType::Grey, 8, true);
1453 let passes = passes_of(&hdr);
1454 let area: usize = passes.iter().map(|p| p.w * p.h).sum();
1455 assert_eq!(area, w * h, "the passes of a {} by {} image cover it once", w, h);
1456
1457 // And no pass may lay a pixel outside the image.
1458 for p in &passes {
1459 assert!(p.x0 + (p.w - 1) * p.dx < w, "a pass of {} by {} runs off the right", w, h);
1460 assert!(p.y0 + (p.h - 1) * p.dy < h, "a pass of {} by {} runs off the bottom", w, h);
1461 }
1462 }
1463 }
1464
1465 // The passes a small image leaves empty are dropped, not carried as zero-sized ones: an
1466 // empty pass contributes no scanline and no filter byte to the stream at all.
1467 assert_eq!(passes_of(&hdr_of(1, 1, ColourType::Grey, 8, true)).len(), 1);
1468 assert_eq!(passes_of(&hdr_of(3, 2, ColourType::Grey, 8, true)).len(), 4);
1469 assert_eq!(passes_of(&hdr_of(8, 8, ColourType::Grey, 8, true)).len(), 7);
1470 assert_eq!(passes_of(&hdr_of(9, 9, ColourType::Grey, 8, false)).len(), 1);
1471 Ok(())
1472 }
1473
1474 #[test]
1475 fn test_the_expected_size_sums_the_passes_13() -> Outcome<()> {
1476 // A 64 by 64 greyscale image carries 64 filter bytes when it is not interlaced, and 112 --
1477 // the scanlines of all seven passes -- when it is. A ceiling computed from the
1478 // non-interlaced figure would refuse a sound interlaced file as too large.
1479 let plain = hdr_of(64, 64, ColourType::Grey, 8, false);
1480 let laced = hdr_of(64, 64, ColourType::Grey, 8, true);
1481 req!(res!(expected_size(&plain, &passes_of(&plain))), 64 * 65);
1482 req!(res!(expected_size(&laced, &passes_of(&laced))), 4216);
1483
1484 // A sub-byte depth rounds each scanline up to a whole byte, so a 17-pixel row of 1-bit
1485 // greyscale is three bytes and not two and an eighth.
1486 let bits = hdr_of(17, 2, ColourType::Grey, 1, false);
1487 req!(res!(expected_size(&bits, &passes_of(&bits))), 2 * 4);
1488
1489 // Sixteen bits double every scanline.
1490 let wide = hdr_of(5, 3, ColourType::Rgba, 16, false);
1491 req!(res!(expected_size(&wide, &passes_of(&wide))), 3 * (5 * 8 + 1));
1492
1493 // And the filter's stride is the pixel in bytes, rounded up to one.
1494 req!(filter_bpp(ColourType::Grey, 1), 1);
1495 req!(filter_bpp(ColourType::Grey, 16), 2);
1496 req!(filter_bpp(ColourType::Rgb, 16), 6);
1497 req!(filter_bpp(ColourType::Rgba, 16), 8);
1498 req!(filter_bpp(ColourType::Palette, 4), 1);
1499 Ok(())
1500 }
1501
1502 #[test]
1503 fn test_an_interlaced_stream_of_the_wrong_length_is_refused_14() -> Outcome<()> {
1504 // The image data must decompress to the sum over the passes, exactly. A stream cut short,
1505 // a stream run long, and a stream of exactly the size the image would have taken without
1506 // interlacing are all wrong, and the last is the one a decoder that got the arithmetic
1507 // wrong would accept.
1508 let full = 4216usize;
1509 for n in [full - 1, full + 1, 64 * 65] {
1510 let buf = assemble(&[
1511 (b"IHDR", ihdr_at(64, 64, 8, 0, 1)),
1512 (b"IDAT", res!(idat_of(&vec![0u8; n]))),
1513 ]);
1514 assert!(decode(&buf).is_err(), "an interlaced stream of {} bytes must be refused", n);
1515 }
1516
1517 // The right length decodes.
1518 let buf = assemble(&[
1519 (b"IHDR", ihdr_at(64, 64, 8, 0, 1)),
1520 (b"IDAT", res!(idat_of(&vec![0u8; full]))),
1521 ]);
1522 let pm = res!(decode(&buf));
1523 req!(pm.width(), 64usize);
1524 req!(pm.height(), 64usize);
1525 Ok(())
1526 }
1527
1528 #[test]
1529 fn test_a_bomb_cannot_grow_past_the_interlaced_ceiling_15() -> Outcome<()> {
1530 // Half a megabyte of zeroes deflates to a few hundred bytes. The header says the image is
1531 // 64 by 64, so the decoder must stop well short of inflating it, and refuse.
1532 let buf = assemble(&[
1533 (b"IHDR", ihdr_at(64, 64, 8, 0, 1)),
1534 (b"IDAT", res!(idat_of(&vec![0u8; 512 * 1024]))),
1535 ]);
1536 assert!(decode(&buf).is_err(), "a stream far larger than the header allows must be refused");
1537 Ok(())
1538 }
1539
1540 // ┌───────────────────────────────────────────────────────────────────────┐
1541 // │ SUB-BYTE DEPTHS │
1542 // └───────────────────────────────────────────────────────────────────────┘
1543 //
1544 // The two files below are written out byte by byte, and the pixels they are checked against
1545 // come from ImageMagick reading these exact bytes, not from this decoder.
1546 //
1547 // Pillow, which wrote the eight-bit fixtures further up, reads the second of them wrongly: it
1548 // reports every pixel opaque, because it compares the tRNS sample against the widened value
1549 // rather than the raw one. ImageMagick and the specification agree with the reading below.
1550
1551 /// Colour type 0 at 1 bit, 3 by 1. The row's five padding bits are all set, and none of them
1552 /// may reach a pixel.
1553 const GREY1_PAD: [u8; 67] = [
1554 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D,
1555 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01,
1556 0x01, 0x00, 0x00, 0x00, 0x00, 0x33, 0x9B, 0x29, 0x19, 0x00, 0x00, 0x00,
1557 0x0A, 0x49, 0x44, 0x41, 0x54, 0x78, 0x9C, 0x63, 0xD8, 0x0F, 0x00, 0x00,
1558 0xC1, 0x00, 0xC0, 0xD5, 0xE9, 0xCD, 0x5C, 0x00, 0x00, 0x00, 0x00, 0x49,
1559 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82,
1560 ];
1561
1562 /// Colour type 0 at 4 bits, 5 by 2, with a tRNS chunk naming the raw sample 5. Both rows carry
1563 /// a set padding nibble, and the two pixels of sample 5 widen to 85 and are transparent.
1564 const GREY4_TRNS: [u8; 87] = [
1565 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D,
1566 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x02,
1567 0x04, 0x00, 0x00, 0x00, 0x00, 0x70, 0xF1, 0xA4, 0x80, 0x00, 0x00, 0x00,
1568 0x02, 0x74, 0x52, 0x4E, 0x53, 0x00, 0x05, 0x06, 0xF9, 0x39, 0xB7, 0x00,
1569 0x00, 0x00, 0x10, 0x49, 0x44, 0x41, 0x54, 0x78, 0x9C, 0x63, 0x60, 0xFD,
1570 0x11, 0xCF, 0xF0, 0x21, 0xF8, 0x38, 0x00, 0x0C, 0x13, 0x03, 0x67, 0x9B,
1571 0x2A, 0x44, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE,
1572 0x42, 0x60, 0x82,
1573 ];
1574
1575 #[test]
1576 fn test_the_padding_of_a_sub_byte_row_stays_out_of_the_pixels_16() -> Outcome<()> {
1577 let pm = res!(decode(&GREY1_PAD));
1578 req!(pm.width(), 3usize);
1579 req!(pm.height(), 1usize);
1580 let want = [Rgba::WHITE, Rgba::new(0, 0, 0, 255), Rgba::WHITE];
1581 for (x, exp) in want.iter().enumerate() {
1582 let got = match pm.pixel(x, 0) {
1583 Some(c) => c,
1584 None => return Err(err!("Pixel {},0 lies outside the pixmap.", x; Invalid, Input)),
1585 };
1586 assert_eq!(got, *exp, "pixel {},0 of a 1-bit row whose padding is all ones", x);
1587 }
1588 Ok(())
1589 }
1590
1591 #[test]
1592 fn test_trns_compares_the_raw_sample_at_a_sub_byte_depth_17() -> Outcome<()> {
1593 let pm = res!(decode(&GREY4_TRNS));
1594 req!(pm.width(), 5usize);
1595 req!(pm.height(), 2usize);
1596 let want: [[(u8, u8); 5]; 2] = [
1597 // Sample, then the alpha ImageMagick reads. The sample 5 is the transparent one.
1598 [(0, 255), (85, 0), (255, 255), (136, 255), (85, 0)],
1599 [(255, 255), (0, 255), (85, 0), (51, 255), (204, 255)],
1600 ];
1601 for (y, row) in want.iter().enumerate() {
1602 for (x, (g, a)) in row.iter().enumerate() {
1603 let got = match pm.pixel(x, y) {
1604 Some(c) => c,
1605 None => return Err(err!(
1606 "Pixel {},{} lies outside the pixmap.", x, y; Invalid, Input)),
1607 };
1608 assert_eq!(got, Rgba::new(*g, *g, *g, *a), "pixel {},{} of the 4-bit tRNS file", x, y);
1609 }
1610 }
1611 Ok(())
1612 }
1613
1614 #[test]
1615 fn test_a_narrow_sample_widens_to_the_full_range_18() {
1616 // The widest value a depth can hold must become 255, or a 1-bit image comes out black and
1617 // very-nearly-black rather than black and white.
1618 assert_eq!(widen(0, 1), 0);
1619 assert_eq!(widen(1, 1), 255);
1620 assert_eq!((0..4).map(|v| widen(v, 2)).collect::<Vec<_>>(), vec![0, 85, 170, 255]);
1621 assert_eq!(widen(0, 4), 0);
1622 assert_eq!(widen(15, 4), 255);
1623 assert_eq!(widen(200, 8), 200);
1624 // Sixteen bits keep the high byte, so an eight-bit value written twice survives exactly.
1625 for v in 0..=255u16 {
1626 assert_eq!(widen(v * 257, 16), v as u8, "the sample {} repeated", v);
1627 }
1628 assert_eq!(widen(0xFFFE, 16), 255);
1629 }
1630
1631 #[test]
1632 fn test_a_sample_is_read_from_the_right_bits_19() -> Outcome<()> {
1633 // Sub-byte samples are packed most significant first.
1634 let line = [0b1101_0010u8, 0b0011_1000];
1635 req!(res!(sample_at(&line, 0, 1)), 1u16);
1636 req!(res!(sample_at(&line, 1, 1)), 1u16);
1637 req!(res!(sample_at(&line, 2, 1)), 0u16);
1638 req!(res!(sample_at(&line, 8, 1)), 0u16);
1639 req!(res!(sample_at(&line, 0, 2)), 0b11u16);
1640 req!(res!(sample_at(&line, 3, 2)), 0b10u16);
1641 req!(res!(sample_at(&line, 0, 4)), 0b1101u16);
1642 req!(res!(sample_at(&line, 1, 4)), 0b0010u16);
1643 req!(res!(sample_at(&line, 0, 8)), 0b1101_0010u16);
1644 req!(res!(sample_at(&line, 0, 16)), 0xD238u16);
1645 // And a sample past the end of the row is an error rather than a panic or a zero.
1646 assert!(sample_at(&line, 16, 1).is_err());
1647 assert!(sample_at(&line, 2, 8).is_err());
1648 assert!(sample_at(&line, 1, 16).is_err());
1649 Ok(())
1650 }
1651
1652 #[test]
1653 fn test_the_header_refuses_what_it_cannot_read_20() -> Outcome<()> {
1654 // A bit depth outside the five the format defines.
1655 for depth in [0u8, 3, 5, 7, 9, 12, 32] {
1656 let buf = assemble(&[
1657 (b"IHDR", ihdr_at(1, 1, depth, 0, 0)),
1658 (b"IDAT", res!(idat_of(&[0, 0]))),
1659 ]);
1660 assert!(decode(&buf).is_err(), "a bit depth of {} must be refused", depth);
1661 }
1662
1663 // A depth the declared colour type does not allow: truecolour and the two alpha types
1664 // start at eight bits, and a palette index cannot be sixteen.
1665 for (ct, depth) in [(2u8, 4u8), (2, 1), (4, 2), (6, 4), (3, 16)] {
1666 let buf = assemble(&[
1667 (b"IHDR", ihdr_at(1, 1, depth, ct, 0)),
1668 (b"PLTE", vec![1, 2, 3]),
1669 (b"IDAT", res!(idat_of(&[0, 0, 0, 0, 0, 0, 0, 0, 0]))),
1670 ]);
1671 assert!(decode(&buf).is_err(),
1672 "colour type {} at {} bits must be refused", ct, depth);
1673 }
1674
1675 // A compression, filter or interlace method the format does not define. The header is
1676 // built by hand here because these three bytes are the ones `ihdr_at` fixes.
1677 for (at, v) in [(10usize, 1u8), (11, 1), (12, 2), (12, 255)] {
1678 let mut h = ihdr(1, 1, 0);
1679 h[at] = v;
1680 let buf = assemble(&[(b"IHDR", h), (b"IDAT", res!(idat_of(&[0, 0])))]);
1681 assert!(decode(&buf).is_err(),
1682 "byte {} of the header set to {} must be refused", at, v);
1683 }
1684
1685 // The same header, untouched, decodes: it is the byte and not the file that is refused.
1686 let buf = assemble(&[(b"IHDR", ihdr(1, 1, 0)), (b"IDAT", res!(idat_of(&[0, 0])))]);
1687 assert!(decode(&buf).is_ok(), "a sound 1 by 1 greyscale file decodes");
1688 Ok(())
1689 }
1690
1691 #[test]
1692 fn test_a_palette_index_beyond_the_palette_is_refused_at_every_depth_21() -> Outcome<()> {
1693 // A four-bit index of 3 against a palette of two entries names a colour that is not there.
1694 // Widening it into range, or reading past the palette, would paint something the file does
1695 // not hold.
1696 let buf = assemble(&[
1697 (b"IHDR", ihdr_at(2, 1, 4, 3, 0)),
1698 (b"PLTE", vec![255, 0, 0, 0, 255, 0]),
1699 (b"IDAT", res!(idat_of(&[0, 0x03]))),
1700 ]);
1701 assert!(decode(&buf).is_err(), "a palette index of 3 against two entries must be refused");
1702
1703 // The same row with both indices in range decodes.
1704 let buf = assemble(&[
1705 (b"IHDR", ihdr_at(2, 1, 4, 3, 0)),
1706 (b"PLTE", vec![255, 0, 0, 0, 255, 0]),
1707 (b"IDAT", res!(idat_of(&[0, 0x01]))),
1708 ]);
1709 let pm = res!(decode(&buf));
1710 req!(pm.pixel(0, 0), Some(Rgba::opaque(255, 0, 0)));
1711 req!(pm.pixel(1, 0), Some(Rgba::opaque(0, 255, 0)));
1712 Ok(())
1713 }
1714
1715 /// Walks an animation's chunks, giving each one's type and data.
1716 fn chunks(buf: &[u8]) -> Outcome<Vec<(String, Vec<u8>)>> {
1717 let mut out = Vec::new();
1718 let mut i = 8; // Past the signature.
1719 while i + 8 <= buf.len() {
1720 let len = u32::from_be_bytes([buf[i], buf[i + 1], buf[i + 2], buf[i + 3]]) as usize;
1721 let kind = String::from_utf8_lossy(&buf[i + 4..i + 8]).to_string();
1722 let from = i + 8;
1723 if from + len + 4 > buf.len() {
1724 return Err(err!("Chunk {} runs past the end of {} bytes.", kind, buf.len();
1725 Invalid, Input));
1726 }
1727 // Every chunk's CRC is checked here, because a writer that frames its chunks wrongly
1728 // writes a file that only a lenient reader will take.
1729 let crc = u32::from_be_bytes([
1730 buf[from + len],
1731 buf[from + len + 1],
1732 buf[from + len + 2],
1733 buf[from + len + 3],
1734 ]);
1735 req!(crc32(&buf[i + 4..from + len]), crc);
1736 out.push((kind, buf[from..from + len].to_vec()));
1737 i = from + len + 4;
1738 }
1739 Ok(out)
1740 }
1741
1742 #[test]
1743 fn test_an_animation_is_a_png_a_still_reader_can_read_22() -> Outcome<()> {
1744 let a = res!(Pixmap::filled(6, 4, Rgba::opaque(200, 30, 40)));
1745 let b = res!(Pixmap::filled(6, 4, Rgba::opaque(30, 200, 40)));
1746 let mut anim = res!(Animation::new(6, 4));
1747 res!(anim.push(&a, res!(Delay::fps(25))));
1748 res!(anim.push(&b, res!(Delay::fps(25))));
1749 req!(anim.frames(), 2);
1750 let buf = res!(anim.finish());
1751
1752 // The default image is the first frame, so our own still decoder reads it and reports no
1753 // error: that is the whole claim the format makes about backwards compatibility.
1754 let pm = res!(decode(&buf));
1755 req!(pm.width(), 6);
1756 req!(pm.height(), 4);
1757 req!(pm.pixel(0, 0), Some(Rgba::opaque(200, 30, 40)));
1758 req!(pm.pixel(5, 3), Some(Rgba::opaque(200, 30, 40)));
1759 Ok(())
1760 }
1761
1762 #[test]
1763 fn test_the_animation_chunks_are_ordered_and_numbered_23() -> Outcome<()> {
1764 let a = res!(Pixmap::filled(6, 4, Rgba::opaque(200, 30, 40)));
1765 let mut b = a.clone();
1766 res!(b.fill_bounds(Bounds::new(1.0, 1.0, 3.0, 3.0), Rgba::opaque(0, 0, 255), None));
1767 let mut anim = res!(Animation::new(6, 4)).plays(3);
1768 res!(anim.push(&a, res!(Delay::fps(10))));
1769 res!(anim.push(&b, res!(Delay::fps(10))));
1770 res!(anim.push(&a, res!(Delay::fps(10))));
1771 let buf = res!(anim.finish());
1772
1773 let cs = res!(chunks(&buf));
1774 let kinds: Vec<&str> = cs.iter().map(|(k, _)| k.as_str()).collect();
1775 req!(kinds, vec!["IHDR", "acTL", "fcTL", "IDAT", "fcTL", "fdAT", "fcTL", "fdAT", "IEND"]);
1776
1777 // The control chunk counts the frames and carries the play count.
1778 let actl = &cs[1].1;
1779 req!(u32::from_be_bytes([actl[0], actl[1], actl[2], actl[3]]), 3u32);
1780 req!(u32::from_be_bytes([actl[4], actl[5], actl[6], actl[7]]), 3u32);
1781
1782 // Sequence numbers run 0, 1, 2, 3, 4 across the frame control and frame data chunks with no
1783 // gap and no repeat, which is what a reader checks and the easiest thing to get wrong.
1784 let mut seqs = Vec::new();
1785 for (kind, data) in &cs {
1786 if kind == "fcTL" || kind == "fdAT" {
1787 seqs.push(u32::from_be_bytes([data[0], data[1], data[2], data[3]]));
1788 }
1789 }
1790 req!(seqs, vec![0u32, 1, 2, 3, 4]);
1791 Ok(())
1792 }
1793
1794 #[test]
1795 fn test_a_frame_writes_only_the_rectangle_that_changed_24() -> Outcome<()> {
1796 let a = res!(Pixmap::filled(40, 40, Rgba::opaque(255, 255, 255)));
1797 let mut b = a.clone();
1798 // A rectangle from (10, 12) to (14, 15), which is 4 wide and 3 high.
1799 res!(b.fill_bounds(Bounds::new(10.0, 12.0, 14.0, 15.0), Rgba::opaque(0, 0, 0), None));
1800 let mut anim = res!(Animation::new(40, 40));
1801 res!(anim.push(&a, Delay::ms(40)));
1802 res!(anim.push(&b, Delay::ms(40)));
1803 let buf = res!(anim.finish());
1804
1805 let cs = res!(chunks(&buf));
1806 // The second frame control chunk: width, height, x, y at bytes 4 through 19.
1807 let f = &cs[4].1;
1808 req!(cs[4].0, "fcTL".to_string());
1809 req!(u32::from_be_bytes([f[4], f[5], f[6], f[7]]), 4u32);
1810 req!(u32::from_be_bytes([f[8], f[9], f[10], f[11]]), 3u32);
1811 req!(u32::from_be_bytes([f[12], f[13], f[14], f[15]]), 10u32);
1812 req!(u32::from_be_bytes([f[16], f[17], f[18], f[19]]), 12u32);
1813 // The delay, as the rational it was given as.
1814 req!(u16::from_be_bytes([f[20], f[21]]), 40u16);
1815 req!(u16::from_be_bytes([f[22], f[23]]), 1000u16);
1816 Ok(())
1817 }
1818
1819 #[test]
1820 fn test_a_frame_identical_to_the_last_still_carries_its_delay_25() -> Outcome<()> {
1821 let a = res!(Pixmap::filled(8, 8, Rgba::opaque(1, 2, 3)));
1822 let mut anim = res!(Animation::new(8, 8));
1823 res!(anim.push(&a, Delay::ms(100)));
1824 res!(anim.push(&a, Delay::ms(500)));
1825 let buf = res!(anim.finish());
1826 let cs = res!(chunks(&buf));
1827 let f = &cs[4].1;
1828 // A frame that changed nothing is written as the one pixel a frame control chunk must
1829 // name at least: a held picture is a frame, not an absence.
1830 req!(u32::from_be_bytes([f[4], f[5], f[6], f[7]]), 1u32);
1831 req!(u32::from_be_bytes([f[8], f[9], f[10], f[11]]), 1u32);
1832 req!(u16::from_be_bytes([f[20], f[21]]), 500u16);
1833 Ok(())
1834 }
1835
1836 #[test]
1837 fn test_an_animation_refuses_what_it_cannot_write_26() -> Outcome<()> {
1838 let empty = res!(Animation::new(8, 8));
1839 assert!(empty.finish().is_err(), "an animation with no frames must be refused");
1840
1841 let mut anim = res!(Animation::new(8, 8));
1842 let wrong = res!(Pixmap::new(9, 8));
1843 assert!(anim.push(&wrong, Delay::ms(40)).is_err(), "a frame of the wrong size must be refused");
1844
1845 let right = res!(Pixmap::new(8, 8));
1846 assert!(
1847 anim.push(&right, Delay { num: 1, den: 0 }).is_err(),
1848 "a delay with a zero denominator must be refused",
1849 );
1850 assert!(Delay::fps(0).is_err(), "a frame rate of zero must be refused");
1851 Ok(())
1852 }
1853
1854 #[test]
1855 fn test_the_difference_of_two_frames_is_the_tightest_rectangle_27() -> Outcome<()> {
1856 let a = res!(Pixmap::filled(10, 10, Rgba::opaque(0, 0, 0)));
1857 req!(difference(&a, &a), None::<(usize, usize, usize, usize)>);
1858
1859 let mut b = a.clone();
1860 b.set_pixel(3, 7, Rgba::opaque(255, 255, 255));
1861 req!(difference(&a, &b), Some((3, 7, 1, 1)));
1862
1863 let mut c = a.clone();
1864 c.set_pixel(2, 1, Rgba::opaque(255, 0, 0));
1865 c.set_pixel(8, 6, Rgba::opaque(0, 255, 0));
1866 req!(difference(&a, &c), Some((2, 1, 7, 6)));
1867 Ok(())
1868 }
1869}