Skip to main content

agg_rust/
rasterizer_outline_aa.rs

1//! Anti-aliased outline rasterizer.
2//!
3//! Port of `agg_rasterizer_outline_aa.h`.
4//! Consumes a vertex source and renders anti-aliased outlines using
5//! the `RendererOutlineAa` renderer.
6//!
7//! Copyright 2025.
8
9use crate::basics::{is_close, is_end_poly, is_move_to, is_stop, VertexSource};
10use crate::line_aa_basics::*;
11use crate::renderer_outline_aa::OutlineAaRenderer;
12
13/// Join type for outline AA lines.
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum OutlineAaJoin {
16    NoJoin,
17    Miter,
18    Round,
19    MiterAccurate,
20}
21
22/// Vertex with distance for AA line rendering.
23/// Port of C++ `line_aa_vertex`.
24#[derive(Debug, Clone, Copy)]
25pub struct LineAaVertex {
26    pub x: i32,
27    pub y: i32,
28    pub len: i32,
29}
30
31impl LineAaVertex {
32    pub fn new(x: i32, y: i32) -> Self {
33        Self { x, y, len: 0 }
34    }
35
36    /// Calculate distance to another vertex and store in self.len.
37    /// Returns true if distance > threshold (i.e., not coincident).
38    /// This is the equivalent of C++ `operator()`.
39    pub fn calc_distance(&mut self, other: &LineAaVertex) -> bool {
40        let dx = (other.x - self.x) as f64;
41        let dy = (other.y - self.y) as f64;
42        self.len = (dx * dx + dy * dy).sqrt().round() as i32;
43        self.len > (LINE_SUBPIXEL_SCALE + LINE_SUBPIXEL_SCALE / 2)
44    }
45}
46
47// ============================================================================
48// Vertex Sequence — mirrors C++ vertex_sequence<line_aa_vertex, 6>
49// ============================================================================
50
51/// A sequence of vertices that automatically removes coincident points.
52/// Port of C++ `vertex_sequence`.
53struct VertexSeq {
54    data: Vec<LineAaVertex>,
55}
56
57impl VertexSeq {
58    fn new() -> Self {
59        Self { data: Vec::new() }
60    }
61
62    fn size(&self) -> usize {
63        self.data.len()
64    }
65
66    fn remove_all(&mut self) {
67        self.data.clear();
68    }
69
70    /// Add a vertex, removing the previous last if it was too close to its predecessor.
71    /// Port of C++ `vertex_sequence::add`.
72    fn add(&mut self, val: LineAaVertex) {
73        if self.data.len() > 1 {
74            let n = self.data.len();
75            let last = self.data[n - 1];
76            if !self.data[n - 2].calc_distance(&last) {
77                self.data.pop();
78            }
79        }
80        self.data.push(val);
81    }
82
83    /// Replace the last element.
84    /// Port of C++ `vertex_sequence::modify_last`.
85    fn modify_last(&mut self, val: LineAaVertex) {
86        if !self.data.is_empty() {
87            self.data.pop();
88        }
89        self.add(val);
90    }
91
92    /// Close the sequence, removing coincident vertices.
93    /// Port of C++ `vertex_sequence::close`.
94    fn close(&mut self, closed: bool) {
95        // First: trim coincident tail vertices
96        while self.data.len() > 1 {
97            let n = self.data.len();
98            let last = self.data[n - 1];
99            if self.data[n - 2].calc_distance(&last) {
100                break;
101            }
102            let t = self.data.pop().unwrap();
103            self.modify_last(t);
104        }
105
106        // If closed: remove last vertex if it coincides with first
107        if closed {
108            while self.data.len() > 1 {
109                let n = self.data.len();
110                let first = self.data[0];
111                if self.data[n - 1].calc_distance(&first) {
112                    break;
113                }
114                self.data.pop();
115            }
116        }
117    }
118
119    fn get(&self, idx: usize) -> &LineAaVertex {
120        &self.data[idx]
121    }
122}
123
124impl std::ops::Index<usize> for VertexSeq {
125    type Output = LineAaVertex;
126    fn index(&self, idx: usize) -> &LineAaVertex {
127        &self.data[idx]
128    }
129}
130
131// ============================================================================
132// Draw Variables — mirrors C++ draw_vars
133// ============================================================================
134
135struct DrawVars {
136    idx: usize,
137    x1: i32,
138    y1: i32,
139    x2: i32,
140    y2: i32,
141    curr: LineParameters,
142    next: LineParameters,
143    lcurr: i32,
144    lnext: i32,
145    xb1: i32,
146    yb1: i32,
147    xb2: i32,
148    yb2: i32,
149    flags: u32,
150}
151
152// ============================================================================
153// RasterizerOutlineAa
154// ============================================================================
155
156/// Anti-aliased outline rasterizer.
157///
158/// Port of C++ `rasterizer_outline_aa<Renderer>`.
159/// Builds polylines from vertex sources, then dispatches to the renderer
160/// for AA line drawing with configurable join types.
161pub struct RasterizerOutlineAa {
162    src_vertices: VertexSeq,
163    line_join: OutlineAaJoin,
164    round_cap: bool,
165    start_x: i32,
166    start_y: i32,
167}
168
169impl RasterizerOutlineAa {
170    pub fn new() -> Self {
171        Self {
172            src_vertices: VertexSeq::new(),
173            line_join: OutlineAaJoin::NoJoin,
174            round_cap: false,
175            start_x: 0,
176            start_y: 0,
177        }
178    }
179
180    pub fn set_line_join(&mut self, join: OutlineAaJoin) {
181        self.line_join = join;
182    }
183
184    pub fn line_join(&self) -> OutlineAaJoin {
185        self.line_join
186    }
187
188    pub fn set_round_cap(&mut self, v: bool) {
189        self.round_cap = v;
190    }
191
192    pub fn round_cap(&self) -> bool {
193        self.round_cap
194    }
195
196    pub fn move_to(&mut self, x: i32, y: i32) {
197        self.start_x = x;
198        self.start_y = y;
199        self.src_vertices.modify_last(LineAaVertex::new(x, y));
200    }
201
202    pub fn line_to(&mut self, x: i32, y: i32) {
203        self.src_vertices.add(LineAaVertex::new(x, y));
204    }
205
206    pub fn move_to_d(&mut self, x: f64, y: f64) {
207        self.move_to(line_coord(x), line_coord(y));
208    }
209
210    pub fn line_to_d(&mut self, x: f64, y: f64) {
211        self.line_to(line_coord(x), line_coord(y));
212    }
213
214    /// Process a single vertex command. Port of C++ `add_vertex`.
215    fn add_vertex<R: OutlineAaRenderer>(
216        &mut self,
217        x: f64,
218        y: f64,
219        cmd: u32,
220        ren: &mut R,
221    ) {
222        if is_move_to(cmd) {
223            self.render(ren, false);
224            self.move_to_d(x, y);
225        } else if is_end_poly(cmd) {
226            self.render(ren, is_close(cmd));
227            if is_close(cmd) {
228                self.move_to(self.start_x, self.start_y);
229            }
230        } else {
231            self.line_to_d(x, y);
232        }
233    }
234
235    /// Add a path from a vertex source and render it.
236    pub fn add_path<VS: VertexSource, R: OutlineAaRenderer>(
237        &mut self,
238        vs: &mut VS,
239        path_id: u32,
240        ren: &mut R,
241    ) {
242        vs.rewind(path_id);
243        let (mut x, mut y) = (0.0, 0.0);
244        loop {
245            let cmd = vs.vertex(&mut x, &mut y);
246            if is_stop(cmd) {
247                break;
248            }
249            self.add_vertex(x, y, cmd, ren);
250        }
251        // C++ has render(false) at the end to flush any remaining open polyline
252        self.render(ren, false);
253    }
254
255    // ========================================================================
256    // draw() — Port of C++ draw(draw_vars&, unsigned start, unsigned end)
257    // ========================================================================
258
259    fn draw<R: OutlineAaRenderer>(
260        &self,
261        dv: &mut DrawVars,
262        start: usize,
263        end: usize,
264        ren: &mut R,
265    ) {
266        for _i in start..end {
267            if self.line_join == OutlineAaJoin::Round {
268                dv.xb1 = dv.curr.x1 + (dv.curr.y2 - dv.curr.y1);
269                dv.yb1 = dv.curr.y1 - (dv.curr.x2 - dv.curr.x1);
270                dv.xb2 = dv.curr.x2 + (dv.curr.y2 - dv.curr.y1);
271                dv.yb2 = dv.curr.y2 - (dv.curr.x2 - dv.curr.x1);
272            }
273
274            match dv.flags {
275                0 => ren.line3(&dv.curr, dv.xb1, dv.yb1, dv.xb2, dv.yb2),
276                1 => ren.line2(&dv.curr, dv.xb2, dv.yb2),
277                2 => ren.line1(&dv.curr, dv.xb1, dv.yb1),
278                _ => ren.line0(&dv.curr),
279            }
280
281            if self.line_join == OutlineAaJoin::Round && (dv.flags & 2) == 0 {
282                ren.pie(
283                    dv.curr.x2,
284                    dv.curr.y2,
285                    dv.curr.x2 + (dv.curr.y2 - dv.curr.y1),
286                    dv.curr.y2 - (dv.curr.x2 - dv.curr.x1),
287                    dv.curr.x2 + (dv.next.y2 - dv.next.y1),
288                    dv.curr.y2 - (dv.next.x2 - dv.next.x1),
289                );
290            }
291
292            dv.x1 = dv.x2;
293            dv.y1 = dv.y2;
294            dv.lcurr = dv.lnext;
295            dv.lnext = self.src_vertices[dv.idx].len;
296
297            dv.idx += 1;
298            if dv.idx >= self.src_vertices.size() {
299                dv.idx = 0;
300            }
301
302            let v = self.src_vertices.get(dv.idx);
303            dv.x2 = v.x;
304            dv.y2 = v.y;
305
306            dv.curr = dv.next;
307            dv.next = LineParameters::new(dv.x1, dv.y1, dv.x2, dv.y2, dv.lnext);
308            dv.xb1 = dv.xb2;
309            dv.yb1 = dv.yb2;
310
311            match self.line_join {
312                OutlineAaJoin::NoJoin => {
313                    dv.flags = 3;
314                }
315                OutlineAaJoin::Miter => {
316                    dv.flags >>= 1;
317                    dv.flags |= if dv.curr.diagonal_quadrant() == dv.next.diagonal_quadrant() {
318                        2
319                    } else {
320                        0
321                    };
322                    if (dv.flags & 2) == 0 {
323                        bisectrix(&dv.curr, &dv.next, &mut dv.xb2, &mut dv.yb2);
324                    }
325                }
326                OutlineAaJoin::Round => {
327                    dv.flags >>= 1;
328                    dv.flags |= if dv.curr.diagonal_quadrant() == dv.next.diagonal_quadrant() {
329                        2
330                    } else {
331                        0
332                    };
333                }
334                OutlineAaJoin::MiterAccurate => {
335                    dv.flags = 0;
336                    bisectrix(&dv.curr, &dv.next, &mut dv.xb2, &mut dv.yb2);
337                }
338            }
339        }
340    }
341
342    // ========================================================================
343    // render() — Port of C++ render(bool close_polygon)
344    // ========================================================================
345
346    pub fn render<R: OutlineAaRenderer>(
347        &mut self,
348        ren: &mut R,
349        close_polygon: bool,
350    ) {
351        self.src_vertices.close(close_polygon);
352
353        // Match C++ behavior: when the renderer only supports accurate joins
354        // (e.g. image pattern renderer), override the join type to MiterAccurate.
355        // In C++, this is done in the constructor; here we do it per-call since
356        // the Rust rasterizer is not parameterized by renderer type.
357        let saved_join = self.line_join;
358        if ren.accurate_join_only() {
359            self.line_join = OutlineAaJoin::MiterAccurate;
360        }
361
362        if close_polygon {
363            // ------- Closed polygon -------
364            if self.src_vertices.size() >= 3 {
365                let mut dv = DrawVars {
366                    idx: 2,
367                    x1: 0, y1: 0, x2: 0, y2: 0,
368                    curr: LineParameters::new(0, 0, 1, 0, 1), // placeholder
369                    next: LineParameters::new(0, 0, 1, 0, 1),
370                    lcurr: 0, lnext: 0,
371                    xb1: 0, yb1: 0, xb2: 0, yb2: 0,
372                    flags: 0,
373                };
374
375                let n = self.src_vertices.size();
376
377                let v_last = self.src_vertices[n - 1];
378                let x1 = v_last.x;
379                let y1 = v_last.y;
380                let lprev = v_last.len;
381
382                let v0 = self.src_vertices[0];
383                let x2 = v0.x;
384                let y2 = v0.y;
385                dv.lcurr = v0.len;
386                let prev = LineParameters::new(x1, y1, x2, y2, lprev);
387
388                let v1 = self.src_vertices[1];
389                dv.x1 = v1.x;
390                dv.y1 = v1.y;
391                dv.lnext = v1.len;
392                dv.curr = LineParameters::new(x2, y2, dv.x1, dv.y1, dv.lcurr);
393
394                let v2 = self.src_vertices[dv.idx];
395                dv.x2 = v2.x;
396                dv.y2 = v2.y;
397                dv.next = LineParameters::new(dv.x1, dv.y1, dv.x2, dv.y2, dv.lnext);
398
399                match self.line_join {
400                    OutlineAaJoin::NoJoin => {
401                        dv.flags = 3;
402                    }
403                    OutlineAaJoin::Miter | OutlineAaJoin::Round => {
404                        let f1 = if prev.diagonal_quadrant() == dv.curr.diagonal_quadrant() { 1 } else { 0 };
405                        let f2 = if dv.curr.diagonal_quadrant() == dv.next.diagonal_quadrant() { 2 } else { 0 };
406                        dv.flags = f1 | f2;
407                    }
408                    OutlineAaJoin::MiterAccurate => {
409                        dv.flags = 0;
410                    }
411                }
412
413                if (dv.flags & 1) == 0 && self.line_join != OutlineAaJoin::Round {
414                    bisectrix(&prev, &dv.curr, &mut dv.xb1, &mut dv.yb1);
415                }
416                if (dv.flags & 2) == 0 && self.line_join != OutlineAaJoin::Round {
417                    bisectrix(&dv.curr, &dv.next, &mut dv.xb2, &mut dv.yb2);
418                }
419
420                self.draw(&mut dv, 0, n, ren);
421            }
422        } else {
423            // ------- Open polyline -------
424            let n = self.src_vertices.size();
425
426            match n {
427                0 | 1 => {} // nothing to draw
428                2 => {
429                    let v0 = self.src_vertices[0];
430                    let x1 = v0.x;
431                    let y1 = v0.y;
432                    let lprev = v0.len;
433                    let v1 = self.src_vertices[1];
434                    let x2 = v1.x;
435                    let y2 = v1.y;
436                    let lp = LineParameters::new(x1, y1, x2, y2, lprev);
437
438                    if self.round_cap {
439                        ren.semidot(
440                            cmp_dist_start,
441                            x1, y1,
442                            x1 + (y2 - y1), y1 - (x2 - x1),
443                        );
444                    }
445                    ren.line3(
446                        &lp,
447                        x1 + (y2 - y1), y1 - (x2 - x1),
448                        x2 + (y2 - y1), y2 - (x2 - x1),
449                    );
450                    if self.round_cap {
451                        ren.semidot(
452                            cmp_dist_end,
453                            x2, y2,
454                            x2 + (y2 - y1), y2 - (x2 - x1),
455                        );
456                    }
457                }
458                3 => {
459                    let v0 = self.src_vertices[0];
460                    let x1 = v0.x;
461                    let y1 = v0.y;
462                    let lprev = v0.len;
463                    let v1 = self.src_vertices[1];
464                    let x2 = v1.x;
465                    let y2 = v1.y;
466                    let lnext = v1.len;
467                    let v2 = self.src_vertices[2];
468                    let x3 = v2.x;
469                    let y3 = v2.y;
470                    let lp1 = LineParameters::new(x1, y1, x2, y2, lprev);
471                    let lp2 = LineParameters::new(x2, y2, x3, y3, lnext);
472
473                    if self.round_cap {
474                        ren.semidot(
475                            cmp_dist_start,
476                            x1, y1,
477                            x1 + (y2 - y1), y1 - (x2 - x1),
478                        );
479                    }
480
481                    if self.line_join == OutlineAaJoin::Round {
482                        ren.line3(
483                            &lp1,
484                            x1 + (y2 - y1), y1 - (x2 - x1),
485                            x2 + (y2 - y1), y2 - (x2 - x1),
486                        );
487                        ren.pie(
488                            x2, y2,
489                            x2 + (y2 - y1), y2 - (x2 - x1),
490                            x2 + (y3 - y2), y2 - (x3 - x2),
491                        );
492                        ren.line3(
493                            &lp2,
494                            x2 + (y3 - y2), y2 - (x3 - x2),
495                            x3 + (y3 - y2), y3 - (x3 - x2),
496                        );
497                    } else {
498                        let (mut xb1, mut yb1) = (0i32, 0i32);
499                        bisectrix(&lp1, &lp2, &mut xb1, &mut yb1);
500                        ren.line3(
501                            &lp1,
502                            x1 + (y2 - y1), y1 - (x2 - x1),
503                            xb1, yb1,
504                        );
505                        ren.line3(
506                            &lp2,
507                            xb1, yb1,
508                            x3 + (y3 - y2), y3 - (x3 - x2),
509                        );
510                    }
511
512                    if self.round_cap {
513                        ren.semidot(
514                            cmp_dist_end,
515                            x3, y3,
516                            x3 + (y3 - y2), y3 - (x3 - x2),
517                        );
518                    }
519                }
520                _ => {
521                    // General case: 4+ vertices, open polyline
522                    let mut dv = DrawVars {
523                        idx: 3,
524                        x1: 0, y1: 0, x2: 0, y2: 0,
525                        curr: LineParameters::new(0, 0, 1, 0, 1),
526                        next: LineParameters::new(0, 0, 1, 0, 1),
527                        lcurr: 0, lnext: 0,
528                        xb1: 0, yb1: 0, xb2: 0, yb2: 0,
529                        flags: 0,
530                    };
531
532                    let v0 = self.src_vertices[0];
533                    let x1 = v0.x;
534                    let y1 = v0.y;
535                    let lprev = v0.len;
536
537                    let v1 = self.src_vertices[1];
538                    let x2 = v1.x;
539                    let y2 = v1.y;
540                    dv.lcurr = v1.len;
541                    let prev = LineParameters::new(x1, y1, x2, y2, lprev);
542
543                    let v2 = self.src_vertices[2];
544                    dv.x1 = v2.x;
545                    dv.y1 = v2.y;
546                    dv.lnext = v2.len;
547                    dv.curr = LineParameters::new(x2, y2, dv.x1, dv.y1, dv.lcurr);
548
549                    let v3 = self.src_vertices[dv.idx];
550                    dv.x2 = v3.x;
551                    dv.y2 = v3.y;
552                    dv.next = LineParameters::new(dv.x1, dv.y1, dv.x2, dv.y2, dv.lnext);
553
554                    match self.line_join {
555                        OutlineAaJoin::NoJoin => {
556                            dv.flags = 3;
557                        }
558                        OutlineAaJoin::Miter | OutlineAaJoin::Round => {
559                            let f1 = if prev.diagonal_quadrant() == dv.curr.diagonal_quadrant() { 1 } else { 0 };
560                            let f2 = if dv.curr.diagonal_quadrant() == dv.next.diagonal_quadrant() { 2 } else { 0 };
561                            dv.flags = f1 | f2;
562                        }
563                        OutlineAaJoin::MiterAccurate => {
564                            dv.flags = 0;
565                        }
566                    }
567
568                    // Start cap
569                    if self.round_cap {
570                        ren.semidot(
571                            cmp_dist_start,
572                            x1, y1,
573                            x1 + (y2 - y1), y1 - (x2 - x1),
574                        );
575                    }
576
577                    // First segment
578                    if (dv.flags & 1) == 0 {
579                        if self.line_join == OutlineAaJoin::Round {
580                            ren.line3(
581                                &prev,
582                                x1 + (y2 - y1), y1 - (x2 - x1),
583                                x2 + (y2 - y1), y2 - (x2 - x1),
584                            );
585                            ren.pie(
586                                prev.x2, prev.y2,
587                                x2 + (y2 - y1), y2 - (x2 - x1),
588                                dv.curr.x1 + (dv.curr.y2 - dv.curr.y1),
589                                dv.curr.y1 - (dv.curr.x2 - dv.curr.x1),
590                            );
591                        } else {
592                            bisectrix(&prev, &dv.curr, &mut dv.xb1, &mut dv.yb1);
593                            ren.line3(
594                                &prev,
595                                x1 + (y2 - y1), y1 - (x2 - x1),
596                                dv.xb1, dv.yb1,
597                            );
598                        }
599                    } else {
600                        ren.line1(
601                            &prev,
602                            x1 + (y2 - y1), y1 - (x2 - x1),
603                        );
604                    }
605
606                    if (dv.flags & 2) == 0 && self.line_join != OutlineAaJoin::Round {
607                        bisectrix(&dv.curr, &dv.next, &mut dv.xb2, &mut dv.yb2);
608                    }
609
610                    // Middle segments
611                    self.draw(&mut dv, 1, n - 2, ren);
612
613                    // Last segment
614                    if (dv.flags & 1) == 0 {
615                        if self.line_join == OutlineAaJoin::Round {
616                            ren.line3(
617                                &dv.curr,
618                                dv.curr.x1 + (dv.curr.y2 - dv.curr.y1),
619                                dv.curr.y1 - (dv.curr.x2 - dv.curr.x1),
620                                dv.curr.x2 + (dv.curr.y2 - dv.curr.y1),
621                                dv.curr.y2 - (dv.curr.x2 - dv.curr.x1),
622                            );
623                        } else {
624                            ren.line3(
625                                &dv.curr,
626                                dv.xb1, dv.yb1,
627                                dv.curr.x2 + (dv.curr.y2 - dv.curr.y1),
628                                dv.curr.y2 - (dv.curr.x2 - dv.curr.x1),
629                            );
630                        }
631                    } else {
632                        ren.line2(
633                            &dv.curr,
634                            dv.curr.x2 + (dv.curr.y2 - dv.curr.y1),
635                            dv.curr.y2 - (dv.curr.x2 - dv.curr.x1),
636                        );
637                    }
638
639                    // End cap
640                    if self.round_cap {
641                        ren.semidot(
642                            cmp_dist_end,
643                            dv.curr.x2, dv.curr.y2,
644                            dv.curr.x2 + (dv.curr.y2 - dv.curr.y1),
645                            dv.curr.y2 - (dv.curr.x2 - dv.curr.x1),
646                        );
647                    }
648                }
649            }
650        }
651        self.src_vertices.remove_all();
652        self.line_join = saved_join;
653    }
654}
655
656impl Default for RasterizerOutlineAa {
657    fn default() -> Self {
658        Self::new()
659    }
660}
661
662/// Comparison function for start caps.
663/// Port of C++ `cmp_dist_start` — returns true when d > 0.
664fn cmp_dist_start(dist: i32) -> bool {
665    dist > 0
666}
667
668/// Comparison function for end caps.
669/// Port of C++ `cmp_dist_end` — returns true when d <= 0.
670fn cmp_dist_end(dist: i32) -> bool {
671    dist <= 0
672}
673
674
675#[cfg(test)]
676mod tests {
677    use super::*;
678    use crate::color::Rgba8;
679    use crate::pixfmt_rgba::PixfmtRgba32;
680    use crate::renderer_base::RendererBase;
681    use crate::renderer_outline_aa::{LineProfileAa, RendererOutlineAa};
682    use crate::rendering_buffer::RowAccessor;
683
684    fn make_buffer(w: u32, h: u32) -> (Vec<u8>, RowAccessor) {
685        let stride = (w * 4) as i32;
686        let buf = vec![0u8; (h * w * 4) as usize];
687        let mut ra = RowAccessor::new();
688        unsafe {
689            ra.attach(buf.as_ptr() as *mut u8, w, h, stride);
690        }
691        (buf, ra)
692    }
693
694    #[test]
695    fn test_rasterizer_creation() {
696        let ras = RasterizerOutlineAa::new();
697        assert_eq!(ras.line_join(), OutlineAaJoin::NoJoin);
698        assert!(!ras.round_cap());
699    }
700
701    fn scan_for_color(ren_aa: &RendererOutlineAa<PixfmtRgba32>, cx: i32, cy: i32, radius: i32, channel: &str) -> bool {
702        for y in (cy - radius)..=(cy + radius) {
703            for x in (cx - radius)..=(cx + radius) {
704                if x < 0 || y < 0 { continue; }
705                let p = ren_aa.ren().pixel(x, y);
706                match channel {
707                    "r" => { if p.r > 0 { return true; } }
708                    "g" => { if p.g > 0 { return true; } }
709                    "b" => { if p.b > 0 { return true; } }
710                    _ => {}
711                }
712            }
713        }
714        false
715    }
716
717    #[test]
718    fn test_rasterizer_two_points() {
719        let (_buf, mut ra) = make_buffer(100, 100);
720        let pixf = PixfmtRgba32::new(&mut ra);
721        let mut ren = RendererBase::new(pixf);
722        let prof = LineProfileAa::with_width(2.0);
723        let mut ren_aa = RendererOutlineAa::new(&mut ren, &prof);
724        ren_aa.set_color(Rgba8::new(255, 0, 0, 255));
725
726        let mut ras = RasterizerOutlineAa::new();
727        ras.move_to_d(10.0, 50.0);
728        ras.line_to_d(90.0, 50.0);
729        ras.render(&mut ren_aa, false);
730
731        assert!(scan_for_color(&ren_aa, 50, 50, 2, "r"), "Expected red pixels near (50,50)");
732    }
733
734    #[test]
735    fn test_rasterizer_polyline() {
736        let (_buf, mut ra) = make_buffer(100, 100);
737        let pixf = PixfmtRgba32::new(&mut ra);
738        let mut ren = RendererBase::new(pixf);
739        let prof = LineProfileAa::with_width(1.5);
740        let mut ren_aa = RendererOutlineAa::new(&mut ren, &prof);
741        ren_aa.set_color(Rgba8::new(0, 255, 0, 255));
742
743        let mut ras = RasterizerOutlineAa::new();
744        ras.set_line_join(OutlineAaJoin::Miter);
745        ras.move_to_d(10.0, 10.0);
746        ras.line_to_d(50.0, 50.0);
747        ras.line_to_d(90.0, 10.0);
748        ras.render(&mut ren_aa, false);
749
750        assert!(scan_for_color(&ren_aa, 50, 50, 2, "g"), "Expected green pixels near (50,50)");
751    }
752
753    #[test]
754    fn test_rasterizer_closed_polygon() {
755        let (_buf, mut ra) = make_buffer(100, 100);
756        let pixf = PixfmtRgba32::new(&mut ra);
757        let mut ren = RendererBase::new(pixf);
758        let prof = LineProfileAa::with_width(1.0);
759        let mut ren_aa = RendererOutlineAa::new(&mut ren, &prof);
760        ren_aa.set_color(Rgba8::new(0, 0, 255, 255));
761
762        let mut ras = RasterizerOutlineAa::new();
763        ras.move_to_d(20.0, 20.0);
764        ras.line_to_d(80.0, 20.0);
765        ras.line_to_d(80.0, 80.0);
766        ras.line_to_d(20.0, 80.0);
767        ras.render(&mut ren_aa, true);
768
769        assert!(scan_for_color(&ren_aa, 50, 20, 2, "b"), "Expected blue pixels near (50,20)");
770    }
771}