1use crate::pixelcolor::{binary_color::*, gray_color::*, rgb_color::*};
2
3/// Convert color channel values from one bit depth to another.
4///
5/// Fixed point implementation of the conversion formula:
6/// `out = round(in * from_max / to_max)`
7const fn convert_channel<const FROM_MAX: u8, const TO_MAX: u8>(value: u8) -> u8 {
8 if TO_MAX != FROM_MAX {
9 const SHIFT: usize = 24;
10 const CONST_0_5: u32 = 1 << (SHIFT - 1);
11
12 // `value * from_max / to_max` scaled by `1 << SHIFT`.
13 let result: u32 = value as u32 * (((TO_MAX as u32) << SHIFT) / FROM_MAX as u32);
14
15 // Scale the result back down into an u8.
16 ((result + CONST_0_5) >> SHIFT) as u8
17 } else {
18 value
19 }
20}
21
22/// Calculates the luma value based on ITU-R BT.601.
23fn luma(color: Rgb888) -> u8 {
24 let r: u16 = u16::from(color.r());
25 let g: u16 = u16::from(color.g());
26 let b: u16 = u16::from(color.b());
27
28 // Original formula: 0.299 * R + 0.587 * G + 0.144 * B
29 ((r * 77 + g * 150 + b * 29 + 128) / 256) as u8
30}
31
32/// Macro to implement conversion between RGB color types.
33macro_rules! impl_rgb_conversion {
34 ($from_type:ident => $($to_type:ident),+) => {
35 $(impl From<$from_type> for $to_type {
36 fn from(other: $from_type) -> Self {
37 Self::new(
38 convert_channel::<{$from_type::MAX_R}, {$to_type::MAX_R}>(other.r()),
39 convert_channel::<{$from_type::MAX_G}, {$to_type::MAX_G}>(other.g()),
40 convert_channel::<{$from_type::MAX_B}, {$to_type::MAX_B}>(other.b()),
41 )
42 }
43 })*
44
45 impl $from_type {
46 pub(crate) const fn with_rgb888(r: u8, g: u8, b: u8) -> Self {
47 Self::new(
48 convert_channel::<{Rgb888::MAX_R}, {$from_type::MAX_R}>(r),
49 convert_channel::<{Rgb888::MAX_G}, {$from_type::MAX_G}>(g),
50 convert_channel::<{Rgb888::MAX_B}, {$from_type::MAX_B}>(b),
51 )
52 }
53 }
54 };
55}
56
57impl_rgb_conversion!(Rgb555 => Bgr555, Rgb565, Bgr565, Rgb666, Bgr666, Rgb888, Bgr888);
58impl_rgb_conversion!(Bgr555 => Rgb555, Rgb565, Bgr565, Rgb666, Bgr666, Rgb888, Bgr888);
59impl_rgb_conversion!(Rgb565 => Rgb555, Bgr555, Bgr565, Rgb666, Bgr666, Rgb888, Bgr888);
60impl_rgb_conversion!(Bgr565 => Rgb555, Bgr555, Rgb565, Rgb666, Bgr666, Rgb888, Bgr888);
61impl_rgb_conversion!(Rgb666 => Rgb555, Bgr555, Rgb565, Bgr666, Bgr565, Bgr888, Rgb888);
62impl_rgb_conversion!(Bgr666 => Rgb555, Bgr555, Rgb565, Rgb666, Bgr565, Bgr888, Rgb888);
63impl_rgb_conversion!(Rgb888 => Rgb555, Bgr555, Rgb565, Rgb666, Bgr666, Bgr565, Bgr888);
64impl_rgb_conversion!(Bgr888 => Rgb555, Bgr555, Rgb565, Rgb666, Bgr666, Bgr565, Rgb888);
65
66/// Macro to implement conversion between grayscale color types.
67macro_rules! impl_gray_conversion {
68 ($from_type:ident => $($to_type:ident),+) => {
69 $(impl From<$from_type> for $to_type {
70 fn from(other: $from_type) -> Self {
71 Self::new(convert_channel::<{$from_type::MAX_LUMA}, {$to_type::MAX_LUMA}>(other.luma()))
72 }
73 })*
74 };
75}
76
77impl_gray_conversion!(Gray2 => Gray4, Gray8);
78impl_gray_conversion!(Gray4 => Gray2, Gray8);
79impl_gray_conversion!(Gray8 => Gray2, Gray4);
80
81/// Macro to implement conversions between grayscale and RGB color types.
82macro_rules! impl_rgb_to_and_from_gray {
83 ($($gray_type:ident),+ => $rgb_type:ident) => {
84 $(impl From<$gray_type> for $rgb_type {
85 fn from(other: $gray_type) -> Self {
86 Self::new(
87 convert_channel::<{$gray_type::MAX_LUMA}, {$rgb_type::MAX_R}>(other.luma()),
88 convert_channel::<{$gray_type::MAX_LUMA}, {$rgb_type::MAX_G}>(other.luma()),
89 convert_channel::<{$gray_type::MAX_LUMA}, {$rgb_type::MAX_B}>(other.luma()),
90 )
91 }
92 })+
93
94 $(impl From<$rgb_type> for $gray_type {
95 fn from(other: $rgb_type) -> Self {
96 let intensity = luma(Rgb888::from(other));
97 Gray8::new(intensity).into()
98 }
99 })+
100 };
101
102 ($($gray_type:ident),+ => $rgb_type:ident, $($rest:ident),+) => {
103 impl_rgb_to_and_from_gray!($($gray_type),+ => $rgb_type);
104 impl_rgb_to_and_from_gray!($($gray_type),+ => $($rest),*);
105 }
106}
107
108impl_rgb_to_and_from_gray!(Gray2, Gray4, Gray8 => Rgb555, Bgr555, Rgb565, Bgr565, Rgb666, Bgr666, Rgb888, Bgr888);
109
110/// Macro to implement conversion from `BinaryColor` to RGB and grayscale types.
111macro_rules! impl_from_binary {
112 ($($type:ident),*) => {
113 $(impl From<BinaryColor> for $type {
114 fn from(color: BinaryColor) -> Self {
115 color.map_color(Self::BLACK, Self::WHITE)
116 }
117 })*
118 };
119}
120
121impl_from_binary!(
122 Rgb555, Bgr555, Rgb565, Bgr565, Rgb666, Bgr666, Rgb888, Bgr888, Gray2, Gray4, Gray8
123);
124
125/// Macro to implement conversion from grayscale types to `BinaryColor`.
126macro_rules! impl_gray_to_binary {
127 ($($type:ident),* ) => {
128 $(impl From<$type> for BinaryColor {
129 fn from(color: $type) -> Self {
130 (color.luma() >= $type::GRAY_50.luma()).into()
131 }
132 })*
133 };
134}
135
136impl_gray_to_binary!(Gray2, Gray4, Gray8);
137
138/// Macro to implement conversion from RGB types to `BinaryColor`.
139macro_rules! impl_rgb_to_binary {
140 ($($type:ident),*) => {
141 $(impl From<$type> for BinaryColor {
142 fn from(color: $type) -> Self {
143 (luma(Rgb888::from(color)) >= 128).into()
144 }
145 })*
146 };
147}
148
149impl_rgb_to_binary!(Rgb555, Bgr555, Rgb565, Bgr565, Rgb666, Bgr666, Rgb888, Bgr888);
150
151#[cfg(test)]
152mod tests {
153 use core::fmt::Debug;
154
155 use super::*;
156
157 #[test]
158 fn convert_rgb565_to_rgb888_and_back() {
159 for r in 0..=Rgb565::MAX_R {
160 let c = Rgb565::new(r, 0, 0);
161 let c2 = Rgb888::from(c);
162 let c3 = Rgb565::from(c2);
163
164 assert_eq!(c, c3);
165 }
166
167 for g in 0..=Rgb565::MAX_G {
168 let c = Rgb565::new(0, g, 0);
169 let c2 = Rgb888::from(c);
170 let c3 = Rgb565::from(c2);
171
172 assert_eq!(c, c3);
173 }
174
175 for b in 0..=Rgb565::MAX_B {
176 let c = Rgb565::new(0, 0, b);
177 let c2 = Rgb888::from(c);
178 let c3 = Rgb565::from(c2);
179
180 assert_eq!(c, c3);
181 }
182 }
183
184 /// Calls the given function with every combination of two sets of types.
185 ///
186 /// If only one set of types is given the same types will be used for both sets.
187 macro_rules! type_matrix {
188 ($function:ident; $from_type:ident => $to_type:ident) => {
189 $function::<$from_type, $to_type>();
190 };
191
192 ($function:ident; $from_type:ident => $to_type:ident, $($to_types:ident),+ ) => {
193 type_matrix!($function; $from_type => $to_type);
194 type_matrix!($function; $from_type => $($to_types),*);
195 };
196
197 ($function:ident; $from_type:ident, $($from_types:ident),+ => $($to_types:ident),+ ) => {
198 type_matrix!($function; $from_type => $($to_types),*);
199 type_matrix!($function; $($from_types),* => $($to_types),*);
200 };
201
202 ($function:ident; $($types:ident),+) => {
203 type_matrix!($function; $($types),* => $($types),*);
204 };
205 }
206
207 #[test]
208 fn rgb_to_rgb() {
209 fn test_rgb_to_rgb<FromC: RgbColor + Debug, ToC: RgbColor + From<FromC> + Debug>() {
210 assert_eq!(ToC::from(FromC::BLACK), ToC::BLACK);
211 assert_eq!(ToC::from(FromC::RED), ToC::RED);
212 assert_eq!(ToC::from(FromC::GREEN), ToC::GREEN);
213 assert_eq!(ToC::from(FromC::BLUE), ToC::BLUE);
214 assert_eq!(ToC::from(FromC::YELLOW), ToC::YELLOW);
215 assert_eq!(ToC::from(FromC::MAGENTA), ToC::MAGENTA);
216 assert_eq!(ToC::from(FromC::CYAN), ToC::CYAN);
217 assert_eq!(ToC::from(FromC::WHITE), ToC::WHITE);
218 }
219
220 type_matrix!(test_rgb_to_rgb; Rgb555, Bgr555, Rgb565, Bgr565, Rgb666, Bgr666, Rgb888, Bgr888);
221 }
222
223 #[test]
224 fn rgb_to_gray() {
225 fn test_rgb_to_gray<FromC: RgbColor + Debug, ToC: GrayColor + From<FromC> + Debug>() {
226 assert_eq!(ToC::from(FromC::BLACK), ToC::BLACK);
227 assert_eq!(ToC::from(FromC::WHITE), ToC::WHITE);
228 }
229
230 type_matrix!(test_rgb_to_gray; Rgb555, Bgr555, Rgb565, Bgr565, Rgb666, Bgr666, Rgb888, Bgr888 => Gray2, Gray4, Gray8);
231 }
232
233 #[test]
234 fn rgb_to_binary() {
235 fn test_rgb_to_binary<FromC: RgbColor + Debug, ToC>()
236 where
237 BinaryColor: From<FromC>,
238 {
239 assert_eq!(BinaryColor::from(FromC::BLACK), BinaryColor::Off);
240 assert_eq!(BinaryColor::from(FromC::WHITE), BinaryColor::On);
241 }
242
243 type_matrix!(test_rgb_to_binary; Rgb555, Bgr555, Rgb565, Bgr565, Rgb666, Bgr666, Rgb888, Bgr888 => BinaryColor);
244 }
245
246 #[test]
247 fn gray_to_gray() {
248 fn test_gray_to_gray<FromC: GrayColor + Debug, ToC: GrayColor + From<FromC> + Debug>() {
249 assert_eq!(ToC::from(FromC::BLACK), ToC::BLACK);
250 assert_eq!(ToC::from(FromC::WHITE), ToC::WHITE);
251 }
252
253 type_matrix!(test_gray_to_gray; Gray2, Gray4, Gray8);
254 }
255
256 #[test]
257 fn gray_to_rgb() {
258 fn test_gray_to_rgb<FromC: GrayColor + Debug, ToC: RgbColor + From<FromC> + Debug>() {
259 assert_eq!(ToC::from(FromC::BLACK), ToC::BLACK);
260 assert_eq!(ToC::from(FromC::WHITE), ToC::WHITE);
261 }
262
263 type_matrix!(test_gray_to_rgb; Gray2, Gray4, Gray8 => Rgb555, Bgr555, Rgb565, Bgr565, Rgb666, Bgr666, Rgb888, Bgr888);
264 }
265
266 #[test]
267 fn gray_to_binary() {
268 fn test_gray_to_binary<FromC: GrayColor + Debug, ToC>()
269 where
270 BinaryColor: From<FromC>,
271 {
272 assert_eq!(BinaryColor::from(FromC::BLACK), BinaryColor::Off);
273 assert_eq!(BinaryColor::from(FromC::WHITE), BinaryColor::On);
274 }
275
276 type_matrix!(test_gray_to_binary; Gray2, Gray4, Gray8 => BinaryColor);
277 }
278
279 #[test]
280 fn binary_to_rgb() {
281 fn test_binary_to_rgb<FromC, ToC: RgbColor + From<BinaryColor> + Debug>() {
282 assert_eq!(ToC::from(BinaryColor::Off), ToC::BLACK);
283 assert_eq!(ToC::from(BinaryColor::On), ToC::WHITE);
284 }
285
286 type_matrix!(test_binary_to_rgb; BinaryColor => Rgb555, Bgr555, Rgb565, Bgr565, Rgb666, Bgr666, Rgb888, Bgr888);
287 }
288
289 #[test]
290 fn binary_to_gray() {
291 fn test_binary_to_gray<FromC, ToC: GrayColor + From<BinaryColor> + Debug>() {
292 assert_eq!(ToC::from(BinaryColor::Off), ToC::BLACK);
293 assert_eq!(ToC::from(BinaryColor::On), ToC::WHITE);
294 }
295
296 type_matrix!(test_binary_to_gray; BinaryColor => Gray2, Gray4, Gray8);
297 }
298
299 #[test]
300 fn test_luma() {
301 assert_eq!(luma(Rgb888::BLACK), 0);
302 assert_eq!(luma(Rgb888::WHITE), 255);
303
304 assert_eq!(
305 luma(Rgb888::new(255, 255, 254)),
306 255,
307 "should be rounded upward"
308 );
309 }
310
311 fn test_channel_conversion<const FROM_MAX: u8, const TO_MAX: u8>() {
312 fn convert_channel_reference(value: u8, from_max: u8, to_max: u8) -> u8 {
313 ((value as u16 * to_max as u16 + from_max as u16 / 2) / from_max as u16) as u8
314 }
315
316 for value in 0..FROM_MAX {
317 assert_eq!(
318 convert_channel::<FROM_MAX, TO_MAX>(value),
319 convert_channel_reference(value, FROM_MAX, TO_MAX),
320 "from_max: {}, to_max: {}, value: {}",
321 FROM_MAX,
322 TO_MAX,
323 value,
324 );
325 }
326 }
327
328 const fn bits_to_max(bits: u8) -> u8 {
329 0xFF >> (8 - bits)
330 }
331
332 #[test]
333 fn channel_conversions_larger() {
334 test_channel_conversion::<{ bits_to_max(2) }, { bits_to_max(4) }>();
335 test_channel_conversion::<{ bits_to_max(2) }, { bits_to_max(5) }>();
336 test_channel_conversion::<{ bits_to_max(2) }, { bits_to_max(6) }>();
337 test_channel_conversion::<{ bits_to_max(2) }, { bits_to_max(8) }>();
338
339 test_channel_conversion::<{ bits_to_max(4) }, { bits_to_max(5) }>();
340 test_channel_conversion::<{ bits_to_max(4) }, { bits_to_max(6) }>();
341 test_channel_conversion::<{ bits_to_max(4) }, { bits_to_max(8) }>();
342
343 test_channel_conversion::<{ bits_to_max(5) }, { bits_to_max(6) }>();
344 test_channel_conversion::<{ bits_to_max(5) }, { bits_to_max(8) }>();
345
346 test_channel_conversion::<{ bits_to_max(6) }, { bits_to_max(8) }>();
347 }
348
349 #[test]
350 fn channel_conversions_smaller() {
351 test_channel_conversion::<{ bits_to_max(8) }, { bits_to_max(6) }>();
352 test_channel_conversion::<{ bits_to_max(8) }, { bits_to_max(5) }>();
353 test_channel_conversion::<{ bits_to_max(8) }, { bits_to_max(4) }>();
354 test_channel_conversion::<{ bits_to_max(8) }, { bits_to_max(2) }>();
355
356 test_channel_conversion::<{ bits_to_max(6) }, { bits_to_max(5) }>();
357 test_channel_conversion::<{ bits_to_max(6) }, { bits_to_max(4) }>();
358 test_channel_conversion::<{ bits_to_max(6) }, { bits_to_max(2) }>();
359
360 test_channel_conversion::<{ bits_to_max(5) }, { bits_to_max(4) }>();
361 test_channel_conversion::<{ bits_to_max(4) }, { bits_to_max(2) }>();
362
363 test_channel_conversion::<{ bits_to_max(4) }, { bits_to_max(2) }>();
364 }
365
366 #[test]
367 fn channel_conversions_identity() {
368 test_channel_conversion::<{ bits_to_max(8) }, { bits_to_max(8) }>();
369 test_channel_conversion::<{ bits_to_max(6) }, { bits_to_max(6) }>();
370 test_channel_conversion::<{ bits_to_max(5) }, { bits_to_max(5) }>();
371 test_channel_conversion::<{ bits_to_max(4) }, { bits_to_max(4) }>();
372 test_channel_conversion::<{ bits_to_max(2) }, { bits_to_max(2) }>();
373 }
374}
375