fn crc32(data: &[u8], mut c: u32) -> u32 {
static TABLE: std::sync::OnceLock<[u32; 256]> = std::sync::OnceLock::new();
let t = TABLE.get_or_init(|| {
let mut t = [0u32; 256];
for (i, e) in t.iter_mut().enumerate() {
let mut v = i as u32;
for _ in 0..8 {
v = if v & 1 != 0 { 0xEDB8_8320 ^ (v >> 1) } else { v >> 1 };
}
*e = v;
}
t
});
c = !c;
for &b in data {
c = t[((c ^ b as u32) & 0xFF) as usize] ^ (c >> 8);
}
!c
}
fn chunk(out: &mut Vec<u8>, kind: &[u8; 4], data: &[u8]) {
out.extend_from_slice(&(data.len() as u32).to_be_bytes());
out.extend_from_slice(kind);
out.extend_from_slice(data);
let c = crc32(data, crc32(kind, 0));
out.extend_from_slice(&c.to_be_bytes());
}
pub fn encode_rgb(width: usize, height: usize, rgb: &[u8]) -> Vec<u8> {
assert_eq!(rgb.len(), width * height * 3, "pixel buffer does not match the size");
let mut raw = Vec::with_capacity(height * (width * 3 + 1));
for row in rgb.chunks_exact(width * 3) {
raw.push(0);
raw.extend_from_slice(row);
}
let mut z = vec![0x78, 0x01];
let mut blocks = raw.chunks(65535).peekable();
while let Some(b) = blocks.next() {
z.push(if blocks.peek().is_none() { 1 } else { 0 });
z.extend_from_slice(&(b.len() as u16).to_le_bytes());
z.extend_from_slice(&(!(b.len() as u16)).to_le_bytes());
z.extend_from_slice(b);
}
let (mut a, mut b) = (1u32, 0u32);
for &x in &raw {
a = (a + x as u32) % 65521;
b = (b + a) % 65521;
}
z.extend_from_slice(&((b << 16) | a).to_be_bytes());
let mut out = vec![0x89, b'P', b'N', b'G', 0x0D, 0x0A, 0x1A, 0x0A];
let mut ihdr = Vec::with_capacity(13);
ihdr.extend_from_slice(&(width as u32).to_be_bytes());
ihdr.extend_from_slice(&(height as u32).to_be_bytes());
ihdr.extend_from_slice(&[8, 2, 0, 0, 0]); chunk(&mut out, b"IHDR", &ihdr);
chunk(&mut out, b"IDAT", &z);
chunk(&mut out, b"IEND", &[]);
out
}
#[cfg(test)]
mod tests {
#[test]
fn crc_matches_the_png_spec_example() {
assert_eq!(super::crc32(b"IEND", 0), 0xAE42_6082);
}
}