Skip to main content

agg_rust/
renderer_primitives.rs

1//! Basic shape primitives renderer.
2//!
3//! Port of `agg_renderer_primitives.h` — draws simple shapes (rectangles,
4//! ellipses, lines) directly into a renderer without anti-aliasing.
5
6use crate::basics::{iround, COVER_FULL};
7use crate::dda_line::{self, LineBresenhamInterpolator};
8use crate::ellipse_bresenham::EllipseBresenhamInterpolator;
9use crate::pixfmt_rgba::PixelFormat;
10use crate::renderer_base::RendererBase;
11
12// ============================================================================
13// RendererPrimitives
14// ============================================================================
15
16/// Basic shape primitives renderer.
17///
18/// Draws rectangles, ellipses, and Bresenham lines directly into a
19/// `RendererBase` without anti-aliasing. Maintains separate fill and line
20/// colors and a current position for line_to operations.
21///
22/// Port of C++ `renderer_primitives<BaseRenderer>`.
23pub struct RendererPrimitives<'a, PF: PixelFormat> {
24    ren: &'a mut RendererBase<PF>,
25    fill_color: PF::ColorType,
26    line_color: PF::ColorType,
27    curr_x: i32,
28    curr_y: i32,
29}
30
31impl<'a, PF: PixelFormat> RendererPrimitives<'a, PF>
32where
33    PF::ColorType: Default + Clone,
34{
35    pub fn new(ren: &'a mut RendererBase<PF>) -> Self {
36        Self {
37            ren,
38            fill_color: PF::ColorType::default(),
39            line_color: PF::ColorType::default(),
40            curr_x: 0,
41            curr_y: 0,
42        }
43    }
44
45    /// Convert a floating-point coordinate to subpixel Bresenham coordinate.
46    pub fn coord(c: f64) -> i32 {
47        iround(c * dda_line::SUBPIXEL_SCALE as f64)
48    }
49
50    pub fn set_fill_color(&mut self, c: PF::ColorType) {
51        self.fill_color = c;
52    }
53
54    pub fn set_line_color(&mut self, c: PF::ColorType) {
55        self.line_color = c;
56    }
57
58    pub fn fill_color(&self) -> &PF::ColorType {
59        &self.fill_color
60    }
61
62    pub fn line_color(&self) -> &PF::ColorType {
63        &self.line_color
64    }
65
66    /// Draw an outlined rectangle (line color only).
67    pub fn rectangle(&mut self, x1: i32, y1: i32, x2: i32, y2: i32) {
68        self.ren
69            .blend_hline(x1, y1, x2 - 1, &self.line_color.clone(), COVER_FULL);
70        self.ren
71            .blend_vline(x2, y1, y2 - 1, &self.line_color.clone(), COVER_FULL);
72        self.ren
73            .blend_hline(x1 + 1, y2, x2, &self.line_color.clone(), COVER_FULL);
74        self.ren
75            .blend_vline(x1, y1 + 1, y2, &self.line_color.clone(), COVER_FULL);
76    }
77
78    /// Draw a solid filled rectangle (fill color only).
79    pub fn solid_rectangle(&mut self, x1: i32, y1: i32, x2: i32, y2: i32) {
80        self.ren
81            .blend_bar(x1, y1, x2, y2, &self.fill_color.clone(), COVER_FULL);
82    }
83
84    /// Draw an outlined and filled rectangle.
85    pub fn outlined_rectangle(&mut self, x1: i32, y1: i32, x2: i32, y2: i32) {
86        self.rectangle(x1, y1, x2, y2);
87        self.ren.blend_bar(
88            x1 + 1,
89            y1 + 1,
90            x2 - 1,
91            y2 - 1,
92            &self.fill_color.clone(),
93            COVER_FULL,
94        );
95    }
96
97    /// Draw an outlined ellipse (line color only).
98    pub fn ellipse(&mut self, x: i32, y: i32, rx: i32, ry: i32) {
99        let mut ei = EllipseBresenhamInterpolator::new(rx, ry);
100        let mut dx = 0i32;
101        let mut dy = -ry;
102
103        loop {
104            ei.next();
105            dx += ei.dx();
106            dy += ei.dy();
107            let lc = self.line_color.clone();
108            self.ren.blend_pixel(x + dx, y + dy, &lc, COVER_FULL);
109            self.ren.blend_pixel(x + dx, y - dy, &lc, COVER_FULL);
110            self.ren.blend_pixel(x - dx, y - dy, &lc, COVER_FULL);
111            self.ren.blend_pixel(x - dx, y + dy, &lc, COVER_FULL);
112            if dy >= 0 {
113                break;
114            }
115        }
116    }
117
118    /// Draw a solid filled ellipse (fill color only).
119    pub fn solid_ellipse(&mut self, x: i32, y: i32, rx: i32, ry: i32) {
120        let mut ei = EllipseBresenhamInterpolator::new(rx, ry);
121        let mut dx = 0i32;
122        let mut dy = -ry;
123        let mut dy0 = dy;
124        let mut dx0 = dx;
125
126        loop {
127            ei.next();
128            dx += ei.dx();
129            dy += ei.dy();
130
131            if dy != dy0 {
132                let fc = self.fill_color.clone();
133                self.ren
134                    .blend_hline(x - dx0, y + dy0, x + dx0, &fc, COVER_FULL);
135                self.ren
136                    .blend_hline(x - dx0, y - dy0, x + dx0, &fc, COVER_FULL);
137            }
138            dx0 = dx;
139            dy0 = dy;
140            if dy >= 0 {
141                break;
142            }
143        }
144        let fc = self.fill_color.clone();
145        self.ren
146            .blend_hline(x - dx0, y + dy0, x + dx0, &fc, COVER_FULL);
147    }
148
149    /// Draw an outlined and filled ellipse.
150    pub fn outlined_ellipse(&mut self, x: i32, y: i32, rx: i32, ry: i32) {
151        let mut ei = EllipseBresenhamInterpolator::new(rx, ry);
152        let mut dx = 0i32;
153        let mut dy = -ry;
154
155        loop {
156            ei.next();
157            dx += ei.dx();
158            dy += ei.dy();
159
160            let lc = self.line_color.clone();
161            self.ren.blend_pixel(x + dx, y + dy, &lc, COVER_FULL);
162            self.ren.blend_pixel(x + dx, y - dy, &lc, COVER_FULL);
163            self.ren.blend_pixel(x - dx, y - dy, &lc, COVER_FULL);
164            self.ren.blend_pixel(x - dx, y + dy, &lc, COVER_FULL);
165
166            if ei.dy() != 0 && dx != 0 {
167                let fc = self.fill_color.clone();
168                self.ren
169                    .blend_hline(x - dx + 1, y + dy, x + dx - 1, &fc, COVER_FULL);
170                self.ren
171                    .blend_hline(x - dx + 1, y - dy, x + dx - 1, &fc, COVER_FULL);
172            }
173            if dy >= 0 {
174                break;
175            }
176        }
177    }
178
179    /// Draw a Bresenham line from (x1,y1) to (x2,y2).
180    ///
181    /// Coordinates are in subpixel units (use `coord()` to convert).
182    pub fn line(&mut self, x1: i32, y1: i32, x2: i32, y2: i32, last: bool) {
183        let mut li = LineBresenhamInterpolator::new(x1, y1, x2, y2);
184
185        let mut len = li.len();
186        if len == 0 {
187            if last {
188                let lc = self.line_color.clone();
189                self.ren.blend_pixel(
190                    dda_line::line_lr(x1),
191                    dda_line::line_lr(y1),
192                    &lc,
193                    COVER_FULL,
194                );
195            }
196            return;
197        }
198
199        if last {
200            len += 1;
201        }
202
203        if li.is_ver() {
204            for _ in 0..len {
205                let lc = self.line_color.clone();
206                self.ren.blend_pixel(li.x2(), li.y1(), &lc, COVER_FULL);
207                li.vstep();
208            }
209        } else {
210            for _ in 0..len {
211                let lc = self.line_color.clone();
212                self.ren.blend_pixel(li.x1(), li.y2(), &lc, COVER_FULL);
213                li.hstep();
214            }
215        }
216    }
217
218    /// Set the current position for line_to.
219    pub fn move_to(&mut self, x: i32, y: i32) {
220        self.curr_x = x;
221        self.curr_y = y;
222    }
223
224    /// Draw a line from the current position to (x, y).
225    pub fn line_to(&mut self, x: i32, y: i32, last: bool) {
226        self.line(self.curr_x, self.curr_y, x, y, last);
227        self.curr_x = x;
228        self.curr_y = y;
229    }
230
231    pub fn ren(&self) -> &RendererBase<PF> {
232        self.ren
233    }
234
235    pub fn ren_mut(&mut self) -> &mut RendererBase<PF> {
236        self.ren
237    }
238}
239
240impl<'a, PF: PixelFormat> crate::rasterizer_outline::RendererPrimitivesLike
241    for RendererPrimitives<'a, PF>
242where
243    PF::ColorType: Default + Clone,
244{
245    type Color = PF::ColorType;
246
247    fn coord(c: f64) -> i32 {
248        Self::coord(c)
249    }
250
251    fn move_to(&mut self, x: i32, y: i32) {
252        self.move_to(x, y);
253    }
254
255    fn line_to(&mut self, x: i32, y: i32, last: bool) {
256        self.line_to(x, y, last);
257    }
258
259    fn set_line_color(&mut self, c: Self::Color) {
260        self.set_line_color(c);
261    }
262}
263
264// ============================================================================
265// Tests
266// ============================================================================
267
268#[cfg(test)]
269mod tests {
270    use super::*;
271    use crate::color::Rgba8;
272    use crate::pixfmt_rgba::PixfmtRgba32;
273    use crate::rendering_buffer::RowAccessor;
274
275    fn make_ren(w: u32, h: u32) -> (Vec<u8>, RowAccessor) {
276        let stride = (w * 4) as i32;
277        let buf = vec![0u8; (w * h * 4) as usize];
278        let mut ra = RowAccessor::new();
279        unsafe { ra.attach(buf.as_ptr() as *mut u8, w, h, stride) };
280        (buf, ra)
281    }
282
283    #[test]
284    fn test_rectangle() {
285        let (_buf, mut ra) = make_ren(20, 20);
286        let pf = PixfmtRgba32::new(&mut ra);
287        let mut rb = RendererBase::new(pf);
288        let mut prim = RendererPrimitives::new(&mut rb);
289        prim.set_line_color(Rgba8::new(255, 0, 0, 255));
290        prim.rectangle(2, 2, 8, 8);
291    }
292
293    #[test]
294    fn test_solid_rectangle() {
295        let (_buf, mut ra) = make_ren(20, 20);
296        let pf = PixfmtRgba32::new(&mut ra);
297        let mut rb = RendererBase::new(pf);
298        let mut prim = RendererPrimitives::new(&mut rb);
299        prim.set_fill_color(Rgba8::new(0, 255, 0, 255));
300        prim.solid_rectangle(3, 3, 6, 6);
301    }
302
303    #[test]
304    fn test_ellipse() {
305        let (_buf, mut ra) = make_ren(30, 30);
306        let pf = PixfmtRgba32::new(&mut ra);
307        let mut rb = RendererBase::new(pf);
308        let mut prim = RendererPrimitives::new(&mut rb);
309        prim.set_line_color(Rgba8::new(0, 0, 255, 255));
310        prim.ellipse(15, 15, 5, 5);
311    }
312
313    #[test]
314    fn test_solid_ellipse() {
315        let (_buf, mut ra) = make_ren(30, 30);
316        let pf = PixfmtRgba32::new(&mut ra);
317        let mut rb = RendererBase::new(pf);
318        let mut prim = RendererPrimitives::new(&mut rb);
319        prim.set_fill_color(Rgba8::new(255, 255, 0, 255));
320        prim.solid_ellipse(15, 15, 5, 5);
321    }
322
323    #[test]
324    fn test_line() {
325        let (_buf, mut ra) = make_ren(20, 20);
326        let pf = PixfmtRgba32::new(&mut ra);
327        let mut rb = RendererBase::new(pf);
328        let mut prim = RendererPrimitives::new(&mut rb);
329        prim.set_line_color(Rgba8::new(255, 0, 0, 255));
330        let x1 = RendererPrimitives::<PixfmtRgba32>::coord(2.0);
331        let y1 = RendererPrimitives::<PixfmtRgba32>::coord(5.0);
332        let x2 = RendererPrimitives::<PixfmtRgba32>::coord(10.0);
333        let y2 = RendererPrimitives::<PixfmtRgba32>::coord(5.0);
334        prim.line(x1, y1, x2, y2, true);
335    }
336
337    #[test]
338    fn test_move_to_line_to() {
339        let (_buf, mut ra) = make_ren(20, 20);
340        let pf = PixfmtRgba32::new(&mut ra);
341        let mut rb = RendererBase::new(pf);
342        let mut prim = RendererPrimitives::new(&mut rb);
343        prim.set_line_color(Rgba8::new(255, 255, 255, 255));
344        let x1 = RendererPrimitives::<PixfmtRgba32>::coord(1.0);
345        let y1 = RendererPrimitives::<PixfmtRgba32>::coord(1.0);
346        prim.move_to(x1, y1);
347        let x2 = RendererPrimitives::<PixfmtRgba32>::coord(10.0);
348        let y2 = RendererPrimitives::<PixfmtRgba32>::coord(1.0);
349        prim.line_to(x2, y2, true);
350    }
351
352    #[test]
353    fn test_color_accessors() {
354        let (_buf, mut ra) = make_ren(10, 10);
355        let pf = PixfmtRgba32::new(&mut ra);
356        let mut rb = RendererBase::new(pf);
357        let mut prim = RendererPrimitives::new(&mut rb);
358        prim.set_fill_color(Rgba8::new(10, 20, 30, 40));
359        prim.set_line_color(Rgba8::new(50, 60, 70, 80));
360        assert_eq!(prim.fill_color().r, 10);
361        assert_eq!(prim.line_color().r, 50);
362    }
363
364    #[test]
365    fn test_outlined_rectangle() {
366        let (_buf, mut ra) = make_ren(20, 20);
367        let pf = PixfmtRgba32::new(&mut ra);
368        let mut rb = RendererBase::new(pf);
369        let mut prim = RendererPrimitives::new(&mut rb);
370        prim.set_line_color(Rgba8::new(255, 0, 0, 255));
371        prim.set_fill_color(Rgba8::new(0, 255, 0, 255));
372        prim.outlined_rectangle(2, 2, 8, 8);
373    }
374}