1use core::convert::Infallible;
2use embedded_graphics_core::{
3 Pixel,
4 draw_target::DrawTarget,
5 geometry::{OriginDimensions, Point, Size},
6 pixelcolor::{Rgb565, RgbColor},
7};
8
9use crate::{
10 geometry::Rect,
11 render::{
12 PixelRead, WindowedDrawTarget,
13 task::{DrawTaskQueue, SoftwareDrawUnit, dispatch_draw_tasks},
14 },
15};
16
17#[derive(Clone, Debug)]
21pub struct PartialBandBuffer<const N: usize> {
22 buffer: [Rgb565; N],
23 width: usize,
24 height: usize,
25}
26
27impl<const N: usize> PartialBandBuffer<N> {
28 pub const fn new(width: usize, height: usize) -> Self {
29 Self {
30 buffer: [Rgb565::BLACK; N],
31 width,
32 height,
33 }
34 }
35
36 pub const fn width(&self) -> usize {
37 self.width
38 }
39
40 pub const fn height(&self) -> usize {
41 self.height
42 }
43
44 pub fn buffer(&self) -> &[Rgb565] {
45 &self.buffer[..self.width * self.height]
46 }
47
48 pub fn buffer_mut(&mut self) -> &mut [Rgb565] {
49 let len = self.width * self.height;
50 &mut self.buffer[..len]
51 }
52
53 pub fn clear_color(&mut self, color: Rgb565) {
54 let len = self.width * self.height;
55 self.buffer[..len].fill(color);
56 }
57
58 pub fn render_tasks_banded<D, const CAP: usize>(
61 &mut self,
62 _viewport: Rect,
63 dirty_rect: Rect,
64 tasks: &DrawTaskQueue<'_, CAP>,
65 target: &mut D,
66 clear_color: Option<Rgb565>,
67 ) -> Result<(), D::Error>
68 where
69 D: WindowedDrawTarget<Color = Rgb565>,
70 {
71 if dirty_rect.w == 0 || dirty_rect.h == 0 {
72 return Ok(());
73 }
74
75 let mut y = dirty_rect.y;
76 let y_end = dirty_rect.y + dirty_rect.h as i32;
77
78 while y < y_end {
79 let band_h = ((y_end - y) as u32).min(self.height as u32);
80 let band_rect = Rect::new(dirty_rect.x, y, dirty_rect.w, band_h);
81
82 let len = self.width * self.height;
83 if let Some(c) = clear_color {
84 self.buffer[..len].fill(c);
85 } else {
86 self.buffer[..len].fill(Rgb565::BLACK);
87 }
88
89 for task in tasks.as_slice() {
91 let tb = task.bounds();
92 let overlap = tb.intersection(band_rect);
93 if !overlap.is_empty() {
94 let mut local_target = BandTargetWrapper {
96 parent: self,
97 band_origin: Point::new(band_rect.x, band_rect.y),
98 };
99 let mut fallback = SoftwareDrawUnit;
100 let mut queue = DrawTaskQueue::<1>::new();
101 let _ = queue.push(*task);
102 let mut units = [];
103 let _ =
104 dispatch_draw_tasks(&queue, &mut local_target, &mut units, &mut fallback);
105 }
106 }
107
108 let eg_rect = embedded_graphics_core::primitives::Rectangle::new(
110 Point::new(band_rect.x, band_rect.y),
111 Size::new(band_rect.w, band_rect.h),
112 );
113 target.set_window(&eg_rect)?;
114
115 let count = (band_rect.w * band_rect.h) as usize;
117 target.fill_contiguous(&eg_rect, self.buffer[..count].iter().copied())?;
118
119 y += band_h as i32;
120 }
121
122 Ok(())
123 }
124}
125
126impl<const N: usize> OriginDimensions for PartialBandBuffer<N> {
127 fn size(&self) -> Size {
128 Size::new(self.width as u32, self.height as u32)
129 }
130}
131
132impl<const N: usize> DrawTarget for PartialBandBuffer<N> {
133 type Color = Rgb565;
134 type Error = Infallible;
135
136 fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
137 where
138 I: IntoIterator<Item = Pixel<Self::Color>>,
139 {
140 for Pixel(coord, color) in pixels {
141 if coord.x >= 0
142 && coord.y >= 0
143 && (coord.x as usize) < self.width
144 && (coord.y as usize) < self.height
145 {
146 let idx = (coord.y as usize) * self.width + (coord.x as usize);
147 if idx < N {
148 self.buffer[idx] = color;
149 }
150 }
151 }
152 Ok(())
153 }
154
155 fn fill_solid(
156 &mut self,
157 area: &embedded_graphics_core::primitives::Rectangle,
158 color: Self::Color,
159 ) -> Result<(), Self::Error> {
160 let intersect = area.intersection(&embedded_graphics_core::primitives::Rectangle::new(
161 Point::zero(),
162 self.size(),
163 ));
164 if intersect.is_zero_sized() {
165 return Ok(());
166 }
167 let x0 = intersect.top_left.x as usize;
168 let y0 = intersect.top_left.y as usize;
169 let w = intersect.size.width as usize;
170 let h = intersect.size.height as usize;
171 let stride = self.width;
172
173 for row in 0..h {
174 let start = (y0 + row) * stride + x0;
175 if start + w <= N {
176 self.buffer[start..start + w].fill(color);
177 }
178 }
179 Ok(())
180 }
181
182 fn fill_contiguous<I>(
183 &mut self,
184 area: &embedded_graphics_core::primitives::Rectangle,
185 colors: I,
186 ) -> Result<(), Self::Error>
187 where
188 I: IntoIterator<Item = Self::Color>,
189 {
190 let mut colors = colors.into_iter();
191 let stride = self.width;
192 let x_end = area.top_left.x + area.size.width as i32;
193 let y_end = area.top_left.y + area.size.height as i32;
194
195 for y in area.top_left.y..y_end {
196 for x in area.top_left.x..x_end {
197 if let Some(c) = colors.next() {
198 if x >= 0 && y >= 0 && (x as usize) < self.width && (y as usize) < self.height {
199 let idx = (y as usize) * stride + (x as usize);
200 if idx < N {
201 self.buffer[idx] = c;
202 }
203 }
204 } else {
205 return Ok(());
206 }
207 }
208 }
209 Ok(())
210 }
211
212 fn clear(&mut self, color: Self::Color) -> Result<(), Self::Error> {
213 self.clear_color(color);
214 Ok(())
215 }
216}
217
218impl<const N: usize> PixelRead for PartialBandBuffer<N> {
219 fn get_pixel(&self, point: Point) -> Self::Color {
220 if point.x >= 0
221 && point.y >= 0
222 && (point.x as usize) < self.width
223 && (point.y as usize) < self.height
224 {
225 let idx = (point.y as usize) * self.width + (point.x as usize);
226 if idx < N {
227 return self.buffer[idx];
228 }
229 }
230 Rgb565::BLACK
231 }
232}
233
234struct BandTargetWrapper<'a, const N: usize> {
236 parent: &'a mut PartialBandBuffer<N>,
237 band_origin: Point,
238}
239
240impl<'a, const N: usize> OriginDimensions for BandTargetWrapper<'a, N> {
241 fn size(&self) -> Size {
242 self.parent.size()
243 }
244}
245
246impl<'a, const N: usize> DrawTarget for BandTargetWrapper<'a, N> {
247 type Color = Rgb565;
248 type Error = Infallible;
249
250 fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
251 where
252 I: IntoIterator<Item = Pixel<Self::Color>>,
253 {
254 let ox = self.band_origin.x;
255 let oy = self.band_origin.y;
256 let w = self.parent.width;
257 let h = self.parent.height;
258 for Pixel(coord, color) in pixels {
259 let lx = coord.x - ox;
260 let ly = coord.y - oy;
261 if lx >= 0 && ly >= 0 && (lx as usize) < w && (ly as usize) < h {
262 let idx = (ly as usize) * w + (lx as usize);
263 if idx < N {
264 self.parent.buffer[idx] = color;
265 }
266 }
267 }
268 Ok(())
269 }
270
271 fn fill_solid(
272 &mut self,
273 area: &embedded_graphics_core::primitives::Rectangle,
274 color: Self::Color,
275 ) -> Result<(), Self::Error> {
276 let ox = self.band_origin.x;
277 let oy = self.band_origin.y;
278 let local_rect = embedded_graphics_core::primitives::Rectangle::new(
279 Point::new(area.top_left.x - ox, area.top_left.y - oy),
280 area.size,
281 );
282 self.parent.fill_solid(&local_rect, color)
283 }
284
285 fn fill_contiguous<I>(
286 &mut self,
287 area: &embedded_graphics_core::primitives::Rectangle,
288 colors: I,
289 ) -> Result<(), Self::Error>
290 where
291 I: IntoIterator<Item = Self::Color>,
292 {
293 let ox = self.band_origin.x;
294 let oy = self.band_origin.y;
295 let local_rect = embedded_graphics_core::primitives::Rectangle::new(
296 Point::new(area.top_left.x - ox, area.top_left.y - oy),
297 area.size,
298 );
299 self.parent.fill_contiguous(&local_rect, colors)
300 }
301}
302
303impl<'a, const N: usize> PixelRead for BandTargetWrapper<'a, N> {
304 fn get_pixel(&self, point: Point) -> Rgb565 {
305 let lx = point.x - self.band_origin.x;
306 let ly = point.y - self.band_origin.y;
307 self.parent.get_pixel(Point::new(lx, ly))
308 }
309}
310
311#[cfg(test)]
312mod tests {
313 use super::*;
314 use crate::render::task::DrawTask;
315 use embedded_graphics_core::primitives::Rectangle;
316
317 struct MockWindowedTarget {
318 pixels: [Rgb565; 400],
319 window: Option<Rectangle>,
320 }
321
322 impl OriginDimensions for MockWindowedTarget {
323 fn size(&self) -> Size {
324 Size::new(20, 20)
325 }
326 }
327
328 impl DrawTarget for MockWindowedTarget {
329 type Color = Rgb565;
330 type Error = Infallible;
331
332 fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
333 where
334 I: IntoIterator<Item = Pixel<Self::Color>>,
335 {
336 for Pixel(pt, color) in pixels {
337 if pt.x >= 0 && pt.y >= 0 && pt.x < 20 && pt.y < 20 {
338 self.pixels[(pt.y * 20 + pt.x) as usize] = color;
339 }
340 }
341 Ok(())
342 }
343 }
344
345 impl WindowedDrawTarget for MockWindowedTarget {
346 fn set_window(&mut self, rect: &Rectangle) -> Result<(), Self::Error> {
347 self.window = Some(*rect);
348 Ok(())
349 }
350 }
351
352 #[test]
353 fn test_partial_band_buffer_render() {
354 let mut band = PartialBandBuffer::<100>::new(20, 5);
355 assert_eq!(band.width(), 20);
356 assert_eq!(band.height(), 5);
357
358 let mut queue = DrawTaskQueue::<2>::new();
359 queue
360 .push(DrawTask::Fill {
361 rect: Rect::new(0, 0, 20, 10),
362 color: Rgb565::GREEN,
363 radius: 0,
364 opacity: 255,
365 })
366 .unwrap();
367
368 let mut target = MockWindowedTarget {
369 pixels: [Rgb565::BLACK; 400],
370 window: None,
371 };
372
373 band.render_tasks_banded(
374 Rect::new(0, 0, 20, 20),
375 Rect::new(0, 0, 20, 10),
376 &queue,
377 &mut target,
378 Some(Rgb565::BLACK),
379 )
380 .unwrap();
381
382 assert_eq!(target.pixels[0], Rgb565::GREEN);
383 assert_eq!(target.pixels[20 * 9 + 5], Rgb565::GREEN);
384 assert_eq!(target.pixels[20 * 11 + 5], Rgb565::BLACK);
385 }
386}