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 | |
| 44 | use crate::{ |
| 45 | colour::Rgba, |
| 46 | pixmap::{ |
| 47 | Pixmap, |
| 48 | MAX_PIXELS, |
| 49 | }, |
| 50 | }; |
| 51 | |
| 52 | use oxedyne_fe2o3_core::prelude::*; |
| 53 | |
| 54 | use std::io::{ |
| 55 | Read, |
| 56 | Write, |
| 57 | }; |
| 58 | |
| 59 | use flate2::{ |
| 60 | read::ZlibDecoder, |
| 61 | write::ZlibEncoder, |
| 62 | Compression, |
| 63 | }; |
| 64 | |
| 65 | // the eight bytes that begin every PNG |
| 66 | const 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)] |
| 70 | enum 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 | |
| 78 | impl 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)] |
| 125 | enum 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)] |
| 133 | struct 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)] |
| 146 | struct 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. |
| 156 | const 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. |
| 171 | fn 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. |
| 196 | pub 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. |
| 238 | pub 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. |
| 268 | fn 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. |
| 291 | fn 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. |
| 305 | fn 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. |
| 341 | fn 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. |
| 362 | pub 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)] |
| 371 | pub struct Delay { |
| 372 | pub num: u16, // numerator of the delay in seconds |
| 373 | pub den: u16, // and its denominator |
| 374 | } |
| 375 | |
| 376 | impl 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. |
| 420 | pub 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 | |
| 430 | impl 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. |
| 560 | fn 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. |
| 605 | fn 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. |
| 623 | fn 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. |
| 638 | fn 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. |
| 649 | fn 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. |
| 672 | pub 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. |
| 822 | fn 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. |
| 886 | fn 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. |
| 899 | fn 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. |
| 951 | fn 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. |
| 968 | fn 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. |
| 1011 | fn 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. |
| 1022 | fn 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. |
| 1057 | fn 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)] |
| 1123 | mod 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 | } |