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}