Skip to main content

agg_rust/
rasterizer_outline.rs

1//! Outline rasterizer.
2//!
3//! Port of `agg_rasterizer_outline.h` — simple wireframe rasterizer that feeds
4//! vertices directly to a renderer (no scanline conversion). Suitable for
5//! previewing paths or drawing non-anti-aliased outlines.
6
7use crate::basics::{is_closed, is_end_poly, is_move_to, is_stop, VertexSource};
8
9// ============================================================================
10// RendererPrimitivesLike trait
11// ============================================================================
12
13/// Trait for renderers that can draw primitive lines.
14///
15/// Matches the subset of `RendererPrimitives` API used by `RasterizerOutline`.
16pub trait RendererPrimitivesLike {
17    type Color: Clone;
18
19    fn coord(c: f64) -> i32;
20    fn move_to(&mut self, x: i32, y: i32);
21    fn line_to(&mut self, x: i32, y: i32, last: bool);
22    fn set_line_color(&mut self, c: Self::Color);
23}
24
25// ============================================================================
26// RasterizerOutline
27// ============================================================================
28
29/// Outline rasterizer.
30///
31/// Reads vertices from a `VertexSource` and feeds them directly to a
32/// primitive renderer. No anti-aliasing or scanline conversion — just
33/// Bresenham lines.
34///
35/// Port of C++ `rasterizer_outline<Renderer>`.
36pub struct RasterizerOutline<'a, Ren: RendererPrimitivesLike> {
37    ren: &'a mut Ren,
38    start_x: i32,
39    start_y: i32,
40    vertices: u32,
41}
42
43impl<'a, Ren: RendererPrimitivesLike> RasterizerOutline<'a, Ren> {
44    pub fn new(ren: &'a mut Ren) -> Self {
45        Self {
46            ren,
47            start_x: 0,
48            start_y: 0,
49            vertices: 0,
50        }
51    }
52
53    /// Move to a new position (subpixel coordinates).
54    pub fn move_to(&mut self, x: i32, y: i32) {
55        self.vertices = 1;
56        self.start_x = x;
57        self.start_y = y;
58        self.ren.move_to(x, y);
59    }
60
61    /// Draw a line to (x, y) (subpixel coordinates).
62    pub fn line_to(&mut self, x: i32, y: i32) {
63        self.vertices += 1;
64        self.ren.line_to(x, y, false);
65    }
66
67    /// Move to a floating-point position (converts to subpixel).
68    pub fn move_to_d(&mut self, x: f64, y: f64) {
69        self.move_to(Ren::coord(x), Ren::coord(y));
70    }
71
72    /// Draw a line to a floating-point position (converts to subpixel).
73    pub fn line_to_d(&mut self, x: f64, y: f64) {
74        self.line_to(Ren::coord(x), Ren::coord(y));
75    }
76
77    /// Close the current polygon by drawing a line back to the start.
78    pub fn close(&mut self) {
79        if self.vertices > 2 {
80            self.line_to(self.start_x, self.start_y);
81        }
82        self.vertices = 0;
83    }
84
85    /// Add a single vertex with a path command.
86    pub fn add_vertex(&mut self, x: f64, y: f64, cmd: u32) {
87        if is_move_to(cmd) {
88            self.move_to_d(x, y);
89        } else if is_end_poly(cmd) {
90            if is_closed(cmd) {
91                self.close();
92            }
93        } else {
94            self.line_to_d(x, y);
95        }
96    }
97
98    /// Add all vertices from a vertex source.
99    pub fn add_path<VS: VertexSource>(&mut self, vs: &mut VS, path_id: u32) {
100        vs.rewind(path_id);
101        let mut x = 0.0;
102        let mut y = 0.0;
103        loop {
104            let cmd = vs.vertex(&mut x, &mut y);
105            if is_stop(cmd) {
106                break;
107            }
108            self.add_vertex(x, y, cmd);
109        }
110    }
111
112    /// Render multiple paths with different colors.
113    pub fn render_all_paths<VS: VertexSource>(
114        &mut self,
115        vs: &mut VS,
116        colors: &[Ren::Color],
117        path_ids: &[u32],
118    ) {
119        for i in 0..colors.len().min(path_ids.len()) {
120            self.ren.set_line_color(colors[i].clone());
121            self.add_path(vs, path_ids[i]);
122        }
123    }
124}
125
126// ============================================================================
127// Tests
128// ============================================================================
129
130#[cfg(test)]
131mod tests {
132    use super::*;
133    use crate::basics::{VertexSource, PATH_CMD_LINE_TO, PATH_CMD_MOVE_TO, PATH_CMD_STOP};
134
135    #[derive(Default)]
136    struct MockRenderer {
137        moves: Vec<(i32, i32)>,
138        lines: Vec<(i32, i32)>,
139    }
140
141    impl RendererPrimitivesLike for MockRenderer {
142        type Color = u32;
143
144        fn coord(c: f64) -> i32 {
145            (c * 256.0) as i32
146        }
147        fn move_to(&mut self, x: i32, y: i32) {
148            self.moves.push((x, y));
149        }
150        fn line_to(&mut self, x: i32, y: i32, _last: bool) {
151            self.lines.push((x, y));
152        }
153        fn set_line_color(&mut self, _c: u32) {}
154    }
155
156    #[test]
157    fn test_move_and_line_to() {
158        let mut ren = MockRenderer::default();
159        let mut ras = RasterizerOutline::new(&mut ren);
160        ras.move_to(10, 20);
161        ras.line_to(30, 40);
162
163        assert_eq!(ren.moves.len(), 1);
164        assert_eq!(ren.moves[0], (10, 20));
165        assert_eq!(ren.lines.len(), 1);
166        assert_eq!(ren.lines[0], (30, 40));
167    }
168
169    #[test]
170    fn test_close() {
171        let mut ren = MockRenderer::default();
172        let mut ras = RasterizerOutline::new(&mut ren);
173        ras.move_to(0, 0);
174        ras.line_to(100, 0);
175        ras.line_to(100, 100);
176        ras.close();
177
178        // Should draw line back to (0, 0)
179        assert_eq!(ren.lines.len(), 3);
180        assert_eq!(ren.lines[2], (0, 0));
181    }
182
183    #[test]
184    fn test_close_with_fewer_than_3_vertices() {
185        let mut ren = MockRenderer::default();
186        let mut ras = RasterizerOutline::new(&mut ren);
187        ras.move_to(0, 0);
188        ras.line_to(100, 0);
189        ras.close();
190
191        // Only 2 vertices — close() should not add a line
192        assert_eq!(ren.lines.len(), 1);
193    }
194
195    #[test]
196    fn test_add_path() {
197        struct TrianglePath {
198            idx: usize,
199        }
200        impl VertexSource for TrianglePath {
201            fn rewind(&mut self, _path_id: u32) {
202                self.idx = 0;
203            }
204            fn vertex(&mut self, x: &mut f64, y: &mut f64) -> u32 {
205                let verts: [(f64, f64, u32); 4] = [
206                    (0.0, 0.0, PATH_CMD_MOVE_TO),
207                    (10.0, 0.0, PATH_CMD_LINE_TO),
208                    (5.0, 10.0, PATH_CMD_LINE_TO),
209                    (0.0, 0.0, PATH_CMD_STOP),
210                ];
211                if self.idx >= verts.len() {
212                    return PATH_CMD_STOP;
213                }
214                let v = verts[self.idx];
215                *x = v.0;
216                *y = v.1;
217                self.idx += 1;
218                v.2
219            }
220        }
221
222        let mut ren = MockRenderer::default();
223        let mut ras = RasterizerOutline::new(&mut ren);
224        let mut path = TrianglePath { idx: 0 };
225        ras.add_path(&mut path, 0);
226
227        assert_eq!(ren.moves.len(), 1);
228        assert_eq!(ren.lines.len(), 2);
229    }
230
231    #[test]
232    fn test_move_to_d() {
233        let mut ren = MockRenderer::default();
234        let mut ras = RasterizerOutline::new(&mut ren);
235        ras.move_to_d(1.5, 2.5);
236        // coord(1.5) = 384, coord(2.5) = 640
237        assert_eq!(ren.moves[0], (384, 640));
238    }
239}