use std::fs::File;
use std::io::{self, BufWriter, Write};
use std::path::Path;
use crate::pixmap::Pixmap;
impl Pixmap {
pub fn encode_png<W: Write>(&self, writer: W) -> io::Result<()> {
let mut encoder = ::png::Encoder::new(writer, self.width(), self.height());
encoder.set_color(::png::ColorType::Rgba);
encoder.set_depth(::png::BitDepth::Eight);
let mut writer = encoder.write_header().map_err(encoding_err)?;
writer.write_image_data(&self.to_straight_rgba()).map_err(encoding_err)?;
Ok(())
}
pub fn save_png(&self, path: impl AsRef<Path>) -> io::Result<()> {
let path = path.as_ref();
if let Some(parent) = path.parent() {
if !parent.as_os_str().is_empty() {
std::fs::create_dir_all(parent)?;
}
}
let file = BufWriter::new(File::create(path)?);
self.encode_png(file)
}
fn to_straight_rgba(&self) -> Vec<u8> {
let data = self.data();
let mut out = Vec::with_capacity(data.len());
for px in data.chunks_exact(4) {
let a = px[3];
match a {
0 => out.extend_from_slice(&[0, 0, 0, 0]),
255 => out.extend_from_slice(px),
_ => {
let unpremul = |c: u8| {
(((c as u32 * 255) + a as u32 / 2) / a as u32).min(255) as u8
};
out.push(unpremul(px[0]));
out.push(unpremul(px[1]));
out.push(unpremul(px[2]));
out.push(a);
}
}
}
out
}
}
fn encoding_err(e: ::png::EncodingError) -> io::Error {
match e {
::png::EncodingError::IoError(e) => e,
other => io::Error::other(other),
}
}
#[cfg(test)]
mod tests {
use crate::color::Color;
use crate::pixmap::Pixmap;
#[test]
fn encodes_png_signature() {
let mut pm = Pixmap::new(4, 4).unwrap();
pm.fill(Color::from_rgba8(10, 20, 30, 255));
let mut buf = Vec::new();
pm.encode_png(&mut buf).unwrap();
assert_eq!(&buf[0..8], &[0x89, b'P', b'N', b'G', 0x0d, 0x0a, 0x1a, 0x0a]);
}
#[test]
fn unpremultiplies_alpha_roundtrip() {
let mut pm = Pixmap::new(1, 1).unwrap();
pm.fill(Color::from_rgba8(255, 0, 0, 128));
let straight = pm.to_straight_rgba();
assert_eq!(straight[3], 128); assert!((straight[0] as i32 - 255).abs() <= 2, "r={}", straight[0]);
assert_eq!(straight[1], 0);
assert_eq!(straight[2], 0);
}
}