embedded_graphics/primitives/line/
styled.rs1use 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#[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 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 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}