use embedded_graphics::{pixelcolor::Rgb565, prelude::*, primitives::Rectangle};
use heapless::Vec;
pub struct Sprite<'a> {
width: u16,
height: u16,
pixels: &'a mut [Rgb565],
}
impl<'a> Sprite<'a> {
pub fn new(width: u16, height: u16, pixels: &'a mut [Rgb565]) -> Result<Self, SpriteError> {
let required_len = usize::from(width) * usize::from(height);
if pixels.len() < required_len {
return Err(SpriteError::BufferTooSmall);
}
Ok(Self {
width,
height,
pixels: &mut pixels[..required_len],
})
}
#[must_use]
pub const fn width(&self) -> u16 {
self.width
}
#[must_use]
pub const fn height(&self) -> u16 {
self.height
}
#[must_use]
pub fn bounds(&self) -> Rectangle {
Rectangle::new(
Point::zero(),
Size::new(u32::from(self.width), u32::from(self.height)),
)
}
pub fn clear(&mut self, color: Rgb565) {
self.pixels.fill(color);
}
#[must_use]
pub fn pixels(&self) -> &[Rgb565] {
self.pixels
}
pub fn pixels_mut(&mut self) -> &mut [Rgb565] {
self.pixels
}
#[must_use]
pub fn pixel(&self, point: Point) -> Option<Rgb565> {
self.pixel_index(point).map(|index| self.pixels[index])
}
pub fn copy_from_slice(&mut self, pixels: &[Rgb565]) -> Result<(), SpriteError> {
if pixels.len() < self.pixels.len() {
return Err(SpriteError::BufferTooSmall);
}
self.pixels.copy_from_slice(&pixels[..self.pixels.len()]);
Ok(())
}
pub fn draw_at<D>(&self, target: &mut D, top_left: Point) -> Result<(), D::Error>
where
D: DrawTarget<Color = Rgb565>,
{
target.fill_contiguous(
&Rectangle::new(top_left, self.size()),
self.pixels.iter().copied(),
)
}
pub fn draw_region_at<D>(
&self,
target: &mut D,
source_area: &Rectangle,
dest_top_left: Point,
) -> Result<(), D::Error>
where
D: DrawTarget<Color = Rgb565>,
{
let clipped = source_area.intersection(&self.bounds());
if clipped.is_zero_sized() {
return Ok(());
}
let offset = clipped.top_left - source_area.top_left;
let target_area = Rectangle::new(dest_top_left + offset, clipped.size);
target.fill_contiguous(&target_area, self.region_pixels(&clipped))
}
pub fn region_pixels(&self, area: &Rectangle) -> SpriteRegionPixels<'_> {
let clipped = area.intersection(&self.bounds());
SpriteRegionPixels {
pixels: self.pixels,
stride: usize::from(self.width),
x: clipped.top_left.x.max(0) as usize,
y: clipped.top_left.y.max(0) as usize,
width: clipped.size.width as usize,
height: clipped.size.height as usize,
current_x: 0,
current_y: 0,
}
}
fn pixel_index(&self, point: Point) -> Option<usize> {
if !self.bounds().contains(point) {
return None;
}
Some(point.y as usize * usize::from(self.width) + point.x as usize)
}
}
pub struct SpriteRegionPixels<'a> {
pixels: &'a [Rgb565],
stride: usize,
x: usize,
y: usize,
width: usize,
height: usize,
current_x: usize,
current_y: usize,
}
impl Iterator for SpriteRegionPixels<'_> {
type Item = Rgb565;
fn next(&mut self) -> Option<Self::Item> {
if self.current_y >= self.height {
return None;
}
let index = (self.y + self.current_y) * self.stride + self.x + self.current_x;
let color = self.pixels.get(index).copied();
self.current_x += 1;
if self.current_x >= self.width {
self.current_x = 0;
self.current_y += 1;
}
color
}
}
pub struct DirtyRegions<const N: usize> {
regions: Vec<Rectangle, N>,
}
impl<const N: usize> DirtyRegions<N> {
#[must_use]
pub const fn new() -> Self {
Self {
regions: Vec::new(),
}
}
pub fn clear(&mut self) {
self.regions.clear();
}
#[must_use]
pub fn len(&self) -> usize {
self.regions.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.regions.is_empty()
}
pub fn push_clipped(
&mut self,
region: Rectangle,
bounds: Rectangle,
) -> Result<(), SpriteError> {
let clipped = region.intersection(&bounds);
if clipped.is_zero_sized() {
return Ok(());
}
self.regions
.push(clipped)
.map_err(|_| SpriteError::DirtyRegionCapacity)
}
pub fn iter(&self) -> impl Iterator<Item = &Rectangle> {
self.regions.iter()
}
pub fn flush_sprite<D>(&self, sprite: &Sprite<'_>, target: &mut D) -> Result<(), D::Error>
where
D: DrawTarget<Color = Rgb565>,
{
for region in self.iter() {
sprite.draw_region_at(target, region, region.top_left)?;
}
Ok(())
}
}
impl<const N: usize> Default for DirtyRegions<N> {
fn default() -> Self {
Self::new()
}
}
impl DrawTarget for Sprite<'_> {
type Color = Rgb565;
type Error = SpriteError;
fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
where
I: IntoIterator<Item = Pixel<Self::Color>>,
{
for Pixel(point, color) in pixels {
if let Some(index) = self.pixel_index(point) {
self.pixels[index] = color;
}
}
Ok(())
}
fn fill_solid(&mut self, area: &Rectangle, color: Self::Color) -> Result<(), Self::Error> {
let clipped = area.intersection(&self.bounds());
if clipped.is_zero_sized() {
return Ok(());
}
let x_start = clipped.top_left.x as usize;
let y_start = clipped.top_left.y as usize;
let width = clipped.size.width as usize;
let height = clipped.size.height as usize;
let stride = usize::from(self.width);
for row in y_start..y_start + height {
let start = row * stride + x_start;
let end = start + width;
if let Some(line) = self.pixels.get_mut(start..end) {
line.fill(color);
}
}
Ok(())
}
fn fill_contiguous<I>(&mut self, area: &Rectangle, colors: I) -> Result<(), Self::Error>
where
I: IntoIterator<Item = Self::Color>,
{
let area_width = area.size.width as i32;
if area_width <= 0 {
return Ok(());
}
for (index, color) in colors.into_iter().enumerate() {
let index = index as i32;
let point = Point::new(
area.top_left.x + index % area_width,
area.top_left.y + index / area_width,
);
if let Some(pixel_index) = self.pixel_index(point) {
self.pixels[pixel_index] = color;
}
}
Ok(())
}
}
impl OriginDimensions for Sprite<'_> {
fn size(&self) -> Size {
Size::new(u32::from(self.width), u32::from(self.height))
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum SpriteError {
BufferTooSmall,
DirtyRegionCapacity,
}