Skip to main content

hub75_framebuffer/
plain.rs

1//! DMA-friendly framebuffer implementation for HUB75 LED panels.
2//!
3//! This module provides a framebuffer implementation with memory
4//! layout optimized for efficient transfer to HUB75 LED panels. The data is
5//! structured for direct signal mapping, making it ideal for DMA transfers but
6//! also suitable for programmatic transfer. It supports RGB color and brightness
7//! control through multiple frames using Binary Code Modulation (BCM).
8//!
9//! # Hardware Requirements
10//! This implementation can be used by any microcontroller that has a peripheral
11//! capable of outputting a clock signal and 16 bits (though only 14 bits are
12//! actually needed) in parallel. The data is structured to directly match the
13//! HUB75 connector signals, making it suitable for various parallel output
14//! peripherals.
15//!
16//! # Features
17//! - Memory layout optimized for efficient data transfer
18//! - Direct signal mapping to HUB75 connector signals
19//! - Support for RGB color with brightness control
20//! - Multiple frame buffers for Binary Code Modulation (BCM)
21//! - Integration with embedded-graphics for easy drawing
22//!
23//! # Brightness Control
24//! Brightness is controlled through Binary Code Modulation (BCM):
25//! - The number of brightness levels is determined by the `BITS` parameter
26//! - Each additional bit doubles the number of brightness levels
27//! - More bits provide better brightness resolution but require more memory
28//! - Memory usage grows exponentially with the number of bits: `(2^BITS)-1`
29//!   frames
30//! - Example: 8 bits = 256 levels, 4 bits = 16 levels
31//!
32//! # Memory Usage
33//! The framebuffer's memory usage is determined by:
34//! - Panel size (ROWS × COLS)
35//! - Number of brightness bits (BITS)
36//! - Memory grows exponentially with bits: `(2^BITS)-1` frames
37//! - 16-bit entries provide direct signal mapping but use more memory
38//!
39//! # Example
40//! ```rust,no_run
41//! use embedded_graphics::pixelcolor::RgbColor;
42//! use embedded_graphics::prelude::*;
43//! use embedded_graphics::primitives::Circle;
44//! use embedded_graphics::primitives::Rectangle;
45//! use hub75_framebuffer::compute_frame_count;
46//! use hub75_framebuffer::compute_rows;
47//! use hub75_framebuffer::Color;
48//! use hub75_framebuffer::plain::DmaFrameBuffer;
49//! use embedded_graphics::primitives::PrimitiveStyle;
50//!
51//! // Create a framebuffer for a 64x32 panel with 3-bit color depth
52//! const ROWS: usize = 32;
53//! const COLS: usize = 64;
54//! const BITS: u8 = 3; // Color depth (8 brightness levels, 7 frames)
55//! const NROWS: usize = compute_rows(ROWS); // Number of rows per scan
56//! const FRAME_COUNT: usize = compute_frame_count(BITS); // Number of frames for BCM
57//!
58//! let mut framebuffer = DmaFrameBuffer::<ROWS, COLS, NROWS, BITS, FRAME_COUNT>::new();
59//!
60//! // Draw a red rectangle
61//! Rectangle::new(Point::new(10, 10), Size::new(20, 20))
62//!     .into_styled(PrimitiveStyle::with_fill(Color::RED))
63//!     .draw(&mut framebuffer)
64//!     .unwrap();
65//!
66//! // Draw a blue circle
67//! Circle::new(Point::new(40, 20), 10)
68//!     .into_styled(PrimitiveStyle::with_fill(Color::BLUE))
69//!     .draw(&mut framebuffer)
70//!     .unwrap();
71//! ```
72//!
73//! # Implementation Details
74//! The framebuffer is organized to directly match the HUB75 connector signals:
75//! - Each 16-bit word maps directly to the HUB75 control signals
76//! - Color data (R, G, B) for two sub-pixels is stored in dedicated bits
77//! - Control signals (output enable, latch, address) are mapped to specific
78//!   bits
79//! - Multiple frames are used to achieve Binary Code Modulation (BCM)
80//! - DMA transfers the data directly to the panel without any transformation
81//!
82//! # HUB75 Signal Bit Mapping
83//! Each 16-bit `Entry` represents the logic levels that will be driven onto the HUB75
84//! bus during a single pixel-clock cycle. Every panel signal—apart from the clock
85//! (`CLK`) itself—occupies a dedicated bit, allowing the word to be streamed via DMA
86//! with zero run-time manipulation.
87//!
88//! ```text
89//! 15 ─ Dummy2   (spare)
90//! 14 ─ B2       Blue  – lower half of the panel
91//! 13 ─ G2       Green – lower half of the panel
92//! 12 ─ R2       Red   – lower half of the panel
93//! 11 ─ B1       Blue  – upper half of the panel
94//! 10 ─ G1       Green – upper half of the panel
95//!  9 ─ R1       Red   – upper half of the panel
96//!  8 ─ OE       Output-Enable / Blank
97//!  7 ─ Dummy1   (spare)
98//!  6 ─ Dummy0   (spare)
99//!  5 ─ LAT      Latch / STB
100//! 4-0 ─ A..E    Row address lines
101//! ```
102//!
103//! The pixel clock is generated by the peripheral that owns the DMA stream and
104//! is therefore **not** part of the 16-bit word stored in the framebuffer.
105//!
106//! # Binary Code Modulation (BCM) Frames
107//! Brightness is achieved with Binary-Code-Modulation as outlined in
108//! <https://www.batsocks.co.uk/readme/art_bcm_1.htm>. For a colour depth of
109//! `BITS`, the driver allocates `FRAME_COUNT = 2^BITS - 1` frames. Frame *n*
110//! (0-based) is displayed for a duration proportional to `2^n`, mirroring the
111//! weight of that bit in a binary number.
112//!
113//! When a pixel is written its 8-bit RGB components are compared against a
114//! per-frame threshold:
115//!
116//! ```text
117//! brightness_step = 256 / 2^BITS
118//! threshold_n     = (n + 1) * brightness_step
119//! ```
120//!
121//! If a channel's value is greater than or equal to `threshold_n` the
122//! corresponding colour bit is set in frame *n*. Streaming the frames from the
123//! least-significant (shortest) to the most-significant (longest) time-slot
124//! produces the correct 8-bit brightness while keeping the refresh routine
125//! trivial.
126//!
127//! # Safety
128//! This implementation uses unsafe code for DMA operations. The framebuffer
129//! must be properly aligned in memory and the DMA configuration must match the
130//! buffer layout.
131
132use core::convert::Infallible;
133
134use crate::FrameBufferOperations;
135use bitfield::bitfield;
136#[cfg(not(feature = "esp-hal-dma"))]
137use embedded_dma::ReadBuffer;
138use embedded_graphics::pixelcolor::RgbColor;
139use embedded_graphics::prelude::Point;
140#[cfg(feature = "esp-hal-dma")]
141use esp_hal::dma::ReadBuffer;
142
143use super::Color;
144use super::FrameBuffer;
145use super::WordSize;
146
147const BLANKING_DELAY: usize = 1;
148
149/// Creates a pre-computed data template for a row with the specified addresses.
150/// This template contains all the timing and control signals but no pixel data.
151#[inline]
152const fn make_data_template<const COLS: usize>(addr: u8, prev_addr: u8) -> [Entry; COLS] {
153    let mut data = [Entry::new(); COLS];
154    let mut i = 0;
155
156    while i < COLS {
157        let mut entry = Entry::new();
158        entry.0 = prev_addr as u16;
159
160        // Apply timing control based on position
161        if i == 1 {
162            entry.0 |= 0b1_0000_0000; // set output_enable bit
163        } else if i == COLS - BLANKING_DELAY - 1 {
164            // output_enable already false from initialization
165        } else if i == COLS - 1 {
166            entry.0 |= 0b0010_0000; // set latch bit
167            entry.0 = (entry.0 & !0b0001_1111) | (addr as u16); // set new address
168        } else if i > 1 && i < COLS - BLANKING_DELAY - 1 {
169            entry.0 |= 0b1_0000_0000; // set output_enable bit
170        }
171
172        data[map_index(i)] = entry;
173        i += 1;
174    }
175
176    data
177}
178
179bitfield! {
180    /// A 16-bit word representing the HUB75 control signals for a single pixel.
181    ///
182    /// This structure directly maps to the HUB75 connector signals:
183    /// - RGB color data for two sub-pixels (color0 and color1)
184    /// - Panel control signals (output enable, latch, address)
185    /// - Dummy bits for timing alignment
186    ///
187    /// The bit layout matches the HUB75 connector signals:
188    /// - Bit 15: Dummy bit 2
189    /// - Bit 14: Blue channel for color1
190    /// - Bit 13: Green channel for color1
191    /// - Bit 12: Red channel for color1
192    /// - Bit 11: Blue channel for color0
193    /// - Bit 10: Green channel for color0
194    /// - Bit 9: Red channel for color0
195    /// - Bit 8: Output enable
196    /// - Bit 7: Dummy bit 1
197    /// - Bit 6: Dummy bit 0
198    /// - Bit 5: Latch signal
199    /// - Bits 4-0: Row address
200    #[derive(Clone, Copy, Default, PartialEq)]
201    #[repr(transparent)]
202    struct Entry(u16);
203    dummy2, set_dummy2: 15;
204    blu2, set_blu2: 14;
205    grn2, set_grn2: 13;
206    red2, set_red2: 12;
207    blu1, set_blu1: 11;
208    grn1, set_grn1: 10;
209    red1, set_red1: 9;
210    output_enable, set_output_enable: 8;
211    dummy1, set_dummy1: 7;
212    dummy0, set_dummy0: 6;
213    latch, set_latch: 5;
214    addr, set_addr: 4, 0;
215}
216
217impl core::fmt::Debug for Entry {
218    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
219        f.debug_tuple("Entry")
220            .field(&format_args!("{:#x}", self.0))
221            .finish()
222    }
223}
224
225#[cfg(feature = "defmt")]
226impl defmt::Format for Entry {
227    fn format(&self, f: defmt::Formatter) {
228        defmt::write!(f, "Entry({=u16:#x})", self.0);
229    }
230}
231
232impl Entry {
233    const fn new() -> Self {
234        Self(0)
235    }
236
237    // Optimized color bit manipulation constants and methods
238    const COLOR0_MASK: u16 = 0b0000_1110_0000_0000; // bits 9-11: R1, G1, B1
239    const COLOR1_MASK: u16 = 0b0111_0000_0000_0000; // bits 12-14: R2, G2, B2
240
241    #[inline]
242    fn set_color0_bits(&mut self, bits: u8) {
243        let bits16 = u16::from(bits) << 9;
244        self.0 = (self.0 & !Self::COLOR0_MASK) | (bits16 & Self::COLOR0_MASK);
245    }
246
247    #[inline]
248    fn set_color1_bits(&mut self, bits: u8) {
249        let bits16 = u16::from(bits) << 12;
250        self.0 = (self.0 & !Self::COLOR1_MASK) | (bits16 & Self::COLOR1_MASK);
251    }
252}
253
254/// Represents a single row of pixels in the framebuffer.
255///
256/// Each row contains a fixed number of columns (`COLS`) and manages the timing
257/// and control signals for the HUB75 panel. The row handles:
258/// - Output enable timing to prevent ghosting
259/// - Latch signal generation for row updates
260/// - Row address management
261/// - Color data for both sub-pixels
262#[derive(Clone, Copy, PartialEq, Debug)]
263#[repr(C)]
264struct Row<const COLS: usize> {
265    data: [Entry; COLS],
266}
267
268const fn map_index(i: usize) -> usize {
269    #[cfg(feature = "esp32-ordering")]
270    {
271        i ^ 1
272    }
273    #[cfg(not(feature = "esp32-ordering"))]
274    {
275        i
276    }
277}
278
279impl<const COLS: usize> Default for Row<COLS> {
280    fn default() -> Self {
281        Self::new()
282    }
283}
284
285impl<const COLS: usize> Row<COLS> {
286    pub const fn new() -> Self {
287        Self {
288            data: [Entry::new(); COLS],
289        }
290    }
291
292    pub fn format(&mut self, addr: u8, prev_addr: u8) {
293        // Use pre-computed template and bulk copy for maximum performance
294        let template = make_data_template::<COLS>(addr, prev_addr);
295        self.data.copy_from_slice(&template);
296    }
297
298    /// Fast clear method that preserves timing/control bits while clearing pixel data.
299    /// Uses bulk memory operations for maximum performance.
300    #[inline]
301    pub fn clear_colors(&mut self) {
302        // Clear color bits while preserving timing and control bits
303        const COLOR_CLEAR_MASK: u16 = !0b0111_1110_0000_0000; // Clear bits 9-14 (R1,G1,B1,R2,G2,B2)
304
305        for entry in &mut self.data {
306            entry.0 &= COLOR_CLEAR_MASK;
307        }
308    }
309
310    #[inline]
311    pub fn set_color0(&mut self, col: usize, r: bool, g: bool, b: bool) {
312        let bits = (u8::from(b) << 2) | (u8::from(g) << 1) | u8::from(r);
313        let col = map_index(col);
314        self.data[col].set_color0_bits(bits);
315    }
316
317    #[inline]
318    pub fn set_color1(&mut self, col: usize, r: bool, g: bool, b: bool) {
319        let bits = (u8::from(b) << 2) | (u8::from(g) << 1) | u8::from(r);
320        let col = map_index(col);
321        self.data[col].set_color1_bits(bits);
322    }
323}
324
325#[derive(Copy, Clone, Debug)]
326#[repr(C)]
327struct Frame<const ROWS: usize, const COLS: usize, const NROWS: usize> {
328    rows: [Row<COLS>; NROWS],
329}
330
331impl<const ROWS: usize, const COLS: usize, const NROWS: usize> Frame<ROWS, COLS, NROWS> {
332    pub const fn new() -> Self {
333        Self {
334            rows: [Row::new(); NROWS],
335        }
336    }
337
338    pub fn format(&mut self) {
339        for (addr, row) in self.rows.iter_mut().enumerate() {
340            let prev_addr = if addr == 0 {
341                NROWS as u8 - 1
342            } else {
343                addr as u8 - 1
344            };
345            row.format(addr as u8, prev_addr);
346        }
347    }
348
349    /// Fast clear method that preserves timing/control bits while clearing pixel data.
350    #[inline]
351    pub fn clear_colors(&mut self) {
352        for row in &mut self.rows {
353            row.clear_colors();
354        }
355    }
356
357    #[inline]
358    pub fn set_pixel(&mut self, y: usize, x: usize, red: bool, green: bool, blue: bool) {
359        let row = &mut self.rows[if y < NROWS { y } else { y - NROWS }];
360        if y < NROWS {
361            row.set_color0(x, red, green, blue);
362        } else {
363            row.set_color1(x, red, green, blue);
364        }
365    }
366}
367
368impl<const ROWS: usize, const COLS: usize, const NROWS: usize> Default
369    for Frame<ROWS, COLS, NROWS>
370{
371    fn default() -> Self {
372        Self::new()
373    }
374}
375
376/// DMA-compatible framebuffer for HUB75 LED panels.
377///
378/// This is a framebuffer implementation that:
379/// - Manages multiple frames for Binary Code Modulation (BCM)
380/// - Provides DMA-compatible memory layout
381/// - Implements the embedded-graphics `DrawTarget` trait
382///
383/// # Type Parameters
384/// - `ROWS`: Total number of rows in the panel
385/// - `COLS`: Number of columns in the panel
386/// - `NROWS`: Number of rows per scan (typically half of ROWS)
387/// - `BITS`: Color depth (1-8 bits)
388/// - `FRAME_COUNT`: Number of frames used for Binary Code Modulation
389///
390/// # Helper Functions
391/// Use these functions to compute the correct values:
392/// - `esp_hub75::compute_frame_count(BITS)`: Computes the required number of
393///   frames
394/// - `esp_hub75::compute_rows(ROWS)`: Computes the number of rows per scan
395///
396/// # Memory Layout
397/// The buffer is aligned to ensure efficient DMA transfers and contains:
398/// - A 64-bit alignment field
399/// - An array of frames, each containing the full panel data
400#[derive(Copy, Clone)]
401#[repr(C)]
402pub struct DmaFrameBuffer<
403    const ROWS: usize,
404    const COLS: usize,
405    const NROWS: usize,
406    const BITS: u8,
407    const FRAME_COUNT: usize,
408> {
409    _align: u64,
410    frames: [Frame<ROWS, COLS, NROWS>; FRAME_COUNT],
411}
412
413impl<
414        const ROWS: usize,
415        const COLS: usize,
416        const NROWS: usize,
417        const BITS: u8,
418        const FRAME_COUNT: usize,
419    > Default for DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
420{
421    fn default() -> Self {
422        Self::new()
423    }
424}
425
426impl<
427        const ROWS: usize,
428        const COLS: usize,
429        const NROWS: usize,
430        const BITS: u8,
431        const FRAME_COUNT: usize,
432    > DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
433{
434    /// Create a new, ready-to-use framebuffer.
435    ///
436    /// This creates a new framebuffer and automatically formats it with proper timing signals.
437    /// The framebuffer is immediately ready for pixel operations and DMA transfers.
438    ///
439    /// # Panics
440    ///
441    /// Panics if `BITS` is greater than 8, as only 1-8 bit color depths are supported.
442    ///
443    /// # Example
444    /// ```rust,no_run
445    /// use hub75_framebuffer::{Color,plain::DmaFrameBuffer,compute_rows,compute_frame_count};
446    ///
447    /// const ROWS: usize = 32;
448    /// const COLS: usize = 64;
449    /// const BITS: u8 = 3; // Color depth (8 brightness levels, 7 frames)
450    /// const NROWS: usize = compute_rows(ROWS); // Number of rows per scan
451    /// const FRAME_COUNT: usize = compute_frame_count(BITS); // Number of frames for BCM
452    ///
453    /// let mut framebuffer = DmaFrameBuffer::<ROWS, COLS, NROWS, BITS, FRAME_COUNT>::new();
454    /// // No need to call format() - framebuffer is ready to use!
455    /// ```
456    #[must_use]
457    pub fn new() -> Self {
458        debug_assert!(BITS <= 8);
459
460        let mut instance = Self {
461            _align: 0,
462            frames: [Frame::new(); FRAME_COUNT],
463        };
464
465        // Pre-format the framebuffer so it's immediately ready for use
466        instance.format();
467        instance
468    }
469
470    /// This returns the size of the DMA buffer in bytes.  Its used to calculate
471    /// the number of DMA descriptors needed.
472    /// # Example
473    /// ```rust
474    /// use hub75_framebuffer::{Color,plain::DmaFrameBuffer,compute_rows,compute_frame_count};
475    ///
476    /// const ROWS: usize = 32;
477    /// const COLS: usize = 64;
478    /// const BITS: u8 = 3; // Color depth (8 brightness levels, 7 frames)
479    /// const NROWS: usize = compute_rows(ROWS); // Number of rows per scan
480    /// const FRAME_COUNT: usize = compute_frame_count(BITS);
481    ///
482    /// type FBType = DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>;
483    /// let (_, tx_descriptors) = esp_hal::dma_descriptors!(0, FBType::dma_buffer_size_bytes());
484    /// ```
485    #[cfg(feature = "esp-hal-dma")]
486    #[must_use]
487    pub const fn dma_buffer_size_bytes() -> usize {
488        core::mem::size_of::<[Frame<ROWS, COLS, NROWS>; FRAME_COUNT]>()
489    }
490
491    /// Perform full formatting of the framebuffer with timing and control signals.
492    ///
493    /// This sets up all the timing and control signals needed for proper HUB75 operation.
494    /// This is automatically called by `new()`, so you typically don't need to call this
495    /// unless you want to completely reinitialize the framebuffer.
496    ///
497    /// # Example
498    /// ```rust,no_run
499    /// use hub75_framebuffer::{Color,plain::DmaFrameBuffer,compute_rows,compute_frame_count};
500    ///
501    /// const ROWS: usize = 32;
502    /// const COLS: usize = 64;
503    /// const BITS: u8 = 3; // Color depth (8 brightness levels, 7 frames)
504    /// const NROWS: usize = compute_rows(ROWS); // Number of rows per scan
505    /// const FRAME_COUNT: usize = compute_frame_count(BITS); // Number of frames for BCM
506    ///
507    /// let mut framebuffer = DmaFrameBuffer::<ROWS, COLS, NROWS, BITS, FRAME_COUNT>::new();
508    /// framebuffer.format(); // Reinitialize if needed
509    /// ```
510    #[inline]
511    pub fn format(&mut self) {
512        for frame in &mut self.frames {
513            frame.format();
514        }
515    }
516
517    /// Fast erase operation that clears all pixel data while preserving timing signals.
518    ///
519    /// This is much faster than `format()` when you just want to clear the display
520    /// since it preserves all the timing and control signals that are already set up.
521    /// Use this for clearing between frames or when you want to start drawing fresh content.
522    ///
523    /// # Example
524    /// ```rust,no_run
525    /// use hub75_framebuffer::{Color,plain::DmaFrameBuffer,compute_rows,compute_frame_count};
526    ///
527    /// const ROWS: usize = 32;
528    /// const COLS: usize = 64;
529    /// const BITS: u8 = 3; // Color depth (8 brightness levels, 7 frames)
530    /// const NROWS: usize = compute_rows(ROWS); // Number of rows per scan
531    /// const FRAME_COUNT: usize = compute_frame_count(BITS); // Number of frames for BCM
532    ///
533    /// let mut framebuffer = DmaFrameBuffer::<ROWS, COLS, NROWS, BITS, FRAME_COUNT>::new();
534    /// framebuffer.erase();
535    /// ```
536    #[inline]
537    pub fn erase(&mut self) {
538        for frame in &mut self.frames {
539            frame.clear_colors();
540        }
541    }
542
543    /// Set a pixel in the framebuffer.
544    /// # Example
545    /// ```rust,no_run
546    /// use hub75_framebuffer::{Color,plain::DmaFrameBuffer,compute_rows,compute_frame_count};
547    /// use embedded_graphics::prelude::*;
548    ///
549    /// const ROWS: usize = 32;
550    /// const COLS: usize = 64;
551    /// const BITS: u8 = 3; // Color depth (8 brightness levels, 7 frames)
552    /// const NROWS: usize = compute_rows(ROWS); // Number of rows per scan
553    /// const FRAME_COUNT: usize = compute_frame_count(BITS); // Number of frames for BCM
554    ///
555    /// let mut framebuffer = DmaFrameBuffer::<ROWS, COLS, NROWS, BITS, FRAME_COUNT>::new();
556    /// framebuffer.set_pixel(Point::new(10, 10), Color::RED);
557    /// ```
558    pub fn set_pixel(&mut self, p: Point, color: Color) {
559        if p.x < 0 || p.y < 0 {
560            return;
561        }
562        self.set_pixel_internal(p.x as usize, p.y as usize, color);
563    }
564
565    #[inline]
566    fn frames_on(v: u8) -> usize {
567        // v / brightness_step but the compiler resolves the shift at build-time
568        (v as usize) >> (8 - BITS)
569    }
570
571    #[inline]
572    fn set_pixel_internal(&mut self, x: usize, y: usize, color: Color) {
573        if x >= COLS || y >= ROWS {
574            return;
575        }
576
577        // Early exit for black pixels - common in UI backgrounds
578        // Only enabled when skip-black-pixels feature is active
579        #[cfg(feature = "skip-black-pixels")]
580        if color == Color::BLACK {
581            return;
582        }
583
584        // Pre-compute how many frames each channel should be on
585        let red_frames = Self::frames_on(color.r());
586        let green_frames = Self::frames_on(color.g());
587        let blue_frames = Self::frames_on(color.b());
588
589        // Set the pixel in all frames based on pre-computed frame counts
590        for (frame_idx, frame) in self.frames.iter_mut().enumerate() {
591            frame.set_pixel(
592                y,
593                x,
594                frame_idx < red_frames,
595                frame_idx < green_frames,
596                frame_idx < blue_frames,
597            );
598        }
599    }
600}
601
602impl<
603        const ROWS: usize,
604        const COLS: usize,
605        const NROWS: usize,
606        const BITS: u8,
607        const FRAME_COUNT: usize,
608    > FrameBufferOperations<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
609    for DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
610{
611    #[inline]
612    fn erase(&mut self) {
613        DmaFrameBuffer::<ROWS, COLS, NROWS, BITS, FRAME_COUNT>::erase(self);
614    }
615
616    #[inline]
617    fn set_pixel(&mut self, p: Point, color: Color) {
618        DmaFrameBuffer::<ROWS, COLS, NROWS, BITS, FRAME_COUNT>::set_pixel(self, p, color);
619    }
620}
621
622impl<
623        const ROWS: usize,
624        const COLS: usize,
625        const NROWS: usize,
626        const BITS: u8,
627        const FRAME_COUNT: usize,
628    > embedded_graphics::prelude::OriginDimensions
629    for DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
630{
631    fn size(&self) -> embedded_graphics::prelude::Size {
632        embedded_graphics::prelude::Size::new(COLS as u32, ROWS as u32)
633    }
634}
635
636impl<
637        const ROWS: usize,
638        const COLS: usize,
639        const NROWS: usize,
640        const BITS: u8,
641        const FRAME_COUNT: usize,
642    > embedded_graphics::prelude::OriginDimensions
643    for &mut DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
644{
645    fn size(&self) -> embedded_graphics::prelude::Size {
646        embedded_graphics::prelude::Size::new(COLS as u32, ROWS as u32)
647    }
648}
649
650impl<
651        const ROWS: usize,
652        const COLS: usize,
653        const NROWS: usize,
654        const BITS: u8,
655        const FRAME_COUNT: usize,
656    > embedded_graphics::draw_target::DrawTarget
657    for DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
658{
659    type Color = Color;
660
661    type Error = Infallible;
662
663    fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
664    where
665        I: IntoIterator<Item = embedded_graphics::Pixel<Self::Color>>,
666    {
667        for pixel in pixels {
668            self.set_pixel_internal(pixel.0.x as usize, pixel.0.y as usize, pixel.1);
669        }
670        Ok(())
671    }
672}
673
674unsafe impl<
675        const ROWS: usize,
676        const COLS: usize,
677        const NROWS: usize,
678        const BITS: u8,
679        const FRAME_COUNT: usize,
680    > ReadBuffer for DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
681{
682    #[cfg(not(feature = "esp-hal-dma"))]
683    type Word = u8;
684
685    unsafe fn read_buffer(&self) -> (*const u8, usize) {
686        let ptr = (&raw const self.frames).cast::<u8>();
687        let len = core::mem::size_of_val(&self.frames);
688        (ptr, len)
689    }
690}
691
692unsafe impl<
693        const ROWS: usize,
694        const COLS: usize,
695        const NROWS: usize,
696        const BITS: u8,
697        const FRAME_COUNT: usize,
698    > ReadBuffer for &mut DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
699{
700    #[cfg(not(feature = "esp-hal-dma"))]
701    type Word = u8;
702
703    unsafe fn read_buffer(&self) -> (*const u8, usize) {
704        let ptr = (&raw const self.frames).cast::<u8>();
705        let len = core::mem::size_of_val(&self.frames);
706        (ptr, len)
707    }
708}
709
710impl<
711        const ROWS: usize,
712        const COLS: usize,
713        const NROWS: usize,
714        const BITS: u8,
715        const FRAME_COUNT: usize,
716    > core::fmt::Debug for DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
717{
718    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
719        let brightness_step = 1 << (8 - BITS);
720        f.debug_struct("DmaFrameBuffer")
721            .field("size", &core::mem::size_of_val(&self.frames))
722            .field("frame_count", &self.frames.len())
723            .field("frame_size", &core::mem::size_of_val(&self.frames[0]))
724            .field("brightness_step", &&brightness_step)
725            .finish_non_exhaustive()
726    }
727}
728
729#[cfg(feature = "defmt")]
730impl<
731        const ROWS: usize,
732        const COLS: usize,
733        const NROWS: usize,
734        const BITS: u8,
735        const FRAME_COUNT: usize,
736    > defmt::Format for DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
737{
738    fn format(&self, f: defmt::Formatter) {
739        let brightness_step = 1 << (8 - BITS);
740        defmt::write!(
741            f,
742            "DmaFrameBuffer<{}, {}, {}, {}, {}>",
743            ROWS,
744            COLS,
745            NROWS,
746            BITS,
747            FRAME_COUNT
748        );
749        defmt::write!(f, " size: {}", core::mem::size_of_val(&self.frames));
750        defmt::write!(
751            f,
752            " frame_size: {}",
753            core::mem::size_of_val(&self.frames[0])
754        );
755        defmt::write!(f, " brightness_step: {}", brightness_step);
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    > FrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
766    for DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
767{
768    fn get_word_size(&self) -> WordSize {
769        WordSize::Sixteen
770    }
771}
772
773impl<
774        const ROWS: usize,
775        const COLS: usize,
776        const NROWS: usize,
777        const BITS: u8,
778        const FRAME_COUNT: usize,
779    > FrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
780    for &mut DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
781{
782    fn get_word_size(&self) -> WordSize {
783        WordSize::Sixteen
784    }
785}
786
787#[cfg(test)]
788mod tests {
789    extern crate std;
790
791    use std::format;
792    use std::vec;
793
794    use super::*;
795    use crate::{FrameBuffer, WordSize};
796    use embedded_graphics::pixelcolor::RgbColor;
797    use embedded_graphics::prelude::*;
798    use embedded_graphics::primitives::{Circle, PrimitiveStyle, Rectangle};
799
800    const TEST_ROWS: usize = 32;
801    const TEST_COLS: usize = 64;
802    const TEST_NROWS: usize = TEST_ROWS / 2;
803    const TEST_BITS: u8 = 3;
804    const TEST_FRAME_COUNT: usize = (1 << TEST_BITS) - 1; // 7 frames for 3-bit depth
805
806    type TestFrameBuffer =
807        DmaFrameBuffer<TEST_ROWS, TEST_COLS, TEST_NROWS, TEST_BITS, TEST_FRAME_COUNT>;
808
809    // Helper function to get mapped index for ESP32
810    fn get_mapped_index(index: usize) -> usize {
811        map_index(index)
812    }
813
814    #[test]
815    fn test_entry_construction() {
816        let entry = Entry::new();
817        assert_eq!(entry.0, 0);
818        assert_eq!(entry.dummy2(), false);
819        assert_eq!(entry.blu2(), false);
820        assert_eq!(entry.grn2(), false);
821        assert_eq!(entry.red2(), false);
822        assert_eq!(entry.blu1(), false);
823        assert_eq!(entry.grn1(), false);
824        assert_eq!(entry.red1(), false);
825        assert_eq!(entry.output_enable(), false);
826        assert_eq!(entry.dummy1(), false);
827        assert_eq!(entry.dummy0(), false);
828        assert_eq!(entry.latch(), false);
829        assert_eq!(entry.addr(), 0);
830    }
831
832    #[test]
833    fn test_entry_setters() {
834        let mut entry = Entry::new();
835
836        entry.set_dummy2(true);
837        assert_eq!(entry.dummy2(), true);
838        assert_eq!(entry.0 & 0b1000000000000000, 0b1000000000000000);
839
840        entry.set_blu2(true);
841        assert_eq!(entry.blu2(), true);
842        assert_eq!(entry.0 & 0b0100000000000000, 0b0100000000000000);
843
844        entry.set_grn2(true);
845        assert_eq!(entry.grn2(), true);
846        assert_eq!(entry.0 & 0b0010000000000000, 0b0010000000000000);
847
848        entry.set_red2(true);
849        assert_eq!(entry.red2(), true);
850        assert_eq!(entry.0 & 0b0001000000000000, 0b0001000000000000);
851
852        entry.set_blu1(true);
853        assert_eq!(entry.blu1(), true);
854        assert_eq!(entry.0 & 0b0000100000000000, 0b0000100000000000);
855
856        entry.set_grn1(true);
857        assert_eq!(entry.grn1(), true);
858        assert_eq!(entry.0 & 0b0000010000000000, 0b0000010000000000);
859
860        entry.set_red1(true);
861        assert_eq!(entry.red1(), true);
862        assert_eq!(entry.0 & 0b0000001000000000, 0b0000001000000000);
863
864        entry.set_output_enable(true);
865        assert_eq!(entry.output_enable(), true);
866        assert_eq!(entry.0 & 0b0000000100000000, 0b0000000100000000);
867
868        entry.set_dummy1(true);
869        assert_eq!(entry.dummy1(), true);
870        assert_eq!(entry.0 & 0b0000000010000000, 0b0000000010000000);
871
872        entry.set_dummy0(true);
873        assert_eq!(entry.dummy0(), true);
874        assert_eq!(entry.0 & 0b0000000001000000, 0b0000000001000000);
875
876        entry.set_latch(true);
877        assert_eq!(entry.latch(), true);
878        assert_eq!(entry.0 & 0b0000000000100000, 0b0000000000100000);
879
880        entry.set_addr(0b11111);
881        assert_eq!(entry.addr(), 0b11111);
882        assert_eq!(entry.0 & 0b0000000000011111, 0b0000000000011111);
883    }
884
885    #[test]
886    fn test_entry_bit_isolation() {
887        let mut entry = Entry::new();
888
889        // Test that setting one field doesn't affect others
890        entry.set_addr(0b11111);
891        entry.set_latch(true);
892        assert_eq!(entry.addr(), 0b11111);
893        assert_eq!(entry.latch(), true);
894        assert_eq!(entry.output_enable(), false);
895        assert_eq!(entry.red1(), false);
896
897        entry.set_red1(true);
898        entry.set_grn2(true);
899        assert_eq!(entry.addr(), 0b11111);
900        assert_eq!(entry.latch(), true);
901        assert_eq!(entry.red1(), true);
902        assert_eq!(entry.grn2(), true);
903        assert_eq!(entry.blu1(), false);
904        assert_eq!(entry.red2(), false);
905    }
906
907    #[test]
908    fn test_entry_set_color0() {
909        let mut entry = Entry::new();
910
911        let bits = (u8::from(true) << 2) | (u8::from(false) << 1) | u8::from(true); // b=1, g=0, r=1 = 0b101
912        entry.set_color0_bits(bits);
913        assert_eq!(entry.red1(), true);
914        assert_eq!(entry.grn1(), false);
915        assert_eq!(entry.blu1(), true);
916        // Check that only the expected bits are set
917        assert_eq!(entry.0 & 0b0000101000000000, 0b0000101000000000); // Red1 and Blue1 bits
918    }
919
920    #[test]
921    fn test_entry_set_color1() {
922        let mut entry = Entry::new();
923
924        let bits = (u8::from(true) << 2) | (u8::from(true) << 1) | u8::from(false); // b=1, g=1, r=0 = 0b110
925        entry.set_color1_bits(bits);
926        assert_eq!(entry.red2(), false);
927        assert_eq!(entry.grn2(), true);
928        assert_eq!(entry.blu2(), true);
929        // Check that only the expected bits are set
930        assert_eq!(entry.0 & 0b0110000000000000, 0b0110000000000000); // Green2 and Blue2 bits
931    }
932
933    #[test]
934    fn test_entry_debug_formatting() {
935        let entry = Entry(0x1234);
936        let debug_str = format!("{:?}", entry);
937        assert_eq!(debug_str, "Entry(0x1234)");
938
939        let entry = Entry(0xabcd);
940        let debug_str = format!("{:?}", entry);
941        assert_eq!(debug_str, "Entry(0xabcd)");
942    }
943
944    #[test]
945    fn test_row_construction() {
946        let row: Row<TEST_COLS> = Row::new();
947        assert_eq!(row.data.len(), TEST_COLS);
948
949        // Check that all entries are initialized to zero
950        for entry in &row.data {
951            assert_eq!(entry.0, 0);
952        }
953    }
954
955    #[test]
956    fn test_row_format() {
957        let mut row: Row<TEST_COLS> = Row::new();
958        let test_addr = 5;
959        let prev_addr = 4;
960
961        row.format(test_addr, prev_addr);
962
963        // Check data entries configuration
964        for (physical_i, entry) in row.data.iter().enumerate() {
965            let logical_i = get_mapped_index(physical_i);
966
967            match logical_i {
968                i if i == TEST_COLS - BLANKING_DELAY - 1 => {
969                    // Second to last pixel should have output_enable false
970                    assert_eq!(entry.output_enable(), false);
971                    assert_eq!(entry.addr(), prev_addr as u16);
972                    assert_eq!(entry.latch(), false);
973                }
974                i if i == TEST_COLS - 1 => {
975                    // Last pixel should have latch true and new address
976                    assert_eq!(entry.latch(), true);
977                    assert_eq!(entry.addr(), test_addr as u16);
978                    assert_eq!(entry.output_enable(), false);
979                }
980                1 => {
981                    // First pixel after start should have output_enable true
982                    assert_eq!(entry.output_enable(), true);
983                    assert_eq!(entry.addr(), prev_addr as u16);
984                    assert_eq!(entry.latch(), false);
985                }
986                _ => {
987                    // Other pixels should have the previous address and no latch
988                    assert_eq!(entry.addr(), prev_addr as u16);
989                    assert_eq!(entry.latch(), false);
990                    if logical_i > 1 && logical_i < TEST_COLS - BLANKING_DELAY - 1 {
991                        assert_eq!(entry.output_enable(), true);
992                    }
993                }
994            }
995        }
996    }
997
998    #[test]
999    fn test_row_set_color0() {
1000        let mut row: Row<TEST_COLS> = Row::new();
1001
1002        row.set_color0(0, true, false, true);
1003
1004        let mapped_col_0 = get_mapped_index(0);
1005        assert_eq!(row.data[mapped_col_0].red1(), true);
1006        assert_eq!(row.data[mapped_col_0].grn1(), false);
1007        assert_eq!(row.data[mapped_col_0].blu1(), true);
1008
1009        // Test another column
1010        row.set_color0(1, false, true, false);
1011
1012        let mapped_col_1 = get_mapped_index(1);
1013        assert_eq!(row.data[mapped_col_1].red1(), false);
1014        assert_eq!(row.data[mapped_col_1].grn1(), true);
1015        assert_eq!(row.data[mapped_col_1].blu1(), false);
1016    }
1017
1018    #[test]
1019    fn test_row_set_color1() {
1020        let mut row: Row<TEST_COLS> = Row::new();
1021
1022        row.set_color1(0, true, true, false);
1023
1024        let mapped_col_0 = get_mapped_index(0);
1025        assert_eq!(row.data[mapped_col_0].red2(), true);
1026        assert_eq!(row.data[mapped_col_0].grn2(), true);
1027        assert_eq!(row.data[mapped_col_0].blu2(), false);
1028    }
1029
1030    #[test]
1031    fn test_row_default() {
1032        let row1: Row<TEST_COLS> = Row::new();
1033        let row2: Row<TEST_COLS> = Row::default();
1034
1035        // Both should be equivalent
1036        assert_eq!(row1, row2);
1037        assert_eq!(row1.data.len(), row2.data.len());
1038
1039        // Check that all entries are initialized to zero
1040        for (entry1, entry2) in row1.data.iter().zip(row2.data.iter()) {
1041            assert_eq!(entry1.0, entry2.0);
1042            assert_eq!(entry1.0, 0);
1043        }
1044    }
1045
1046    #[test]
1047    fn test_frame_construction() {
1048        let frame: Frame<TEST_ROWS, TEST_COLS, TEST_NROWS> = Frame::new();
1049        assert_eq!(frame.rows.len(), TEST_NROWS);
1050    }
1051
1052    #[test]
1053    fn test_frame_format() {
1054        let mut frame: Frame<TEST_ROWS, TEST_COLS, TEST_NROWS> = Frame::new();
1055
1056        frame.format();
1057
1058        // Check that each row was formatted with correct address parameters
1059        for addr in 0..TEST_NROWS {
1060            let prev_addr = if addr == 0 { TEST_NROWS - 1 } else { addr - 1 };
1061
1062            // Check some key pixels in each row
1063            let row = &frame.rows[addr];
1064
1065            // Check last pixel has correct new address
1066            let last_pixel_idx = get_mapped_index(TEST_COLS - 1);
1067            assert_eq!(row.data[last_pixel_idx].addr(), addr as u16);
1068            assert_eq!(row.data[last_pixel_idx].latch(), true);
1069
1070            // Check non-last pixels have previous address
1071            let first_pixel_idx = get_mapped_index(0);
1072            assert_eq!(row.data[first_pixel_idx].addr(), prev_addr as u16);
1073            assert_eq!(row.data[first_pixel_idx].latch(), false);
1074        }
1075    }
1076
1077    #[test]
1078    fn test_frame_set_pixel() {
1079        let mut frame: Frame<TEST_ROWS, TEST_COLS, TEST_NROWS> = Frame::new();
1080
1081        // Test setting pixel in upper half (y < NROWS)
1082        frame.set_pixel(5, 10, true, false, true);
1083
1084        let mapped_col_10 = get_mapped_index(10);
1085        assert_eq!(frame.rows[5].data[mapped_col_10].red1(), true);
1086        assert_eq!(frame.rows[5].data[mapped_col_10].grn1(), false);
1087        assert_eq!(frame.rows[5].data[mapped_col_10].blu1(), true);
1088
1089        // Test setting pixel in lower half (y >= NROWS)
1090        frame.set_pixel(TEST_NROWS + 5, 15, false, true, false);
1091
1092        let mapped_col_15 = get_mapped_index(15);
1093        assert_eq!(frame.rows[5].data[mapped_col_15].red2(), false);
1094        assert_eq!(frame.rows[5].data[mapped_col_15].grn2(), true);
1095        assert_eq!(frame.rows[5].data[mapped_col_15].blu2(), false);
1096    }
1097
1098    #[test]
1099    fn test_frame_default() {
1100        let frame1: Frame<TEST_ROWS, TEST_COLS, TEST_NROWS> = Frame::new();
1101        let frame2: Frame<TEST_ROWS, TEST_COLS, TEST_NROWS> = Frame::default();
1102
1103        // Both should be equivalent
1104        assert_eq!(frame1.rows.len(), frame2.rows.len());
1105
1106        // Check that all rows are equivalent
1107        for (row1, row2) in frame1.rows.iter().zip(frame2.rows.iter()) {
1108            assert_eq!(row1, row2);
1109
1110            // Verify all entries are zero-initialized
1111            for (entry1, entry2) in row1.data.iter().zip(row2.data.iter()) {
1112                assert_eq!(entry1.0, entry2.0);
1113                assert_eq!(entry1.0, 0);
1114            }
1115        }
1116    }
1117
1118    #[test]
1119    fn test_dma_framebuffer_construction() {
1120        let fb = TestFrameBuffer::new();
1121        assert_eq!(fb.frames.len(), TEST_FRAME_COUNT);
1122        assert_eq!(fb._align, 0);
1123    }
1124
1125    #[test]
1126    #[cfg(feature = "esp-hal-dma")]
1127    fn test_dma_framebuffer_dma_buffer_size() {
1128        let expected_size =
1129            core::mem::size_of::<[Frame<TEST_ROWS, TEST_COLS, TEST_NROWS>; TEST_FRAME_COUNT]>();
1130        assert_eq!(TestFrameBuffer::dma_buffer_size_bytes(), expected_size);
1131    }
1132
1133    #[test]
1134    fn test_dma_framebuffer_erase() {
1135        let fb = TestFrameBuffer::new();
1136
1137        // After erasing, all frames should be formatted
1138        for frame in &fb.frames {
1139            for addr in 0..TEST_NROWS {
1140                let prev_addr = if addr == 0 { TEST_NROWS - 1 } else { addr - 1 };
1141
1142                // Check some key pixels in each row
1143                let row = &frame.rows[addr];
1144
1145                // Check last pixel has correct new address
1146                let last_pixel_idx = get_mapped_index(TEST_COLS - 1);
1147                assert_eq!(row.data[last_pixel_idx].addr(), addr as u16);
1148                assert_eq!(row.data[last_pixel_idx].latch(), true);
1149
1150                // Check non-last pixels have previous address
1151                let first_pixel_idx = get_mapped_index(0);
1152                assert_eq!(row.data[first_pixel_idx].addr(), prev_addr as u16);
1153                assert_eq!(row.data[first_pixel_idx].latch(), false);
1154            }
1155        }
1156    }
1157
1158    #[test]
1159    fn test_dma_framebuffer_set_pixel_bounds() {
1160        let mut fb = TestFrameBuffer::new();
1161
1162        // Test negative coordinates
1163        fb.set_pixel(Point::new(-1, 5), Color::RED);
1164        fb.set_pixel(Point::new(5, -1), Color::RED);
1165
1166        // Test coordinates out of bounds (should not panic)
1167        fb.set_pixel(Point::new(TEST_COLS as i32, 5), Color::RED);
1168        fb.set_pixel(Point::new(5, TEST_ROWS as i32), Color::RED);
1169    }
1170
1171    #[test]
1172    fn test_dma_framebuffer_set_pixel_internal() {
1173        let mut fb = TestFrameBuffer::new();
1174
1175        let red_color = Color::RED;
1176        fb.set_pixel_internal(10, 5, red_color);
1177
1178        // With 3-bit depth, brightness steps are 32 (256/8)
1179        // Frames represent thresholds: 32, 64, 96, 128, 160, 192, 224
1180        // Red value 255 should activate all frames
1181        for frame in &fb.frames {
1182            // Check upper half pixel
1183            let mapped_col_10 = get_mapped_index(10);
1184            assert_eq!(frame.rows[5].data[mapped_col_10].red1(), true);
1185            assert_eq!(frame.rows[5].data[mapped_col_10].grn1(), false);
1186            assert_eq!(frame.rows[5].data[mapped_col_10].blu1(), false);
1187        }
1188    }
1189
1190    #[test]
1191    fn test_dma_framebuffer_brightness_modulation() {
1192        let mut fb = TestFrameBuffer::new();
1193
1194        // Test with a medium brightness value
1195        let brightness_step = 1 << (8 - TEST_BITS); // 32 for 3-bit
1196        let test_brightness = brightness_step * 3; // 96
1197        let color = Color::from(embedded_graphics::pixelcolor::Rgb888::new(
1198            test_brightness,
1199            0,
1200            0,
1201        ));
1202
1203        fb.set_pixel_internal(0, 0, color);
1204
1205        // Should activate frames 0, 1, 2 (thresholds 32, 64, 96)
1206        // but not frames 3, 4, 5, 6 (thresholds 128, 160, 192, 224)
1207        for (frame_idx, frame) in fb.frames.iter().enumerate() {
1208            let frame_threshold = (frame_idx as u8 + 1) * brightness_step;
1209            let should_be_active = test_brightness >= frame_threshold;
1210
1211            let mapped_col_0 = get_mapped_index(0);
1212            assert_eq!(frame.rows[0].data[mapped_col_0].red1(), should_be_active);
1213        }
1214    }
1215
1216    #[test]
1217    fn test_origin_dimensions() {
1218        let fb = TestFrameBuffer::new();
1219        let size = fb.size();
1220        assert_eq!(size.width, TEST_COLS as u32);
1221        assert_eq!(size.height, TEST_ROWS as u32);
1222
1223        // Test reference
1224        let size = (&fb).size();
1225        assert_eq!(size.width, TEST_COLS as u32);
1226        assert_eq!(size.height, TEST_ROWS as u32);
1227
1228        // Test mutable reference
1229        let mut fb = TestFrameBuffer::new();
1230        let fb_ref = &mut fb;
1231        let size = fb_ref.size();
1232        assert_eq!(size.width, TEST_COLS as u32);
1233        assert_eq!(size.height, TEST_ROWS as u32);
1234    }
1235
1236    #[test]
1237    fn test_draw_target() {
1238        let mut fb = TestFrameBuffer::new();
1239
1240        let pixels = vec![
1241            embedded_graphics::Pixel(Point::new(0, 0), Color::RED),
1242            embedded_graphics::Pixel(Point::new(1, 1), Color::GREEN),
1243            embedded_graphics::Pixel(Point::new(2, 2), Color::BLUE),
1244        ];
1245
1246        let result = fb.draw_iter(pixels);
1247        assert!(result.is_ok());
1248
1249        // Test mutable reference
1250        let result = (&mut fb).draw_iter(vec![embedded_graphics::Pixel(
1251            Point::new(3, 3),
1252            Color::WHITE,
1253        )]);
1254        assert!(result.is_ok());
1255    }
1256
1257    #[test]
1258    fn test_draw_iter_pixel_verification() {
1259        let mut fb = TestFrameBuffer::new();
1260
1261        // Create test pixels with specific colors and positions
1262        let pixels = vec![
1263            // Upper half pixels (y < NROWS) - should set color0
1264            embedded_graphics::Pixel(Point::new(5, 2), Color::RED), // (5, 2) -> red
1265            embedded_graphics::Pixel(Point::new(10, 5), Color::GREEN), // (10, 5) -> green
1266            embedded_graphics::Pixel(Point::new(15, 8), Color::BLUE), // (15, 8) -> blue
1267            embedded_graphics::Pixel(Point::new(20, 10), Color::WHITE), // (20, 10) -> white
1268            // Lower half pixels (y >= NROWS) - should set color1
1269            embedded_graphics::Pixel(Point::new(25, (TEST_NROWS + 3) as i32), Color::RED), // (25, 19) -> red
1270            embedded_graphics::Pixel(Point::new(30, (TEST_NROWS + 7) as i32), Color::GREEN), // (30, 23) -> green
1271            embedded_graphics::Pixel(Point::new(35, (TEST_NROWS + 12) as i32), Color::BLUE), // (35, 28) -> blue
1272            // Edge case: black pixel (should not be visible in first frame)
1273            embedded_graphics::Pixel(Point::new(40, 1), Color::BLACK), // (40, 1) -> black
1274        ];
1275
1276        let result = fb.draw_iter(pixels);
1277        assert!(result.is_ok());
1278
1279        // Check the first frame only
1280        let first_frame = &fb.frames[0];
1281        let brightness_step = 1 << (8 - TEST_BITS); // 32 for 3-bit
1282        let first_frame_threshold = brightness_step; // 32
1283
1284        // Test upper half pixels (color0)
1285        // Red pixel at (5, 2) - should be red in first frame
1286        let col_idx = get_mapped_index(5);
1287        assert_eq!(
1288            first_frame.rows[2].data[col_idx].red1(),
1289            Color::RED.r() >= first_frame_threshold
1290        );
1291        assert_eq!(
1292            first_frame.rows[2].data[col_idx].grn1(),
1293            Color::RED.g() >= first_frame_threshold
1294        );
1295        assert_eq!(
1296            first_frame.rows[2].data[col_idx].blu1(),
1297            Color::RED.b() >= first_frame_threshold
1298        );
1299
1300        // Green pixel at (10, 5) - should be green in first frame
1301        let col_idx = get_mapped_index(10);
1302        assert_eq!(
1303            first_frame.rows[5].data[col_idx].red1(),
1304            Color::GREEN.r() >= first_frame_threshold
1305        );
1306        assert_eq!(
1307            first_frame.rows[5].data[col_idx].grn1(),
1308            Color::GREEN.g() >= first_frame_threshold
1309        );
1310        assert_eq!(
1311            first_frame.rows[5].data[col_idx].blu1(),
1312            Color::GREEN.b() >= first_frame_threshold
1313        );
1314
1315        // Blue pixel at (15, 8) - should be blue in first frame
1316        let col_idx = get_mapped_index(15);
1317        assert_eq!(
1318            first_frame.rows[8].data[col_idx].red1(),
1319            Color::BLUE.r() >= first_frame_threshold
1320        );
1321        assert_eq!(
1322            first_frame.rows[8].data[col_idx].grn1(),
1323            Color::BLUE.g() >= first_frame_threshold
1324        );
1325        assert_eq!(
1326            first_frame.rows[8].data[col_idx].blu1(),
1327            Color::BLUE.b() >= first_frame_threshold
1328        );
1329
1330        // White pixel at (20, 10) - should be white in first frame
1331        let col_idx = get_mapped_index(20);
1332        assert_eq!(
1333            first_frame.rows[10].data[col_idx].red1(),
1334            Color::WHITE.r() >= first_frame_threshold
1335        );
1336        assert_eq!(
1337            first_frame.rows[10].data[col_idx].grn1(),
1338            Color::WHITE.g() >= first_frame_threshold
1339        );
1340        assert_eq!(
1341            first_frame.rows[10].data[col_idx].blu1(),
1342            Color::WHITE.b() >= first_frame_threshold
1343        );
1344
1345        // Test lower half pixels (color1)
1346        // Red pixel at (25, TEST_NROWS + 3) -> row 3, color1
1347        let col_idx = get_mapped_index(25);
1348        assert_eq!(
1349            first_frame.rows[3].data[col_idx].red2(),
1350            Color::RED.r() >= first_frame_threshold
1351        );
1352        assert_eq!(
1353            first_frame.rows[3].data[col_idx].grn2(),
1354            Color::RED.g() >= first_frame_threshold
1355        );
1356        assert_eq!(
1357            first_frame.rows[3].data[col_idx].blu2(),
1358            Color::RED.b() >= first_frame_threshold
1359        );
1360
1361        // Green pixel at (30, TEST_NROWS + 7) -> row 7, color1
1362        let col_idx = get_mapped_index(30);
1363        assert_eq!(
1364            first_frame.rows[7].data[col_idx].red2(),
1365            Color::GREEN.r() >= first_frame_threshold
1366        );
1367        assert_eq!(
1368            first_frame.rows[7].data[col_idx].grn2(),
1369            Color::GREEN.g() >= first_frame_threshold
1370        );
1371        assert_eq!(
1372            first_frame.rows[7].data[col_idx].blu2(),
1373            Color::GREEN.b() >= first_frame_threshold
1374        );
1375
1376        // Blue pixel at (35, TEST_NROWS + 12) -> row 12, color1
1377        let col_idx = get_mapped_index(35);
1378        assert_eq!(
1379            first_frame.rows[12].data[col_idx].red2(),
1380            Color::BLUE.r() >= first_frame_threshold
1381        );
1382        assert_eq!(
1383            first_frame.rows[12].data[col_idx].grn2(),
1384            Color::BLUE.g() >= first_frame_threshold
1385        );
1386        assert_eq!(
1387            first_frame.rows[12].data[col_idx].blu2(),
1388            Color::BLUE.b() >= first_frame_threshold
1389        );
1390
1391        // Test black pixel - should not be visible in any frame
1392        let col_idx = get_mapped_index(40);
1393        assert_eq!(first_frame.rows[1].data[col_idx].red1(), false);
1394        assert_eq!(first_frame.rows[1].data[col_idx].grn1(), false);
1395        assert_eq!(first_frame.rows[1].data[col_idx].blu1(), false);
1396    }
1397
1398    #[test]
1399    fn test_embedded_graphics_integration() {
1400        let mut fb = TestFrameBuffer::new();
1401
1402        // Draw a rectangle
1403        let result = Rectangle::new(Point::new(5, 5), Size::new(10, 8))
1404            .into_styled(PrimitiveStyle::with_fill(Color::RED))
1405            .draw(&mut fb);
1406        assert!(result.is_ok());
1407
1408        // Draw a circle
1409        let result = Circle::new(Point::new(30, 15), 8)
1410            .into_styled(PrimitiveStyle::with_fill(Color::BLUE))
1411            .draw(&mut fb);
1412        assert!(result.is_ok());
1413    }
1414
1415    #[test]
1416    #[cfg(feature = "skip-black-pixels")]
1417    fn test_skip_black_pixels_enabled() {
1418        let mut fb = TestFrameBuffer::new();
1419
1420        // Set a red pixel first
1421        fb.set_pixel_internal(10, 5, Color::RED);
1422
1423        // Verify it's red in the first frame
1424        let mapped_col_10 = get_mapped_index(10);
1425        assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].red1(), true);
1426        assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].grn1(), false);
1427        assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].blu1(), false);
1428
1429        // Now set it to black - with skip-black-pixels enabled, this should be ignored
1430        fb.set_pixel_internal(10, 5, Color::BLACK);
1431
1432        // The pixel should still be red (black write was skipped)
1433        assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].red1(), true);
1434        assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].grn1(), false);
1435        assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].blu1(), false);
1436    }
1437
1438    #[test]
1439    #[cfg(not(feature = "skip-black-pixels"))]
1440    fn test_skip_black_pixels_disabled() {
1441        let mut fb = TestFrameBuffer::new();
1442
1443        // Set a red pixel first
1444        fb.set_pixel_internal(10, 5, Color::RED);
1445
1446        // Verify it's red in the first frame
1447        let mapped_col_10 = get_mapped_index(10);
1448        assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].red1(), true);
1449        assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].grn1(), false);
1450        assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].blu1(), false);
1451
1452        // Now set it to black - with skip-black-pixels disabled, this should overwrite
1453        fb.set_pixel_internal(10, 5, Color::BLACK);
1454
1455        // The pixel should now be black (all bits false)
1456        assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].red1(), false);
1457        assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].grn1(), false);
1458        assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].blu1(), false);
1459    }
1460
1461    #[test]
1462    fn test_bcm_frame_overwrite() {
1463        let mut fb = TestFrameBuffer::new();
1464
1465        // First write a white pixel (255, 255, 255)
1466        fb.set_pixel_internal(10, 5, Color::WHITE);
1467
1468        let mapped_col_10 = get_mapped_index(10);
1469
1470        // Verify white pixel is lit in all frames (255 >= all thresholds)
1471        for frame in fb.frames.iter() {
1472            // White (255) should be active in all frames since it's >= all thresholds
1473            assert_eq!(frame.rows[5].data[mapped_col_10].red1(), true);
1474            assert_eq!(frame.rows[5].data[mapped_col_10].grn1(), true);
1475            assert_eq!(frame.rows[5].data[mapped_col_10].blu1(), true);
1476        }
1477
1478        // Now overwrite with 50% white (128, 128, 128)
1479        let half_white = Color::from(embedded_graphics::pixelcolor::Rgb888::new(128, 128, 128));
1480        fb.set_pixel_internal(10, 5, half_white);
1481
1482        // Verify only the correct frames are lit for 50% white
1483        // With 3-bit depth: thresholds are 32, 64, 96, 128, 160, 192, 224
1484        // 128 should activate frames 0, 1, 2, 3 (thresholds 32, 64, 96, 128)
1485        // but not frames 4, 5, 6 (thresholds 160, 192, 224)
1486        let brightness_step = 1 << (8 - TEST_BITS); // 32 for 3-bit
1487        for (frame_idx, frame) in fb.frames.iter().enumerate() {
1488            let frame_threshold = (frame_idx as u8 + 1) * brightness_step;
1489            let should_be_active = 128 >= frame_threshold;
1490
1491            assert_eq!(frame.rows[5].data[mapped_col_10].red1(), should_be_active);
1492            assert_eq!(frame.rows[5].data[mapped_col_10].grn1(), should_be_active);
1493            assert_eq!(frame.rows[5].data[mapped_col_10].blu1(), should_be_active);
1494        }
1495
1496        // Specifically verify the expected pattern for 3-bit depth
1497        // Frames 0-3 should be active (thresholds 32, 64, 96, 128)
1498        for frame_idx in 0..4 {
1499            assert_eq!(
1500                fb.frames[frame_idx].rows[5].data[mapped_col_10].red1(),
1501                true
1502            );
1503        }
1504        // Frames 4-6 should be inactive (thresholds 160, 192, 224)
1505        for frame_idx in 4..TEST_FRAME_COUNT {
1506            assert_eq!(
1507                fb.frames[frame_idx].rows[5].data[mapped_col_10].red1(),
1508                false
1509            );
1510        }
1511    }
1512
1513    #[test]
1514    fn test_read_buffer_implementation() {
1515        // Test owned implementation - explicitly move the framebuffer to ensure we're testing the owned impl
1516        let fb = TestFrameBuffer::new();
1517        let expected_size = core::mem::size_of_val(&fb.frames);
1518
1519        // Test owned ReadBuffer implementation by calling ReadBuffer::read_buffer explicitly
1520        unsafe {
1521            let (ptr, len) = <TestFrameBuffer as ReadBuffer>::read_buffer(&fb);
1522            assert!(!ptr.is_null());
1523            assert_eq!(len, expected_size);
1524        }
1525
1526        // Test direct method call on owned value
1527        unsafe {
1528            let (ptr, len) = fb.read_buffer();
1529            assert!(!ptr.is_null());
1530            assert_eq!(len, expected_size);
1531        }
1532
1533        // Test reference implementation
1534        let fb = TestFrameBuffer::new();
1535        let fb_ref = &fb;
1536        unsafe {
1537            let (ptr, len) = fb_ref.read_buffer();
1538            assert!(!ptr.is_null());
1539            assert_eq!(len, core::mem::size_of_val(&fb.frames));
1540        }
1541
1542        // Test mutable reference implementation
1543        let mut fb = TestFrameBuffer::new();
1544        let fb_ref = &mut fb;
1545        unsafe {
1546            let (ptr, len) = fb_ref.read_buffer();
1547            assert!(!ptr.is_null());
1548            assert_eq!(len, core::mem::size_of_val(&fb.frames));
1549        }
1550    }
1551
1552    #[test]
1553    fn test_read_buffer_owned_implementation() {
1554        // This test specifically ensures the owned ReadBuffer implementation is tested
1555        // by consuming the framebuffer and testing the pointer validity
1556        fn test_owned_read_buffer(fb: TestFrameBuffer) -> (bool, usize) {
1557            unsafe {
1558                let (ptr, len) = fb.read_buffer();
1559                (!ptr.is_null(), len)
1560            }
1561        }
1562
1563        let fb = TestFrameBuffer::new();
1564        let expected_len = core::mem::size_of_val(&fb.frames);
1565
1566        let (ptr_valid, actual_len) = test_owned_read_buffer(fb);
1567        assert!(ptr_valid);
1568        assert_eq!(actual_len, expected_len);
1569    }
1570
1571    #[test]
1572    fn test_framebuffer_trait() {
1573        let fb = TestFrameBuffer::new();
1574        assert_eq!(fb.get_word_size(), WordSize::Sixteen);
1575
1576        let fb_ref = &fb;
1577        assert_eq!(fb_ref.get_word_size(), WordSize::Sixteen);
1578
1579        let mut fb = TestFrameBuffer::new();
1580        let fb_ref = &mut fb;
1581        assert_eq!(fb_ref.get_word_size(), WordSize::Sixteen);
1582    }
1583
1584    #[test]
1585    fn test_debug_formatting() {
1586        let fb = TestFrameBuffer::new();
1587        let debug_string = format!("{:?}", fb);
1588        assert!(debug_string.contains("DmaFrameBuffer"));
1589        assert!(debug_string.contains("frame_count"));
1590        assert!(debug_string.contains("frame_size"));
1591        assert!(debug_string.contains("brightness_step"));
1592    }
1593
1594    #[test]
1595    fn test_default_implementation() {
1596        let fb1 = TestFrameBuffer::new();
1597        let fb2 = TestFrameBuffer::default();
1598
1599        // Both should be equivalent in size, but may differ in content since new() calls format()
1600        assert_eq!(fb1.frames.len(), fb2.frames.len());
1601        assert_eq!(fb1._align, fb2._align);
1602    }
1603
1604    #[test]
1605    fn test_memory_alignment() {
1606        let fb = TestFrameBuffer::new();
1607        let ptr = &fb as *const _ as usize;
1608
1609        // Should be aligned to 64-bit boundary due to the _align field
1610        assert_eq!(ptr % 8, 0);
1611    }
1612
1613    #[test]
1614    fn test_color_values() {
1615        let mut fb = TestFrameBuffer::new();
1616
1617        // Test different color values
1618        let colors = [
1619            (Color::RED, (255, 0, 0)),
1620            (Color::GREEN, (0, 255, 0)),
1621            (Color::BLUE, (0, 0, 255)),
1622            (Color::WHITE, (255, 255, 255)),
1623            (Color::BLACK, (0, 0, 0)),
1624        ];
1625
1626        for (i, (color, (r, g, b))) in colors.iter().enumerate() {
1627            fb.set_pixel(Point::new(i as i32, 0), *color);
1628            assert_eq!(color.r(), *r);
1629            assert_eq!(color.g(), *g);
1630            assert_eq!(color.b(), *b);
1631        }
1632    }
1633
1634    #[test]
1635    fn test_blanking_delay() {
1636        let mut row: Row<TEST_COLS> = Row::new();
1637        let test_addr = 5;
1638        let prev_addr = 4;
1639
1640        row.format(test_addr, prev_addr);
1641
1642        // Test that the blanking delay is respected
1643        let blanking_pixel_idx = get_mapped_index(TEST_COLS - BLANKING_DELAY - 1);
1644        assert_eq!(row.data[blanking_pixel_idx].output_enable(), false);
1645
1646        // Test that pixels before blanking delay have output enabled (if after pixel 1)
1647        let before_blanking_idx = get_mapped_index(TEST_COLS - BLANKING_DELAY - 2);
1648        assert_eq!(row.data[before_blanking_idx].output_enable(), true);
1649    }
1650
1651    #[test]
1652    fn test_esp32_mapping() {
1653        // Test the ESP32-specific index mapping
1654        #[cfg(feature = "esp32-ordering")]
1655        {
1656            assert_eq!(map_index(0), 1);
1657            assert_eq!(map_index(1), 0);
1658            assert_eq!(map_index(2), 3);
1659            assert_eq!(map_index(3), 2);
1660            assert_eq!(map_index(4), 5);
1661            assert_eq!(map_index(5), 4);
1662        }
1663        #[cfg(not(feature = "esp32-ordering"))]
1664        {
1665            assert_eq!(map_index(0), 0);
1666            assert_eq!(map_index(1), 1);
1667            assert_eq!(map_index(2), 2);
1668            assert_eq!(map_index(3), 3);
1669        }
1670    }
1671
1672    #[test]
1673    fn test_bits_assertion() {
1674        // Test that BITS <= 8 assertion is enforced at compile time
1675        // This test mainly documents the constraint
1676        assert!(TEST_BITS <= 8);
1677    }
1678
1679    #[test]
1680    fn test_fast_clear_method() {
1681        let mut fb = TestFrameBuffer::new();
1682
1683        // Set some pixels
1684        fb.set_pixel_internal(10, 5, Color::RED);
1685        fb.set_pixel_internal(20, 10, Color::GREEN);
1686
1687        // Verify pixels are set
1688        let mapped_col_10 = get_mapped_index(10);
1689        assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].red1(), true);
1690
1691        // Clear using fast method
1692        fb.erase();
1693
1694        // Verify pixels are cleared but timing signals remain
1695        assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].red1(), false);
1696        assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].grn1(), false);
1697        assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].blu1(), false);
1698
1699        // Verify timing signals are still present (check last pixel has latch)
1700        let last_col = get_mapped_index(TEST_COLS - 1);
1701        assert_eq!(fb.frames[0].rows[5].data[last_col].latch(), true);
1702    }
1703
1704    // Remove the old `test_draw_char_bottom_right` and replace with a helper + combined test.
1705
1706    // Constants for FONT_6X10 glyph size
1707    const CHAR_W: i32 = 6;
1708    const CHAR_H: i32 = 10;
1709
1710    /// Draws glyph 'A' at `origin` and validates framebuffer pixels against a reference.
1711    fn verify_glyph_at(fb: &mut TestFrameBuffer, origin: Point) {
1712        use embedded_graphics::mock_display::MockDisplay;
1713        use embedded_graphics::mono_font::ascii::FONT_6X10;
1714        use embedded_graphics::mono_font::MonoTextStyle;
1715        use embedded_graphics::text::{Baseline, Text};
1716
1717        // Draw the glyph
1718        let style = MonoTextStyle::new(&FONT_6X10, Color::WHITE);
1719        Text::with_baseline("A", origin, style, Baseline::Top)
1720            .draw(fb)
1721            .unwrap();
1722
1723        // Reference glyph at (0,0)
1724        let mut reference: MockDisplay<Color> = MockDisplay::new();
1725        Text::with_baseline("A", Point::zero(), style, Baseline::Top)
1726            .draw(&mut reference)
1727            .unwrap();
1728
1729        for dy in 0..CHAR_H {
1730            for dx in 0..CHAR_W {
1731                let expected_on = reference
1732                    .get_pixel(Point::new(dx, dy))
1733                    .unwrap_or(Color::BLACK)
1734                    != Color::BLACK;
1735
1736                let gx = (origin.x + dx) as usize;
1737                let gy = (origin.y + dy) as usize;
1738
1739                // we have computed the origin to be within the panel, so we don't need to check for bounds
1740                // if gx >= TEST_COLS || gy >= TEST_ROWS {
1741                //     continue;
1742                // }
1743
1744                // Fetch Entry from frame 0
1745                let frame0 = &fb.frames[0];
1746                let e = if gy < TEST_NROWS {
1747                    &frame0.rows[gy].data[get_mapped_index(gx)]
1748                } else {
1749                    &frame0.rows[gy - TEST_NROWS].data[get_mapped_index(gx)]
1750                };
1751
1752                let (r, g, b) = if gy >= TEST_NROWS {
1753                    (e.red2(), e.grn2(), e.blu2())
1754                } else {
1755                    (e.red1(), e.grn1(), e.blu1())
1756                };
1757
1758                if expected_on {
1759                    assert!(r && g && b,);
1760                } else {
1761                    assert!(!r && !g && !b);
1762                }
1763            }
1764        }
1765    }
1766
1767    #[test]
1768    fn test_draw_char_corners() {
1769        let upper_left = Point::new(0, 0);
1770        let lower_right = Point::new(TEST_COLS as i32 - CHAR_W, TEST_ROWS as i32 - CHAR_H);
1771
1772        let mut fb = TestFrameBuffer::new();
1773
1774        verify_glyph_at(&mut fb, upper_left);
1775        verify_glyph_at(&mut fb, lower_right);
1776    }
1777
1778    #[test]
1779    fn test_framebuffer_operations_trait_erase() {
1780        let mut fb = TestFrameBuffer::new();
1781
1782        // Set pixels so erase has work to do
1783        fb.set_pixel_internal(10, 5, Color::RED);
1784        fb.set_pixel_internal(20, 10, Color::GREEN);
1785
1786        // Explicitly call trait method to hit the FrameBufferOperations impl
1787        <TestFrameBuffer as FrameBufferOperations<
1788            TEST_ROWS,
1789            TEST_COLS,
1790            TEST_NROWS,
1791            TEST_BITS,
1792            TEST_FRAME_COUNT,
1793        >>::erase(&mut fb);
1794
1795        // Verify colors cleared on frame 0
1796        let mc10 = get_mapped_index(10);
1797        let mc20 = get_mapped_index(20);
1798        assert_eq!(fb.frames[0].rows[5].data[mc10].red1(), false);
1799        assert_eq!(fb.frames[0].rows[10].data[mc20].grn1(), false);
1800
1801        // Timing signals preserved: last pixel should have latch
1802        let last_col = get_mapped_index(TEST_COLS - 1);
1803        assert!(fb.frames[0].rows[0].data[last_col].latch());
1804    }
1805
1806    #[test]
1807    fn test_framebuffer_operations_trait_set_pixel() {
1808        let mut fb = TestFrameBuffer::new();
1809
1810        // Explicitly call trait method to hit the FrameBufferOperations impl
1811        <TestFrameBuffer as FrameBufferOperations<
1812            TEST_ROWS,
1813            TEST_COLS,
1814            TEST_NROWS,
1815            TEST_BITS,
1816            TEST_FRAME_COUNT,
1817        >>::set_pixel(&mut fb, Point::new(8, 3), Color::BLUE);
1818
1819        let idx = get_mapped_index(8);
1820        assert_eq!(fb.frames[0].rows[3].data[idx].blu1(), true);
1821        assert_eq!(fb.frames[0].rows[3].data[idx].red1(), false);
1822        assert_eq!(fb.frames[0].rows[3].data[idx].grn1(), false);
1823    }
1824}