embedded_graphics/primitives/line/
styled.rs

1use crate::{
2    draw_target::DrawTarget,
3    pixelcolor::PixelColor,
4    primitives::{
5        line::{thick_points::ThickPoints, Line, StrokeOffset},
6        styled::{StyledDimensions, StyledDrawable, StyledPixels},
7        PrimitiveStyle, Rectangle,
8    },
9    Pixel,
10};
11use az::SaturatingAs;
12
13/// Styled line iterator.
14#[derive(Clone, Debug)]
15#[cfg_attr(feature = "defmt", derive(::defmt::Format))]
16pub struct StyledPixelsIterator<C> {
17    stroke_color: Option<C>,
18    line_iter: ThickPoints,
19}
20
21impl<C: PixelColor> StyledPixelsIterator<C> {
22    pub(in crate::primitives::line) fn new(primitive: &Line, style: &PrimitiveStyle<C>) -> Self {
23        // Note: stroke color will be None if stroke width is 0
24        let stroke_color = style.effective_stroke_color();
25        let stroke_width = style.stroke_width.saturating_as();
26
27        Self {
28            stroke_color,
29            line_iter: ThickPoints::new(primitive, stroke_width),
30        }
31    }
32}
33
34impl<C: PixelColor> Iterator for StyledPixelsIterator<C> {
35    type Item = Pixel<C>;
36
37    fn next(&mut self) -> Option<Self::Item> {
38        // Return none if stroke color is none
39        let stroke_color = self.stroke_color?;
40
41        self.line_iter
42            .next()
43            .map(|point| Pixel(point, stroke_color))
44    }
45}
46
47impl<C: PixelColor> StyledPixels<PrimitiveStyle<C>> for Line {
48    type Iter = StyledPixelsIterator<C>;
49
50    fn pixels(&self, style: &PrimitiveStyle<C>) -> Self::Iter {
51        StyledPixelsIterator::new(self, style)
52    }
53}
54
55impl<C: PixelColor> StyledDrawable<PrimitiveStyle<C>> for Line {
56    type Color = C;
57    type Output = ();
58
59    fn draw_styled<D>(
60        &self,
61        style: &PrimitiveStyle<C>,
62        target: &mut D,
63    ) -> Result<Self::Output, D::Error>
64    where
65        D: DrawTarget<Color = C>,
66    {
67        target.draw_iter(StyledPixelsIterator::new(self, style))
68    }
69}
70
71impl<C: PixelColor> StyledDimensions<PrimitiveStyle<C>> for Line {
72    fn styled_bounding_box(&self, style: &PrimitiveStyle<C>) -> Rectangle {
73        let (l, r) = self.extents(style.stroke_width, StrokeOffset::None);
74
75        let min = l
76            .start
77            .component_min(l.end)
78            .component_min(r.start)
79            .component_min(r.end);
80        let max = l
81            .start
82            .component_max(l.end)
83            .component_max(r.start)
84            .component_max(r.end);
85
86        Rectangle::with_corners(min, max)
87    }
88}
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93    use crate::{
94        geometry::{Dimensions, Point},
95        mock_display::MockDisplay,
96        pixelcolor::{Rgb888, RgbColor},
97        primitives::{Primitive, PrimitiveStyleBuilder},
98        Drawable,
99    };
100
101    #[test]
102    fn bounding_box() {
103        let lines = [
104            (
105                Line::new(Point::new(10, 20), Point::new(10, 50)),
106                "vertical",
107            ),
108            (
109                Line::new(Point::new(20, 20), Point::new(50, 20)),
110                "horizontal",
111            ),
112            (
113                Line::new(Point::new(20, 20), Point::new(55, 55)),
114                "diagonal",
115            ),
116            (Line::new(Point::new(20, 20), Point::new(55, 55)), "thin"),
117            (
118                Line::new(Point::new(40, 40), Point::new(13, 14)),
119                "random angle 1",
120            ),
121            (
122                Line::new(Point::new(30, 30), Point::new(12, 53)),
123                "random angle 2",
124            ),
125        ];
126
127        for (line, name) in lines.iter() {
128            for thickness in 1..15 {
129                let style = PrimitiveStyle::with_stroke(Rgb888::RED, thickness);
130                let styled = line.into_styled(style);
131
132                let mut display = MockDisplay::new();
133                styled.draw(&mut display).unwrap();
134                assert_eq!(
135                    display.affected_area(),
136                    styled.bounding_box(),
137                    "{}, {} px",
138                    name,
139                    thickness
140                );
141            }
142        }
143    }
144
145    #[test]
146    fn bounding_box_is_independent_of_colors() {
147        let line = Line::new(Point::new(5, 5), Point::new(11, 14));
148
149        let transparent_line = line.into_styled(
150            PrimitiveStyleBuilder::<Rgb888>::new()
151                .stroke_width(10)
152                .build(),
153        );
154        let stroked_line = line.into_styled(PrimitiveStyle::with_stroke(Rgb888::RED, 10));
155
156        assert_eq!(transparent_line.bounding_box(), stroked_line.bounding_box(),);
157    }
158}