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: a flat RGBA or similar framebuffer that accepts individual
48/// pixel writes by integer coordinates.
49pub trait PixelTarget {
50    fn width(&self) -> usize;
51    fn height(&self) -> usize;
52    fn put_pixel(&mut self, x: usize, y: usize, color: Rgb888);
53
54    /// Write a pre-packed RGB565 pixel (bit layout `RRRRR_GGGGGG_BBBBB`).
55    fn put_pixel_565(&mut self, x: usize, y: usize, rgb565: u16) {
56        self.put_pixel(x, y, rgb888_from_rgb565(rgb565));
57    }
58}
59
60/// Expands a packed RGB565 value (`RRRRR_GGGGGG_BBBBB`) to [`Rgb888`].
61pub const fn rgb888_from_rgb565(rgb565: u16) -> Rgb888 {
62    let red5 = ((rgb565 >> 11) & 0x1f) as u8;
63    let green6 = ((rgb565 >> 5) & 0x3f) as u8;
64    let blue5 = (rgb565 & 0x1f) as u8;
65
66    let red = (red5 << 3) | (red5 >> 2);
67    let green = (green6 << 2) | (green6 >> 4);
68    let blue = (blue5 << 3) | (blue5 >> 2);
69
70    Rgb888::new(red, green, blue)
71}
72
73/// Converts 8-bit RGB components to [`Rgb565`] by keeping each channel's high bits.
74pub const fn rgb565_from_rgb888_components(red: u8, green: u8, blue: u8) -> Rgb565 {
75    Rgb565::new(red >> 3, green >> 2, blue >> 3)
76}
77
78/// Converts [`Rgb888`] to [`Rgb565`].
79pub fn rgb565_from_rgb888(color: Rgb888) -> Rgb565 {
80    rgb565_from_rgb888_components(color.r(), color.g(), color.b())
81}
82
83/// Bounds-checked pixel write for a [`PixelTarget`]. Out-of-bounds writes are silently discarded.
84pub fn pixel_put<T: PixelTarget>(target: &mut T, x: i32, y: i32, color: Rgb888) {
85    if x < 0 || y < 0 {
86        return;
87    }
88    let x = x as usize;
89    let y = y as usize;
90    if x >= target.width() || y >= target.height() {
91        return;
92    }
93    target.put_pixel(x, y, color);
94}
95
96/// Bounds-checked raw-RGB565 pixel write for a [`PixelTarget`].
97pub fn pixel_put_565<T: PixelTarget>(target: &mut T, x: i32, y: i32, rgb565: u16) {
98    if x < 0 || y < 0 {
99        return;
100    }
101    let x = x as usize;
102    let y = y as usize;
103    if x >= target.width() || y >= target.height() {
104        return;
105    }
106    target.put_pixel_565(x, y, rgb565);
107}
108
109/// Bridges a [`PixelTarget`] to the embedded-graphics [`DrawTarget`] interface.
110pub struct PixelTargetAdapter<'a, T: PixelTarget>(pub &'a mut T);
111
112impl<T: PixelTarget> DrawTarget for PixelTargetAdapter<'_, T> {
113    type Color = Rgb888;
114    type Error = Infallible;
115
116    fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
117    where
118        I: IntoIterator<Item = Pixel<Rgb888>>,
119    {
120        for Pixel(point, color) in pixels {
121            pixel_put(self.0, point.x, point.y, color);
122        }
123        Ok(())
124    }
125}
126
127impl<T: PixelTarget> OriginDimensions for PixelTargetAdapter<'_, T> {
128    fn size(&self) -> Size {
129        Size::new(self.0.width() as u32, self.0.height() as u32)
130    }
131}