Skip to main content

embedded_gui/
framebuffer.rs

1//! A RAM-backed, readback-capable framebuffer.
2//!
3//! [`Framebuffer`] implements [`DrawTarget`] **and** [`PixelRead`], so rendering
4//! into it unlocks true per-pixel alpha compositing (`RenderCtx::*_true_alpha`)
5//! instead of the ordered-dither approximation used for write-only displays.
6//! The typical pattern is a software double buffer: render the UI into a
7//! `Framebuffer`, then blit it to the physical panel once per frame.
8//!
9//! Storage is a fixed `[Rgb565; N]` array (no allocator). Pick `N >= W * H`;
10//! put the buffer behind a `StaticCell` (or on the stack for host tooling).
11
12use embedded_graphics_core::{
13    Pixel,
14    draw_target::DrawTarget,
15    geometry::{OriginDimensions, Point, Size},
16    pixelcolor::{Gray8, GrayColor, Rgb565, RgbColor},
17};
18
19use crate::geometry::Rect;
20use crate::render::PixelRead;
21
22/// An 8-bit RGBA color representation (32-bit total).
23#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
24pub struct Rgba8888 {
25    pub r: u8,
26    pub g: u8,
27    pub b: u8,
28    pub a: u8,
29}
30
31impl Rgba8888 {
32    pub const BLACK: Self = Self {
33        r: 0,
34        g: 0,
35        b: 0,
36        a: 255,
37    };
38    pub const WHITE: Self = Self {
39        r: 255,
40        g: 255,
41        b: 255,
42        a: 255,
43    };
44    pub const TRANSPARENT: Self = Self {
45        r: 0,
46        g: 0,
47        b: 0,
48        a: 0,
49    };
50
51    pub const fn new(r: u8, g: u8, b: u8, a: u8) -> Self {
52        Self { r, g, b, a }
53    }
54
55    pub fn from_rgb565(rgb: Rgb565, alpha: u8) -> Self {
56        let r5 = rgb.r();
57        let g6 = rgb.g();
58        let b5 = rgb.b();
59        let r8 = (r5 << 3) | (r5 >> 2);
60        let g8 = (g6 << 2) | (g6 >> 4);
61        let b8 = (b5 << 3) | (b5 >> 2);
62        Self {
63            r: r8,
64            g: g8,
65            b: b8,
66            a: alpha,
67        }
68    }
69
70    pub fn to_rgb565(self) -> Rgb565 {
71        Rgb565::new(self.r >> 3, self.g >> 2, self.b >> 3)
72    }
73}
74
75impl embedded_graphics_core::pixelcolor::PixelColor for Rgba8888 {
76    type Raw = embedded_graphics_core::pixelcolor::raw::RawU32;
77}
78
79/// A fixed-capacity RGB565 framebuffer. `N` is the backing array length and
80/// must be at least `width * height`.
81#[derive(Clone, Debug, PartialEq, Eq)]
82pub struct Framebuffer<const N: usize> {
83    pixels: [Rgb565; N],
84    width: u32,
85    height: u32,
86}
87
88impl<const N: usize> Framebuffer<N> {
89    /// Create a `width × height` framebuffer cleared to black.
90    ///
91    /// Panics if `width * height > N`.
92    pub fn new(width: u32, height: u32) -> Self {
93        assert!(
94            (width as usize) * (height as usize) <= N,
95            "Framebuffer backing array too small for width * height",
96        );
97        Self {
98            pixels: [Rgb565::BLACK; N],
99            width,
100            height,
101        }
102    }
103
104    /// Fill the whole framebuffer with a single color.
105    pub fn clear_color(&mut self, color: Rgb565) {
106        let len = self.len();
107        for p in self.pixels[..len].iter_mut() {
108            *p = color;
109        }
110    }
111
112    /// The active pixels, row-major, `width * height` long. Handy for blitting
113    /// to a physical display.
114    pub fn pixels(&self) -> &[Rgb565] {
115        &self.pixels[..self.len()]
116    }
117
118    /// Mutable slice of active pixels.
119    pub fn pixels_mut(&mut self) -> &mut [Rgb565] {
120        let len = self.len();
121        &mut self.pixels[..len]
122    }
123
124    pub const fn width(&self) -> u32 {
125        self.width
126    }
127
128    pub const fn height(&self) -> u32 {
129        self.height
130    }
131
132    #[inline]
133    fn len(&self) -> usize {
134        (self.width as usize) * (self.height as usize)
135    }
136
137    #[inline]
138    fn index(&self, x: i32, y: i32) -> Option<usize> {
139        if x < 0 || y < 0 || x as u32 >= self.width || y as u32 >= self.height {
140            return None;
141        }
142        Some((y as usize) * (self.width as usize) + (x as usize))
143    }
144
145    /// Apply Fast IIR Blur to the whole framebuffer.
146    pub fn apply_iir_blur(&mut self, blur_degree: u8) {
147        let rect = Rect::new(0, 0, self.width, self.height);
148        self.blur_rect(rect, blur_degree);
149    }
150
151    /// Apply Fast IIR Blur to a sub-region `rect` of the framebuffer.
152    pub fn blur_rect(&mut self, rect: Rect, blur_degree: u8) {
153        if blur_degree == 0 || self.width == 0 || self.height == 0 {
154            return;
155        }
156
157        let x0 = rect.x.max(0) as u32;
158        let y0 = rect.y.max(0) as u32;
159        let x1 = (rect.right() as u32).min(self.width);
160        let y1 = (rect.bottom() as u32).min(self.height);
161
162        if x0 >= x1 || y0 >= y1 {
163            return;
164        }
165
166        let alpha = 256 - (blur_degree as i32);
167        let w = self.width as usize;
168
169        // Pass 1: Horizontal Forward
170        for y in y0..y1 {
171            let row_start = y as usize * w;
172            let idx0 = row_start + x0 as usize;
173            let raw0 = self.pixels[idx0];
174            let (r5, g6, b5) = (raw0.r(), raw0.g(), raw0.b());
175            let mut acc_r = (((r5 << 3) | (r5 >> 2)) as i32) << 8;
176            let mut acc_g = (((g6 << 2) | (g6 >> 4)) as i32) << 8;
177            let mut acc_b = (((b5 << 3) | (b5 >> 2)) as i32) << 8;
178
179            for x in x0..x1 {
180                let idx = row_start + x as usize;
181                let raw = self.pixels[idx];
182                let (r5, g6, b5) = (raw.r(), raw.g(), raw.b());
183                let r8 = ((r5 << 3) | (r5 >> 2)) as i32;
184                let g8 = ((g6 << 2) | (g6 >> 4)) as i32;
185                let b8 = ((b5 << 3) | (b5 >> 2)) as i32;
186
187                acc_r += (((r8 << 8) - acc_r) * alpha) >> 8;
188                acc_g += (((g8 << 8) - acc_g) * alpha) >> 8;
189                acc_b += (((b8 << 8) - acc_b) * alpha) >> 8;
190
191                let r_out = ((acc_r >> 8).clamp(0, 255) as u8) >> 3;
192                let g_out = ((acc_g >> 8).clamp(0, 255) as u8) >> 2;
193                let b_out = ((acc_b >> 8).clamp(0, 255) as u8) >> 3;
194                self.pixels[idx] = Rgb565::new(r_out, g_out, b_out);
195            }
196        }
197
198        // Pass 2: Horizontal Reverse
199        for y in y0..y1 {
200            let row_start = y as usize * w;
201            let idx_last = row_start + (x1 - 1) as usize;
202            let raw_last = self.pixels[idx_last];
203            let (r5, g6, b5) = (raw_last.r(), raw_last.g(), raw_last.b());
204            let mut acc_r = (((r5 << 3) | (r5 >> 2)) as i32) << 8;
205            let mut acc_g = (((g6 << 2) | (g6 >> 4)) as i32) << 8;
206            let mut acc_b = (((b5 << 3) | (b5 >> 2)) as i32) << 8;
207
208            for x in (x0..x1).rev() {
209                let idx = row_start + x as usize;
210                let raw = self.pixels[idx];
211                let (r5, g6, b5) = (raw.r(), raw.g(), raw.b());
212                let r8 = ((r5 << 3) | (r5 >> 2)) as i32;
213                let g8 = ((g6 << 2) | (g6 >> 4)) as i32;
214                let b8 = ((b5 << 3) | (b5 >> 2)) as i32;
215
216                acc_r += (((r8 << 8) - acc_r) * alpha) >> 8;
217                acc_g += (((g8 << 8) - acc_g) * alpha) >> 8;
218                acc_b += (((b8 << 8) - acc_b) * alpha) >> 8;
219
220                let r_out = ((acc_r >> 8).clamp(0, 255) as u8) >> 3;
221                let g_out = ((acc_g >> 8).clamp(0, 255) as u8) >> 2;
222                let b_out = ((acc_b >> 8).clamp(0, 255) as u8) >> 3;
223                self.pixels[idx] = Rgb565::new(r_out, g_out, b_out);
224            }
225        }
226
227        // Pass 3: Vertical Forward
228        for x in x0..x1 {
229            let idx0 = y0 as usize * w + x as usize;
230            let raw0 = self.pixels[idx0];
231            let (r5, g6, b5) = (raw0.r(), raw0.g(), raw0.b());
232            let mut acc_r = (((r5 << 3) | (r5 >> 2)) as i32) << 8;
233            let mut acc_g = (((g6 << 2) | (g6 >> 4)) as i32) << 8;
234            let mut acc_b = (((b5 << 3) | (b5 >> 2)) as i32) << 8;
235
236            for y in y0..y1 {
237                let idx = y as usize * w + x as usize;
238                let raw = self.pixels[idx];
239                let (r5, g6, b5) = (raw.r(), raw.g(), raw.b());
240                let r8 = ((r5 << 3) | (r5 >> 2)) as i32;
241                let g8 = ((g6 << 2) | (g6 >> 4)) as i32;
242                let b8 = ((b5 << 3) | (b5 >> 2)) as i32;
243
244                acc_r += (((r8 << 8) - acc_r) * alpha) >> 8;
245                acc_g += (((g8 << 8) - acc_g) * alpha) >> 8;
246                acc_b += (((b8 << 8) - acc_b) * alpha) >> 8;
247
248                let r_out = ((acc_r >> 8).clamp(0, 255) as u8) >> 3;
249                let g_out = ((acc_g >> 8).clamp(0, 255) as u8) >> 2;
250                let b_out = ((acc_b >> 8).clamp(0, 255) as u8) >> 3;
251                self.pixels[idx] = Rgb565::new(r_out, g_out, b_out);
252            }
253        }
254
255        // Pass 4: Vertical Reverse
256        for x in x0..x1 {
257            let idx_last = (y1 - 1) as usize * w + x as usize;
258            let raw_last = self.pixels[idx_last];
259            let (r5, g6, b5) = (raw_last.r(), raw_last.g(), raw_last.b());
260            let mut acc_r = (((r5 << 3) | (r5 >> 2)) as i32) << 8;
261            let mut acc_g = (((g6 << 2) | (g6 >> 4)) as i32) << 8;
262            let mut acc_b = (((b5 << 3) | (b5 >> 2)) as i32) << 8;
263
264            for y in (y0..y1).rev() {
265                let idx = y as usize * w + x as usize;
266                let raw = self.pixels[idx];
267                let (r5, g6, b5) = (raw.r(), raw.g(), raw.b());
268                let r8 = ((r5 << 3) | (r5 >> 2)) as i32;
269                let g8 = ((g6 << 2) | (g6 >> 4)) as i32;
270                let b8 = ((b5 << 3) | (b5 >> 2)) as i32;
271
272                acc_r += (((r8 << 8) - acc_r) * alpha) >> 8;
273                acc_g += (((g8 << 8) - acc_g) * alpha) >> 8;
274                acc_b += (((b8 << 8) - acc_b) * alpha) >> 8;
275
276                let r_out = ((acc_r >> 8).clamp(0, 255) as u8) >> 3;
277                let g_out = ((acc_g >> 8).clamp(0, 255) as u8) >> 2;
278                let b_out = ((acc_b >> 8).clamp(0, 255) as u8) >> 3;
279                self.pixels[idx] = Rgb565::new(r_out, g_out, b_out);
280            }
281        }
282    }
283
284    /// Apply reverse colour (color inversion) filter to a sub-region `rect`.
285    pub fn reverse_colour_rect(&mut self, rect: Rect) {
286        let x0 = rect.x.max(0) as u32;
287        let y0 = rect.y.max(0) as u32;
288        let x1 = (rect.right() as u32).min(self.width);
289        let y1 = (rect.bottom() as u32).min(self.height);
290
291        if x0 >= x1 || y0 >= y1 {
292            return;
293        }
294
295        let w = self.width as usize;
296        for y in y0..y1 {
297            let row_start = y as usize * w;
298            for x in x0..x1 {
299                let idx = row_start + x as usize;
300                let c = self.pixels[idx];
301                self.pixels[idx] = Rgb565::new(31 - c.r(), 63 - c.g(), 31 - c.b());
302            }
303        }
304    }
305
306    /// Apply reverse colour filter to the whole framebuffer.
307    pub fn apply_reverse_colour(&mut self) {
308        let rect = Rect::new(0, 0, self.width, self.height);
309        self.reverse_colour_rect(rect);
310    }
311}
312
313impl<const N: usize> OriginDimensions for Framebuffer<N> {
314    fn size(&self) -> Size {
315        Size::new(self.width, self.height)
316    }
317}
318
319impl<const N: usize> DrawTarget for Framebuffer<N> {
320    type Color = Rgb565;
321    type Error = core::convert::Infallible;
322
323    fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
324    where
325        I: IntoIterator<Item = Pixel<Self::Color>>,
326    {
327        for Pixel(point, color) in pixels {
328            if let Some(idx) = self.index(point.x, point.y) {
329                self.pixels[idx] = color;
330            }
331        }
332        Ok(())
333    }
334}
335
336impl<const N: usize> PixelRead for Framebuffer<N> {
337    fn get_pixel(&self, point: Point) -> Rgb565 {
338        match self.index(point.x, point.y) {
339            Some(idx) => self.pixels[idx],
340            None => Rgb565::BLACK,
341        }
342    }
343}
344
345/// A fixed-capacity RGBA8888 framebuffer.
346#[derive(Clone, Debug, PartialEq, Eq)]
347pub struct FramebufferRgba8888<const N: usize> {
348    pixels: [Rgba8888; N],
349    width: u32,
350    height: u32,
351}
352
353impl<const N: usize> FramebufferRgba8888<N> {
354    pub fn new(width: u32, height: u32) -> Self {
355        assert!(
356            (width as usize) * (height as usize) <= N,
357            "Framebuffer backing array too small for width * height",
358        );
359        Self {
360            pixels: [Rgba8888::BLACK; N],
361            width,
362            height,
363        }
364    }
365
366    pub fn clear_color(&mut self, color: Rgba8888) {
367        let len = self.len();
368        for p in self.pixels[..len].iter_mut() {
369            *p = color;
370        }
371    }
372
373    pub fn pixels(&self) -> &[Rgba8888] {
374        &self.pixels[..self.len()]
375    }
376
377    pub fn pixels_mut(&mut self) -> &mut [Rgba8888] {
378        let len = self.len();
379        &mut self.pixels[..len]
380    }
381
382    pub const fn width(&self) -> u32 {
383        self.width
384    }
385
386    pub const fn height(&self) -> u32 {
387        self.height
388    }
389
390    #[inline]
391    fn len(&self) -> usize {
392        (self.width as usize) * (self.height as usize)
393    }
394
395    #[inline]
396    fn index(&self, x: i32, y: i32) -> Option<usize> {
397        if x < 0 || y < 0 || x as u32 >= self.width || y as u32 >= self.height {
398            return None;
399        }
400        Some((y as usize) * (self.width as usize) + (x as usize))
401    }
402
403    pub fn apply_iir_blur(&mut self, blur_degree: u8) {
404        let rect = Rect::new(0, 0, self.width, self.height);
405        self.blur_rect(rect, blur_degree);
406    }
407
408    pub fn blur_rect(&mut self, rect: Rect, blur_degree: u8) {
409        if blur_degree == 0 || self.width == 0 || self.height == 0 {
410            return;
411        }
412        let x0 = rect.x.max(0) as u32;
413        let y0 = rect.y.max(0) as u32;
414        let x1 = (rect.right() as u32).min(self.width);
415        let y1 = (rect.bottom() as u32).min(self.height);
416
417        if x0 >= x1 || y0 >= y1 {
418            return;
419        }
420        let alpha = 256 - (blur_degree as i32);
421        let w = self.width as usize;
422
423        // Pass 1: Horizontal Forward
424        for y in y0..y1 {
425            let row_start = y as usize * w;
426            let p0 = self.pixels[row_start + x0 as usize];
427            let mut acc_r = (p0.r as i32) << 8;
428            let mut acc_g = (p0.g as i32) << 8;
429            let mut acc_b = (p0.b as i32) << 8;
430            let mut acc_a = (p0.a as i32) << 8;
431
432            for x in x0..x1 {
433                let idx = row_start + x as usize;
434                let p = self.pixels[idx];
435                acc_r += ((((p.r as i32) << 8) - acc_r) * alpha) >> 8;
436                acc_g += ((((p.g as i32) << 8) - acc_g) * alpha) >> 8;
437                acc_b += ((((p.b as i32) << 8) - acc_b) * alpha) >> 8;
438                acc_a += ((((p.a as i32) << 8) - acc_a) * alpha) >> 8;
439
440                self.pixels[idx] = Rgba8888 {
441                    r: (acc_r >> 8).clamp(0, 255) as u8,
442                    g: (acc_g >> 8).clamp(0, 255) as u8,
443                    b: (acc_b >> 8).clamp(0, 255) as u8,
444                    a: (acc_a >> 8).clamp(0, 255) as u8,
445                };
446            }
447        }
448
449        // Pass 2: Horizontal Reverse
450        for y in y0..y1 {
451            let row_start = y as usize * w;
452            let p_last = self.pixels[row_start + (x1 - 1) as usize];
453            let mut acc_r = (p_last.r as i32) << 8;
454            let mut acc_g = (p_last.g as i32) << 8;
455            let mut acc_b = (p_last.b as i32) << 8;
456            let mut acc_a = (p_last.a as i32) << 8;
457
458            for x in (x0..x1).rev() {
459                let idx = row_start + x as usize;
460                let p = self.pixels[idx];
461                acc_r += ((((p.r as i32) << 8) - acc_r) * alpha) >> 8;
462                acc_g += ((((p.g as i32) << 8) - acc_g) * alpha) >> 8;
463                acc_b += ((((p.b as i32) << 8) - acc_b) * alpha) >> 8;
464                acc_a += ((((p.a as i32) << 8) - acc_a) * alpha) >> 8;
465
466                self.pixels[idx] = Rgba8888 {
467                    r: (acc_r >> 8).clamp(0, 255) as u8,
468                    g: (acc_g >> 8).clamp(0, 255) as u8,
469                    b: (acc_b >> 8).clamp(0, 255) as u8,
470                    a: (acc_a >> 8).clamp(0, 255) as u8,
471                };
472            }
473        }
474
475        // Pass 3: Vertical Forward
476        for x in x0..x1 {
477            let p0 = self.pixels[y0 as usize * w + x as usize];
478            let mut acc_r = (p0.r as i32) << 8;
479            let mut acc_g = (p0.g as i32) << 8;
480            let mut acc_b = (p0.b as i32) << 8;
481            let mut acc_a = (p0.a as i32) << 8;
482
483            for y in y0..y1 {
484                let idx = y as usize * w + x as usize;
485                let p = self.pixels[idx];
486                acc_r += ((((p.r as i32) << 8) - acc_r) * alpha) >> 8;
487                acc_g += ((((p.g as i32) << 8) - acc_g) * alpha) >> 8;
488                acc_b += ((((p.b as i32) << 8) - acc_b) * alpha) >> 8;
489                acc_a += ((((p.a as i32) << 8) - acc_a) * alpha) >> 8;
490
491                self.pixels[idx] = Rgba8888 {
492                    r: (acc_r >> 8).clamp(0, 255) as u8,
493                    g: (acc_g >> 8).clamp(0, 255) as u8,
494                    b: (acc_b >> 8).clamp(0, 255) as u8,
495                    a: (acc_a >> 8).clamp(0, 255) as u8,
496                };
497            }
498        }
499
500        // Pass 4: Vertical Reverse
501        for x in x0..x1 {
502            let p_last = self.pixels[(y1 - 1) as usize * w + x as usize];
503            let mut acc_r = (p_last.r as i32) << 8;
504            let mut acc_g = (p_last.g as i32) << 8;
505            let mut acc_b = (p_last.b as i32) << 8;
506            let mut acc_a = (p_last.a as i32) << 8;
507
508            for y in (y0..y1).rev() {
509                let idx = y as usize * w + x as usize;
510                let p = self.pixels[idx];
511                acc_r += ((((p.r as i32) << 8) - acc_r) * alpha) >> 8;
512                acc_g += ((((p.g as i32) << 8) - acc_g) * alpha) >> 8;
513                acc_b += ((((p.b as i32) << 8) - acc_b) * alpha) >> 8;
514                acc_a += ((((p.a as i32) << 8) - acc_a) * alpha) >> 8;
515
516                self.pixels[idx] = Rgba8888 {
517                    r: (acc_r >> 8).clamp(0, 255) as u8,
518                    g: (acc_g >> 8).clamp(0, 255) as u8,
519                    b: (acc_b >> 8).clamp(0, 255) as u8,
520                    a: (acc_a >> 8).clamp(0, 255) as u8,
521                };
522            }
523        }
524    }
525
526    /// Apply reverse colour (color inversion) filter to a sub-region `rect`.
527    pub fn reverse_colour_rect(&mut self, rect: Rect) {
528        let x0 = rect.x.max(0) as u32;
529        let y0 = rect.y.max(0) as u32;
530        let x1 = (rect.right() as u32).min(self.width);
531        let y1 = (rect.bottom() as u32).min(self.height);
532
533        if x0 >= x1 || y0 >= y1 {
534            return;
535        }
536
537        let w = self.width as usize;
538        for y in y0..y1 {
539            let row_start = y as usize * w;
540            for x in x0..x1 {
541                let idx = row_start + x as usize;
542                let p = self.pixels[idx];
543                self.pixels[idx] = Rgba8888 {
544                    r: 255 - p.r,
545                    g: 255 - p.g,
546                    b: 255 - p.b,
547                    a: p.a,
548                };
549            }
550        }
551    }
552
553    /// Apply reverse colour filter to the whole framebuffer.
554    pub fn apply_reverse_colour(&mut self) {
555        let rect = Rect::new(0, 0, self.width, self.height);
556        self.reverse_colour_rect(rect);
557    }
558}
559
560impl<const N: usize> OriginDimensions for FramebufferRgba8888<N> {
561    fn size(&self) -> Size {
562        Size::new(self.width, self.height)
563    }
564}
565
566impl<const N: usize> DrawTarget for FramebufferRgba8888<N> {
567    type Color = Rgba8888;
568    type Error = core::convert::Infallible;
569
570    fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
571    where
572        I: IntoIterator<Item = Pixel<Self::Color>>,
573    {
574        for Pixel(point, color) in pixels {
575            if let Some(idx) = self.index(point.x, point.y) {
576                self.pixels[idx] = color;
577            }
578        }
579        Ok(())
580    }
581}
582
583impl<const N: usize> PixelRead for FramebufferRgba8888<N> {
584    fn get_pixel(&self, point: Point) -> Rgba8888 {
585        match self.index(point.x, point.y) {
586            Some(idx) => self.pixels[idx],
587            None => Rgba8888::TRANSPARENT,
588        }
589    }
590}
591
592/// A fixed-capacity Gray8 framebuffer.
593#[derive(Clone, Debug, PartialEq, Eq)]
594pub struct FramebufferGray8<const N: usize> {
595    pixels: [Gray8; N],
596    width: u32,
597    height: u32,
598}
599
600impl<const N: usize> FramebufferGray8<N> {
601    pub fn new(width: u32, height: u32) -> Self {
602        assert!(
603            (width as usize) * (height as usize) <= N,
604            "Framebuffer backing array too small for width * height",
605        );
606        Self {
607            pixels: [Gray8::new(0); N],
608            width,
609            height,
610        }
611    }
612
613    pub fn clear_color(&mut self, color: Gray8) {
614        let len = self.len();
615        for p in self.pixels[..len].iter_mut() {
616            *p = color;
617        }
618    }
619
620    pub fn pixels(&self) -> &[Gray8] {
621        &self.pixels[..self.len()]
622    }
623
624    pub fn pixels_mut(&mut self) -> &mut [Gray8] {
625        let len = self.len();
626        &mut self.pixels[..len]
627    }
628
629    pub const fn width(&self) -> u32 {
630        self.width
631    }
632
633    pub const fn height(&self) -> u32 {
634        self.height
635    }
636
637    #[inline]
638    fn len(&self) -> usize {
639        (self.width as usize) * (self.height as usize)
640    }
641
642    #[inline]
643    fn index(&self, x: i32, y: i32) -> Option<usize> {
644        if x < 0 || y < 0 || x as u32 >= self.width || y as u32 >= self.height {
645            return None;
646        }
647        Some((y as usize) * (self.width as usize) + (x as usize))
648    }
649
650    pub fn apply_iir_blur(&mut self, blur_degree: u8) {
651        let rect = Rect::new(0, 0, self.width, self.height);
652        self.blur_rect(rect, blur_degree);
653    }
654
655    pub fn blur_rect(&mut self, rect: Rect, blur_degree: u8) {
656        if blur_degree == 0 || self.width == 0 || self.height == 0 {
657            return;
658        }
659        let x0 = rect.x.max(0) as u32;
660        let y0 = rect.y.max(0) as u32;
661        let x1 = (rect.right() as u32).min(self.width);
662        let y1 = (rect.bottom() as u32).min(self.height);
663
664        if x0 >= x1 || y0 >= y1 {
665            return;
666        }
667        let alpha = 256 - (blur_degree as i32);
668        let w = self.width as usize;
669
670        // Pass 1: Horizontal Forward
671        for y in y0..y1 {
672            let row_start = y as usize * w;
673            let p0 = self.pixels[row_start + x0 as usize].luma();
674            let mut acc = (p0 as i32) << 8;
675
676            for x in x0..x1 {
677                let idx = row_start + x as usize;
678                let luma = self.pixels[idx].luma();
679                acc += ((((luma as i32) << 8) - acc) * alpha) >> 8;
680                self.pixels[idx] = Gray8::new((acc >> 8).clamp(0, 255) as u8);
681            }
682        }
683
684        // Pass 2: Horizontal Reverse
685        for y in y0..y1 {
686            let row_start = y as usize * w;
687            let p_last = self.pixels[row_start + (x1 - 1) as usize].luma();
688            let mut acc = (p_last as i32) << 8;
689
690            for x in (x0..x1).rev() {
691                let idx = row_start + x as usize;
692                let luma = self.pixels[idx].luma();
693                acc += ((((luma as i32) << 8) - acc) * alpha) >> 8;
694                self.pixels[idx] = Gray8::new((acc >> 8).clamp(0, 255) as u8);
695            }
696        }
697
698        // Pass 3: Vertical Forward
699        for x in x0..x1 {
700            let p0 = self.pixels[y0 as usize * w + x as usize].luma();
701            let mut acc = (p0 as i32) << 8;
702
703            for y in y0..y1 {
704                let idx = y as usize * w + x as usize;
705                let luma = self.pixels[idx].luma();
706                acc += ((((luma as i32) << 8) - acc) * alpha) >> 8;
707                self.pixels[idx] = Gray8::new((acc >> 8).clamp(0, 255) as u8);
708            }
709        }
710
711        // Pass 4: Vertical Reverse
712        for x in x0..x1 {
713            let p_last = self.pixels[(y1 - 1) as usize * w + x as usize].luma();
714            let mut acc = (p_last as i32) << 8;
715
716            for y in (y0..y1).rev() {
717                let idx = y as usize * w + x as usize;
718                let luma = self.pixels[idx].luma();
719                acc += ((((luma as i32) << 8) - acc) * alpha) >> 8;
720                self.pixels[idx] = Gray8::new((acc >> 8).clamp(0, 255) as u8);
721            }
722        }
723    }
724}
725
726impl<const N: usize> OriginDimensions for FramebufferGray8<N> {
727    fn size(&self) -> Size {
728        Size::new(self.width, self.height)
729    }
730}
731
732impl<const N: usize> DrawTarget for FramebufferGray8<N> {
733    type Color = Gray8;
734    type Error = core::convert::Infallible;
735
736    fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
737    where
738        I: IntoIterator<Item = Pixel<Self::Color>>,
739    {
740        for Pixel(point, color) in pixels {
741            if let Some(idx) = self.index(point.x, point.y) {
742                self.pixels[idx] = color;
743            }
744        }
745        Ok(())
746    }
747}
748
749impl<const N: usize> PixelRead for FramebufferGray8<N> {
750    fn get_pixel(&self, point: Point) -> Gray8 {
751        match self.index(point.x, point.y) {
752            Some(idx) => self.pixels[idx],
753            None => Gray8::new(0),
754        }
755    }
756}
757
758#[cfg(test)]
759mod tests {
760    use super::*;
761
762    #[test]
763    fn test_iir_blur_rgb565() {
764        let mut fb = Framebuffer::<100>::new(10, 10);
765        fb.clear_color(Rgb565::WHITE);
766        for y in 3..7 {
767            for x in 3..7 {
768                fb.pixels_mut()[y * 10 + x] = Rgb565::BLACK;
769            }
770        }
771        fb.apply_iir_blur(128);
772        let center_pixel = fb.pixels()[5 * 10 + 5];
773        assert_ne!(center_pixel, Rgb565::BLACK);
774    }
775
776    #[test]
777    fn test_iir_blur_rgba8888() {
778        let mut fb = FramebufferRgba8888::<100>::new(10, 10);
779        fb.clear_color(Rgba8888::WHITE);
780        for y in 3..7 {
781            for x in 3..7 {
782                fb.pixels_mut()[y * 10 + x] = Rgba8888::BLACK;
783            }
784        }
785        fb.apply_iir_blur(128);
786        let center_pixel = fb.pixels()[5 * 10 + 5];
787        assert_ne!(center_pixel, Rgba8888::BLACK);
788        assert!(center_pixel.r > 0);
789    }
790
791    #[test]
792    fn test_iir_blur_gray8() {
793        let mut fb = FramebufferGray8::<100>::new(10, 10);
794        fb.clear_color(Gray8::new(255));
795        for y in 3..7 {
796            for x in 3..7 {
797                fb.pixels_mut()[y * 10 + x] = Gray8::new(0);
798            }
799        }
800        fb.apply_iir_blur(128);
801        let center_pixel = fb.pixels()[5 * 10 + 5].luma();
802        assert!(center_pixel > 0 && center_pixel < 255);
803    }
804}