Skip to main content

devpage_design/bands/
mod.rs

1//! The PNG, written band by band: each band as many rows as fit in
2//! [`BAND_BYTES`] of pixels, its rows streamed into the `png` crate's encoder
3//! as it is had, so what is held at once is one band — whatever paints or
4//! caches its pieces, and however wide the drawing is.
5
6use std::io::Write;
7
8use crate::display::List;
9use crate::tiles::TILE;
10
11/// The most pixel bytes a band holds: a band is as many rows as fit, at least
12/// one. A drawing up to 16,384 pixels wide fits a whole row of tiles.
13pub const BAND_BYTES: usize = 16 << 20;
14
15/// A piece of a band, `TILE` pixels wide at column `col`: a whole tile, at
16/// tile row `row`, or a strip `rows` tall starting at pixel row `y` — what a
17/// band of a drawing too wide for a row of tiles is made of. Either is its
18/// straight RGBA pixels, `TILE` wide, row after row.
19#[derive(Clone, Copy, Debug, PartialEq, Eq)]
20pub enum Piece {
21    Tile { col: u32, row: u32 },
22    Strip { col: u32, y: u32, rows: u32 },
23}
24
25/// How many rows a band of a drawing `width` pixels wide holds: as many as
26/// fit in [`BAND_BYTES`], its pieces padded to whole tiles, at least one and
27/// at most a tile's.
28pub fn rows_per_band(width: u32) -> u32 {
29    let padded = width.max(1).div_ceil(TILE) as usize * TILE as usize * 4;
30    (BAND_BYTES / padded).clamp(1, TILE as usize) as u32
31}
32
33/// The drawing at 1× as a PNG written to `out`, band by band.
34pub fn png(list: &List, out: impl Write) -> Result<(), png::EncodingError> {
35    let (w, h) = (list.area.width as u32, list.area.height as u32);
36    bands(w, h, out, |piece| {
37        Ok::<_, std::convert::Infallible>(match piece {
38            Piece::Tile { col, row } => crate::cpu::tile(list, 1.0, col, row),
39            Piece::Strip { col, y, rows } => crate::cpu::strip(list, col, y, rows),
40        })
41    })
42    .map_err(|e| match e {
43        Band::Encoding(e) => e,
44    })
45}
46
47/// Why writing a PNG from bands stopped: the encoder or its writer failed,
48/// or a piece couldn't be had.
49#[derive(Debug)]
50pub enum Band<E> {
51    Encoding(png::EncodingError),
52    Tile(E),
53}
54
55impl<E: std::fmt::Display> std::fmt::Display for Band<E> {
56    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57        match self {
58            Band::Encoding(e) => write!(f, "{e}"),
59            Band::Tile(e) => write!(f, "{e}"),
60        }
61    }
62}
63
64/// A `width` × `height` PNG written to `out` band by band, each band's pieces
65/// from `piece` — whole tiles while a row of them fits in [`BAND_BYTES`],
66/// shorter strips otherwise — its rows streamed to the encoder as each band
67/// is had, so what is held at once is one band.
68pub fn bands<P: AsRef<[u8]>, E>(
69    width: u32,
70    height: u32,
71    out: impl Write,
72    mut piece: impl FnMut(Piece) -> Result<P, E>,
73) -> Result<(), Band<E>> {
74    let (w, h) = (width.max(1), height.max(1));
75    let mut encoder = png::Encoder::new(out, w, h);
76    encoder.set_color(png::ColorType::Rgba);
77    encoder.set_depth(png::BitDepth::Eight);
78    let mut header = encoder.write_header().map_err(Band::Encoding)?;
79    let mut writer = header.stream_writer().map_err(Band::Encoding)?;
80    let cols = w.div_ceil(TILE);
81    let per = rows_per_band(w);
82    let mut y = 0;
83    while y < h {
84        let rows = per.min(h - y);
85        let band = (0..cols)
86            .map(|col| {
87                piece(if per == TILE {
88                    Piece::Tile { col, row: y / TILE }
89                } else {
90                    Piece::Strip { col, y, rows }
91                })
92            })
93            .collect::<Result<Vec<P>, E>>()
94            .map_err(Band::Tile)?;
95        for r in 0..rows {
96            for (col, p) in band.iter().enumerate() {
97                let across = (w - col as u32 * TILE).min(TILE) as usize;
98                let start = r as usize * TILE as usize * 4;
99                writer
100                    .write_all(&p.as_ref()[start..start + across * 4])
101                    .map_err(|e| Band::Encoding(e.into()))?;
102            }
103        }
104        y += rows;
105    }
106    writer.finish().map_err(Band::Encoding)
107}
108
109#[cfg(test)]
110mod tests;