use std::fs::File;
use std::io::{self, BufReader, Read};
use std::path::Path;
use ::png::ColorType;
use crate::color::ColorU8;
use crate::pixmap::Pixmap;
impl Pixmap {
pub fn decode_png<R: Read>(reader: R) -> io::Result<Pixmap> {
let mut decoder = ::png::Decoder::new(reader);
decoder.set_transformations(
::png::Transformations::EXPAND | ::png::Transformations::STRIP_16,
);
let mut reader = decoder.read_info().map_err(decoding_err)?;
let mut buf = vec![0; reader.output_buffer_size()];
let info = reader.next_frame(&mut buf).map_err(decoding_err)?;
let mut pixmap = Pixmap::new(info.width, info.height)
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "zero-size PNG"))?;
let src = &buf[..info.buffer_size()];
let dst = pixmap.data_mut();
let n = info.width as usize * info.height as usize;
let put = |dst: &mut [u8], di: usize, r: u8, g: u8, b: u8, a: u8| {
let p = ColorU8::from_rgba(r, g, b, a).premultiply();
dst[di] = p.red();
dst[di + 1] = p.green();
dst[di + 2] = p.blue();
dst[di + 3] = p.alpha();
};
match info.color_type {
ColorType::Rgba => {
for i in 0..n {
let s = i * 4;
put(dst, i * 4, src[s], src[s + 1], src[s + 2], src[s + 3]);
}
}
ColorType::Rgb => {
for i in 0..n {
let s = i * 3;
put(dst, i * 4, src[s], src[s + 1], src[s + 2], 255);
}
}
ColorType::GrayscaleAlpha => {
for i in 0..n {
let s = i * 2;
let v = src[s];
put(dst, i * 4, v, v, v, src[s + 1]);
}
}
ColorType::Grayscale => {
for (i, &v) in src.iter().take(n).enumerate() {
put(dst, i * 4, v, v, v, 255);
}
}
ColorType::Indexed => {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"indexed PNG was not expanded",
));
}
}
Ok(pixmap)
}
pub fn from_png_file(path: impl AsRef<Path>) -> io::Result<Pixmap> {
Pixmap::decode_png(BufReader::new(File::open(path)?))
}
}
fn decoding_err(e: ::png::DecodingError) -> io::Error {
match e {
::png::DecodingError::IoError(e) => e,
other => io::Error::other(other),
}
}
#[cfg(test)]
mod tests {
use crate::color::Color;
use crate::pixmap::Pixmap;
#[test]
fn png_round_trip() {
let mut pm = Pixmap::new(3, 2).unwrap();
pm.fill(Color::from_rgba8(200, 100, 50, 255));
let mut buf = Vec::new();
pm.encode_png(&mut buf).unwrap();
let decoded = Pixmap::decode_png(&buf[..]).unwrap();
assert_eq!(decoded.width(), 3);
assert_eq!(decoded.height(), 2);
let p = decoded.pixel(1, 1).unwrap();
assert_eq!((p.red(), p.green(), p.blue(), p.alpha()), (200, 100, 50, 255));
}
#[test]
fn png_round_trip_semitransparent() {
let mut pm = Pixmap::new(1, 1).unwrap();
pm.fill(Color::from_rgba8(255, 0, 0, 128));
let mut buf = Vec::new();
pm.encode_png(&mut buf).unwrap();
let decoded = Pixmap::decode_png(&buf[..]).unwrap();
let p = decoded.pixel(0, 0).unwrap();
assert_eq!(p.alpha(), 128);
assert!((p.red() as i32 - 128).abs() <= 2, "r={}", p.red());
}
}