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}