1#![cfg_attr(feature = "doc-images",
27cfg_attr(all(),
28doc = ::embed_doc_image::embed_image!("latch-circuit", "images/latch-circuit.png")))]
29#![cfg_attr(
30 not(feature = "doc-images"),
31 doc = "**Doc images not enabled**. Compile with feature `doc-images` and Rust version >= 1.54 \
32 to enable."
33)]
34use core::convert::Infallible;
178
179use super::Color;
180use crate::{
181 map_row_index, slot_addresses, BcmLenReps, FrameBuffer, FrameBufferOperations,
182 MutableFrameBuffer, BCM_SEQUENCE_CAPACITY, INTER_ROW_BLANK, LEAD_BLANK_DELAY,
183 TRAIL_BLANK_DELAY,
184};
185use bitfield::bitfield;
186use embedded_graphics::pixelcolor::Rgb888;
187use embedded_graphics::pixelcolor::RgbColor;
188use embedded_graphics::prelude::Point;
189
190use crate::pinmap::{
191 LATCHED_COLOR0_MASK, LATCHED_COLOR1_MASK, LATCHED_COLOR_MASK, LATCHED_LATCH_BIT,
192 LATCHED_OE_BIT, LATCHED_PINMAP,
193};
194
195#[cfg(not(feature = "invert-oe"))]
196const OE_ACTIVE: u8 = LATCHED_OE_BIT;
197#[cfg(not(feature = "invert-oe"))]
198const OE_BLANK: u8 = 0;
199
200#[cfg(feature = "invert-oe")]
201const OE_ACTIVE: u8 = 0;
202#[cfg(feature = "invert-oe")]
203const OE_BLANK: u8 = LATCHED_OE_BIT;
204
205bitfield! {
206 #[derive(Clone, Copy, Default, PartialEq, Eq)]
241 #[repr(transparent)]
242 struct Address(u8);
243 impl Debug;
244 pub output_enable, set_output_enable: LATCHED_PINMAP.oe;
245 pub latch, set_latch: LATCHED_PINMAP.latch;
246 pub addr, set_addr: LATCHED_PINMAP.addr_msb(), LATCHED_PINMAP.addr_lsb;
247}
248
249impl Address {
250 pub const fn new() -> Self {
251 Self(0)
252 }
253}
254
255bitfield! {
256 #[derive(Clone, Copy, Default, PartialEq)]
273 #[repr(transparent)]
274 struct Entry(u8);
275 impl Debug;
276 pub output_enable, set_output_enable: LATCHED_PINMAP.oe;
277 pub latch, set_latch: LATCHED_PINMAP.latch;
278 pub blu2, set_blu2: LATCHED_PINMAP.blu1;
279 pub grn2, set_grn2: LATCHED_PINMAP.grn1;
280 pub red2, set_red2: LATCHED_PINMAP.red1;
281 pub blu1, set_blu1: LATCHED_PINMAP.blu0;
282 pub grn1, set_grn1: LATCHED_PINMAP.grn0;
283 pub red1, set_red1: LATCHED_PINMAP.red0;
284}
285
286impl Entry {
287 pub const fn new() -> Self {
288 Self(0)
289 }
290
291 #[inline]
294 fn set_color0_bits(&mut self, bits: u8) {
295 self.0 = (self.0 & !LATCHED_COLOR0_MASK) | (bits & LATCHED_COLOR0_MASK);
296 }
297
298 #[inline]
299 fn set_color1_bits(&mut self, bits: u8) {
300 self.0 = (self.0 & !LATCHED_COLOR1_MASK) | ((bits << 3) & LATCHED_COLOR1_MASK);
301 }
302}
303
304#[derive(Clone, Copy, PartialEq, Debug)]
315#[repr(C)]
316struct Row<const COLS: usize> {
317 data: [Entry; COLS],
318 address: [Address; 4],
319 gap: [Entry; INTER_ROW_BLANK],
320}
321
322#[inline]
324const fn map_index(index: usize) -> usize {
325 #[cfg(feature = "esp32-ordering")]
326 {
327 index ^ 2
328 }
329 #[cfg(not(feature = "esp32-ordering"))]
330 {
331 index
332 }
333}
334
335const fn make_addr_table() -> [[Address; 4]; 32] {
338 let mut tbl = [[Address::new(); 4]; 32];
339 let mut addr = 0;
340 while addr < 32 {
341 let mut i = 0;
342 while i < 4 {
343 let latch = i != 3;
344 let mapped_i = map_index(i);
345 let latch_bit = if latch { LATCHED_LATCH_BIT } else { 0u8 };
346 tbl[addr][mapped_i].0 = OE_BLANK | latch_bit | addr as u8;
347 i += 1;
348 }
349 addr += 1;
350 }
351 tbl
352}
353
354const ADDR_TABLE: [[Address; 4]; 32] = make_addr_table();
355
356#[allow(clippy::absurd_extreme_comparisons)]
359const fn make_data_template<const COLS: usize>() -> [Entry; COLS] {
360 let mut data = [Entry::new(); COLS];
361 let mut i = 0;
362 while i < COLS {
363 let mapped_i = map_index(i);
364 data[mapped_i].0 =
365 if i >= TRAIL_BLANK_DELAY && i < COLS.saturating_sub(LEAD_BLANK_DELAY + 1) {
366 OE_ACTIVE
367 } else {
368 OE_BLANK
369 };
370 i += 1;
371 }
372 data
373}
374
375impl<const COLS: usize> Row<COLS> {
376 pub const fn new() -> Self {
377 Self {
378 address: [Address::new(); 4],
379 data: [Entry::new(); COLS],
380 gap: [Entry::new(); INTER_ROW_BLANK],
381 }
382 }
383
384 #[inline]
385 pub const fn format(&mut self, addr: u8) {
386 let src_addr = ADDR_TABLE[addr as usize];
387 let mut i = 0;
388 while i < 4 {
389 self.address[i] = src_addr[i];
390 i += 1;
391 }
392
393 let data_template = make_data_template::<COLS>();
394 let mut i = 0;
395 while i < COLS {
396 self.data[i] = data_template[i];
397 i += 1;
398 }
399
400 let mut i = 0;
401 #[allow(clippy::absurd_extreme_comparisons)]
403 while i < INTER_ROW_BLANK {
404 self.gap[i].0 = OE_BLANK;
405 i += 1;
406 }
407 }
408
409 #[inline]
411 pub fn clear_colors(&mut self) {
412 const COLOR_CLEAR_MASK: u8 = !LATCHED_COLOR_MASK; for entry in &mut self.data {
416 entry.0 &= COLOR_CLEAR_MASK;
417 }
418 }
419
420 #[inline]
421 pub fn set_color0(&mut self, col: usize, r: bool, g: bool, b: bool) {
422 let bits = (u8::from(b) << 2) | (u8::from(g) << 1) | u8::from(r);
423 let col = map_index(col);
424 self.data[col].set_color0_bits(bits);
425 }
426
427 #[inline]
428 pub fn set_color1(&mut self, col: usize, r: bool, g: bool, b: bool) {
429 let bits = (u8::from(b) << 2) | (u8::from(g) << 1) | u8::from(r);
430 let col = map_index(col);
431 self.data[col].set_color1_bits(bits);
432 }
433}
434
435impl<const COLS: usize> Default for Row<COLS> {
436 fn default() -> Self {
437 Self::new()
438 }
439}
440
441#[derive(Copy, Clone, Debug)]
442#[repr(C)]
443struct Frame<const ROWS: usize, const COLS: usize, const NROWS: usize> {
444 rows: [Row<COLS>; NROWS],
445}
446
447impl<const ROWS: usize, const COLS: usize, const NROWS: usize> Frame<ROWS, COLS, NROWS> {
448 pub const fn new() -> Self {
449 Self {
450 rows: [Row::new(); NROWS],
451 }
452 }
453
454 #[inline]
455 pub const fn format(&mut self) {
456 let mut slot = 0;
457 while slot < NROWS {
458 let (_, addr) = slot_addresses::<NROWS>(slot);
459 self.rows[slot].format(addr);
460 slot += 1;
461 }
462 }
463
464 #[inline]
466 pub fn clear_colors(&mut self) {
467 for row in &mut self.rows {
468 row.clear_colors();
469 }
470 }
471
472 #[inline]
473 pub fn set_pixel(&mut self, y: usize, x: usize, red: bool, green: bool, blue: bool) {
474 let row_idx = map_row_index::<NROWS>(if y < NROWS { y } else { y - NROWS });
475 let row = &mut self.rows[row_idx];
476 if y < NROWS {
477 row.set_color0(x, red, green, blue);
478 } else {
479 row.set_color1(x, red, green, blue);
480 }
481 }
482}
483
484impl<const ROWS: usize, const COLS: usize, const NROWS: usize> Default
485 for Frame<ROWS, COLS, NROWS>
486{
487 fn default() -> Self {
488 Self::new()
489 }
490}
491
492#[derive(Copy, Clone)]
521#[repr(C)]
522#[repr(align(4))]
523pub struct DmaFrameBuffer<
524 const ROWS: usize,
525 const COLS: usize,
526 const NROWS: usize,
527 const BITS: u8,
528 const FRAME_COUNT: usize,
529> {
530 frames: [Frame<ROWS, COLS, NROWS>; FRAME_COUNT],
531}
532
533impl<
534 const ROWS: usize,
535 const COLS: usize,
536 const NROWS: usize,
537 const BITS: u8,
538 const FRAME_COUNT: usize,
539 > Default for DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
540{
541 fn default() -> Self {
542 Self::new()
543 }
544}
545
546impl<
547 const ROWS: usize,
548 const COLS: usize,
549 const NROWS: usize,
550 const BITS: u8,
551 const FRAME_COUNT: usize,
552 > DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
553{
554 #[must_use]
591 pub const fn new() -> Self {
592 #[cfg(feature = "esp32-ordering")]
593 assert!(
594 COLS % 4 == 0,
595 "esp32-ordering feature requires COLS to be divisible by 4"
596 );
597 let mut fb = Self {
598 frames: [Frame::new(); FRAME_COUNT],
599 };
600 fb.format();
601 fb
602 }
603
604 #[must_use]
607 #[deprecated(since = "0.11.0", note = "use BCM_SEQUENCE instead")]
608 pub const fn bcm_chunk_count() -> usize {
609 1
610 }
611
612 #[must_use]
615 #[deprecated(since = "0.11.0", note = "use BCM_SEQUENCE instead")]
616 pub const fn bcm_chunk_bytes() -> usize {
617 core::mem::size_of::<[Frame<ROWS, COLS, NROWS>; FRAME_COUNT]>()
618 }
619
620 pub const fn format(&mut self) {
637 let mut i = 0;
638 while i < FRAME_COUNT {
639 self.frames[i].format();
640 i += 1;
641 }
642 }
643
644 #[inline]
661 pub fn erase(&mut self) {
662 for frame in &mut self.frames {
663 frame.clear_colors();
664 }
665 }
666
667 pub fn set_pixel(&mut self, p: Point, color: Color) {
683 if p.x < 0 || p.y < 0 {
684 return;
685 }
686 self.set_pixel_internal(p.x as usize, p.y as usize, color);
687 }
688
689 #[inline]
690 fn frames_on(v: u8) -> usize {
691 (v as usize) >> (8 - BITS)
693 }
694
695 #[inline]
696 fn set_pixel_internal(&mut self, x: usize, y: usize, color: Rgb888) {
697 if x >= COLS || y >= ROWS {
698 return;
699 }
700
701 #[cfg(feature = "skip-black-pixels")]
704 if color == Rgb888::BLACK {
705 return;
706 }
707
708 let red_frames = Self::frames_on(color.r());
710 let green_frames = Self::frames_on(color.g());
711 let blue_frames = Self::frames_on(color.b());
712
713 for (frame_idx, frame) in self.frames.iter_mut().enumerate() {
715 frame.set_pixel(
716 y,
717 x,
718 frame_idx < red_frames,
719 frame_idx < green_frames,
720 frame_idx < blue_frames,
721 );
722 }
723 }
724}
725
726impl<
727 const ROWS: usize,
728 const COLS: usize,
729 const NROWS: usize,
730 const BITS: u8,
731 const FRAME_COUNT: usize,
732 > FrameBufferOperations for DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
733{
734 #[inline]
735 fn erase(&mut self) {
736 DmaFrameBuffer::<ROWS, COLS, NROWS, BITS, FRAME_COUNT>::erase(self);
737 }
738
739 #[inline]
740 fn set_pixel(&mut self, p: Point, color: Color) {
741 DmaFrameBuffer::<ROWS, COLS, NROWS, BITS, FRAME_COUNT>::set_pixel(self, p, color);
742 }
743}
744
745impl<
746 const ROWS: usize,
747 const COLS: usize,
748 const NROWS: usize,
749 const BITS: u8,
750 const FRAME_COUNT: usize,
751 > embedded_graphics::prelude::OriginDimensions
752 for DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
753{
754 fn size(&self) -> embedded_graphics::prelude::Size {
755 embedded_graphics::prelude::Size::new(COLS as u32, ROWS as u32)
756 }
757}
758
759impl<
760 const ROWS: usize,
761 const COLS: usize,
762 const NROWS: usize,
763 const BITS: u8,
764 const FRAME_COUNT: usize,
765 > embedded_graphics::draw_target::DrawTarget
766 for DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
767{
768 type Color = Color;
769
770 type Error = Infallible;
771
772 fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
773 where
774 I: IntoIterator<Item = embedded_graphics::Pixel<Self::Color>>,
775 {
776 for pixel in pixels {
777 if pixel.0.x < 0 || pixel.0.y < 0 {
778 continue;
779 }
780 self.set_pixel_internal(pixel.0.x as usize, pixel.0.y as usize, pixel.1);
781 }
782 Ok(())
783 }
784}
785
786impl<
787 const ROWS: usize,
788 const COLS: usize,
789 const NROWS: usize,
790 const BITS: u8,
791 const FRAME_COUNT: usize,
792 > core::fmt::Debug for DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
793{
794 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
795 let brightness_step = 1 << (8 - BITS);
796 f.debug_struct("DmaFrameBuffer")
797 .field("size", &core::mem::size_of_val(&self.frames))
798 .field("frame_count", &self.frames.len())
799 .field("frame_size", &core::mem::size_of_val(&self.frames[0]))
800 .field("brightness_step", &&brightness_step)
801 .finish()
802 }
803}
804
805#[cfg(feature = "defmt")]
806impl<
807 const ROWS: usize,
808 const COLS: usize,
809 const NROWS: usize,
810 const BITS: u8,
811 const FRAME_COUNT: usize,
812 > defmt::Format for DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
813{
814 fn format(&self, f: defmt::Formatter) {
815 let brightness_step = 1 << (8 - BITS);
816 defmt::write!(
817 f,
818 "DmaFrameBuffer<{}, {}, {}, {}, {}>",
819 ROWS,
820 COLS,
821 NROWS,
822 BITS,
823 FRAME_COUNT
824 );
825 defmt::write!(f, " size: {}", core::mem::size_of_val(&self.frames));
826 defmt::write!(
827 f,
828 " frame_size: {}",
829 core::mem::size_of_val(&self.frames[0])
830 );
831 defmt::write!(f, " brightness_step: {}", brightness_step);
832 }
833}
834
835impl<
836 const ROWS: usize,
837 const COLS: usize,
838 const NROWS: usize,
839 const BITS: u8,
840 const FRAME_COUNT: usize,
841 > embedded_graphics::prelude::OriginDimensions
842 for &mut DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
843{
844 fn size(&self) -> embedded_graphics::prelude::Size {
845 embedded_graphics::prelude::Size::new(COLS as u32, ROWS as u32)
846 }
847}
848
849impl<
850 const ROWS: usize,
851 const COLS: usize,
852 const NROWS: usize,
853 const BITS: u8,
854 const FRAME_COUNT: usize,
855 > FrameBuffer for DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
856{
857 type Word = u8;
858
859 const BCM_SEQUENCE: [BcmLenReps; BCM_SEQUENCE_CAPACITY] = {
860 let mut seq = [BcmLenReps::ZERO; BCM_SEQUENCE_CAPACITY];
861 seq[0] = BcmLenReps {
862 len: core::mem::size_of::<[Frame<ROWS, COLS, NROWS>; FRAME_COUNT]>(),
863 reps: 1,
864 };
865 seq
866 };
867
868 const BCM_SEQUENCE_LEN: usize = 1;
869
870 const BCM_SEQUENCE_COUNT: usize = 1;
871
872 fn bcm_segment_ptr(&self, index: usize) -> *const u8 {
873 assert!(index == 0, "threshold DmaFrameBuffer has only 1 segment");
874 (&raw const self.frames).cast::<u8>()
875 }
876}
877
878impl<
879 const ROWS: usize,
880 const COLS: usize,
881 const NROWS: usize,
882 const BITS: u8,
883 const FRAME_COUNT: usize,
884 > MutableFrameBuffer for DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
885{
886}
887
888#[cfg(test)]
889mod tests {
890 extern crate std;
891
892 use std::format;
893 use std::vec;
894
895 use super::*;
896 use embedded_graphics::pixelcolor::RgbColor;
897 use embedded_graphics::prelude::*;
898 use embedded_graphics::primitives::{Circle, PrimitiveStyle, Rectangle};
899
900 const TEST_ROWS: usize = 32;
901 const TEST_COLS: usize = if LEAD_BLANK_DELAY + TRAIL_BLANK_DELAY + 2 > 64 {
902 128
903 } else {
904 64
905 };
906 const TEST_NROWS: usize = TEST_ROWS / 2;
907 const TEST_BITS: u8 = 3;
908 const TEST_FRAME_COUNT: usize = (1 << TEST_BITS) - 1; type TestFrameBuffer =
911 DmaFrameBuffer<TEST_ROWS, TEST_COLS, TEST_NROWS, TEST_BITS, TEST_FRAME_COUNT>;
912
913 fn get_mapped_row(row_idx: usize) -> usize {
916 map_row_index::<TEST_NROWS>(row_idx)
917 }
918
919 #[test]
920 fn test_address_construction() {
921 let addr = Address::new();
922 assert_eq!(addr.0, 0);
923 assert_eq!(addr.latch(), false);
924 assert_eq!(addr.addr(), 0);
925 }
926
927 #[test]
928 fn test_address_setters() {
929 let mut addr = Address::new();
930
931 addr.set_latch(true);
932 assert_eq!(addr.latch(), true);
933 assert_eq!(addr.0 & 0b01000000, 0b01000000);
934
935 addr.set_addr(0b11111);
936 assert_eq!(addr.addr(), 0b11111);
937 assert_eq!(addr.0 & 0b00011111, 0b00011111);
938 }
939
940 #[test]
941 fn test_address_bit_isolation() {
942 let mut addr = Address::new();
943
944 addr.set_addr(0b11111);
946 addr.set_latch(true);
947 assert_eq!(addr.addr(), 0b11111);
948 assert_eq!(addr.latch(), true);
949 }
950
951 #[test]
952 fn test_entry_construction() {
953 let entry = Entry::new();
954 assert_eq!(entry.0, 0);
955 assert_eq!(entry.output_enable(), false);
956 assert_eq!(entry.latch(), false);
957 assert_eq!(entry.red1(), false);
958 assert_eq!(entry.grn1(), false);
959 assert_eq!(entry.blu1(), false);
960 assert_eq!(entry.red2(), false);
961 assert_eq!(entry.grn2(), false);
962 assert_eq!(entry.blu2(), false);
963 }
964
965 #[test]
966 fn test_entry_setters() {
967 let mut entry = Entry::new();
968
969 entry.set_output_enable(true);
970 assert_eq!(entry.output_enable(), true);
971 assert_eq!(entry.0 & 0b10000000, 0b10000000);
972
973 entry.set_latch(true);
974 assert_eq!(entry.latch(), true);
975 assert_eq!(entry.0 & 0b01000000, 0b01000000);
976
977 entry.set_red1(true);
979 entry.set_grn1(true);
980 entry.set_blu1(true);
981 assert_eq!(entry.red1(), true);
982 assert_eq!(entry.grn1(), true);
983 assert_eq!(entry.blu1(), true);
984 assert_eq!(entry.0 & 0b00000111, 0b00000111);
985
986 entry.set_red2(true);
988 entry.set_grn2(true);
989 entry.set_blu2(true);
990 assert_eq!(entry.red2(), true);
991 assert_eq!(entry.grn2(), true);
992 assert_eq!(entry.blu2(), true);
993 assert_eq!(entry.0 & 0b00111000, 0b00111000);
994 }
995
996 #[test]
997 fn test_entry_set_color0() {
998 let mut entry = Entry::new();
999
1000 let bits = (u8::from(true) << 2) | (u8::from(false) << 1) | u8::from(true); entry.set_color0_bits(bits);
1002 assert_eq!(entry.red1(), true);
1003 assert_eq!(entry.grn1(), false);
1004 assert_eq!(entry.blu1(), true);
1005 assert_eq!(entry.0 & 0b00000111, 0b00000101); }
1007
1008 #[test]
1009 fn test_entry_set_color1() {
1010 let mut entry = Entry::new();
1011
1012 let bits = (u8::from(true) << 2) | (u8::from(true) << 1) | u8::from(false); entry.set_color1_bits(bits);
1014 assert_eq!(entry.red2(), false);
1015 assert_eq!(entry.grn2(), true);
1016 assert_eq!(entry.blu2(), true);
1017 assert_eq!(entry.0 & 0b00111000, 0b00110000); }
1019
1020 #[test]
1021 fn test_row_construction() {
1022 let row: Row<TEST_COLS> = Row::new();
1023 assert_eq!(row.data.len(), TEST_COLS);
1024 assert_eq!(row.address.len(), 4);
1025
1026 for entry in &row.data {
1028 assert_eq!(entry.0, 0);
1029 }
1030 for addr in &row.address {
1031 assert_eq!(addr.0, 0);
1032 }
1033 }
1034
1035 #[test]
1036 fn test_row_format() {
1037 let mut row: Row<TEST_COLS> = Row::new();
1038 let test_addr = 5;
1039
1040 row.format(test_addr);
1041
1042 for addr in &row.address {
1044 assert_eq!(addr.addr(), test_addr);
1045 }
1049 let latch_false_count = row.address.iter().filter(|addr| !addr.latch()).count();
1053 assert_eq!(latch_false_count, 1);
1054
1055 for entry in &row.data {
1057 assert_eq!(entry.latch(), false);
1058 }
1059 let oe_active = !cfg!(feature = "invert-oe");
1060 let active_count = TEST_COLS.saturating_sub(LEAD_BLANK_DELAY + TRAIL_BLANK_DELAY + 1);
1061 let blank_count = TEST_COLS - active_count;
1062 let oe_blank_count = row
1063 .data
1064 .iter()
1065 .filter(|entry| entry.output_enable() != oe_active)
1066 .count();
1067 assert_eq!(oe_blank_count, blank_count);
1068 }
1069
1070 #[test]
1071 fn test_row_set_color0() {
1072 let mut row: Row<TEST_COLS> = Row::new();
1073
1074 row.set_color0(0, true, false, true);
1075
1076 let mapped_col_0 = map_index(0);
1077 assert_eq!(row.data[mapped_col_0].red1(), true);
1078 assert_eq!(row.data[mapped_col_0].grn1(), false);
1079 assert_eq!(row.data[mapped_col_0].blu1(), true);
1080
1081 row.set_color0(1, false, true, false);
1083
1084 let mapped_col_1 = map_index(1);
1085 assert_eq!(row.data[mapped_col_1].red1(), false);
1086 assert_eq!(row.data[mapped_col_1].grn1(), true);
1087 assert_eq!(row.data[mapped_col_1].blu1(), false);
1088 }
1089
1090 #[test]
1091 fn test_row_set_color1() {
1092 let mut row: Row<TEST_COLS> = Row::new();
1093
1094 row.set_color1(0, true, true, false);
1095
1096 let mapped_col_0 = map_index(0);
1097 assert_eq!(row.data[mapped_col_0].red2(), true);
1098 assert_eq!(row.data[mapped_col_0].grn2(), true);
1099 assert_eq!(row.data[mapped_col_0].blu2(), false);
1100 }
1101
1102 #[test]
1103 fn test_frame_construction() {
1104 let frame: Frame<TEST_ROWS, TEST_COLS, TEST_NROWS> = Frame::new();
1105 assert_eq!(frame.rows.len(), TEST_NROWS);
1106 }
1107
1108 #[test]
1109 fn test_frame_format() {
1110 let mut frame: Frame<TEST_ROWS, TEST_COLS, TEST_NROWS> = Frame::new();
1111
1112 frame.format();
1113
1114 for (slot, row) in frame.rows.iter().enumerate() {
1115 let (_, addr) = slot_addresses::<TEST_NROWS>(slot);
1117 for address in &row.address {
1118 assert_eq!(address.addr(), addr);
1119 }
1120 }
1121 }
1122
1123 #[test]
1124 fn test_frame_set_pixel() {
1125 let mut frame: Frame<TEST_ROWS, TEST_COLS, TEST_NROWS> = Frame::new();
1126
1127 frame.set_pixel(5, 10, true, false, true);
1129
1130 let mapped_col_10 = map_index(10);
1131 assert_eq!(
1132 frame.rows[get_mapped_row(5)].data[mapped_col_10].red1(),
1133 true
1134 );
1135 assert_eq!(
1136 frame.rows[get_mapped_row(5)].data[mapped_col_10].grn1(),
1137 false
1138 );
1139 assert_eq!(
1140 frame.rows[get_mapped_row(5)].data[mapped_col_10].blu1(),
1141 true
1142 );
1143
1144 frame.set_pixel(TEST_NROWS + 5, 15, false, true, false);
1146
1147 let mapped_col_15 = map_index(15);
1148 assert_eq!(
1149 frame.rows[get_mapped_row(5)].data[mapped_col_15].red2(),
1150 false
1151 );
1152 assert_eq!(
1153 frame.rows[get_mapped_row(5)].data[mapped_col_15].grn2(),
1154 true
1155 );
1156 assert_eq!(
1157 frame.rows[get_mapped_row(5)].data[mapped_col_15].blu2(),
1158 false
1159 );
1160 }
1161
1162 #[test]
1163 #[cfg(feature = "reverse-row-order")]
1164 fn test_reverse_row_order() {
1165 let mut fb = TestFrameBuffer::new();
1166
1167 let first = &fb.frames[0].rows[0];
1171 for address in &first.address {
1172 assert_eq!(address.addr(), (TEST_NROWS - 1) as u8);
1173 }
1174 let last = &fb.frames[0].rows[TEST_NROWS - 1];
1175 for address in &last.address {
1176 assert_eq!(address.addr(), 0);
1177 }
1178
1179 fb.set_pixel(Point::new(3, 0), Color::RED);
1181 let col3 = map_index(3);
1182 assert!(fb.frames[0].rows[TEST_NROWS - 1].data[col3].red1());
1183 assert!(!fb.frames[0].rows[0].data[col3].red1());
1184 }
1185
1186 #[test]
1187 fn test_row_default() {
1188 let row1: Row<TEST_COLS> = Row::new();
1189 let row2: Row<TEST_COLS> = Row::default();
1190
1191 assert_eq!(row1, row2);
1193 assert_eq!(row1.data.len(), row2.data.len());
1194 assert_eq!(row1.address.len(), row2.address.len());
1195
1196 for (entry1, entry2) in row1.data.iter().zip(row2.data.iter()) {
1198 assert_eq!(entry1.0, entry2.0);
1199 assert_eq!(entry1.0, 0);
1200 }
1201 for (addr1, addr2) in row1.address.iter().zip(row2.address.iter()) {
1202 assert_eq!(addr1.0, addr2.0);
1203 assert_eq!(addr1.0, 0);
1204 }
1205 }
1206
1207 #[test]
1208 fn test_frame_default() {
1209 let frame1: Frame<TEST_ROWS, TEST_COLS, TEST_NROWS> = Frame::new();
1210 let frame2: Frame<TEST_ROWS, TEST_COLS, TEST_NROWS> = Frame::default();
1211
1212 assert_eq!(frame1.rows.len(), frame2.rows.len());
1214
1215 for (row1, row2) in frame1.rows.iter().zip(frame2.rows.iter()) {
1217 assert_eq!(row1, row2);
1218
1219 for (entry1, entry2) in row1.data.iter().zip(row2.data.iter()) {
1221 assert_eq!(entry1.0, entry2.0);
1222 assert_eq!(entry1.0, 0);
1223 }
1224 for (addr1, addr2) in row1.address.iter().zip(row2.address.iter()) {
1225 assert_eq!(addr1.0, addr2.0);
1226 assert_eq!(addr1.0, 0);
1227 }
1228 }
1229 }
1230
1231 #[test]
1232 fn test_dma_framebuffer_construction() {
1233 let fb = TestFrameBuffer::new();
1234 assert_eq!(fb.frames.len(), TEST_FRAME_COUNT);
1235 }
1236
1237 #[test]
1238 #[allow(deprecated)]
1239 fn test_bcm_chunk_info() {
1240 let expected_size =
1241 core::mem::size_of::<[Frame<TEST_ROWS, TEST_COLS, TEST_NROWS>; TEST_FRAME_COUNT]>();
1242 assert_eq!(TestFrameBuffer::bcm_chunk_bytes(), expected_size);
1243 assert_eq!(TestFrameBuffer::bcm_chunk_count(), 1);
1244 }
1245
1246 #[test]
1247 fn test_dma_framebuffer_format() {
1248 let mut fb = TestFrameBuffer {
1249 frames: [Frame::new(); TEST_FRAME_COUNT],
1250 };
1251 fb.format();
1252
1253 for frame in &fb.frames {
1255 for (slot, row) in frame.rows.iter().enumerate() {
1256 let (_, addr) = slot_addresses::<TEST_NROWS>(slot);
1257 for address in &row.address {
1258 assert_eq!(address.addr(), addr);
1259 }
1260 }
1261 }
1262 }
1263
1264 #[test]
1265 fn test_dma_framebuffer_set_pixel_bounds() {
1266 let mut fb = TestFrameBuffer::new();
1267
1268 fb.set_pixel(Point::new(-1, 5), Color::RED);
1270 fb.set_pixel(Point::new(5, -1), Color::RED);
1271
1272 fb.set_pixel(Point::new(TEST_COLS as i32, 5), Color::RED);
1274 fb.set_pixel(Point::new(5, TEST_ROWS as i32), Color::RED);
1275 }
1276
1277 #[test]
1278 fn test_dma_framebuffer_set_pixel_internal() {
1279 let mut fb = TestFrameBuffer::new();
1280
1281 let red_color = Rgb888::new(255, 0, 0);
1282 fb.set_pixel_internal(10, 5, red_color);
1283
1284 for frame in &fb.frames {
1288 let mapped_col_10 = map_index(10);
1290 assert_eq!(
1291 frame.rows[get_mapped_row(5)].data[mapped_col_10].red1(),
1292 true
1293 );
1294 assert_eq!(
1295 frame.rows[get_mapped_row(5)].data[mapped_col_10].grn1(),
1296 false
1297 );
1298 assert_eq!(
1299 frame.rows[get_mapped_row(5)].data[mapped_col_10].blu1(),
1300 false
1301 );
1302 }
1303 }
1304
1305 #[test]
1306 fn test_dma_framebuffer_brightness_modulation() {
1307 let mut fb = TestFrameBuffer::new();
1308
1309 let brightness_step = 1 << (8 - TEST_BITS); let test_brightness = brightness_step * 3; let color = Rgb888::new(test_brightness, 0, 0);
1313
1314 fb.set_pixel_internal(0, 0, color);
1315
1316 for (frame_idx, frame) in fb.frames.iter().enumerate() {
1319 let frame_threshold = (frame_idx as u8 + 1) * brightness_step;
1320 let should_be_active = test_brightness >= frame_threshold;
1321
1322 let mapped_col_0 = map_index(0);
1323 assert_eq!(
1324 frame.rows[get_mapped_row(0)].data[mapped_col_0].red1(),
1325 should_be_active
1326 );
1327 }
1328 }
1329
1330 #[test]
1331 fn test_origin_dimensions() {
1332 let fb = TestFrameBuffer::new();
1333 let size = fb.size();
1334 assert_eq!(size.width, TEST_COLS as u32);
1335 assert_eq!(size.height, TEST_ROWS as u32);
1336
1337 let mut fb = TestFrameBuffer::new();
1339 let fb_ref = &mut fb;
1340 let size = fb_ref.size();
1341 assert_eq!(size.width, TEST_COLS as u32);
1342 assert_eq!(size.height, TEST_ROWS as u32);
1343 }
1344
1345 #[test]
1346 fn test_draw_target() {
1347 let mut fb = TestFrameBuffer::new();
1348
1349 let pixels = vec![
1350 embedded_graphics::Pixel(Point::new(0, 0), Color::RED),
1351 embedded_graphics::Pixel(Point::new(1, 1), Color::GREEN),
1352 embedded_graphics::Pixel(Point::new(2, 2), Color::BLUE),
1353 ];
1354
1355 let result = fb.draw_iter(pixels);
1356 assert!(result.is_ok());
1357 }
1358
1359 #[test]
1360 fn test_draw_iter_pixel_verification() {
1361 let mut fb = TestFrameBuffer::new();
1362
1363 let pixels = vec![
1365 embedded_graphics::Pixel(Point::new(5, 2), Color::RED), embedded_graphics::Pixel(Point::new(10, 5), Color::GREEN), embedded_graphics::Pixel(Point::new(15, 8), Color::BLUE), embedded_graphics::Pixel(Point::new(20, 10), Color::WHITE), embedded_graphics::Pixel(Point::new(25, (TEST_NROWS + 3) as i32), Color::RED), embedded_graphics::Pixel(Point::new(30, (TEST_NROWS + 7) as i32), Color::GREEN), embedded_graphics::Pixel(Point::new(35, (TEST_NROWS + 12) as i32), Color::BLUE), embedded_graphics::Pixel(Point::new(40, 1), Color::BLACK), embedded_graphics::Pixel(Point::new(45, 3), Rgb888::new(16, 16, 16)), ];
1379
1380 let result = fb.draw_iter(pixels);
1381 assert!(result.is_ok());
1382
1383 let first_frame = &fb.frames[0];
1385 let brightness_step = 1 << (8 - TEST_BITS); let first_frame_threshold = brightness_step; let col_idx = map_index(5);
1391 assert_eq!(
1392 first_frame.rows[get_mapped_row(2)].data[col_idx].red1(),
1393 Color::RED.r() >= first_frame_threshold
1394 );
1395 assert_eq!(
1396 first_frame.rows[get_mapped_row(2)].data[col_idx].grn1(),
1397 Color::RED.g() >= first_frame_threshold
1398 );
1399 assert_eq!(
1400 first_frame.rows[get_mapped_row(2)].data[col_idx].blu1(),
1401 Color::RED.b() >= first_frame_threshold
1402 );
1403
1404 let col_idx = map_index(10);
1406 assert_eq!(
1407 first_frame.rows[get_mapped_row(5)].data[col_idx].red1(),
1408 Color::GREEN.r() >= first_frame_threshold
1409 );
1410 assert_eq!(
1411 first_frame.rows[get_mapped_row(5)].data[col_idx].grn1(),
1412 Color::GREEN.g() >= first_frame_threshold
1413 );
1414 assert_eq!(
1415 first_frame.rows[get_mapped_row(5)].data[col_idx].blu1(),
1416 Color::GREEN.b() >= first_frame_threshold
1417 );
1418
1419 let col_idx = map_index(15);
1421 assert_eq!(
1422 first_frame.rows[get_mapped_row(8)].data[col_idx].red1(),
1423 Color::BLUE.r() >= first_frame_threshold
1424 );
1425 assert_eq!(
1426 first_frame.rows[get_mapped_row(8)].data[col_idx].grn1(),
1427 Color::BLUE.g() >= first_frame_threshold
1428 );
1429 assert_eq!(
1430 first_frame.rows[get_mapped_row(8)].data[col_idx].blu1(),
1431 Color::BLUE.b() >= first_frame_threshold
1432 );
1433
1434 let col_idx = map_index(20);
1436 assert_eq!(
1437 first_frame.rows[get_mapped_row(10)].data[col_idx].red1(),
1438 Color::WHITE.r() >= first_frame_threshold
1439 );
1440 assert_eq!(
1441 first_frame.rows[get_mapped_row(10)].data[col_idx].grn1(),
1442 Color::WHITE.g() >= first_frame_threshold
1443 );
1444 assert_eq!(
1445 first_frame.rows[get_mapped_row(10)].data[col_idx].blu1(),
1446 Color::WHITE.b() >= first_frame_threshold
1447 );
1448
1449 let col_idx = map_index(25);
1452 assert_eq!(
1453 first_frame.rows[get_mapped_row(3)].data[col_idx].red2(),
1454 Color::RED.r() >= first_frame_threshold
1455 );
1456 assert_eq!(
1457 first_frame.rows[get_mapped_row(3)].data[col_idx].grn2(),
1458 Color::RED.g() >= first_frame_threshold
1459 );
1460 assert_eq!(
1461 first_frame.rows[get_mapped_row(3)].data[col_idx].blu2(),
1462 Color::RED.b() >= first_frame_threshold
1463 );
1464
1465 let col_idx = map_index(30);
1467 assert_eq!(
1468 first_frame.rows[get_mapped_row(7)].data[col_idx].red2(),
1469 Color::GREEN.r() >= first_frame_threshold
1470 );
1471 assert_eq!(
1472 first_frame.rows[get_mapped_row(7)].data[col_idx].grn2(),
1473 Color::GREEN.g() >= first_frame_threshold
1474 );
1475 assert_eq!(
1476 first_frame.rows[get_mapped_row(7)].data[col_idx].blu2(),
1477 Color::GREEN.b() >= first_frame_threshold
1478 );
1479
1480 let col_idx = map_index(35);
1482 assert_eq!(
1483 first_frame.rows[get_mapped_row(12)].data[col_idx].red2(),
1484 Color::BLUE.r() >= first_frame_threshold
1485 );
1486 assert_eq!(
1487 first_frame.rows[get_mapped_row(12)].data[col_idx].grn2(),
1488 Color::BLUE.g() >= first_frame_threshold
1489 );
1490 assert_eq!(
1491 first_frame.rows[get_mapped_row(12)].data[col_idx].blu2(),
1492 Color::BLUE.b() >= first_frame_threshold
1493 );
1494
1495 let col_idx = map_index(40);
1497 assert_eq!(
1498 first_frame.rows[get_mapped_row(1)].data[col_idx].red1(),
1499 false
1500 );
1501 assert_eq!(
1502 first_frame.rows[get_mapped_row(1)].data[col_idx].grn1(),
1503 false
1504 );
1505 assert_eq!(
1506 first_frame.rows[get_mapped_row(1)].data[col_idx].blu1(),
1507 false
1508 );
1509
1510 let col_idx = map_index(45);
1512 assert_eq!(
1513 first_frame.rows[get_mapped_row(3)].data[col_idx].red1(),
1514 16 >= first_frame_threshold
1515 ); assert_eq!(
1517 first_frame.rows[get_mapped_row(3)].data[col_idx].grn1(),
1518 16 >= first_frame_threshold
1519 ); assert_eq!(
1521 first_frame.rows[get_mapped_row(3)].data[col_idx].blu1(),
1522 16 >= first_frame_threshold
1523 ); }
1525
1526 #[test]
1527 fn test_embedded_graphics_integration() {
1528 let mut fb = TestFrameBuffer::new();
1529
1530 let result = Rectangle::new(Point::new(5, 5), Size::new(10, 8))
1532 .into_styled(PrimitiveStyle::with_fill(Color::RED))
1533 .draw(&mut fb);
1534 assert!(result.is_ok());
1535
1536 let result = Circle::new(Point::new(30, 15), 8)
1538 .into_styled(PrimitiveStyle::with_fill(Color::BLUE))
1539 .draw(&mut fb);
1540 assert!(result.is_ok());
1541 }
1542
1543 #[test]
1544 fn test_debug_formatting() {
1545 let fb = TestFrameBuffer::new();
1546 let debug_string = format!("{:?}", fb);
1547 assert!(debug_string.contains("DmaFrameBuffer"));
1548 assert!(debug_string.contains("frame_count"));
1549 assert!(debug_string.contains("frame_size"));
1550 assert!(debug_string.contains("brightness_step"));
1551 }
1552
1553 #[test]
1554 fn test_default_implementation() {
1555 let fb1 = TestFrameBuffer::new();
1556 let fb2 = TestFrameBuffer::default();
1557
1558 assert_eq!(fb1.frames.len(), fb2.frames.len());
1560 }
1561
1562 #[cfg(feature = "esp32-ordering")]
1563 #[test]
1564 fn test_esp32_mapping() {
1565 assert_eq!(map_index(0), 2);
1567 assert_eq!(map_index(1), 3);
1568 assert_eq!(map_index(2), 0);
1569 assert_eq!(map_index(3), 1);
1570 assert_eq!(map_index(4), 6); assert_eq!(map_index(5), 7); }
1573
1574 #[test]
1575 fn test_memory_alignment() {
1576 let fb = TestFrameBuffer::new();
1577 let ptr = &fb as *const _ as usize;
1578
1579 assert_eq!(ptr % 4, 0);
1581 }
1582
1583 #[test]
1584 fn test_color_values() {
1585 let mut fb = TestFrameBuffer::new();
1586
1587 let colors = [
1589 (Color::RED, (255, 0, 0)),
1590 (Color::GREEN, (0, 255, 0)),
1591 (Color::BLUE, (0, 0, 255)),
1592 (Color::WHITE, (255, 255, 255)),
1593 (Color::BLACK, (0, 0, 0)),
1594 ];
1595
1596 for (i, (color, (r, g, b))) in colors.iter().enumerate() {
1597 fb.set_pixel(Point::new(i as i32, 0), *color);
1598 assert_eq!(color.r(), *r);
1599 assert_eq!(color.g(), *g);
1600 assert_eq!(color.b(), *b);
1601 }
1602 }
1603
1604 #[test]
1605 fn test_bits_assertion() {
1606 assert!(TEST_BITS <= 8);
1609 }
1610
1611 #[test]
1612 #[cfg(feature = "skip-black-pixels")]
1613 fn test_skip_black_pixels_enabled() {
1614 let mut fb = TestFrameBuffer::new();
1615
1616 fb.set_pixel_internal(10, 5, Color::RED);
1618
1619 let mapped_col_10 = map_index(10);
1621 assert_eq!(
1622 fb.frames[0].rows[get_mapped_row(5)].data[mapped_col_10].red1(),
1623 true
1624 );
1625 assert_eq!(
1626 fb.frames[0].rows[get_mapped_row(5)].data[mapped_col_10].grn1(),
1627 false
1628 );
1629 assert_eq!(
1630 fb.frames[0].rows[get_mapped_row(5)].data[mapped_col_10].blu1(),
1631 false
1632 );
1633
1634 fb.set_pixel_internal(10, 5, Color::BLACK);
1636
1637 assert_eq!(
1639 fb.frames[0].rows[get_mapped_row(5)].data[mapped_col_10].red1(),
1640 true
1641 );
1642 assert_eq!(
1643 fb.frames[0].rows[get_mapped_row(5)].data[mapped_col_10].grn1(),
1644 false
1645 );
1646 assert_eq!(
1647 fb.frames[0].rows[get_mapped_row(5)].data[mapped_col_10].blu1(),
1648 false
1649 );
1650 }
1651
1652 #[test]
1653 #[cfg(not(feature = "skip-black-pixels"))]
1654 fn test_skip_black_pixels_disabled() {
1655 let mut fb = TestFrameBuffer::new();
1656
1657 fb.set_pixel_internal(10, 5, Color::RED);
1659
1660 let mapped_col_10 = map_index(10);
1662 assert_eq!(
1663 fb.frames[0].rows[get_mapped_row(5)].data[mapped_col_10].red1(),
1664 true
1665 );
1666 assert_eq!(
1667 fb.frames[0].rows[get_mapped_row(5)].data[mapped_col_10].grn1(),
1668 false
1669 );
1670 assert_eq!(
1671 fb.frames[0].rows[get_mapped_row(5)].data[mapped_col_10].blu1(),
1672 false
1673 );
1674
1675 fb.set_pixel_internal(10, 5, Color::BLACK);
1677
1678 assert_eq!(
1680 fb.frames[0].rows[get_mapped_row(5)].data[mapped_col_10].red1(),
1681 false
1682 );
1683 assert_eq!(
1684 fb.frames[0].rows[get_mapped_row(5)].data[mapped_col_10].grn1(),
1685 false
1686 );
1687 assert_eq!(
1688 fb.frames[0].rows[get_mapped_row(5)].data[mapped_col_10].blu1(),
1689 false
1690 );
1691 }
1692
1693 #[test]
1694 fn test_bcm_frame_overwrite() {
1695 let mut fb = TestFrameBuffer::new();
1696
1697 fb.set_pixel_internal(10, 5, Color::WHITE);
1699
1700 let mapped_col_10 = map_index(10);
1701
1702 for frame in fb.frames.iter() {
1704 assert_eq!(
1706 frame.rows[get_mapped_row(5)].data[mapped_col_10].red1(),
1707 true
1708 );
1709 assert_eq!(
1710 frame.rows[get_mapped_row(5)].data[mapped_col_10].grn1(),
1711 true
1712 );
1713 assert_eq!(
1714 frame.rows[get_mapped_row(5)].data[mapped_col_10].blu1(),
1715 true
1716 );
1717 }
1718
1719 let half_white = embedded_graphics::pixelcolor::Rgb888::new(128, 128, 128);
1721 fb.set_pixel_internal(10, 5, half_white);
1722
1723 let brightness_step = 1 << (8 - TEST_BITS); for (frame_idx, frame) in fb.frames.iter().enumerate() {
1729 let frame_threshold = (frame_idx as u8 + 1) * brightness_step;
1730 let should_be_active = 128 >= frame_threshold;
1731
1732 assert_eq!(
1733 frame.rows[get_mapped_row(5)].data[mapped_col_10].red1(),
1734 should_be_active
1735 );
1736 assert_eq!(
1737 frame.rows[get_mapped_row(5)].data[mapped_col_10].grn1(),
1738 should_be_active
1739 );
1740 assert_eq!(
1741 frame.rows[get_mapped_row(5)].data[mapped_col_10].blu1(),
1742 should_be_active
1743 );
1744 }
1745
1746 for frame_idx in 0..4 {
1749 assert_eq!(
1750 fb.frames[frame_idx].rows[get_mapped_row(5)].data[mapped_col_10].red1(),
1751 true
1752 );
1753 }
1754 for frame_idx in 4..TEST_FRAME_COUNT {
1756 assert_eq!(
1757 fb.frames[frame_idx].rows[get_mapped_row(5)].data[mapped_col_10].red1(),
1758 false
1759 );
1760 }
1761 }
1762
1763 #[test]
1764 fn test_new_auto_formats() {
1765 let fb = TestFrameBuffer::new();
1766
1767 for frame in &fb.frames {
1769 for (slot, row) in frame.rows.iter().enumerate() {
1770 let (_, addr) = slot_addresses::<TEST_NROWS>(slot);
1771 for address in &row.address {
1772 assert_eq!(address.addr(), addr);
1773 }
1774 }
1775 }
1776 }
1777
1778 #[test]
1779 fn test_erase() {
1780 let mut fb = TestFrameBuffer::new();
1781
1782 fb.set_pixel_internal(10, 5, Color::RED);
1784 fb.set_pixel_internal(20, 10, Color::GREEN);
1785
1786 let mapped_col_10 = map_index(10);
1787 let mapped_col_20 = map_index(20);
1788
1789 assert_eq!(
1791 fb.frames[0].rows[get_mapped_row(5)].data[mapped_col_10].red1(),
1792 true
1793 );
1794 assert_eq!(
1795 fb.frames[0].rows[get_mapped_row(10)].data[mapped_col_20].grn1(),
1796 true
1797 );
1798
1799 fb.erase();
1801
1802 assert_eq!(
1804 fb.frames[0].rows[get_mapped_row(5)].data[mapped_col_10].red1(),
1805 false
1806 );
1807 assert_eq!(
1808 fb.frames[0].rows[get_mapped_row(5)].data[mapped_col_10].grn1(),
1809 false
1810 );
1811 assert_eq!(
1812 fb.frames[0].rows[get_mapped_row(5)].data[mapped_col_10].blu1(),
1813 false
1814 );
1815 assert_eq!(
1816 fb.frames[0].rows[get_mapped_row(10)].data[mapped_col_20].red1(),
1817 false
1818 );
1819 assert_eq!(
1820 fb.frames[0].rows[get_mapped_row(10)].data[mapped_col_20].grn1(),
1821 false
1822 );
1823 assert_eq!(
1824 fb.frames[0].rows[get_mapped_row(10)].data[mapped_col_20].blu1(),
1825 false
1826 );
1827
1828 let oe_active = !cfg!(feature = "invert-oe");
1830 let active_count = TEST_COLS.saturating_sub(LEAD_BLANK_DELAY + TRAIL_BLANK_DELAY + 1);
1831 let blank_count = TEST_COLS - active_count;
1832 for frame in &fb.frames {
1833 for (slot, row) in frame.rows.iter().enumerate() {
1834 let (_, addr) = slot_addresses::<TEST_NROWS>(slot);
1836 for address in &row.address {
1837 assert_eq!(address.addr(), addr);
1838 }
1839 let oe_blank_count = row
1840 .data
1841 .iter()
1842 .filter(|entry| entry.output_enable() != oe_active)
1843 .count();
1844 assert_eq!(oe_blank_count, blank_count);
1845 }
1846 }
1847 }
1848
1849 #[test]
1850 fn test_row_clear_colors() {
1851 let mut row: Row<TEST_COLS> = Row::new();
1852 row.format(5);
1853
1854 row.set_color0(0, true, false, true);
1856 row.set_color1(1, false, true, false);
1857
1858 let mapped_col_0 = map_index(0);
1859 let mapped_col_1 = map_index(1);
1860
1861 assert_eq!(row.data[mapped_col_0].red1(), true);
1863 assert_eq!(row.data[mapped_col_0].blu1(), true);
1864 assert_eq!(row.data[mapped_col_1].grn2(), true);
1865
1866 let original_oe_0 = row.data[mapped_col_0].output_enable();
1868 let original_latch_0 = row.data[mapped_col_0].latch();
1869 let original_oe_1 = row.data[mapped_col_1].output_enable();
1870 let original_latch_1 = row.data[mapped_col_1].latch();
1871
1872 row.clear_colors();
1874
1875 assert_eq!(row.data[mapped_col_0].red1(), false);
1877 assert_eq!(row.data[mapped_col_0].grn1(), false);
1878 assert_eq!(row.data[mapped_col_0].blu1(), false);
1879 assert_eq!(row.data[mapped_col_1].red2(), false);
1880 assert_eq!(row.data[mapped_col_1].grn2(), false);
1881 assert_eq!(row.data[mapped_col_1].blu2(), false);
1882
1883 assert_eq!(row.data[mapped_col_0].output_enable(), original_oe_0);
1885 assert_eq!(row.data[mapped_col_0].latch(), original_latch_0);
1886 assert_eq!(row.data[mapped_col_1].output_enable(), original_oe_1);
1887 assert_eq!(row.data[mapped_col_1].latch(), original_latch_1);
1888 }
1889
1890 #[test]
1891 fn test_make_addr_table_function() {
1892 let table = make_addr_table();
1894
1895 assert_eq!(table.len(), 32); let addr_0 = &table[0];
1900 assert_eq!(addr_0.len(), 4); let latch_false_count = addr_0.iter().filter(|addr| !addr.latch()).count();
1904 assert_eq!(latch_false_count, 1);
1905
1906 for addr in addr_0 {
1908 assert_eq!(addr.addr(), 0);
1909 }
1910
1911 let addr_31 = &table[31];
1913 let latch_false_count = addr_31.iter().filter(|addr| !addr.latch()).count();
1914 assert_eq!(latch_false_count, 1);
1915
1916 for addr in addr_31 {
1918 assert_eq!(addr.addr(), 31);
1919 }
1920 }
1921
1922 #[test]
1923 fn test_make_data_template_function() {
1924 let template = make_data_template::<TEST_COLS>();
1926
1927 assert_eq!(template.len(), TEST_COLS);
1929
1930 for entry in &template {
1932 assert_eq!(entry.latch(), false);
1933 }
1934
1935 let oe_active = !cfg!(feature = "invert-oe");
1936 let active_count = TEST_COLS.saturating_sub(LEAD_BLANK_DELAY + TRAIL_BLANK_DELAY + 1);
1937 let blank_count = TEST_COLS - active_count;
1938 let oe_blank_count = template
1939 .iter()
1940 .filter(|entry| entry.output_enable() != oe_active)
1941 .count();
1942 assert_eq!(oe_blank_count, blank_count);
1943
1944 let small_template = make_data_template::<4>();
1946 assert_eq!(small_template.len(), 4);
1947
1948 let small_active = 4_usize.saturating_sub(LEAD_BLANK_DELAY + TRAIL_BLANK_DELAY + 1);
1949 let small_blank = 4 - small_active;
1950 let oe_blank_count = small_template
1951 .iter()
1952 .filter(|entry| entry.output_enable() != oe_active)
1953 .count();
1954 assert_eq!(oe_blank_count, small_blank);
1955
1956 #[cfg(not(feature = "esp32-ordering"))]
1959 {
1960 let single_template = make_data_template::<1>();
1961 assert_eq!(single_template.len(), 1);
1962 assert_eq!(single_template[0].output_enable(), !oe_active);
1963 assert_eq!(single_template[0].latch(), false);
1964 }
1965 }
1966
1967 #[test]
1968 fn test_addr_table_correctness() {
1969 for addr in 0..32 {
1971 let mut expected_addresses = [Address::new(); 4];
1972
1973 for i in 0..4 {
1975 let latch = !matches!(i, 3);
1976 #[cfg(feature = "esp32-ordering")]
1977 let mapped_i = map_index(i);
1978 #[cfg(not(feature = "esp32-ordering"))]
1979 let mapped_i = i;
1980
1981 expected_addresses[mapped_i].set_latch(latch);
1982 expected_addresses[mapped_i].set_addr(addr);
1983 expected_addresses[mapped_i].0 |= OE_BLANK;
1984 }
1985
1986 let table_addresses = &ADDR_TABLE[addr as usize];
1988 for i in 0..4 {
1989 assert_eq!(table_addresses[i].0, expected_addresses[i].0);
1990 }
1991 }
1992 }
1993
1994 const CHAR_W: i32 = 6;
1996 const CHAR_H: i32 = 10;
1997
1998 fn verify_glyph_at(fb: &mut TestFrameBuffer, origin: Point) {
2001 use embedded_graphics::mock_display::MockDisplay;
2002 use embedded_graphics::mono_font::ascii::FONT_6X10;
2003 use embedded_graphics::mono_font::MonoTextStyle;
2004 use embedded_graphics::text::{Baseline, Text};
2005
2006 let style = MonoTextStyle::new(&FONT_6X10, Color::WHITE);
2008 Text::with_baseline("A", origin, style, Baseline::Top)
2009 .draw(fb)
2010 .unwrap();
2011
2012 let mut reference: MockDisplay<Color> = MockDisplay::new();
2014 Text::with_baseline("A", Point::zero(), style, Baseline::Top)
2015 .draw(&mut reference)
2016 .unwrap();
2017
2018 for dy in 0..CHAR_H {
2020 for dx in 0..CHAR_W {
2021 let expected_on = reference
2022 .get_pixel(Point::new(dx, dy))
2023 .unwrap_or(Color::BLACK)
2024 != Color::BLACK;
2025
2026 let gx = (origin.x + dx) as usize;
2027 let gy = (origin.y + dy) as usize;
2028
2029 let frame0 = &fb.frames[0];
2036 let e = if gy < TEST_NROWS {
2037 &frame0.rows[get_mapped_row(gy)].data[map_index(gx)]
2038 } else {
2039 &frame0.rows[get_mapped_row(gy - TEST_NROWS)].data[map_index(gx)]
2040 };
2041
2042 let (r, g, b) = if gy >= TEST_NROWS {
2043 (e.red2(), e.grn2(), e.blu2())
2044 } else {
2045 (e.red1(), e.grn1(), e.blu1())
2046 };
2047
2048 if expected_on {
2049 assert!(r && g && b);
2050 } else {
2051 assert!(!r && !g && !b);
2052 }
2053 }
2054 }
2055 }
2056
2057 #[test]
2058 fn test_draw_char_corners() {
2059 let upper_left = Point::new(0, 0);
2061 let lower_right = Point::new(TEST_COLS as i32 - CHAR_W, TEST_ROWS as i32 - CHAR_H);
2062
2063 let mut fb = TestFrameBuffer::new();
2064
2065 verify_glyph_at(&mut fb, upper_left);
2067 verify_glyph_at(&mut fb, lower_right);
2069 }
2070
2071 #[test]
2072 fn test_framebuffer_operations_trait_erase() {
2073 let mut fb = TestFrameBuffer::new();
2074
2075 fb.set_pixel_internal(10, 5, Color::RED);
2077 fb.set_pixel_internal(20, 10, Color::GREEN);
2078
2079 <TestFrameBuffer as FrameBufferOperations>::erase(&mut fb);
2081
2082 let mc10 = map_index(10);
2084 let mc20 = map_index(20);
2085 assert_eq!(
2086 fb.frames[0].rows[get_mapped_row(5)].data[mc10].red1(),
2087 false
2088 );
2089 assert_eq!(
2090 fb.frames[0].rows[get_mapped_row(10)].data[mc20].grn1(),
2091 false
2092 );
2093
2094 let oe_active = !cfg!(feature = "invert-oe");
2096 let active_count = TEST_COLS.saturating_sub(LEAD_BLANK_DELAY + TRAIL_BLANK_DELAY + 1);
2097 let blank_count = TEST_COLS - active_count;
2098 let row0 = &fb.frames[0].rows[get_mapped_row(0)];
2099 let oe_blank_count = row0
2100 .data
2101 .iter()
2102 .filter(|entry| entry.output_enable() != oe_active)
2103 .count();
2104 assert_eq!(oe_blank_count, blank_count);
2105 assert!(row0.data.iter().all(|e| !e.latch()));
2106
2107 for (i, addr) in row0.address.iter().enumerate() {
2109 assert_eq!(addr.0, ADDR_TABLE[0][i].0);
2110 }
2111 }
2112
2113 #[test]
2114 fn test_framebuffer_operations_trait_set_pixel() {
2115 let mut fb = TestFrameBuffer::new();
2116
2117 <TestFrameBuffer as FrameBufferOperations>::set_pixel(
2119 &mut fb,
2120 Point::new(8, 3),
2121 Color::BLUE,
2122 );
2123
2124 let idx = map_index(8);
2126 assert_eq!(fb.frames[0].rows[get_mapped_row(3)].data[idx].blu1(), true);
2127 assert_eq!(fb.frames[0].rows[get_mapped_row(3)].data[idx].red1(), false);
2129 assert_eq!(fb.frames[0].rows[get_mapped_row(3)].data[idx].grn1(), false);
2130 }
2131
2132 #[test]
2133 fn test_blanking_delay() {
2134 let mut row: Row<TEST_COLS> = Row::new();
2135 row.format(5);
2136
2137 let oe_active = !cfg!(feature = "invert-oe");
2138
2139 if TRAIL_BLANK_DELAY > 0 {
2141 let trail_blank_idx = map_index(TRAIL_BLANK_DELAY - 1);
2142 assert_eq!(row.data[trail_blank_idx].output_enable(), !oe_active);
2143 }
2144
2145 let first_active_idx = map_index(TRAIL_BLANK_DELAY);
2146 assert_eq!(row.data[first_active_idx].output_enable(), oe_active);
2147
2148 let last_active_idx = map_index(TEST_COLS - LEAD_BLANK_DELAY - 2);
2150 assert_eq!(row.data[last_active_idx].output_enable(), oe_active);
2151
2152 let lead_blank_idx = map_index(TEST_COLS - LEAD_BLANK_DELAY - 1);
2153 assert_eq!(row.data[lead_blank_idx].output_enable(), !oe_active);
2154
2155 let last_pixel_idx = map_index(TEST_COLS - 1);
2156 assert_eq!(row.data[last_pixel_idx].output_enable(), !oe_active);
2157 }
2158
2159 static STATIC_FB: TestFrameBuffer = TestFrameBuffer::new();
2160
2161 #[test]
2162 fn test_static_construction_is_formatted() {
2163 let runtime_fb = TestFrameBuffer::new();
2164
2165 for (fi, frame) in STATIC_FB.frames.iter().enumerate() {
2166 for (ri, row) in frame.rows.iter().enumerate() {
2167 assert_eq!(
2168 row.data, runtime_fb.frames[fi].rows[ri].data,
2169 "static vs runtime data mismatch at frame {fi}, row {ri}"
2170 );
2171 assert_eq!(
2172 row.address, runtime_fb.frames[fi].rows[ri].address,
2173 "static vs runtime address mismatch at frame {fi}, row {ri}"
2174 );
2175 assert_eq!(
2176 row.gap, runtime_fb.frames[fi].rows[ri].gap,
2177 "static vs runtime gap mismatch at frame {fi}, row {ri}"
2178 );
2179 }
2180 }
2181 }
2182
2183 #[test]
2184 fn test_format_reinitializes_at_runtime() {
2185 let mut fb = TestFrameBuffer::new();
2186 fb.erase();
2187 fb.format();
2188
2189 for (fi, frame) in fb.frames.iter().enumerate() {
2190 for (ri, row) in frame.rows.iter().enumerate() {
2191 assert_eq!(
2192 row.data, STATIC_FB.frames[fi].rows[ri].data,
2193 "re-formatted vs static mismatch at frame {fi}, row {ri}"
2194 );
2195 }
2196 }
2197 }
2198
2199 #[test]
2200 fn bcm_segment_count_is_one() {
2201 use crate::FrameBuffer;
2202 let fb = TestFrameBuffer::new();
2203 assert_eq!(fb.bcm_segment_count(), 1);
2204 }
2205
2206 #[test]
2207 fn bcm_segment_covers_entire_buffer() {
2208 use crate::FrameBuffer;
2209 let fb = TestFrameBuffer::new();
2210 let seg = fb.bcm_segment(0);
2211 let expected_ptr = (&raw const fb.frames).cast::<u8>();
2212 let expected_len = core::mem::size_of_val(&fb.frames);
2213 assert_eq!(seg.ptr, expected_ptr);
2214 assert_eq!(seg.len, expected_len);
2215 assert_eq!(seg.reps, 1);
2216 }
2217
2218 #[test]
2219 #[should_panic(expected = "out of range")]
2220 fn bcm_segment_panics_for_invalid_index() {
2221 use crate::FrameBuffer;
2222 let fb = TestFrameBuffer::new();
2223 let _ = fb.bcm_segment(1);
2224 }
2225
2226 #[test]
2227 fn bcm_sequence_matches_runtime_segments() {
2228 use crate::FrameBuffer;
2229 let fb = TestFrameBuffer::new();
2230 assert_eq!(
2231 TestFrameBuffer::BCM_SEGMENT_COUNT,
2232 TestFrameBuffer::BCM_SEQUENCE_LEN * TestFrameBuffer::BCM_SEQUENCE_COUNT
2233 );
2234 assert_eq!(fb.bcm_segment_count(), TestFrameBuffer::BCM_SEGMENT_COUNT);
2235 for i in 0..TestFrameBuffer::BCM_SEGMENT_COUNT {
2236 let entry = TestFrameBuffer::BCM_SEQUENCE[i % TestFrameBuffer::BCM_SEQUENCE_LEN];
2237 let seg = fb.bcm_segment(i);
2238 assert_eq!((seg.len, seg.reps), (entry.len, entry.reps), "segment {i}");
2239 assert!(!seg.ptr.is_null(), "segment {i} has null pointer");
2240 }
2241 for entry in &TestFrameBuffer::BCM_SEQUENCE[TestFrameBuffer::BCM_SEQUENCE_LEN..] {
2242 assert_eq!(*entry, crate::BcmLenReps::ZERO, "padding must be zero");
2243 }
2244 }
2245}