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)]
46fn 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 r1 = c1.r() as u32;
76    let g1 = c1.g() as u32;
77    let b1 = c1.b() as u32;
78    let r2 = c2.r() as u32;
79    let g2 = c2.g() as u32;
80    let b2 = c2.b() as u32;
81
82    let r = ((r1 * (255 - a) + r2 * a) / 255) as u8;
83    let g = ((g1 * (255 - a) + g2 * a) / 255) as u8;
84    let b = ((b1 * (255 - a) + b2 * a) / 255) as u8;
85
86    Rgb565::new(r, g, b)
87}
88
89#[inline(always)]
90fn apply_blend_mode(fg: Rgb565, mode: BlendMode, bg: Rgb565) -> Rgb565 {
91    match mode {
92        BlendMode::Normal => fg,
93        BlendMode::Add => {
94            let r = (fg.r() as u16 + bg.r() as u16).min(31) as u8;
95            let g = (fg.g() as u16 + bg.g() as u16).min(63) as u8;
96            let b = (fg.b() as u16 + bg.b() as u16).min(31) as u8;
97            Rgb565::new(r, g, b)
98        }
99        BlendMode::Multiply => {
100            let r = ((fg.r() as u16 * bg.r() as u16) / 31) as u8;
101            let g = ((fg.g() as u16 * bg.g() as u16) / 63) as u8;
102            let b = ((fg.b() as u16 * bg.b() as u16) / 31) as u8;
103            Rgb565::new(r, g, b)
104        }
105        BlendMode::Screen => {
106            let r = (31 - ((31 - fg.r() as u16) * (31 - bg.r() as u16)) / 31) as u8;
107            let g = (63 - ((63 - fg.g() as u16) * (63 - bg.g() as u16)) / 63) as u8;
108            let b = (31 - ((31 - fg.b() as u16) * (31 - bg.b() as u16)) / 31) as u8;
109            Rgb565::new(r, g, b)
110        }
111    }
112}
113
114impl<D: DrawTarget<Color = Rgb565>> Compositor<D> for Dither {
115    fn plot(
116        target: &mut D,
117        x: i32,
118        y: i32,
119        color: Rgb565,
120        opacity: u8,
121        blend: BlendMode,
122        backdrop: Rgb565,
123    ) -> Result<(), D::Error> {
124        if !should_draw_at_opacity(x, y, opacity) {
125            return Ok(());
126        }
127        let color = apply_blend_mode(color, blend, backdrop);
128        target.draw_iter([Pixel(Point::new(x, y), color)])
129    }
130}
131
132impl<D: DrawTarget<Color = Rgb565> + PixelRead> Compositor<D> for Blend {
133    fn plot(
134        target: &mut D,
135        x: i32,
136        y: i32,
137        color: Rgb565,
138        opacity: u8,
139        blend: BlendMode,
140        backdrop: Rgb565,
141    ) -> Result<(), D::Error> {
142        if opacity == 0 {
143            return Ok(());
144        }
145        let bg = target.get_pixel(Point::new(x, y));
146        let blended = lerp_rgb565(bg, color, opacity);
147        let blended = apply_blend_mode(blended, blend, backdrop);
148        target.draw_iter([Pixel(Point::new(x, y), blended)])
149    }
150}
151
152#[derive(Clone, Copy, Debug, PartialEq, Eq)]
153pub enum ColorFormat {
154    Rgb565,
155    Rgb888,
156    Argb8888,
157}
158
159#[derive(Clone, Copy, Debug, PartialEq, Eq)]
160pub struct RenderBackendCaps {
161    pub color_format: ColorFormat,
162    pub supports_layers: bool,
163    pub supports_subpixel: bool,
164}
165
166impl RenderBackendCaps {
167    pub const fn software_rgb565() -> Self {
168        Self {
169            color_format: ColorFormat::Rgb565,
170            supports_layers: true,
171            supports_subpixel: false,
172        }
173    }
174}
175
176#[derive(Clone, Copy, Debug, PartialEq, Eq)]
177pub struct LayerState {
178    pub opacity: u8,
179    pub blend: BlendMode,
180    pub backdrop: Rgb565,
181}
182
183impl LayerState {
184    pub const fn normal() -> Self {
185        Self {
186            opacity: 255,
187            blend: BlendMode::Normal,
188            backdrop: Rgb565::BLACK,
189        }
190    }
191}