Skip to main content

proofsheet_core/
png.rs

1//! Just enough PNG to verify what the browser handed back.
2//!
3//! We never decode pixels; we only read the header, because the one thing
4//! worth asserting is that the image is exactly the size that was requested.
5
6use crate::error::{Error, Result};
7
8const MAGIC: [u8; 8] = [0x89, b'P', b'N', b'G', 0x0D, 0x0A, 0x1A, 0x0A];
9
10/// Width and height straight out of the IHDR chunk.
11pub fn dimensions(data: &[u8]) -> Result<(u32, u32)> {
12    if data.len() < 24 {
13        return Err(Error::Shape(format!("png too short: {} bytes", data.len())));
14    }
15    if data[..8] != MAGIC {
16        return Err(Error::Shape("not a png (bad magic)".into()));
17    }
18    if &data[12..16] != b"IHDR" {
19        return Err(Error::Shape("first chunk is not IHDR".into()));
20    }
21    let w = u32::from_be_bytes([data[16], data[17], data[18], data[19]]);
22    let h = u32::from_be_bytes([data[20], data[21], data[22], data[23]]);
23    Ok((w, h))
24}
25
26#[cfg(test)]
27mod tests {
28    use super::*;
29
30    fn header(w: u32, h: u32) -> Vec<u8> {
31        let mut v = Vec::new();
32        v.extend_from_slice(&MAGIC);
33        v.extend_from_slice(&13u32.to_be_bytes());
34        v.extend_from_slice(b"IHDR");
35        v.extend_from_slice(&w.to_be_bytes());
36        v.extend_from_slice(&h.to_be_bytes());
37        v
38    }
39
40    #[test]
41    fn reads_dimensions() {
42        assert_eq!(dimensions(&header(1290, 2796)).unwrap(), (1290, 2796));
43    }
44
45    #[test]
46    fn rejects_non_png() {
47        let mut bad = header(1, 1);
48        bad[1] = b'X';
49        assert!(dimensions(&bad).is_err());
50    }
51
52    #[test]
53    fn rejects_truncated() {
54        assert!(dimensions(&[0u8; 10]).is_err());
55    }
56}