use crate::curves::{subdivide_cubic, subdivide_quadratic};
use crate::edge::{Edge, EdgeList};
use crate::fixed::F26Dot6;
use crate::{DropoutMode, FillRule};
use skrifa::outline::OutlinePen;
#[derive(Debug)]
pub struct ScanConverter {
edge_table: Vec<EdgeList>,
active_edges: EdgeList,
current_x: F26Dot6,
current_y: F26Dot6,
contour_start_x: F26Dot6,
contour_start_y: F26Dot6,
fill_rule: FillRule,
dropout_mode: DropoutMode,
width: usize,
height: usize,
}
impl ScanConverter {
pub fn new(width: usize, height: usize) -> Self {
let mut edge_table = Vec::with_capacity(height);
for _ in 0..height {
edge_table.push(EdgeList::with_capacity(16)); }
Self {
edge_table,
active_edges: EdgeList::with_capacity(32), current_x: F26Dot6::ZERO,
current_y: F26Dot6::ZERO,
contour_start_x: F26Dot6::ZERO,
contour_start_y: F26Dot6::ZERO,
fill_rule: FillRule::NonZeroWinding,
dropout_mode: DropoutMode::None,
width,
height,
}
}
pub fn set_fill_rule(&mut self, rule: FillRule) {
self.fill_rule = rule;
}
pub fn set_dropout_mode(&mut self, mode: DropoutMode) {
self.dropout_mode = mode;
}
pub fn fill_rule(&self) -> FillRule {
self.fill_rule
}
pub fn dropout_mode(&self) -> DropoutMode {
self.dropout_mode
}
pub fn reset(&mut self) {
for list in &mut self.edge_table {
list.clear();
}
self.active_edges.clear();
self.current_x = F26Dot6::ZERO;
self.current_y = F26Dot6::ZERO;
self.contour_start_x = F26Dot6::ZERO;
self.contour_start_y = F26Dot6::ZERO;
}
pub fn move_to(&mut self, x: F26Dot6, y: F26Dot6) {
self.current_x = x;
self.current_y = y;
self.contour_start_x = x;
self.contour_start_y = y;
}
pub fn line_to(&mut self, x: F26Dot6, y: F26Dot6) {
self.add_line(self.current_x, self.current_y, x, y);
self.current_x = x;
self.current_y = y;
}
pub fn quadratic_to(&mut self, x1: F26Dot6, y1: F26Dot6, x2: F26Dot6, y2: F26Dot6) {
let x0 = self.current_x;
let y0 = self.current_y;
subdivide_quadratic(
x0,
y0,
x1,
y1,
x2,
y2,
&mut |x, y| {
self.add_line(self.current_x, self.current_y, x, y);
self.current_x = x;
self.current_y = y;
},
0,
);
}
pub fn cubic_to(
&mut self,
x1: F26Dot6,
y1: F26Dot6,
x2: F26Dot6,
y2: F26Dot6,
x3: F26Dot6,
y3: F26Dot6,
) {
let x0 = self.current_x;
let y0 = self.current_y;
subdivide_cubic(
x0,
y0,
x1,
y1,
x2,
y2,
x3,
y3,
&mut |x, y| {
self.add_line(self.current_x, self.current_y, x, y);
self.current_x = x;
self.current_y = y;
},
0,
);
}
pub fn close(&mut self) {
if self.current_x != self.contour_start_x || self.current_y != self.contour_start_y {
self.add_line(
self.current_x,
self.current_y,
self.contour_start_x,
self.contour_start_y,
);
}
self.current_x = self.contour_start_x;
self.current_y = self.contour_start_y;
}
fn add_line(&mut self, x1: F26Dot6, y1: F26Dot6, x2: F26Dot6, y2: F26Dot6) {
if let Some(mut edge) = Edge::new(x1, y1, x2, y2) {
let y_min = edge.y_min;
if y_min >= self.height as i32 {
return;
}
if edge.y_max < 0 {
return;
}
let start_scanline = if y_min < 0 {
let skip = -y_min;
edge.x = edge.x + edge.x_increment.mul(F26Dot6::from_int(skip));
0
} else {
y_min as usize
};
if start_scanline < self.edge_table.len() {
self.edge_table[start_scanline].push(edge);
}
}
}
pub fn rasterize(&mut self) -> Vec<u8> {
let mut mono = vec![0u8; self.width * self.height];
self.render_mono(&mut mono);
mono.iter().map(|&p| if p == 1 { 255 } else { 0 }).collect()
}
pub fn render_mono(&mut self, bitmap: &mut [u8]) {
assert_eq!(
bitmap.len(),
self.width * self.height,
"Bitmap size mismatch"
);
bitmap.fill(0);
for y in 0..self.height {
self.scan_line_mono(y as i32, bitmap);
}
}
fn scan_line_mono(&mut self, y: i32, bitmap: &mut [u8]) {
if y < 0 || y >= self.height as i32 {
return;
}
if (y as usize) < self.edge_table.len() {
self.active_edges.extend(&self.edge_table[y as usize]);
}
self.active_edges.remove_inactive(y);
self.active_edges.sort_by_x();
match self.fill_rule {
FillRule::NonZeroWinding => self.fill_nonzero_winding(y, bitmap),
FillRule::EvenOdd => self.fill_even_odd(y, bitmap),
}
self.active_edges.step_all();
}
fn fill_nonzero_winding(&self, y: i32, bitmap: &mut [u8]) {
let mut winding = 0i32;
let mut fill_start: Option<i32> = None;
for edge in self.active_edges.iter() {
let x = edge.x.to_int();
let old_winding = winding;
winding += edge.direction as i32;
if old_winding == 0 && winding != 0 {
fill_start = Some(x);
} else if old_winding != 0 && winding == 0 {
if let Some(start) = fill_start {
self.fill_span(start, x, y, bitmap);
fill_start = None;
}
}
}
}
fn fill_even_odd(&self, y: i32, bitmap: &mut [u8]) {
let mut inside = false;
let mut fill_start = 0i32;
for edge in self.active_edges.iter() {
let x = edge.x.to_int();
if inside {
self.fill_span(fill_start, x, y, bitmap);
inside = false;
} else {
fill_start = x;
inside = true;
}
}
}
fn fill_span(&self, x1: i32, x2: i32, y: i32, bitmap: &mut [u8]) {
if y < 0 || y >= self.height as i32 || x1 >= x2 {
return;
}
let x_start = x1.max(0).min(self.width as i32) as usize;
let x_end = x2.max(0).min(self.width as i32) as usize;
if x_start >= x_end {
return;
}
let row_offset = y as usize * self.width;
let span_start = row_offset + x_start;
let span_end = row_offset + x_end;
if let Some(span) = bitmap.get_mut(span_start..span_end) {
span.fill(1);
}
}
}
impl OutlinePen for ScanConverter {
fn move_to(&mut self, x: f32, y: f32) {
let y_flipped = self.height as f32 - y;
self.move_to(F26Dot6::from_float(x), F26Dot6::from_float(y_flipped));
}
fn line_to(&mut self, x: f32, y: f32) {
let y_flipped = self.height as f32 - y;
self.line_to(F26Dot6::from_float(x), F26Dot6::from_float(y_flipped));
}
fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) {
let y1_flipped = self.height as f32 - y1;
let y_flipped = self.height as f32 - y;
self.quadratic_to(
F26Dot6::from_float(x1),
F26Dot6::from_float(y1_flipped),
F26Dot6::from_float(x),
F26Dot6::from_float(y_flipped),
);
}
fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) {
let y1_flipped = self.height as f32 - y1;
let y2_flipped = self.height as f32 - y2;
let y_flipped = self.height as f32 - y;
self.cubic_to(
F26Dot6::from_float(x1),
F26Dot6::from_float(y1_flipped),
F26Dot6::from_float(x2),
F26Dot6::from_float(y2_flipped),
F26Dot6::from_float(x),
F26Dot6::from_float(y_flipped),
);
}
fn close(&mut self) {
self.close();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_scan_converter_new() {
let sc = ScanConverter::new(64, 64);
assert_eq!(sc.width, 64);
assert_eq!(sc.height, 64);
assert_eq!(sc.fill_rule(), FillRule::NonZeroWinding);
assert_eq!(sc.dropout_mode(), DropoutMode::None);
}
#[test]
fn test_scan_converter_set_fill_rule() {
let mut sc = ScanConverter::new(64, 64);
sc.set_fill_rule(FillRule::EvenOdd);
assert_eq!(sc.fill_rule(), FillRule::EvenOdd);
}
#[test]
fn test_scan_converter_set_dropout_mode() {
let mut sc = ScanConverter::new(64, 64);
sc.set_dropout_mode(DropoutMode::Simple);
assert_eq!(sc.dropout_mode(), DropoutMode::Simple);
}
#[test]
fn test_scan_converter_move_to() {
let mut sc = ScanConverter::new(64, 64);
sc.move_to(F26Dot6::from_int(10), F26Dot6::from_int(20));
assert_eq!(sc.current_x.to_int(), 10);
assert_eq!(sc.current_y.to_int(), 20);
assert_eq!(sc.contour_start_x.to_int(), 10);
assert_eq!(sc.contour_start_y.to_int(), 20);
}
#[test]
fn test_scan_converter_line_to() {
let mut sc = ScanConverter::new(64, 64);
sc.move_to(F26Dot6::from_int(0), F26Dot6::from_int(0));
sc.line_to(F26Dot6::from_int(10), F26Dot6::from_int(10));
assert_eq!(sc.current_x.to_int(), 10);
assert_eq!(sc.current_y.to_int(), 10);
}
#[test]
fn test_render_simple_rectangle() {
let mut sc = ScanConverter::new(10, 10);
sc.move_to(F26Dot6::from_int(2), F26Dot6::from_int(2));
sc.line_to(F26Dot6::from_int(8), F26Dot6::from_int(2));
sc.line_to(F26Dot6::from_int(8), F26Dot6::from_int(8));
sc.line_to(F26Dot6::from_int(2), F26Dot6::from_int(8));
sc.close();
let mut bitmap = vec![0u8; 100];
sc.render_mono(&mut bitmap);
for x in 2..8 {
assert_eq!(bitmap[4 * 10 + x], 1, "Pixel ({}, 4) should be black", x);
}
assert_eq!(bitmap[4 * 10], 0, "Pixel (0, 4) should be white");
assert_eq!(bitmap[4 * 10 + 9], 0, "Pixel (9, 4) should be white");
}
#[test]
fn test_render_triangle() {
let mut sc = ScanConverter::new(20, 20);
sc.move_to(F26Dot6::from_int(5), F26Dot6::from_int(5));
sc.line_to(F26Dot6::from_int(15), F26Dot6::from_int(5));
sc.line_to(F26Dot6::from_int(10), F26Dot6::from_int(15));
sc.close();
let mut bitmap = vec![0u8; 400];
sc.render_mono(&mut bitmap);
let filled_count: usize = bitmap.iter().filter(|&&p| p == 1).count();
assert!(
filled_count > 20,
"Triangle should have filled pixels (got {})",
filled_count
);
}
#[test]
fn test_even_odd_fill_rule() {
let mut sc = ScanConverter::new(10, 10);
sc.set_fill_rule(FillRule::EvenOdd);
sc.move_to(F26Dot6::from_int(2), F26Dot6::from_int(2));
sc.line_to(F26Dot6::from_int(8), F26Dot6::from_int(2));
sc.line_to(F26Dot6::from_int(8), F26Dot6::from_int(8));
sc.line_to(F26Dot6::from_int(2), F26Dot6::from_int(8));
sc.close();
let mut bitmap = vec![0u8; 100];
sc.render_mono(&mut bitmap);
assert_eq!(bitmap[5 * 10 + 5], 1, "Center should be filled");
}
#[test]
fn test_reset() {
let mut sc = ScanConverter::new(10, 10);
sc.move_to(F26Dot6::from_int(5), F26Dot6::from_int(5));
sc.line_to(F26Dot6::from_int(10), F26Dot6::from_int(10));
sc.reset();
assert_eq!(sc.current_x.to_int(), 0);
assert_eq!(sc.current_y.to_int(), 0);
}
}