coolor/
rgb.rs

1use crate::*;
2
3/// RGB color, with u8 components
4#[derive(Clone, Copy, Debug, PartialEq, Eq)]
5pub struct Rgb {
6    /// red
7    pub r: u8,
8    /// green
9    pub g: u8,
10    /// blue
11    pub b: u8,
12}
13
14impl Rgb {
15    /// Create a new RGB color from its components
16    pub const fn new(r: u8, g: u8, b: u8) -> Self {
17        Self { r, g, b }
18    }
19    pub const fn is_grey(self) -> bool {
20        self.r == self.g && self.g == self.b
21    }
22    #[inline]
23    pub fn nearest_ansi_in_range(self, min: u8, max: u8) -> AnsiColor {
24        let mut best = AnsiColor { code: min };
25        let mut smallest_distance: f32 = self.distance_to(best.to_rgb());
26        for code in min+1..=max {
27            let color = AnsiColor { code };
28            let distance = self.distance_to(color.to_rgb());
29            if distance < smallest_distance {
30                best = color;
31                smallest_distance = distance;
32            }
33        }
34        best
35    }
36    /// Return the nearest ANSI color
37    ///
38    /// The ansi->rgb->ansi round trip is guaranteed to
39    /// always fall on the first color.
40    pub fn to_ansi(self) -> AnsiColor {
41        if self.r == self.g && self.g == self.b {
42            AnsiColor { code: GREY_TO_ANSI[self.r as usize] }
43        } else if self.r < 108 {
44            if self.r < 41 {
45                self.nearest_ansi_in_range(17, 51)
46            } else {
47                self.nearest_ansi_in_range(52, 87)
48            }
49        } else if self.r < 195 {
50            if self.r < 151 {
51                self.nearest_ansi_in_range(88, 123)
52            } else {
53                self.nearest_ansi_in_range(124, 159)
54            }
55        } else {
56            if self.r < 235 {
57                self.nearest_ansi_in_range(160, 195)
58            } else {
59                self.nearest_ansi_in_range(196, 230)
60            }
61        }
62    }
63    pub fn mix(c1: Self, w1: f32, c2: Self, w2: f32) -> Self {
64        debug_assert!(w1 + w2 > 0.0);
65        let (r1, g1, b1) = c1.parts();
66        let (r2, g2, b2) = c2.parts();
67        let r = (w1 * r1 + w2 * r2) / (w1 + w2);
68        let g = (w1 * g1 + w2 * g2) / (w1 + w2);
69        let b = (w1 * b1 + w2 * b2) / (w1 + w2);
70        (r, g, b).into()
71    }
72    #[allow(clippy::float_cmp)]
73    pub fn to_hsl(self) -> Hsl {
74        let (r, g, b) = (self.rp(), self.gp(), self.bp());
75        let min = r.min(g).min(b);
76        let max = r.max(g).max(b);
77
78        let l = 0.5 * (max + min);
79
80        if min == max {
81            // gray level
82            return Hsl::new(0.0, 0.0, l);
83        }
84
85        let h = if max == r {
86            60.0 * (g - b) / (max - min)
87        } else if max == g {
88            60.0 * (b - r) / (max - min) + 120.0
89        } else if max == b {
90            60.0 * (r - g) / (max - min) + 240.0
91        } else {
92            0.0
93        };
94        let h = (h + 360.0) % 360.0;
95
96        let s = if 0.0 < l && l <= 0.5 {
97            (max - min) / (2.0 * l)
98        } else {
99            (max - min) / (2.0 - 2.0 * l)
100        };
101
102        Hsl { h, s, l }
103    }
104    /// red part in `[0,1]`
105    pub fn rp(self) -> f32 {
106        self.r as f32 / 256f32
107    }
108    /// green part in `[0,1]`
109    pub fn gp(self) -> f32 {
110        self.g as f32 / 256f32
111    }
112    /// blue part in `[0,1]`
113    pub fn bp(self) -> f32 {
114        self.b as f32 / 256f32
115    }
116    pub fn parts(self) -> (f32, f32, f32) {
117        (self.rp(), self.gp(), self.bp())
118    }
119    /// Compute the Luma value characterizing the "light" of the color,
120    /// going from 0 (black) to 1 (white).
121    ///
122    /// Reference: <https://en.wikipedia.org/wiki/Luma_(video)>
123    pub fn luma(self) -> f32 {
124        0.2627 * self.rp() + 0.6780 * self.gp() + 0.0593 * self.bp()
125    }
126    /// tentatively perceptual distance between two RGB colors
127    /// (adapted from the ansi_colours crate, by mina86, who adapted
128    /// a formula found at https://www.compuphase.com/cmetric.htm)
129    #[inline(always)]
130    pub fn distance_to<O: Into<Rgb>>(self, other: O) -> f32 {
131        let other = other.into();
132        let r_sum = self.r as f32 + other.r as f32;
133        let r = self.r as f32 - other.r as f32;
134        let g = self.g as f32 - other.g as f32;
135        let b = self.b as f32 - other.b as f32;
136        (1024.0 + r_sum) * r * r + 2048.0 * g * g + (1534.0 - r_sum) * b * b
137    }
138}
139
140pub fn r255(v: f32) -> u8 {
141    (v * 255.0) as u8
142}
143
144impl From<(f32, f32, f32)> for Rgb {
145    /// Convert from a (r,g,b) float tupples with components in [0,1[
146    fn from(c: (f32, f32, f32)) -> Self {
147        debug_assert!(c.0 <= 1.0);
148        debug_assert!(c.1 <= 1.0);
149        debug_assert!(c.2 <= 1.0);
150        Rgb::new(r255(c.0), r255(c.1), r255(c.2))
151    }
152}
153
154pub const GREY_TO_ANSI: &[u8] = &[
155    16,
156    16,
157    16,
158    16,
159    16,
160    232,
161    232,
162    232,
163    232,
164    232,
165    232,
166    232,
167    232,
168    232,
169    233,
170    233,
171    233,
172    233,
173    233,
174    233,
175    233,
176    233,
177    233,
178    233,
179    234,
180    234,
181    234,
182    234,
183    234,
184    234,
185    234,
186    234,
187    234,
188    234,
189    235,
190    235,
191    235,
192    235,
193    235,
194    235,
195    235,
196    235,
197    235,
198    235,
199    236,
200    236,
201    236,
202    236,
203    236,
204    236,
205    236,
206    236,
207    236,
208    236,
209    237,
210    237,
211    237,
212    237,
213    237,
214    237,
215    237,
216    237,
217    237,
218    237,
219    238,
220    238,
221    238,
222    238,
223    238,
224    238,
225    238,
226    238,
227    238,
228    238,
229    239,
230    239,
231    239,
232    239,
233    239,
234    239,
235    239,
236    239,
237    239,
238    239,
239    240,
240    240,
241    240,
242    240,
243    240,
244    240,
245    240,
246    240,
247    59,
248    59,
249    59,
250    59,
251    241,
252    241,
253    241,
254    241,
255    242,
256    242,
257    242,
258    242,
259    242,
260    242,
261    242,
262    242,
263    242,
264    242,
265    242,
266    243,
267    243,
268    243,
269    243,
270    243,
271    243,
272    243,
273    243,
274    243,
275    243,
276    243,
277    243,
278    243,
279    244,
280    244,
281    244,
282    244,
283    244,
284    244,
285    244,
286    244,
287    102,
288    102,
289    102,
290    102,
291    102,
292    245,
293    245,
294    245,
295    245,
296    245,
297    245,
298    245,
299    246,
300    246,
301    246,
302    246,
303    246,
304    246,
305    246,
306    246,
307    246,
308    246,
309    247,
310    247,
311    247,
312    247,
313    247,
314    247,
315    247,
316    247,
317    247,
318    247,
319    248,
320    248,
321    248,
322    248,
323    248,
324    248,
325    248,
326    248,
327    145,
328    145,
329    145,
330    145,
331    145,
332    249,
333    249,
334    249,
335    249,
336    249,
337    249,
338    249,
339    250,
340    250,
341    250,
342    250,
343    250,
344    250,
345    250,
346    250,
347    250,
348    250,
349    251,
350    251,
351    251,
352    251,
353    251,
354    251,
355    251,
356    251,
357    251,
358    251,
359    252,
360    252,
361    252,
362    252,
363    252,
364    252,
365    252,
366    252,
367    188,
368    188,
369    188,
370    188,
371    188,
372    253,
373    253,
374    253,
375    253,
376    253,
377    253,
378    253,
379    254,
380    254,
381    254,
382    254,
383    254,
384    254,
385    254,
386    254,
387    254,
388    254,
389    255,
390    255,
391    255,
392    255,
393    255,
394    255,
395    255,
396    255,
397    255,
398    255,
399    255,
400    255,
401    255,
402    231,
403    231,
404    231,
405    231,
406    231,
407    231,
408    231,
409    231,
410    231,
411];
412
413