ferravex_graphics/
image.rs

1use crate::Framebuffer;
2impl Framebuffer {
3    /// Binary RGB PPM, portable and intentionally uncompressed.
4    pub fn to_ppm(&self) -> Vec<u8> {
5        let mut output = format!("P6\n{} {}\n255\n", self.width, self.height).into_bytes();
6        output.reserve(self.pixels().len() * 3);
7        for pixel in self.pixels() {
8            output.extend_from_slice(&pixel[..3]);
9        }
10        output
11    }
12    /// RGBA PNG with original CRC/Adler and uncompressed DEFLATE blocks.
13    /// This trades file size for a small dependency-free, browser-viewable export.
14    pub fn to_png(&self) -> Vec<u8> {
15        let mut filtered = Vec::with_capacity(self.pixels().len() * 4 + self.height as usize);
16        for row in self.pixels().chunks_exact(self.width as usize) {
17            filtered.push(0);
18            for pixel in row {
19                filtered.extend_from_slice(pixel);
20            }
21        }
22        let mut zlib = vec![0x78, 0x01];
23        let block_count = filtered.len().div_ceil(65535);
24        for (index, block) in filtered.chunks(65535).enumerate() {
25            zlib.push(u8::from(index + 1 == block_count));
26            let length = block.len() as u16;
27            zlib.extend_from_slice(&length.to_le_bytes());
28            zlib.extend_from_slice(&(!length).to_le_bytes());
29            zlib.extend_from_slice(block);
30        }
31        zlib.extend_from_slice(&adler32(&filtered).to_be_bytes());
32        let mut output = b"\x89PNG\r\n\x1a\n".to_vec();
33        let mut header = Vec::with_capacity(13);
34        header.extend_from_slice(&self.width.to_be_bytes());
35        header.extend_from_slice(&self.height.to_be_bytes());
36        header.extend([8, 6, 0, 0, 0]);
37        chunk(&mut output, b"IHDR", &header);
38        chunk(&mut output, b"IDAT", &zlib);
39        chunk(&mut output, b"IEND", &[]);
40        output
41    }
42    #[cfg(not(target_arch = "wasm32"))]
43    pub fn save_png(&self, path: impl AsRef<std::path::Path>) -> std::io::Result<()> {
44        std::fs::write(path, self.to_png())
45    }
46    #[cfg(not(target_arch = "wasm32"))]
47    pub fn save_ppm(&self, path: impl AsRef<std::path::Path>) -> std::io::Result<()> {
48        std::fs::write(path, self.to_ppm())
49    }
50}
51fn adler32(bytes: &[u8]) -> u32 {
52    let mut a = 1_u32;
53    let mut b = 0_u32;
54    for byte in bytes {
55        a = (a + u32::from(*byte)) % 65521;
56        b = (b + a) % 65521;
57    }
58    (b << 16) | a
59}
60fn crc32(bytes: impl Iterator<Item = u8>) -> u32 {
61    let mut crc = !0_u32;
62    for byte in bytes {
63        crc ^= u32::from(byte);
64        for _ in 0..8 {
65            crc = (crc >> 1) ^ if crc & 1 != 0 { 0xedb88320 } else { 0 };
66        }
67    }
68    !crc
69}
70fn chunk(output: &mut Vec<u8>, kind: &[u8; 4], bytes: &[u8]) {
71    output.extend_from_slice(&(bytes.len() as u32).to_be_bytes());
72    output.extend_from_slice(kind);
73    output.extend_from_slice(bytes);
74    output.extend_from_slice(&crc32(kind.iter().chain(bytes).copied()).to_be_bytes());
75}
76#[cfg(test)]
77mod tests {
78    use super::*;
79    #[test]
80    fn checksum_known_vectors() {
81        assert_eq!(crc32(b"123456789".iter().copied()), 0xcbf43926);
82        assert_eq!(adler32(b"Wikipedia"), 0x11e60398);
83    }
84}