Skip to main content

device_envoy_core/
pixel_target.rs

1//! Generic RGB pixel-buffer helpers shared by display-style device modules.
2
3use core::convert::Infallible;
4
5use embedded_graphics::{
6    Pixel,
7    draw_target::DrawTarget,
8    geometry::{OriginDimensions, Size},
9    pixelcolor::{Rgb565, Rgb888},
10    prelude::RgbColor,
11};
12
13/// Rasterize an ellipse pixel-by-pixel via a callback.
14///
15/// The ellipse is the locus of `center + s·axis_a + t·axis_b` where `s²+t² ≤ 1`.
16/// All values are in pixel space. Skips degenerate (edge-on) ellipses silently.
17pub fn fill_ellipse_pixels(
18    center: (f32, f32),
19    axis_a: (f32, f32),
20    axis_b: (f32, f32),
21    mut put_pixel: impl FnMut(i32, i32),
22) {
23    let (axis_ax, axis_ay) = axis_a;
24    let (axis_bx, axis_by) = axis_b;
25    let determinant = axis_ax * axis_by - axis_ay * axis_bx;
26    if determinant.abs() < 0.5 {
27        return;
28    }
29    let inverse_determinant = 1.0 / determinant;
30    let bound_x = (axis_ax.abs() + axis_bx.abs()) as i32 + 1;
31    let bound_y = (axis_ay.abs() + axis_by.abs()) as i32 + 1;
32    let center_x = center.0 as i32;
33    let center_y = center.1 as i32;
34    for local_y in -bound_y..=bound_y {
35        for local_x in -bound_x..=bound_x {
36            let delta_x = local_x as f32;
37            let delta_y = local_y as f32;
38            let s = (axis_by * delta_x - axis_bx * delta_y) * inverse_determinant;
39            let t = (axis_ax * delta_y - axis_ay * delta_x) * inverse_determinant;
40            if s * s + t * t <= 1.0 {
41                put_pixel(center_x + local_x, center_y + local_y);
42            }
43        }
44    }
45}
46
47/// A raw pixel sink that accepts individual pixels by integer coordinates.
48///
49/// Every [`CydFrame`](crate::cyd::display::CydFrame) implements this trait, so
50/// [`DrawItem`](crate::cyd::display::DrawItem) values can draw directly onto any
51/// CYD frame. See the canonical [`DrawItem`](crate::cyd::display::DrawItem)
52/// example for construction, rendering, and framebuffer verification.
53pub trait PixelTarget {
54    fn width(&self) -> usize;
55    fn height(&self) -> usize;
56    fn put_pixel(&mut self, x: usize, y: usize, color: Rgb888);
57
58    /// Write a pre-packed RGB565 pixel (bit layout `RRRRR_GGGGGG_BBBBB`).
59    fn put_pixel_565(&mut self, x: usize, y: usize, rgb565: u16) {
60        self.put_pixel(x, y, rgb888_from_rgb565(rgb565));
61    }
62}
63
64/// Expands a packed RGB565 value (`RRRRR_GGGGGG_BBBBB`) to [`Rgb888`].
65pub const fn rgb888_from_rgb565(rgb565: u16) -> Rgb888 {
66    let red5 = ((rgb565 >> 11) & 0x1f) as u8;
67    let green6 = ((rgb565 >> 5) & 0x3f) as u8;
68    let blue5 = (rgb565 & 0x1f) as u8;
69
70    let red = (red5 << 3) | (red5 >> 2);
71    let green = (green6 << 2) | (green6 >> 4);
72    let blue = (blue5 << 3) | (blue5 >> 2);
73
74    Rgb888::new(red, green, blue)
75}
76
77/// Converts 8-bit RGB components to [`Rgb565`] by keeping each channel's high bits.
78pub const fn rgb565_from_rgb888_components(red: u8, green: u8, blue: u8) -> Rgb565 {
79    Rgb565::new(red >> 3, green >> 2, blue >> 3)
80}
81
82/// Converts [`Rgb888`] to [`Rgb565`].
83pub fn rgb565_from_rgb888(color: Rgb888) -> Rgb565 {
84    rgb565_from_rgb888_components(color.r(), color.g(), color.b())
85}
86
87/// Bounds-checked pixel write for a [`PixelTarget`]. Out-of-bounds writes are silently discarded.
88pub fn pixel_put<T: PixelTarget>(target: &mut T, x: i32, y: i32, color: Rgb888) {
89    if x < 0 || y < 0 {
90        return;
91    }
92    let x = x as usize;
93    let y = y as usize;
94    if x >= target.width() || y >= target.height() {
95        return;
96    }
97    target.put_pixel(x, y, color);
98}
99
100/// Bounds-checked raw-RGB565 pixel write for a [`PixelTarget`].
101pub fn pixel_put_565<T: PixelTarget>(target: &mut T, x: i32, y: i32, rgb565: u16) {
102    if x < 0 || y < 0 {
103        return;
104    }
105    let x = x as usize;
106    let y = y as usize;
107    if x >= target.width() || y >= target.height() {
108        return;
109    }
110    target.put_pixel_565(x, y, rgb565);
111}
112
113/// Bridges a [`PixelTarget`] to the embedded-graphics [`DrawTarget`] interface.
114pub struct PixelTargetAdapter<'a, T: PixelTarget>(pub &'a mut T);
115
116impl<T: PixelTarget> DrawTarget for PixelTargetAdapter<'_, T> {
117    type Color = Rgb888;
118    type Error = Infallible;
119
120    fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
121    where
122        I: IntoIterator<Item = Pixel<Rgb888>>,
123    {
124        for Pixel(point, color) in pixels {
125            pixel_put(self.0, point.x, point.y, color);
126        }
127        Ok(())
128    }
129}
130
131impl<T: PixelTarget> OriginDimensions for PixelTargetAdapter<'_, T> {
132    fn size(&self) -> Size {
133        Size::new(self.0.width() as u32, self.0.height() as u32)
134    }
135}