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}