Skip to main content

embedded_3dgfx/
bridge.rs

1//! Interoperability bridges between embedded-3dgfx and embedded-graphics.
2//!
3//! # What's here
4//!
5//! - [`draw_to`] — render a [`DrawPrimitive`] to any `DrawTarget<Color = C>`
6//!   (not just `Rgb565`) via on-the-fly color conversion.
7//! - [`AsEgPoint`] / [`AsNalgebraPoint`] — zero-cost conversions between
8//!   `embedded_graphics_core::geometry::Point` and `nalgebra::Point2<i32>`.
9//! - [`nalgebra_to_eg`] / [`eg_to_nalgebra`] — free-function equivalents.
10//! - `ReadPixel` impl for `FrameBuf<Rgb565, B>` (enabled with the `aa`
11//!   feature) so anti-aliased rasterization works without boilerplate.
12//! - [`render_drawable_to_buffer`] — rasterize any `Drawable<Color = Rgb565>`
13//!   into a `&mut [Rgb565]` slice so it can be used as a 3D texture.
14
15use core::fmt::Debug;
16use core::marker::PhantomData;
17
18use embedded_graphics_core::{
19    Drawable, Pixel,
20    draw_target::DrawTarget,
21    geometry::{Dimensions, Point},
22    pixelcolor::{PixelColor, Rgb565},
23    primitives::Rectangle,
24};
25use embedded_graphics_framebuf::{FrameBuf, backends::FrameBufferBackend};
26
27use crate::DrawPrimitive;
28#[cfg(feature = "aa")]
29use crate::draw::ReadPixel;
30use crate::draw::draw;
31#[cfg(feature = "aa")]
32use embedded_graphics_core::pixelcolor::RgbColor;
33
34// ── 1. Color adapter ─────────────────────────────────────────────────────────
35
36/// Adapts any `DrawTarget<Color = C>` to accept `Rgb565` pixels by converting
37/// each pixel's color on the fly. Constructed internally by [`draw_to`].
38pub struct ColorAdapter<'a, C, D> {
39    inner: &'a mut D,
40    _phantom: PhantomData<C>,
41}
42
43impl<C, D> Dimensions for ColorAdapter<'_, C, D>
44where
45    C: PixelColor,
46    D: DrawTarget<Color = C>,
47{
48    fn bounding_box(&self) -> Rectangle {
49        self.inner.bounding_box()
50    }
51}
52
53impl<C, D> DrawTarget for ColorAdapter<'_, C, D>
54where
55    C: PixelColor + From<Rgb565>,
56    D: DrawTarget<Color = C>,
57{
58    type Color = Rgb565;
59    type Error = D::Error;
60
61    fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
62    where
63        I: IntoIterator<Item = Pixel<Rgb565>>,
64    {
65        self.inner.draw_iter(
66            pixels
67                .into_iter()
68                .map(|Pixel(pos, color)| Pixel(pos, C::from(color))),
69        )
70    }
71}
72
73/// Draw a [`DrawPrimitive`] to any [`DrawTarget`], converting pixel colors from
74/// `Rgb565` (the 3D engine's internal format) to the target's native color type.
75///
76/// Use the plain [`draw`](crate::draw::draw) function directly when your
77/// framebuffer already uses `Rgb565` — it has zero conversion overhead.
78///
79/// # Example
80///
81/// ```ignore
82/// use embedded_3dgfx::bridge::draw_to;
83/// use embedded_graphics_core::pixelcolor::Rgb888;
84///
85/// // display: impl DrawTarget<Color = Rgb888>
86/// draw_to(primitive, &mut display);
87/// ```
88pub fn draw_to<C, D>(primitive: DrawPrimitive, fb: &mut D)
89where
90    C: PixelColor + From<Rgb565>,
91    D: DrawTarget<Color = C>,
92    D::Error: Debug,
93{
94    let mut adapter = ColorAdapter {
95        inner: fb,
96        _phantom: PhantomData,
97    };
98    draw(primitive, &mut adapter);
99}
100
101// ── 2. Point conversion helpers ───────────────────────────────────────────────
102
103/// Convert a `nalgebra::Point2<i32>` to an embedded-graphics `Point`.
104#[inline]
105pub fn nalgebra_to_eg(p: nalgebra::Point2<i32>) -> Point {
106    Point::new(p.x, p.y)
107}
108
109/// Convert an embedded-graphics `Point` to a `nalgebra::Point2<i32>`.
110#[inline]
111pub fn eg_to_nalgebra(p: Point) -> nalgebra::Point2<i32> {
112    nalgebra::Point2::new(p.x, p.y)
113}
114
115/// Extension trait: convert a `nalgebra::Point2<i32>` to an embedded-graphics `Point`.
116pub trait AsEgPoint {
117    /// Returns the equivalent embedded-graphics `Point`.
118    fn as_eg_point(&self) -> Point;
119}
120
121impl AsEgPoint for nalgebra::Point2<i32> {
122    #[inline]
123    fn as_eg_point(&self) -> Point {
124        Point::new(self.x, self.y)
125    }
126}
127
128/// Extension trait: convert an embedded-graphics `Point` to `nalgebra::Point2<i32>`.
129pub trait AsNalgebraPoint {
130    /// Returns the equivalent `nalgebra::Point2<i32>`.
131    fn as_nalgebra(&self) -> nalgebra::Point2<i32>;
132}
133
134impl AsNalgebraPoint for Point {
135    #[inline]
136    fn as_nalgebra(&self) -> nalgebra::Point2<i32> {
137        nalgebra::Point2::new(self.x, self.y)
138    }
139}
140
141// ── 3. ReadPixel for FrameBuf ────────────────────────────────────────────────
142
143/// Implements [`ReadPixel`] for any `FrameBuf<Rgb565, B>`.
144///
145/// Required for anti-aliased rasterization (`aa-heuristic` / `aa-coverage`
146/// features).  Because `FrameBuf` already holds the pixel data in RAM, the
147/// implementation is a direct indexed lookup — no extra memory needed.
148#[cfg(feature = "aa")]
149impl<B> ReadPixel for FrameBuf<Rgb565, B>
150where
151    B: FrameBufferBackend<Color = Rgb565>,
152{
153    fn read_pixel(&self, point: Point) -> Rgb565 {
154        let w = self.width() as i32;
155        let h = self.height() as i32;
156        if point.x < 0 || point.x >= w || point.y < 0 || point.y >= h {
157            return <Rgb565 as RgbColor>::BLACK;
158        }
159        self.get_color_at(point)
160    }
161}
162
163// ── 4. Drawable → texture buffer helper ──────────────────────────────────────
164
165struct SliceBackend<'a>(pub &'a mut [Rgb565]);
166
167impl FrameBufferBackend for SliceBackend<'_> {
168    type Color = Rgb565;
169    fn set(&mut self, index: usize, color: Rgb565) {
170        self.0[index] = color;
171    }
172    fn get(&self, index: usize) -> Rgb565 {
173        self.0[index]
174    }
175    fn nr_elements(&self) -> usize {
176        self.0.len()
177    }
178}
179
180/// Rasterize a [`Drawable<Color = Rgb565>`](Drawable) into a caller-supplied
181/// pixel buffer, ready to be used as a 3D texture.
182///
183/// `width` and `height` must be powers of two (required by
184/// [`Texture::new`](crate::texture::Texture::new)), and `buffer.len()` must
185/// equal `width * height`.
186///
187/// # Usage
188///
189/// ```ignore
190/// use embedded_3dgfx::{bridge::render_drawable_to_buffer, texture::Texture};
191/// use embedded_graphics_core::pixelcolor::{Rgb565, RgbColor};
192///
193/// static mut BUF: [Rgb565; 32 * 32] = [Rgb565::BLACK; 32 * 32];
194///
195/// // SAFETY: single-threaded, called once before the render loop starts.
196/// render_drawable_to_buffer(&my_icon, unsafe { &mut BUF }, 32, 32).unwrap();
197/// let texture = Texture::new(unsafe { &BUF }, 32, 32);
198/// ```
199///
200/// # Errors
201///
202/// Returns `Err(())` when `buffer.len() != width * height`.
203pub fn render_drawable_to_buffer<D>(
204    drawable: &D,
205    buffer: &mut [Rgb565],
206    width: usize,
207    height: usize,
208) -> Result<(), ()>
209where
210    D: Drawable<Color = Rgb565>,
211{
212    if buffer.len() != width * height {
213        return Err(());
214    }
215    let mut fb = FrameBuf::new(SliceBackend(buffer), width, height);
216    drawable.draw(&mut fb).map(|_| ()).map_err(|_| ())
217}