use core::fmt::Debug;
use core::marker::PhantomData;
use embedded_graphics_core::{
Drawable, Pixel,
draw_target::DrawTarget,
geometry::{Dimensions, Point},
pixelcolor::{PixelColor, Rgb565},
primitives::Rectangle,
};
use embedded_graphics_framebuf::{FrameBuf, backends::FrameBufferBackend};
use crate::draw::draw;
use crate::primitive::DrawPrimitive;
pub struct ColorAdapter<'a, C, D> {
inner: &'a mut D,
_phantom: PhantomData<C>,
}
impl<C, D> Dimensions for ColorAdapter<'_, C, D>
where
C: PixelColor,
D: DrawTarget<Color = C>,
{
fn bounding_box(&self) -> Rectangle {
self.inner.bounding_box()
}
}
impl<C, D> DrawTarget for ColorAdapter<'_, C, D>
where
C: PixelColor + From<Rgb565>,
D: DrawTarget<Color = C>,
{
type Color = Rgb565;
type Error = D::Error;
fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
where
I: IntoIterator<Item = Pixel<Rgb565>>,
{
self.inner.draw_iter(
pixels
.into_iter()
.map(|Pixel(pos, color)| Pixel(pos, C::from(color))),
)
}
}
pub fn draw_to<C, D>(primitive: DrawPrimitive, fb: &mut D)
where
C: PixelColor + From<Rgb565>,
D: DrawTarget<Color = C>,
D::Error: Debug,
{
let mut adapter = ColorAdapter {
inner: fb,
_phantom: PhantomData,
};
draw(primitive, &mut adapter);
}
#[inline]
pub fn nalgebra_to_eg(p: nalgebra::Point2<i32>) -> Point {
Point::new(p.x, p.y)
}
#[inline]
pub fn eg_to_nalgebra(p: Point) -> nalgebra::Point2<i32> {
nalgebra::Point2::new(p.x, p.y)
}
pub trait AsEgPoint {
fn as_eg_point(&self) -> Point;
}
impl AsEgPoint for nalgebra::Point2<i32> {
#[inline]
fn as_eg_point(&self) -> Point {
Point::new(self.x, self.y)
}
}
pub trait AsNalgebraPoint {
fn as_nalgebra(&self) -> nalgebra::Point2<i32>;
}
impl AsNalgebraPoint for Point {
#[inline]
fn as_nalgebra(&self) -> nalgebra::Point2<i32> {
nalgebra::Point2::new(self.x, self.y)
}
}
struct SliceBackend<'a>(pub &'a mut [Rgb565]);
impl FrameBufferBackend for SliceBackend<'_> {
type Color = Rgb565;
fn set(&mut self, index: usize, color: Rgb565) {
self.0[index] = color;
}
fn get(&self, index: usize) -> Rgb565 {
self.0[index]
}
fn nr_elements(&self) -> usize {
self.0.len()
}
}
pub fn render_drawable_to_buffer<D>(
drawable: &D,
buffer: &mut [Rgb565],
width: usize,
height: usize,
) -> Result<(), ()>
where
D: Drawable<Color = Rgb565>,
{
if buffer.len() != width * height {
return Err(());
}
let mut fb = FrameBuf::new(SliceBackend(buffer), width, height);
drawable.draw(&mut fb).map(|_| ()).map_err(|_| ())
}
pub struct HalfWidthDrawTargetAdapter<'a, D> {
inner: &'a mut D,
}
impl<'a, D> HalfWidthDrawTargetAdapter<'a, D> {
pub fn new(inner: &'a mut D) -> Self {
Self { inner }
}
}
impl<D> Dimensions for HalfWidthDrawTargetAdapter<'_, D>
where
D: Dimensions,
{
fn bounding_box(&self) -> Rectangle {
let orig = self.inner.bounding_box();
Rectangle::new(
Point::new(orig.top_left.x / 2, orig.top_left.y),
embedded_graphics_core::geometry::Size::new(orig.size.width / 2, orig.size.height),
)
}
}
impl<D> DrawTarget for HalfWidthDrawTargetAdapter<'_, D>
where
D: DrawTarget<Color = Rgb565>,
{
type Color = Rgb565;
type Error = D::Error;
fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
where
I: IntoIterator<Item = Pixel<Rgb565>>,
{
self.inner
.draw_iter(pixels.into_iter().flat_map(|Pixel(pos, color)| {
[
Pixel(Point::new(pos.x * 2, pos.y), color),
Pixel(Point::new(pos.x * 2 + 1, pos.y), color),
]
}))
}
}
pub fn scanout_half_width_row(src: &[Rgb565], dst: &mut [Rgb565]) {
let count = src.len().min(dst.len() / 2);
for i in 0..count {
dst[i * 2] = src[i];
dst[i * 2 + 1] = src[i];
}
}
pub fn scanout_half_width_buffer<D>(
src: &[Rgb565],
half_w: usize,
height: usize,
target: &mut D,
) -> Result<(), D::Error>
where
D: DrawTarget<Color = Rgb565>,
{
for y in 0..height {
let row_start = y * half_w;
let row_end = row_start + half_w;
if row_end > src.len() {
break;
}
let row = &src[row_start..row_end];
target.draw_iter(row.iter().enumerate().flat_map(|(x, &color)| {
[
Pixel(Point::new((x * 2) as i32, y as i32), color),
Pixel(Point::new((x * 2 + 1) as i32, y as i32), color),
]
}))?;
}
Ok(())
}
pub fn reconstruct_checkerboard_row(
row: &mut [Rgb565],
y: usize,
field: crate::draw::effects::CheckerboardField,
) {
if field == crate::draw::effects::CheckerboardField::Disabled || row.len() < 2 {
return;
}
let len = row.len();
for x in 0..len {
if !field.includes_pixel(x as i32, y as i32) {
let left = if x > 0 {
row[x - 1]
} else {
row[(x + 1).min(len - 1)]
};
let right = if x + 1 < len {
row[x + 1]
} else {
row[x.saturating_sub(1)]
};
row[x] = crate::shader::blend::fast_blend_rgb565(left, right, 128);
}
}
}
pub fn reconstruct_checkerboard_buffer(
fb: &mut [Rgb565],
width: usize,
height: usize,
field: crate::draw::effects::CheckerboardField,
) {
if field == crate::draw::effects::CheckerboardField::Disabled || width == 0 {
return;
}
for y in 0..height {
let start = y * width;
let end = start + width;
if end <= fb.len() {
reconstruct_checkerboard_row(&mut fb[start..end], y, field);
}
}
}
#[cfg(test)]
mod tests {
extern crate std;
use super::*;
use embedded_graphics_core::pixelcolor::RgbColor;
use embedded_graphics_framebuf::backends::{EndianCorrectedBuffer, EndianCorrection};
use nalgebra::Point2;
struct SingleRedPixel;
impl Drawable for SingleRedPixel {
type Color = Rgb565;
type Output = ();
fn draw<D>(&self, target: &mut D) -> Result<(), D::Error>
where
D: DrawTarget<Color = Self::Color>,
{
target.draw_iter([Pixel(Point::new(0, 0), Rgb565::RED)])
}
}
#[test]
fn point_conversion_helpers_roundtrip() {
let n = Point2::new(12, -9);
let eg = nalgebra_to_eg(n);
assert_eq!(eg, Point::new(12, -9));
let n2 = eg_to_nalgebra(eg);
assert_eq!(n2, n);
assert_eq!(n.as_eg_point(), eg);
assert_eq!(eg.as_nalgebra(), n);
}
#[test]
fn render_drawable_to_buffer_validates_buffer_size() {
let mut too_short = [Rgb565::BLACK; 3];
let err = render_drawable_to_buffer(&SingleRedPixel, &mut too_short, 2, 2);
assert!(err.is_err());
}
#[test]
fn render_drawable_to_buffer_draws_into_slice() {
let mut data = [Rgb565::BLACK; 4];
render_drawable_to_buffer(&SingleRedPixel, &mut data, 2, 2).unwrap();
assert_eq!(data[0], Rgb565::RED);
}
#[test]
fn draw_to_writes_primitive_to_target() {
let backing = std::vec![Rgb565::BLACK; 4].leak();
let mut fb = FrameBuf::new(
EndianCorrectedBuffer::new(backing, EndianCorrection::ToLittleEndian),
2,
2,
);
draw_to::<Rgb565, _>(
DrawPrimitive::ColoredPoint(Point2::new(1, 1), Rgb565::new(31, 0, 0)),
&mut fb,
);
assert_eq!(fb.get_color_at(Point::new(1, 1)), Rgb565::new(31, 0, 0));
}
#[test]
fn test_half_width_draw_target_adapter() {
let backing = std::vec![Rgb565::BLACK; 8].leak();
let mut fb = FrameBuf::new(
EndianCorrectedBuffer::new(backing, EndianCorrection::ToLittleEndian),
4,
2,
);
{
let mut adapter = HalfWidthDrawTargetAdapter::new(&mut fb);
adapter
.draw_iter([Pixel(Point::new(0, 0), Rgb565::RED)])
.unwrap();
}
assert_eq!(fb.get_color_at(Point::new(0, 0)), Rgb565::RED);
assert_eq!(fb.get_color_at(Point::new(1, 0)), Rgb565::RED);
assert_eq!(fb.get_color_at(Point::new(2, 0)), Rgb565::BLACK);
}
#[test]
fn test_scanout_half_width() {
let src = [Rgb565::RED, Rgb565::GREEN];
let mut dst = [Rgb565::BLACK; 4];
scanout_half_width_row(&src, &mut dst);
assert_eq!(dst[0], Rgb565::RED);
assert_eq!(dst[1], Rgb565::RED);
assert_eq!(dst[2], Rgb565::GREEN);
assert_eq!(dst[3], Rgb565::GREEN);
}
#[test]
fn test_reconstruct_checkerboard() {
use crate::draw::effects::CheckerboardField;
let mut row = [Rgb565::RED, Rgb565::BLACK, Rgb565::RED];
reconstruct_checkerboard_row(&mut row, 0, CheckerboardField::Even);
assert_eq!(row[0], Rgb565::RED);
assert_eq!(row[2], Rgb565::RED);
assert_eq!(row[1], Rgb565::RED);
}
}