embedded_graphics/mock_display/
color_mapping.rs

1use embedded_graphics_core::pixelcolor::{
2    Bgr555, Bgr565, Bgr888, BinaryColor, Gray2, Gray4, Gray8, GrayColor, Rgb555, Rgb565, Rgb888,
3    RgbColor, WebColors,
4};
5
6/// Mapping between `char`s and colors.
7///
8/// See the [module-level documentation](super) for a table of implemented mappings.
9pub trait ColorMapping: Into<Rgb888> {
10    /// Color used to display `None` values when `EG_FANCY_PANIC` is enabled.
11    ///
12    /// This color must be set to a color that isn't available in normal patterns to make it
13    /// distinguishable in the output. For non grayscale colors the default value should be used.
14    const NONE_COLOR: Rgb888 = Rgb888::new(128, 128, 128);
15
16    /// Converts a char into a color of type `C`.
17    fn char_to_color(c: char) -> Self;
18
19    /// Converts a color of type `C` into a char.
20    fn color_to_char(color: Self) -> char;
21}
22
23impl ColorMapping for BinaryColor {
24    fn char_to_color(c: char) -> Self {
25        match c {
26            '.' => BinaryColor::Off,
27            '#' => BinaryColor::On,
28            _ => panic!("Invalid char in pattern: '{}'", c),
29        }
30    }
31
32    fn color_to_char(color: Self) -> char {
33        match color {
34            BinaryColor::Off => '.',
35            BinaryColor::On => '#',
36        }
37    }
38}
39
40macro_rules! impl_gray_color_mapping {
41    ($type:ident, $radix:expr) => {
42        impl ColorMapping for $type {
43            const NONE_COLOR: Rgb888 = Rgb888::CSS_STEEL_BLUE;
44
45            fn char_to_color(c: char) -> Self {
46                if let Some(digit) = c.to_digit($radix) {
47                    Self::new(digit as u8)
48                } else {
49                    panic!("invalid char in pattern: '{}'", c)
50                }
51            }
52
53            fn color_to_char(color: Self) -> char {
54                core::char::from_digit(color.luma() as u32, $radix)
55                    .unwrap()
56                    .to_ascii_uppercase()
57            }
58        }
59    };
60}
61
62impl_gray_color_mapping!(Gray2, 4);
63impl_gray_color_mapping!(Gray4, 16);
64
65impl ColorMapping for Gray8 {
66    const NONE_COLOR: Rgb888 = Rgb888::CSS_STEEL_BLUE;
67
68    fn char_to_color(c: char) -> Self {
69        if let Some(digit) = c.to_digit(16) {
70            Self::new(digit as u8 * 0x11)
71        } else {
72            panic!("invalid char in pattern: '{}'", c);
73        }
74    }
75
76    fn color_to_char(color: Self) -> char {
77        let luma = color.luma();
78        let lower = luma & 0xF;
79        let upper = luma >> 4;
80
81        if lower != upper {
82            '?'
83        } else {
84            core::char::from_digit(lower as u32, 16)
85                .unwrap()
86                .to_ascii_uppercase()
87        }
88    }
89}
90
91macro_rules! impl_rgb_color_mapping {
92    ($type:ident) => {
93        impl ColorMapping for $type {
94            fn char_to_color(c: char) -> Self {
95                match c {
96                    'K' => Self::BLACK,
97                    'R' => Self::RED,
98                    'G' => Self::GREEN,
99                    'B' => Self::BLUE,
100                    'Y' => Self::YELLOW,
101                    'M' => Self::MAGENTA,
102                    'C' => Self::CYAN,
103                    'W' => Self::WHITE,
104                    _ => panic!("Invalid char in pattern: '{}'", c),
105                }
106            }
107
108            fn color_to_char(color: Self) -> char {
109                match color {
110                    Self::BLACK => 'K',
111                    Self::RED => 'R',
112                    Self::GREEN => 'G',
113                    Self::BLUE => 'B',
114                    Self::YELLOW => 'Y',
115                    Self::MAGENTA => 'M',
116                    Self::CYAN => 'C',
117                    Self::WHITE => 'W',
118                    _ => '?',
119                }
120            }
121        }
122    };
123}
124
125impl_rgb_color_mapping!(Rgb555);
126impl_rgb_color_mapping!(Bgr555);
127impl_rgb_color_mapping!(Rgb565);
128impl_rgb_color_mapping!(Bgr565);
129impl_rgb_color_mapping!(Rgb888);
130impl_rgb_color_mapping!(Bgr888);
131
132#[cfg(test)]
133mod tests {
134    use super::*;
135
136    #[test]
137    fn gray2_mapping() {
138        for luma in 0..4 {
139            let color = Gray2::new(luma);
140
141            assert_eq!(color, Gray2::char_to_color(Gray2::color_to_char(color)));
142        }
143    }
144
145    #[test]
146    fn gray4_mapping() {
147        for luma in 0..16 {
148            let color = Gray4::new(luma);
149
150            assert_eq!(color, Gray4::char_to_color(Gray4::color_to_char(color)));
151        }
152    }
153
154    #[test]
155    fn gray8_mapping() {
156        for luma in 0..16 {
157            let color = Gray8::new(luma * 0x11);
158
159            assert_eq!(color, Gray8::char_to_color(Gray8::color_to_char(color)));
160        }
161    }
162
163    #[test]
164    #[should_panic(expected = "invalid char in pattern: '4'")]
165    fn invalid_gray2_char_4() {
166        Gray2::char_to_color('4');
167    }
168
169    #[test]
170    #[should_panic(expected = "invalid char in pattern: 'A'")]
171    fn invalid_gray2_char_a() {
172        Gray2::char_to_color('A');
173    }
174
175    #[test]
176    #[should_panic(expected = "invalid char in pattern: 'G'")]
177    fn invalid_gray4_char_g() {
178        Gray2::char_to_color('G');
179    }
180
181    #[test]
182    #[should_panic(expected = "invalid char in pattern: 'G'")]
183    fn invalid_gray8_char_g() {
184        Gray8::char_to_color('G');
185    }
186}