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}