1use core::array;
10
11use embedded_graphics::pixelcolor::*;
12
13#[derive(Debug, Clone)]
17#[cfg_attr(feature = "defmt", derive(defmt::Format))]
18pub struct Colormap<T: Copy, const N: usize>([T; N]);
19
20pub trait Linear<T> {
26 fn linear(start: T, end: T) -> Self;
28}
29
30pub trait Invert {
34 fn invert(self) -> Self;
36}
37
38pub trait Screen {
44 fn screen(self, other: Self, start: Self, end: Self) -> Self;
47}
48
49pub trait WeightedAvg {
56 fn weighted_avg(
60 self,
61 other: Self,
62 start: Self,
63 end: Self,
64 other_start: Self,
65 other_end: Self,
66 ) -> Self;
67}
68
69impl<T: Copy, const N: usize> Colormap<T, N> {
70 pub const fn first(&self) -> T {
72 let Colormap(array) = self;
73
74 array[0]
75 }
76
77 pub const fn last(&self) -> T {
79 let Colormap(array) = self;
80
81 array[N - 1]
82 }
83}
84
85macro_rules! impl_colormap {
86 (
87 $(
88 $color_ident:ident, $color_type:ty, $array_length:literal, $into_index:expr,
89 )*
90 ) => {
91 $(
92 impl<T: Copy> Colormap<T, $array_length> {
93 pub fn get(&self, $color_ident: $color_type) -> T {
95 let Colormap(array) = self;
96 let index: usize = $into_index.into();
97
98 array[index % $array_length]
99 }
100 }
101 )*
102 }
103}
104
105impl_colormap! {
106 color, BinaryColor, 2, color.is_on(),
107 color, Gray2, 4, color.luma(),
108 color, Gray4, 16, color.luma(),
109 color, Gray8, 256, color.luma(),
110}
111
112const fn convert_channel<const N: usize>(value: u8, start: u8, end: u8) -> u8 {
113 const SHIFT: usize = 23;
114 const CONST_0_5: i32 = 1 << (SHIFT - 1);
115
116 let diff = end as i32 - start as i32;
117 let a = (diff << SHIFT) / (N - 1) as i32;
118 let b = (start as i32) << SHIFT;
119 let result = a * value as i32 + b + CONST_0_5;
120
121 (result >> SHIFT) as u8
122}
123
124macro_rules! impl_linear_rgb {
125 ($($rgb_type:ty),+) => {
126 $(
127 impl<const N: usize> Linear<$rgb_type> for Colormap<$rgb_type, N> {
128 fn linear(start: $rgb_type, end: $rgb_type) -> Self {
129 let colors = array::from_fn(|index| {
130 let r = convert_channel::<N>(index as u8, start.r(), end.r());
131 let g = convert_channel::<N>(index as u8, start.g(), end.g());
132 let b = convert_channel::<N>(index as u8, start.b(), end.b());
133
134 <$rgb_type>::new(r, g, b)
135 });
136
137 Self(colors)
138 }
139 }
140 )*
141 }
142}
143
144impl_linear_rgb!(
145 Rgb555, Bgr555, Rgb565, Bgr565, Rgb666, Bgr666, Rgb888, Bgr888
146);
147
148macro_rules! impl_linear_gray {
149 ($($gray_type:ty),+) => {
150 $(
151 impl<const N: usize> Linear<$gray_type> for Colormap<$gray_type, N> {
152 fn linear(start: $gray_type, end: $gray_type) -> Self {
153 let colors = array::from_fn(|index| {
154 let luma = convert_channel::<N>(index as u8, start.luma(), end.luma());
155
156 <$gray_type>::new(luma)
157 });
158
159 Self(colors)
160 }
161 }
162 )*
163 }
164}
165
166impl_linear_gray!(Gray2, Gray4, Gray8);
167
168impl<const N: usize> Linear<BinaryColor> for Colormap<BinaryColor, N> {
169 fn linear(start: BinaryColor, end: BinaryColor) -> Self {
170 let colors = array::from_fn(|index| if index < N / 2 { start } else { end });
171
172 Self(colors)
173 }
174}
175
176macro_rules! impl_invert_rgb {
177 ($($rgb_type:ty),+) => {
178 $(
179 impl Invert for $rgb_type {
180 fn invert(self) -> Self {
181 let r = <$rgb_type>::MAX_R - self.r();
182 let g = <$rgb_type>::MAX_G - self.g();
183 let b = <$rgb_type>::MAX_B - self.b();
184
185 Self::new(r, g, b)
186 }
187 }
188 )*
189 }
190}
191
192impl_invert_rgb!(
193 Rgb555, Bgr555, Rgb565, Bgr565, Rgb666, Bgr666, Rgb888, Bgr888
194);
195
196impl Invert for Gray2 {
197 fn invert(self) -> Self {
198 Self::new(0b00000011 - self.luma())
199 }
200}
201
202impl Invert for Gray4 {
203 fn invert(self) -> Self {
204 Self::new(0b00001111 - self.luma())
205 }
206}
207
208impl Invert for Gray8 {
209 fn invert(self) -> Self {
210 Self::new(0b11111111 - self.luma())
211 }
212}
213
214impl Invert for BinaryColor {
215 fn invert(self) -> Self {
216 self.invert()
217 }
218}
219
220const fn screen_mix_channel(first: u8, second: u8, start: u8, end: u8) -> u8 {
221 const SHIFT: usize = 15;
222 const CONST_0_5: i32 = 1 << (SHIFT - 1);
223
224 if start == end {
225 return start;
226 }
227
228 let diff = end as i32 - start as i32;
229 let first = end as i32 - first as i32;
230 let second = end as i32 - second as i32;
231 let product = first * ((second << SHIFT) + CONST_0_5);
232 let minuend = ((end as i32) << SHIFT) + CONST_0_5;
233 let result = minuend - product / diff;
234
235 (result >> SHIFT) as u8
236}
237
238macro_rules! impl_screen_mix_rgb {
239 ($($rgb_type:ty),+) => {
240 $(
241 impl Screen for $rgb_type {
242 fn screen(self, other: Self, start: Self, end: Self) -> Self {
243 let r = screen_mix_channel(self.r(), other.r(), start.r(), end.r());
244 let g = screen_mix_channel(self.g(), other.g(), start.g(), end.g());
245 let b = screen_mix_channel(self.b(), other.b(), start.b(), end.b());
246
247 <$rgb_type>::new(r, g, b)
248 }
249 }
250 )*
251 }
252}
253
254impl_screen_mix_rgb!(
255 Rgb555, Bgr555, Rgb565, Bgr565, Rgb666, Bgr666, Rgb888, Bgr888
256);
257
258macro_rules! impl_screen_mix_gray {
259 ($($gray_type:ty),+) => {
260 $(
261 impl Screen for $gray_type {
262 fn screen(self, other: Self, start: Self, end: Self) -> Self {
263 let luma = screen_mix_channel(
264 self.luma(),
265 other.luma(),
266 start.luma(),
267 end.luma());
268
269 <$gray_type>::new(luma)
270 }
271 }
272 )*
273 }
274}
275
276impl_screen_mix_gray!(Gray2, Gray4, Gray8);
277
278impl Screen for BinaryColor {
279 fn screen(self, other: Self, start: Self, end: Self) -> Self {
280 if self == end || other == end {
281 end
282 } else {
283 start
284 }
285 }
286}
287
288const fn weighted_avg_mix_channel(
289 first: u8,
290 second: u8,
291 first_start: u8,
292 first_end: u8,
293 second_start: u8,
294 second_end: u8,
295) -> u8 {
296 const SHIFT: usize = 15;
297 const CONST_0_5: i32 = 1 << (SHIFT - 1);
298
299 const fn weight(value: u8, start: u8, end: u8) -> i32 {
300 if start == end {
301 return 0;
302 }
303
304 let diff = end as i32 - start as i32;
305 let value = value as i32 - start as i32;
306 let value = value << (SHIFT - 1);
307
308 value / diff
309 }
310
311 let first_weight = weight(first, first_start, first_end);
312 let second_weight = weight(second, second_start, second_end);
313 let sum_of_weights = first_weight + second_weight;
314 let [first_half, second_half] = if first_weight == second_weight || sum_of_weights == 0 {
315 let first_half = (first as i32) << (SHIFT - 1);
316 let second_half = (second as i32) << (SHIFT - 1);
317
318 [first_half, second_half]
319 } else {
320 let first_weight = first_weight << SHIFT;
321 let first_weight = first_weight / sum_of_weights;
322 let second_weight = second_weight << SHIFT;
323 let second_weight = second_weight / sum_of_weights;
324
325 [first_weight * first as i32, second_weight * second as i32]
326 };
327 let result = first_half + second_half + CONST_0_5;
328
329 (result >> SHIFT) as u8
330}
331
332macro_rules! impl_weighted_avg_mix_rgb {
333 ($($rgb_type:ty),+) => {
334 $(
335 impl WeightedAvg for $rgb_type {
336 fn weighted_avg(
337 self,
338 other: Self,
339 start: Self,
340 end: Self,
341 other_start: Self,
342 other_end: Self,
343 ) -> Self {
344 let [r, g, b] = [Self::r, Self::g, Self::b].map(|value_of| {
345 weighted_avg_mix_channel(
346 value_of(&self),
347 value_of(&other),
348 value_of(&start),
349 value_of(&end),
350 value_of(&other_start),
351 value_of(&other_end),
352 )
353 });
354
355 <$rgb_type>::new(r, g, b)
356 }
357 }
358 )*
359 }
360}
361
362impl_weighted_avg_mix_rgb!(
363 Rgb555, Bgr555, Rgb565, Bgr565, Rgb666, Bgr666, Rgb888, Bgr888
364);
365
366macro_rules! impl_weighted_avg_mix_gray {
367 ($($gray_type:ty),+) => {
368 $(
369 impl WeightedAvg for $gray_type {
370 fn weighted_avg(
371 self,
372 other: Self,
373 start: Self,
374 end: Self,
375 other_start: Self,
376 other_end: Self,
377 ) -> Self {
378 let luma = weighted_avg_mix_channel(
379 self.luma(),
380 other.luma(),
381 start.luma(),
382 end.luma(),
383 other_start.luma(),
384 other_end.luma(),
385 );
386
387 <$gray_type>::new(luma)
388 }
389 }
390 )*
391 }
392}
393
394impl_weighted_avg_mix_gray!(Gray2, Gray4, Gray8);
395
396impl WeightedAvg for BinaryColor {
397 fn weighted_avg(
398 self,
399 other: Self,
400 start: Self,
401 end: Self,
402 other_start: Self,
403 other_end: Self,
404 ) -> Self {
405 if start == other_start {
406 if self == end || other == other_end {
407 end
408 } else {
409 start
410 }
411 } else {
412 self
413 }
414 }
415}
416
417#[cfg(test)]
418mod tests {
419 use super::*;
420
421 macro_rules! test_convert_channel {
422 (
423 $(
424 $fn_ident:ident, $n:expr, $value:expr, $start:expr, $end:expr, $expected:expr,
425 )*
426 ) => {
427 $(
428 #[test]
429 fn $fn_ident() {
430 let result = convert_channel::<$n>($value, $start, $end);
431 assert_eq!(result, $expected);
432 }
433 )*
434 }
435 }
436
437 test_convert_channel! {
438 convert_8bpp_255_to_0_255, { 2usize.pow(8) }, 255, 0, 255, 255,
439 convert_8bpp_255_to_0_128, { 2usize.pow(8) }, 255, 0, 128, 128,
440 convert_8bpp_128_to_0_128, { 2usize.pow(8) }, 128, 0, 128, 64,
441 convert_8bpp_128_to_0_64, { 2usize.pow(8) }, 128, 0, 64, 32,
442 convert_8bpp_64_to_0_64, { 2usize.pow(8) }, 64, 0, 64, 16,
443
444 convert_8bpp_255_to_255_0, { 2usize.pow(8) }, 255, 255, 0, 0,
445 convert_8bpp_255_to_255_128, { 2usize.pow(8) }, 255, 255, 128, 128,
446 convert_8bpp_128_to_255_128, { 2usize.pow(8) }, 128, 255, 128, 255 - 128 / 2,
447 convert_8bpp_128_to_255_64, { 2usize.pow(8) }, 128, 255, 64, 255 - 128 / 4 * 3,
448 convert_8bpp_64_to_255_64, { 2usize.pow(8) }, 64, 255, 64, 255 - 64 / 4 * 3,
449
450 convert_5bpp_31_to_0_255, { 2usize.pow(5) }, 31, 0, 255, 255,
451 convert_4bpp_15_to_0_255, { 2usize.pow(4) }, 15, 0, 255, 255,
452 convert_3bpp_7_to_0_255, { 2usize.pow(3) }, 7, 0, 255, 255,
453 convert_2bpp_3_to_0_255, { 2usize.pow(2) }, 3, 0, 255, 255,
454 convert_1bpp_1_to_0_255, { 2usize.pow(1) }, 1, 0, 255, 255,
455 }
456
457 macro_rules! test_screen_mix_channel {
458 (
459 $(
460 $fn_ident:ident,
461 $first:expr,
462 $second:expr,
463 $start:expr,
464 $end:expr,
465 $expected:expr,
466 )*
467 ) => {
468 $(
469 #[test]
470 fn $fn_ident() {
471 let result = screen_mix_channel($first, $second, $start, $end);
472 assert_eq!(result, $expected);
473 }
474 )*
475 }
476 }
477
478 test_screen_mix_channel! {
479 screen_mix_channel_255_255_on_0_255, 255, 255, 0, 255, 255,
480 screen_mix_channel_128_128_on_0_255, 128, 128, 0, 255, 128 + 128 / 2,
481 screen_mix_channel_64_64_on_0_255, 64, 64, 0, 255, 64 + 64 / 4 * 3,
482 screen_mix_channel_32_32_on_0_255, 32, 32, 0, 255, 32 + 32 / 8 * 7,
483 screen_mix_channel_0_0_on_0_255, 0, 0, 0, 255, 0,
484
485 screen_mix_channel_255_255_on_255_0, 255, 255, 255, 0, 255,
486 screen_mix_channel_224_224_on_255_0, 224, 224, 255, 0, 224 - 224 / 8,
487 screen_mix_channel_192_192_on_255_0, 192, 192, 255, 0, 192 - 192 / 4,
488 screen_mix_channel_128_128_on_255_0, 128, 128, 255, 0, 128 - 128 / 2,
489 screen_mix_channel_0_0_on_255_0, 0, 0, 255, 0, 0,
490
491 screen_mix_channel_255_0_on_0_255, 255, 0, 0, 255, 255,
492 screen_mix_channel_128_64_on_0_255, 128, 64, 0, 255, 128 + 64 / 2,
493 screen_mix_channel_128_32_on_0_255, 128, 32, 0, 255, 128 + 32 / 2,
494 screen_mix_channel_64_128_on_0_255, 64, 128, 0, 255, 64 + 128 / 4 * 3,
495 screen_mix_channel_32_128_on_0_255, 32, 128, 0, 255, 32 + 128 / 8 * 7,
496
497 screen_mix_channel_255_0_on_255_0, 255, 0, 255, 0, 0,
498 screen_mix_channel_224_192_on_255_0, 224, 192, 255, 0, 224 / 4 * 3,
499 screen_mix_channel_224_128_on_255_0, 224, 128, 255, 0, 224 / 2,
500 screen_mix_channel_192_224_on_255_0, 192, 224, 255, 0, 192 / 8 * 7,
501 screen_mix_channel_128_224_on_255_0, 128, 224, 255, 0, 128 / 8 * 7,
502
503 screen_mix_channel_128_128_on_0_128, 128, 128, 0, 128, 128,
504 screen_mix_channel_64_64_on_0_128, 64, 64, 0, 128, 64 + 64 / 2,
505 screen_mix_channel_64_64_on_0_64, 64, 64, 0, 64, 64,
506 screen_mix_channel_32_32_on_0_64, 32, 32, 0, 64, 32 + 32 / 2,
507 screen_mix_channel_32_32_on_0_32, 32, 32, 0, 32, 32,
508
509 screen_mix_channel_128_128_on_128_0, 128, 128, 128, 0, 128,
510 screen_mix_channel_128_64_on_128_0, 128, 64, 128, 0, 64,
511 screen_mix_channel_128_32_on_128_0, 128, 32, 128, 0, 32,
512 screen_mix_channel_64_128_on_128_0, 64, 128, 128, 0, 64,
513 screen_mix_channel_32_128_on_128_0, 32, 128, 128, 0, 32,
514
515 screen_mix_channel_255_255_on_255_255, 255, 255, 255, 255, 255,
516 screen_mix_channel_255_255_on_128_128, 255, 255, 128, 128, 128,
517 screen_mix_channel_128_128_on_128_128, 128, 128, 128, 128, 128,
518 screen_mix_channel_0_0_on_128_128, 0, 0, 128, 128, 128,
519 screen_mix_channel_0_0_on_0_0, 0, 0, 0, 0, 0,
520 }
521
522 macro_rules! test_weighted_avg_mix_channel {
523 (
524 $(
525 $fn_ident:ident,
526 $first:expr,
527 $second:expr,
528 $first_start:expr,
529 $first_end:expr,
530 $second_start:expr,
531 $second_end:expr,
532 $expected:expr,
533 )*
534 ) => {
535 $(
536 #[test]
537 fn $fn_ident() {
538 let result = weighted_avg_mix_channel(
539 $first,
540 $second,
541 $first_start,
542 $first_end,
543 $second_start,
544 $second_end,
545 );
546 assert_eq!(result, $expected);
547 }
548 )*
549 }
550 }
551
552 test_weighted_avg_mix_channel! {
553 weighted_avg_mix_channel_255_255_on_0_255_and_0_255, 255, 255, 0, 255, 0, 255, 255,
554 weighted_avg_mix_channel_128_128_on_0_255_and_0_255, 128, 128, 0, 255, 0, 255, 128,
555 weighted_avg_mix_channel_64_192_on_0_255_and_0_255, 64, 192, 0, 255, 0, 255, 160,
556 weighted_avg_mix_channel_32_96_on_0_255_and_0_255, 32, 96, 0, 255, 0, 255, 80,
557 weighted_avg_mix_channel_32_224_on_0_255_and_0_255, 32, 224, 0, 255, 0, 255, 200,
558
559 weighted_avg_mix_channel_128_255_on_128_0_and_128_255, 128, 255, 128, 0, 128, 255, 255,
560 weighted_avg_mix_channel_128_128_on_128_0_and_128_255, 128, 128, 128, 0, 128, 255, 128,
561 weighted_avg_mix_channel_64_192_on_128_0_and_128_255, 64, 192, 128, 0, 128, 255, 128,
562 weighted_avg_mix_channel_0_255_on_128_0_and_128_255, 0, 255, 128, 0, 128, 255, 128,
563 weighted_avg_mix_channel_0_128_on_128_0_and_128_255, 0, 128, 128, 0, 128, 255, 0,
564
565 weighted_avg_mix_channel_255_255_on_255_0_and_0_255, 255, 255, 255, 0, 0, 255, 255,
566 weighted_avg_mix_channel_192_192_on_255_0_and_0_255, 192, 192, 255, 0, 0, 255, 192,
567 weighted_avg_mix_channel_128_128_on_255_0_and_0_255, 128, 128, 255, 0, 0, 255, 128,
568 weighted_avg_mix_channel_64_64_on_255_0_and_0_255, 64, 64, 255, 0, 0, 255, 64,
569 weighted_avg_mix_channel_0_0_on_255_0_and_0_255, 0, 0, 255, 0, 0, 255, 0,
570
571 weighted_avg_mix_channel_255_128_on_255_0_and_0_255, 255, 128, 255, 0, 0, 255, 128,
572 weighted_avg_mix_channel_255_0_on_255_0_and_0_255, 255, 0, 255, 0, 0, 255, 128,
573 weighted_avg_mix_channel_64_192_on_255_0_and_0_255, 64, 192, 255, 0, 0, 255, 128,
574 weighted_avg_mix_channel_32_224_on_255_0_and_0_255, 0, 255, 255, 0, 0, 255, 128,
575 weighted_avg_mix_channel_0_255_on_255_0_and_0_255, 0, 255, 255, 0, 0, 255, 128,
576
577 weighted_avg_mix_channel_255_255_on_255_255_and_255_255, 255, 255, 255, 255, 255, 255, 255,
578 weighted_avg_mix_channel_192_255_on_0_255_and_255_255, 192, 255, 0, 255, 255, 255, 192,
579 weighted_avg_mix_channel_128_255_on_0_255_and_255_255, 128, 255, 0, 255, 255, 255, 128,
580 weighted_avg_mix_channel_0_128_on_0_0_and_128_128, 0, 128, 0, 0, 128, 128, 64,
581 weighted_avg_mix_channel_0_0_on_0_0_and_0_0, 0, 0, 0, 0, 0, 0, 0,
582 }
583}