1#[cfg(any(
7 feature = "canvas",
8 feature = "gif",
9 feature = "apng",
10 feature = "nes",
11 feature = "png",
12 feature = "jpeg",
13 feature = "qrcode",
14 feature = "lottie",
15 feature = "fontdue",
16 test,
17))]
18use alloc::vec::Vec;
19#[cfg(feature = "fontdue")]
20use alloc::{collections::BTreeMap, vec};
21use bitflags::bitflags;
22use heapless::Vec as HVec;
23#[cfg(feature = "fontdue")]
24use rlvgl_core::fontdue::{Metrics, line_metrics, rasterize_glyph};
25use rlvgl_core::renderer::Renderer;
26use rlvgl_core::widget::{Color, Rect as WidgetRect};
27
28#[cfg(feature = "fontdue")]
29const FONT_DATA: &[u8] = include_bytes!("../../assets/fonts/DejaVuSans.ttf");
30
31#[cfg(feature = "fontdue")]
32fn round_to_i32(value: f32) -> i32 {
33 if value.is_nan() {
34 0
35 } else if value >= 0.0 {
36 (value + 0.5) as i32
37 } else {
38 (value - 0.5) as i32
39 }
40}
41
42#[cfg(feature = "fontdue")]
43#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
44struct GlyphKey {
46 font: *const u8,
48 size: u32,
50 ch: char,
52}
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq)]
56pub enum PixelFmt {
57 Argb8888,
59 Rgb565,
61 L8,
63 A8,
65 A4,
67}
68
69#[derive(Debug, Clone, Copy, PartialEq, Eq)]
71pub struct Rect {
72 pub x: i32,
74 pub y: i32,
76 pub w: u32,
78 pub h: u32,
80}
81
82pub struct Surface<'a> {
84 pub buf: &'a mut [u8],
86 pub stride: usize,
88 pub format: PixelFmt,
90 pub width: u32,
92 pub height: u32,
94}
95
96impl<'a> Surface<'a> {
97 pub fn new(
99 buf: &'a mut [u8],
100 stride: usize,
101 format: PixelFmt,
102 width: u32,
103 height: u32,
104 ) -> Self {
105 Self {
106 buf,
107 stride,
108 format,
109 width,
110 height,
111 }
112 }
113}
114
115bitflags! {
116 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
118 pub struct BlitCaps: u32 {
119 const FILL = 0b0001;
121 const BLIT = 0b0010;
123 const BLEND = 0b0100;
125 const PFC = 0b1000;
127 }
128}
129
130pub trait Blitter {
132 fn caps(&self) -> BlitCaps;
134
135 fn fill(&mut self, dst: &mut Surface, area: Rect, color: u32);
137
138 fn blit(&mut self, src: &Surface, src_area: Rect, dst: &mut Surface, dst_pos: (i32, i32));
140
141 fn blend(&mut self, src: &Surface, src_area: Rect, dst: &mut Surface, dst_pos: (i32, i32));
143}
144
145pub struct BlitPlanner<const N: usize> {
152 rects: HVec<Rect, N>,
153}
154
155impl<const N: usize> BlitPlanner<N> {
156 pub fn new() -> Self {
158 Self { rects: HVec::new() }
159 }
160
161 pub fn add(&mut self, rect: Rect) {
163 let _ = self.rects.push(rect);
164 }
165
166 pub fn rects(&self) -> &[Rect] {
168 &self.rects
169 }
170
171 pub fn clear(&mut self) {
173 self.rects.clear();
174 }
175}
176
177impl<const N: usize> Default for BlitPlanner<N> {
178 fn default() -> Self {
179 Self::new()
180 }
181}
182
183pub struct BlitterRenderer<'a, B: Blitter, const N: usize> {
189 blitter: &'a mut B,
190 surface: Surface<'a>,
191 planner: BlitPlanner<N>,
192 #[cfg(any(
193 feature = "canvas",
194 feature = "gif",
195 feature = "apng",
196 feature = "nes",
197 all(feature = "png", not(target_os = "none")),
198 all(feature = "jpeg", not(target_os = "none")),
199 all(feature = "qrcode", not(target_os = "none")),
200 feature = "lottie",
201 test,
202 ))]
203 scratch: Option<Vec<u8>>,
204 #[cfg(feature = "fontdue")]
205 glyph_cache: BTreeMap<GlyphKey, (Metrics, Vec<u8>)>,
206}
207
208impl<'a, B: Blitter, const N: usize> BlitterRenderer<'a, B, N> {
209 pub fn new(blitter: &'a mut B, surface: Surface<'a>) -> Self {
211 Self {
212 blitter,
213 surface,
214 planner: BlitPlanner::new(),
215 #[cfg(any(
216 feature = "canvas",
217 feature = "gif",
218 feature = "apng",
219 feature = "nes",
220 all(feature = "png", not(target_os = "none")),
221 all(feature = "jpeg", not(target_os = "none")),
222 all(feature = "qrcode", not(target_os = "none")),
223 feature = "lottie",
224 test,
225 ))]
226 scratch: None,
227 #[cfg(feature = "fontdue")]
228 glyph_cache: BTreeMap::new(),
229 }
230 }
231
232 pub fn planner(&mut self) -> &mut BlitPlanner<N> {
234 &mut self.planner
235 }
236
237 #[cfg(any(
238 feature = "canvas",
239 feature = "gif",
240 feature = "apng",
241 feature = "nes",
242 all(feature = "png", not(target_os = "none")),
243 all(feature = "jpeg", not(target_os = "none")),
244 all(feature = "qrcode", not(target_os = "none")),
245 feature = "lottie",
246 test,
247 ))]
248 fn blit_colors(&mut self, position: (i32, i32), pixels: &[Color], w: u32, h: u32) {
249 let required = (w * h * 4) as usize;
250 let buf = self.scratch.get_or_insert_with(Vec::new);
251 if buf.len() < required {
252 buf.resize(required, 0);
253 }
254 for (i, c) in pixels.iter().enumerate() {
255 buf[i * 4..i * 4 + 4].copy_from_slice(&c.to_argb8888().to_le_bytes());
256 }
257 let src = Surface::new(
258 &mut buf[..required],
259 (w * 4) as usize,
260 PixelFmt::Argb8888,
261 w,
262 h,
263 );
264 self.blitter
265 .blit(&src, Rect { x: 0, y: 0, w, h }, &mut self.surface, position);
266 self.planner.add(Rect {
267 x: position.0,
268 y: position.1,
269 w,
270 h,
271 });
272 }
273
274 #[cfg(all(feature = "png", not(target_os = "none")))]
275 pub fn draw_png(
277 &mut self,
278 position: (i32, i32),
279 data: &[u8],
280 ) -> Result<(), rlvgl_core::png::DecodingError> {
281 let (pixels, w, h) = rlvgl_core::png::decode(data)?;
282 self.blit_colors(position, &pixels, w, h);
283 Ok(())
284 }
285
286 #[cfg(all(feature = "jpeg", not(target_os = "none")))]
287 pub fn draw_jpeg(
289 &mut self,
290 position: (i32, i32),
291 data: &[u8],
292 ) -> Result<(), rlvgl_core::jpeg::Error> {
293 let (pixels, w, h) = rlvgl_core::jpeg::decode(data)?;
294 self.blit_colors(position, &pixels, w as u32, h as u32);
295 Ok(())
296 }
297
298 #[cfg(all(feature = "qrcode", not(target_os = "none")))]
299 pub fn draw_qr(
301 &mut self,
302 position: (i32, i32),
303 data: &[u8],
304 ) -> Result<(), rlvgl_core::qrcode::QrError> {
305 let (pixels, w, h) = rlvgl_core::qrcode::generate(data)?;
306 self.blit_colors(position, &pixels, w, h);
307 Ok(())
308 }
309
310 #[cfg(feature = "lottie")]
311 pub fn draw_lottie_frame(
315 &mut self,
316 position: (i32, i32),
317 json: &str,
318 frame: usize,
319 width: u32,
320 height: u32,
321 ) -> Result<(), rlvgl_core::lottie::Error> {
322 let pixels =
323 rlvgl_core::lottie::render_lottie_frame(json, frame, width as usize, height as usize)?;
324 self.blit_colors(position, &pixels, width, height);
325 Ok(())
326 }
327
328 #[cfg(feature = "canvas")]
329 pub fn draw_canvas(&mut self, position: (i32, i32), canvas: &rlvgl_core::canvas::Canvas) {
331 let (w, h) = canvas.size();
332 let pixels = canvas.pixels();
333 self.blit_colors(position, &pixels, w, h);
334 }
335
336 #[cfg(feature = "gif")]
337 pub fn draw_gif_frame(
339 &mut self,
340 position: (i32, i32),
341 data: &[u8],
342 frame: usize,
343 ) -> Result<(), rlvgl_core::gif::DecodingError> {
344 let (frames, w, h) = rlvgl_core::gif::decode(data)?;
345 if let Some(f) = frames.get(frame) {
346 self.blit_colors(position, &f.pixels, w as u32, h as u32);
347 }
348 Ok(())
349 }
350
351 #[cfg(feature = "apng")]
352 pub fn draw_apng_frame(
354 &mut self,
355 position: (i32, i32),
356 data: &[u8],
357 frame: usize,
358 ) -> Result<(), image::ImageError> {
359 let (frames, w, h) = rlvgl_core::apng::decode(data)?;
360 if let Some(f) = frames.get(frame) {
361 self.blit_colors(position, &f.pixels, w, h);
362 }
363 Ok(())
364 }
365
366 #[cfg(all(feature = "pinyin", feature = "fontdue"))]
367 pub fn draw_pinyin_candidates(
371 &mut self,
372 position: (i32, i32),
373 ime: &rlvgl_core::pinyin::PinyinInputMethod,
374 input: &str,
375 color: Color,
376 ) -> bool {
377 if let Some(chars) = ime.candidates(input) {
378 let max_w = self.surface.width as i32 - position.0;
380 let max_h = self.surface.height as i32 - position.1;
381 if max_w <= 0 || max_h < 16 {
382 return false;
383 }
384
385 let text: alloc::string::String = chars.into_iter().collect();
387 let max_chars = (max_w / 16) as usize;
388 let clipped: alloc::string::String = text.chars().take(max_chars).collect();
389 if clipped.is_empty() {
390 return false;
391 }
392 Renderer::draw_text(self, position, &clipped, color);
393 true
394 } else {
395 false
396 }
397 }
398
399 #[cfg(all(feature = "fatfs", feature = "fontdue"))]
400 pub fn draw_fatfs_dir<T>(
402 &mut self,
403 position: (i32, i32),
404 image: &mut T,
405 dir: &str,
406 color: Color,
407 ) -> Result<(), std::io::Error>
408 where
409 T: std::io::Read + std::io::Write + std::io::Seek,
410 {
411 let max_w = self.surface.width as i32 - position.0;
412 let max_h = self.surface.height as i32 - position.1;
413 if max_w <= 0 || max_h <= 0 {
414 return Ok(());
415 }
416 let line_h = 16;
417 let max_lines = (max_h / line_h) as usize;
418 let max_chars = (max_w / 16) as usize;
419 let names = rlvgl_core::fatfs::list_dir(image, dir)?;
420 for (i, name) in names.iter().take(max_lines).enumerate() {
421 let y = position.1 + (i as i32) * line_h;
422 let clipped: alloc::string::String = name.chars().take(max_chars).collect();
423 if clipped.is_empty() {
424 break;
425 }
426 Renderer::draw_text(self, (position.0, y), &clipped, color);
427 }
428 Ok(())
429 }
430
431 #[cfg(feature = "nes")]
432 pub fn draw_nes_frame(
434 &mut self,
435 position: (i32, i32),
436 pixels: &[Color],
437 width: u32,
438 height: u32,
439 ) {
440 self.blit_colors(position, pixels, width, height);
441 }
442
443 #[cfg(feature = "fontdue")]
444 pub fn draw_text(
446 &mut self,
447 position: (i32, i32),
448 text: &str,
449 color: Color,
450 font_data: &[u8],
451 px: f32,
452 ) {
453 let vm = line_metrics(font_data, px).unwrap();
454 let ascent = round_to_i32(vm.ascent);
455 let baseline = position.1 + ascent;
456 let mut x_cursor = position.0;
457 for ch in text.chars() {
458 let key = GlyphKey {
459 font: font_data.as_ptr(),
460 size: px.to_bits(),
461 ch,
462 };
463 let (metrics, bitmap) = {
464 let entry = self
465 .glyph_cache
466 .entry(key)
467 .or_insert_with(|| rasterize_glyph(font_data, ch, px).unwrap());
468 (entry.0, entry.1.clone())
469 };
470 let w = metrics.width as i32;
471 let h = metrics.height as i32;
472 if w == 0 || h == 0 {
473 x_cursor += round_to_i32(metrics.advance_width);
474 continue;
475 }
476 let mut argb = vec![0u8; (w * h * 4) as usize];
477 for y in 0..h {
478 for x in 0..w {
479 let alpha = bitmap[(y) as usize * metrics.width + x as usize];
480 let idx = ((y * w + x) * 4) as usize;
481 argb[idx] = (color.0 as u16 * alpha as u16 / 255) as u8;
482 argb[idx + 1] = (color.1 as u16 * alpha as u16 / 255) as u8;
483 argb[idx + 2] = (color.2 as u16 * alpha as u16 / 255) as u8;
484 argb[idx + 3] = alpha;
485 }
486 }
487 let src = Surface::new(
488 argb.as_mut_slice(),
489 (w * 4) as usize,
490 PixelFmt::Argb8888,
491 w as u32,
492 h as u32,
493 );
494 let dst_pos = (
495 x_cursor + metrics.xmin,
496 baseline - ascent - metrics.ymin - (h - 1),
497 );
498 self.blitter.blend(
499 &src,
500 Rect {
501 x: 0,
502 y: 0,
503 w: w as u32,
504 h: h as u32,
505 },
506 &mut self.surface,
507 dst_pos,
508 );
509 self.planner.add(Rect {
510 x: dst_pos.0,
511 y: dst_pos.1,
512 w: w as u32,
513 h: h as u32,
514 });
515 x_cursor += round_to_i32(metrics.advance_width);
516 }
517 }
518
519 #[cfg(not(feature = "fontdue"))]
520 pub fn draw_text(
522 &mut self,
523 position: (i32, i32),
524 text: &str,
525 color: Color,
526 _font_data: &[u8],
527 _px: f32,
528 ) {
529 let _ = (position, text, color);
530 }
531}
532
533impl<B: Blitter, const N: usize> Renderer for BlitterRenderer<'_, B, N> {
534 fn blend_rect(&mut self, rect: WidgetRect, color: Color) {
535 let alpha = color.3 as u16;
536 if alpha == 0 {
537 return;
538 }
539 if alpha == 255 {
540 self.fill_rect(rect, color);
541 return;
542 }
543 if self.surface.format == PixelFmt::Argb8888 {
545 let sw = self.surface.width as i32;
546 let sh = self.surface.height as i32;
547 let stride = self.surface.stride;
548 let inv = 255 - alpha;
549 let x0 = rect.x.max(0);
550 let y0 = rect.y.max(0);
551 let x1 = (rect.x + rect.width).min(sw);
552 let y1 = (rect.y + rect.height).min(sh);
553 for y in y0..y1 {
554 for x in x0..x1 {
555 let off = y as usize * stride + x as usize * 4;
556 let bg_b = self.surface.buf[off] as u16;
557 let bg_g = self.surface.buf[off + 1] as u16;
558 let bg_r = self.surface.buf[off + 2] as u16;
559 self.surface.buf[off] = ((color.2 as u16 * alpha + bg_b * inv) / 255) as u8;
560 self.surface.buf[off + 1] = ((color.1 as u16 * alpha + bg_g * inv) / 255) as u8;
561 self.surface.buf[off + 2] = ((color.0 as u16 * alpha + bg_r * inv) / 255) as u8;
562 self.surface.buf[off + 3] = 0xff;
563 }
564 }
565 self.planner.add(Rect {
566 x: rect.x,
567 y: rect.y,
568 w: rect.width as u32,
569 h: rect.height as u32,
570 });
571 } else {
572 self.fill_rect(rect, color);
574 }
575 }
576
577 fn fill_rect(&mut self, rect: WidgetRect, color: Color) {
578 let r = Rect {
579 x: rect.x,
580 y: rect.y,
581 w: rect.width as u32,
582 h: rect.height as u32,
583 };
584 self.planner.add(r);
585 self.blitter.fill(&mut self.surface, r, color.to_argb8888());
586 }
587
588 fn draw_text(&mut self, position: (i32, i32), text: &str, color: Color) {
589 #[cfg(feature = "fontdue")]
590 {
591 const PX: f32 = 16.0;
592 BlitterRenderer::draw_text(self, position, text, color, FONT_DATA, PX);
593 }
594 #[cfg(not(feature = "fontdue"))]
595 {
596 let _ = (position, text, color);
597 }
598 }
599
600 fn draw_pixels(&mut self, position: (i32, i32), pixels: &[Color], width: u32, height: u32) {
601 if self.surface.format == PixelFmt::Argb8888 {
604 let sw = self.surface.width as i32;
605 let sh = self.surface.height as i32;
606 let stride = self.surface.stride;
607 for y in 0..height as i32 {
608 let dy = position.1 + y;
609 if dy < 0 || dy >= sh {
610 continue;
611 }
612 for x in 0..width as i32 {
613 let dx = position.0 + x;
614 if dx < 0 || dx >= sw {
615 continue;
616 }
617 let src_idx = (y as u32 * width + x as u32) as usize;
618 if let Some(&c) = pixels.get(src_idx) {
619 let off = dy as usize * stride + dx as usize * 4;
620 self.surface.buf[off..off + 4]
621 .copy_from_slice(&c.to_argb8888().to_le_bytes());
622 }
623 }
624 }
625 self.planner.add(Rect {
626 x: position.0,
627 y: position.1,
628 w: width,
629 h: height,
630 });
631 } else {
632 for y in 0..height as i32 {
634 for x in 0..width as i32 {
635 let idx = (y as u32 * width + x as u32) as usize;
636 if let Some(&c) = pixels.get(idx) {
637 self.fill_rect(
638 WidgetRect {
639 x: position.0 + x,
640 y: position.1 + y,
641 width: 1,
642 height: 1,
643 },
644 c,
645 );
646 }
647 }
648 }
649 }
650 }
651}
652
653pub struct RotatedRenderer<'a> {
663 inner: &'a mut dyn Renderer,
664 fb_width: i32,
666}
667
668impl<'a> RotatedRenderer<'a> {
669 pub fn new(inner: &'a mut dyn Renderer, fb_width: u32) -> Self {
673 Self {
674 inner,
675 fb_width: fb_width as i32,
676 }
677 }
678}
679
680impl Renderer for RotatedRenderer<'_> {
681 fn fill_rect(&mut self, rect: WidgetRect, color: Color) {
682 let mut fb_x = self.fb_width - rect.y - rect.height;
683 let fb_y = rect.x;
684 let mut fb_w = rect.height;
685 let fb_h = rect.width;
686
687 if fb_w <= 0 || fb_h <= 0 {
688 return;
689 }
690
691 if fb_x < 0 {
693 fb_w += fb_x; fb_x = 0;
695 }
696 if fb_x + fb_w > self.fb_width {
698 fb_w = self.fb_width - fb_x;
699 }
700 if fb_w <= 0 {
701 return;
702 }
703
704 self.inner.fill_rect(
705 WidgetRect {
706 x: fb_x,
707 y: fb_y,
708 width: fb_w,
709 height: fb_h,
710 },
711 color,
712 );
713 }
714
715 fn blend_rect(&mut self, rect: WidgetRect, color: Color) {
716 let mut fb_x = self.fb_width - rect.y - rect.height;
717 let fb_y = rect.x;
718 let mut fb_w = rect.height;
719 let fb_h = rect.width;
720
721 if fb_w <= 0 || fb_h <= 0 {
722 return;
723 }
724
725 if fb_x < 0 {
726 fb_w += fb_x;
727 fb_x = 0;
728 }
729 if fb_x + fb_w > self.fb_width {
730 fb_w = self.fb_width - fb_x;
731 }
732 if fb_w <= 0 {
733 return;
734 }
735
736 self.inner.blend_rect(
737 WidgetRect {
738 x: fb_x,
739 y: fb_y,
740 width: fb_w,
741 height: fb_h,
742 },
743 color,
744 );
745 }
746
747 fn draw_text(&mut self, position: (i32, i32), text: &str, color: Color) {
748 let fx = self.fb_width - 1 - position.1;
749 if fx >= 0 {
750 self.inner.draw_text((fx, position.0), text, color);
751 }
752 }
753
754 fn draw_pixels(&mut self, position: (i32, i32), pixels: &[Color], width: u32, height: u32) {
755 for py in 0..height as i32 {
756 for px in 0..width as i32 {
757 let idx = (py as u32 * width + px as u32) as usize;
758 if let Some(&c) = pixels.get(idx) {
759 self.fill_rect(
760 WidgetRect {
761 x: position.0 + px,
762 y: position.1 + py,
763 width: 1,
764 height: 1,
765 },
766 c,
767 );
768 }
769 }
770 }
771 }
772}
773
774#[cfg(test)]
775mod scratch_tests {
776 use super::*;
777 use crate::cpu_blitter::CpuBlitter;
778
779 #[test]
780 fn blit_colors_reuses_scratch_buffer() {
781 let mut buf = [0u8; 4 * 4 * 4];
782 let surface = Surface::new(&mut buf, 4 * 4, PixelFmt::Argb8888, 4, 4);
783 let mut blit = CpuBlitter;
784 let mut renderer: BlitterRenderer<'_, CpuBlitter, 4> =
785 BlitterRenderer::new(&mut blit, surface);
786 let pixels = [Color(0, 0, 0, 0)];
787 renderer.blit_colors((0, 0), &pixels, 1, 1);
788 let first_ptr = renderer.scratch.as_ref().unwrap().as_ptr();
789 renderer.blit_colors((1, 1), &pixels, 1, 1);
790 let second_ptr = renderer.scratch.as_ref().unwrap().as_ptr();
791 assert_eq!(first_ptr, second_ptr);
792 }
793}
794
795#[cfg(all(test, feature = "fontdue"))]
796mod text_tests {
797 use super::*;
798 use crate::cpu_blitter::CpuBlitter;
799
800 #[test]
801 fn blitter_draws_text() {
802 let mut buf = [0u8; 64 * 64 * 4];
803 let surface = Surface::new(&mut buf, 64 * 4, PixelFmt::Argb8888, 64, 64);
804 let mut blit = CpuBlitter;
805 let mut renderer: BlitterRenderer<'_, CpuBlitter, 4> =
806 BlitterRenderer::new(&mut blit, surface);
807 Renderer::draw_text(&mut renderer, (0, 32), "A", Color(255, 255, 255, 255));
808 assert!(buf.iter().any(|&p| p != 0));
809 }
810
811 #[test]
812 fn cache_accounts_for_size() {
813 let mut buf = [0u8; 64 * 64 * 4];
814 let surface = Surface::new(&mut buf, 64 * 4, PixelFmt::Argb8888, 64, 64);
815 let mut blit = CpuBlitter;
816 let mut renderer: BlitterRenderer<'_, CpuBlitter, 4> =
817 BlitterRenderer::new(&mut blit, surface);
818 renderer.draw_text((0, 32), "Hi", Color(255, 255, 255, 255), FONT_DATA, 16.0);
819 let len_after_small = renderer.glyph_cache.len();
820 renderer.draw_text((0, 32), "Hi", Color(255, 255, 255, 255), FONT_DATA, 16.0);
821 assert_eq!(len_after_small, renderer.glyph_cache.len());
822 renderer.draw_text((0, 32), "Hi", Color(255, 255, 255, 255), FONT_DATA, 24.0);
823 assert!(renderer.glyph_cache.len() > len_after_small);
824 }
825}
826
827#[cfg(all(test, feature = "png", not(target_os = "none")))]
828mod png_tests {
829 use super::*;
830 use crate::cpu_blitter::CpuBlitter;
831 use base64::Engine;
832 use rlvgl_core::png::DecodingError;
833
834 const RED_DOT_PNG: &str = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVR4nGP4z8AAAAMBAQDJ/pLvAAAAAElFTkSuQmCC";
835
836 #[test]
837 fn blitter_draws_png() {
838 let data = base64::engine::general_purpose::STANDARD
839 .decode(RED_DOT_PNG)
840 .unwrap();
841 let mut buf = [0u8; 4 * 4 * 4];
842 let surface = Surface::new(&mut buf, 4 * 4, PixelFmt::Argb8888, 4, 4);
843 let mut blit = CpuBlitter;
844 let mut renderer: BlitterRenderer<'_, CpuBlitter, 4> =
845 BlitterRenderer::new(&mut blit, surface);
846 renderer.draw_png((0, 0), &data).unwrap();
847 assert!(buf.iter().any(|&p| p != 0));
848 }
849
850 #[test]
851 fn blitter_rejects_invalid_png() {
852 let mut buf = [0u8; 4 * 4 * 4];
853 let surface = Surface::new(&mut buf, 4 * 4, PixelFmt::Argb8888, 4, 4);
854 let mut blit = CpuBlitter;
855 let mut renderer: BlitterRenderer<'_, CpuBlitter, 4> =
856 BlitterRenderer::new(&mut blit, surface);
857 let err = renderer.draw_png((0, 0), b"not a png").unwrap_err();
858 assert!(matches!(err, DecodingError::Format(_)));
859 }
860}
861
862#[cfg(all(test, feature = "jpeg", not(target_os = "none")))]
863mod jpeg_tests {
864 use super::*;
865 use crate::cpu_blitter::CpuBlitter;
866 use base64::Engine;
867 use rlvgl_core::jpeg::Error as JpegError;
868
869 const RED_DOT_JPEG: &str = "/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCAABAAEDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwDi6KKK+ZP3E//Z";
870
871 #[test]
872 fn blitter_draws_jpeg() {
873 let data = base64::engine::general_purpose::STANDARD
874 .decode(RED_DOT_JPEG)
875 .unwrap();
876 let mut buf = [0u8; 4 * 4 * 4];
877 let surface = Surface::new(&mut buf, 4 * 4, PixelFmt::Argb8888, 4, 4);
878 let mut blit = CpuBlitter;
879 let mut renderer: BlitterRenderer<'_, CpuBlitter, 4> =
880 BlitterRenderer::new(&mut blit, surface);
881 renderer.draw_jpeg((0, 0), &data).unwrap();
882 assert!(buf.iter().any(|&p| p != 0));
883 }
884
885 #[test]
886 fn blitter_rejects_invalid_jpeg() {
887 let mut buf = [0u8; 4 * 4 * 4];
888 let surface = Surface::new(&mut buf, 4 * 4, PixelFmt::Argb8888, 4, 4);
889 let mut blit = CpuBlitter;
890 let mut renderer: BlitterRenderer<'_, CpuBlitter, 4> =
891 BlitterRenderer::new(&mut blit, surface);
892 let err = renderer.draw_jpeg((0, 0), b"not a jpeg").unwrap_err();
893 assert!(matches!(err, JpegError::Format(_)));
894 }
895}
896
897#[cfg(all(test, feature = "gif"))]
898mod gif_tests {
899 use super::*;
900 use crate::cpu_blitter::CpuBlitter;
901 use base64::Engine;
902 use rlvgl_core::gif::DecodingError as GifDecodingError;
903
904 const RED_DOT_GIF: &str = "R0lGODdhAQABAPAAAP8AAP///yH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==";
905
906 #[test]
907 fn blitter_draws_gif() {
908 let data = base64::engine::general_purpose::STANDARD
909 .decode(RED_DOT_GIF)
910 .unwrap();
911 let mut buf = [0u8; 4 * 4 * 4];
912 let surface = Surface::new(&mut buf, 4 * 4, PixelFmt::Argb8888, 4, 4);
913 let mut blit = CpuBlitter;
914 let mut renderer: BlitterRenderer<'_, CpuBlitter, 4> =
915 BlitterRenderer::new(&mut blit, surface);
916 renderer.draw_gif_frame((0, 0), &data, 0).unwrap();
917 assert!(buf.iter().any(|&p| p != 0));
918 }
919
920 #[test]
921 fn blitter_rejects_invalid_gif() {
922 let mut buf = [0u8; 4 * 4 * 4];
923 let surface = Surface::new(&mut buf, 4 * 4, PixelFmt::Argb8888, 4, 4);
924 let mut blit = CpuBlitter;
925 let mut renderer: BlitterRenderer<'_, CpuBlitter, 4> =
926 BlitterRenderer::new(&mut blit, surface);
927 let err = renderer
928 .draw_gif_frame((0, 0), b"not a gif", 0)
929 .unwrap_err();
930 assert!(matches!(err, GifDecodingError::Format(_)));
931 }
932}
933
934#[cfg(all(test, feature = "apng"))]
935mod apng_tests {
936 use super::*;
937 use crate::cpu_blitter::CpuBlitter;
938 use base64::Engine;
939
940 const RED_DOT_APNG: &str = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACGFjVEwAAAABAAAAALQt6aAAAAAaZmNUTAAAAAAAAAABAAAAAQAAAAAAAAAAAGQD6AEAqmVSjAAAAA1JREFUeJxj+M/A8B8ABQAB/4mZPR0AAAAASUVORK5CYII=";
941
942 #[test]
943 fn blitter_draws_apng() {
944 let data = base64::engine::general_purpose::STANDARD
945 .decode(RED_DOT_APNG)
946 .unwrap();
947 let mut buf = [0u8; 4 * 4 * 4];
948 let surface = Surface::new(&mut buf, 4 * 4, PixelFmt::Argb8888, 4, 4);
949 let mut blit = CpuBlitter;
950 let mut renderer: BlitterRenderer<'_, CpuBlitter, 4> =
951 BlitterRenderer::new(&mut blit, surface);
952 renderer.draw_apng_frame((0, 0), &data, 0).unwrap();
953 assert!(buf.iter().any(|&p| p != 0));
954 }
955}
956
957#[cfg(all(test, feature = "canvas"))]
958mod canvas_tests {
959 use super::*;
960 use crate::cpu_blitter::CpuBlitter;
961 use embedded_graphics::prelude::Point;
962 use rlvgl_core::canvas::Canvas;
963
964 #[test]
965 fn blitter_draws_canvas() {
966 let mut canvas = Canvas::new(1, 1);
967 canvas.draw_pixel(Point::new(0, 0), Color(255, 0, 0, 255));
968 let mut buf = [0u8; 4];
969 let surface = Surface::new(&mut buf, 4, PixelFmt::Argb8888, 1, 1);
970 let mut blit = CpuBlitter;
971 let mut renderer: BlitterRenderer<'_, CpuBlitter, 4> =
972 BlitterRenderer::new(&mut blit, surface);
973 renderer.draw_canvas((0, 0), &canvas);
974 assert!(buf.iter().any(|&p| p != 0));
975 }
976}
977
978#[cfg(all(test, feature = "qrcode", not(target_os = "none")))]
979mod qrcode_tests {
980 use super::*;
981 use crate::cpu_blitter::CpuBlitter;
982 use rlvgl_core::qrcode::QrError;
983
984 #[test]
985 fn blitter_draws_qr() {
986 let mut buf = [0u8; 64 * 64 * 4];
987 let surface = Surface::new(&mut buf, 64 * 4, PixelFmt::Argb8888, 64, 64);
988 let mut blit = CpuBlitter;
989 let mut renderer: BlitterRenderer<'_, CpuBlitter, 4> =
990 BlitterRenderer::new(&mut blit, surface);
991 renderer.draw_qr((0, 0), b"hi").unwrap();
992 assert!(buf.iter().any(|&p| p != 0));
993 }
994
995 #[test]
996 fn blitter_rejects_invalid_qr_data() {
997 let mut buf = [0u8; 64 * 64 * 4];
998 let surface = Surface::new(&mut buf, 64 * 4, PixelFmt::Argb8888, 64, 64);
999 let mut blit = CpuBlitter;
1000 let mut renderer: BlitterRenderer<'_, CpuBlitter, 4> =
1001 BlitterRenderer::new(&mut blit, surface);
1002 let data = vec![0u8; 3000];
1003 let err = renderer.draw_qr((0, 0), &data).unwrap_err();
1004 assert!(matches!(err, QrError::DataTooLong));
1005 }
1006}
1007
1008#[cfg(all(test, feature = "lottie"))]
1009mod lottie_tests {
1010 use super::*;
1011 use crate::cpu_blitter::CpuBlitter;
1012
1013 const SIMPLE_JSON: &str =
1014 "{\"v\":\"5.7\",\"fr\":30,\"ip\":0,\"op\":0,\"w\":1,\"h\":1,\"layers\":[]}";
1015
1016 #[test]
1017 fn blitter_draws_lottie() {
1018 let mut buf = [0u8; 4 * 4 * 4];
1019 let surface = Surface::new(&mut buf, 4 * 4, PixelFmt::Argb8888, 4, 4);
1020 let mut blit = CpuBlitter;
1021 let mut renderer: BlitterRenderer<'_, CpuBlitter, 4> =
1022 BlitterRenderer::new(&mut blit, surface);
1023 renderer
1024 .draw_lottie_frame((0, 0), SIMPLE_JSON, 0, 1, 1)
1025 .unwrap();
1026 assert!(buf.iter().any(|&p| p != 0));
1027 }
1028
1029 #[test]
1030 fn blitter_rejects_invalid_lottie() {
1031 let mut buf = [0u8; 4 * 4 * 4];
1032 let surface = Surface::new(&mut buf, 4 * 4, PixelFmt::Argb8888, 4, 4);
1033 let mut blit = CpuBlitter;
1034 let mut renderer: BlitterRenderer<'_, CpuBlitter, 4> =
1035 BlitterRenderer::new(&mut blit, surface);
1036 assert!(
1037 renderer
1038 .draw_lottie_frame((0, 0), "not json", 0, 1, 1)
1039 .is_err()
1040 );
1041 }
1042}
1043
1044#[cfg(all(test, feature = "pinyin", feature = "fontdue"))]
1045mod pinyin_tests {
1046 use super::*;
1047 use crate::cpu_blitter::CpuBlitter;
1048 use rlvgl_core::pinyin::PinyinInputMethod;
1049
1050 #[test]
1051 fn blitter_draws_pinyin() {
1052 let mut buf = [0u8; 64 * 64 * 4];
1053 let surface = Surface::new(&mut buf, 64 * 4, PixelFmt::Argb8888, 64, 64);
1054 let mut blit = CpuBlitter;
1055 let mut renderer: BlitterRenderer<'_, CpuBlitter, 4> =
1056 BlitterRenderer::new(&mut blit, surface);
1057 let ime = PinyinInputMethod;
1058 assert!(renderer.draw_pinyin_candidates((0, 0), &ime, "zhong", Color(255, 255, 255, 255)));
1059 assert!(buf.iter().any(|&p| p != 0));
1060 }
1061
1062 #[test]
1063 fn pinyin_candidates_clipped_to_surface() {
1064 let mut buf = [0u8; 32 * 16 * 4];
1065 let surface = Surface::new(&mut buf, 32 * 4, PixelFmt::Argb8888, 32, 16);
1066 let mut blit = CpuBlitter;
1067 let mut renderer: BlitterRenderer<'_, CpuBlitter, 4> =
1068 BlitterRenderer::new(&mut blit, surface);
1069 let ime = PinyinInputMethod;
1070 assert!(renderer.draw_pinyin_candidates((0, 0), &ime, "zhong", Color(255, 255, 255, 255)));
1071
1072 let mut expected = [0u8; 32 * 16 * 4];
1073 let surface_e = Surface::new(&mut expected, 32 * 4, PixelFmt::Argb8888, 32, 16);
1074 let mut blit_e = CpuBlitter;
1075 let mut renderer_e: BlitterRenderer<'_, CpuBlitter, 4> =
1076 BlitterRenderer::new(&mut blit_e, surface_e);
1077 let chars = ime.candidates("zhong").unwrap();
1078 let text: alloc::string::String = chars.into_iter().collect();
1079 let clipped: alloc::string::String = text.chars().take(2).collect();
1080 Renderer::draw_text(&mut renderer_e, (0, 0), &clipped, Color(255, 255, 255, 255));
1081 assert_eq!(buf[..], expected[..]);
1082 }
1083}
1084
1085#[cfg(all(test, feature = "fatfs", feature = "fontdue"))]
1086mod fatfs_tests {
1087 use super::*;
1088 use crate::cpu_blitter::CpuBlitter;
1089 use fatfs::{FileSystem, FormatVolumeOptions, FsOptions};
1090 use fscommon::BufStream;
1091 use std::io::{Cursor, Seek, SeekFrom, Write};
1092
1093 #[test]
1094 fn blitter_draws_fatfs_listing() {
1095 let mut img = Cursor::new(vec![0u8; 1024 * 512]);
1096 fatfs::format_volume(&mut img, FormatVolumeOptions::new()).unwrap();
1097 img.seek(SeekFrom::Start(0)).unwrap();
1098 {
1099 let buf_stream = BufStream::new(&mut img);
1100 let fs = FileSystem::new(buf_stream, FsOptions::new()).unwrap();
1101 fs.root_dir()
1102 .create_file("foo.txt")
1103 .unwrap()
1104 .write_all(b"hi")
1105 .unwrap();
1106 }
1107 img.seek(SeekFrom::Start(0)).unwrap();
1108 let mut buf = [0u8; 64 * 64 * 4];
1109 let surface = Surface::new(&mut buf, 64 * 4, PixelFmt::Argb8888, 64, 64);
1110 let mut blit = CpuBlitter;
1111 let mut renderer: BlitterRenderer<'_, CpuBlitter, 4> =
1112 BlitterRenderer::new(&mut blit, surface);
1113 renderer
1114 .draw_fatfs_dir((0, 0), &mut img, "/", Color(255, 255, 255, 255))
1115 .unwrap();
1116 assert!(buf.iter().any(|&p| p != 0));
1117 }
1118
1119 #[test]
1120 fn fatfs_listing_clipped_to_surface() {
1121 let mut img = Cursor::new(vec![0u8; 1024 * 512]);
1122 fatfs::format_volume(&mut img, FormatVolumeOptions::new()).unwrap();
1123 img.seek(SeekFrom::Start(0)).unwrap();
1124 {
1125 let buf_stream = BufStream::new(&mut img);
1126 let fs = FileSystem::new(buf_stream, FsOptions::new()).unwrap();
1127 fs.root_dir().create_file("first_long_name.txt").unwrap();
1128 fs.root_dir().create_file("second_long_name.txt").unwrap();
1129 fs.root_dir().create_file("third_long_name.txt").unwrap();
1130 }
1131 img.seek(SeekFrom::Start(0)).unwrap();
1132 let image_vec = img.get_ref().clone();
1133 let mut img_expected = Cursor::new(image_vec.clone());
1134 let mut img_actual = Cursor::new(image_vec);
1135
1136 let mut buf = [0u8; 32 * 32 * 4];
1137 let surface = Surface::new(&mut buf, 32 * 4, PixelFmt::Argb8888, 32, 32);
1138 let mut blit = CpuBlitter;
1139 let mut renderer: BlitterRenderer<'_, CpuBlitter, 4> =
1140 BlitterRenderer::new(&mut blit, surface);
1141 renderer
1142 .draw_fatfs_dir((0, 0), &mut img_actual, "/", Color(255, 255, 255, 255))
1143 .unwrap();
1144
1145 let names = rlvgl_core::fatfs::list_dir(&mut img_expected, "/").unwrap();
1146 let mut expected = [0u8; 32 * 32 * 4];
1147 let surface_e = Surface::new(&mut expected, 32 * 4, PixelFmt::Argb8888, 32, 32);
1148 let mut blit_e = CpuBlitter;
1149 let mut renderer_e: BlitterRenderer<'_, CpuBlitter, 4> =
1150 BlitterRenderer::new(&mut blit_e, surface_e);
1151 for (i, name) in names.iter().take(2).enumerate() {
1152 let clipped: alloc::string::String = name.chars().take(2).collect();
1153 Renderer::draw_text(
1154 &mut renderer_e,
1155 (0, (i as i32) * 16),
1156 &clipped,
1157 Color(255, 255, 255, 255),
1158 );
1159 }
1160 assert_eq!(buf[..], expected[..]);
1161 }
1162}
1163
1164#[cfg(all(test, feature = "nes"))]
1165mod nes_tests {
1166 use super::*;
1167 use crate::cpu_blitter::CpuBlitter;
1168
1169 #[test]
1170 fn blitter_draws_nes_frame() {
1171 let pixels = [Color(255, 0, 0, 255)];
1172 let mut buf = [0u8; 4];
1173 let surface = Surface::new(&mut buf, 4, PixelFmt::Argb8888, 1, 1);
1174 let mut blit = CpuBlitter;
1175 let mut renderer: BlitterRenderer<'_, CpuBlitter, 4> =
1176 BlitterRenderer::new(&mut blit, surface);
1177 renderer.draw_nes_frame((0, 0), &pixels, 1, 1);
1178 assert!(buf.iter().any(|&p| p != 0));
1179 }
1180
1181 #[test]
1182 fn blitter_draws_full_nes_frame() {
1183 let mut pixels = [Color(0, 0, 0, 255); 256 * 240];
1184 for y in 0..240 {
1185 for x in 0..256 {
1186 pixels[y * 256 + x] = Color(x as u8, y as u8, 0, 255);
1187 }
1188 }
1189 let mut buf = [0u8; 256 * 240 * 4];
1190 let surface = Surface::new(&mut buf, 256 * 4, PixelFmt::Argb8888, 256, 240);
1191 let mut blit = CpuBlitter;
1192 let mut renderer: BlitterRenderer<'_, CpuBlitter, 4> =
1193 BlitterRenderer::new(&mut blit, surface);
1194 renderer.draw_nes_frame((0, 0), &pixels, 256, 240);
1195 let x = 128usize;
1196 let y = 120usize;
1197 let idx = (y * 256 + x) * 4;
1198 let actual = u32::from_le_bytes(buf[idx..idx + 4].try_into().unwrap());
1199 let expected = Color(x as u8, y as u8, 0, 255).to_argb8888();
1200 assert_eq!(actual, expected);
1201 }
1202}