keyset_color/
lib.rs

1//! This crate contains the [`Color`] type implementation used internally in [keyset].
2//!
3//! ## *But why not use an existing crate like [`rgb`]???*
4//!
5//! [`rgb`] has great storage containers for colours, and many convenience methods for converting
6//! between different pixel formats. Unfortunately one thing it doesn't do well is convert between
7//! different component types. For example `From<RGB<f32>>` is implemented for `RGB<u8>` but the
8//! conversion does not scale the component ranges.
9//!
10//! [Keyset] uses several different libraries internally and multiple different component types.
11//! This crate is designed to scale `0.0f32..1.0f32` to `0u8..255u8` and `0u16..65535u16` as is
12//! commonly expected. It also provides conversion traits to [`RGB<u8>`], [`RGB<u16>`], and
13//! [`RGB<f32>`] for interoperability; and supports direct conversion to other colour types
14//! used by dependencies of [keyset].
15//!
16//! [keyset]: https://crates.io/crates/keyset
17//! [`rgb`]: https://crates.io/crates/rgb
18//! [`RGB<u8>`]: ::rgb::RGB
19//! [`RGB<u16>`]: ::rgb::RGB
20//! [`RGB<f32>`]: ::rgb::RGB
21
22#![warn(
23    missing_docs,
24    clippy::all,
25    clippy::correctness,
26    clippy::suspicious,
27    clippy::style,
28    clippy::complexity,
29    clippy::perf,
30    clippy::pedantic,
31    clippy::cargo,
32    clippy::nursery
33)]
34#![allow(
35    clippy::suboptimal_flops // Optimiser is pretty good, and mul_add is pretty ugly
36)]
37
38#[cfg(feature = "tiny-skia")]
39mod skia;
40
41#[cfg(feature = "rgb")]
42mod rgb;
43
44use std::fmt::{Display, LowerHex, UpperHex};
45
46/// sRGB Color type.
47///
48/// Internally stores red, green, and blue components as [`f32`].
49#[derive(Debug, Clone, Copy, PartialEq, Default)]
50pub struct Color([f32; 3]); // r, g, b in that order
51
52impl Color {
53    /// Creates a new [`Color`] value with the given red, green, and blue component values.
54    ///
55    /// The components should be in the range `0.0..1.0` for a semantically valid colour, although
56    /// this function does not perform any range checks.
57    #[must_use]
58    pub const fn new(r: f32, g: f32, b: f32) -> Self {
59        Self([r, g, b])
60    }
61
62    /// Returns the red component.
63    #[must_use]
64    pub const fn r(&self) -> f32 {
65        self.0[0]
66    }
67
68    /// Returns the green component.
69    #[must_use]
70    pub const fn g(&self) -> f32 {
71        self.0[1]
72    }
73
74    /// Returns the blue component.
75    #[must_use]
76    pub const fn b(&self) -> f32 {
77        self.0[2]
78    }
79
80    /// Returns an iterator over the colour's components.
81    pub fn iter(&self) -> impl Iterator<Item = &f32> {
82        self.0.iter()
83    }
84
85    /// Returns an iterator that allows modifying the colour's components.
86    ///
87    /// Modified components should be in the range `0.0..1.0` for a semantically valid colour,
88    /// although this function does not perform any range checks.
89    pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut f32> {
90        self.0.iter_mut()
91    }
92
93    /// Applies the function `f` to each of the colour's components.
94    ///
95    /// The resulting components should be in the range `0.0..1.0` for a semantically valid colour,
96    /// although this function does not perform any range checks.
97    #[must_use]
98    pub fn map(self, f: impl FnMut(f32) -> f32) -> Self {
99        Self(self.0.map(f))
100    }
101
102    /// Returns a tuple containing the red, green, and blue components as [`u8`].
103    #[must_use]
104    pub fn as_rgb8(&self) -> (u8, u8, u8) {
105        #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] // We want truncation
106        let [r, g, b] = self.0.map(|c| (c * 256.0) as u8);
107        (r, g, b)
108    }
109
110    /// Returns a tuple containing the red, green, and blue components as [`u16`].
111    #[must_use]
112    pub fn as_rgb16(&self) -> (u16, u16, u16) {
113        #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] // We want truncation
114        let [r, g, b] = self.0.map(|c| (c * 65536.0) as u16);
115        (r, g, b)
116    }
117
118    /// Creates a new [`Color`] from a tuple containing the red, green, and blue
119    /// components as [`u8`].
120    #[must_use]
121    pub fn from_rgb8((r, g, b): (u8, u8, u8)) -> Self {
122        [r, g, b].map(|c| f32::from(c) / 255.0).into()
123    }
124
125    /// Creates a new [`Color`] from a tuple containing the red, green, and blue
126    /// components as [`u16`].
127    #[must_use]
128    pub fn from_rgb16((r, g, b): (u16, u16, u16)) -> Self {
129        [r, g, b].map(|c| f32::from(c) / 65535.0).into()
130    }
131
132    /// Returns a slice containing the red, green, and blue components of the colour.
133    #[must_use]
134    pub const fn as_slice(&self) -> &[f32] {
135        &self.0
136    }
137
138    /// Returns a mutable slice containing the red, green, and blue components of the colour.
139    #[must_use]
140    pub fn as_mut_slice(&mut self) -> &mut [f32] {
141        &mut self.0
142    }
143}
144
145impl IntoIterator for Color {
146    type Item = f32;
147    type IntoIter = std::array::IntoIter<f32, 3>;
148
149    fn into_iter(self) -> Self::IntoIter {
150        self.0.into_iter()
151    }
152}
153
154impl AsMut<[f32; 3]> for Color {
155    fn as_mut(&mut self) -> &mut [f32; 3] {
156        &mut self.0
157    }
158}
159
160impl AsMut<[f32]> for Color {
161    fn as_mut(&mut self) -> &mut [f32] {
162        &mut self.0
163    }
164}
165
166impl AsRef<[f32; 3]> for Color {
167    fn as_ref(&self) -> &[f32; 3] {
168        &self.0
169    }
170}
171
172impl AsRef<[f32]> for Color {
173    fn as_ref(&self) -> &[f32] {
174        &self.0
175    }
176}
177
178impl Display for Color {
179    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
180        let [r, g, b] = self.0.map(|c| (c * 1e3).round() / 1e3);
181        write!(f, "rgb({r},{g},{b})")
182    }
183}
184
185impl LowerHex for Color {
186    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
187        let prefix = if f.alternate() { "0x" } else { "#" };
188        let (r, g, b) = self.as_rgb8();
189        write!(f, "{prefix}{r:02x}{g:02x}{b:02x}")
190    }
191}
192
193impl UpperHex for Color {
194    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
195        let prefix = if f.alternate() { "0x" } else { "#" };
196        let (r, g, b) = self.as_rgb8();
197        write!(f, "{prefix}{r:02X}{g:02X}{b:02X}")
198    }
199}
200
201impl From<[f32; 3]> for Color {
202    fn from(value: [f32; 3]) -> Self {
203        Self(value)
204    }
205}
206
207impl From<(f32, f32, f32)> for Color {
208    fn from((r, g, b): (f32, f32, f32)) -> Self {
209        Self::new(r, g, b)
210    }
211}
212
213impl From<Color> for [f32; 3] {
214    fn from(value: Color) -> Self {
215        value.0
216    }
217}
218
219impl From<Color> for (f32, f32, f32) {
220    fn from(value: Color) -> Self {
221        (value.r(), value.g(), value.b())
222    }
223}
224
225impl Color {
226    /// Lightens the colour by a given amount.
227    ///
228    /// `val` should be in the range `0.0..1.0` for a semantically valid factor, although this
229    /// function does not perform any range checks.
230    #[must_use]
231    pub fn lighter(self, val: f32) -> Self {
232        self.map(|c| val + c * (1.0 - val))
233    }
234
235    /// Darkens the colour by a given amount.
236    ///
237    /// `val` should be in the range `0.0..1.0` for a semantically valid factor, although this
238    /// function does not perform any range checks.
239    #[must_use]
240    pub fn darker(self, val: f32) -> Self {
241        self.map(|c| c * (1.0 - val))
242    }
243
244    /// Either calls [`lighter`] or [`darker`] on the colour depending on its luminance.
245    ///
246    /// `val` should be in the range `0.0..1.0` for a semantically valid factor, although this
247    /// function does not perform any range checks.
248    ///
249    /// [`lighter`]: Color::lighter
250    /// [`darker`]: Color::darker
251    #[must_use]
252    pub fn highlight(self, val: f32) -> Self {
253        let (c_max, c_min) = self
254            .0
255            .into_iter()
256            .fold((f32::NEG_INFINITY, f32::INFINITY), |(max, min), c| {
257                (max.max(c), min.min(c))
258            });
259        let lum_x2 = c_max + c_min;
260
261        // If (lum * 2) > (0.5 * 2)
262        if lum_x2 > 1.0 {
263            self.darker(val)
264        } else {
265            self.lighter(val)
266        }
267    }
268}
269
270#[cfg(test)]
271mod tests {
272    use assert_approx_eq::assert_approx_eq;
273
274    use super::*;
275
276    #[test]
277    fn new() {
278        let color = Color::new(0.2, 0.4, 0.6);
279        assert_eq!(color.0[0], 0.2);
280        assert_eq!(color.0[1], 0.4);
281        assert_eq!(color.0[2], 0.6);
282    }
283
284    #[test]
285    fn r() {
286        let color = Color::new(0.2, 0.4, 0.6);
287        assert_eq!(color.r(), 0.2);
288    }
289
290    #[test]
291    fn g() {
292        let color = Color::new(0.2, 0.4, 0.6);
293        assert_eq!(color.g(), 0.4);
294    }
295
296    #[test]
297    fn b() {
298        let color = Color::new(0.2, 0.4, 0.6);
299        assert_eq!(color.b(), 0.6);
300    }
301
302    #[test]
303    fn iter() {
304        let color = Color::new(0.2, 0.4, 0.6);
305        let mut iter = color.iter();
306
307        assert_eq!(iter.next(), Some(&0.2));
308        assert_eq!(iter.next(), Some(&0.4));
309        assert_eq!(iter.next(), Some(&0.6));
310        assert_eq!(iter.next(), None);
311    }
312
313    #[test]
314    fn iter_mut() {
315        let mut color = Color::new(0.2, 0.4, 0.6);
316        let iter = color.iter_mut();
317        iter.for_each(|c| *c = 1.0 - *c);
318
319        assert_approx_eq!(color.0[0], 0.8);
320        assert_approx_eq!(color.0[1], 0.6);
321        assert_approx_eq!(color.0[2], 0.4);
322    }
323
324    #[test]
325    fn map() {
326        let color = Color::new(0.2, 0.4, 0.6).map(|c| 1.0 - c);
327
328        assert_approx_eq!(color.0[0], 0.8);
329        assert_approx_eq!(color.0[1], 0.6);
330        assert_approx_eq!(color.0[2], 0.4);
331    }
332
333    #[test]
334    fn as_rgb8() {
335        let color = Color::new(0.2, 0.4, 0.6);
336        let (r, g, b) = color.as_rgb8();
337
338        assert_eq!(r, 0x33);
339        assert_eq!(g, 0x66);
340        assert_eq!(b, 0x99);
341    }
342
343    #[test]
344    fn as_rgb16() {
345        let color = Color::new(0.2, 0.4, 0.6);
346        let (r, g, b) = color.as_rgb16();
347
348        assert_eq!(r, 0x3333);
349        assert_eq!(g, 0x6666);
350        assert_eq!(b, 0x9999);
351    }
352
353    #[test]
354    fn from_rgb8() {
355        let rgb = (0x33, 0x66, 0x99);
356        let color = Color::from_rgb8(rgb);
357
358        assert_approx_eq!(color.0[0], 0.2);
359        assert_approx_eq!(color.0[1], 0.4);
360        assert_approx_eq!(color.0[2], 0.6);
361    }
362
363    #[test]
364    fn from_rgb16() {
365        let rgb = (0x3333, 0x6666, 0x9999);
366        let color = Color::from_rgb16(rgb);
367
368        assert_approx_eq!(color.0[0], 0.2);
369        assert_approx_eq!(color.0[1], 0.4);
370        assert_approx_eq!(color.0[2], 0.6);
371    }
372
373    #[test]
374    fn as_slice() {
375        let color = Color::new(0.2, 0.4, 0.6);
376        let slice = color.as_slice();
377
378        assert_eq!(slice.len(), 3);
379        assert_eq!(slice[0], 0.2);
380        assert_eq!(slice[1], 0.4);
381        assert_eq!(slice[2], 0.6);
382    }
383
384    #[test]
385    fn as_mut_slice() {
386        let mut color = Color::new(0.2, 0.4, 0.6);
387        let slice = color.as_mut_slice();
388
389        assert_eq!(slice.len(), 3);
390
391        slice[0] = 0.8;
392        slice[1] = 0.6;
393        slice[2] = 0.4;
394
395        assert_eq!(color.0[0], 0.8);
396        assert_eq!(color.0[1], 0.6);
397        assert_eq!(color.0[2], 0.4);
398    }
399
400    #[test]
401    fn into_iter() {
402        let color = Color::new(0.2, 0.4, 0.6);
403        let iter = color.into_iter();
404        let vec: Vec<_> = iter.collect();
405
406        assert_eq!(vec.len(), 3);
407        assert_eq!(vec[0], 0.2);
408        assert_eq!(vec[1], 0.4);
409        assert_eq!(vec[2], 0.6);
410    }
411
412    #[test]
413    fn as_mut() {
414        let mut color = Color::new(0.2, 0.4, 0.6);
415
416        {
417            let array: &mut [f32; 3] = color.as_mut();
418            assert_eq!(array.len(), 3);
419            array[0] = 0.8;
420            array[1] = 0.6;
421            array[2] = 0.4;
422        }
423
424        assert_eq!(color.0[0], 0.8);
425        assert_eq!(color.0[1], 0.6);
426        assert_eq!(color.0[2], 0.4);
427
428        {
429            let slice: &mut [f32] = color.as_mut();
430            assert_eq!(slice.len(), 3);
431            slice[0] = 0.2;
432            slice[1] = 0.4;
433            slice[2] = 0.6;
434        }
435
436        assert_eq!(color.0[0], 0.2);
437        assert_eq!(color.0[1], 0.4);
438        assert_eq!(color.0[2], 0.6);
439    }
440
441    #[test]
442    fn as_ref() {
443        let color = Color::new(0.2, 0.4, 0.6);
444
445        let array: &[f32; 3] = color.as_ref();
446
447        assert_eq!(array.len(), 3);
448        assert_eq!(array[0], 0.2);
449        assert_eq!(array[1], 0.4);
450        assert_eq!(array[2], 0.6);
451
452        let slice: &[f32] = color.as_ref();
453
454        assert_eq!(slice.len(), 3);
455        assert_eq!(slice[0], 0.2);
456        assert_eq!(slice[1], 0.4);
457        assert_eq!(slice[2], 0.6);
458    }
459
460    #[test]
461    fn fmt() {
462        let color = Color::new(0.6, 0.8, 1.0);
463
464        assert_eq!(format!("{color}"), "rgb(0.6,0.8,1)");
465        assert_eq!(format!("{color:x}"), "#99ccff");
466        assert_eq!(format!("{color:X}"), "#99CCFF");
467        assert_eq!(format!("{color:#x}"), "0x99ccff");
468        assert_eq!(format!("{color:#X}"), "0x99CCFF");
469    }
470
471    #[test]
472    fn from_array() {
473        let array = [0.2, 0.4, 0.6];
474        let color = Color::from(array);
475
476        assert_eq!(color.0[0], 0.2);
477        assert_eq!(color.0[1], 0.4);
478        assert_eq!(color.0[2], 0.6);
479    }
480
481    #[test]
482    fn from_tuple() {
483        let tuple = (0.2, 0.4, 0.6);
484        let color = Color::from(tuple);
485
486        assert_eq!(color.0[0], 0.2);
487        assert_eq!(color.0[1], 0.4);
488        assert_eq!(color.0[2], 0.6);
489    }
490
491    #[test]
492    fn into_array() {
493        let color = Color::new(0.2, 0.4, 0.6);
494        let array = <[f32; 3]>::from(color);
495
496        assert_eq!(array.len(), 3);
497        assert_eq!(array[0], 0.2);
498        assert_eq!(array[1], 0.4);
499        assert_eq!(array[2], 0.6);
500    }
501
502    #[test]
503    fn into_tuple() {
504        let color = Color::new(0.2, 0.4, 0.6);
505        let tuple = <(f32, f32, f32)>::from(color);
506
507        assert_eq!(tuple.0, 0.2);
508        assert_eq!(tuple.1, 0.4);
509        assert_eq!(tuple.2, 0.6);
510    }
511
512    #[test]
513    fn lighter() {
514        let color = Color::new(0.2, 0.4, 0.6).lighter(0.5);
515
516        assert_approx_eq!(color.0[0], 0.6);
517        assert_approx_eq!(color.0[1], 0.7);
518        assert_approx_eq!(color.0[2], 0.8);
519    }
520
521    #[test]
522    fn darker() {
523        let color = Color::new(0.6, 0.8, 1.0).darker(0.5);
524
525        assert_approx_eq!(color.0[0], 0.3);
526        assert_approx_eq!(color.0[1], 0.4);
527        assert_approx_eq!(color.0[2], 0.5);
528    }
529
530    #[test]
531    fn highlight() {
532        let color = Color::new(0.6, 0.8, 1.0).highlight(0.5);
533
534        assert_approx_eq!(color.0[0], 0.3);
535        assert_approx_eq!(color.0[1], 0.4);
536        assert_approx_eq!(color.0[2], 0.5);
537
538        let color = Color::new(0.2, 0.4, 0.6).highlight(0.5);
539
540        assert_approx_eq!(color.0[0], 0.6);
541        assert_approx_eq!(color.0[1], 0.7);
542        assert_approx_eq!(color.0[2], 0.8);
543    }
544}