sensehat_screen/
color.rs

1//! RGB color for LED pixels, with RGB565 rendering support.
2/// A single LED pixel color, with RGB565 rendering.
3///
4/// ```
5/// # extern crate sensehat_screen;
6/// # use sensehat_screen::color::{PixelColor, Rgb565};
7/// # fn main() {
8///     // FROM
9///     // convert directly from 3-bytes in a tuple
10///     let red: PixelColor = (0xFF, 0, 0).into();
11///     assert_eq!(red, PixelColor::RED);
12///
13///     let green: PixelColor = (0, 0xFF, 0).into();
14///     assert_eq!(green, PixelColor::GREEN);
15///
16///     // convert directly from Rgb565
17///     let blue_rgb565 = Rgb565::from_rgb(0, 0, 0xFF);
18///     let blue: PixelColor = blue_rgb565.into();
19///     assert_eq!(blue, PixelColor::new(0, 0, 0xF8));
20///
21///     // INTO
22///     // convert directly into a 3-bytes tuple
23///     let red_tuple: (u8, u8, u8) = PixelColor::RED.into();
24///     assert_eq!(red_tuple, (0xFF, 0, 0));
25///
26///     let yellow_tuple: (u8, u8, u8) = PixelColor::YELLOW.into();
27///     assert_eq!(yellow_tuple, (0xFF, 0xFF, 0));
28///
29///     // convert directly into Rgb565
30///     let blue_565: Rgb565 = PixelColor::BLUE.into();
31///     assert_eq!(blue_565, Rgb565::from_rgb(0, 0, 0xF8));
32/// # }
33/// ```
34///
35///
36use std::fmt;
37
38/// 24-bit RGB color pixel.
39///
40/// This is the fundamental representation for RGB colors.
41#[derive(Copy, Clone, Default, PartialEq)]
42#[cfg_attr(feature = "serde-support", derive(Serialize, Deserialize))]
43pub struct PixelColor {
44    pub red: u8,
45    pub green: u8,
46    pub blue: u8,
47}
48
49impl fmt::Debug for PixelColor {
50    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
51        write!(f, "#{:02X}{:02X}{:02X}", self.red, self.green, self.blue)
52    }
53}
54
55impl PixelColor {
56    pub const BLACK: PixelColor = PixelColor {
57        red: 0,
58        green: 0,
59        blue: 0,
60    };
61
62    pub const RED: PixelColor = PixelColor {
63        red: 0xFF,
64        green: 0,
65        blue: 0,
66    };
67
68    pub const BLUE: PixelColor = PixelColor {
69        red: 0,
70        green: 0,
71        blue: 0xFF,
72    };
73
74    pub const GREEN: PixelColor = PixelColor {
75        red: 0,
76        green: 0xFF,
77        blue: 0,
78    };
79
80    pub const WHITE: PixelColor = PixelColor {
81        red: 0xFF,
82        green: 0xFF,
83        blue: 0xFF,
84    };
85
86    pub const YELLOW: PixelColor = PixelColor {
87        red: 0xFF,
88        green: 0xFF,
89        blue: 0,
90    };
91
92    pub const CYAN: PixelColor = PixelColor {
93        red: 0,
94        green: 0xFF,
95        blue: 0xFF,
96    };
97
98    pub const MAGENTA: PixelColor = PixelColor {
99        red: 0xFF,
100        green: 0,
101        blue: 0xFF,
102    };
103
104    /// Create a new LED pixel color.
105    pub fn new(red: u8, green: u8, blue: u8) -> Self {
106        Self { red, green, blue }
107    }
108
109    /// Create a new LED pixel color from a pair of RGB565-encoded bytes.
110    pub fn from_rgb565_bytes(color: [u8; 2]) -> Self {
111        let rgb565: Rgb565 = color.into();
112        rgb565.into()
113    }
114
115    #[cfg(not(feature = "big-endian"))]
116    /// Encodes the current LED pixel color into a pair of RGB565-encoded bytes.
117    pub fn rgb565(&self) -> [u8; 2] {
118        Rgb565::from(self).split_le()
119    }
120
121    #[cfg(feature = "big-endian")]
122    /// Encodes the current LED pixel color into a pair of RGB565-encoded bytes.
123    pub fn rgb565(&self) -> [u8; 2] {
124        Rgb565::from(self).split_be()
125    }
126
127    /// Sets the brightness of this colour.
128    ///
129    /// The `scale` value should be between 0 and 1. Values outside this range
130    /// are clamped.
131    pub fn dim(self, mut scale: f32) -> PixelColor {
132        if scale > 1.0 {
133            scale = 1.0;
134        }
135        if scale < 0.0 {
136            scale = 0.0;
137        }
138        fn scale_byte(b: u8, scale: f32) -> u8 {
139            (f32::from(b) * scale) as u8
140        }
141        PixelColor {
142            red: scale_byte(self.red, scale),
143            green: scale_byte(self.green, scale),
144            blue: scale_byte(self.blue, scale),
145        }
146    }
147}
148
149impl From<Rgb565> for PixelColor {
150    fn from(color: Rgb565) -> Self {
151        let rgb565 = color.to_rgb();
152        PixelColor::new(rgb565.0, rgb565.1, rgb565.2)
153    }
154}
155
156impl From<(u8, u8, u8)> for PixelColor {
157    fn from(color: (u8, u8, u8)) -> Self {
158        PixelColor::new(color.0, color.1, color.2)
159    }
160}
161
162impl Into<(u8, u8, u8)> for PixelColor {
163    fn into(self) -> (u8, u8, u8) {
164        (self.red, self.green, self.blue)
165    }
166}
167
168/// RGB color stored as 16-bit digit, using RGB565 encoding/decoding.
169///
170/// ```
171/// # extern crate sensehat_screen;
172/// # use sensehat_screen::color::Rgb565;
173/// # fn main() {
174///     // convert directly from u16
175///     let red: Rgb565 = 0xF800.into();
176///     // convert from a 3-byte tuple
177///     let green: Rgb565 = (0, 0xFF, 0).into();
178///     assert_eq!(green, 0x07E0.into());
179///     // convert from a 2-byte array
180///     if cfg!(not(feature = "big-endian")) {
181///         let blue: Rgb565 = [0x1F, 0x00].into();
182///         assert_eq!(blue, 0x001F.into());
183///     }
184///
185///     if cfg!(feature = "big-endian") {
186///         let blue: Rgb565 = [0x00, 0x1F].into();
187///         assert_eq!(blue, 0x001F.into());
188///     }
189/// # }
190/// ```
191#[derive(Copy, Clone, Debug, Default, PartialEq)]
192#[cfg_attr(feature = "serde-support", derive(Serialize, Deserialize))]
193pub struct Rgb565(u16);
194
195impl Rgb565 {
196    /// Create `Rgb565` instance from red, green, and blue `u8` values.
197    pub fn from_rgb(red: u8, green: u8, blue: u8) -> Self {
198        let r = u16::from((red >> 3) & 0x1F);
199        let g = u16::from((green >> 2) & 0x3F);
200        let b = u16::from((blue >> 3) & 0x1F);
201        let rgb = (r << 11) + (g << 5) + b;
202        Rgb565(rgb)
203    }
204
205    /// Create `(u8, u8, u8)` instance from a `Rgb565` instance.
206    pub fn to_rgb(self) -> (u8, u8, u8) {
207        let red = (((self.0 & 0b1111_1000_0000_0000) >> 11) << 3) as u8;
208        let green = (((self.0 & 0b0000_0111_1110_0000) >> 5) << 2) as u8;
209        let blue = ((self.0 & 0b0000_0000_0001_1111) << 3) as u8;
210        (red, green, blue)
211    }
212
213    #[cfg(not(feature = "big-endian"))]
214    // Create `Rgb565` from a pair of little-endian bytes.
215    fn from_le(bytes: [u8; 2]) -> Self {
216        let lo = u16::from(bytes[1]) << 8;
217        let hi = u16::from(bytes[0]);
218        Rgb565(hi | lo)
219    }
220
221    #[cfg(feature = "big-endian")]
222    // Create `Rgb565` from a pair of big-endian bytes.
223    fn from_be(bytes: [u8; 2]) -> Self {
224        let lo = u16::from(bytes[0]) << 8;
225        let hi = u16::from(bytes[1]);
226        Rgb565(hi | lo)
227    }
228
229    #[cfg(not(feature = "big-endian"))]
230    // Consume the current `Rgb565` and create `[u8; 2]`, with little-endian format.
231    fn split_le(self) -> [u8; 2] {
232        let lo = (self.0 & 0x00FF) as u8;
233        let hi = (self.0.swap_bytes() & 0x00FF) as u8;
234        [lo, hi]
235    }
236
237    #[cfg(feature = "big-endian")]
238    // Consume the current `Rgb565` and create `[u8; 2]`, with big-endian format.
239    fn split_be(self) -> [u8; 2] {
240        let lo = (self.0 & 0x00FF) as u8;
241        let hi = (self.0.swap_bytes() & 0x00FF) as u8;
242        [hi, lo]
243    }
244}
245
246impl Into<u16> for Rgb565 {
247    fn into(self) -> u16 {
248        self.0
249    }
250}
251
252impl From<u16> for Rgb565 {
253    fn from(bytes: u16) -> Self {
254        Rgb565(bytes)
255    }
256}
257#[cfg(not(feature = "big-endian"))]
258impl Into<[u8; 2]> for Rgb565 {
259    fn into(self) -> [u8; 2] {
260        Rgb565::split_le(self)
261    }
262}
263
264#[cfg(not(feature = "big-endian"))]
265impl From<[u8; 2]> for Rgb565 {
266    fn from(bytes: [u8; 2]) -> Self {
267        Rgb565::from_le(bytes)
268    }
269}
270
271#[cfg(feature = "big-endian")]
272impl Into<[u8; 2]> for Rgb565 {
273    fn into(self) -> [u8; 2] {
274        Rgb565::split_be(self)
275    }
276}
277
278#[cfg(feature = "big-endian")]
279impl From<[u8; 2]> for Rgb565 {
280    fn from(bytes: [u8; 2]) -> Self {
281        Rgb565::from_be(bytes)
282    }
283}
284
285impl From<(u8, u8, u8)> for Rgb565 {
286    fn from(color: (u8, u8, u8)) -> Self {
287        Rgb565::from_rgb(color.0, color.1, color.2)
288    }
289}
290
291impl Into<(u8, u8, u8)> for Rgb565 {
292    fn into(self) -> (u8, u8, u8) {
293        self.to_rgb()
294    }
295}
296
297impl From<PixelColor> for Rgb565 {
298    fn from(color: PixelColor) -> Self {
299        Rgb565::from_rgb(color.red, color.green, color.blue)
300    }
301}
302
303impl<'a> From<&'a PixelColor> for Rgb565 {
304    fn from(color: &'a PixelColor) -> Self {
305        Rgb565::from_rgb(color.red, color.green, color.blue)
306    }
307}
308
309/// Common trait for types that can be rendered into a `PixelFrame`, with a
310/// background color.
311pub trait BackgroundColor {
312    /// Sets the background color.
313    fn set_background_color(&mut self, color: PixelColor);
314    /// Gets the background color.
315    fn get_background_color(&self) -> PixelColor;
316}
317
318/// Common trait for types that can be rendered into a `PixelFrame`, with a
319/// stroke color.
320pub trait StrokeColor {
321    /// Sets the stroke color.
322    fn set_stroke_color(&mut self, color: PixelColor);
323    /// Gets the stroke color.
324    fn get_stroke_color(&self) -> PixelColor;
325}
326
327#[cfg(test)]
328mod tests {
329    use super::*;
330
331    #[cfg(not(feature = "big-endian"))]
332    #[test]
333    fn color_pixel_encodes_rgb_into_2_bytes_rgb565_with_losses() {
334        // black 5-bit, 6-bit, 5-bit resolution
335        assert_eq!(
336            PixelColor::from_rgb565_bytes([0x00, 0x00]),
337            PixelColor::new(0x00, 0x00, 0x00)
338        );
339        // white 5-bit, 6-bit, 5-bit resolution
340        assert_eq!(
341            PixelColor::from_rgb565_bytes([0xFF, 0xFF]),
342            PixelColor::new(0xF8, 0xFC, 0xF8)
343        );
344        // 100% green - 6-bit resolution
345        assert_eq!(
346            PixelColor::from_rgb565_bytes([0xE0, 0x07]),
347            PixelColor::new(0x00, 0xFC, 0x00)
348        );
349    }
350
351    #[cfg(feature = "big-endian")]
352    #[test]
353    fn color_pixel_encodes_rgb_into_2_bytes_rgb565_with_losses() {
354        // black 5-bit, 6-bit, 5-bit resolution
355        assert_eq!(
356            PixelColor::from_rgb565_bytes([0x00, 0x00]),
357            PixelColor::new(0x00, 0x00, 0x00)
358        );
359        // white 5-bit, 6-bit, 5-bit resolution
360        assert_eq!(
361            PixelColor::from_rgb565_bytes([0xFF, 0xFF]),
362            PixelColor::new(0xF8, 0xFC, 0xF8)
363        );
364        // 100% green - 6-bit resolution
365        assert_eq!(
366            PixelColor::from_rgb565_bytes([0x07, 0xE0]),
367            PixelColor::new(0x00, 0xFC, 0x00)
368        );
369    }
370
371    #[cfg(not(feature = "big-endian"))]
372    #[test]
373    fn convert_rgb565_to_byte_array() {
374        let bytes = [0xFF, 0xFF];
375        assert_eq!(Rgb565::from(bytes), Rgb565(0xFFFF));
376        let bytes = [0xE0, 0x07];
377        assert_eq!(Rgb565::from(bytes), Rgb565(0x07E0));
378        let bytes = [0x1F, 0x00];
379        assert_eq!(Rgb565::from(bytes), Rgb565(0x001F));
380    }
381
382    #[cfg(feature = "big-endian")]
383    #[test]
384    fn convert_rgb565_to_byte_array() {
385        let bytes = [0xFF, 0xFF];
386        assert_eq!(Rgb565::from(bytes), Rgb565(0xFFFF));
387        let bytes = [0x07, 0xE0];
388        assert_eq!(Rgb565::from(bytes), Rgb565(0x07E0));
389        let bytes = [0x00, 0x1F];
390        assert_eq!(Rgb565::from(bytes), Rgb565(0x001F));
391    }
392
393    #[cfg(not(feature = "big-endian"))]
394    #[test]
395    fn convert_byte_array_to_rgb565() {
396        let rgb: [u8; 2] = Rgb565(0x07E0).into();
397        assert_eq!(rgb, [0xE0, 0x07]);
398    }
399
400    #[cfg(feature = "big-endian")]
401    #[test]
402    fn convert_byte_array_to_rgb565() {
403        let rgb: [u8; 2] = Rgb565(0x07E0).into();
404        assert_eq!(rgb, [0x07, 0xE0]);
405    }
406
407    #[cfg(not(feature = "big-endian"))]
408    #[test]
409    fn color_pixel_converts_rgb_into_2_bytes_rgb565() {
410        let white_pixel = PixelColor::WHITE;
411        assert_eq!(white_pixel.rgb565(), [0xFF, 0xFF]);
412
413        let red_pixel = PixelColor::RED;
414        assert_eq!(red_pixel.rgb565(), [0x00, 0xF8]);
415
416        let green_pixel = PixelColor::GREEN;
417        assert_eq!(green_pixel.rgb565(), [0xE0, 0x07]);
418
419        let blue_pixel = PixelColor::BLUE;
420        assert_eq!(blue_pixel.rgb565(), [0x1F, 0x00]);
421    }
422
423    #[cfg(feature = "big-endian")]
424    #[test]
425    fn color_pixel_converts_rgb_into_2_bytes_rgb565() {
426        let white_pixel = PixelColor::WHITE;
427        assert_eq!(white_pixel.rgb565(), [0xFF, 0xFF]);
428
429        let red_pixel = PixelColor::RED;
430        assert_eq!(red_pixel.rgb565(), [0xF8, 0x00]);
431
432        let green_pixel = PixelColor::GREEN;
433        assert_eq!(green_pixel.rgb565(), [0x07, 0xE0]);
434
435        let blue_pixel = PixelColor::BLUE;
436        assert_eq!(blue_pixel.rgb565(), [0x00, 0x1F]);
437    }
438}