embedded_graphics/primitives/triangle/
styled.rs

1use crate::{
2    draw_target::DrawTarget,
3    geometry::{Dimensions, Point},
4    pixelcolor::PixelColor,
5    primitives::{
6        common::{ClosedThickSegmentIter, PointType, Scanline, StrokeOffset},
7        styled::{StyledDimensions, StyledDrawable, StyledPixels},
8        triangle::{scanline_iterator::ScanlineIterator, Triangle},
9        PrimitiveStyle, Rectangle, StrokeAlignment,
10    },
11    Pixel,
12};
13
14/// Pixel iterator for each pixel in the triangle border
15#[derive(Clone, Eq, PartialEq, Hash, Debug)]
16#[cfg_attr(feature = "defmt", derive(::defmt::Format))]
17pub struct StyledPixelsIterator<C> {
18    lines_iter: ScanlineIterator,
19    current_line: Scanline,
20    current_color: Option<C>,
21    fill_color: Option<C>,
22    stroke_color: Option<C>,
23}
24
25impl<C: PixelColor> StyledPixelsIterator<C> {
26    pub(in crate::primitives) fn new(primitive: &Triangle, style: &PrimitiveStyle<C>) -> Self {
27        let mut lines_iter = ScanlineIterator::new(
28            primitive,
29            style.stroke_width,
30            StrokeOffset::from(style.stroke_alignment),
31            style.fill_color.is_some(),
32            &primitive.styled_bounding_box(style),
33        );
34
35        let (current_line, point_type) = lines_iter
36            .next()
37            .unwrap_or_else(|| (Scanline::new_empty(0), PointType::Stroke));
38
39        let current_color = match point_type {
40            PointType::Stroke => style.effective_stroke_color(),
41            PointType::Fill => style.fill_color,
42        };
43
44        Self {
45            lines_iter,
46            current_line,
47            current_color,
48            fill_color: style.fill_color,
49            stroke_color: style.effective_stroke_color(),
50        }
51    }
52}
53
54impl<C: PixelColor> Iterator for StyledPixelsIterator<C> {
55    type Item = Pixel<C>;
56
57    fn next(&mut self) -> Option<Self::Item> {
58        loop {
59            if let Some(p) = self.current_line.next() {
60                return Some(Pixel(p, self.current_color?));
61            } else {
62                let (next_line, next_type) = self.lines_iter.next()?;
63
64                self.current_line = next_line;
65
66                self.current_color = match next_type {
67                    PointType::Stroke => self.stroke_color,
68                    PointType::Fill => self.fill_color,
69                };
70            }
71        }
72    }
73}
74
75impl<C: PixelColor> StyledPixels<PrimitiveStyle<C>> for Triangle {
76    type Iter = StyledPixelsIterator<C>;
77
78    fn pixels(&self, style: &PrimitiveStyle<C>) -> Self::Iter {
79        StyledPixelsIterator::new(self, style)
80    }
81}
82
83impl<C: PixelColor> StyledDrawable<PrimitiveStyle<C>> for Triangle {
84    type Color = C;
85    type Output = ();
86
87    fn draw_styled<D>(
88        &self,
89        style: &PrimitiveStyle<C>,
90        target: &mut D,
91    ) -> Result<Self::Output, D::Error>
92    where
93        D: DrawTarget<Color = C>,
94    {
95        if style.is_transparent() {
96            return Ok(());
97        }
98
99        for (line, kind) in ScanlineIterator::new(
100            self,
101            style.stroke_width,
102            StrokeOffset::from(style.stroke_alignment),
103            style.fill_color.is_some(),
104            &self.styled_bounding_box(style),
105        ) {
106            let color = match kind {
107                PointType::Stroke => style.effective_stroke_color(),
108                PointType::Fill => style.fill_color,
109            };
110
111            if let Some(color) = color {
112                let rect = line.to_rectangle();
113
114                if !rect.is_zero_sized() {
115                    target.fill_solid(&rect, color)?;
116                }
117            }
118        }
119
120        Ok(())
121    }
122}
123
124impl<C: PixelColor> StyledDimensions<PrimitiveStyle<C>> for Triangle {
125    fn styled_bounding_box(&self, style: &PrimitiveStyle<C>) -> Rectangle {
126        // Short circuit special cases
127        if style.stroke_width < 2 || style.stroke_alignment == StrokeAlignment::Inside {
128            return self.bounding_box();
129        }
130
131        let t = self.sorted_clockwise();
132
133        let (min, max) = ClosedThickSegmentIter::new(
134            &t.vertices,
135            style.stroke_width,
136            StrokeOffset::from(style.stroke_alignment),
137        )
138        .fold(
139            (
140                Point::new_equal(core::i32::MAX),
141                Point::new_equal(core::i32::MIN),
142            ),
143            |(min, max), segment| {
144                let bb = segment.edges_bounding_box();
145
146                (
147                    min.component_min(bb.top_left),
148                    max.component_max(bb.bottom_right().unwrap_or(bb.top_left)),
149                )
150            },
151        );
152
153        Rectangle::with_corners(min, max)
154    }
155}
156
157#[cfg(test)]
158mod tests {
159    use super::*;
160    use crate::{
161        geometry::Point,
162        mock_display::MockDisplay,
163        pixelcolor::{BinaryColor, Rgb565, Rgb888, RgbColor},
164        primitives::{Line, Primitive, PrimitiveStyleBuilder, StrokeAlignment},
165        transform::Transform,
166        Drawable,
167    };
168
169    #[test]
170    fn unfilled_no_stroke_width_no_triangle() {
171        let mut tri = Triangle::new(Point::new(2, 2), Point::new(4, 2), Point::new(2, 4))
172            .into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 0))
173            .pixels();
174
175        assert_eq!(tri.next(), None);
176    }
177
178    #[test]
179    fn issue_308_infinite() {
180        let mut display: MockDisplay<BinaryColor> = MockDisplay::new();
181        display.set_allow_out_of_bounds_drawing(true);
182
183        Triangle::new(Point::new(10, 10), Point::new(20, 30), Point::new(30, -10))
184            .into_styled(PrimitiveStyle::with_fill(BinaryColor::On))
185            .draw(&mut display)
186            .unwrap();
187    }
188
189    #[test]
190    fn it_draws_filled_strokeless_tri() {
191        let mut display: MockDisplay<BinaryColor> = MockDisplay::new();
192
193        Triangle::new(Point::new(2, 2), Point::new(2, 4), Point::new(4, 2))
194            .into_styled(PrimitiveStyle::with_fill(BinaryColor::On))
195            .draw(&mut display)
196            .unwrap();
197
198        display.assert_pattern(&[
199            "     ", //
200            "     ", //
201            "  ###", //
202            "  ## ", //
203            "  #  ", //
204        ]);
205    }
206
207    #[test]
208    fn stroke_fill_colors() {
209        let mut display: MockDisplay<Rgb888> = MockDisplay::new();
210
211        Triangle::new(Point::new(2, 2), Point::new(8, 2), Point::new(2, 8))
212            .into_styled(
213                PrimitiveStyleBuilder::new()
214                    .stroke_width(1)
215                    .stroke_color(Rgb888::RED)
216                    .fill_color(Rgb888::GREEN)
217                    .build(),
218            )
219            .draw(&mut display)
220            .unwrap();
221
222        display.assert_pattern(&[
223            "          ",
224            "          ",
225            "  RRRRRRR ",
226            "  RGGGGR  ",
227            "  RGGGR   ",
228            "  RGGR    ",
229            "  RGR     ",
230            "  RR      ",
231            "  R       ",
232        ]);
233    }
234
235    #[test]
236    fn off_screen() {
237        let mut display: MockDisplay<BinaryColor> = MockDisplay::new();
238        display.set_allow_out_of_bounds_drawing(true);
239
240        Triangle::new(Point::new(5, 5), Point::new(10, 15), Point::new(15, -5))
241            .into_styled(PrimitiveStyle::with_fill(BinaryColor::On))
242            .draw(&mut display)
243            .unwrap();
244
245        display.assert_pattern(&[
246            "          #####",
247            "         ######",
248            "        ###### ",
249            "       ####### ",
250            "      ######## ",
251            "     ######### ",
252            "     ########  ",
253            "      #######  ",
254            "      #######  ",
255            "       ######  ",
256            "       #####   ",
257            "        ####   ",
258            "        ####   ",
259            "         ###   ",
260            "         ##    ",
261            "          #    ",
262        ]);
263    }
264
265    #[test]
266    fn styled_off_screen_still_draws_points() {
267        let off_screen = Triangle::new(Point::new(10, 10), Point::new(20, 20), Point::new(30, -30))
268            .into_styled(PrimitiveStyle::with_fill(BinaryColor::On));
269        let on_screen = off_screen.translate(Point::new(0, 35));
270
271        assert!(off_screen.pixels().eq(on_screen
272            .pixels()
273            .map(|Pixel(p, col)| Pixel(p - Point::new(0, 35), col))));
274    }
275
276    #[test]
277    fn styled_stroke_equals_lines() {
278        let triangle = Triangle::new(Point::new(10, 10), Point::new(30, 20), Point::new(20, 25));
279
280        let styled = triangle.into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 1));
281
282        let mut tri_display: MockDisplay<BinaryColor> = MockDisplay::new();
283        styled.draw(&mut tri_display).unwrap();
284
285        let mut lines_display: MockDisplay<BinaryColor> = MockDisplay::new();
286        lines_display.set_allow_overdraw(true);
287
288        let [p1, p2, p3] = triangle.vertices;
289
290        Line::new(p1, p2)
291            .into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 1))
292            .draw(&mut lines_display)
293            .unwrap();
294        Line::new(p2, p3)
295            .into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 1))
296            .draw(&mut lines_display)
297            .unwrap();
298        Line::new(p3, p1)
299            .into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 1))
300            .draw(&mut lines_display)
301            .unwrap();
302
303        tri_display.assert_eq(&lines_display);
304    }
305
306    #[test]
307    fn no_stroke_overdraw() {
308        let triangle = Triangle::new(Point::new(10, 10), Point::new(30, 20), Point::new(20, 25));
309
310        let styled = triangle.into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 1));
311
312        let mut display: MockDisplay<BinaryColor> = MockDisplay::new();
313
314        styled.draw(&mut display).unwrap();
315    }
316
317    #[test]
318    fn bounding_box() {
319        let triangle = Triangle::new(Point::new(10, 10), Point::new(30, 20), Point::new(20, 25));
320
321        let styled = triangle.into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 20));
322
323        let mut display = MockDisplay::new();
324
325        styled.draw(&mut display).unwrap();
326        assert_eq!(display.affected_area(), styled.bounding_box());
327    }
328
329    #[test]
330    fn bounding_box_is_independent_of_colors() {
331        let triangle = Triangle::new(Point::new(10, 10), Point::new(30, 20), Point::new(20, 25));
332
333        let transparent = triangle.into_styled(PrimitiveStyle::<BinaryColor>::new());
334        let filled = triangle.into_styled(PrimitiveStyle::with_fill(BinaryColor::On));
335
336        assert_eq!(transparent.bounding_box(), filled.bounding_box(),);
337    }
338
339    #[test]
340    fn outside_rendering_missing_lines() {
341        let p1 = Point::new(10, 11);
342        let p2 = Point::new(20, 11);
343        let p3 = Point::new(8, 4);
344
345        let styled = Triangle::new(p1, p2, p3).into_styled(
346            PrimitiveStyleBuilder::new()
347                .stroke_alignment(StrokeAlignment::Outside)
348                .stroke_width(5)
349                .stroke_color(Rgb565::RED)
350                .fill_color(Rgb565::GREEN)
351                .build(),
352        );
353
354        let mut display = MockDisplay::new();
355
356        styled.draw(&mut display).unwrap();
357
358        // Believe it or not, this is actually a triangle.
359        display.assert_pattern(&[
360            "          R            ",
361            "         RRRR          ",
362            "        RRRRRRR        ",
363            "       RRRRRRRRR       ",
364            "     RRRRRRRRRRRRR     ",
365            "    RRRRRRRRRRRRRRRR   ",
366            "    RRRRRRGRRRRRRRRRRR ",
367            "     RRRRRGGGRRRRRRRRRR",
368            "     RRRRRGGGGGRRRRRRRR",
369            "     RRRRRGGGGGGRRRRRRR",
370            "     RRRRRRGGGGGGGRRRR ",
371            "      RRRRRRRRRRRRRRRR ",
372            "      RRRRRRRRRRRRRRRR ",
373            "      RRRRRRRRRRRRRRR  ",
374            "       RRRRRRRRRRRRRR  ",
375            "       RRRRRRRRRRRRRR  ",
376        ]);
377    }
378
379    #[test]
380    fn thick_stroke_only_no_overdraw() {
381        let p1 = Point::new(10, 11);
382        let p2 = Point::new(20, 11);
383        let p3 = Point::new(8, 4);
384
385        let styled = Triangle::new(p1, p2, p3).into_styled(
386            PrimitiveStyleBuilder::new()
387                .stroke_alignment(StrokeAlignment::Outside)
388                .stroke_width(5)
389                .stroke_color(Rgb565::RED)
390                .build(),
391        );
392
393        let mut display = MockDisplay::new();
394
395        styled.draw(&mut display).unwrap();
396    }
397
398    #[test]
399    fn inner_fill_leak() {
400        let p1 = Point::new(0, 20);
401        let p2 = Point::new(20, 0);
402        let p3 = Point::new(14, 24);
403
404        let styled = Triangle::new(p1, p2, p3).into_styled(
405            PrimitiveStyleBuilder::new()
406                .stroke_alignment(StrokeAlignment::Inside)
407                .stroke_width(5)
408                .stroke_color(Rgb565::RED)
409                .fill_color(Rgb565::GREEN)
410                .build(),
411        );
412
413        let mut display = MockDisplay::new();
414
415        styled.draw(&mut display).unwrap();
416
417        // In the failing case, there are some `G`s sitting on the end of each line that
418        // shouldn't be there.
419        display.assert_pattern(&[
420            "                    R",
421            "                   RR",
422            "                  RR ",
423            "                 RRR ",
424            "                RRRR ",
425            "               RRRRR ",
426            "              RRRRR  ",
427            "             RRRRRR  ",
428            "            RRRRRRR  ",
429            "           RRRRRRRR  ",
430            "          RRRRRRRR   ",
431            "         RRRRRRRRR   ",
432            "        RRRRRRRRRR   ",
433            "       RRRRRRRRRRR   ",
434            "      RRRRRRRRRRR    ",
435            "     RRRRRRRRRRRR    ",
436            "    RRRRRRRGRRRRR    ",
437            "   RRRRRRRGRRRRRR    ",
438            "  RRRRRRRRGRRRRR     ",
439            " RRRRRRRRRRRRRRR     ",
440            "RRRRRRRRRRRRRRRR     ",
441            "  RRRRRRRRRRRRRR     ",
442            "      RRRRRRRRR      ",
443            "         RRRRRR      ",
444            "             RR      ",
445        ]);
446    }
447
448    #[test]
449    fn colinear() {
450        let p1 = Point::new(90, 80);
451        let p2 = Point::new(100, 70);
452        let p3 = Point::new(95, 75);
453
454        let t = Triangle::new(p1, p2, p3).translate(Point::new(-85, -70));
455
456        let styled = t.into_styled(
457            PrimitiveStyleBuilder::new()
458                .stroke_alignment(StrokeAlignment::Inside)
459                .stroke_width(5)
460                .stroke_color(Rgb565::RED)
461                .fill_color(Rgb565::GREEN)
462                .build(),
463        );
464
465        let mut display = MockDisplay::new();
466
467        styled.draw(&mut display).unwrap();
468
469        display.assert_pattern(&[
470            "               R",
471            "              R ",
472            "             R  ",
473            "            R   ",
474            "           R    ",
475            "          R     ",
476            "         R      ",
477            "        R       ",
478            "       R        ",
479            "      R         ",
480            "     R          ",
481        ]);
482    }
483
484    // Original bug has a weird "lump" drawn at one end of a colinear triangle.
485    #[test]
486    fn colinear_lump() {
487        let p1 = Point::new(90, 80);
488        let p2 = Point::new(100, 70);
489        let p3 = Point::new(102, 73);
490
491        let t = Triangle::new(p1, p2, p3).translate(Point::new(-90, -70));
492
493        let styled = t.into_styled(
494            PrimitiveStyleBuilder::new()
495                .stroke_alignment(StrokeAlignment::Inside)
496                .stroke_width(5)
497                .stroke_color(Rgb565::RED)
498                .fill_color(Rgb565::GREEN)
499                .build(),
500        );
501
502        let mut display = MockDisplay::new();
503
504        styled.draw(&mut display).unwrap();
505
506        display.assert_pattern(&[
507            "          R  ",
508            "         RRR ",
509            "        RRRR ",
510            "       RRRRRR",
511            "      RRRRRR ",
512            "     RRRRR   ",
513            "    RRRR     ",
514            "   RRR       ",
515            "  RRR        ",
516            " RR          ",
517            "R            ",
518        ]);
519    }
520
521    #[test]
522    fn colinear_lump_2() {
523        let p1 = Point::new(90, 80);
524        let p2 = Point::new(100, 70);
525        let p3 = Point::new(102, 73);
526
527        let t = Triangle::new(p1, p2, p3).translate(Point::new(-90, -70));
528
529        let styled = t.into_styled(
530            PrimitiveStyleBuilder::new()
531                .stroke_alignment(StrokeAlignment::Inside)
532                .stroke_width(5)
533                .stroke_color(Rgb565::RED)
534                .fill_color(Rgb565::GREEN)
535                .build(),
536        );
537
538        let mut display = MockDisplay::new();
539
540        styled.draw(&mut display).unwrap();
541
542        display.assert_pattern(&[
543            "          R  ",
544            "         RRR ",
545            "        RRRR ",
546            "       RRRRRR",
547            "      RRRRRR ",
548            "     RRRRR   ",
549            "    RRRR     ",
550            "   RRR       ",
551            "  RRR        ",
552            " RR          ",
553            "R            ",
554        ]);
555    }
556}