use std::io::Write;
use crate::display::List;
use crate::tiles::TILE;
pub const BAND_BYTES: usize = 16 << 20;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Piece {
Tile { col: u32, row: u32 },
Strip { col: u32, y: u32, rows: u32 },
}
pub fn rows_per_band(width: u32) -> u32 {
let padded = width.max(1).div_ceil(TILE) as usize * TILE as usize * 4;
(BAND_BYTES / padded).clamp(1, TILE as usize) as u32
}
pub fn png(list: &List, out: impl Write) -> Result<(), png::EncodingError> {
let (w, h) = (list.area.width as u32, list.area.height as u32);
bands(w, h, out, |piece| {
Ok::<_, std::convert::Infallible>(match piece {
Piece::Tile { col, row } => crate::cpu::tile(list, 1.0, col, row),
Piece::Strip { col, y, rows } => crate::cpu::strip(list, col, y, rows),
})
})
.map_err(|e| match e {
Band::Encoding(e) => e,
})
}
#[derive(Debug)]
pub enum Band<E> {
Encoding(png::EncodingError),
Tile(E),
}
impl<E: std::fmt::Display> std::fmt::Display for Band<E> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Band::Encoding(e) => write!(f, "{e}"),
Band::Tile(e) => write!(f, "{e}"),
}
}
}
pub fn bands<P: AsRef<[u8]>, E>(
width: u32,
height: u32,
out: impl Write,
mut piece: impl FnMut(Piece) -> Result<P, E>,
) -> Result<(), Band<E>> {
let (w, h) = (width.max(1), height.max(1));
let mut encoder = png::Encoder::new(out, w, h);
encoder.set_color(png::ColorType::Rgba);
encoder.set_depth(png::BitDepth::Eight);
let mut header = encoder.write_header().map_err(Band::Encoding)?;
let mut writer = header.stream_writer().map_err(Band::Encoding)?;
let cols = w.div_ceil(TILE);
let per = rows_per_band(w);
let mut y = 0;
while y < h {
let rows = per.min(h - y);
let band = (0..cols)
.map(|col| {
piece(if per == TILE {
Piece::Tile { col, row: y / TILE }
} else {
Piece::Strip { col, y, rows }
})
})
.collect::<Result<Vec<P>, E>>()
.map_err(Band::Tile)?;
for r in 0..rows {
for (col, p) in band.iter().enumerate() {
let across = (w - col as u32 * TILE).min(TILE) as usize;
let start = r as usize * TILE as usize * 4;
writer
.write_all(&p.as_ref()[start..start + across * 4])
.map_err(|e| Band::Encoding(e.into()))?;
}
}
y += rows;
}
writer.finish().map_err(Band::Encoding)
}
#[cfg(test)]
mod tests;