embedded_graphics_framebuf/
lib.rs1#![no_std]
43use embedded_dma::{ReadBuffer, WriteBuffer};
44use embedded_graphics::{
45 draw_target::DrawTarget,
46 geometry::OriginDimensions,
47 prelude::{PixelColor, Point, Size},
48 Pixel,
49};
50
51pub mod backends;
52use backends::{DMACapableFrameBufferBackend, FrameBufferBackend};
53
54pub struct FrameBuf<C: PixelColor, B: FrameBufferBackend<Color = C>> {
83 pub data: B,
84 width: usize,
85 height: usize,
86 origin: Point,
87}
88
89impl<C: PixelColor, B: FrameBufferBackend<Color = C>> FrameBuf<C, B> {
90 pub fn new(data: B, width: usize, height: usize) -> Self {
104 Self::new_with_origin(data, width, height, Point::new(0, 0))
105 }
106
107 pub fn new_with_origin(data: B, width: usize, height: usize, origin: Point) -> Self {
122 assert_eq!(
123 data.nr_elements(),
124 width * height,
125 "FrameBuf underlying data size does not match width ({}) * height ({}) = {} but is {}",
126 width,
127 height,
128 width * height,
129 data.nr_elements(),
130 );
131 Self {
132 data,
133 width,
134 height,
135 origin,
136 }
137 }
138
139 pub fn width(&self) -> usize {
141 self.width
142 }
143
144 pub fn height(&self) -> usize {
146 self.height
147 }
148
149 pub fn size(&self) -> Size {
151 Size::new(self.width as u32, self.height as u32)
152 }
153
154 pub fn origin(&self) -> Point {
155 self.origin
156 }
157
158 fn point_to_index(&self, p: Point) -> usize {
159 self.width * p.y as usize + p.x as usize
160 }
161
162 pub fn set_color_at(&mut self, p: Point, color: C) {
164 self.data.set(self.point_to_index(p), color)
165 }
166
167 pub fn get_color_at(&self, p: Point) -> C {
169 self.data.get(self.point_to_index(p))
170 }
171}
172impl<C: PixelColor + Default, B: FrameBufferBackend<Color = C>> FrameBuf<C, B> {
173 pub fn reset(&mut self) {
174 self.clear(C::default()).unwrap();
175 }
176}
177
178impl<'a, C: PixelColor, B: FrameBufferBackend<Color = C>> IntoIterator for &'a FrameBuf<C, B> {
179 type Item = Pixel<C>;
180 type IntoIter = PixelIterator<'a, C, B>;
181
182 fn into_iter(self) -> Self::IntoIter {
206 PixelIterator {
207 fbuf: self,
208 index: 0,
209 }
210 }
211}
212
213impl<C: PixelColor, B: FrameBufferBackend<Color = C>> OriginDimensions for FrameBuf<C, B> {
214 fn size(&self) -> Size {
215 self.size()
216 }
217}
218
219impl<C: PixelColor, B: FrameBufferBackend<Color = C>> DrawTarget for FrameBuf<C, B> {
220 type Color = C;
221 type Error = core::convert::Infallible;
222
223 fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
224 where
225 I: IntoIterator<Item = Pixel<Self::Color>>,
226 {
227 for Pixel(coord, color) in pixels.into_iter() {
228 if coord.x >= 0
229 && coord.x < self.width as i32
230 && coord.y >= 0
231 && coord.y < self.height as i32
232 {
233 self.set_color_at(coord, color);
234 }
235 }
236 Ok(())
237 }
238
239 fn clear(&mut self, color: Self::Color) -> Result<(), Self::Error> {
240 for y in 0..self.height {
241 for x in 0..self.width {
242 self.set_color_at(Point::new(x as i32, y as i32), color);
243 }
244 }
245 Ok(())
246 }
247}
248
249pub struct PixelIterator<'a, C: PixelColor, B: FrameBufferBackend<Color = C>> {
251 fbuf: &'a FrameBuf<C, B>,
252 index: usize,
253}
254
255impl<'a, C: PixelColor, B: FrameBufferBackend<Color = C>> Iterator for PixelIterator<'a, C, B> {
256 type Item = Pixel<C>;
257 fn next(&mut self) -> Option<Pixel<C>> {
258 let y = self.index / self.fbuf.width;
259 let x = self.index - y * self.fbuf.width;
260
261 if self.index >= self.fbuf.width * self.fbuf.height {
262 return None;
263 }
264 self.index += 1;
265 let p = Point::new(x as i32, y as i32);
266 Some(Pixel(self.fbuf.origin + p, self.fbuf.get_color_at(p)))
267 }
268}
269
270unsafe impl<C: PixelColor, B: DMACapableFrameBufferBackend<Color = C>> ReadBuffer
271 for FrameBuf<C, B>
272{
273 type Word = u8;
274 unsafe fn read_buffer(&self) -> (*const Self::Word, usize) {
275 (
276 (self.data.data_ptr() as *const Self::Word),
277 self.height
278 * self.width
279 * (core::mem::size_of::<C>() / core::mem::size_of::<Self::Word>()),
280 )
281 }
282}
283
284unsafe impl<C: PixelColor, B: DMACapableFrameBufferBackend<Color = C>> WriteBuffer
285 for FrameBuf<C, B>
286{
287 type Word = u8;
288 unsafe fn write_buffer(&mut self) -> (*mut Self::Word, usize) {
289 (
290 (self.data.data_ptr() as *mut Self::Word),
291 self.height
292 * self.width
293 * (core::mem::size_of::<C>() / core::mem::size_of::<Self::Word>()),
294 )
295 }
296}
297
298#[cfg(test)]
299mod tests {
300 extern crate std;
301
302 use embedded_graphics::mock_display::MockDisplay;
303 use embedded_graphics::pixelcolor::BinaryColor;
304 use embedded_graphics::prelude::Point;
305 use embedded_graphics::prelude::Primitive;
306 use embedded_graphics::primitives::Line;
307 use embedded_graphics::primitives::PrimitiveStyle;
308 use embedded_graphics::Drawable;
309 use embedded_graphics::{pixelcolor::Rgb565, prelude::RgbColor};
310 use std::collections::HashMap;
311 use std::fmt::Debug;
312 use std::hash::Hash;
313
314 use super::*;
315
316 fn get_px_nums<C: PixelColor, B: FrameBufferBackend<Color = C>>(
317 fbuf: &FrameBuf<C, B>,
318 ) -> HashMap<C, i32>
319 where
320 C: Hash,
321 C: std::cmp::Eq,
322 {
323 let mut px_nums: HashMap<C, i32> = HashMap::new();
324 for px in fbuf.into_iter() {
325 match px_nums.get_mut(&px.1) {
327 Some(v) => *v += 1,
328 None => {
329 px_nums.insert(px.1, 1);
330 }
331 };
332 }
334 px_nums
335 }
336
337 #[test]
338 fn clears_buffer() {
339 let mut data = [Rgb565::WHITE; 5 * 10];
340 let mut fbuf = FrameBuf::new(&mut data, 5, 10);
341 fbuf.reset();
342
343 let px_nums = get_px_nums(&fbuf);
344
345 assert_eq!(px_nums.get(&Rgb565::BLACK).unwrap(), &50);
346 assert_eq!(px_nums.get(&Rgb565::WHITE), None);
347 }
348
349 #[test]
350 fn clears_with_color() {
351 let mut data = [Rgb565::WHITE; 5 * 5];
352 let mut fbuf = FrameBuf::new(&mut data, 5, 5);
353 fbuf.clear(Rgb565::BLUE).unwrap();
354
355 let px_nums = get_px_nums(&fbuf);
356
357 assert_eq!(px_nums.get(&Rgb565::BLUE).unwrap(), &25);
358 assert_eq!(px_nums.get(&Rgb565::RED), None);
359 }
360
361 #[test]
362 fn draws_into_display() {
363 let mut data = [BinaryColor::Off; 12 * 11];
364 let mut fbuf = FrameBuf::new(&mut data, 12, 11);
365 let mut display: MockDisplay<BinaryColor> = MockDisplay::new();
366
367 Line::new(Point::new(2, 2), Point::new(10, 2))
369 .into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 2))
370 .draw(&mut fbuf)
371 .unwrap();
372
373 Line::new(Point::new(2, 5), Point::new(2, 10))
375 .into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 3))
376 .draw(&mut fbuf)
377 .unwrap();
378
379 display.draw_iter(fbuf.into_iter()).unwrap();
380 display.assert_pattern(&[
381 "............",
382 "..#########.",
383 "..#########.",
384 "............",
385 "............",
386 ".###........",
387 ".###........",
388 ".###........",
389 ".###........",
390 ".###........",
391 ".###........",
392 ]);
393 }
394
395 fn draw_into_drawtarget<D>(mut dt: D)
396 where
397 D: DrawTarget<Color = BinaryColor>,
398 D::Error: Debug,
399 {
400 Line::new(Point::new(2, 2), Point::new(10, 2))
401 .into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 2))
402 .draw(&mut dt)
403 .unwrap();
404 }
405
406 #[test]
407 fn usable_as_draw_target() {
408 let mut data = [BinaryColor::Off; 15 * 5];
409 let fbuf = FrameBuf::new(&mut data, 15, 5);
410 draw_into_drawtarget(fbuf)
411 }
412
413 #[test]
414 fn raw_data() {
415 let mut data = [Rgb565::new(1, 2, 3); 3 * 3];
416 let mut fbuf = FrameBuf::new(&mut data, 3, 3);
417 fbuf.set_color_at(Point { x: 1, y: 0 }, Rgb565::new(3, 2, 1));
418 let mut raw_iter = fbuf.data.iter();
419 assert_eq!(*raw_iter.next().unwrap(), Rgb565::new(1, 2, 3));
420 assert_eq!(*raw_iter.next().unwrap(), Rgb565::new(3, 2, 1));
421 }
422
423 #[test]
424 #[should_panic]
425 fn wrong_data_size() {
426 let mut data = [BinaryColor::Off; 5 * 5];
427 let _ = &mut FrameBuf::new(&mut data, 12, 3);
428 }
429}