#![feature(decl_macro)]
use colorsys::Hsl;
use lab::Lab;
pub mod constants;
pub mod color;
mod traits;
pub use self::constants::*;
pub use self::color::*;
pub use self::traits::*;
pub use rgb;
pub use rgb::{Rgb, Rgba, RGB8, RGBA8};
pub const fn normalize_rgb (Rgb { r, g, b } : RGB8) -> Rgb <f32> {
Rgb::new (
r as f32 / 255.0,
g as f32 / 255.0,
b as f32 / 255.0
)
}
pub const fn normalize_rgba (Rgba { r, g, b, a } : RGBA8) -> Rgba <f32> {
Rgba::new (
r as f32 / 255.0,
g as f32 / 255.0,
b as f32 / 255.0,
a as f32 / 255.0
)
}
#[expect(clippy::cast_possible_truncation)]
#[expect(clippy::cast_sign_loss)]
pub const fn quantize_rgb (Rgb {r, g, b } : Rgb <f32>) -> RGB8 {
Rgb::new (
(r * 255.0) as u8,
(g * 255.0) as u8,
(b * 255.0) as u8
)
}
#[expect(clippy::cast_possible_truncation)]
#[expect(clippy::cast_sign_loss)]
pub const fn quantize_rgba (Rgba { r, g, b, a } : Rgba <f32>) -> RGBA8 {
Rgba::new (
(r * 255.0) as u8,
(g * 255.0) as u8,
(b * 255.0) as u8,
(a * 255.0) as u8
)
}
pub fn hue_deg (rgb : RGB8) -> Option <f32> {
let Rgb { r, g, b } = normalize_rgb (rgb);
let max = f32::max (f32::max (r, g), b);
let min = f32::min (f32::min (r, g), b);
let delta = max - min;
if delta == 0.0 {
return None
}
let mut hue;
if max == r {
hue = (g - b) / delta % 6.0
} else if max == g {
hue = ((b - r) / delta) + 2.0;
} else {
hue = ((r - g) / delta) + 4.0;
}
hue *= 60.0;
if hue < 0.0 {
hue += 360.0;
}
Some (hue)
}
pub fn hue_luminance_custom (hue_deg : f32, luminance : f32) -> RGB8 {
let mut rgb = hue_to_rgb (hue_deg);
loop {
let lum = luminance_custom (rgb);
let diff = lum - luminance;
if diff.abs() < 0.25 {
return rgb
}
let mut hsl = Hsl::from (colorsys::Rgb::from (rgb.into_array()));
let new_lightness = if lum > luminance {
hsl.lightness() - 1.0
} else {
debug_assert!(lum < luminance);
hsl.lightness() + 1.0
};
hsl.set_lightness (new_lightness);
let array : [u8; 3] = colorsys::Rgb::from (hsl).into();
rgb = Rgb::from (array);
}
}
pub fn hue_to_rgb (hue : f32) -> RGB8 {
let h = hue.rem_euclid (360.0);
let c = 1.0; let x = c * (1.0 - (((h / 60.0) % 2.0) - 1.0).abs());
let [r1, g1, b1] = match h {
h if h < 60.0 => [c, x, 0.0],
h if h < 120.0 => [x, c, 0.0],
h if h < 180.0 => [0.0, c, x],
h if h < 240.0 => [0.0, x, c],
h if h < 300.0 => [x, 0.0, c],
_ => [c, 0.0, x]
};
quantize_rgb (Rgb::new (r1, g1, b1))
}
#[expect(clippy::cast_possible_truncation)]
pub fn luminance_custom (Rgb { r, g, b } : RGB8) -> f32 {
if r == g && g == b {
return (r as f32 / 255.0) * 100.0
}
let tmp_lab = Lab::from_rgb (&[r, g, b]);
let tmp_rgb = Lab { a: 0.0, b: 0.0, .. tmp_lab }.to_rgb();
let lum_lab_a = ((tmp_lab.a / 127.0) * 50.0) / 10.0;
let lum_lab_b = (((tmp_lab.a - tmp_lab.b) / 127.0) * 50.0) / 10.0 / 2.0;
let mut lum_lab_ab = lum_lab_a + lum_lab_b;
if tmp_lab.a > tmp_lab.b {
lum_lab_ab += (((tmp_lab.a - tmp_lab.b) / 127.0) * 50.0) / 10.0;
}
Hsl::from (colorsys::Rgb::from (&tmp_rgb)).lightness() as f32 + lum_lab_ab
}
pub fn report_sizes() {
use std::mem::size_of;
macro_rules! show {
($e:expr) => { println!("{}: {:?}", stringify!($e), $e); }
}
println!("report sizes...");
show!(size_of::<Color>());
println!("...report sizes");
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn hue() {
for hue in 0..360 {
let rgb = hue_to_rgb (hue as f32);
let hue_deg = hue_deg (rgb).unwrap();
assert!((hue as f32 - hue_deg).abs() < 1.0,
"rgb: {rgb:?}, hue: {hue}, hue_deg: {hue_deg}");
}
}
#[test]
fn hue_lum() {
assert_eq!([211, 0, 0], hue_luminance_custom (0.0, 44.0).into_array());
assert_eq!([121, 121, 0], hue_luminance_custom (60.0, 44.0).into_array());
assert_eq!([0, 145, 0], hue_luminance_custom (120.0, 44.0).into_array());
assert_eq!([0, 130, 130], hue_luminance_custom (180.0, 44.0).into_array());
assert_eq!([0, 0, 255], hue_luminance_custom (240.0, 44.0).into_array());
assert_eq!([159, 0, 159], hue_luminance_custom (300.0, 44.0).into_array());
for hue in 0..360 {
for luminance in 0..100 {
let _rgb = hue_luminance_custom (hue as f32, luminance as f32);
}
}
}
}