dinamika_cpu/pixmap/
encode.rs1use std::fs::File;
8use std::io::{self, BufWriter, Write};
9use std::path::Path;
10
11use crate::pixmap::Pixmap;
12
13impl Pixmap {
14 pub fn encode_png<W: Write>(&self, writer: W) -> io::Result<()> {
17 let mut encoder = ::png::Encoder::new(writer, self.width(), self.height());
18 encoder.set_color(::png::ColorType::Rgba);
19 encoder.set_depth(::png::BitDepth::Eight);
20
21 let mut writer = encoder.write_header().map_err(encoding_err)?;
22 writer.write_image_data(&self.to_straight_rgba()).map_err(encoding_err)?;
23 Ok(())
24 }
25
26 pub fn save_png(&self, path: impl AsRef<Path>) -> io::Result<()> {
28 let path = path.as_ref();
29 if let Some(parent) = path.parent() {
30 if !parent.as_os_str().is_empty() {
31 std::fs::create_dir_all(parent)?;
32 }
33 }
34 let file = BufWriter::new(File::create(path)?);
35 self.encode_png(file)
36 }
37
38 fn to_straight_rgba(&self) -> Vec<u8> {
41 let data = self.data();
42 let mut out = Vec::with_capacity(data.len());
43 for px in data.chunks_exact(4) {
44 let a = px[3];
45 match a {
46 0 => out.extend_from_slice(&[0, 0, 0, 0]),
47 255 => out.extend_from_slice(px),
48 _ => {
49 let unpremul = |c: u8| {
50 (((c as u32 * 255) + a as u32 / 2) / a as u32).min(255) as u8
53 };
54 out.push(unpremul(px[0]));
55 out.push(unpremul(px[1]));
56 out.push(unpremul(px[2]));
57 out.push(a);
58 }
59 }
60 }
61 out
62 }
63}
64
65fn encoding_err(e: ::png::EncodingError) -> io::Error {
67 match e {
68 ::png::EncodingError::IoError(e) => e,
69 other => io::Error::other(other),
70 }
71}
72
73#[cfg(test)]
74mod tests {
75 use crate::color::Color;
76 use crate::pixmap::Pixmap;
77
78 #[test]
80 fn encodes_png_signature() {
81 let mut pm = Pixmap::new(4, 4).unwrap();
82 pm.fill(Color::from_rgba8(10, 20, 30, 255));
83 let mut buf = Vec::new();
84 pm.encode_png(&mut buf).unwrap();
85 assert_eq!(&buf[0..8], &[0x89, b'P', b'N', b'G', 0x0d, 0x0a, 0x1a, 0x0a]);
86 }
87
88 #[test]
90 fn unpremultiplies_alpha_roundtrip() {
91 let mut pm = Pixmap::new(1, 1).unwrap();
92 pm.fill(Color::from_rgba8(255, 0, 0, 128));
94 let straight = pm.to_straight_rgba();
95 assert_eq!(straight[3], 128); assert!((straight[0] as i32 - 255).abs() <= 2, "r={}", straight[0]);
97 assert_eq!(straight[1], 0);
98 assert_eq!(straight[2], 0);
99 }
100}