use embedded_graphics_core::{
Pixel,
draw_target::DrawTarget,
geometry::{OriginDimensions, Point, Size},
pixelcolor::Rgb565,
};
use heapless::Vec;
pub struct FramebufDrawTarget<'a> {
pixels: &'a mut [Rgb565],
width: usize,
height: usize,
}
impl<'a> FramebufDrawTarget<'a> {
#[inline]
pub fn new(pixels: &'a mut [Rgb565], width: usize, height: usize) -> Self {
Self {
pixels,
width,
height,
}
}
}
impl OriginDimensions for FramebufDrawTarget<'_> {
fn size(&self) -> Size {
Size::new(self.width as u32, self.height as u32)
}
}
impl DrawTarget for FramebufDrawTarget<'_> {
type Color = Rgb565;
type Error = core::convert::Infallible;
#[inline]
fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
where
I: IntoIterator<Item = Pixel<Self::Color>>,
{
for Pixel(coord, color) in pixels {
if coord.x >= 0
&& coord.y >= 0
&& (coord.x as usize) < self.width
&& (coord.y as usize) < self.height
{
let idx = coord.y as usize * self.width + coord.x as usize;
if idx < self.pixels.len() {
self.pixels[idx] = color;
}
}
}
Ok(())
}
}
pub fn format_u16_dec(mut value: u16, buf: &mut [u8], digits: usize) -> &str {
let start = buf.len().saturating_sub(digits);
for i in (start..buf.len()).rev() {
buf[i] = b'0' + (value % 10) as u8;
value /= 10;
}
core::str::from_utf8(&buf[start..]).unwrap_or("")
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum SpriteBlendMode {
#[default]
Normal,
ColorKey(Rgb565),
Alpha(u8),
Additive,
}
#[derive(Clone, Copy, Debug)]
pub struct Sprite2D<'a> {
pub x: i32,
pub y: i32,
pub width: u32,
pub height: u32,
pub scale: u8,
pub blend_mode: SpriteBlendMode,
pub data: &'a [Rgb565],
}
impl<'a> Sprite2D<'a> {
pub const fn new(x: i32, y: i32, width: u32, height: u32, data: &'a [Rgb565]) -> Self {
Self {
x,
y,
width,
height,
scale: 1,
blend_mode: SpriteBlendMode::Normal,
data,
}
}
pub const fn with_colorkey(mut self, key: Rgb565) -> Self {
self.blend_mode = SpriteBlendMode::ColorKey(key);
self
}
pub const fn with_alpha(mut self, alpha: u8) -> Self {
self.blend_mode = SpriteBlendMode::Alpha(alpha);
self
}
pub const fn with_scale(mut self, scale: u8) -> Self {
self.scale = if scale > 0 { scale } else { 1 };
self
}
pub fn draw<D>(&self, target: &mut D) -> Result<(), D::Error>
where
D: DrawTarget<Color = Rgb565>,
{
let scale = self.scale as i32;
let w = self.width as usize;
let h = self.height as usize;
for sy in 0..h {
for sx in 0..w {
let idx = sy * w + sx;
if idx >= self.data.len() {
break;
}
let color = self.data[idx];
if let SpriteBlendMode::ColorKey(key) = self.blend_mode {
if color == key {
continue;
}
}
let base_x = self.x + sx as i32 * scale;
let base_y = self.y + sy as i32 * scale;
if scale == 1 {
target.draw_iter([Pixel(Point::new(base_x, base_y), color)])?;
} else {
for dy in 0..scale {
for dx in 0..scale {
target
.draw_iter([Pixel(Point::new(base_x + dx, base_y + dy), color)])?;
}
}
}
}
}
Ok(())
}
}
#[derive(Clone, Copy, Debug)]
pub enum HudElement<'a> {
FillRect {
x: i32,
y: i32,
w: u32,
h: u32,
color: Rgb565,
},
ProgressBar {
x: i32,
y: i32,
w: u32,
h: u32,
value: f32,
fg: Rgb565,
bg: Rgb565,
},
Border {
x: i32,
y: i32,
w: u32,
h: u32,
color: Rgb565,
},
TranslucentRect {
x: i32,
y: i32,
w: u32,
h: u32,
color: Rgb565,
alpha: u8,
},
TranslucentBorder {
x: i32,
y: i32,
w: u32,
h: u32,
color: Rgb565,
alpha: u8,
},
Sprite(Sprite2D<'a>),
}
pub struct HudLayer<'a, const N: usize> {
elements: Vec<HudElement<'a>, N>,
}
impl<const N: usize> Default for HudLayer<'_, N> {
fn default() -> Self {
Self::new()
}
}
impl<'a, const N: usize> HudLayer<'a, N> {
pub const fn new() -> Self {
Self {
elements: Vec::new(),
}
}
pub fn clear(&mut self) {
self.elements.clear();
}
pub fn push(&mut self, element: HudElement<'a>) -> Result<(), HudElement<'a>> {
self.elements.push(element)
}
pub fn draw<D>(&self, target: &mut D)
where
D: DrawTarget<Color = Rgb565>,
{
for elem in &self.elements {
match elem {
HudElement::FillRect { x, y, w, h, color } => {
let _ = target.draw_iter(
rect_pixels(*x, *y, *w, *h)
.map(|(px, py)| Pixel(Point::new(px, py), *color)),
);
}
HudElement::TranslucentRect {
x,
y,
w,
h,
color,
alpha: _,
} => {
let _ = target.draw_iter(
rect_pixels(*x, *y, *w, *h)
.map(|(px, py)| Pixel(Point::new(px, py), *color)),
);
}
HudElement::ProgressBar {
x,
y,
w,
h,
value,
fg,
bg,
} => {
let filled = ((*w as f32 * value.clamp(0.0, 1.0)) as u32).min(*w);
if filled > 0 {
let _ = target.draw_iter(
rect_pixels(*x, *y, filled, *h)
.map(|(px, py)| Pixel(Point::new(px, py), *fg)),
);
}
if filled < *w {
let _ = target.draw_iter(
rect_pixels(*x + filled as i32, *y, *w - filled, *h)
.map(|(px, py)| Pixel(Point::new(px, py), *bg)),
);
}
}
HudElement::Border { x, y, w, h, color }
| HudElement::TranslucentBorder {
x,
y,
w,
h,
color,
alpha: _,
} => {
let _ = target.draw_iter(
border_pixels(*x, *y, *w, *h)
.map(|(px, py)| Pixel(Point::new(px, py), *color)),
);
}
HudElement::Sprite(sprite) => {
let _ = sprite.draw(target);
}
}
}
}
#[cfg(feature = "aa")]
pub fn draw_blended<D>(&self, target: &mut D)
where
D: DrawTarget<Color = Rgb565> + crate::draw::ReadPixel,
{
for elem in &self.elements {
match *elem {
HudElement::TranslucentRect {
x,
y,
w,
h,
color,
alpha,
} => {
for (px, py) in rect_pixels(x, y, w, h) {
let pt = Point::new(px, py);
let bg = target.read_pixel(pt);
let blended = crate::draw::fast_blend_rgb565(bg, color, alpha);
let _ = target.draw_iter([Pixel(pt, blended)]);
}
}
HudElement::TranslucentBorder {
x,
y,
w,
h,
color,
alpha,
} => {
for (px, py) in border_pixels(x, y, w, h) {
let pt = Point::new(px, py);
let bg = target.read_pixel(pt);
let blended = crate::draw::fast_blend_rgb565(bg, color, alpha);
let _ = target.draw_iter([Pixel(pt, blended)]);
}
}
_ => {
self.draw(target);
}
}
}
}
}
fn rect_pixels(x: i32, y: i32, w: u32, h: u32) -> impl Iterator<Item = (i32, i32)> {
(0..h as i32).flat_map(move |dy| (0..w as i32).map(move |dx| (x + dx, y + dy)))
}
fn border_pixels(x: i32, y: i32, w: u32, h: u32) -> impl Iterator<Item = (i32, i32)> {
let wi = w as i32;
let hi = h as i32;
(0..wi)
.map(move |dx| (x + dx, y))
.chain((0..wi).map(move |dx| (x + dx, y + hi - 1)))
.chain((1..hi - 1).map(move |dy| (x, y + dy)))
.chain((1..hi - 1).map(move |dy| (x + wi - 1, y + dy)))
}
#[cfg(test)]
mod tests {
use super::*;
use embedded_graphics_core::pixelcolor::RgbColor;
struct MockTarget {
pixels: heapless::Vec<(i32, i32, Rgb565), 4096>,
}
impl MockTarget {
fn new() -> Self {
Self {
pixels: heapless::Vec::new(),
}
}
}
impl DrawTarget for MockTarget {
type Color = Rgb565;
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(p, c) in pixels {
let _ = self.pixels.push((p.x, p.y, c));
}
Ok(())
}
}
impl embedded_graphics_core::geometry::OriginDimensions for MockTarget {
fn size(&self) -> embedded_graphics_core::geometry::Size {
embedded_graphics_core::geometry::Size::new(320, 240)
}
}
#[test]
fn fill_rect_pixel_count() {
let mut hud = HudLayer::<4>::new();
hud.push(HudElement::FillRect {
x: 0,
y: 0,
w: 3,
h: 2,
color: Rgb565::RED,
})
.unwrap();
let mut target = MockTarget::new();
hud.draw(&mut target);
assert_eq!(target.pixels.len(), 6);
}
#[test]
fn progress_bar_full() {
let mut hud = HudLayer::<4>::new();
hud.push(HudElement::ProgressBar {
x: 0,
y: 0,
w: 10,
h: 2,
value: 1.0,
fg: Rgb565::GREEN,
bg: Rgb565::BLACK,
})
.unwrap();
let mut target = MockTarget::new();
hud.draw(&mut target);
assert_eq!(target.pixels.len(), 20);
assert!(target.pixels.iter().all(|&(_, _, c)| c == Rgb565::GREEN));
}
#[test]
fn progress_bar_empty() {
let mut hud = HudLayer::<4>::new();
hud.push(HudElement::ProgressBar {
x: 0,
y: 0,
w: 10,
h: 2,
value: 0.0,
fg: Rgb565::GREEN,
bg: Rgb565::BLACK,
})
.unwrap();
let mut target = MockTarget::new();
hud.draw(&mut target);
assert_eq!(target.pixels.len(), 20);
assert!(target.pixels.iter().all(|&(_, _, c)| c == Rgb565::BLACK));
}
#[test]
fn border_pixel_count() {
let mut hud = HudLayer::<4>::new();
hud.push(HudElement::Border {
x: 0,
y: 0,
w: 4,
h: 3,
color: Rgb565::WHITE,
})
.unwrap();
let mut target = MockTarget::new();
hud.draw(&mut target);
assert_eq!(target.pixels.len(), 10);
}
#[test]
fn layer_full_returns_err() {
let mut hud = HudLayer::<1>::new();
hud.push(HudElement::FillRect {
x: 0,
y: 0,
w: 1,
h: 1,
color: Rgb565::RED,
})
.unwrap();
let result = hud.push(HudElement::FillRect {
x: 0,
y: 0,
w: 1,
h: 1,
color: Rgb565::RED,
});
assert!(result.is_err());
}
#[test]
fn clear_empties_layer() {
let mut hud = HudLayer::<4>::new();
hud.push(HudElement::FillRect {
x: 0,
y: 0,
w: 2,
h: 2,
color: Rgb565::RED,
})
.unwrap();
hud.clear();
let mut target = MockTarget::new();
hud.draw(&mut target);
assert_eq!(target.pixels.len(), 0);
}
#[test]
fn test_sprite2d_colorkey_and_scale() {
let sprite_data = [
Rgb565::RED,
Rgb565::MAGENTA, Rgb565::BLUE,
Rgb565::GREEN,
];
let sprite = Sprite2D::new(10, 10, 2, 2, &sprite_data)
.with_colorkey(Rgb565::MAGENTA)
.with_scale(2);
let mut target = MockTarget::new();
sprite.draw(&mut target).unwrap();
assert_eq!(target.pixels.len(), 12);
assert!(target.pixels.iter().all(|&(_, _, c)| c != Rgb565::MAGENTA));
}
}