Skip to main content

alpha2_geometry/raster/
dense.rs

1//! The reference rasterizer: every pixel of the clip accumulated and summed.
2//! [`super::rasterize`] must give its bytes; tests and the fuzz target hold
3//! it to that.
4
5use super::accumulate::{accumulate_line, clip_line};
6use super::{TILE, TILE_BYTES, TileCoverage, TileState, coverage_byte, flatten, tile_aligned};
7use crate::IRect;
8use alpha2_model::FillRule;
9use kurbo::{BezPath, Point};
10
11/// The coverage of `path` under `rule` in `clip` grown to whole tiles, one
12/// byte per pixel row by row, and its width and height.
13#[must_use]
14pub fn coverage_bytes(path: &BezPath, rule: FillRule, clip: IRect) -> (Vec<u8>, usize, usize) {
15    let clip = tile_aligned(clip);
16    let (w, h) = (clip.width() as usize, clip.height() as usize);
17    if w == 0 || h == 0 {
18        return (Vec::new(), w, h);
19    }
20    let mut lines = Vec::new();
21    flatten::flatten(
22        path,
23        Point::new(f64::from(clip.x0), f64::from(clip.y0)),
24        &mut lines,
25    );
26    let stride = w + 2;
27    let mut acc = vec![0.0f32; stride * h];
28    #[allow(clippy::cast_precision_loss)]
29    let (wf, hf, wf32) = (w as f64, h as f64, w as f32);
30    for line in lines {
31        clip_line(line, wf, hf, |p0, p1| {
32            accumulate_line(&mut acc, stride, 0, (0, h), wf32, p0, p1, |_, _, _| {});
33        });
34    }
35    let mut data = vec![0u8; w * h];
36    for y in 0..h {
37        let mut winding = 0.0f32;
38        let row = &acc[y * stride..y * stride + w];
39        let out = &mut data[y * w..y * w + w];
40        for (value, byte) in row.iter().zip(out.iter_mut()) {
41            winding += *value;
42            *byte = coverage_byte(rule, winding);
43        }
44    }
45    (data, w, h)
46}
47
48/// [`coverage_bytes`] cut into tiles as [`super::rasterize`] cuts them.
49#[must_use]
50pub fn rasterize_dense(path: &BezPath, rule: FillRule, clip: IRect) -> TileCoverage {
51    let aligned = tile_aligned(clip);
52    let (data, w, h) = coverage_bytes(path, rule, clip);
53    let tile = i32::try_from(TILE).unwrap_or(16);
54    let (tiles_x, tiles_y) = (w / TILE, h / TILE);
55    let mut out = TileCoverage {
56        origin: (aligned.x0.div_euclid(tile), aligned.y0.div_euclid(tile)),
57        size: (
58            u32::try_from(tiles_x).unwrap_or(0),
59            u32::try_from(tiles_y).unwrap_or(0),
60        ),
61        tiles: Vec::with_capacity(tiles_x * tiles_y),
62        partial: Vec::new(),
63    };
64    for ty in 0..tiles_y {
65        for tx in 0..tiles_x {
66            let mut bytes = [0u8; TILE_BYTES];
67            for r in 0..TILE {
68                let start = (ty * TILE + r) * w + tx * TILE;
69                bytes[r * TILE..r * TILE + TILE].copy_from_slice(&data[start..start + TILE]);
70            }
71            let state = if bytes.iter().all(|byte| *byte == 255) {
72                TileState::Full
73            } else if bytes.iter().all(|byte| *byte == 0) {
74                TileState::Empty
75            } else {
76                out.partial.push(bytes);
77                TileState::Partial(u32::try_from(out.partial.len() - 1).unwrap_or(u32::MAX))
78            };
79            out.tiles.push(state);
80        }
81    }
82    out
83}