embedded_graphics/primitives/polyline/
styled.rs

1use crate::{
2    draw_target::{DrawTarget, DrawTargetExt},
3    geometry::{Dimensions, Point, Size},
4    pixelcolor::PixelColor,
5    primitives::{
6        common::{Scanline, StrokeOffset, ThickSegmentIter},
7        polyline::{self, scanline_iterator::ScanlineIterator, Polyline},
8        styled::{StyledDimensions, StyledDrawable, StyledPixels},
9        PointsIter, PrimitiveStyle, Rectangle,
10    },
11    transform::Transform,
12    Pixel,
13};
14
15/// Compute the bounding box of the non-translated polyline.
16pub(in crate::primitives::polyline) fn untranslated_bounding_box<C: PixelColor>(
17    primitive: &Polyline,
18    style: &PrimitiveStyle<C>,
19) -> Rectangle {
20    if style.effective_stroke_color().is_some() && primitive.vertices.len() > 1 {
21        let (min, max) =
22            ThickSegmentIter::new(primitive.vertices, style.stroke_width, StrokeOffset::None).fold(
23                (
24                    Point::new_equal(core::i32::MAX),
25                    Point::new_equal(core::i32::MIN),
26                ),
27                |(min, max), segment| {
28                    let bb = segment.edges_bounding_box();
29
30                    (
31                        min.component_min(bb.top_left),
32                        max.component_max(bb.bottom_right().unwrap_or(bb.top_left)),
33                    )
34                },
35            );
36
37        Rectangle::with_corners(min, max)
38    } else {
39        Rectangle::new(primitive.bounding_box().center(), Size::zero())
40    }
41}
42
43fn draw_thick<D>(
44    polyline: &Polyline,
45    style: &PrimitiveStyle<D::Color>,
46    stroke_color: D::Color,
47    target: &mut D,
48) -> Result<(), D::Error>
49where
50    D: DrawTarget,
51{
52    for line in ScanlineIterator::new(polyline, style) {
53        let rect = line.to_rectangle();
54
55        if !rect.is_zero_sized() {
56            target.fill_solid(&rect, stroke_color)?;
57        }
58    }
59
60    Ok(())
61}
62
63#[derive(Clone, Debug)]
64#[cfg_attr(feature = "defmt", derive(::defmt::Format))]
65enum StyledIter<'a> {
66    Thin(polyline::Points<'a>),
67    Thick {
68        scanline_iter: ScanlineIterator<'a>,
69        line_iter: Scanline,
70        translate: Point,
71    },
72}
73
74/// Pixel iterator for each pixel in the line
75#[derive(Clone, Debug)]
76#[cfg_attr(feature = "defmt", derive(::defmt::Format))]
77pub struct StyledPixelsIterator<'a, C> {
78    stroke_color: Option<C>,
79    line_iter: StyledIter<'a>,
80}
81
82impl<'a, C: PixelColor> StyledPixelsIterator<'a, C> {
83    pub(in crate::primitives) fn new(primitive: &Polyline<'a>, style: &PrimitiveStyle<C>) -> Self {
84        let line_iter = if style.stroke_width <= 1 {
85            StyledIter::Thin(primitive.points())
86        } else {
87            let mut scanline_iter = ScanlineIterator::new(primitive, style);
88            let line_iter = scanline_iter
89                .next()
90                .unwrap_or_else(|| Scanline::new_empty(0));
91
92            StyledIter::Thick {
93                scanline_iter,
94                line_iter,
95                translate: primitive.translate,
96            }
97        };
98
99        StyledPixelsIterator {
100            stroke_color: style.effective_stroke_color(),
101            line_iter,
102        }
103    }
104}
105
106impl<C: PixelColor> Iterator for StyledPixelsIterator<'_, C> {
107    type Item = Pixel<C>;
108
109    fn next(&mut self) -> Option<Self::Item> {
110        // Return none if stroke color is none
111        let stroke_color = self.stroke_color?;
112
113        match self.line_iter {
114            StyledIter::Thin(ref mut it) => it.next(),
115            StyledIter::Thick {
116                ref mut scanline_iter,
117                ref mut line_iter,
118                translate,
119            } => {
120                // We've got a line to iterate over, so get it's next pixel.
121                if let Some(p) = line_iter.next() {
122                    Some(p)
123                }
124                // Finished this line. Get the next one from the scanline iterator.
125                else {
126                    *line_iter = scanline_iter.next()?;
127
128                    line_iter.next()
129                }
130                .map(|p| p + translate)
131            }
132        }
133        .map(|point| Pixel(point, stroke_color))
134    }
135}
136
137impl<'a, C: PixelColor> StyledPixels<PrimitiveStyle<C>> for Polyline<'a> {
138    type Iter = StyledPixelsIterator<'a, C>;
139
140    fn pixels(&self, style: &PrimitiveStyle<C>) -> Self::Iter {
141        StyledPixelsIterator::new(self, style)
142    }
143}
144
145impl<C: PixelColor> StyledDrawable<PrimitiveStyle<C>> for Polyline<'_> {
146    type Color = C;
147    type Output = ();
148
149    fn draw_styled<D>(
150        &self,
151        style: &PrimitiveStyle<C>,
152        target: &mut D,
153    ) -> Result<Self::Output, D::Error>
154    where
155        D: DrawTarget<Color = C>,
156    {
157        if let Some(stroke_color) = style.stroke_color {
158            match style.stroke_width {
159                0 => Ok(()),
160                1 => target.draw_iter(self.points().map(|point| Pixel(point, stroke_color))),
161                _ => {
162                    if self.translate != Point::zero() {
163                        draw_thick(
164                            self,
165                            style,
166                            stroke_color,
167                            &mut target.translated(self.translate),
168                        )
169                    } else {
170                        draw_thick(self, style, stroke_color, target)
171                    }
172                }
173            }
174        } else {
175            Ok(())
176        }
177    }
178}
179
180impl<C: PixelColor> StyledDimensions<PrimitiveStyle<C>> for Polyline<'_> {
181    fn styled_bounding_box(&self, style: &PrimitiveStyle<C>) -> Rectangle {
182        untranslated_bounding_box(self, style).translate(self.translate)
183    }
184}
185
186#[cfg(test)]
187mod tests {
188    use super::*;
189    use crate::{
190        geometry::Point,
191        iterator::PixelIteratorExt,
192        mock_display::MockDisplay,
193        pixelcolor::{BinaryColor, Rgb565, RgbColor},
194        primitives::{Primitive, PrimitiveStyle, PrimitiveStyleBuilder, StrokeAlignment},
195        Drawable,
196    };
197
198    // Smaller test pattern for mock display
199    pub(in crate::primitives::polyline) const PATTERN: [Point; 4] = [
200        Point::new(5, 10),
201        Point::new(13, 5),
202        Point::new(20, 10),
203        Point::new(30, 5),
204    ];
205
206    #[test]
207    fn one_px_stroke() {
208        let mut display = MockDisplay::new();
209
210        Polyline::new(&PATTERN)
211            .translate(Point::new(-5, -5))
212            .into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 1))
213            .draw(&mut display)
214            .unwrap();
215
216        display.assert_pattern(&[
217            "        #                #",
218            "      ## ##            ## ",
219            "     #     #         ##   ",
220            "   ##       #      ##     ",
221            " ##          ##  ##       ",
222            "#              ##         ",
223        ]);
224    }
225
226    #[test]
227    fn one_px_stroke_translated() {
228        let mut display = MockDisplay::new();
229
230        Polyline::new(&PATTERN)
231            .into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 1))
232            .draw(&mut display)
233            .unwrap();
234
235        display.assert_pattern(&[
236            "                               ",
237            "                               ",
238            "                               ",
239            "                               ",
240            "                               ",
241            "             #                #",
242            "           ## ##            ## ",
243            "          #     #         ##   ",
244            "        ##       #      ##     ",
245            "      ##          ##  ##       ",
246            "     #              ##         ",
247        ]);
248    }
249
250    #[test]
251    fn thick_stroke() {
252        let mut display = MockDisplay::new();
253
254        Polyline::new(&PATTERN)
255            .into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 4))
256            .draw(&mut display)
257            .unwrap();
258
259        display.assert_pattern(&[
260            "                                ",
261            "                                ",
262            "                                ",
263            "             #                  ",
264            "           #####            ##  ",
265            "          #######         ##### ",
266            "        ##########      ####### ",
267            "       #############  ##########",
268            "     ######## ################# ",
269            "    #######     #############   ",
270            "     ####         #########     ",
271            "      #            ######       ",
272            "                     ##         ",
273        ]);
274    }
275
276    #[test]
277    fn thick_stroke_translated() {
278        let mut display = MockDisplay::new();
279
280        let styled = Polyline::new(&PATTERN)
281            .translate(Point::new(-4, -3))
282            .into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 4));
283
284        assert_eq!(
285            styled.bounding_box(),
286            Rectangle::new(Point::zero(), Size::new(28, 10))
287        );
288
289        styled.draw(&mut display).unwrap();
290
291        display.assert_pattern(&[
292            "         #                  ",
293            "       #####            ##  ",
294            "      #######         ##### ",
295            "    ##########      ####### ",
296            "   #############  ##########",
297            " ######## ################# ",
298            "#######     #############   ",
299            " ####         #########     ",
300            "  #            ######       ",
301            "                 ##         ",
302        ]);
303    }
304
305    #[test]
306    fn thick_stroke_points() {
307        let mut d1 = MockDisplay::new();
308        let mut d2 = MockDisplay::new();
309
310        let pl = Polyline::new(&PATTERN)
311            .translate(Point::new(2, 3))
312            .into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 4));
313
314        pl.draw(&mut d1).unwrap();
315
316        pl.pixels().draw(&mut d2).unwrap();
317
318        d1.assert_eq(&d2);
319    }
320
321    #[test]
322    fn joints() {
323        let cases: [(&str, &[Point], &[&str]); 4] = [
324            (
325                "Bevel with outside on right",
326                &[Point::new(0, 6), Point::new(25, 6), Point::new(3, 1)],
327                &[
328                    "   ###                    ",
329                    "   #######                ",
330                    "   ###########            ",
331                    "   ################       ",
332                    "#######################   ",
333                    "##########################",
334                    "##########################",
335                    "##########################",
336                ],
337            ),
338            (
339                "Bevel with outside on left",
340                &[Point::new(0, 2), Point::new(20, 2), Point::new(3, 8)],
341                &[
342                    "##################### ",
343                    "##################### ",
344                    "##################### ",
345                    "######################",
346                    "          ############",
347                    "        ############  ",
348                    "     ############     ",
349                    "   ###########        ",
350                    "   #########          ",
351                    "    #####             ",
352                    "    ##                ",
353                ],
354            ),
355            (
356                "Miter with outside on right",
357                &[Point::new(0, 6), Point::new(10, 6), Point::new(3, 1)],
358                &[
359                    "    #          ",
360                    "   ####        ",
361                    "  ######       ",
362                    "   ######      ",
363                    "###########    ",
364                    "############   ",
365                    "############## ",
366                    "###############",
367                ],
368            ),
369            (
370                "Miter with outside on left",
371                &[Point::new(0, 2), Point::new(10, 2), Point::new(3, 8)],
372                &[
373                    "################",
374                    "############### ",
375                    "##############  ",
376                    "############    ",
377                    "      #####     ",
378                    "    ######      ",
379                    "   ######       ",
380                    "  ######        ",
381                    "   ###          ",
382                    "    #           ",
383                ],
384            ),
385        ];
386
387        for (case, points, expected) in cases.iter() {
388            let mut display = MockDisplay::new();
389
390            Polyline::new(points)
391                .into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 4))
392                .draw(&mut display)
393                .unwrap();
394
395            display.assert_pattern_with_message(expected, |f| write!(f, "Join {}", case));
396        }
397    }
398
399    #[test]
400    fn degenerate_joint() {
401        let mut display = MockDisplay::new();
402
403        Polyline::new(&[Point::new(2, 5), Point::new(25, 5), Point::new(5, 2)])
404            .into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 5))
405            .draw(&mut display)
406            .unwrap();
407
408        display.assert_pattern(&[
409            "     ####                 ",
410            "     ##########           ",
411            "     #################    ",
412            "  ########################",
413            "  ########################",
414            "  ########################",
415            "  ########################",
416            "  ########################",
417        ]);
418    }
419
420    #[test]
421    fn alignment_has_no_effect() {
422        let base_style = PrimitiveStyleBuilder::new()
423            .stroke_color(BinaryColor::On)
424            .stroke_width(3);
425
426        let mut expected_display = MockDisplay::new();
427
428        Polyline::new(&PATTERN)
429            .into_styled(base_style.build())
430            .draw(&mut expected_display)
431            .unwrap();
432
433        for alignment in [StrokeAlignment::Inside, StrokeAlignment::Outside].iter() {
434            let mut display = MockDisplay::new();
435
436            Polyline::new(&PATTERN)
437                .into_styled(base_style.stroke_alignment(*alignment).build())
438                .draw(&mut display)
439                .unwrap();
440
441            display.assert_eq_with_message(&expected_display, |f| write!(f, "{:?}", alignment));
442        }
443    }
444
445    #[test]
446    fn thick_points() {
447        let base_style = PrimitiveStyle::with_stroke(BinaryColor::On, 5);
448
449        let mut expected_display = MockDisplay::new();
450        let mut display = MockDisplay::new();
451
452        Polyline::new(&PATTERN)
453            .into_styled(base_style)
454            .draw(&mut expected_display)
455            .unwrap();
456
457        Polyline::new(&PATTERN)
458            .into_styled(base_style)
459            .pixels()
460            .draw(&mut display)
461            .unwrap();
462
463        display.assert_eq(&expected_display);
464    }
465
466    #[test]
467    fn empty_styled_iterators() {
468        let points: [Point; 3] = [Point::new(2, 5), Point::new(3, 4), Point::new(4, 3)];
469
470        // No stroke width = no pixels
471        assert!(Polyline::new(&points)
472            .into_styled(PrimitiveStyle::with_stroke(Rgb565::BLUE, 0))
473            .pixels()
474            .eq(core::iter::empty()));
475
476        // No stroke color = no pixels
477        assert!(Polyline::new(&points)
478            .into_styled(
479                PrimitiveStyleBuilder::<Rgb565>::new()
480                    .stroke_width(1)
481                    .build()
482            )
483            .pixels()
484            .eq(core::iter::empty()));
485    }
486
487    #[test]
488    fn bounding_box() {
489        let pl = Polyline::new(&PATTERN);
490
491        let styled = pl.into_styled(PrimitiveStyle::with_stroke(Rgb565::BLUE, 5));
492
493        let mut display = MockDisplay::new();
494        styled.draw(&mut display).unwrap();
495        assert_eq!(display.affected_area(), styled.bounding_box());
496
497        assert_eq!(
498            pl.into_styled(
499                PrimitiveStyleBuilder::<Rgb565>::new()
500                    .stroke_width(5)
501                    .build()
502            )
503            .bounding_box(),
504            Rectangle::new(pl.bounding_box().center(), Size::zero()),
505            "transparent"
506        );
507
508        assert_eq!(
509            pl.into_styled(PrimitiveStyle::with_fill(Rgb565::RED))
510                .bounding_box(),
511            Rectangle::new(pl.bounding_box().center(), Size::zero()),
512            "filled"
513        );
514
515        assert_eq!(
516            Polyline::new(&PATTERN[0..2])
517                .into_styled(PrimitiveStyle::with_stroke(Rgb565::RED, 5))
518                .bounding_box(),
519            Rectangle::new(Point::new(4, 3), Size::new(11, 9)),
520            "two points"
521        );
522
523        assert_eq!(
524            Polyline::new(&PATTERN[0..1])
525                .into_styled(PrimitiveStyle::with_stroke(Rgb565::RED, 5))
526                .bounding_box(),
527            Rectangle::new(Point::new(5, 10), Size::zero()),
528            "one point"
529        );
530    }
531
532    #[test]
533    fn translated_bounding_box() {
534        let by = Point::new(10, 12);
535        let pl = Polyline::new(&PATTERN).translate(by);
536
537        assert_eq!(
538            pl.into_styled(PrimitiveStyle::with_stroke(Rgb565::RED, 1))
539                .bounding_box(),
540            Rectangle::new(Point::new(15, 17), Size::new(26, 6)),
541            "thin translated"
542        );
543
544        assert_eq!(
545            pl.into_styled(PrimitiveStyle::with_stroke(Rgb565::RED, 5))
546                .bounding_box(),
547            Rectangle::new(Point::new(14, 14), Size::new(28, 11)),
548            "thick translated"
549        );
550
551        assert_eq!(
552            Polyline::new(&PATTERN[0..2])
553                .translate(by)
554                .into_styled(PrimitiveStyle::with_stroke(Rgb565::RED, 5))
555                .bounding_box(),
556            Rectangle::new(Point::new(14, 15), Size::new(11, 9)),
557            "two points translated"
558        );
559
560        assert_eq!(
561            Polyline::new(&PATTERN[0..1])
562                .translate(by)
563                .into_styled(PrimitiveStyle::with_stroke(Rgb565::RED, 5))
564                .bounding_box(),
565            Rectangle::new(Point::new(15, 22), Size::zero()),
566            "one point translated"
567        );
568    }
569
570    #[test]
571    fn empty_line_no_draw() {
572        let mut display = MockDisplay::new();
573
574        Polyline::new(&[])
575            .into_styled(PrimitiveStyle::with_stroke(Rgb565::GREEN, 2))
576            .pixels()
577            .draw(&mut display)
578            .unwrap();
579
580        display.assert_eq(&MockDisplay::new());
581    }
582
583    #[test]
584    fn issue_489_overdraw() {
585        let mut display = MockDisplay::new();
586
587        // Panics if pixel is drawn twice.
588        Polyline::new(&[Point::new(10, 5), Point::new(5, 10), Point::new(10, 10)])
589            .into_styled(PrimitiveStyle::with_stroke(Rgb565::RED, 5))
590            .draw(&mut display)
591            .unwrap();
592    }
593
594    #[test]
595    fn issue_471_spurs() {
596        let points = [Point::new(10, 70), Point::new(20, 50), Point::new(31, 30)];
597
598        let line = Polyline::new(&points)
599            .translate(Point::new(0, -15))
600            .into_styled(PrimitiveStyle::with_stroke(Rgb565::RED, 18));
601
602        let bb = line.bounding_box();
603
604        // Check bounding box is correct
605        assert_eq!(bb, Rectangle::new(Point::new(1, 11), Size::new(39, 49)));
606
607        let mut display = MockDisplay::new();
608        line.draw(&mut display).unwrap();
609
610        // Check no pixels are drawn outside bounding box
611        assert_eq!(display.affected_area(), bb);
612    }
613
614    #[test]
615    // FIXME: Un-ignore when more polyline spur fixes are made. This test checks for a smaller
616    // spur created from a different set of points than `issue_471_spurs`.
617    #[ignore]
618    fn issue_471_spurs_2() {
619        let points = [Point::new(13, 65), Point::new(20, 50), Point::new(31, 30)];
620
621        let line = Polyline::new(&points)
622            .translate(Point::new(0, -15))
623            .into_styled(PrimitiveStyle::with_stroke(Rgb565::RED, 18));
624
625        let bb = line.bounding_box();
626
627        // Check bounding box is correct
628        assert_eq!(bb, Rectangle::new(Point::new(4, 26), Size::new(36, 44)));
629
630        let mut display = MockDisplay::new();
631        line.draw(&mut display).unwrap();
632
633        // Check no pixels are drawn outside bounding box
634        assert_eq!(display.affected_area(), bb);
635    }
636}