use crate::api::style::FillRule;
use crate::pipeline::cache::SweepFn;
pub struct AnalyticRasterizer {
cells: Vec<i32>,
cov_buf: Vec<u8>,
edge_sort_buf: Vec<(f64, f64, f64, f64)>,
}
impl AnalyticRasterizer {
pub fn new() -> Self {
Self {
cells: Vec::new(),
cov_buf: Vec::new(),
edge_sort_buf: Vec::new(),
}
}
#[allow(clippy::too_many_arguments)]
pub fn rasterize<F>(
&mut self,
edges: &[(f64, f64, f64, f64)],
clip_x0: i32,
clip_y0: i32,
clip_x1: i32,
clip_y1: i32,
sweep_fn: SweepFn,
mut f: F,
) where
F: FnMut(i32, i32, &[u8]),
{
let width = (clip_x1 - clip_x0) as usize;
if width == 0 {
return;
}
self.cells.resize(width + 1, 0);
self.cov_buf.resize(width, 0);
let mut sorted = std::mem::take(&mut self.edge_sort_buf);
sorted.clear();
sorted.extend_from_slice(edges);
sort_edges_by_y_min(&mut sorted);
let mut next_edge_idx: usize = 0;
let mut ctx = ScanlineCtx {
top: 0.0,
bottom: 0.0,
clip_x0,
dirty_min: width + 1,
dirty_max: 0,
right_clipped: false,
};
for y in clip_y0..clip_y1 {
ctx.top = y as f64;
ctx.bottom = (y + 1) as f64;
if ctx.dirty_max > width && ctx.dirty_max <= width + 1 {
self.cells[width] = 0;
}
ctx.dirty_min = width + 1;
ctx.dirty_max = 0;
ctx.right_clipped = false;
while next_edge_idx < sorted.len() {
let (_, y0, _, y1) = sorted[next_edge_idx];
let y_max = y0.max(y1);
if y_max > ctx.top {
break;
}
next_edge_idx += 1;
}
for &(x0, y0, x1, y1) in &sorted[next_edge_idx..] {
let y_min = y0.min(y1);
if y_min >= ctx.bottom {
break;
}
add_edge(&mut self.cells, x0, y0, x1, y1, &mut ctx);
}
let sweep_dirty_min = ctx.dirty_min.min(width);
if sweep_dirty_min < ctx.dirty_max {
let sweep_max = if ctx.right_clipped {
width
} else {
ctx.dirty_max.min(width)
};
if sweep_dirty_min < sweep_max {
let sweep_len = sweep_max - sweep_dirty_min;
unsafe {
sweep_fn(
self.cells[sweep_dirty_min..].as_mut_ptr(),
self.cov_buf[sweep_dirty_min..].as_mut_ptr(),
sweep_len,
);
}
let sweep_slice = &self.cov_buf[sweep_dirty_min..sweep_max];
if let Some(first) = sweep_slice.iter().position(|&c| c != 0) {
let last = sweep_slice.iter().rposition(|&c| c != 0).unwrap();
let start = sweep_dirty_min + first;
let end = sweep_dirty_min + last + 1;
f(y, clip_x0 + start as i32, &self.cov_buf[start..end]);
}
}
}
}
self.edge_sort_buf = sorted;
}
}
impl Default for AnalyticRasterizer {
fn default() -> Self {
Self::new()
}
}
fn sort_edges_by_y_min(edges: &mut [(f64, f64, f64, f64)]) {
edges.sort_unstable_by(|a, b| {
let a_y_min = a.1.min(a.3);
let b_y_min = b.1.min(b.3);
debug_assert!(!a_y_min.is_nan(), "エッジの Y 座標が NaN");
debug_assert!(!b_y_min.is_nan(), "エッジの Y 座標が NaN");
a_y_min.total_cmp(&b_y_min)
});
}
struct ScanlineCtx {
top: f64,
bottom: f64,
clip_x0: i32,
dirty_min: usize,
dirty_max: usize,
right_clipped: bool,
}
fn write_cell(cells: &mut [i32], idx: isize, value: i32, ctx: &mut ScanlineCtx) {
if idx >= 0 && (idx as usize) < cells.len() {
let i = idx as usize;
cells[i] += value;
if i < ctx.dirty_min {
ctx.dirty_min = i;
}
if i + 1 > ctx.dirty_max {
ctx.dirty_max = i + 1;
}
} else if idx < 0 {
cells[0] += value;
if 0 < ctx.dirty_min {
ctx.dirty_min = 0;
}
if 1 > ctx.dirty_max {
ctx.dirty_max = 1;
}
} else {
ctx.right_clipped = true;
}
}
#[allow(clippy::too_many_arguments)]
fn emit_cell(
cells: &mut [i32],
cell_x: i32,
x_start: f64,
x_end: f64,
y_start: f64,
y_end: f64,
direction_sign: i32,
ctx: &mut ScanlineCtx,
) {
let fx_start = x_start - cell_x as f64;
let fx_end = x_end - cell_x as f64;
let dy = y_end - y_start;
let cover = ((y_end * 256.0) as i32 - (y_start * 256.0) as i32) * direction_sign;
let area = ((fx_start + fx_end) * dy * 65536.0) as i32 * direction_sign;
let delta = cover * 512 - area;
let idx = (cell_x - ctx.clip_x0) as isize;
write_cell(cells, idx, delta, ctx);
write_cell(cells, idx + 1, area, ctx);
}
fn add_edge(cells: &mut [i32], x0: f64, y0: f64, x1: f64, y1: f64, ctx: &mut ScanlineCtx) {
if y0 == y1 {
return;
}
let (ey0, ey1) = if y0 < y1 { (y0, y1) } else { (y1, y0) };
if ey1 <= ctx.top || ey0 >= ctx.bottom {
return;
}
let direction_sign: i32 = if y0 < y1 { 1 } else { -1 };
let clipped_top = ey0.max(ctx.top);
let clipped_bottom = ey1.min(ctx.bottom);
let dy_total = y1 - y0;
let dx_total = x1 - x0;
let inv_dy_total = 1.0 / dy_total;
let t_top = (clipped_top - y0) * inv_dy_total;
let t_bot = (clipped_bottom - y0) * inv_dy_total;
let x_at_top = x0 + t_top * dx_total;
let x_at_bot = x0 + t_bot * dx_total;
let ex_top = x_at_top.floor() as i32;
let ex_bot = x_at_bot.floor() as i32;
if ex_top == ex_bot {
emit_cell(
cells,
ex_top,
x_at_top,
x_at_bot,
clipped_top,
clipped_bottom,
direction_sign,
ctx,
);
return;
}
let dx = x_at_bot - x_at_top;
let dy = clipped_bottom - clipped_top;
let slope_y_per_x = if dx.abs() > 1e-12 { dy / dx } else { 0.0 };
let mut x_cur = x_at_top;
let mut y_cur = clipped_top;
if dx > 0.0 {
for cell_x in ex_top..=ex_bot {
let x_next = if cell_x == ex_bot {
x_at_bot
} else {
(cell_x + 1) as f64
};
let y_next = clipped_top + (x_next - x_at_top) * slope_y_per_x;
emit_cell(
cells,
cell_x,
x_cur,
x_next,
y_cur,
y_next,
direction_sign,
ctx,
);
x_cur = x_next;
y_cur = y_next;
}
} else {
for cell_x in (ex_bot..=ex_top).rev() {
let x_next = if cell_x == ex_bot {
x_at_bot
} else {
cell_x as f64
};
let y_next = clipped_top + (x_next - x_at_top) * slope_y_per_x;
emit_cell(
cells,
cell_x,
x_cur,
x_next,
y_cur,
y_next,
direction_sign,
ctx,
);
x_cur = x_next;
y_cur = y_next;
}
}
}
pub fn sweep_reference(
cells: &[i32],
cov_buf: &mut [u8],
dirty_min: usize,
dirty_max: usize,
fill_rule: FillRule,
) -> Option<(usize, usize)> {
let mut cover: i32 = 0;
let mut start = dirty_max; let mut end = 0usize;
for i in dirty_min..dirty_max {
cover += cells[i];
let shifted = (cover >> 9) as i64;
let cov = match fill_rule {
FillRule::NonZero => shifted.unsigned_abs().min(255) as u8,
FillRule::EvenOdd => {
let val = shifted.unsigned_abs() & 511;
let folded = 512 - val;
val.min(folded).min(255) as u8
}
};
cov_buf[i] = cov;
if cov > 0 {
if start == dirty_max {
start = i;
}
end = i + 1;
}
}
if start < end {
Some((start, end))
} else {
None
}
}