1use crate::framebuffer::Framebuffer;
9use oxiui_core::Color;
10
11#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
13pub enum FillRule {
14 EvenOdd,
17 #[default]
20 NonZero,
21}
22
23#[derive(Clone, Debug)]
30pub(crate) struct Edge {
31 x: f32,
33 dx: f32,
35 y_max: i32,
37 winding: i32,
40}
41
42pub struct RasterizerScratch {
51 pub(crate) global_edges: Vec<(i32, Edge)>,
53 pub(crate) active: Vec<Edge>,
55}
56
57impl RasterizerScratch {
58 pub fn new() -> Self {
60 Self {
61 global_edges: Vec::new(),
62 active: Vec::new(),
63 }
64 }
65
66 pub fn clear(&mut self) {
68 self.global_edges.clear();
69 self.active.clear();
70 }
71}
72
73impl Default for RasterizerScratch {
74 fn default() -> Self {
75 Self::new()
76 }
77}
78
79pub struct Rasterizer {
86 scratch: RasterizerScratch,
87}
88
89impl Rasterizer {
90 pub fn new() -> Self {
92 Self {
93 scratch: RasterizerScratch::new(),
94 }
95 }
96
97 pub fn fill_polygon(
99 &mut self,
100 fb: &mut Framebuffer,
101 points: &[(f32, f32)],
102 color: Color,
103 fill_rule: FillRule,
104 aa: bool,
105 ) {
106 fill_polygon_with_scratch(fb, points, color, fill_rule, aa, &mut self.scratch);
107 }
108
109 pub fn fill_polygon_clipped(
111 &mut self,
112 fb: &mut Framebuffer,
113 points: &[(f32, f32)],
114 color: Color,
115 fill_rule: FillRule,
116 aa: bool,
117 clip: crate::clip::ClipRect,
118 ) {
119 fill_polygon_clipped_with_scratch(
120 fb,
121 points,
122 color,
123 fill_rule,
124 aa,
125 clip,
126 &mut self.scratch,
127 );
128 }
129}
130
131impl Default for Rasterizer {
132 fn default() -> Self {
133 Self::new()
134 }
135}
136
137fn build_edges_into(points: &[(f32, f32)], out: &mut Vec<(i32, Edge)>) {
144 let n = points.len();
145 if n < 2 {
146 return;
147 }
148 out.reserve(n);
149 for i in 0..n {
150 let (x0, y0) = points[i];
151 let (x1, y1) = points[(i + 1) % n];
152 if (y0 - y1).abs() < f32::EPSILON {
153 continue;
155 }
156 let (top_y, bot_y, top_x, bot_x, winding) = if y0 < y1 {
157 (y0, y1, x0, x1, 1i32)
158 } else {
159 (y1, y0, x1, x0, -1i32)
160 };
161 let dy = bot_y - top_y;
162 let dx = (bot_x - top_x) / dy;
163 let y_start = top_y.ceil() as i32;
164 let x_at_start = top_x + dx * (y_start as f32 - top_y);
166 let y_max = bot_y.ceil() as i32;
167 out.push((
168 y_start,
169 Edge {
170 x: x_at_start,
171 dx,
172 y_max,
173 winding,
174 },
175 ));
176 }
177}
178
179#[inline]
185fn span_coverage(left: f32, right: f32, px: f32) -> f32 {
186 let pl = px;
188 let pr = px + 1.0;
189 if right <= pl || left >= pr {
190 return 0.0;
191 }
192 let cl = left.max(pl);
193 let cr = right.min(pr);
194 (cr - cl).clamp(0.0, 1.0)
195}
196
197fn fill_polygon_with_scratch(
199 fb: &mut Framebuffer,
200 points: &[(f32, f32)],
201 color: Color,
202 fill_rule: FillRule,
203 aa: bool,
204 scratch: &mut RasterizerScratch,
205) {
206 if points.len() < 3 {
207 return;
208 }
209
210 let (mut min_y, mut max_y) = (f32::INFINITY, f32::NEG_INFINITY);
212 for &(_, y) in points {
213 if y < min_y {
214 min_y = y;
215 }
216 if y > max_y {
217 max_y = y;
218 }
219 }
220 let y_start = min_y.floor() as i32;
221 let y_end = max_y.ceil() as i32;
222
223 scratch.clear();
225 build_edges_into(points, &mut scratch.global_edges);
226
227 scratch.global_edges.sort_by(|a, b| {
229 a.0.cmp(&b.0).then(
230 a.1.x
231 .partial_cmp(&b.1.x)
232 .unwrap_or(core::cmp::Ordering::Equal),
233 )
234 });
235
236 let mut gi = 0usize;
237
238 for y in y_start..y_end {
239 while gi < scratch.global_edges.len() && scratch.global_edges[gi].0 == y {
241 scratch.active.push(scratch.global_edges[gi].1.clone());
242 gi += 1;
243 }
244
245 scratch.active.retain(|e| e.y_max > y);
247
248 scratch
250 .active
251 .sort_by(|a, b| a.x.partial_cmp(&b.x).unwrap_or(core::cmp::Ordering::Equal));
252
253 fill_spans(
255 fb,
256 &scratch.active,
257 y as u32,
258 y as f32,
259 color,
260 fill_rule,
261 aa,
262 );
263
264 for e in &mut scratch.active {
266 e.x += e.dx;
267 }
268 }
269}
270
271pub fn fill_polygon(
282 fb: &mut Framebuffer,
283 points: &[(f32, f32)],
284 color: Color,
285 fill_rule: FillRule,
286 aa: bool,
287) {
288 let mut scratch = RasterizerScratch::new();
289 fill_polygon_with_scratch(fb, points, color, fill_rule, aa, &mut scratch);
290}
291
292fn fill_spans(
294 fb: &mut Framebuffer,
295 active: &[Edge],
296 y: u32,
297 _y_float: f32,
298 color: Color,
299 fill_rule: FillRule,
300 aa: bool,
301) {
302 if y >= fb.height() || active.is_empty() {
303 return;
304 }
305
306 match fill_rule {
307 FillRule::EvenOdd => fill_even_odd(fb, active, y, color, aa),
308 FillRule::NonZero => fill_non_zero(fb, active, y, color, aa),
309 }
310}
311
312fn fill_even_odd(fb: &mut Framebuffer, active: &[Edge], y: u32, color: Color, aa: bool) {
314 let mut i = 0;
315 while i + 1 < active.len() {
316 let left = active[i].x;
317 let right = active[i + 1].x;
318 if right > left {
319 paint_span(fb, left, right, y, color, aa);
320 }
321 i += 2;
322 }
323}
324
325fn fill_non_zero(fb: &mut Framebuffer, active: &[Edge], y: u32, color: Color, aa: bool) {
327 let mut winding = 0i32;
328 let mut fill_start: Option<f32> = None;
329
330 for edge in active {
331 let prev_winding = winding;
332 winding += edge.winding;
333
334 if prev_winding == 0 && winding != 0 {
335 fill_start = Some(edge.x);
337 } else if prev_winding != 0 && winding == 0 {
338 if let Some(start) = fill_start.take() {
340 let end = edge.x;
341 if end > start {
342 paint_span(fb, start, end, y, color, aa);
343 }
344 }
345 }
346 }
347}
348
349fn paint_span(fb: &mut Framebuffer, left: f32, right: f32, y: u32, color: Color, aa: bool) {
351 if y >= fb.height() {
352 return;
353 }
354 let x0 = left.floor() as i32;
355 let x1 = right.ceil() as i32;
356 let Color(cr, cg, cb, ca) = color;
357
358 for px in x0..x1 {
359 if px < 0 || px as u32 >= fb.width() {
360 continue;
361 }
362 let coverage = if aa {
363 span_coverage(left, right, px as f32)
364 } else {
365 let centre = px as f32 + 0.5;
367 if centre >= left && centre < right {
368 1.0
369 } else {
370 0.0
371 }
372 };
373 if coverage <= 0.0 {
374 continue;
375 }
376 let alpha = (ca as f32 * coverage).round() as u8;
377 fb.blend(
378 px as u32,
379 y,
380 crate::framebuffer::pack_rgba(cr, cg, cb, alpha),
381 );
382 }
383}
384
385fn fill_polygon_clipped_with_scratch(
387 fb: &mut Framebuffer,
388 points: &[(f32, f32)],
389 color: Color,
390 fill_rule: FillRule,
391 aa: bool,
392 clip: crate::clip::ClipRect,
393 scratch: &mut RasterizerScratch,
394) {
395 if points.len() < 3 {
396 return;
397 }
398
399 let (mut min_y, mut max_y) = (f32::INFINITY, f32::NEG_INFINITY);
401 for &(_, y) in points {
402 if y < min_y {
403 min_y = y;
404 }
405 if y > max_y {
406 max_y = y;
407 }
408 }
409 let y_clip_start = (min_y.floor() as i64).max(clip.y0) as i32;
410 let y_end = (max_y.ceil() as i64).min(clip.y1) as i32;
411
412 scratch.clear();
413 build_edges_into(points, &mut scratch.global_edges);
414 scratch.global_edges.sort_by(|a, b| {
415 a.0.cmp(&b.0).then(
416 a.1.x
417 .partial_cmp(&b.1.x)
418 .unwrap_or(core::cmp::Ordering::Equal),
419 )
420 });
421
422 let mut gi = 0usize;
423
424 for y in (min_y.floor() as i32)..y_end {
425 while gi < scratch.global_edges.len() && scratch.global_edges[gi].0 == y {
426 scratch.active.push(scratch.global_edges[gi].1.clone());
427 gi += 1;
428 }
429 scratch.active.retain(|e| e.y_max > y);
430 scratch
431 .active
432 .sort_by(|a, b| a.x.partial_cmp(&b.x).unwrap_or(core::cmp::Ordering::Equal));
433 if y >= y_clip_start {
434 fill_spans_clipped(fb, &scratch.active, y as u32, color, fill_rule, aa, &clip);
435 }
436 for e in &mut scratch.active {
437 e.x += e.dx;
438 }
439 }
440}
441
442pub fn fill_polygon_clipped(
449 fb: &mut Framebuffer,
450 points: &[(f32, f32)],
451 color: Color,
452 fill_rule: FillRule,
453 aa: bool,
454 clip: crate::clip::ClipRect,
455) {
456 let mut scratch = RasterizerScratch::new();
457 fill_polygon_clipped_with_scratch(fb, points, color, fill_rule, aa, clip, &mut scratch);
458}
459
460fn fill_spans_clipped(
462 fb: &mut Framebuffer,
463 active: &[Edge],
464 y: u32,
465 color: Color,
466 fill_rule: FillRule,
467 aa: bool,
468 clip: &crate::clip::ClipRect,
469) {
470 if y >= fb.height() || (y as i64) < clip.y0 || (y as i64) >= clip.y1 || active.is_empty() {
471 return;
472 }
473 match fill_rule {
474 FillRule::EvenOdd => {
475 let mut i = 0;
476 while i + 1 < active.len() {
477 let left = active[i].x.max(clip.x0 as f32);
478 let right = active[i + 1].x.min(clip.x1 as f32);
479 if right > left {
480 paint_span(fb, left, right, y, color, aa);
481 }
482 i += 2;
483 }
484 }
485 FillRule::NonZero => {
486 let mut winding = 0i32;
487 let mut fill_start: Option<f32> = None;
488 for edge in active {
489 let prev_winding = winding;
490 winding += edge.winding;
491 if prev_winding == 0 && winding != 0 {
492 fill_start = Some(edge.x.max(clip.x0 as f32));
493 } else if prev_winding != 0 && winding == 0 {
494 if let Some(start) = fill_start.take() {
495 let end = edge.x.min(clip.x1 as f32);
496 if end > start {
497 paint_span(fb, start, end, y, color, aa);
498 }
499 }
500 }
501 }
502 }
503 }
504}
505
506pub fn fill_triangle(
510 fb: &mut Framebuffer,
511 p0: (f32, f32),
512 p1: (f32, f32),
513 p2: (f32, f32),
514 color: Color,
515) {
516 fill_polygon(fb, &[p0, p1, p2], color, FillRule::NonZero, true);
517}
518
519#[cfg(test)]
524mod tests {
525 use super::*;
526 use crate::framebuffer::Framebuffer;
527
528 fn fresh(w: u32, h: u32) -> Framebuffer {
529 Framebuffer::with_fill(w, h, Color(0, 0, 0, 255))
530 }
531
532 #[test]
533 fn triangle_fill_coverage() {
534 let mut fb = fresh(20, 20);
535 fill_triangle(
537 &mut fb,
538 (0.0, 0.0),
539 (20.0, 0.0),
540 (10.0, 20.0),
541 Color(255, 255, 255, 255),
542 );
543 let mut count = 0u32;
545 for y in 0..20 {
546 for x in 0..20 {
547 let (r, _, _, _) = fb.get_rgba(x, y).unwrap_or((0, 0, 0, 0));
548 if r > 0 {
549 count += 1;
550 }
551 }
552 }
553 assert!(count >= 50, "expected >= 50 filled pixels, got {count}");
555 }
556
557 #[test]
558 fn polygon_fill_even_odd() {
559 let cx = 35.0f32;
564 let cy = 35.0f32;
565 let r = 30.0f32;
566 let star: Vec<(f32, f32)> = (0..5)
567 .map(|i| {
568 let angle = std::f32::consts::PI * (2.0 * (2 * i) as f32 / 5.0 - 0.5);
570 (cx + r * angle.cos(), cy + r * angle.sin())
571 })
572 .collect();
573
574 let mut fb_eo = fresh(70, 70);
575 let mut fb_nz = fresh(70, 70);
576 fill_polygon(
577 &mut fb_eo,
578 &star,
579 Color(255, 0, 0, 255),
580 FillRule::EvenOdd,
581 false,
582 );
583 fill_polygon(
584 &mut fb_nz,
585 &star,
586 Color(255, 0, 0, 255),
587 FillRule::NonZero,
588 false,
589 );
590
591 let (r_eo_tip, _, _, _) = fb_eo.get_rgba(35, 8).unwrap_or((0, 0, 0, 0));
593 let (r_nz_tip, _, _, _) = fb_nz.get_rgba(35, 8).unwrap_or((0, 0, 0, 0));
594 assert!(r_eo_tip > 0, "EvenOdd: star arm should be painted");
595 assert!(r_nz_tip > 0, "NonZero: star arm should be painted");
596
597 let (r_nz_ctr, _, _, _) = fb_nz.get_rgba(35, 35).unwrap_or((0, 0, 0, 0));
599 assert!(r_nz_ctr > 0, "NonZero: star center must be filled");
600 let (r_eo_ctr, _, _, _) = fb_eo.get_rgba(35, 35).unwrap_or((0, 0, 0, 0));
601 assert_eq!(
602 r_eo_ctr, 0,
603 "EvenOdd: star center should be a hole (r={r_eo_ctr})"
604 );
605 }
606
607 #[test]
608 fn fill_rect_via_polygon() {
609 let mut fb = fresh(10, 10);
610 let pts = [(2.0f32, 2.0), (8.0, 2.0), (8.0, 8.0), (2.0, 8.0)];
611 fill_polygon(
612 &mut fb,
613 &pts,
614 Color(0, 255, 0, 255),
615 FillRule::NonZero,
616 false,
617 );
618 assert_eq!(fb.get_rgba(5, 5), Some((0, 255, 0, 255)));
620 assert_eq!(fb.get_rgba(0, 0), Some((0, 0, 0, 255)));
622 }
623
624 #[test]
625 fn fill_rule_noop_on_empty_polygon() {
626 let mut fb = fresh(5, 5);
627 fill_polygon(
628 &mut fb,
629 &[],
630 Color(255, 0, 0, 255),
631 FillRule::NonZero,
632 false,
633 );
634 assert_eq!(fb.get_rgba(2, 2), Some((0, 0, 0, 255)));
636 }
637
638 #[test]
643 fn test_scanline_reuse_output_identical() {
644 let triangle: &[(f32, f32)] = &[(10.0, 90.0), (50.0, 10.0), (90.0, 90.0)];
649 let color = Color(200, 100, 50, 255);
650
651 let mut fb1 = fresh(100, 100);
653 let mut ras = Rasterizer::new();
654 ras.fill_polygon(&mut fb1, triangle, color, FillRule::NonZero, true);
655
656 let mut fb2 = fresh(100, 100);
658 ras.fill_polygon(&mut fb2, triangle, color, FillRule::NonZero, true);
659
660 for y in 0..100 {
662 for x in 0..100 {
663 let p1 = fb1.get_rgba(x, y);
664 let p2 = fb2.get_rgba(x, y);
665 assert_eq!(
666 p1, p2,
667 "pixel ({x},{y}) differs: first={p1:?} second={p2:?}"
668 );
669 }
670 }
671
672 let mut fb3 = fresh(100, 100);
674 fill_polygon(&mut fb3, triangle, color, FillRule::NonZero, true);
675 for y in 0..100 {
676 for x in 0..100 {
677 let p1 = fb1.get_rgba(x, y);
678 let p3 = fb3.get_rgba(x, y);
679 assert_eq!(
680 p1, p3,
681 "pixel ({x},{y}): Rasterizer={p1:?} vs free fn={p3:?}"
682 );
683 }
684 }
685 }
686}