firefly_runtime/
color.rs

1use core::marker::PhantomData;
2use embedded_graphics::geometry::OriginDimensions;
3use embedded_graphics::pixelcolor::raw::RawU16;
4use embedded_graphics::pixelcolor::*;
5use embedded_graphics::prelude::*;
6use embedded_graphics::Pixel;
7
8/// Color optimized for fast rendering on the device.
9#[derive(PartialEq, Clone, Copy)]
10pub struct Rgb16(pub u8, pub u8);
11
12impl Rgb16 {
13    pub const fn from_rgb(r: u16, g: u16, b: u16) -> Self {
14        let r = r >> 3;
15        let g = g >> 2;
16        let b = b >> 3;
17        let raw = (b << 11) | ((g & 0b_0011_1111) << 5) | (r & 0b_0001_1111);
18        let raw = raw.to_le_bytes();
19        Self(!raw[0], !raw[1])
20    }
21
22    pub const fn into_rgb(self) -> (u8, u8, u8) {
23        let raw = u16::from_le_bytes([!self.0, !self.1]);
24        let r = (raw << 3) as u8;
25        let g = ((raw >> 5) << 2) as u8;
26        let b = ((raw >> 11) << 3) as u8;
27        (r, g, b)
28    }
29}
30
31impl PixelColor for Rgb16 {
32    type Raw = RawU16;
33}
34
35impl RgbColor for Rgb16 {
36    const MAX_R: u8 = 32;
37    const MAX_G: u8 = 64;
38    const MAX_B: u8 = 32;
39    const BLACK: Self = Self::from_rgb(0, 0, 0);
40    const RED: Self = Self::from_rgb(255, 0, 0);
41    const GREEN: Self = Self::from_rgb(0, 255, 0);
42    const BLUE: Self = Self::from_rgb(0, 0, 255);
43    const YELLOW: Self = Self::from_rgb(255, 255, 0);
44    const MAGENTA: Self = Self::from_rgb(255, 0, 255);
45    const CYAN: Self = Self::from_rgb(0, 255, 255);
46    const WHITE: Self = Self::from_rgb(255, 255, 255);
47
48    fn r(&self) -> u8 {
49        let (r, _, _) = self.into_rgb();
50        r
51    }
52
53    fn g(&self) -> u8 {
54        let (_, g, _) = self.into_rgb();
55        g
56    }
57
58    fn b(&self) -> u8 {
59        let (_, _, b) = self.into_rgb();
60        b
61    }
62}
63
64impl From<Rgb888> for Rgb16 {
65    fn from(c: Rgb888) -> Self {
66        let r = u16::from(c.r());
67        let g = u16::from(c.g());
68        let b = u16::from(c.b());
69        Self::from_rgb(r, g, b)
70    }
71}
72
73impl From<Rgb16> for Rgb888 {
74    fn from(c: Rgb16) -> Self {
75        let (mut r, mut g, mut b) = c.into_rgb();
76        if r == 0b1111_1000 {
77            r = 0xff;
78        }
79        if g == 0b1111_1100 {
80            g = 0xff;
81        }
82        if b == 0b1111_1000 {
83            b = 0xff;
84        }
85        Self::new(r, g, b)
86    }
87}
88
89// Convert 1/2/4 BPP image into 4 BPP ([`Gray4`]) color.
90pub(crate) struct BPPAdapter<'a, D, C>
91where
92    D: DrawTarget<Color = Gray4> + OriginDimensions,
93    C: PixelColor + IntoStorage<Storage = u8>,
94{
95    target: &'a mut D,
96    swaps: [Option<Gray4>; 16],
97    color: PhantomData<C>,
98}
99
100impl<'a, D, C> BPPAdapter<'a, D, C>
101where
102    D: DrawTarget<Color = Gray4> + OriginDimensions,
103    C: PixelColor + IntoStorage<Storage = u8>,
104{
105    pub fn new(target: &'a mut D, transp: u8, swaps: &'_ [u8]) -> Self {
106        Self {
107            target,
108            swaps: parse_swaps(transp, swaps),
109            color: PhantomData,
110        }
111    }
112}
113
114/// Required by the DrawTarget trait.
115impl<D, C> OriginDimensions for BPPAdapter<'_, D, C>
116where
117    D: DrawTarget<Color = Gray4> + OriginDimensions,
118    C: PixelColor + IntoStorage<Storage = u8>,
119{
120    fn size(&self) -> embedded_graphics::prelude::Size {
121        self.target.size()
122    }
123}
124
125impl<D, C> DrawTarget for BPPAdapter<'_, D, C>
126where
127    D: DrawTarget<Color = Gray4> + OriginDimensions,
128    C: PixelColor + IntoStorage<Storage = u8>,
129{
130    type Color = C;
131    type Error = D::Error;
132
133    fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
134    where
135        I: IntoIterator<Item = Pixel<Self::Color>>,
136    {
137        let iter = pixels.into_iter().filter_map(|Pixel(p, c)| {
138            let c = c.into_storage();
139            match self.swaps.get(c as usize) {
140                Some(Some(c)) => Some(Pixel(p, *c)),
141                _ => None,
142            }
143        });
144        self.target.draw_iter(iter)
145    }
146}
147
148/// Create RGB (or BGR) color from R, G, and B components in 0-255 range.
149pub trait FromRGB {
150    /// The white background color.
151    const BG: Self;
152    /// The primary black text color.
153    const PRIMARY: Self;
154    /// The dark green accent color.
155    const ACCENT: Self;
156    /// The dark red danger color.
157    const DANGER: Self;
158    /// The gray muted text color.
159    const MUTED: Self;
160
161    fn from_rgb(rgb: Rgb16) -> Self;
162}
163
164impl FromRGB for Rgb16 {
165    const BG: Self = Self::from_rgb(0xf4, 0xf4, 0xf4);
166    const PRIMARY: Self = Self::from_rgb(0x1a, 0x1c, 0x2c);
167    const ACCENT: Self = Self::from_rgb(0x38, 0xb7, 0x64);
168    const DANGER: Self = Self::from_rgb(0xb1, 0x3e, 0x53);
169    const MUTED: Self = Self::from_rgb(0x94, 0xb0, 0xc2);
170
171    fn from_rgb(rgb: Self) -> Self {
172        rgb
173    }
174}
175
176impl FromRGB for Rgb565 {
177    const BG: Self = new_rgb565(0xf4, 0xf4, 0xf4);
178    const PRIMARY: Self = new_rgb565(0x1a, 0x1c, 0x2c);
179    const ACCENT: Self = new_rgb565(0x38, 0xb7, 0x64);
180    const DANGER: Self = new_rgb565(0xb1, 0x3e, 0x53);
181    const MUTED: Self = new_rgb565(0x94, 0xb0, 0xc2);
182
183    fn from_rgb(rgb: Rgb16) -> Self {
184        let (r, g, b) = rgb.into_rgb();
185        Self::new(r, g, b)
186    }
187}
188
189impl FromRGB for Rgb888 {
190    const BG: Self = Self::new(0xf4, 0xf4, 0xf4);
191    const PRIMARY: Self = Self::new(0x1a, 0x1c, 0x2c);
192    const ACCENT: Self = Self::new(0x38, 0xb7, 0x64);
193    const DANGER: Self = Self::new(0xb1, 0x3e, 0x53);
194    const MUTED: Self = Self::new(0x94, 0xb0, 0xc2);
195
196    fn from_rgb(rgb: Rgb16) -> Self {
197        rgb.into()
198    }
199}
200
201const fn new_rgb565(r: u8, g: u8, b: u8) -> Rgb565 {
202    let r = r as u32 * Rgb565::MAX_R as u32 / Rgb888::MAX_R as u32;
203    let g = g as u32 * Rgb565::MAX_G as u32 / Rgb888::MAX_G as u32;
204    let b = b as u32 * Rgb565::MAX_B as u32 / Rgb888::MAX_B as u32;
205    debug_assert!(r < 256);
206    debug_assert!(g < 256);
207    debug_assert!(b < 256);
208    Rgb565::new(r as u8, g as u8, b as u8)
209}
210
211#[allow(clippy::get_first)]
212pub(crate) fn parse_swaps(transp: u8, swaps: &[u8]) -> [Option<Gray4>; 16] {
213    [
214        // 0-4
215        parse_color_l(transp, swaps.get(0)),
216        parse_color_r(transp, swaps.get(0)),
217        parse_color_l(transp, swaps.get(1)),
218        parse_color_r(transp, swaps.get(1)),
219        // 4-8
220        parse_color_l(transp, swaps.get(2)),
221        parse_color_r(transp, swaps.get(2)),
222        parse_color_l(transp, swaps.get(3)),
223        parse_color_r(transp, swaps.get(3)),
224        // 8-12
225        parse_color_l(transp, swaps.get(4)),
226        parse_color_r(transp, swaps.get(4)),
227        parse_color_l(transp, swaps.get(5)),
228        parse_color_r(transp, swaps.get(5)),
229        // 12-16
230        parse_color_l(transp, swaps.get(6)),
231        parse_color_r(transp, swaps.get(6)),
232        parse_color_l(transp, swaps.get(7)),
233        parse_color_r(transp, swaps.get(7)),
234    ]
235}
236
237/// Parse the high bits of a byte as a color.
238fn parse_color_r(transp: u8, c: Option<&u8>) -> Option<Gray4> {
239    let c = c?;
240    let c = c & 0b1111;
241    if c == transp {
242        return None;
243    }
244    Some(Gray4::new(c))
245}
246
247/// Parse the low bits of a byte as a color.
248fn parse_color_l(transp: u8, c: Option<&u8>) -> Option<Gray4> {
249    let c = c?;
250    let c = (c >> 4) & 0b1111;
251    if c == transp {
252        return None;
253    }
254    Some(Gray4::new(c))
255}