/* * a png from raw rgba pixels, for tray icons that come as pixels (IconPixmap) rather * than a name: amane draws images from files. stdlib only, so the deflate stream is * stored, not compressed; tray icons are a few kB either way */ // the image, straight rgba (not premultiplied), rows top to bottom pub fn encode(width: u32, height: u32, rgba: &[u8]) -> Vec { let row = width as usize * 4; // every row starts with its filter byte, 0: none let mut raw = Vec::with_capacity((row + 1) * height as usize); for line in rgba.chunks(row).take(height as usize) { raw.push(0); raw.extend_from_slice(line); } let mut header = Vec::with_capacity(13); header.extend_from_slice(&width.to_be_bytes()); header.extend_from_slice(&height.to_be_bytes()); // 8 bits per channel, colour type 6 (rgba), deflate, adaptive filtering, no interlace header.extend_from_slice(&[8, 6, 0, 0, 0]); let mut png = vec![0x89, b'P', b'N', b'G', b'\r', b'\n', 0x1a, b'\n']; chunk(&mut png, b"IHDR", &header); chunk(&mut png, b"IDAT", &zlib_stored(&raw)); chunk(&mut png, b"IEND", &[]); png } /* * IconPixmap's pixels: argb32 in network byte order, so a, r, g, b per pixel; the * same as rgba for amane */ pub fn argb_to_rgba(argb: &[u8]) -> Vec { argb.chunks_exact(4).flat_map(|pixel| [pixel[1], pixel[2], pixel[3], pixel[0]]).collect() } fn chunk(png: &mut Vec, kind: &[u8; 4], data: &[u8]) { png.extend_from_slice(&(data.len() as u32).to_be_bytes()); png.extend_from_slice(kind); png.extend_from_slice(data); let mut checked = kind.to_vec(); checked.extend_from_slice(data); png.extend_from_slice(&crc32(&checked).to_be_bytes()); } // zlib around deflate's stored blocks, at most 65535 bytes each fn zlib_stored(data: &[u8]) -> Vec { let mut out = vec![0x78, 0x01]; let blocks: Vec<&[u8]> = if data.is_empty() { vec![&[]] } else { data.chunks(65_535).collect() }; for (index, block) in blocks.iter().enumerate() { let last = index + 1 == blocks.len(); let length = block.len() as u16; out.push(u8::from(last)); out.extend_from_slice(&length.to_le_bytes()); out.extend_from_slice(&(!length).to_le_bytes()); out.extend_from_slice(block); } out.extend_from_slice(&adler32(data).to_be_bytes()); out } fn crc32(data: &[u8]) -> u32 { let mut crc = 0xffff_ffff_u32; for byte in data { crc ^= u32::from(*byte); for _ in 0..8 { crc = if crc & 1 == 1 { (crc >> 1) ^ 0xedb8_8320 } else { crc >> 1 }; } } !crc } fn adler32(data: &[u8]) -> u32 { let (mut a, mut b) = (1_u32, 0_u32); for byte in data { a = (a + u32::from(*byte)) % 65_521; b = (b + a) % 65_521; } (b << 16) | a } #[cfg(test)] mod tests { use super::*; #[test] fn checksums_and_layout() { // the well-known values: IEND's crc, and adler32 of "Wikipedia" assert_eq!(crc32(b"IEND"), 0xae42_6082); assert_eq!(adler32(b"Wikipedia"), 0x11e6_0398); let png = encode(2, 1, &[255, 0, 0, 255, 0, 0, 255, 128]); assert_eq!(&png[..8], &[0x89, b'P', b'N', b'G', b'\r', b'\n', 0x1a, b'\n']); assert_eq!(&png[12..16], b"IHDR"); assert_eq!(&png[png.len() - 12..], &[0, 0, 0, 0, b'I', b'E', b'N', b'D', 0xae, 0x42, 0x60, 0x82]); // one row: filter byte, then the pixels, in one final stored block let idat = &png[33 + 8..]; assert_eq!(&idat[..2], &[0x78, 0x01]); assert_eq!(&idat[2..7], &[1, 9, 0, 0xf6, 0xff]); assert_eq!(argb_to_rgba(&[128, 1, 2, 3]), [1, 2, 3, 128]); } }