use core::ops::Range;
use store::CellStore;
mod store;
pub(crate) const SUBPIXEL_SCALE: i32 = 256;
pub(crate) const SUBPIXEL_SHIFT: u32 = 8;
pub(crate) const SUBPIXEL_MASK: i32 = SUBPIXEL_SCALE - 1;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub(crate) struct Cell {
pub x: i32,
pub y: i32,
pub cover: i32,
pub area: i32,
}
impl Cell {
const fn at(x: i32, y: i32) -> Self {
Self {
x,
y,
cover: 0,
area: 0,
}
}
const fn is_empty(self) -> bool {
self.cover == 0 && self.area == 0
}
}
#[derive(Debug, Default)]
pub struct Rasterizer {
store: CellStore,
current: Option<Cell>,
keep: Option<Range<i32>>,
x: i32,
y: i32,
start_x: i32,
start_y: i32,
open: bool,
}
const COORDINATE_LIMIT: f64 = (1i32 << 22) as f64;
#[must_use]
pub(crate) fn to_subpixel(v: f64) -> i32 {
if !v.is_finite() {
return 0;
}
let scaled = v.clamp(-COORDINATE_LIMIT, COORDINATE_LIMIT) * f64::from(SUBPIXEL_SCALE);
#[expect(
clippy::cast_possible_truncation,
reason = "the clamp bounds the product to +/-2^30; truncation toward \
zero is the ported conversion, not an accident"
)]
let out = scaled as i32;
out
}
impl Rasterizer {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn reset(&mut self) {
self.store.clear();
self.current = None;
self.open = false;
}
pub fn keep_rows(&mut self, rows: Range<i32>) {
self.keep = Some(rows);
}
fn kept(&self, y: i32) -> bool {
self.keep.as_ref().is_none_or(|rows| rows.contains(&y))
}
fn bank(&mut self, cell: Cell) {
if !cell.is_empty() && self.kept(cell.y) {
self.store.push(cell);
}
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.store.is_empty() && self.current.is_none_or(Cell::is_empty)
}
pub fn move_to(&mut self, x: f64, y: f64) {
self.close_polygon();
let (x, y) = (to_subpixel(x), to_subpixel(y));
self.set_current(x >> SUBPIXEL_SHIFT, y >> SUBPIXEL_SHIFT);
self.x = x;
self.y = y;
self.start_x = x;
self.start_y = y;
self.open = true;
}
pub fn line_to(&mut self, x: f64, y: f64) {
if !self.open {
return;
}
let (x, y) = (to_subpixel(x), to_subpixel(y));
self.render_line(self.x, self.y, x, y);
self.x = x;
self.y = y;
}
pub fn close_polygon(&mut self) {
if !self.open {
return;
}
self.render_line(self.x, self.y, self.start_x, self.start_y);
self.x = self.start_x;
self.y = self.start_y;
self.open = false;
}
pub fn add_path(&mut self, path: &kurbo::BezPath, tolerance: f64) {
kurbo::flatten(path.iter(), tolerance, |el| match el {
kurbo::PathEl::MoveTo(p) => self.move_to(p.x, p.y),
kurbo::PathEl::LineTo(p) => self.line_to(p.x, p.y),
kurbo::PathEl::ClosePath => self.close_polygon(),
kurbo::PathEl::QuadTo(..) | kurbo::PathEl::CurveTo(..) => {
debug_assert!(false, "kurbo::flatten emits no curves");
}
});
self.close_polygon();
}
fn set_current(&mut self, x: i32, y: i32) {
match self.current {
Some(cell) if cell.x == x && cell.y == y => {}
Some(cell) => {
self.bank(cell);
self.current = Some(Cell::at(x, y));
}
None => self.current = Some(Cell::at(x, y)),
}
}
fn add_cover(&mut self, cover: i32, area: i32) {
if let Some(cell) = self.current.as_mut() {
cell.cover = cell.cover.saturating_add(cover);
cell.area = cell.area.saturating_add(area);
}
}
fn render_hline(&mut self, ey: i32, x1: i32, y1: i32, x2: i32, y2: i32) {
let ex1 = x1 >> SUBPIXEL_SHIFT;
let ex2 = x2 >> SUBPIXEL_SHIFT;
let fx1 = x1 & SUBPIXEL_MASK;
let fx2 = x2 & SUBPIXEL_MASK;
if y1 == y2 {
self.set_current(ex2, ey);
return;
}
if ex1 == ex2 {
let delta = y2 - y1;
self.add_cover(delta, (fx1 + fx2).saturating_mul(delta));
return;
}
let (p, first, incr, dx) = if x2 > x1 {
(
(SUBPIXEL_SCALE - fx1) * (y2 - y1),
SUBPIXEL_SCALE,
1,
x2 - x1,
)
} else {
(fx1 * (y2 - y1), 0, -1, x1 - x2)
};
if dx == 0 {
return;
}
let mut delta = p / dx;
let mut modulo = p % dx;
if modulo < 0 {
delta -= 1;
modulo += dx;
}
self.add_cover(delta, (fx1 + first).saturating_mul(delta));
let mut ex = ex1 + incr;
self.set_current(ex, ey);
let mut y = y1 + delta;
if ex != ex2 {
let step = SUBPIXEL_SCALE * (y2 - y + delta);
let mut lift = step / dx;
let mut rem = step % dx;
if rem < 0 {
lift -= 1;
rem += dx;
}
modulo -= dx;
while ex != ex2 {
delta = lift;
modulo += rem;
if modulo >= 0 {
modulo -= dx;
delta += 1;
}
self.add_cover(delta, SUBPIXEL_SCALE.saturating_mul(delta));
y += delta;
ex += incr;
self.set_current(ex, ey);
}
}
delta = y2 - y;
self.add_cover(delta, (fx2 + SUBPIXEL_SCALE - first).saturating_mul(delta));
}
fn render_line(&mut self, x1: i32, y1: i32, x2: i32, y2: i32) {
const DX_LIMIT: i32 = 16384 << SUBPIXEL_SHIFT;
let dx_total = x2.saturating_sub(x1);
if dx_total >= DX_LIMIT || dx_total <= -DX_LIMIT {
let cx = x1.saturating_add(x2) / 2;
let cy = y1.saturating_add(y2) / 2;
self.render_line(x1, y1, cx, cy);
self.render_line(cx, cy, x2, y2);
return;
}
let dy = y2 - y1;
let ey1 = y1 >> SUBPIXEL_SHIFT;
let ey2 = y2 >> SUBPIXEL_SHIFT;
let fy1 = y1 & SUBPIXEL_MASK;
let fy2 = y2 & SUBPIXEL_MASK;
if ey1 == ey2 {
self.render_hline(ey1, x1, fy1, x2, fy2);
return;
}
if dx_total == 0 {
let ex = x1 >> SUBPIXEL_SHIFT;
let two_fx = (x1 - (ex << SUBPIXEL_SHIFT)) << 1;
let (first, incr) = if dy < 0 { (0, -1) } else { (SUBPIXEL_SCALE, 1) };
let mut delta = first - fy1;
self.add_cover(delta, two_fx.saturating_mul(delta));
let mut ey = ey1 + incr;
self.set_current(ex, ey);
delta = first + first - SUBPIXEL_SCALE;
let area = two_fx.saturating_mul(delta);
while ey != ey2 {
if let Some(cell) = self.current.as_mut() {
cell.cover = delta;
cell.area = area;
}
ey += incr;
self.set_current(ex, ey);
}
delta = fy2 - SUBPIXEL_SCALE + first;
self.add_cover(delta, two_fx.saturating_mul(delta));
return;
}
let (p, first, incr, dy_abs) = if dy < 0 {
(i64::from(fy1) * i64::from(dx_total), 0, -1, -dy)
} else {
(
i64::from(SUBPIXEL_SCALE - fy1) * i64::from(dx_total),
SUBPIXEL_SCALE,
1,
dy,
)
};
if dy_abs == 0 {
return;
}
let dy64 = i64::from(dy_abs);
let narrow = |v: i64| -> i32 { i32::try_from(v).unwrap_or(0) };
let mut delta = narrow(p / dy64);
let mut modulo = narrow(p % dy64);
if modulo < 0 {
delta -= 1;
modulo += dy_abs;
}
let mut x_from = x1.saturating_add(delta);
self.render_hline(ey1, x1, fy1, x_from, first);
let mut ey = ey1 + incr;
self.set_current(x_from >> SUBPIXEL_SHIFT, ey);
if ey != ey2 {
let step = i64::from(SUBPIXEL_SCALE) * i64::from(dx_total);
let mut lift = narrow(step / dy64);
let mut rem = narrow(step % dy64);
if rem < 0 {
lift -= 1;
rem += dy_abs;
}
modulo -= dy_abs;
while ey != ey2 {
delta = lift;
modulo += rem;
if modulo >= 0 {
modulo -= dy_abs;
delta += 1;
}
let x_to = x_from.saturating_add(delta);
self.render_hline(ey, x_from, SUBPIXEL_SCALE - first, x_to, first);
x_from = x_to;
ey += incr;
self.set_current(x_from >> SUBPIXEL_SHIFT, ey);
}
}
self.render_hline(ey, x_from, SUBPIXEL_SCALE - first, x2, fy2);
}
fn finish(&mut self) {
self.close_polygon();
if let Some(cell) = self.current.take() {
self.bank(cell);
}
self.store.sort();
}
pub fn sweep(
&mut self,
rule: FillRule,
coverage: Coverage,
mut emit: impl FnMut(i32, i32, i32, u8),
) {
self.finish();
for (y, row) in self.store.rows() {
sweep_row(row, y, rule, coverage, &mut emit);
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Coverage {
#[default]
Exact,
Thresholded,
Full,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum FillRule {
#[default]
NonZero,
EvenOdd,
}
fn sweep_row(
row: &[Cell],
y: i32,
rule: FillRule,
coverage: Coverage,
emit: &mut impl FnMut(i32, i32, i32, u8),
) {
let mut cover = 0i32;
let mut i = 0usize;
while let Some(&first) = row.get(i) {
let x = first.x;
let mut area = first.area;
cover = cover.saturating_add(first.cover);
i += 1;
while let Some(&next) = row.get(i) {
if next.x != x {
break;
}
area = area.saturating_add(next.area);
cover = cover.saturating_add(next.cover);
i += 1;
}
let mut next_x = x;
if area != 0 {
let alpha = coverage_to_alpha(
(cover << (SUBPIXEL_SHIFT + 1)).saturating_sub(area),
rule,
coverage,
);
if alpha != 0 {
emit(x, 1, y, alpha);
}
next_x = x + 1;
}
if let Some(&next) = row.get(i)
&& next.x > next_x
{
let alpha = coverage_to_alpha(cover << (SUBPIXEL_SHIFT + 1), rule, coverage);
if alpha != 0 {
emit(next_x, next.x - next_x, y, alpha);
}
}
}
}
#[must_use]
pub(crate) fn coverage_to_alpha(area: i32, rule: FillRule, coverage: Coverage) -> u8 {
const COVER_FULL: i32 = 1 << 8;
const COVER_MASK: i32 = COVER_FULL - 1;
let mut cover = area >> (SUBPIXEL_SHIFT * 2 + 1 - 8);
if cover < 0 {
cover = cover.saturating_neg();
}
if rule == FillRule::EvenOdd {
cover &= (COVER_FULL * 2) - 1;
if cover > COVER_FULL {
cover = COVER_FULL * 2 - cover;
}
}
match coverage {
Coverage::Exact => {}
Coverage::Thresholded => {
cover = if cover > COVER_MASK / 2 {
COVER_MASK
} else {
0
};
}
Coverage::Full => {
if cover > 0 {
cover = COVER_MASK;
}
}
}
if cover > COVER_MASK {
cover = COVER_MASK;
}
#[expect(
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
reason = "the clamp above bounds cover to 0..=255"
)]
let byte = cover as u8;
byte
}
#[cfg(test)]
mod tests {
use super::*;
fn coverage(path: &kurbo::BezPath, w: i32, h: i32, rule: FillRule, mode: Coverage) -> Vec<u8> {
let cells = usize::try_from(w * h).expect("a test plane fits");
let mut out = vec![0u8; cells];
let mut raster = Rasterizer::new();
raster.add_path(path, 0.1);
raster.sweep(rule, mode, |x, len, y, alpha| {
if y < 0 || y >= h {
return;
}
for col in x.max(0)..(x + len).min(w) {
let Ok(index) = usize::try_from(y * w + col) else {
continue;
};
if let Some(slot) = out.get_mut(index) {
*slot = alpha;
}
}
});
out
}
fn banded_coverage(
path: &kurbo::BezPath,
w: i32,
h: i32,
rule: FillRule,
mode: Coverage,
) -> Vec<u8> {
let cells = usize::try_from(w * h).expect("a test plane fits");
let mut out = vec![0u8; cells];
let mut raster = Rasterizer::new();
raster.keep_rows(0..h);
raster.add_path(path, 0.1);
raster.sweep(rule, mode, |x, len, y, alpha| {
if y < 0 || y >= h {
return;
}
for col in x.max(0)..(x + len).min(w) {
let Ok(index) = usize::try_from(y * w + col) else {
continue;
};
if let Some(slot) = out.get_mut(index) {
*slot = alpha;
}
}
});
out
}
fn banked(path: &kurbo::BezPath, rows: Option<core::ops::Range<i32>>) -> usize {
let mut raster = Rasterizer::new();
if let Some(rows) = rows {
raster.keep_rows(rows);
}
raster.add_path(path, 0.1);
let mut cells = 0usize;
raster.sweep(FillRule::NonZero, Coverage::Exact, |_, _, _, _| {});
for (_, row) in raster.store.rows() {
cells += row.len();
}
cells
}
fn rect(x0: f64, y0: f64, x1: f64, y1: f64) -> kurbo::BezPath {
let mut p = kurbo::BezPath::new();
p.move_to((x0, y0));
p.line_to((x1, y0));
p.line_to((x1, y1));
p.line_to((x0, y1));
p.close_path();
p
}
#[test]
fn a_whole_pixel_rect_is_fully_covered() {
let cov = coverage(
&rect(1.0, 1.0, 3.0, 3.0),
4,
4,
FillRule::NonZero,
Coverage::Exact,
);
assert_eq!(cov.first().copied(), Some(0), "outside");
assert_eq!(cov.get(5).copied(), Some(255), "inside (1,1)");
assert_eq!(cov.get(10).copied(), Some(255), "inside (2,2)");
assert_eq!(cov.get(15).copied(), Some(0), "outside (3,3)");
}
#[test]
fn a_half_covered_pixel_is_exactly_half() {
let cov = coverage(
&rect(0.0, 0.0, 0.5, 1.0),
1,
1,
FillRule::NonZero,
Coverage::Exact,
);
assert_eq!(cov.first().copied(), Some(128));
}
#[test]
fn the_alpha_mapping_is_times_256_truncating() {
for (num, expected) in [(1, 32u8), (3, 96), (5, 160), (7, 224)] {
let frac = f64::from(num) / 8.0;
let cov = coverage(
&rect(0.0, 0.0, frac, 1.0),
1,
1,
FillRule::NonZero,
Coverage::Exact,
);
assert_eq!(
cov.first().copied(),
Some(expected),
"coverage {num}/8 must map to {expected}"
);
}
}
#[test]
fn full_coverage_clamps_to_255_not_256() {
assert_eq!(
coverage_to_alpha(1 << 17, FillRule::NonZero, Coverage::Exact),
255
);
}
#[test]
fn even_odd_punches_out_an_overlap() {
let mut p = rect(0.0, 0.0, 6.0, 6.0);
p.extend(rect(2.0, 2.0, 4.0, 4.0).iter());
let eo = coverage(&p, 6, 6, FillRule::EvenOdd, Coverage::Exact);
let nz = coverage(&p, 6, 6, FillRule::NonZero, Coverage::Exact);
assert_eq!(eo.get(3 * 6 + 3).copied(), Some(0), "even-odd punches out");
assert_eq!(nz.get(3 * 6 + 3).copied(), Some(255), "non-zero fills");
assert_eq!(eo.get(6 + 1).copied(), Some(255));
assert_eq!(nz.get(6 + 1).copied(), Some(255));
}
#[test]
fn aliasing_thresholds_at_the_midpoint() {
let over = coverage(
&rect(0.0, 0.0, 0.6, 1.0),
1,
1,
FillRule::NonZero,
Coverage::Thresholded,
);
let under = coverage(
&rect(0.0, 0.0, 0.4, 1.0),
1,
1,
FillRule::NonZero,
Coverage::Thresholded,
);
assert_eq!(over.first().copied(), Some(255));
assert_eq!(under.first().copied(), Some(0));
}
#[test]
fn full_cover_tests_against_zero_rather_than_the_midpoint() {
for frac in [0.4, 0.6, 0.05] {
let cov = coverage(
&rect(0.0, 0.0, frac, 1.0),
1,
1,
FillRule::NonZero,
Coverage::Full,
);
assert_eq!(
cov.first().copied(),
Some(255),
"coverage {frac} is non-zero, so full_cover writes it opaque"
);
}
let miss = coverage(
&rect(2.0, 2.0, 3.0, 3.0),
1,
1,
FillRule::NonZero,
Coverage::Full,
);
assert_eq!(miss.first().copied(), Some(0));
}
#[test]
fn an_exact_45_degree_edge_halves_every_boundary_pixel() {
let mut p = kurbo::BezPath::new();
p.move_to((0.0, 0.0));
p.line_to((32.0, 0.0));
p.line_to((0.0, 32.0));
p.close_path();
let cov = coverage(&p, 32, 32, FillRule::NonZero, Coverage::Exact);
let mut levels: Vec<u8> = cov
.iter()
.copied()
.filter(|&a| a != 0 && a != 255)
.collect();
levels.sort_unstable();
levels.dedup();
assert_eq!(levels, vec![128], "a 45 degree edge halves its pixels");
}
#[test]
fn a_shallow_edge_has_more_than_seventeen_levels() {
let mut p = kurbo::BezPath::new();
p.move_to((0.0, 0.0));
p.line_to((64.0, 0.0));
p.line_to((64.0, 5.0));
p.close_path();
let cov = coverage(&p, 64, 8, FillRule::NonZero, Coverage::Exact);
let mut levels: Vec<u8> = cov
.iter()
.copied()
.filter(|&a| a != 0 && a != 255)
.collect();
levels.sort_unstable();
levels.dedup();
assert!(
levels.len() > 17,
"only {} partial levels along a shallow edge",
levels.len()
);
}
#[test]
fn winding_direction_does_not_change_coverage() {
let cw = coverage(
&rect(0.0, 0.0, 4.0, 4.0),
4,
4,
FillRule::NonZero,
Coverage::Exact,
);
let mut ccw = kurbo::BezPath::new();
ccw.move_to((0.0, 0.0));
ccw.line_to((0.0, 4.0));
ccw.line_to((4.0, 4.0));
ccw.line_to((4.0, 0.0));
ccw.close_path();
assert_eq!(cw, coverage(&ccw, 4, 4, FillRule::NonZero, Coverage::Exact));
}
#[test]
fn an_unclosed_subpath_fills_as_though_closed() {
let mut open = kurbo::BezPath::new();
open.move_to((0.0, 0.0));
open.line_to((4.0, 0.0));
open.line_to((4.0, 4.0));
open.line_to((0.0, 4.0));
let closed = coverage(
&rect(0.0, 0.0, 4.0, 4.0),
4,
4,
FillRule::NonZero,
Coverage::Exact,
);
assert_eq!(
coverage(&open, 4, 4, FillRule::NonZero, Coverage::Exact),
closed
);
}
#[test]
fn coordinates_truncate_toward_zero() {
assert_eq!(to_subpixel(1.0), 256);
assert_eq!(to_subpixel(1.5), 384);
assert_eq!(to_subpixel(-1.5), -384);
assert_eq!(to_subpixel(0.999), 255);
}
#[test]
fn a_non_finite_coordinate_becomes_zero_rather_than_panicking() {
assert_eq!(to_subpixel(f64::NAN), 0);
assert_eq!(to_subpixel(f64::INFINITY), 0);
}
#[test]
fn an_empty_path_sweeps_nothing() {
let mut r = Rasterizer::new();
r.add_path(&kurbo::BezPath::new(), 0.1);
let mut spans = 0;
r.sweep(FillRule::NonZero, Coverage::Exact, |_, _, _, _| spans += 1);
assert_eq!(spans, 0);
}
#[test]
fn a_line_to_without_a_move_to_is_ignored() {
let mut r = Rasterizer::new();
r.line_to(4.0, 4.0);
assert!(r.is_empty());
}
#[test]
fn total_coverage_matches_the_area_of_a_slanted_quad() {
let mut p = kurbo::BezPath::new();
p.move_to((2.0, 1.0));
p.line_to((14.0, 3.0));
p.line_to((13.0, 14.0));
p.line_to((1.0, 12.0));
p.close_path();
let cov = coverage(&p, 16, 16, FillRule::NonZero, Coverage::Exact);
let painted: f64 = cov.iter().map(|&a| f64::from(a) / 256.0).sum();
let pts = [(2.0, 1.0), (14.0, 3.0), (13.0, 14.0), (1.0, 12.0)];
let mut area = 0.0f64;
for i in 0..4 {
let (Some(&(x0, y0)), Some(&(x1, y1))) = (pts.get(i), pts.get((i + 1) % 4)) else {
continue;
};
area += x0 * y1 - x1 * y0;
}
let area = (area / 2.0).abs();
assert!(
(painted - area).abs() < 1.0,
"painted {painted:.3} vs geometric {area:.3}"
);
}
fn off_target_paths() -> Vec<(&'static str, kurbo::BezPath)> {
vec![
("above", rect(2.0, -900.0, 6.0, 5.0)),
("below", rect(2.0, 3.0, 6.0, 900.0)),
("both", rect(2.0, -900.0, 6.0, 900.0)),
("clamped", rect(2.0, -32000.0, 6.0, 32000.0)),
("clamped-slanted", {
let mut p = kurbo::BezPath::new();
p.move_to((1.5, -32000.0));
p.line_to((6.5, 32000.0));
p.line_to((7.5, 32000.0));
p.line_to((2.5, -32000.0));
p.close_path();
p
}),
("left", rect(-32000.0, 2.0, 3.5, 6.0)),
("right", rect(4.5, 2.0, 32000.0, 6.0)),
("every-side", rect(-32000.0, -32000.0, 32000.0, 32000.0)),
]
}
#[test]
fn the_band_reproduces_the_unbanded_plane() {
for (name, path) in off_target_paths() {
for rule in [FillRule::NonZero, FillRule::EvenOdd] {
for mode in [Coverage::Exact, Coverage::Thresholded, Coverage::Full] {
let spec = coverage(&path, 8, 8, rule, mode);
let banded = banded_coverage(&path, 8, 8, rule, mode);
assert_eq!(spec, banded, "{name} under {rule:?}/{mode:?}");
}
}
}
}
#[test]
fn a_path_that_stays_inside_the_band_is_untouched_by_it() {
let path = rect(1.25, 1.75, 6.5, 5.5);
for rule in [FillRule::NonZero, FillRule::EvenOdd] {
for mode in [Coverage::Exact, Coverage::Thresholded, Coverage::Full] {
assert_eq!(
coverage(&path, 8, 8, rule, mode),
banded_coverage(&path, 8, 8, rule, mode),
"{rule:?}/{mode:?}"
);
}
}
}
#[test]
fn the_band_keeps_the_targets_last_row() {
let path = rect(1.0, 6.0, 7.0, 8.0);
let banded = banded_coverage(&path, 8, 8, FillRule::NonZero, Coverage::Exact);
assert_eq!(banded.get(8 * 7 + 3).copied(), Some(255), "the last row");
assert_eq!(
coverage(&path, 8, 8, FillRule::NonZero, Coverage::Exact),
banded
);
}
#[test]
fn the_band_keeps_the_targets_first_row() {
let path = rect(1.0, -4.0, 7.0, 1.0);
let banded = banded_coverage(&path, 8, 8, FillRule::NonZero, Coverage::Exact);
assert_eq!(banded.first().copied(), Some(0), "column 0 is outside");
assert_eq!(banded.get(3).copied(), Some(255), "the first row");
assert_eq!(
coverage(&path, 8, 8, FillRule::NonZero, Coverage::Exact),
banded
);
}
#[test]
fn the_band_drops_the_rows_it_says_it_drops() {
let path = rect(2.0, -32000.0, 6.0, 32000.0);
let unbanded = banked(&path, None);
let banded = banked(&path, Some(0..8));
assert!(
unbanded > 60_000,
"the unbanded store holds a cell per crossed row, got {unbanded}"
);
assert!(
banded <= 32,
"the banded store holds only the target's rows, got {banded}"
);
}
#[test]
fn a_band_is_only_about_rows() {
let path = rect(-32000.0, 2.0, 32000.0, 6.0);
assert_eq!(banked(&path, None), banked(&path, Some(0..8)));
}
#[test]
fn a_band_outside_the_path_leaves_nothing() {
let path = rect(2.0, -900.0, 6.0, -100.0);
assert_eq!(
banded_coverage(&path, 8, 8, FillRule::NonZero, Coverage::Exact),
vec![0u8; 64]
);
assert_eq!(banked(&path, Some(0..8)), 0);
}
#[test]
fn every_rows_cover_returns_to_zero_by_its_end() {
for (name, path) in off_target_paths() {
let mut raster = Rasterizer::new();
raster.add_path(&path, 0.1);
raster.sweep(FillRule::NonZero, Coverage::Exact, |_, _, _, _| {});
for (y, row) in raster.store.rows() {
let cover: i32 = row.iter().map(|c| c.cover).sum();
assert_eq!(cover, 0, "{name} leaves cover on row {y}");
}
}
}
#[test]
fn a_reset_keeps_the_band_the_caller_set() {
let mut raster = Rasterizer::new();
raster.keep_rows(0..8);
raster.add_path(&rect(2.0, -900.0, 6.0, 900.0), 0.1);
raster.sweep(FillRule::NonZero, Coverage::Exact, |_, _, _, _| {});
raster.reset();
raster.add_path(&rect(2.0, -900.0, 6.0, 900.0), 0.1);
raster.sweep(FillRule::NonZero, Coverage::Exact, |_, _, _, _| {});
let cells: usize = raster.store.rows().map(|(_, row)| row.len()).sum();
assert!(cells <= 32, "the band survives a reset, got {cells}");
}
}