epd_waveshare/
color.rs

1//! B/W Color for EPDs
2//!
3//! EPD representation of multicolor with separate buffers
4//! for each bit makes it hard to properly represent colors here
5
6#[cfg(feature = "graphics")]
7use embedded_graphics_core::pixelcolor::BinaryColor;
8#[cfg(feature = "graphics")]
9use embedded_graphics_core::pixelcolor::PixelColor;
10
11/// When trying to parse u8 to one of the color types
12#[derive(Debug, PartialEq, Eq)]
13pub struct OutOfColorRangeParseError(u8);
14impl core::fmt::Display for OutOfColorRangeParseError {
15    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
16        write!(f, "Outside of possible Color Range: {}", self.0)
17    }
18}
19
20impl OutOfColorRangeParseError {
21    fn _new(size: u8) -> OutOfColorRangeParseError {
22        OutOfColorRangeParseError(size)
23    }
24}
25
26/// Only for the Black/White-Displays
27// TODO : 'color' is not a good name for black and white, rename it to BiColor/BWColor ?
28#[derive(Clone, Copy, PartialEq, Eq, Debug)]
29pub enum Color {
30    /// Black color
31    Black,
32    /// White color
33    White,
34}
35
36/// Only for the Black/White/Color-Displays
37#[derive(Clone, Copy, PartialEq, Eq, Debug)]
38pub enum TriColor {
39    /// Black color
40    Black,
41    /// White color
42    White,
43    /// Chromatic color
44    Chromatic,
45}
46
47/// For the 7 Color Displays
48#[derive(Clone, Copy, PartialEq, Eq, Debug)]
49pub enum OctColor {
50    /// Black Color
51    Black = 0x00,
52    /// White Color
53    White = 0x01,
54    /// Green Color
55    Green = 0x02,
56    /// Blue Color
57    Blue = 0x03,
58    /// Red Color
59    Red = 0x04,
60    /// Yellow Color
61    Yellow = 0x05,
62    /// Orange Color
63    Orange = 0x06,
64    /// HiZ / Clean Color
65    HiZ = 0x07,
66}
67
68/// Color trait for use in `Display`s
69pub trait ColorType {
70    /// Number of bit used to represent this color type in a single buffer.
71    /// To get the real number of bits per pixel you should multiply this by `BUFFER_COUNT`
72    const BITS_PER_PIXEL_PER_BUFFER: usize;
73
74    /// Number of buffer used to represent this color type
75    /// splitted buffer like tricolo is 2, otherwise this should be 1.
76    const BUFFER_COUNT: usize;
77
78    /// Return the data used to set a pixel color
79    ///
80    /// * bwrbit is used to tell the value of the unused bit when a chromatic
81    ///   color is set (TriColor only as for now)
82    /// * pos is the pixel position in the line, used to know which pixels must be set
83    ///
84    /// Return values are :
85    /// * .0 is the mask used to exclude this pixel from the byte (eg: 0x7F in BiColor)
86    /// * .1 are the bits used to set the color in the byte (eg: 0x80 in BiColor)
87    ///      this is u16 because we set 2 bytes in case of split buffer
88    fn bitmask(&self, bwrbit: bool, pos: u32) -> (u8, u16);
89}
90
91impl ColorType for Color {
92    const BITS_PER_PIXEL_PER_BUFFER: usize = 1;
93    const BUFFER_COUNT: usize = 1;
94    fn bitmask(&self, _bwrbit: bool, pos: u32) -> (u8, u16) {
95        let bit = 0x80 >> (pos % 8);
96        match self {
97            Color::Black => (!bit, 0u16),
98            Color::White => (!bit, bit as u16),
99        }
100    }
101}
102
103impl ColorType for TriColor {
104    const BITS_PER_PIXEL_PER_BUFFER: usize = 1;
105    const BUFFER_COUNT: usize = 2;
106    fn bitmask(&self, bwrbit: bool, pos: u32) -> (u8, u16) {
107        let bit = 0x80 >> (pos % 8);
108        match self {
109            TriColor::Black => (!bit, 0u16),
110            TriColor::White => (!bit, bit as u16),
111            TriColor::Chromatic => (
112                !bit,
113                if bwrbit {
114                    (bit as u16) << 8
115                } else {
116                    (bit as u16) << 8 | bit as u16
117                },
118            ),
119        }
120    }
121}
122
123impl ColorType for OctColor {
124    const BITS_PER_PIXEL_PER_BUFFER: usize = 4;
125    const BUFFER_COUNT: usize = 1;
126    fn bitmask(&self, _bwrbit: bool, pos: u32) -> (u8, u16) {
127        let mask = !(0xF0 >> ((pos % 2) * 4));
128        let bits = self.get_nibble() as u16;
129        (mask, if pos % 2 == 1 { bits } else { bits << 4 })
130    }
131}
132
133#[cfg(feature = "graphics")]
134impl From<BinaryColor> for OctColor {
135    fn from(b: BinaryColor) -> OctColor {
136        match b {
137            BinaryColor::On => OctColor::Black,
138            BinaryColor::Off => OctColor::White,
139        }
140    }
141}
142
143#[cfg(feature = "graphics")]
144impl From<OctColor> for embedded_graphics_core::pixelcolor::Rgb888 {
145    fn from(b: OctColor) -> Self {
146        let (r, g, b) = b.rgb();
147        Self::new(r, g, b)
148    }
149}
150
151#[cfg(feature = "graphics")]
152impl From<embedded_graphics_core::pixelcolor::Rgb888> for OctColor {
153    fn from(p: embedded_graphics_core::pixelcolor::Rgb888) -> OctColor {
154        use embedded_graphics_core::prelude::RgbColor;
155        let colors = [
156            OctColor::Black,
157            OctColor::White,
158            OctColor::Green,
159            OctColor::Blue,
160            OctColor::Red,
161            OctColor::Yellow,
162            OctColor::Orange,
163            OctColor::HiZ,
164        ];
165        // if the user has already mapped to the right color space, it will just be in the list
166        if let Some(found) = colors.iter().find(|c| c.rgb() == (p.r(), p.g(), p.b())) {
167            return *found;
168        }
169
170        // This is not ideal but just pick the nearest color
171        *colors
172            .iter()
173            .map(|c| (c, c.rgb()))
174            .map(|(c, (r, g, b))| {
175                let dist = (i32::from(r) - i32::from(p.r())).pow(2)
176                    + (i32::from(g) - i32::from(p.g())).pow(2)
177                    + (i32::from(b) - i32::from(p.b())).pow(2);
178                (c, dist)
179            })
180            .min_by_key(|(_c, dist)| *dist)
181            .map(|(c, _)| c)
182            .unwrap_or(&OctColor::White)
183    }
184}
185
186#[cfg(feature = "graphics")]
187impl From<embedded_graphics_core::pixelcolor::raw::RawU4> for OctColor {
188    fn from(b: embedded_graphics_core::pixelcolor::raw::RawU4) -> Self {
189        use embedded_graphics_core::prelude::RawData;
190        OctColor::from_nibble(b.into_inner()).unwrap()
191    }
192}
193
194#[cfg(feature = "graphics")]
195impl PixelColor for OctColor {
196    type Raw = embedded_graphics_core::pixelcolor::raw::RawU4;
197}
198
199impl OctColor {
200    /// Gets the Nibble representation of the Color as needed by the display
201    pub fn get_nibble(self) -> u8 {
202        self as u8
203    }
204    /// Converts two colors into a single byte for the Display
205    pub fn colors_byte(a: OctColor, b: OctColor) -> u8 {
206        a.get_nibble() << 4 | b.get_nibble()
207    }
208
209    ///Take the nibble (lower 4 bits) and convert to an OctColor if possible
210    pub fn from_nibble(nibble: u8) -> Result<OctColor, OutOfColorRangeParseError> {
211        match nibble & 0xf {
212            0x00 => Ok(OctColor::Black),
213            0x01 => Ok(OctColor::White),
214            0x02 => Ok(OctColor::Green),
215            0x03 => Ok(OctColor::Blue),
216            0x04 => Ok(OctColor::Red),
217            0x05 => Ok(OctColor::Yellow),
218            0x06 => Ok(OctColor::Orange),
219            0x07 => Ok(OctColor::HiZ),
220            e => Err(OutOfColorRangeParseError(e)),
221        }
222    }
223    ///Split the nibbles of a single byte and convert both to an OctColor if possible
224    pub fn split_byte(byte: u8) -> Result<(OctColor, OctColor), OutOfColorRangeParseError> {
225        let low = OctColor::from_nibble(byte & 0xf)?;
226        let high = OctColor::from_nibble((byte >> 4) & 0xf)?;
227        Ok((high, low))
228    }
229    /// Converts to limited range of RGB values.
230    pub fn rgb(self) -> (u8, u8, u8) {
231        match self {
232            OctColor::White => (0xff, 0xff, 0xff),
233            OctColor::Black => (0x00, 0x00, 0x00),
234            OctColor::Green => (0x00, 0xff, 0x00),
235            OctColor::Blue => (0x00, 0x00, 0xff),
236            OctColor::Red => (0xff, 0x00, 0x00),
237            OctColor::Yellow => (0xff, 0xff, 0x00),
238            OctColor::Orange => (0xff, 0x80, 0x00),
239            OctColor::HiZ => (0x80, 0x80, 0x80), /* looks greyish */
240        }
241    }
242}
243//TODO: Rename get_bit_value to bit() and get_byte_value to byte() ?
244
245impl Color {
246    /// Get the color encoding of the color for one bit
247    pub fn get_bit_value(self) -> u8 {
248        match self {
249            Color::White => 1u8,
250            Color::Black => 0u8,
251        }
252    }
253
254    /// Gets a full byte of black or white pixels
255    pub fn get_byte_value(self) -> u8 {
256        match self {
257            Color::White => 0xff,
258            Color::Black => 0x00,
259        }
260    }
261
262    /// Parses from u8 to Color
263    fn from_u8(val: u8) -> Self {
264        match val {
265            0 => Color::Black,
266            1 => Color::White,
267            e => panic!(
268                "DisplayColor only parses 0 and 1 (Black and White) and not `{}`",
269                e
270            ),
271        }
272    }
273
274    /// Returns the inverse of the given color.
275    ///
276    /// Black returns White and White returns Black
277    pub fn inverse(self) -> Color {
278        match self {
279            Color::White => Color::Black,
280            Color::Black => Color::White,
281        }
282    }
283}
284
285impl From<u8> for Color {
286    fn from(value: u8) -> Self {
287        Color::from_u8(value)
288    }
289}
290
291#[cfg(feature = "graphics")]
292impl PixelColor for Color {
293    type Raw = ();
294}
295
296#[cfg(feature = "graphics")]
297impl From<BinaryColor> for Color {
298    fn from(b: BinaryColor) -> Color {
299        match b {
300            BinaryColor::On => Color::Black,
301            BinaryColor::Off => Color::White,
302        }
303    }
304}
305
306#[cfg(feature = "graphics")]
307impl From<embedded_graphics_core::pixelcolor::Rgb888> for Color {
308    fn from(rgb: embedded_graphics_core::pixelcolor::Rgb888) -> Self {
309        use embedded_graphics_core::pixelcolor::RgbColor;
310        if rgb == RgbColor::BLACK {
311            Color::Black
312        } else if rgb == RgbColor::WHITE {
313            Color::White
314        } else {
315            // choose closest color
316            if (rgb.r() as u16 + rgb.g() as u16 + rgb.b() as u16) > 255 * 3 / 2 {
317                Color::White
318            } else {
319                Color::Black
320            }
321        }
322    }
323}
324
325#[cfg(feature = "graphics")]
326impl From<Color> for embedded_graphics_core::pixelcolor::Rgb888 {
327    fn from(color: Color) -> Self {
328        use embedded_graphics_core::pixelcolor::RgbColor;
329        match color {
330            Color::Black => embedded_graphics_core::pixelcolor::Rgb888::BLACK,
331            Color::White => embedded_graphics_core::pixelcolor::Rgb888::WHITE,
332        }
333    }
334}
335
336impl TriColor {
337    /// Get the color encoding of the color for one bit
338    pub fn get_bit_value(self) -> u8 {
339        match self {
340            TriColor::White => 1u8,
341            TriColor::Black | TriColor::Chromatic => 0u8,
342        }
343    }
344
345    /// Gets a full byte of black or white pixels
346    pub fn get_byte_value(self) -> u8 {
347        match self {
348            TriColor::White => 0xff,
349            TriColor::Black | TriColor::Chromatic => 0x00,
350        }
351    }
352}
353
354#[cfg(feature = "graphics")]
355impl PixelColor for TriColor {
356    type Raw = ();
357}
358
359#[cfg(feature = "graphics")]
360impl From<BinaryColor> for TriColor {
361    fn from(b: BinaryColor) -> TriColor {
362        match b {
363            BinaryColor::On => TriColor::Black,
364            BinaryColor::Off => TriColor::White,
365        }
366    }
367}
368#[cfg(feature = "graphics")]
369impl From<embedded_graphics_core::pixelcolor::Rgb888> for TriColor {
370    fn from(rgb: embedded_graphics_core::pixelcolor::Rgb888) -> Self {
371        use embedded_graphics_core::pixelcolor::RgbColor;
372        if rgb == RgbColor::BLACK {
373            TriColor::Black
374        } else if rgb == RgbColor::WHITE {
375            TriColor::White
376        } else {
377            // there is no good approximation here since we don't know which color is 'chromatic'
378            TriColor::Chromatic
379        }
380    }
381}
382#[cfg(feature = "graphics")]
383impl From<TriColor> for embedded_graphics_core::pixelcolor::Rgb888 {
384    fn from(tri_color: TriColor) -> Self {
385        use embedded_graphics_core::pixelcolor::RgbColor;
386        match tri_color {
387            TriColor::Black => embedded_graphics_core::pixelcolor::Rgb888::BLACK,
388            TriColor::White => embedded_graphics_core::pixelcolor::Rgb888::WHITE,
389            // assume chromatic is red
390            TriColor::Chromatic => embedded_graphics_core::pixelcolor::Rgb888::new(255, 0, 0),
391        }
392    }
393}
394
395#[cfg(test)]
396mod tests {
397    use super::*;
398
399    #[test]
400    fn from_u8() {
401        assert_eq!(Color::Black, Color::from(0u8));
402        assert_eq!(Color::White, Color::from(1u8));
403    }
404
405    // test all values aside from 0 and 1 which all should panic
406    #[test]
407    fn from_u8_panic() {
408        for val in 2..=u8::MAX {
409            extern crate std;
410            let result = std::panic::catch_unwind(|| Color::from(val));
411            assert!(result.is_err());
412        }
413    }
414
415    #[test]
416    fn u8_conversion_black() {
417        assert_eq!(Color::from(Color::Black.get_bit_value()), Color::Black);
418        assert_eq!(Color::from(0u8).get_bit_value(), 0u8);
419    }
420
421    #[test]
422    fn u8_conversion_white() {
423        assert_eq!(Color::from(Color::White.get_bit_value()), Color::White);
424        assert_eq!(Color::from(1u8).get_bit_value(), 1u8);
425    }
426
427    #[test]
428    fn test_oct() {
429        let left = OctColor::Red;
430        let right = OctColor::Green;
431        assert_eq!(
432            OctColor::split_byte(OctColor::colors_byte(left, right)),
433            Ok((left, right))
434        );
435    }
436}