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//! - [`render_drawable_to_buffer`] — rasterize any `Drawable<Color = Rgb565>`
11//!   into a `&mut [Rgb565]` slice so it can be used as a 3D texture.
12//!
13//! Readback for `FrameBuf` is no longer implemented here: `embedded-draw-target`
14//! provides `PixelRead` for it, which satisfies `ReadPixel` through this
15//! crate's blanket impl, so anti-aliased rasterization still works on a plain
16//! `FrameBuf` without boilerplate.
17
18use core::fmt::Debug;
19use core::marker::PhantomData;
20
21use embedded_graphics_core::{
22    Drawable, Pixel,
23    draw_target::DrawTarget,
24    geometry::{Dimensions, Point},
25    pixelcolor::{PixelColor, Rgb565},
26    primitives::Rectangle,
27};
28use embedded_graphics_framebuf::{FrameBuf, backends::FrameBufferBackend};
29
30use crate::DrawPrimitive;
31use crate::draw::draw;
32
33// ── 1. Color adapter ─────────────────────────────────────────────────────────
34
35/// Adapts any `DrawTarget<Color = C>` to accept `Rgb565` pixels by converting
36/// each pixel's color on the fly. Constructed internally by [`draw_to`].
37pub struct ColorAdapter<'a, C, D> {
38    inner: &'a mut D,
39    _phantom: PhantomData<C>,
40}
41
42impl<C, D> Dimensions for ColorAdapter<'_, C, D>
43where
44    C: PixelColor,
45    D: DrawTarget<Color = C>,
46{
47    fn bounding_box(&self) -> Rectangle {
48        self.inner.bounding_box()
49    }
50}
51
52impl<C, D> DrawTarget for ColorAdapter<'_, C, D>
53where
54    C: PixelColor + From<Rgb565>,
55    D: DrawTarget<Color = C>,
56{
57    type Color = Rgb565;
58    type Error = D::Error;
59
60    fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
61    where
62        I: IntoIterator<Item = Pixel<Rgb565>>,
63    {
64        self.inner.draw_iter(
65            pixels
66                .into_iter()
67                .map(|Pixel(pos, color)| Pixel(pos, C::from(color))),
68        )
69    }
70}
71
72/// Draw a [`DrawPrimitive`] to any [`DrawTarget`], converting pixel colors from
73/// `Rgb565` (the 3D engine's internal format) to the target's native color type.
74///
75/// Use the plain [`draw`] function directly when your
76/// framebuffer already uses `Rgb565` — it has zero conversion overhead.
77///
78/// # Example
79///
80/// ```ignore
81/// use embedded_3dgfx::bridge::draw_to;
82/// use embedded_graphics_core::pixelcolor::Rgb888;
83///
84/// // display: impl DrawTarget<Color = Rgb888>
85/// draw_to(primitive, &mut display);
86/// ```
87pub fn draw_to<C, D>(primitive: DrawPrimitive, fb: &mut D)
88where
89    C: PixelColor + From<Rgb565>,
90    D: DrawTarget<Color = C>,
91    D::Error: Debug,
92{
93    let mut adapter = ColorAdapter {
94        inner: fb,
95        _phantom: PhantomData,
96    };
97    draw(primitive, &mut adapter);
98}
99
100// ── 2. Point conversion helpers ───────────────────────────────────────────────
101
102/// Convert a `nalgebra::Point2<i32>` to an embedded-graphics `Point`.
103#[inline]
104pub fn nalgebra_to_eg(p: nalgebra::Point2<i32>) -> Point {
105    Point::new(p.x, p.y)
106}
107
108/// Convert an embedded-graphics `Point` to a `nalgebra::Point2<i32>`.
109#[inline]
110pub fn eg_to_nalgebra(p: Point) -> nalgebra::Point2<i32> {
111    nalgebra::Point2::new(p.x, p.y)
112}
113
114/// Extension trait: convert a `nalgebra::Point2<i32>` to an embedded-graphics `Point`.
115pub trait AsEgPoint {
116    /// Returns the equivalent embedded-graphics `Point`.
117    fn as_eg_point(&self) -> Point;
118}
119
120impl AsEgPoint for nalgebra::Point2<i32> {
121    #[inline]
122    fn as_eg_point(&self) -> Point {
123        Point::new(self.x, self.y)
124    }
125}
126
127/// Extension trait: convert an embedded-graphics `Point` to `nalgebra::Point2<i32>`.
128pub trait AsNalgebraPoint {
129    /// Returns the equivalent `nalgebra::Point2<i32>`.
130    fn as_nalgebra(&self) -> nalgebra::Point2<i32>;
131}
132
133impl AsNalgebraPoint for Point {
134    #[inline]
135    fn as_nalgebra(&self) -> nalgebra::Point2<i32> {
136        nalgebra::Point2::new(self.x, self.y)
137    }
138}
139
140// ── 3. Drawable → texture buffer helper ──────────────────────────────────────
141
142struct SliceBackend<'a>(pub &'a mut [Rgb565]);
143
144impl FrameBufferBackend for SliceBackend<'_> {
145    type Color = Rgb565;
146    fn set(&mut self, index: usize, color: Rgb565) {
147        self.0[index] = color;
148    }
149    fn get(&self, index: usize) -> Rgb565 {
150        self.0[index]
151    }
152    fn nr_elements(&self) -> usize {
153        self.0.len()
154    }
155}
156
157/// Rasterize a [`Drawable<Color = Rgb565>`](Drawable) into a caller-supplied
158/// pixel buffer, ready to be used as a 3D texture.
159///
160/// `width` and `height` must be powers of two (required by
161/// [`Texture::new`](crate::texture::Texture::new)), and `buffer.len()` must
162/// equal `width * height`.
163///
164/// # Usage
165///
166/// ```ignore
167/// use embedded_3dgfx::{bridge::render_drawable_to_buffer, texture::Texture};
168/// use embedded_graphics_core::pixelcolor::{Rgb565, RgbColor};
169///
170/// static mut BUF: [Rgb565; 32 * 32] = [Rgb565::BLACK; 32 * 32];
171///
172/// // SAFETY: single-threaded, called once before the render loop starts.
173/// render_drawable_to_buffer(&my_icon, unsafe { &mut BUF }, 32, 32).unwrap();
174/// let texture = Texture::new(unsafe { &BUF }, 32, 32);
175/// ```
176///
177/// # Errors
178///
179/// Returns `Err(())` when `buffer.len() != width * height`.
180pub fn render_drawable_to_buffer<D>(
181    drawable: &D,
182    buffer: &mut [Rgb565],
183    width: usize,
184    height: usize,
185) -> Result<(), ()>
186where
187    D: Drawable<Color = Rgb565>,
188{
189    if buffer.len() != width * height {
190        return Err(());
191    }
192    let mut fb = FrameBuf::new(SliceBackend(buffer), width, height);
193    drawable.draw(&mut fb).map(|_| ()).map_err(|_| ())
194}
195
196#[cfg(test)]
197mod tests {
198    extern crate std;
199
200    use super::*;
201    use embedded_graphics_core::pixelcolor::RgbColor;
202    use embedded_graphics_framebuf::backends::{EndianCorrectedBuffer, EndianCorrection};
203    use nalgebra::Point2;
204
205    struct SingleRedPixel;
206
207    impl Drawable for SingleRedPixel {
208        type Color = Rgb565;
209        type Output = ();
210
211        fn draw<D>(&self, target: &mut D) -> Result<(), D::Error>
212        where
213            D: DrawTarget<Color = Self::Color>,
214        {
215            target.draw_iter([Pixel(Point::new(0, 0), Rgb565::RED)])
216        }
217    }
218
219    #[test]
220    fn point_conversion_helpers_roundtrip() {
221        let n = Point2::new(12, -9);
222        let eg = nalgebra_to_eg(n);
223        assert_eq!(eg, Point::new(12, -9));
224        let n2 = eg_to_nalgebra(eg);
225        assert_eq!(n2, n);
226        assert_eq!(n.as_eg_point(), eg);
227        assert_eq!(eg.as_nalgebra(), n);
228    }
229
230    #[test]
231    fn render_drawable_to_buffer_validates_buffer_size() {
232        let mut too_short = [Rgb565::BLACK; 3];
233        let err = render_drawable_to_buffer(&SingleRedPixel, &mut too_short, 2, 2);
234        assert!(err.is_err());
235    }
236
237    #[test]
238    fn render_drawable_to_buffer_draws_into_slice() {
239        let mut data = [Rgb565::BLACK; 4];
240        render_drawable_to_buffer(&SingleRedPixel, &mut data, 2, 2).unwrap();
241        assert_eq!(data[0], Rgb565::RED);
242    }
243
244    #[test]
245    fn draw_to_writes_primitive_to_target() {
246        let backing = std::vec![Rgb565::BLACK; 4].leak();
247        let mut fb = FrameBuf::new(
248            EndianCorrectedBuffer::new(backing, EndianCorrection::ToLittleEndian),
249            2,
250            2,
251        );
252        draw_to::<Rgb565, _>(
253            DrawPrimitive::ColoredPoint(Point2::new(1, 1), Rgb565::new(31, 0, 0)),
254            &mut fb,
255        );
256        assert_eq!(fb.get_color_at(Point::new(1, 1)), Rgb565::new(31, 0, 0));
257    }
258}