dinamika_cpu/pixmap/
decode.rs1use std::fs::File;
8use std::io::{self, BufReader, Read};
9use std::path::Path;
10
11use ::png::ColorType;
12
13use crate::color::ColorU8;
14use crate::pixmap::Pixmap;
15
16impl Pixmap {
17 pub fn decode_png<R: Read>(reader: R) -> io::Result<Pixmap> {
20 let mut decoder = ::png::Decoder::new(reader);
21 decoder.set_transformations(
24 ::png::Transformations::EXPAND | ::png::Transformations::STRIP_16,
25 );
26
27 let mut reader = decoder.read_info().map_err(decoding_err)?;
28 let mut buf = vec![0; reader.output_buffer_size()];
29 let info = reader.next_frame(&mut buf).map_err(decoding_err)?;
30
31 let mut pixmap = Pixmap::new(info.width, info.height)
32 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "zero-size PNG"))?;
33 let src = &buf[..info.buffer_size()];
34 let dst = pixmap.data_mut();
35 let n = info.width as usize * info.height as usize;
36
37 let put = |dst: &mut [u8], di: usize, r: u8, g: u8, b: u8, a: u8| {
39 let p = ColorU8::from_rgba(r, g, b, a).premultiply();
40 dst[di] = p.red();
41 dst[di + 1] = p.green();
42 dst[di + 2] = p.blue();
43 dst[di + 3] = p.alpha();
44 };
45
46 match info.color_type {
47 ColorType::Rgba => {
48 for i in 0..n {
49 let s = i * 4;
50 put(dst, i * 4, src[s], src[s + 1], src[s + 2], src[s + 3]);
51 }
52 }
53 ColorType::Rgb => {
54 for i in 0..n {
55 let s = i * 3;
56 put(dst, i * 4, src[s], src[s + 1], src[s + 2], 255);
57 }
58 }
59 ColorType::GrayscaleAlpha => {
60 for i in 0..n {
61 let s = i * 2;
62 let v = src[s];
63 put(dst, i * 4, v, v, v, src[s + 1]);
64 }
65 }
66 ColorType::Grayscale => {
67 for (i, &v) in src.iter().take(n).enumerate() {
68 put(dst, i * 4, v, v, v, 255);
69 }
70 }
71 ColorType::Indexed => {
73 return Err(io::Error::new(
74 io::ErrorKind::InvalidData,
75 "indexed PNG was not expanded",
76 ));
77 }
78 }
79
80 Ok(pixmap)
81 }
82
83 pub fn from_png_file(path: impl AsRef<Path>) -> io::Result<Pixmap> {
85 Pixmap::decode_png(BufReader::new(File::open(path)?))
86 }
87}
88
89fn decoding_err(e: ::png::DecodingError) -> io::Error {
91 match e {
92 ::png::DecodingError::IoError(e) => e,
93 other => io::Error::other(other),
94 }
95}
96
97#[cfg(test)]
98mod tests {
99 use crate::color::Color;
100 use crate::pixmap::Pixmap;
101
102 #[test]
104 fn png_round_trip() {
105 let mut pm = Pixmap::new(3, 2).unwrap();
106 pm.fill(Color::from_rgba8(200, 100, 50, 255));
107 let mut buf = Vec::new();
108 pm.encode_png(&mut buf).unwrap();
109
110 let decoded = Pixmap::decode_png(&buf[..]).unwrap();
111 assert_eq!(decoded.width(), 3);
112 assert_eq!(decoded.height(), 2);
113 let p = decoded.pixel(1, 1).unwrap();
114 assert_eq!((p.red(), p.green(), p.blue(), p.alpha()), (200, 100, 50, 255));
115 }
116
117 #[test]
119 fn png_round_trip_semitransparent() {
120 let mut pm = Pixmap::new(1, 1).unwrap();
121 pm.fill(Color::from_rgba8(255, 0, 0, 128));
122 let mut buf = Vec::new();
123 pm.encode_png(&mut buf).unwrap();
124
125 let decoded = Pixmap::decode_png(&buf[..]).unwrap();
126 let p = decoded.pixel(0, 0).unwrap();
127 assert_eq!(p.alpha(), 128);
128 assert!((p.red() as i32 - 128).abs() <= 2, "r={}", p.red());
130 }
131}