gemini_engine/core/colchar/
colour.rs

1use std::{
2    ops::{Add, AddAssign, Mul, MulAssign},
3    str::FromStr,
4};
5
6/// Only used on f64 values between 0.0 and 255.0
7#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
8fn mul_by_f64_to_u8<T: Into<f64>>(value: T, rhs: f64) -> u8 {
9    (value.into() * rhs).round() as u8
10}
11
12/// A struct to store colour values. Can be created from RGB, HSV or greyscale values, but is ultimately stored as RGB.
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub struct Colour {
15    /// The red channel of the colour
16    pub r: u8,
17    /// The green channel of the colour
18    pub g: u8,
19    /// The blue channel of the colour
20    pub b: u8,
21}
22
23impl Colour {
24    /// A white `Colour` of RGB (0,0,0)
25    pub const BLACK: Self = Self::greyscale(0);
26    /// A white `Colour` of RGB (255,255,255)
27    pub const WHITE: Self = Self::greyscale(255);
28
29    /// Create a `Colour` from an RGB value
30    #[must_use]
31    pub const fn rgb(r: u8, g: u8, b: u8) -> Self {
32        Self { r, g, b }
33    }
34
35    /// Create a `Colour` from an HSV value
36    #[must_use]
37    pub fn hsv(hue: u8, sat: u8, val: u8) -> Self {
38        let hue = f32::from(hue) / 255.0;
39        let sat = f32::from(sat) / 255.0;
40        let val = f32::from(val) / 255.0;
41
42        let index = (hue * 6.0).floor();
43        let f = hue.mul_add(6.0, -index);
44        let p = val * f.mul_add(-sat, 1.0);
45        let q = val * f.mul_add(-sat, 1.0);
46        let t = val * (1.0 - f).mul_add(-sat, 1.0);
47
48        let (red, green, blue) = [
49            (val, t, p),
50            (q, val, p),
51            (p, val, t),
52            (p, q, val),
53            (t, p, val),
54            (val, p, q),
55        ][index as usize];
56
57        Self::rgb(
58            mul_by_f64_to_u8(red, 255.0),
59            mul_by_f64_to_u8(green, 255.0),
60            mul_by_f64_to_u8(blue, 255.0),
61        )
62    }
63
64    /// Create a `Colour` from a single brightness value, resulting in a shade of grey
65    #[must_use]
66    pub const fn greyscale(v: u8) -> Self {
67        Self::rgb(v, v, v)
68    }
69}
70
71impl Add for Colour {
72    type Output = Self;
73    fn add(self, rhs: Self) -> Self::Output {
74        Self::rgb(self.r + rhs.r, self.g + rhs.g, self.b + rhs.b)
75    }
76}
77
78impl AddAssign for Colour {
79    fn add_assign(&mut self, rhs: Self) {
80        self.r += rhs.r;
81        self.g += rhs.g;
82        self.b += rhs.b;
83    }
84}
85
86impl Mul<f64> for Colour {
87    type Output = Self;
88    fn mul(self, rhs: f64) -> Self::Output {
89        Self::rgb(
90            mul_by_f64_to_u8(self.r, rhs),
91            mul_by_f64_to_u8(self.g, rhs),
92            mul_by_f64_to_u8(self.b, rhs),
93        )
94    }
95}
96
97impl MulAssign<f64> for Colour {
98    fn mul_assign(&mut self, rhs: f64) {
99        self.r = mul_by_f64_to_u8(self.r, rhs);
100        self.r = mul_by_f64_to_u8(self.g, rhs);
101        self.r = mul_by_f64_to_u8(self.b, rhs);
102    }
103}
104
105impl FromStr for Colour {
106    type Err = String;
107
108    /// Colours should be passed in the format `<r>,<g>,<b>`, for example `255,0,0` for red
109    fn from_str(s: &str) -> Result<Self, Self::Err> {
110        let s = s.replace(' ', "");
111        let parts: Vec<&str> = s.split(',').collect();
112
113        if parts.len() != 3 {
114            return Err(String::from(
115                "Incorrect number of arguments, string must be in format r,g,b to be parsed correctly",
116            ));
117        }
118        println!("{parts:?}");
119
120        let mut nums = [0u8; 3];
121
122        for i in 0..3 {
123            nums[i] = parts[i].parse::<u8>().map_err(|_| {
124                String::from("Could not parse part of argument, make sure it's a valid number")
125            })?;
126        }
127
128        Ok(Self::rgb(nums[0], nums[1], nums[2]))
129    }
130}