visioncortex/
color.rs

1pub trait ColorType {
2    type ValueType;
3
4    fn channel(&self, c: usize) -> Option<Self::ValueType>;
5}
6
7/// RGBA; each channel is 8 bit unsigned
8#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)]
9pub struct Color {
10    pub r: u8,
11    pub g: u8,
12    pub b: u8,
13    pub a: u8,
14}
15
16/// Color names
17pub enum ColorName {
18    Black,
19    White,
20    Red,
21}
22
23/// RGB; each channel is 32 bit signed
24#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)]
25pub struct ColorI32 {
26    pub r: i32,
27    pub g: i32,
28    pub b: i32,
29}
30
31/// RGB; each channel is 64 bit float
32#[derive(Copy, Clone, Default, PartialEq, Debug)]
33pub struct ColorF64 {
34    pub r: f64,
35    pub g: f64,
36    pub b: f64,
37}
38
39/// RGBA; each channel is 32 bit unsigned
40#[derive(Copy, Clone, Default, PartialEq, Eq)]
41pub struct ColorSum {
42    pub r: u32,
43    pub g: u32,
44    pub b: u32,
45    pub a: u32,
46    pub counter: u32,
47}
48
49/// HSV; each channel is 64 bit float
50#[derive(Copy, Clone, PartialEq)]
51pub struct ColorHsv {
52    pub h: f64,
53    pub s: f64,
54    pub v: f64,
55}
56
57impl Color {
58    pub fn new(r: u8, g: u8, b: u8) -> Self {
59        Self::new_rgba(r, g, b, 255)
60    }
61
62    pub fn new_rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
63        Self { r, g, b, a }
64    }
65
66    pub fn color(name: &ColorName) -> Self {
67        match name {
68            ColorName::Black => Self::new(0, 0, 0),
69            ColorName::White => Self::new(255, 255, 255),
70            ColorName::Red => Self::new(255, 0, 0),
71        }
72    }
73
74    pub fn get_palette_color(i: usize) -> Self {
75        match i % 8 {
76            // https://codepen.io/chorijan/pen/azVzPO
77            0 => Self::new(216, 51, 74),   // Ruby
78            1 => Self::new(255, 232, 96),  // Lemon
79            2 => Self::new(160, 212, 104), // Grass
80            3 => Self::new(72, 207, 173),  // Mint
81            4 => Self::new(79, 193, 233),  // Aqua
82            5 => Self::new(93, 156, 236),  // Jeans
83            6 => Self::new(128, 103, 183), // Plum
84            7 => Self::new(172, 146, 236), // Lavender
85            _ => panic!("%"),
86        }
87    }
88
89    pub fn to_color_string(&self) -> String {
90        format!(
91            "rgba({},{},{},{})",
92            self.r,
93            self.g,
94            self.b,
95            self.a as f64 / 255.0
96        )
97    }
98
99    pub fn to_hex_string(&self) -> String {
100        format!("#{:02X}{:02X}{:02X}", self.r, self.g, self.b)
101    }
102
103    pub fn to_color_i32(&self) -> ColorI32 {
104        ColorI32::new(self)
105    }
106
107    #[allow(
108        clippy::many_single_char_names,
109        clippy::float_cmp
110    )]
111    pub fn to_hsv(&self) -> ColorHsv {
112        // Adapted from
113        // https://github.com/bgrins/TinyColor
114        // Brian Grinstead, MIT License
115
116        // Converts an RGB color value to HSV
117        // *Assumes:* [ r, g, b ] in [0, 255]
118        // *Returns:* [ h, s, v ] in [0, 1]
119
120        let r = self.r as f64 / 255.0;
121        let g = self.g as f64 / 255.0;
122        let b = self.b as f64 / 255.0;
123
124        let max = maxf64(r, maxf64(g, b));
125        let min = minf64(r, minf64(g, b));
126        let mut h;
127        let s;
128        let v = max;
129        let d = max - min;
130        s = if max == 0.0 { 0.0 } else { d / max };
131
132        if max == min {
133            h = 0.0; // achromatic
134        } else {
135            h = match max {
136                k if (k == r) => (g - b) / d + (if g < b { 6.0 } else { 0.0 }),
137                k if (k == g) => (b - r) / d + 2.0,
138                k if (k == b) => (r - g) / d + 4.0,
139                _ => unreachable!(),
140            };
141            h /= 6.0;
142        }
143        return ColorHsv::new(h, s, v);
144
145        fn maxf64(a: f64, b: f64) -> f64 {
146            if a > b {
147                a
148            } else {
149                b
150            }
151        }
152
153        fn minf64(a: f64, b: f64) -> f64 {
154            if a < b {
155                a
156            } else {
157                b
158            }
159        }
160    }
161}
162
163impl ColorType for Color {
164    type ValueType = u8;
165
166    fn channel(&self, c: usize) -> Option<Self::ValueType> {
167        match c {
168            0 => Some(self.r),
169            1 => Some(self.g),
170            2 => Some(self.b),
171            3 => Some(self.a),
172            _ => None,
173        }
174    }
175}
176
177impl ColorI32 {
178    pub fn new(color: &Color) -> Self {
179        Self {
180            r: color.r as i32,
181            g: color.g as i32,
182            b: color.b as i32,
183        }
184    }
185
186    pub fn add(&self, other: &Self) -> Self {
187        Self {
188            r: self.r + other.r,
189            g: self.g + other.g,
190            b: self.b + other.b,
191        }
192    }
193
194    pub fn diff(&self, other: &Self) -> Self {
195        Self {
196            r: self.r - other.r,
197            g: self.g - other.g,
198            b: self.b - other.b,
199        }
200    }
201
202    pub fn to_color(&self) -> Color {
203        assert!(0 <= self.r && self.r < 256);
204        assert!(0 <= self.g && self.g < 256);
205        assert!(0 <= self.b && self.b < 256);
206        Color::new(self.r as u8, self.g as u8, self.b as u8)
207    }
208
209    pub fn absolute(&self) -> i32 {
210        self.r.abs().max(self.g.abs().max(self.b.abs()))
211    }
212}
213
214impl ColorF64 {
215    pub fn new(color: &ColorI32) -> Self {
216        Self {
217            r: color.r as f64,
218            g: color.g as f64,
219            b: color.b as f64,
220        }
221    }
222
223    pub fn magnitude(&self) -> f64 {
224        (self.r * self.r + self.g * self.g + self.b * self.b).sqrt()
225    }
226}
227
228impl ColorHsv {
229    pub fn new(h: f64, s: f64, v: f64) -> Self {
230        Self { h, s, v }
231    }
232}
233
234impl ColorSum {
235    pub fn new() -> Self {
236        Default::default()
237    }
238
239    pub fn add(&mut self, color: &Color) {
240        self.r += color.r as u32;
241        self.g += color.g as u32;
242        self.b += color.b as u32;
243        self.a += color.a as u32;
244        self.counter += 1;
245    }
246
247    pub fn merge(&mut self, color: &ColorSum) {
248        self.r += color.r;
249        self.g += color.g;
250        self.b += color.b;
251        self.a += color.a;
252        self.counter += color.counter;
253    }
254
255    pub fn average(&self) -> Color {
256        Color::new_rgba(
257            (self.r / self.counter) as u8,
258            (self.g / self.counter) as u8,
259            (self.b / self.counter) as u8,
260            (self.a / self.counter) as u8,
261        )
262    }
263
264    pub fn clear(&mut self) {
265        self.r = 0;
266        self.g = 0;
267        self.b = 0;
268        self.a = 0;
269        self.counter = 0;
270    }
271}