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