Skip to main content

embedded_gui/render/
compositor.rs

1use embedded_graphics_core::{
2    Pixel,
3    draw_target::DrawTarget,
4    geometry::Point,
5    pixelcolor::{Rgb565, RgbColor},
6};
7
8#[derive(Clone, Copy, Debug, PartialEq, Eq)]
9pub enum BlendMode {
10    Normal,
11    Add,
12    Multiply,
13    Screen,
14}
15
16pub trait PixelRead: DrawTarget {
17    fn get_pixel(&self, point: Point) -> Self::Color;
18}
19
20pub trait WindowedDrawTarget: DrawTarget {
21    fn set_window(
22        &mut self,
23        area: &embedded_graphics_core::primitives::Rectangle,
24    ) -> Result<(), Self::Error>;
25}
26
27pub trait Compositor<D: DrawTarget<Color = Rgb565>> {
28    fn plot(
29        target: &mut D,
30        x: i32,
31        y: i32,
32        color: Rgb565,
33        opacity: u8,
34        blend: BlendMode,
35        backdrop: Rgb565,
36    ) -> Result<(), D::Error>;
37}
38
39#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
40pub struct Dither;
41
42#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
43pub struct Blend;
44
45#[inline(always)]
46pub(crate) fn should_draw_at_opacity(x: i32, y: i32, opacity: u8) -> bool {
47    if opacity == 255 {
48        return true;
49    }
50    if opacity == 0 {
51        return false;
52    }
53    #[rustfmt::skip]
54    const BAYER: [u8; 16] = [
55         0,  8,  2, 10,
56        12,  4, 14,  6,
57         3, 11,  1,  9,
58        15,  7, 13,  5,
59    ];
60    let xi = (x & 3) as usize;
61    let yi = (y & 3) as usize;
62    let threshold = BAYER[yi * 4 + xi] * 16 + 8;
63    opacity > threshold
64}
65
66#[inline(always)]
67pub fn lerp_rgb565(c1: Rgb565, c2: Rgb565, alpha: u8) -> Rgb565 {
68    if alpha == 0 {
69        return c1;
70    }
71    if alpha == 255 {
72        return c2;
73    }
74    let a = alpha as u32;
75    let inv = 255 - a;
76    let r1 = c1.r() as u32;
77    let g1 = c1.g() as u32;
78    let b1 = c1.b() as u32;
79    let r2 = c2.r() as u32;
80    let g2 = c2.g() as u32;
81    let b2 = c2.b() as u32;
82
83    // Fast division-free /255: for x in 0..(255*63), ((x * 257 + 257) >> 16) == x / 255
84    let r_val = r1 * inv + r2 * a;
85    let g_val = g1 * inv + g2 * a;
86    let b_val = b1 * inv + b2 * a;
87
88    let r = ((r_val * 257 + 257) >> 16) as u8;
89    let g = ((g_val * 257 + 257) >> 16) as u8;
90    let b = ((b_val * 257 + 257) >> 16) as u8;
91
92    Rgb565::new(r, g, b)
93}
94
95#[inline(always)]
96pub(crate) fn apply_blend_mode(fg: Rgb565, mode: BlendMode, bg: Rgb565) -> Rgb565 {
97    match mode {
98        BlendMode::Normal => fg,
99        BlendMode::Add => {
100            let r = (fg.r() as u16 + bg.r() as u16).min(31) as u8;
101            let g = (fg.g() as u16 + bg.g() as u16).min(63) as u8;
102            let b = (fg.b() as u16 + bg.b() as u16).min(31) as u8;
103            Rgb565::new(r, g, b)
104        }
105        BlendMode::Multiply => {
106            let r = ((fg.r() as u16 * bg.r() as u16) / 31) as u8;
107            let g = ((fg.g() as u16 * bg.g() as u16) / 63) as u8;
108            let b = ((fg.b() as u16 * bg.b() as u16) / 31) as u8;
109            Rgb565::new(r, g, b)
110        }
111        BlendMode::Screen => {
112            let r = (31 - ((31 - fg.r() as u16) * (31 - bg.r() as u16)) / 31) as u8;
113            let g = (63 - ((63 - fg.g() as u16) * (63 - bg.g() as u16)) / 63) as u8;
114            let b = (31 - ((31 - fg.b() as u16) * (31 - bg.b() as u16)) / 31) as u8;
115            Rgb565::new(r, g, b)
116        }
117    }
118}
119
120impl<D: DrawTarget<Color = Rgb565>> Compositor<D> for Dither {
121    fn plot(
122        target: &mut D,
123        x: i32,
124        y: i32,
125        color: Rgb565,
126        opacity: u8,
127        blend: BlendMode,
128        backdrop: Rgb565,
129    ) -> Result<(), D::Error> {
130        if !should_draw_at_opacity(x, y, opacity) {
131            return Ok(());
132        }
133        let color = apply_blend_mode(color, blend, backdrop);
134        target.draw_iter([Pixel(Point::new(x, y), color)])
135    }
136}
137
138impl<D: DrawTarget<Color = Rgb565> + PixelRead> Compositor<D> for Blend {
139    fn plot(
140        target: &mut D,
141        x: i32,
142        y: i32,
143        color: Rgb565,
144        opacity: u8,
145        blend: BlendMode,
146        backdrop: Rgb565,
147    ) -> Result<(), D::Error> {
148        if opacity == 0 {
149            return Ok(());
150        }
151        let bg = target.get_pixel(Point::new(x, y));
152        let blended = lerp_rgb565(bg, color, opacity);
153        let blended = apply_blend_mode(blended, blend, backdrop);
154        target.draw_iter([Pixel(Point::new(x, y), blended)])
155    }
156}
157
158#[derive(Clone, Copy, Debug, PartialEq, Eq)]
159pub enum ColorFormat {
160    Rgb565,
161    Rgb888,
162    Argb8888,
163}
164
165#[derive(Clone, Copy, Debug, PartialEq, Eq)]
166pub struct RenderBackendCaps {
167    pub color_format: ColorFormat,
168    pub supports_layers: bool,
169    pub supports_subpixel: bool,
170}
171
172impl RenderBackendCaps {
173    pub const fn software_rgb565() -> Self {
174        Self {
175            color_format: ColorFormat::Rgb565,
176            supports_layers: true,
177            supports_subpixel: false,
178        }
179    }
180}
181
182#[derive(Clone, Copy, Debug, PartialEq, Eq)]
183pub struct LayerState {
184    pub opacity: u8,
185    pub blend: BlendMode,
186    pub backdrop: Rgb565,
187}
188
189impl LayerState {
190    pub const fn normal() -> Self {
191        Self {
192            opacity: 255,
193            blend: BlendMode::Normal,
194            backdrop: Rgb565::BLACK,
195        }
196    }
197}