diff --git a/colorizer/src/core/ranged/mod.rs b/colorizer/src/core/ranged/mod.rs index a8fd853..06f51ec 100644 --- a/colorizer/src/core/ranged/mod.rs +++ b/colorizer/src/core/ranged/mod.rs @@ -3,7 +3,7 @@ mod self_operations; pub type BaseNumber = i16; -#[derive(Eq, Clone)] +#[derive(Eq, Clone, Debug)] pub struct RangedInt(BaseNumber); impl RangedInt<{ LOW }, { HIGH }> { @@ -13,4 +13,8 @@ impl RangedInt<{ LOW }, { HIGH }> pub fn new(number: BaseNumber) -> Self { Self(number.min(Self::HIGH).max(Self::LOW)) } + + pub fn to_f32(&self) -> f32 { + self.0 as f32 + } } diff --git a/colorizer/src/core/ranged/self_operations.rs b/colorizer/src/core/ranged/self_operations.rs index 54b7c80..ce5ce24 100644 --- a/colorizer/src/core/ranged/self_operations.rs +++ b/colorizer/src/core/ranged/self_operations.rs @@ -5,6 +5,12 @@ use std::{ ops::{Add, Div, Mul, Sub}, }; +impl Into for RangedInt<{ LOW }, { HIGH }> { + fn into(self) -> f32 { + self.0 as f32 + } +} + impl PartialEq for RangedInt<{ LOW }, { HIGH }> { fn eq(&self, other: &Self) -> bool { self.0 == other.0 diff --git a/colorizer/src/formats/colors.rs b/colorizer/src/formats/colors.rs new file mode 100644 index 0000000..8bf69ee --- /dev/null +++ b/colorizer/src/formats/colors.rs @@ -0,0 +1,226 @@ +use std::convert::From; + +use crate::core::ranged::{BaseNumber, RangedInt}; + +pub type ColorIntensity = RangedInt<0, 255>; +pub type ColorHue = RangedInt<0, 360>; +pub type Percentage = RangedInt<0, 100>; +#[derive(Debug)] +pub struct RGB(ColorIntensity, ColorIntensity, ColorIntensity); +pub struct HSL(ColorHue, Percentage, Percentage); +// pub struct HSV(ColorHue, Percentage, Percentage); + +#[derive(Debug)] +pub struct Color(RGB); + +impl Color { + pub fn format(&self) -> String { + format!("{:?}", self.0) + } +} + +impl PartialEq for Color { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } + + fn ne(&self, other: &Self) -> bool { + self.0 != other.0 + } +} + +impl RGB { + pub fn new(r: u8, g: u8, b: u8) -> Self { + Self( + ColorIntensity::new(r as BaseNumber), + ColorIntensity::new(g as BaseNumber), + ColorIntensity::new(b as BaseNumber), + ) + } +} + +impl PartialEq for RGB { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 && self.1 == other.1 && self.2 == other.2 + } + + fn ne(&self, other: &Self) -> bool { + self.0 != other.0 || self.1 != other.1 || self.2 != other.2 + } +} + +impl HSL { + pub fn new(h: u16, s: u8, l: u8) -> Self { + Self( + ColorHue::new(h as i16), + Percentage::new(s as i16), + Percentage::new(l as i16), + ) + } +} + +impl From for Color { + fn from(color: RGB) -> Self { + Self(color) + } +} + +impl From for Color { + fn from(color: HSL) -> Self { + Color(RGB::from(color)) + } +} + +fn min_of_float_vec(vector: Vec) -> Option { + let mut min: Option = None; + + for element in vector.iter() { + if let Some(value) = min { + if element < &value { + min = Some(*element) + } + } else { + min = Some(*element); + } + } + + min +} + +fn max_of_float_vec(vector: Vec) -> Option { + let mut max: Option = None; + + for element in vector.iter() { + if let Some(value) = max { + if element > &value { + max = Some(*element) + } + } else { + max = Some(*element); + } + } + + max +} + +impl From for HSL { + fn from(value: RGB) -> Self { + let r = value.0.to_f32() / 255.0; + let g = value.1.to_f32() / 255.0; + let b = value.2.to_f32() / 255.0; + + let min: f32 = min_of_float_vec(vec![r, g, b]).unwrap(); + let max: f32 = max_of_float_vec(vec![r, g, b]).unwrap(); + + // Luminance + let l = ((min + max) / 2.0).round(); + + // Saturation + let s: f32; + if r == g && g == b { + s = 0.0; + } else { + if l <= 0.5 { + s = (max - min) / (max + min); + } else { + s = (max - min) / (2.0 - max - min); + } + } + + // Hue + let h: f32; + if max == r { + h = (g - b) / (max - min); + } else if max == g { + h = 2.0 + (b - r) / (max - min); + } else { + h = 4.0 + (r - g) / (max - min); + } + + HSL::new( + (h * 60.0).round() as u16, + (s * 100.0).round() as u8, + (l * 100.0).round() as u8, + ) + } +} + +impl From for RGB { + fn from(color: HSL) -> Self { + // No saturation + if color.1 == 0 { + let shade = color.2 * 255 / 100; + let intensity = ColorIntensity::new(shade); + return Self(intensity.clone(), intensity.clone(), intensity.clone()); + } + + let temp_1: f32; + + if color.2 < 50 { + // Low lum + temp_1 = (color.2.to_f32() / 100.0) * (color.1.to_f32() / 100.0 + 1.0); + } else { + // High lum + temp_1 = (color.2.to_f32() / 100.0 + color.1.to_f32()) + - (color.2.to_f32() * color.1.to_f32()); + } + + let temp_2: f32 = (color.2.to_f32() / 100.0) * 2.0 - temp_1; + + let hue = color.0.to_f32() / 360.0; + + let mut temp_r = hue + 0.333; + let temp_g = hue; + let temp_b = hue - 0.333; + + // Normalize values + if temp_r > 1.0 { + temp_r = temp_r - 1.0; + } + if temp_b < 0.0 { + temp_r = temp_r + 1.0; + } + + // Calc Red + let red: f32; + if temp_r * 6.0 < 1.0 { + red = temp_2 + (temp_1 - temp_2) * 6.0 * temp_r; + } else if temp_r * 2.0 < 1.0 { + red = temp_1; + } else if temp_r * 3.0 < 2.0 { + red = temp_2 + (temp_1 - temp_2) * (0.666 - temp_r) * 6.0; + } else { + red = temp_2; + } + + // Calc Green + let green: f32; + if temp_g * 6.0 < 1.0 { + green = temp_2 + (temp_1 - temp_2) * 6.0 * temp_g; + } else if temp_g * 2.0 < 1.0 { + green = temp_1; + } else if temp_g * 3.0 < 2.0 { + green = temp_2 + (temp_1 - temp_2) * (0.666 - temp_g) * 6.0; + } else { + green = temp_2; + } + + // Calc blue + let blue: f32; + if temp_b * 6.0 < 1.0 { + blue = temp_2 + (temp_1 - temp_2) * 6.0 * temp_b; + } else if temp_b * 2.0 < 1.0 { + blue = temp_1; + } else if temp_b * 3.0 < 2.0 { + blue = temp_2 + (temp_1 - temp_2) * (0.666 - temp_b) * 6.0; + } else { + blue = temp_2; + } + + Self::new( + (red * 255.0).round() as u8, + (green * 255.0).round() as u8, + (blue * 255.0).round() as u8, + ) + } +} diff --git a/colorizer/src/formats/mod.rs b/colorizer/src/formats/mod.rs new file mode 100644 index 0000000..cb548ff --- /dev/null +++ b/colorizer/src/formats/mod.rs @@ -0,0 +1,5 @@ +pub mod colors; + +#[cfg(test)] +#[path = "./test/colors.test.rs"] +mod test; diff --git a/colorizer/src/formats/test/colors.test.rs b/colorizer/src/formats/test/colors.test.rs new file mode 100644 index 0000000..9a4a145 --- /dev/null +++ b/colorizer/src/formats/test/colors.test.rs @@ -0,0 +1,26 @@ +#[cfg(test)] +pub mod tests { + use crate::formats::colors::{Color, HSL, RGB}; + + #[test] + fn test_conversion() { + let hsl_color = Color::from(HSL::new(193, 67, 28)); + let rgb_color = Color::from(RGB::from(HSL::new(193, 67, 28))); + assert_eq!(hsl_color, rgb_color); + } + + #[test] + fn test_hsl_variants() { + let red_hsl = Color::from(HSL::new(0, 100, 50)); + let red_rgb = Color::from(RGB::new(255, 0, 0)); + assert_eq!(red_hsl, red_rgb); + + let green_hsl = Color::from(HSL::new(120, 100, 50)); + let green_rgb = Color::from(RGB::new(0, 255, 0)); + assert_eq!(green_hsl, green_rgb); + + let blue_hsl = Color::from(HSL::new(240, 100, 50)); + let blue_rgb = Color::from(RGB::new(0, 0, 255)); + assert_eq!(blue_hsl, blue_rgb); + } +} diff --git a/colorizer/src/main.rs b/colorizer/src/main.rs index dab1fdb..a0c1cf3 100644 --- a/colorizer/src/main.rs +++ b/colorizer/src/main.rs @@ -2,11 +2,10 @@ use clipboard::ClipboardContext; use clipboard::ClipboardProvider; pub mod core; -mod transmuter; +mod formats; +use formats::colors::{Color, RGB}; -#[cfg(test)] -#[path = "./test/transmuter.test.rs"] -mod test; +use crate::formats::colors::HSL; fn example() { let mut ctx: ClipboardContext = ClipboardProvider::new().unwrap(); @@ -17,4 +16,9 @@ fn example() { fn main() { println!("Hello, world!"); example(); + + let hsl_color = Color::from(HSL::new(193, 67, 28)); + let rgb_color = Color::from(RGB::from(HSL::new(193, 67, 28))); + println!("HSL Color: {}", hsl_color.format()); + println!("RGB Color: {}", rgb_color.format()); } diff --git a/colorizer/src/test/transmuter.test.rs b/colorizer/src/test/transmuter.test.rs deleted file mode 100644 index f1b8928..0000000 --- a/colorizer/src/test/transmuter.test.rs +++ /dev/null @@ -1,8 +0,0 @@ -#[cfg(test)] -pub mod tests { - #[test] - fn test_success() { - let my_hello = "Hello world!"; - assert_eq!(my_hello, "Hello world!"); - } -} diff --git a/colorizer/src/transmuter/colors.rs b/colorizer/src/transmuter/colors.rs deleted file mode 100644 index 3c4d34a..0000000 --- a/colorizer/src/transmuter/colors.rs +++ /dev/null @@ -1,60 +0,0 @@ -use std::convert::From; - -use crate::core::ranged::{BaseNumber, RangedInt}; - -type ColorIntensity = RangedInt<0, 255>; -struct RGB(ColorIntensity, ColorIntensity, ColorIntensity); -struct HSL(RangedInt<0, 360>, RangedInt<0, 100>, RangedInt<0, 100>); -struct HSV(RangedInt<0, 360>, RangedInt<0, 100>, RangedInt<0, 100>); - -pub struct Color(RGB); - -impl Color {} - -impl RGB { - fn new(r: u8, g: u8, b: u8) -> Self { - Self( - ColorIntensity::new(r as BaseNumber), - ColorIntensity::new(g as BaseNumber), - ColorIntensity::new(b as BaseNumber), - ) - } -} - -impl From for Color { - fn from(color: RGB) -> Self { - Self(color) - } -} - -impl From for Color { - fn from(color: HSL) -> Self { - Color(RGB::from(color)) - } -} - -impl From for RGB { - fn from(color: HSL) -> Self { - // No saturation - if color.1 == 0 { - let shade = color.2 / 100 * 255; - let intensity = ColorIntensity::new(shade); - return Self(intensity.clone(), intensity.clone(), intensity.clone()); - } - - let mut temp_1: i16 = 0; - - if color.2 < 50 { - // Low lum - temp_1 = color.2.clone() * (color.1 + 100); - } else { - // High lum - temp_1 = color.2.clone() + color.1.clone() - color.2.clone() * color.1.clone(); - } - - let temp_2: i16 = color.2 * 2 - temp_1; - let hue = (color.0.clone() / 360) * 100; - - Self::new(0, 0, 0) - } -} diff --git a/colorizer/src/transmuter/mod.rs b/colorizer/src/transmuter/mod.rs deleted file mode 100644 index 936df40..0000000 --- a/colorizer/src/transmuter/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -mod colors; -mod parsers; diff --git a/colorizer/src/transmuter/parsers.rs b/colorizer/src/transmuter/parsers.rs deleted file mode 100644 index 8b13789..0000000 --- a/colorizer/src/transmuter/parsers.rs +++ /dev/null @@ -1 +0,0 @@ -