Skip to main content

embedded_rgba/
alpha.rs

1use crate::*;
2use embedded_graphics_core::pixelcolor::*;
3use embedded_graphics_core::prelude::*;
4use embedded_graphics_core::primitives::*;
5
6pub struct AlphaCanvas<'a, C: RgbColor, const N: usize, const W: usize, const H: usize> {
7    buffer: &'a mut Framebuffer<C, N, W, H>,
8}
9
10impl<'a, C: RgbColor, const N: usize, const W: usize, const H: usize> AlphaCanvas<'a, C, N, W, H>
11where
12    Rgba<C>: Blend<C>,
13{
14    pub fn new(buffer: &'a mut Framebuffer<C, N, W, H>) -> Self {
15        Self { buffer }
16    }
17}
18
19impl<'a, C: RgbColor, const N: usize, const W: usize, const H: usize> OriginDimensions
20    for AlphaCanvas<'a, C, N, W, H>
21where
22    Rgba<C>: Blend<C>,
23{
24    fn size(&self) -> Size {
25        self.buffer.size()
26    }
27}
28
29impl<'a, C: RgbColor, const N: usize, const W: usize, const H: usize> DrawTarget
30    for AlphaCanvas<'a, C, N, W, H>
31where
32    Rgba<C>: Blend<C>,
33{
34    type Error = core::convert::Infallible;
35    type Color = Rgba<C>;
36
37    fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
38    where
39        I: IntoIterator<Item = Pixel<Self::Color>>,
40    {
41        // Access the underlying buffer once for speed.
42        let buf = self.buffer.buf_mut();
43
44        for Pixel(point, fg) in pixels {
45            let x = point.x;
46            let y = point.y;
47
48            // Fast integer bounds check without converting more than once.
49            if x >= 0 && x < W as i32 && y >= 0 && y < H as i32 {
50                let xi = x as usize;
51                let yi = y as usize;
52                let idx = yi * W + xi;
53
54                // Read current background pixel, blend, write back.
55                let bg = buf[idx];
56                buf[idx] = fg.blend(bg);
57            }
58        }
59
60        Ok(())
61    }
62
63    fn fill_contiguous<I>(&mut self, area: &Rectangle, colors: I) -> Result<(), Self::Error>
64    where
65        I: IntoIterator<Item = Self::Color>,
66    {
67        use core::cmp::{max, min};
68
69        let mut it = colors.into_iter();
70
71        // Early out if area is empty
72        if area.size.width == 0 || area.size.height == 0 {
73            return Ok(());
74        }
75
76        // Framebuffer bounds
77        let fb_left = 0i32;
78        let fb_top = 0i32;
79        let fb_right = W as i32; // exclusive
80        let fb_bottom = H as i32; // exclusive
81
82        let area_left = area.top_left.x;
83        let area_top = area.top_left.y;
84        let area_right = area_left + area.size.width as i32; // exclusive
85        let area_bottom = area_top + area.size.height as i32; // exclusive
86
87        // Clip once
88        let clip_left = max(fb_left, area_left);
89        let clip_top = max(fb_top, area_top);
90        let clip_right = min(fb_right, area_right);
91        let clip_bottom = min(fb_bottom, area_bottom);
92
93        // Nothing to draw if fully outside
94        if clip_left >= clip_right || clip_top >= clip_bottom {
95            // Still must exhaust iterator for API contract
96            let _ = it
97                .by_ref()
98                .take((area.size.width as usize) * (area.size.height as usize))
99                .for_each(drop);
100            return Ok(());
101        }
102
103        let buf = self.buffer.buf_mut();
104
105        // Walk over the original (unclipped) area in row-major order, consuming colors
106        // For rows that are outside, we just consume and skip. For the clipped span we do a tight loop.
107        for y in area_top..area_bottom {
108            let inside_y = y >= clip_top && y < clip_bottom;
109
110            // Left run outside
111            let left_run = (clip_left - area_left).max(0) as usize;
112            // Middle run inside
113            let mid_run = if inside_y {
114                (clip_right - clip_left).max(0) as usize
115            } else {
116                // if the row is outside vertically, mid span is 0
117                0
118            };
119            // Right run outside
120            let right_run = (area_right - clip_right).max(0) as usize;
121
122            // Consume left outside span
123            for _ in 0..left_run {
124                let _ = it.next();
125            }
126
127            if inside_y {
128                let yi = y as usize;
129                let row_start = yi * W;
130                let x0 = clip_left as usize;
131                let idx0 = row_start + x0;
132
133                // Process contiguous inside span with minimal checks
134                for i in 0..mid_run {
135                    if let Some(fg) = it.next() {
136                        let idx = idx0 + i;
137                        let bg = buf[idx];
138                        buf[idx] = fg.blend(bg);
139                    } else {
140                        break;
141                    }
142                }
143            } else {
144                // Row is completely outside vertically: consume the mid span too
145                for _ in 0..((clip_right - clip_left).max(0) as usize) {
146                    let _ = it.next();
147                }
148            }
149
150            // Consume right outside span
151            for _ in 0..right_run {
152                let _ = it.next();
153            }
154        }
155
156        Ok(())
157    }
158
159    fn fill_solid(&mut self, area: &Rectangle, color: Self::Color) -> Result<(), Self::Error> {
160        use core::cmp::{max, min};
161
162        // Early out
163        let a = color.a();
164        if a == 0 {
165            return Ok(());
166        }
167
168        // Compute clipped rectangle
169        let fb_left = 0i32;
170        let fb_top = 0i32;
171        let fb_right = W as i32; // exclusive
172        let fb_bottom = H as i32; // exclusive
173
174        let area_left = area.top_left.x;
175        let area_top = area.top_left.y;
176        let area_right = area_left + area.size.width as i32; // exclusive
177        let area_bottom = area_top + area.size.height as i32; // exclusive
178
179        let clip_left = max(fb_left, area_left);
180        let clip_top = max(fb_top, area_top);
181        let clip_right = min(fb_right, area_right);
182        let clip_bottom = min(fb_bottom, area_bottom);
183
184        if clip_left >= clip_right || clip_top >= clip_bottom {
185            return Ok(());
186        }
187
188        let buf = self.buffer.buf_mut();
189
190        if a == 255 {
191            // Opaque: just overwrite the span rows with the opaque color
192            let oc: C = color.rgb();
193            for y in clip_top as usize..clip_bottom as usize {
194                let row_start = y * W;
195                let start = row_start + clip_left as usize;
196                let end = row_start + clip_right as usize;
197                for idx in start..end {
198                    buf[idx] = oc;
199                }
200            }
201            return Ok(());
202        }
203
204        // Alpha blend per pixel for the clipped span
205        for y in clip_top as usize..clip_bottom as usize {
206            let row_start = y * W;
207            let start = row_start + clip_left as usize;
208            let end = row_start + clip_right as usize;
209            for idx in start..end {
210                let bg = buf[idx];
211                buf[idx] = color.blend(bg);
212            }
213        }
214
215        Ok(())
216    }
217
218    fn clear(&mut self, color: Self::Color) -> Result<(), Self::Error> {
219        let a = color.a();
220        if a == 0 {
221            return Ok(());
222        }
223
224        let buf = self.buffer.buf_mut();
225
226        if a == 255 {
227            let oc: C = color.rgb();
228            for px in buf.iter_mut() {
229                *px = oc;
230            }
231            return Ok(());
232        }
233
234        for px in buf.iter_mut() {
235            let bg = *px;
236            *px = color.blend(bg);
237        }
238
239        Ok(())
240    }
241}