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