use crate::geometry::Point;
use crate::path::FillRule;
pub(crate) mod mask;
pub(crate) struct Rasterizer {
ox: i32,
oy: i32,
width: usize,
height: usize,
cover: Vec<f32>,
area: Vec<f32>,
min_x: usize,
max_x: usize,
min_y: usize,
max_y: usize,
touched: bool,
}
impl Rasterizer {
pub fn new(ox: i32, oy: i32, width: usize, height: usize) -> Self {
Rasterizer {
ox,
oy,
width,
height,
cover: vec![0.0; width * height],
area: vec![0.0; width * height],
min_x: 0,
max_x: 0,
min_y: 0,
max_y: 0,
touched: false,
}
}
#[inline]
fn mark(&mut self, col: usize, row: usize) {
if !self.touched {
self.touched = true;
self.min_x = col;
self.max_x = col;
self.min_y = row;
self.max_y = row;
} else {
if col < self.min_x {
self.min_x = col;
}
if col > self.max_x {
self.max_x = col;
}
if row < self.min_y {
self.min_y = row;
}
if row > self.max_y {
self.max_y = row;
}
}
}
pub fn add_line(&mut self, p0: Point, p1: Point) {
let h = self.height as f32;
let (ox, oy) = (self.ox as f32, self.oy as f32);
let (mut ax, mut ay) = (p0.x - ox, p0.y - oy);
let (mut bx, mut by) = (p1.x - ox, p1.y - oy);
if (ay - by).abs() < 1e-6 {
return; }
let winding = if ay < by { 1.0 } else { -1.0 };
if ay > by {
std::mem::swap(&mut ax, &mut bx);
std::mem::swap(&mut ay, &mut by);
}
let dxdy = (bx - ax) / (by - ay);
if ay < 0.0 {
ax += (0.0 - ay) * dxdy;
ay = 0.0;
}
if by > h {
by = h; }
if ay >= by {
return;
}
let mut y = ay;
let mut x_cur = ax;
while y < by {
let row = y.floor();
let y_next = (row + 1.0).min(by);
let x_next = ax + (y_next - ay) * dxdy;
let dcover = (y_next - y) * winding;
self.add_band(row as i32, x_cur, x_next, dcover);
y = y_next;
x_cur = x_next;
}
}
fn add_band(&mut self, row: i32, x0: f32, x1: f32, dcover: f32) {
if dcover == 0.0 || row < 0 || row >= self.height as i32 || self.width == 0 {
return;
}
let row = row as usize;
let w = self.width;
let base = row * w;
let (lo, hi) = if x0 <= x1 { (x0, x1) } else { (x1, x0) };
if lo >= w as f32 {
return; }
if (hi - lo) < 1e-6 {
let mut col = lo.floor() as i32;
let mut fx = lo - col as f32;
if col < 0 {
col = 0;
fx = 0.0;
}
if col >= w as i32 {
return;
}
let idx = base + col as usize;
self.cover[idx] += dcover;
self.area[idx] += 2.0 * fx * dcover;
self.mark(col as usize, row);
return;
}
let inv_dx = 1.0 / (hi - lo);
let mut start = lo;
if lo < 0.0 {
let d_left = dcover * ((0.0 - lo) * inv_dx);
self.cover[base] += d_left;
self.mark(0, row);
start = 0.0;
}
let end = hi.min(w as f32);
let mut x = start;
let mut col = start.floor() as i32;
while x < end {
let next_edge = ((col + 1) as f32).min(end);
let seg = next_edge - x;
let d = dcover * (seg * inv_dx);
let fxa = x - col as f32;
let fxb = next_edge - col as f32;
let idx = base + col as usize;
self.cover[idx] += d;
self.area[idx] += (fxa + fxb) * d;
self.mark(col as usize, row);
x = next_edge;
col += 1;
}
}
pub fn for_each_row<F: FnMut(usize, usize, &[f32])>(&self, fill_rule: FillRule, mut f: F) {
if !self.touched {
return;
}
let mut row: Vec<f32> = Vec::with_capacity(self.max_x - self.min_x + 1);
for y in self.min_y..=self.max_y {
let base = y * self.width;
row.clear();
let mut acc = 0.0f32;
let mut x = self.min_x;
while x < self.width {
acc += self.cover[base + x];
let raw = acc - 0.5 * self.area[base + x];
let coverage = match fill_rule {
FillRule::NonZero => raw.abs().min(1.0),
FillRule::EvenOdd => {
let t = raw.abs() % 2.0;
if t > 1.0 {
2.0 - t
} else {
t
}
}
};
row.push(coverage);
x += 1;
if x > self.max_x && acc.abs() < 1e-4 {
break; }
}
if !row.is_empty() {
f(y + self.oy as usize, self.min_x + self.ox as usize, &row);
}
}
}
pub fn for_each_pixel<F: FnMut(usize, usize, f32)>(&self, fill_rule: FillRule, mut f: F) {
self.for_each_row(fill_rule, |y, x_start, coverages| {
for (i, &coverage) in coverages.iter().enumerate() {
if coverage > 0.000_1 {
f(x_start + i, y, coverage);
}
}
});
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fills_axis_aligned_rect() {
let mut r = Rasterizer::new(0, 0, 10, 10);
let pts = [
Point::new(2.0, 2.0),
Point::new(8.0, 2.0),
Point::new(8.0, 8.0),
Point::new(2.0, 8.0),
];
for i in 0..4 {
r.add_line(pts[i], pts[(i + 1) % 4]);
}
let mut grid = [[0.0f32; 10]; 10];
r.for_each_pixel(FillRule::NonZero, |x, y, c| grid[y][x] = c);
assert!((grid[5][5] - 1.0).abs() < 1e-3, "interior pixel: {}", grid[5][5]);
assert!(grid[0][0] < 1e-3, "exterior pixel");
assert!(grid[5][9] < 1e-3, "past the right edge");
}
#[test]
fn half_covered_edge() {
let mut r = Rasterizer::new(0, 0, 10, 10);
let pts = [
Point::new(2.5, 2.0),
Point::new(8.0, 2.0),
Point::new(8.0, 8.0),
Point::new(2.5, 8.0),
];
for i in 0..4 {
r.add_line(pts[i], pts[(i + 1) % 4]);
}
let mut grid = [[0.0f32; 10]; 10];
r.for_each_pixel(FillRule::NonZero, |x, y, c| grid[y][x] = c);
assert!((grid[5][2] - 0.5).abs() < 0.05, "edge: {}", grid[5][2]);
}
}