Skip to main content

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