use std::io;
use std::iter::repeat;
use png::{BitDepth, ColorType, HasParameters};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Error {
PngEncoding,
Io,
PaletteTooBig,
PaletteTooSmall,
}
impl From<png::EncodingError> for Error {
fn from(err: png::EncodingError) -> Error {
use png::EncodingError;
match err {
EncodingError::IoError(_) => Error::Io,
EncodingError::Format(_) => Error::PngEncoding,
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum Color {
Rgb(u8, u8, u8),
Rgba(u8, u8, u8, u8),
}
pub const WHITE: Color = Color::Rgb(255, 255, 255);
pub const BLACK: Color = Color::Rgb(0, 0, 0);
pub struct Image<'a> {
pub palette: &'a [Color],
pub pixels: &'a [u8],
pub width: usize,
pub scale: usize,
}
impl<'a> Image<'a> {
pub fn render<W: io::Write>(&self, writer: W) -> Result<(), Error> {
if self.palette.len() > 256 {
return Err(Error::PaletteTooBig);
}
if self.palette.len() < 2 {
return Err(Error::PaletteTooSmall);
}
let bit_depth = self.bit_depth();
let depth = bit_depth as usize;
let pixels_in_byte = 8 / depth;
let (img_width, img_height) = self.dimensions();
let bytes_per_line = ceil_div(img_width, pixels_in_byte);
let mut data = vec![0; bytes_per_line * img_height];
let chunk_size = bytes_per_line * self.scale;
for (chunk, pixels) in data.chunks_mut(chunk_size).zip(self.pixels.chunks(self.width)) {
let (first_line, chunk) = chunk.split_at_mut(bytes_per_line);
let mut pixels = pixels.iter().flat_map(|pixel| repeat(*pixel).take(self.scale));
for byte in first_line.iter_mut() {
for (idx, pixel) in (&mut pixels).take(pixels_in_byte).enumerate() {
*byte |= pixel << (8 - depth) - (idx * depth);
}
}
for row in chunk.chunks_mut(bytes_per_line) {
row.copy_from_slice(first_line);
}
}
let mut encoder = png::Encoder::new(writer, img_width as u32, img_height as u32);
encoder.set(ColorType::Indexed).set(bit_depth);
let mut writer = encoder.write_header()?;
writer.write_chunk(png::chunk::PLTE, &self.palette_data())?;
if let Some(transparency) = self.transparency() {
writer.write_chunk(png::chunk::tRNS, &transparency)?;
}
writer.write_image_data(&data)?;
Ok(())
}
fn dimensions(&self) -> (usize, usize) {
let width = self.width * self.scale;
let height = (self.pixels.len() / self.width) * self.scale;
(width, height)
}
fn bit_depth(&self) -> BitDepth {
match self.palette.len() as u8 {
0...2 => BitDepth::One,
3...4 => BitDepth::Two,
5...16 => BitDepth::Four,
_ => BitDepth::Eight,
}
}
fn palette_data(&self) -> Vec<u8> {
let mut data = Vec::with_capacity(self.palette.len() * 3);
for color in self.palette.iter().cloned() {
match color {
Color::Rgb(r, g, b) | Color::Rgba(r, g, b, _) => data.extend_from_slice(&[r,g,b])
}
}
data
}
fn transparency(&self) -> Option<Vec<u8>> {
let mut data = None;
let len = self.palette.len();
for (idx, color) in self.palette.iter().enumerate() {
let alpha = match color {
Color::Rgb(_, _, _) => continue,
Color::Rgba(_, _, _, alpha) => *alpha,
};
let mut buf = data.take().unwrap_or_else(|| vec![255; len]);
buf[idx] = alpha;
data = Some(buf);
}
data
}
}
fn ceil_div(a: usize, b: usize) -> usize {
a / b + (a % b != 0) as usize
}