1use alloc::{boxed::Box, vec};
2
3use embedded_graphics_core::{prelude::*, primitives::Rectangle};
4
5use crate::utils::center_offset;
6
7#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
11#[derive(Clone)]
12pub struct Canvas<C> {
13 pub canvas: Size,
15 pub pixels: Box<[Option<C>]>,
17}
18
19impl<C: PixelColor> Canvas<C> {
20 pub fn new(canvas: Size) -> Self {
26 Self {
27 canvas,
28 pixels: new_pixels(canvas, None),
29 }
30 }
31
32 pub fn with_default_color(canvas: Size, default_color: C) -> Self {
38 Self {
39 canvas,
40 pixels: new_pixels(canvas, default_color.into()),
41 }
42 }
43
44 fn point_to_index(&self, point: Point) -> Option<usize> {
46 point_to_index(self.canvas, Point::zero(), point)
47 }
48
49 fn index_to_point(&self, index: usize) -> Option<Point> {
50 index_to_point(self.canvas, index)
51 }
52
53 pub fn center(&self) -> Point {
55 Point::zero() + center_offset(self.canvas)
56 }
57}
58
59impl<C: PixelColor> Canvas<C> {
60 pub fn crop(&self, area: &Rectangle) -> Option<Canvas<C>> {
69 let mut new = Canvas::new(area.size);
70
71 let area_bottom_right = area.bottom_right()?;
74
75 let new_pixels = self.pixels.iter().enumerate().filter_map(|(index, color)| {
76 let color = match color {
77 Some(color) => *color,
78 None => return None,
79 };
80
81 let point = self.index_to_point(index).unwrap();
83
84 if point >= area.top_left && point <= area_bottom_right {
86 let pixel = Pixel(point - area.top_left, color);
88
89 Some(pixel)
90 } else {
91 None
92 }
93 });
94
95 new.draw_iter(new_pixels).ok().map(|_| new)
96 }
97
98 pub fn place_at(&self, top_left: Point) -> CanvasAt<C> {
100 CanvasAt {
101 top_left,
102 canvas: self.canvas,
103 pixels: self.pixels.clone(),
104 }
105 }
106
107 pub fn place_center(&self, center: Point) -> CanvasAt<C> {
109 let top_left = center - center_offset(self.canvas);
110
111 self.place_at(top_left)
112 }
113}
114
115impl<C> OriginDimensions for Canvas<C> {
116 fn size(&self) -> Size {
117 self.canvas
118 }
119}
120
121impl<C: PixelColor> DrawTarget for Canvas<C> {
122 type Color = C;
123 type Error = core::convert::Infallible;
124
125 fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
126 where
127 I: IntoIterator<Item = Pixel<Self::Color>>,
128 {
129 for Pixel(point, color) in pixels.into_iter() {
130 if let Some(index) = self.point_to_index(point) {
131 self.pixels[index] = Some(color);
132 }
133 }
134
135 Ok(())
136 }
137}
138
139#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
141#[derive(Debug, Clone)]
142
143pub struct CanvasAt<C> {
144 pub top_left: Point,
146 pub canvas: Size,
148 pub pixels: Box<[Option<C>]>,
150}
151
152impl<C: PixelColor> CanvasAt<C> {
153 pub fn new(top_left: Point, canvas: Size) -> Self {
159 let pixels = new_pixels(canvas, None);
160
161 Self {
162 top_left,
163 canvas,
164 pixels,
165 }
166 }
167
168 pub fn with_default_color(top_left: Point, canvas: Size, default_color: C) -> Self {
174 let pixel_count = canvas.width as usize * canvas.height as usize;
175
176 let pixels = vec![Some(default_color); pixel_count].into_boxed_slice();
177
178 Self {
179 top_left,
180 canvas,
181 pixels,
182 }
183 }
184
185 pub fn with_center(center: Point, canvas: Size) -> Self {
187 let top_left = center - center_offset(canvas);
188
189 Self::new(top_left, canvas)
190 }
191
192 pub fn center(&self) -> Point {
194 self.bounding_box().center()
195 }
196
197 pub fn get_pixel(&self, point: Point) -> Option<C> {
201 self.point_to_index(point)
202 .and_then(|index| self.pixels.get(index).copied().flatten())
203 }
204
205 fn point_to_index(&self, point: Point) -> Option<usize> {
207 point_to_index(self.canvas, self.top_left, point)
208 }
209
210 fn index_to_point(&self, index: usize) -> Option<Point> {
211 index_to_point(self.canvas, index).map(|point| point + self.top_left)
213 }
214
215 pub fn crop(&self, area: &Rectangle) -> Option<CanvasAt<C>> {
224 let mut new = CanvasAt::new(area.top_left, area.size);
225
226 let area_bottom_right = area.bottom_right()?;
229
230 let new_pixels = self.pixels.iter().enumerate().filter_map(|(index, color)| {
231 let color = match color {
232 Some(color) => *color,
233 None => return None,
234 };
235
236 let point = self.index_to_point(index).expect("Will never fail");
237
238 if point >= area.top_left && point <= area_bottom_right {
240 let pixel = Pixel(point, color);
241
242 Some(pixel)
243 } else {
244 None
245 }
246 });
247
248 new.draw_iter(new_pixels).ok().map(|_| new)
249 }
250}
251
252impl<C: PixelColor> Dimensions for CanvasAt<C> {
253 fn bounding_box(&self) -> Rectangle {
254 Rectangle::new(self.top_left, self.canvas)
255 }
256}
257
258impl<C: PixelColor> DrawTarget for CanvasAt<C> {
259 type Color = C;
260 type Error = core::convert::Infallible;
261
262 fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
263 where
264 I: IntoIterator<Item = Pixel<Self::Color>>,
265 {
266 for Pixel(point, color) in pixels.into_iter() {
267 if let Some(index) = self.point_to_index(point) {
268 self.pixels[index] = Some(color);
269 }
270 }
271
272 Ok(())
273 }
274}
275
276impl<C> Drawable for CanvasAt<C>
277where
278 C: PixelColor,
279{
280 type Color = C;
281 type Output = ();
282
283 fn draw<D>(&self, target: &mut D) -> Result<Self::Output, D::Error>
284 where
285 D: DrawTarget<Color = C>,
286 {
287 let pixels_iter = self.bounding_box().points().filter_map(|point| {
288 self.get_pixel(point).map(|color| Pixel(point, color))
290 });
291
292 target.draw_iter(pixels_iter)
293 }
294}
295
296#[cfg(feature = "transform")]
297#[cfg_attr(docsrs, doc(cfg(feature = "transform")))]
298impl<C: PixelColor> embedded_graphics::transform::Transform for CanvasAt<C> {
299 fn translate(&self, by: Point) -> Self {
300 Self {
301 top_left: self.top_left + by,
303 canvas: self.canvas,
304 pixels: self.pixels.clone(),
305 }
306 }
307
308 fn translate_mut(&mut self, by: Point) -> &mut Self {
309 self.top_left += by;
310
311 self
312 }
313}
314
315fn point_to_index(size: Size, top_left_offset: Point, point: Point) -> Option<usize> {
318 if let Ok((x, y)) = <(u32, u32)>::try_from(point - top_left_offset) {
320 if x < size.width && y < size.height {
321 return Some((x + y * size.width) as usize);
322 }
323 }
324
325 None
326}
327
328fn index_to_point(size: Size, index: usize) -> Option<Point> {
329 let x = index as i32 % size.width as i32;
330 let y = index as i32 / size.width as i32;
331 let point = Point { x, y };
332
333 Some(point)
334}
335
336fn new_pixels<C: PixelColor>(size: Size, color: Option<C>) -> Box<[Option<C>]> {
337 let pixel_count = size.width as usize * size.height as usize;
338
339 vec![color; pixel_count].into_boxed_slice()
340}
341
342#[cfg(test)]
343mod test {
344 use embedded_graphics_core::pixelcolor::BinaryColor;
345
346 use super::*;
347
348 #[test]
349 fn test_index_to_point() {
350 let canvas = Canvas::<BinaryColor>::new(Size {
351 width: 320,
352 height: 240,
353 });
354
355 {
356 let center = Point::new(160, 120);
357 let center_index = canvas.point_to_index(center).expect("Inside the canvas");
358
359 assert_eq!(
360 center,
361 canvas
362 .index_to_point(center_index)
363 .expect("Should fetch the index")
364 );
365 }
366 {
367 let bottom_right = Point::new(320 - 1, 240 - 1);
368 let br_index = canvas
369 .point_to_index(bottom_right)
370 .expect("Inside the canvas");
371
372 assert_eq!(
373 bottom_right,
374 canvas
375 .index_to_point(br_index)
376 .expect("Should fetch the index")
377 );
378 }
379 {
380 let top_left = Point::new(0, 0);
381 let tl_index = canvas.point_to_index(top_left).expect("Inside the canvas");
382
383 assert_eq!(
384 top_left,
385 canvas
386 .index_to_point(tl_index)
387 .expect("Should fetch the index")
388 );
389 }
390
391 {
392 let bottom_left = Point::new(0, 240 - 1);
393 let bl_index = canvas
394 .point_to_index(bottom_left)
395 .expect("Inside the canvas");
396
397 assert_eq!(
398 bottom_left,
399 canvas
400 .index_to_point(bl_index)
401 .expect("Should fetch the index")
402 );
403 }
404 {
405 let top_right = Point::new(320 - 1, 0);
406 let tr_index = canvas.point_to_index(top_right).expect("Inside the canvas");
407
408 assert_eq!(
409 top_right,
410 canvas
411 .index_to_point(tr_index)
412 .expect("Should fetch the index")
413 );
414 }
415 }
416}