gemini_engine/core/colchar/
colour.rs1use std::{
2 ops::{Add, AddAssign, Mul, MulAssign},
3 str::FromStr,
4};
5
6#[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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub struct Colour {
15 pub r: u8,
17 pub g: u8,
19 pub b: u8,
21}
22
23impl Colour {
24 pub const BLACK: Self = Self::greyscale(0);
26 pub const WHITE: Self = Self::greyscale(255);
28
29 #[must_use]
31 pub const fn rgb(r: u8, g: u8, b: u8) -> Self {
32 Self { r, g, b }
33 }
34
35 #[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 #[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 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}