embedded_canvas/consts.rs
1//! `Canvas`es implemented with const generics
2use embedded_graphics_core::{
3 prelude::{
4 Dimensions, DrawTarget, Drawable, OriginDimensions, Pixel, PixelColor, Point, PointsIter,
5 Size,
6 },
7 primitives::Rectangle,
8};
9
10use crate::utils::center_offset;
11
12/// Canvas on which you can draw but it's not drawable on the display yet.
13/// Implemented using [const generics][const_generics_rfc].
14///
15/// Draw on the [`CCanvas`] using origin of [`Point::zero()`].
16///
17/// The width (`W`) and height (`H`) constants of the [`CCanvas`]
18/// should less than [`u32::MAX`] as [`Size`] uses [`u32`].
19///
20/// [const_generics_rfc]: https://rust-lang.github.io/rfcs/2000-const-generics.html
21pub struct CCanvas<C: PixelColor, const W: usize, const H: usize> {
22 // we also store the size for working with embedded-graphics
23 pub size: Size,
24 pub pixels: [[Option<C>; H]; W],
25}
26
27impl<C, const W: usize, const H: usize> Default for CCanvas<C, W, H>
28where
29 C: PixelColor,
30{
31 fn default() -> Self {
32 Self::new()
33 }
34}
35
36impl<C, const W: usize, const H: usize> CCanvas<C, W, H>
37where
38 C: PixelColor,
39{
40 /// Create a new blank [`CCanvas`].
41 ///
42 /// # Panics
43 ///
44 /// If either the width (`W`) or heigh (`H`) is larger than [`u32::MAX`]
45 /// due to the internal [`Size`] used for implementing [`OriginDimensions`].
46 pub fn new() -> Self {
47 Self {
48 size: Size::new(W as u32, H as u32),
49 pixels: [[None; H]; W],
50 }
51 }
52
53 /// Create a [`CCanvas`] filled with a default color.
54 ///
55 /// # Panics
56 ///
57 /// If either the width (`W`) or heigh (`H`) is larger than [`u32::MAX`]
58 /// due to the internal [`Size`] used for implementing [`OriginDimensions`].
59 pub fn with_default_color(default_color: C) -> Self {
60 Self {
61 size: Size::new(W as u32, H as u32),
62 pixels: [[Some(default_color); H]; W],
63 }
64 }
65
66 /// Returns the color of the pixel at a given [`Point`].
67 ///
68 /// Returns [`None`] if the [`Point`] is outside of the [`CCanvas`].
69 pub fn get_pixel(&self, point: Point) -> Option<C> {
70 // `Point` implements TryFrom only for `u32` & `i32`.
71 let x = usize::try_from(point.x).ok()?;
72 let y = usize::try_from(point.y).ok()?;
73
74 self.pixels
75 .get(x)
76 .and_then(|x_row| x_row.get(y))
77 .copied()
78 .flatten()
79 }
80
81 /// Returns the center of [`Size`] of the [`CCanvas`].
82 pub fn center(&self) -> Point {
83 Point::zero() + center_offset(self.size)
84 }
85
86 /// Create a new cropped [`CCanvas`].
87 ///
88 /// This method takes into account the top left [`Point`] of the `area`
89 /// you'd like to crop relative to the [`CCanvas`] itself.
90 //
91 /// If the width or height of the [`Rectangle`] is `0`, this method will
92 /// return [`None`] (see [`Rectangle::bottom_right()`])
93 // todo: make safer
94 pub fn crop<const NW: usize, const NH: usize>(
95 &self,
96 area: &Rectangle,
97 ) -> Option<CCanvas<C, NW, NH>> {
98 let mut new = CCanvas::<C, NW, NH>::new();
99
100 // returns None when width or height is `0`
101 // it's safe to return `None` for Canvas too!
102 let area_bottom_right = area.bottom_right()?;
103
104 let new_pixels = self.pixels.iter().enumerate().flat_map(|(x, x_row)| {
105 x_row.iter().enumerate().filter_map(move |(y, color)| {
106 let color = match color {
107 Some(color) => *color,
108 None => return None,
109 };
110
111 // Canvas always starts from `Point::zero()`
112 // todo: make safer
113 let point = Point::new(x as i32, y as i32);
114
115 // for here on, we should compare the point based on the area we want to crop
116 if point >= area.top_left && point <= area_bottom_right {
117 // remove the area top_left to make the origin at `Point::zero()` for the cropped part
118 let pixel = Pixel(point - area.top_left, color);
119
120 Some(pixel)
121 } else {
122 None
123 }
124 })
125 });
126
127 new.draw_iter(new_pixels).ok().map(|_| new)
128 }
129
130 /// Sets the place with top left offset where the canvas will be drawn to the display.
131 pub fn place_at(&self, top_left: Point) -> CCanvasAt<C, W, H> {
132 CCanvasAt {
133 top_left,
134 size: self.size,
135 pixels: self.pixels,
136 }
137 }
138
139 /// Sets the center of the [`CCanvas`] where it will be drawn to the display.
140 pub fn place_center(&self, center: Point) -> CCanvasAt<C, W, H> {
141 let top_left = center - center_offset(self.size);
142
143 self.place_at(top_left)
144 }
145}
146
147impl<C: PixelColor, const W: usize, const H: usize> OriginDimensions for CCanvas<C, W, H> {
148 fn size(&self) -> Size {
149 self.size
150 }
151}
152
153impl<C: PixelColor, const W: usize, const H: usize> DrawTarget for CCanvas<C, W, H> {
154 type Color = C;
155 type Error = core::convert::Infallible;
156
157 fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
158 where
159 I: IntoIterator<Item = Pixel<Self::Color>>,
160 {
161 for Pixel(point, color) in pixels.into_iter() {
162 // if Pixel is outside of the canvas, skip it
163 if let (Some(x), Some(y)) =
164 (usize::try_from(point.x).ok(), usize::try_from(point.y).ok())
165 {
166 self.pixels[x][y] = Some(color);
167 }
168 }
169
170 Ok(())
171 }
172}
173
174/// Canvas which is drawable at the provided [`Point`] (location) on the display.
175#[derive(Debug, Clone, Copy)]
176pub struct CCanvasAt<C: PixelColor, const W: usize, const H: usize> {
177 /// The top left offset where the [`CCanvasAt`] will be drawn to the display.
178 pub top_left: Point,
179 /// The size of the [`CCanvasAt`].
180 size: Size,
181 /// The pixels of the [`CCanvasAt`].
182 pub pixels: [[Option<C>; H]; W],
183}
184
185impl<C, const W: usize, const H: usize> CCanvasAt<C, W, H>
186where
187 C: PixelColor,
188{
189 /// Create a new blank [`CCanvasAt`].
190 ///
191 /// # Panics
192 ///
193 /// If either of width (`W`) or heigh (`H`) is larger than [`u32::MAX`]
194 /// due to the internal [`Size`] used for implementing [`Dimensions`].
195 pub fn new(top_left: Point) -> Self {
196 Self {
197 top_left,
198 size: Size::new(W as u32, H as u32),
199 pixels: [[None; H]; W],
200 }
201 }
202
203 /// Create a [`CCanvasAt`] filled with a default color.
204 ///
205 /// If either of width (`W`) or heigh (`H`) is larger than [`u32::MAX`]
206 /// due to the internal [`Size`] used for implementing [`Dimensions`].
207 pub fn with_default_color(top_left: Point, default_color: C) -> Self {
208 Self {
209 top_left,
210 size: Size::new(W as u32, H as u32),
211 pixels: [[Some(default_color); H]; W],
212 }
213 }
214
215 /// Create a new blank [`CCanvasAt`] with a set center on the display.
216 ///
217 /// # Panics
218 ///
219 /// If either of width (`W`) or heigh (`H`) is larger than [`u32::MAX`]
220 /// due to the internal [`Size`] used for implementing [`Dimensions`].
221 pub fn with_center(center: Point) -> Self {
222 let top_left = center - center_offset(Size::new(W as u32, H as u32));
223
224 Self::new(top_left)
225 }
226
227 /// Returns the center of the bounding box.
228 pub fn center(&self) -> Point {
229 self.bounding_box().center()
230 }
231
232 /// Returns the color of the pixel at a given [`Point`].
233 ///
234 /// Returns [`None`] if the [`Point`] is outside of the [`CCanvasAt`].
235 pub fn get_pixel(&self, point: Point) -> Option<C> {
236 // account for the top_left offset of the CanvasAt
237 let point_adjusted = point - self.top_left;
238
239 // `Point` implements TryFrom only for `u32` & `i32`.
240 let x = usize::try_from(point_adjusted.x).ok()?;
241 let y = usize::try_from(point_adjusted.y).ok()?;
242
243 self.pixels
244 .get(x)
245 .and_then(|x_row| x_row.get(y))
246 .copied()
247 .flatten()
248 }
249
250 // /// Create a new cropped [`CCanvasAt`].
251 // ///
252 // /// This method takes into account the top left [`Point`] of the `area`
253 // /// you'd like to crop relative to the **display**.
254 // ///
255 // /// If the width or height of the [`Rectangle`] is `0`, this method will
256 // /// return [`None`] (see [`Rectangle::bottom_right()`])
257 // // todo: make safer
258 // pub fn crop(&self, area: &Rectangle) -> Option<CanvasAt<C>> {
259 // let mut new = CanvasAt::new(area.top_left, area.size);
260
261 // // returns None when width or height is `0`
262 // // it's safe to return `None` for Canvas too!
263 // let area_bottom_right = area.bottom_right()?;
264
265 // let new_pixels = self.pixels.iter().enumerate().filter_map(|(index, color)| {
266 // let color = match color {
267 // Some(color) => *color,
268 // None => return None,
269 // };
270
271 // let point = self.index_to_point(index).expect("Will never fail");
272
273 // // for here on, we should compare the point based on the area we want to crop
274 // if point >= area.top_left && point <= area_bottom_right {
275 // let pixel = Pixel(point, color);
276
277 // Some(pixel)
278 // } else {
279 // None
280 // }
281 // });
282
283 // new.draw_iter(new_pixels).ok().map(|_| new)
284 // }
285}
286
287impl<C: PixelColor, const W: usize, const H: usize> Dimensions for CCanvasAt<C, W, H> {
288 fn bounding_box(&self) -> Rectangle {
289 Rectangle::new(self.top_left, self.size)
290 }
291}
292
293impl<C: PixelColor, const W: usize, const H: usize> DrawTarget for CCanvasAt<C, W, H> {
294 type Color = C;
295 type Error = core::convert::Infallible;
296
297 fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
298 where
299 I: IntoIterator<Item = Pixel<Self::Color>>,
300 {
301 for Pixel(point, color) in pixels.into_iter() {
302 // account for the top_left offset of the CanvasAt
303 let point_adjusted = point - self.top_left;
304
305 // skip if:
306 // - Pixel's Point is outside of the canvas
307 // - Pixel's Point is not `usize` representable
308 if let (Some(x), Some(y)) = (
309 usize::try_from(point_adjusted.x).ok(),
310 usize::try_from(point_adjusted.y).ok(),
311 ) {
312 self.pixels[x][y] = Some(color);
313 };
314 }
315
316 Ok(())
317 }
318}
319
320impl<C, const W: usize, const H: usize> Drawable for CCanvasAt<C, W, H>
321where
322 C: PixelColor,
323{
324 type Color = C;
325 type Output = ();
326
327 fn draw<D>(&self, target: &mut D) -> Result<Self::Output, D::Error>
328 where
329 D: DrawTarget<Color = C>,
330 {
331 let pixels_iter = self.bounding_box().points().filter_map(|point| {
332 // for the drawing position we need to account for the top_left offset of the drawn display
333 self.get_pixel(point).map(|color| Pixel(point, color))
334 });
335
336 target.draw_iter(pixels_iter)
337 }
338}
339
340#[cfg(feature = "transform")]
341#[cfg_attr(docsrs, doc(cfg(feature = "transform")))]
342impl<C: PixelColor, const W: usize, const H: usize> embedded_graphics::transform::Transform
343 for CCanvasAt<C, W, H>
344{
345 fn translate(&self, by: Point) -> Self {
346 Self {
347 // update the CanvasAt top_left!
348 top_left: self.top_left + by,
349 size: self.size,
350 pixels: self.pixels,
351 }
352 }
353
354 fn translate_mut(&mut self, by: Point) -> &mut Self {
355 self.top_left += by;
356
357 self
358 }
359}