| 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 | |