use alloc::{boxed::Box, vec};
use embedded_graphics_core::{prelude::*, primitives::Rectangle};
use crate::utils::center_offset;
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
#[derive(Clone)]
pub struct Canvas<C> {
pub canvas: Size,
pub pixels: Box<[Option<C>]>,
}
impl<C: PixelColor> Canvas<C> {
pub fn new(canvas: Size) -> Self {
Self {
canvas,
pixels: new_pixels(canvas, None),
}
}
pub fn with_default_color(canvas: Size, default_color: C) -> Self {
Self {
canvas,
pixels: new_pixels(canvas, default_color.into()),
}
}
fn point_to_index(&self, point: Point) -> Option<usize> {
point_to_index(self.canvas, Point::zero(), point)
}
fn index_to_point(&self, index: usize) -> Option<Point> {
index_to_point(self.canvas, index)
}
pub fn center(&self) -> Point {
Point::zero() + center_offset(self.canvas)
}
}
impl<C: PixelColor> Canvas<C> {
pub fn crop(&self, area: &Rectangle) -> Option<Canvas<C>> {
let mut new = Canvas::new(area.size);
let area_bottom_right = area.bottom_right()?;
let new_pixels = self.pixels.iter().enumerate().filter_map(|(index, color)| {
let color = match color {
Some(color) => *color,
None => return None,
};
let point = self.index_to_point(index).unwrap();
if point >= area.top_left && point <= area_bottom_right {
let pixel = Pixel(point - area.top_left, color);
Some(pixel)
} else {
None
}
});
new.draw_iter(new_pixels).ok().map(|_| new)
}
pub fn place_at(&self, top_left: Point) -> CanvasAt<C> {
CanvasAt {
top_left,
canvas: self.canvas,
pixels: self.pixels.clone(),
}
}
pub fn place_center(&self, center: Point) -> CanvasAt<C> {
let top_left = center - center_offset(self.canvas);
self.place_at(top_left)
}
}
impl<C> OriginDimensions for Canvas<C> {
fn size(&self) -> Size {
self.canvas
}
}
impl<C: PixelColor> DrawTarget for Canvas<C> {
type Color = C;
type Error = core::convert::Infallible;
fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
where
I: IntoIterator<Item = Pixel<Self::Color>>,
{
for Pixel(point, color) in pixels.into_iter() {
if let Some(index) = self.point_to_index(point) {
self.pixels[index] = Some(color);
}
}
Ok(())
}
}
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
#[derive(Debug, Clone)]
pub struct CanvasAt<C> {
pub top_left: Point,
pub canvas: Size,
pub pixels: Box<[Option<C>]>,
}
impl<C: PixelColor> CanvasAt<C> {
pub fn new(top_left: Point, canvas: Size) -> Self {
let pixels = new_pixels(canvas, None);
Self {
top_left,
canvas,
pixels,
}
}
pub fn with_default_color(top_left: Point, canvas: Size, default_color: C) -> Self {
let pixel_count = canvas.width as usize * canvas.height as usize;
let pixels = vec![Some(default_color); pixel_count].into_boxed_slice();
Self {
top_left,
canvas,
pixels,
}
}
pub fn with_center(center: Point, canvas: Size) -> Self {
let top_left = center - center_offset(canvas);
Self::new(top_left, canvas)
}
pub fn center(&self) -> Point {
self.bounding_box().center()
}
pub fn get_pixel(&self, point: Point) -> Option<C> {
self.point_to_index(point)
.and_then(|index| self.pixels.get(index).copied().flatten())
}
fn point_to_index(&self, point: Point) -> Option<usize> {
point_to_index(self.canvas, self.top_left, point)
}
fn index_to_point(&self, index: usize) -> Option<Point> {
index_to_point(self.canvas, index).map(|point| point + self.top_left)
}
pub fn crop(&self, area: &Rectangle) -> Option<CanvasAt<C>> {
let mut new = CanvasAt::new(area.top_left, area.size);
let area_bottom_right = area.bottom_right()?;
let new_pixels = self.pixels.iter().enumerate().filter_map(|(index, color)| {
let color = match color {
Some(color) => *color,
None => return None,
};
let point = self.index_to_point(index).expect("Will never fail");
if point >= area.top_left && point <= area_bottom_right {
let pixel = Pixel(point, color);
Some(pixel)
} else {
None
}
});
new.draw_iter(new_pixels).ok().map(|_| new)
}
}
impl<C: PixelColor> Dimensions for CanvasAt<C> {
fn bounding_box(&self) -> Rectangle {
Rectangle::new(self.top_left, self.canvas)
}
}
impl<C: PixelColor> DrawTarget for CanvasAt<C> {
type Color = C;
type Error = core::convert::Infallible;
fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
where
I: IntoIterator<Item = Pixel<Self::Color>>,
{
for Pixel(point, color) in pixels.into_iter() {
if let Some(index) = self.point_to_index(point) {
self.pixels[index] = Some(color);
}
}
Ok(())
}
}
impl<C> Drawable for CanvasAt<C>
where
C: PixelColor,
{
type Color = C;
type Output = ();
fn draw<D>(&self, target: &mut D) -> Result<Self::Output, D::Error>
where
D: DrawTarget<Color = C>,
{
let pixels_iter = self.bounding_box().points().filter_map(|point| {
self.get_pixel(point).map(|color| Pixel(point, color))
});
target.draw_iter(pixels_iter)
}
}
#[cfg(feature = "transform")]
#[cfg_attr(docsrs, doc(cfg(feature = "transform")))]
impl<C: PixelColor> embedded_graphics::transform::Transform for CanvasAt<C> {
fn translate(&self, by: Point) -> Self {
Self {
top_left: self.top_left + by,
canvas: self.canvas,
pixels: self.pixels.clone(),
}
}
fn translate_mut(&mut self, by: Point) -> &mut Self {
self.top_left += by;
self
}
}
fn point_to_index(size: Size, top_left_offset: Point, point: Point) -> Option<usize> {
if let Ok((x, y)) = <(u32, u32)>::try_from(point - top_left_offset) {
if x < size.width && y < size.height {
return Some((x + y * size.width) as usize);
}
}
None
}
fn index_to_point(size: Size, index: usize) -> Option<Point> {
let x = index as i32 % size.width as i32;
let y = index as i32 / size.width as i32;
let point = Point { x, y };
Some(point)
}
fn new_pixels<C: PixelColor>(size: Size, color: Option<C>) -> Box<[Option<C>]> {
let pixel_count = size.width as usize * size.height as usize;
vec![color; pixel_count].into_boxed_slice()
}
#[cfg(test)]
mod test {
use embedded_graphics_core::pixelcolor::BinaryColor;
use super::*;
#[test]
fn test_index_to_point() {
let canvas = Canvas::<BinaryColor>::new(Size {
width: 320,
height: 240,
});
{
let center = Point::new(160, 120);
let center_index = canvas.point_to_index(center).expect("Inside the canvas");
assert_eq!(
center,
canvas
.index_to_point(center_index)
.expect("Should fetch the index")
);
}
{
let bottom_right = Point::new(320 - 1, 240 - 1);
let br_index = canvas
.point_to_index(bottom_right)
.expect("Inside the canvas");
assert_eq!(
bottom_right,
canvas
.index_to_point(br_index)
.expect("Should fetch the index")
);
}
{
let top_left = Point::new(0, 0);
let tl_index = canvas.point_to_index(top_left).expect("Inside the canvas");
assert_eq!(
top_left,
canvas
.index_to_point(tl_index)
.expect("Should fetch the index")
);
}
{
let bottom_left = Point::new(0, 240 - 1);
let bl_index = canvas
.point_to_index(bottom_left)
.expect("Inside the canvas");
assert_eq!(
bottom_left,
canvas
.index_to_point(bl_index)
.expect("Should fetch the index")
);
}
{
let top_right = Point::new(320 - 1, 0);
let tr_index = canvas.point_to_index(top_right).expect("Inside the canvas");
assert_eq!(
top_right,
canvas
.index_to_point(tr_index)
.expect("Should fetch the index")
);
}
}
}