Skip to main content

shuyi_cli/
codec.rs

1//! PNG files as RGBA8 pixels, for the tools that read and write pictures.
2
3use std::io::BufWriter;
4
5/// Encodes RGBA8 pixels as PNG.
6pub fn encode_png(width: u32, height: u32, rgba: &[u8]) -> Result<Vec<u8>, String> {
7    let mut out = Vec::new();
8    {
9        let mut encoder = png::Encoder::new(BufWriter::new(&mut out), width, height);
10        encoder.set_color(png::ColorType::Rgba);
11        encoder.set_depth(png::BitDepth::Eight);
12        encoder.set_compression(png::Compression::High);
13        let mut writer = encoder.write_header().map_err(|e| e.to_string())?;
14        writer.write_image_data(rgba).map_err(|e| e.to_string())?;
15    }
16    Ok(out)
17}
18
19/// Decodes a PNG to (width, height, RGBA8 pixels).
20pub fn decode_png(bytes: &[u8]) -> Result<(u32, u32, Vec<u8>), String> {
21    let mut decoder = png::Decoder::new(std::io::Cursor::new(bytes));
22    // Any PNG comes out as RGBA8.
23    let mut wanted = png::Transformations::EXPAND;
24    wanted.insert(png::Transformations::ALPHA);
25    wanted.insert(png::Transformations::STRIP_16);
26    decoder.set_transformations(wanted);
27    let mut reader = decoder.read_info().map_err(|e| e.to_string())?;
28    let size = reader
29        .output_buffer_size()
30        .ok_or("the picture is too large to decode")?;
31    let mut buf = vec![0; size];
32    let info = reader.next_frame(&mut buf).map_err(|e| e.to_string())?;
33    buf.truncate(info.buffer_size());
34    Ok((info.width, info.height, buf))
35}
36
37#[cfg(test)]
38mod tests {
39    use super::*;
40
41    #[test]
42    fn png_round_trips() {
43        let rgba: Vec<u8> = (0..2 * 3 * 4)
44            .map(|i| u8::try_from(i * 10).unwrap())
45            .collect();
46        let png = encode_png(2, 3, &rgba).unwrap();
47        assert_eq!(decode_png(&png).unwrap(), (2, 3, rgba));
48        assert!(decode_png(b"nope").is_err());
49        assert!(encode_png(2, 3, &[0; 4]).is_err());
50    }
51}