const SIGNATURE: [u8; 8] = [0x89, b'P', b'N', b'G', 0x0D, 0x0A, 0x1A, 0x0A];
const MAX_STORED_BLOCK: usize = 65_535;
pub(crate) fn encode_grayscale(width: usize, height: usize, pixels: &[u8]) -> Option<Vec<u8>> {
if width == 0 || height == 0 || pixels.len() != width.checked_mul(height)? {
return None;
}
let w32 = u32::try_from(width).ok()?;
let h32 = u32::try_from(height).ok()?;
let mut raw = Vec::with_capacity(height * (width + 1));
for row in pixels.chunks_exact(width) {
raw.push(0u8);
raw.extend_from_slice(row);
}
let mut out = Vec::new();
out.extend_from_slice(&SIGNATURE);
let mut ihdr = Vec::with_capacity(13);
ihdr.extend_from_slice(&w32.to_be_bytes());
ihdr.extend_from_slice(&h32.to_be_bytes());
ihdr.push(8); ihdr.push(0); ihdr.push(0); ihdr.push(0); ihdr.push(0); write_chunk(&mut out, b"IHDR", &ihdr);
write_chunk(&mut out, b"IDAT", &zlib_stored(&raw));
write_chunk(&mut out, b"IEND", &[]);
Some(out)
}
fn write_chunk(out: &mut Vec<u8>, kind: &[u8; 4], data: &[u8]) {
let len = u32::try_from(data.len()).unwrap_or(u32::MAX);
out.extend_from_slice(&len.to_be_bytes());
out.extend_from_slice(kind);
out.extend_from_slice(data);
let mut crc_input = Vec::with_capacity(4 + data.len());
crc_input.extend_from_slice(kind);
crc_input.extend_from_slice(data);
out.extend_from_slice(&crc32(&crc_input).to_be_bytes());
}
fn zlib_stored(data: &[u8]) -> Vec<u8> {
let mut out = Vec::with_capacity(data.len() + 64);
out.push(0x78);
out.push(0x01);
if data.is_empty() {
out.push(0x01); out.extend_from_slice(&0u16.to_le_bytes());
out.extend_from_slice(&(!0u16).to_le_bytes());
} else {
let mut chunks = data.chunks(MAX_STORED_BLOCK).peekable();
while let Some(chunk) = chunks.next() {
let final_block = u8::from(chunks.peek().is_none());
out.push(final_block); let len = u16::try_from(chunk.len()).unwrap_or(u16::MAX);
out.extend_from_slice(&len.to_le_bytes());
out.extend_from_slice(&(!len).to_le_bytes());
out.extend_from_slice(chunk);
}
}
out.extend_from_slice(&adler32(data).to_be_bytes());
out
}
fn crc32(data: &[u8]) -> u32 {
let mut crc = 0xFFFF_FFFFu32;
for &byte in data {
crc ^= u32::from(byte);
for _ in 0..8 {
let mask = 0u32.wrapping_sub(crc & 1);
crc = (crc >> 1) ^ (0xEDB8_8320 & mask);
}
}
!crc
}
fn adler32(data: &[u8]) -> u32 {
let mut a = 1u32;
let mut b = 0u32;
for &byte in data {
a = (a + u32::from(byte)) % 65_521;
b = (b + a) % 65_521;
}
(b << 16) | a
}
#[cfg(test)]
#[path = "png_encode_tests.rs"]
mod tests;