1 | //! The polyline primitive |
2 | |
3 | use crate::{ |
4 | geometry::{Dimensions, Point, Size}, |
5 | primitives::{PointsIter, Primitive, Rectangle}, |
6 | transform::Transform, |
7 | }; |
8 | |
9 | mod points; |
10 | pub(in crate::primitives) mod scanline_intersections; |
11 | mod scanline_iterator; |
12 | mod styled; |
13 | |
14 | pub use points::Points; |
15 | pub use styled::StyledPixelsIterator; |
16 | |
17 | /// Polyline primitive |
18 | /// |
19 | /// Creates an unfilled chained line shape. |
20 | /// |
21 | /// # Examples |
22 | /// |
23 | /// ## Draw a "heartbeat" shaped polyline |
24 | /// |
25 | /// This example draws a stylized cardiogram in a 5px green stroke. |
26 | /// |
27 | /// ```rust |
28 | /// use embedded_graphics::{ |
29 | /// pixelcolor::Rgb565, prelude::*, primitives::{Polyline, PrimitiveStyle}, |
30 | /// }; |
31 | /// # use embedded_graphics::mock_display::MockDisplay; |
32 | /// # let mut display = MockDisplay::default(); |
33 | /// # display.set_allow_out_of_bounds_drawing(true); |
34 | /// |
35 | /// // A "heartbeat" shaped polyline |
36 | /// let points: [Point; 10] = [ |
37 | /// Point::new(10, 64), |
38 | /// Point::new(50, 64), |
39 | /// Point::new(60, 44), |
40 | /// Point::new(70, 64), |
41 | /// Point::new(80, 64), |
42 | /// Point::new(90, 74), |
43 | /// Point::new(100, 10), |
44 | /// Point::new(110, 84), |
45 | /// Point::new(120, 64), |
46 | /// Point::new(300, 64), |
47 | /// ]; |
48 | /// |
49 | /// let line_style = PrimitiveStyle::with_stroke(Rgb565::GREEN, 5); |
50 | /// |
51 | /// Polyline::new(&points) |
52 | /// .into_styled(line_style) |
53 | /// .draw(&mut display)?; |
54 | /// # Ok::<(), core::convert::Infallible>(()) |
55 | /// ``` |
56 | #[derive (Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)] |
57 | #[cfg_attr (feature = "defmt" , derive(::defmt::Format))] |
58 | pub struct Polyline<'a> { |
59 | /// An offset to apply to the polyline as a whole |
60 | pub translate: Point, |
61 | |
62 | /// All vertices in the line |
63 | pub vertices: &'a [Point], |
64 | } |
65 | |
66 | impl<'a> Polyline<'a> { |
67 | /// Create a new polyline from a list of vertices |
68 | /// |
69 | /// If fewer than two vertices are provided, the line will not render anything when drawn. |
70 | pub const fn new(vertices: &'a [Point]) -> Self { |
71 | Self { |
72 | vertices, |
73 | translate: Point::zero(), |
74 | } |
75 | } |
76 | } |
77 | |
78 | impl<'a> Primitive for Polyline<'a> {} |
79 | |
80 | impl<'a> PointsIter for Polyline<'a> { |
81 | type Iter = Points<'a>; |
82 | |
83 | fn points(&self) -> Self::Iter { |
84 | Points::new(self) |
85 | } |
86 | } |
87 | |
88 | impl<'a> Dimensions for Polyline<'a> { |
89 | fn bounding_box(&self) -> Rectangle { |
90 | match self.vertices { |
91 | [] => Rectangle::zero(), |
92 | [v] => Rectangle::new(*v, Size::zero()), |
93 | vertices => { |
94 | let top_left = vertices |
95 | .iter() |
96 | .map(|v| *v + self.translate) |
97 | .fold(Point::new(core::i32::MAX, core::i32::MAX), |accum, v| { |
98 | Point::new(accum.x.min(v.x), accum.y.min(v.y)) |
99 | }); |
100 | |
101 | let bottom_right = vertices |
102 | .iter() |
103 | .map(|v| *v + self.translate) |
104 | .fold(Point::new(core::i32::MIN, core::i32::MIN), |accum, v| { |
105 | Point::new(accum.x.max(v.x), accum.y.max(v.y)) |
106 | }); |
107 | |
108 | Rectangle::with_corners(top_left, bottom_right) |
109 | } |
110 | } |
111 | } |
112 | } |
113 | |
114 | impl<'a> Transform for Polyline<'a> { |
115 | /// Translate the polyline from its current position to a new position by (x, y) pixels, returning |
116 | /// a new `Polyline`. For a mutating transform, see `translate_mut`. |
117 | /// |
118 | /// ``` |
119 | /// # use embedded_graphics::primitives::Polyline; |
120 | /// # use embedded_graphics::prelude::*; |
121 | /// let points = [ |
122 | /// Point::new(5, 10), |
123 | /// Point::new(7, 7), |
124 | /// Point::new(5, 8), |
125 | /// Point::new(10, 10), |
126 | /// ]; |
127 | /// |
128 | /// let polyline = Polyline::new(&points); |
129 | /// let moved = polyline.translate(Point::new(10, 12)); |
130 | /// |
131 | /// assert_eq!(polyline.bounding_box().top_left, Point::new(5, 7)); |
132 | /// assert_eq!(moved.bounding_box().top_left, Point::new(15, 19)); |
133 | /// ``` |
134 | fn translate(&self, by: Point) -> Self { |
135 | Self { |
136 | translate: self.translate + by, |
137 | ..*self |
138 | } |
139 | } |
140 | |
141 | /// Translate the polyline from its current position to a new position by (x, y) pixels. |
142 | /// |
143 | /// ``` |
144 | /// # use embedded_graphics::primitives::Polyline; |
145 | /// # use embedded_graphics::prelude::*; |
146 | /// let points = [ |
147 | /// Point::new(5, 10), |
148 | /// Point::new(7, 7), |
149 | /// Point::new(5, 8), |
150 | /// Point::new(10, 10), |
151 | /// ]; |
152 | /// |
153 | /// let mut polyline = Polyline::new(&points); |
154 | /// |
155 | /// polyline.translate_mut(Point::new(10, 12)); |
156 | /// |
157 | /// assert_eq!(polyline.bounding_box().top_left, Point::new(15, 19)); |
158 | /// ``` |
159 | fn translate_mut(&mut self, by: Point) -> &mut Self { |
160 | self.translate += by; |
161 | |
162 | self |
163 | } |
164 | } |
165 | |
166 | #[cfg (test)] |
167 | mod tests { |
168 | use super::*; |
169 | use crate::geometry::{Point, Size}; |
170 | |
171 | // A "heartbeat" shaped polyline |
172 | pub(in crate::primitives::polyline) const HEARTBEAT: [Point; 10] = [ |
173 | Point::new(10, 64), |
174 | Point::new(50, 64), |
175 | Point::new(60, 44), |
176 | Point::new(70, 64), |
177 | Point::new(80, 64), |
178 | Point::new(90, 74), |
179 | Point::new(100, 10), |
180 | Point::new(110, 84), |
181 | Point::new(120, 64), |
182 | Point::new(300, 64), |
183 | ]; |
184 | |
185 | // Smaller test pattern for mock display |
186 | pub(in crate::primitives::polyline) const SMALL: [Point; 4] = [ |
187 | Point::new(2, 5), |
188 | Point::new(5, 2), |
189 | Point::new(10, 5), |
190 | Point::new(15, 2), |
191 | ]; |
192 | |
193 | #[test ] |
194 | fn special_case_dimensions() { |
195 | assert_eq!(Polyline::new(&[]).bounding_box(), Rectangle::zero(),); |
196 | |
197 | assert_eq!( |
198 | Polyline::new(&[Point::new(15, 17)]).bounding_box(), |
199 | Rectangle::new(Point::new(15, 17), Size::zero()) |
200 | ); |
201 | } |
202 | |
203 | #[test ] |
204 | fn positive_dimensions() { |
205 | let polyline = Polyline::new(&HEARTBEAT); |
206 | |
207 | let bb = polyline.bounding_box(); |
208 | |
209 | assert_eq!( |
210 | bb, |
211 | Rectangle::with_corners(Point::new(10, 10), Point::new(300, 84)) |
212 | ); |
213 | } |
214 | |
215 | #[test ] |
216 | fn negative_dimensions() { |
217 | let mut negative: [Point; 10] = [Point::zero(); 10]; |
218 | |
219 | for (i, v) in HEARTBEAT.iter().enumerate() { |
220 | negative[i] = *v - Point::new(100, 100); |
221 | } |
222 | |
223 | let polyline = Polyline::new(&negative); |
224 | |
225 | let bb = polyline.bounding_box(); |
226 | |
227 | assert_eq!( |
228 | bb, |
229 | Rectangle::with_corners(Point::new(-90, -90), Point::new(200, -16)) |
230 | ); |
231 | } |
232 | |
233 | #[test ] |
234 | fn transformed_dimensions() { |
235 | let polyline = Polyline::new(&HEARTBEAT).translate(Point::new(-100, -100)); |
236 | |
237 | let bb = polyline.bounding_box(); |
238 | |
239 | assert_eq!( |
240 | bb, |
241 | Rectangle::with_corners(Point::new(-90, -90), Point::new(200, -16)) |
242 | ); |
243 | } |
244 | |
245 | #[test ] |
246 | fn translate_does_not_modify_size() { |
247 | let points = [ |
248 | Point::new(5, 10), |
249 | Point::new(7, 7), |
250 | Point::new(5, 8), |
251 | Point::new(10, 10), |
252 | ]; |
253 | |
254 | let polyline = Polyline::new(&points); |
255 | let moved = polyline.translate(Point::new(10, 12)); |
256 | |
257 | assert_eq!(moved.bounding_box().size, polyline.bounding_box().size); |
258 | } |
259 | |
260 | #[test ] |
261 | fn translate_translated() { |
262 | let points = [ |
263 | Point::new(5, 10), |
264 | Point::new(7, 7), |
265 | Point::new(5, 8), |
266 | Point::new(10, 10), |
267 | ]; |
268 | |
269 | let polyline = Polyline::new(&points); |
270 | let moved = polyline.translate(Point::new(10, 12)); |
271 | let moved2 = moved.translate(Point::new(10, 12)); |
272 | |
273 | assert_eq!( |
274 | moved.bounding_box(), |
275 | polyline.bounding_box().translate(Point::new(10, 12)) |
276 | ); |
277 | assert_eq!( |
278 | moved2.bounding_box(), |
279 | polyline.bounding_box().translate(Point::new(20, 24)) |
280 | ); |
281 | } |
282 | } |
283 | |