st7789/
batch.rs

1//! Original code from: https://github.com/lupyuen/piet-embedded/blob/master/piet-embedded-graphics/src/batch.rs
2//! Batch the pixels to be rendered into Pixel Rows and Pixel Blocks (contiguous Pixel Rows).
3//! This enables the pixels to be rendered efficiently as Pixel Blocks, which may be transmitted in a single Non-Blocking SPI request.
4use crate::{Error, ST7789};
5use display_interface::WriteOnlyDataCommand;
6use embedded_graphics_core::{
7    pixelcolor::{raw::RawU16, Rgb565},
8    prelude::*,
9};
10use embedded_hal::digital::v2::OutputPin;
11
12pub trait DrawBatch<DI, RST, BL, T, PinE>
13where
14    DI: WriteOnlyDataCommand,
15    RST: OutputPin<Error = PinE>,
16    BL: OutputPin<Error = PinE>,
17    T: IntoIterator<Item = Pixel<Rgb565>>,
18{
19    fn draw_batch(&mut self, item_pixels: T) -> Result<(), Error<PinE>>;
20}
21
22impl<DI, RST, BL, T, PinE> DrawBatch<DI, RST, BL, T, PinE> for ST7789<DI, RST, BL>
23where
24    DI: WriteOnlyDataCommand,
25    RST: OutputPin<Error = PinE>,
26    BL: OutputPin<Error = PinE>,
27    T: IntoIterator<Item = Pixel<Rgb565>>,
28{
29    fn draw_batch(&mut self, item_pixels: T) -> Result<(), Error<PinE>> {
30        //  Get the pixels for the item to be rendered.
31        let pixels = item_pixels.into_iter();
32        //  Batch the pixels into Pixel Rows.
33        let rows = to_rows(pixels);
34        //  Batch the Pixel Rows into Pixel Blocks.
35        let blocks = to_blocks(rows);
36        //  For each Pixel Block...
37        for PixelBlock {
38            x_left,
39            x_right,
40            y_top,
41            y_bottom,
42            colors,
43            ..
44        } in blocks
45        {
46            //  Render the Pixel Block.
47            self.set_pixels(x_left, y_top, x_right, y_bottom, colors)?;
48
49            //  Dump out the Pixel Blocks for the square in test_display()
50            /* if x_left >= 60 && x_left <= 150 && x_right >= 60 && x_right <= 150 && y_top >= 60 && y_top <= 150 && y_bottom >= 60 && y_bottom <= 150 {
51                console::print("pixel block ("); console::printint(x_left as i32); console::print(", "); console::printint(y_top as i32); ////
52                console::print("), ("); console::printint(x_right as i32); console::print(", "); console::printint(y_bottom as i32); console::print(")\n"); ////
53            } */
54        }
55        Ok(())
56    }
57}
58
59/// Max number of pixels per Pixel Row
60const MAX_ROW_SIZE: usize = 50;
61/// Max number of pixels per Pixel Block
62const MAX_BLOCK_SIZE: usize = 100;
63
64/// Consecutive color words for a Pixel Row
65type RowColors = heapless::Vec<u16, MAX_ROW_SIZE>;
66/// Consecutive color words for a Pixel Block
67type BlockColors = heapless::Vec<u16, MAX_BLOCK_SIZE>;
68
69/// Iterator for each Pixel Row in the pixel data. A Pixel Row consists of contiguous pixels on the same row.
70#[derive(Debug, Clone)]
71pub struct RowIterator<P: Iterator<Item = Pixel<Rgb565>>> {
72    /// Pixels to be batched into rows
73    pixels: P,
74    /// Start column number
75    x_left: u16,
76    /// End column number
77    x_right: u16,
78    /// Row number
79    y: u16,
80    /// List of pixel colours for the entire row
81    colors: RowColors,
82    /// True if this is the first pixel for the row
83    first_pixel: bool,
84}
85
86/// Iterator for each Pixel Block in the pixel data. A Pixel Block consists of contiguous Pixel Rows with the same start and end column number.
87#[derive(Debug, Clone)]
88pub struct BlockIterator<R: Iterator<Item = PixelRow>> {
89    /// Pixel Rows to be batched into blocks
90    rows: R,
91    /// Start column number
92    x_left: u16,
93    /// End column number
94    x_right: u16,
95    /// Start row number
96    y_top: u16,
97    /// End row number
98    y_bottom: u16,
99    /// List of pixel colours for the entire block, row by row
100    colors: BlockColors,
101    /// True if this is the first row for the block
102    first_row: bool,
103}
104
105/// A row of contiguous pixels
106pub struct PixelRow {
107    /// Start column number
108    pub x_left: u16,
109    /// End column number
110    pub x_right: u16,
111    /// Row number
112    pub y: u16,
113    /// List of pixel colours for the entire row
114    pub colors: RowColors,
115}
116
117/// A block of contiguous pixel rows with the same start and end column number
118pub struct PixelBlock {
119    /// Start column number
120    pub x_left: u16,
121    /// End column number
122    pub x_right: u16,
123    /// Start row number
124    pub y_top: u16,
125    /// End row number
126    pub y_bottom: u16,
127    /// List of pixel colours for the entire block, row by row
128    pub colors: BlockColors,
129}
130
131/// Batch the pixels into Pixel Rows, which are contiguous pixels on the same row.
132/// P can be any Pixel Iterator (e.g. a rectangle).
133fn to_rows<P>(pixels: P) -> RowIterator<P>
134where
135    P: Iterator<Item = Pixel<Rgb565>>,
136{
137    RowIterator::<P> {
138        pixels,
139        x_left: 0,
140        x_right: 0,
141        y: 0,
142        colors: RowColors::new(),
143        first_pixel: true,
144    }
145}
146
147/// Batch the Pixel Rows into Pixel Blocks, which are contiguous Pixel Rows with the same start and end column number
148/// R can be any Pixel Row Iterator.
149fn to_blocks<R>(rows: R) -> BlockIterator<R>
150where
151    R: Iterator<Item = PixelRow>,
152{
153    BlockIterator::<R> {
154        rows,
155        x_left: 0,
156        x_right: 0,
157        y_top: 0,
158        y_bottom: 0,
159        colors: BlockColors::new(),
160        first_row: true,
161    }
162}
163
164/// Implement the Iterator for Pixel Rows.
165/// P can be any Pixel Iterator (e.g. a rectangle).
166impl<P: Iterator<Item = Pixel<Rgb565>>> Iterator for RowIterator<P> {
167    /// This Iterator returns Pixel Rows
168    type Item = PixelRow;
169
170    /// Return the next Pixel Row of contiguous pixels on the same row
171    fn next(&mut self) -> Option<Self::Item> {
172        //  Loop over all pixels until we have composed a Pixel Row, or we have run out of pixels.
173        loop {
174            //  Get the next pixel.
175            let next_pixel = self.pixels.next();
176            match next_pixel {
177                None => {
178                    //  If no more pixels...
179                    if self.first_pixel {
180                        return None; //  No pixels to group
181                    }
182                    //  Else return previous pixels as row.
183                    let row = PixelRow {
184                        x_left: self.x_left,
185                        x_right: self.x_right,
186                        y: self.y,
187                        colors: self.colors.clone(),
188                    };
189                    self.colors.clear();
190                    self.first_pixel = true;
191                    return Some(row);
192                }
193                Some(Pixel(coord, color)) => {
194                    //  If there is a pixel...
195                    if coord.x < 0 || coord.y < 0 {
196                        continue; // if we do not clip this here, the library panics later on
197                    }
198                    let x = coord.x as u16;
199                    let y = coord.y as u16;
200                    let color = RawU16::from(color).into_inner();
201                    //  Save the first pixel as the row start and handle next pixel.
202                    if self.first_pixel {
203                        self.first_pixel = false;
204                        self.x_left = x;
205                        self.x_right = x;
206                        self.y = y;
207                        self.colors.clear();
208                        self.colors.push(color).expect("never");
209                        continue;
210                    }
211                    //  If this pixel is adjacent to the previous pixel, add to the row.
212                    if x == self.x_right.wrapping_add(1)
213                        && y == self.y
214                        && self.colors.push(color).is_ok()
215                    {
216                        // Don't add pixel if too many pixels in the row.
217                        self.x_right = x;
218                        continue;
219                    }
220                    //  Else return previous pixels as row.
221                    let row = PixelRow {
222                        x_left: self.x_left,
223                        x_right: self.x_right,
224                        y: self.y,
225                        colors: self.colors.clone(),
226                    };
227                    self.x_left = x;
228                    self.x_right = x;
229                    self.y = y;
230                    self.colors.clear();
231                    self.colors.push(color).expect("never");
232                    return Some(row);
233                }
234            }
235        }
236    }
237}
238
239/// Implement the Iterator for Pixel Blocks.
240/// R can be any Pixel Row Iterator.
241impl<R: Iterator<Item = PixelRow>> Iterator for BlockIterator<R> {
242    /// This Iterator returns Pixel Blocks
243    type Item = PixelBlock;
244
245    /// Return the next Pixel Block of contiguous Pixel Rows with the same start and end column number
246    fn next(&mut self) -> Option<Self::Item> {
247        //  Loop over all Pixel Rows until we have composed a Pixel Block, or we have run out of Pixel Rows.
248        loop {
249            //  Get the next Pixel Row.
250            let next_row = self.rows.next();
251            match next_row {
252                None => {
253                    //  If no more Pixel Rows...
254                    if self.first_row {
255                        return None; //  No rows to group
256                    }
257                    //  Else return previous rows as block.
258                    let row = PixelBlock {
259                        x_left: self.x_left,
260                        x_right: self.x_right,
261                        y_top: self.y_top,
262                        y_bottom: self.y_bottom,
263                        colors: self.colors.clone(),
264                    };
265                    self.colors.clear();
266                    self.first_row = true;
267                    return Some(row);
268                }
269                Some(PixelRow {
270                    x_left,
271                    x_right,
272                    y,
273                    colors,
274                    ..
275                }) => {
276                    //  If there is a Pixel Row...
277                    //  Save the first row as the block start and handle next block.
278                    if self.first_row {
279                        self.first_row = false;
280                        self.x_left = x_left;
281                        self.x_right = x_right;
282                        self.y_top = y;
283                        self.y_bottom = y;
284                        self.colors.clear();
285                        self.colors.extend_from_slice(&colors).expect("never");
286                        continue;
287                    }
288                    //  If this row is adjacent to the previous row and same size, add to the block.
289                    if y == self.y_bottom + 1 && x_left == self.x_left && x_right == self.x_right {
290                        //  Don't add row if too many pixels in the block.
291                        if self.colors.extend_from_slice(&colors).is_ok() {
292                            self.y_bottom = y;
293                            continue;
294                        }
295                    }
296                    //  Else return previous rows as block.
297                    let row = PixelBlock {
298                        x_left: self.x_left,
299                        x_right: self.x_right,
300                        y_top: self.y_top,
301                        y_bottom: self.y_bottom,
302                        colors: self.colors.clone(),
303                    };
304                    self.x_left = x_left;
305                    self.x_right = x_right;
306                    self.y_top = y;
307                    self.y_bottom = y;
308                    self.colors.clear();
309                    self.colors.extend_from_slice(&colors).expect("never");
310                    return Some(row);
311                }
312            }
313        }
314    }
315}