Skip to main content

game_toolkit_dosvideo/
png.rs

1//! A tiny dependency-free PNG encoder (stored deflate blocks), for screenshots.
2
3fn crc32(data: &[u8], mut crc: u32) -> u32 {
4    for &b in data {
5        crc ^= b as u32;
6        for _ in 0..8 {
7            crc = if crc & 1 != 0 {
8                0xEDB8_8320 ^ (crc >> 1)
9            } else {
10                crc >> 1
11            };
12        }
13    }
14    crc
15}
16
17fn chunk(out: &mut Vec<u8>, kind: &[u8; 4], data: &[u8]) {
18    out.extend_from_slice(&(data.len() as u32).to_be_bytes());
19    out.extend_from_slice(kind);
20    out.extend_from_slice(data);
21    let c = crc32(data, crc32(kind, 0xFFFF_FFFF)) ^ 0xFFFF_FFFF;
22    out.extend_from_slice(&c.to_be_bytes());
23}
24
25/// Encode RGBA8 pixels as a PNG file.
26pub fn encode_rgba(width: u32, height: u32, rgba: &[u8]) -> Vec<u8> {
27    let mut raw = Vec::with_capacity((width as usize * 4 + 1) * height as usize);
28    for row in rgba.chunks_exact(width as usize * 4).take(height as usize) {
29        raw.push(0);
30        raw.extend_from_slice(row);
31    }
32    let mut z = vec![0x78, 0x01];
33    let mut blocks = raw.chunks(65535).peekable();
34    if blocks.peek().is_none() {
35        z.extend_from_slice(&[1, 0, 0, 0xFF, 0xFF]);
36    }
37    while let Some(b) = blocks.next() {
38        z.push(u8::from(blocks.peek().is_none()));
39        let n = b.len() as u16;
40        z.extend_from_slice(&n.to_le_bytes());
41        z.extend_from_slice(&(!n).to_le_bytes());
42        z.extend_from_slice(b);
43    }
44    let (mut a, mut bb) = (1u32, 0u32);
45    for &x in &raw {
46        a = (a + x as u32) % 65521;
47        bb = (bb + a) % 65521;
48    }
49    z.extend_from_slice(&((bb << 16) | a).to_be_bytes());
50    let mut out = b"\x89PNG\r\n\x1a\n".to_vec();
51    let mut ihdr = Vec::with_capacity(13);
52    ihdr.extend_from_slice(&width.to_be_bytes());
53    ihdr.extend_from_slice(&height.to_be_bytes());
54    ihdr.extend_from_slice(&[8, 6, 0, 0, 0]);
55    chunk(&mut out, b"IHDR", &ihdr);
56    chunk(&mut out, b"IDAT", &z);
57    chunk(&mut out, b"IEND", &[]);
58    out
59}
60
61impl crate::crt::FrameView<'_> {
62    /// The frame as a PNG file.
63    pub fn to_png(&self) -> Vec<u8> {
64        encode_rgba(self.width, self.height, self.rgba)
65    }
66}