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 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}