use crate::cie::lab::Lab;
use crate::cie::xyz::XYZ;
use serde::{Deserialize, Serialize};
use std::num::ParseIntError;
use crate::cie::illumination::Illumination;
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
pub struct RGB {
pub r: u8,
pub g: u8,
pub b: u8,
}
impl RGB {
pub fn new(r: u8, g: u8, b: u8) -> Self {
Self { r, g, b }
}
fn xyz(self, illumination: Illumination) -> XYZ {
let r = inverse_gamma(linearize(self.r));
let g = inverse_gamma(linearize(self.g));
let b = inverse_gamma(linearize(self.b));
let x = illumination.x[0] * r + illumination.x[1] * g + illumination.x[2] * b;
let y = illumination.y[0] * r + illumination.y[1] * g + illumination.y[2] * b;
let z = illumination.z[0] * r + illumination.z[1] * g + illumination.z[2] * b;
XYZ { x, y, z }
}
pub fn lab(self, illumination: Illumination) -> Lab {
self.xyz(illumination).lab(illumination)
}
}
impl TryFrom<String> for RGB {
type Error = ParseIntError;
fn try_from(value: String) -> Result<Self, Self::Error> {
let hex = value.trim_start_matches("#");
let v = u32::from_str_radix(hex, 16)?;
Ok(RGB::from(v))
}
}
impl From<u32> for RGB {
fn from(v: u32) -> Self {
let r = ((v >> 16) & 0xFF) as u8;
let g = ((v >> 8) & 0xFF) as u8;
let b = (v & 0xFF) as u8;
Self::new(r, g, b)
}
}
impl From<RGB> for u32 {
fn from(RGB { r, g, b }: RGB) -> Self {
((r as u32) << 16) | ((g as u32) << 8) | (b as u32)
}
}
impl From<RGB> for String {
fn from(rgb: RGB) -> Self {
format!("#{:06X}", u32::from(rgb))
}
}
fn linearize(v: u8) -> f32 {
f32::min(v as f32, 255.0) / 255.0
}
fn inverse_gamma(v: f32) -> f32 {
if v < 0.04045 {
v * 0.0773993808
} else {
f32::powf((v + 0.055) * 0.9478672986, 2.4)
}
}
#[cfg(test)]
mod tests {
use crate::cie::rgb::RGB;
use crate::cie::illumination::D65;
#[test]
fn it_converts_hex_to_rgb() {
assert_eq!(
RGB::try_from("#FFFFFF".to_string()),
Ok(RGB::new(0xFF, 0xFF, 0xFF))
);
assert_eq!(
RGB::try_from("#000000".to_string()),
Ok(RGB::new(0x00, 0x00, 0x00))
);
assert_eq!(
RGB::try_from("#FF0000".to_string()),
Ok(RGB::new(0xFF, 0x00, 0x00))
);
assert_eq!(
RGB::try_from("#808080".to_string()),
Ok(RGB::new(0x80, 0x80, 0x80))
);
assert_eq!(
RGB::try_from("#7F11B2".to_string()),
Ok(RGB::new(127, 17, 178))
);
}
#[test]
fn it_converts_rgb_to_hex() {
assert_eq!(String::from(RGB::new(0xFF, 0xFF, 0xFF)), "#FFFFFF");
assert_eq!(String::from(RGB::new(0x00, 0x00, 0x00)), "#000000");
assert_eq!(String::from(RGB::new(0xFF, 0x80, 0x80)), "#FF8080");
}
macro_rules! assert_lab {
($example:expr, $l:expr, $a:expr, $b:expr) => {
assert!($example.l - $l < 1e-2);
assert!($example.a - $a < 1e-2);
assert!($example.b - $b < 1e-2);
};
}
#[test]
fn it_converts_rgb_to_lab() {
assert_lab!(RGB::new(0x00, 0x00, 0x00).lab(D65), 0.0, 0.0, 0.0);
assert_lab!(RGB::new(0xFF, 0x00, 0x00).lab(D65), 53.24, 80.09, 67.20);
assert_lab!(RGB::new(0xFF, 0xFF, 0x00).lab(D65), 97.14, -21.55, 94.48);
assert_lab!(RGB::new(0x00, 0xFF, 0x00).lab(D65), 87.74, -86.18, 83.18);
assert_lab!(RGB::new(0, 255, 255).lab(D65), 91.11, -48.09, -14.130);
assert_lab!(RGB::new(0, 0, 255).lab(D65), 32.30, 79.19, -107.860);
assert_lab!(RGB::new(255, 0, 255).lab(D65), 60.32, 98.24, -60.830);
assert_lab!(RGB::new(255, 255, 255).lab(D65), 100.0, 0.00, 0.000);
assert_lab!(RGB::new(127, 127, 127).lab(D65), 53.39, 0.00, 0.000);
assert_lab!(RGB::new(191, 0, 0).lab(D65), 39.77, 64.51, 54.130);
assert_lab!(RGB::new(127, 0, 0).lab(D65), 25.42, 47.91, 37.910);
assert_lab!(RGB::new(63, 0, 0).lab(D65), 9.66, 29.68, 15.240);
assert_lab!(RGB::new(255, 127, 127).lab(D65), 68.00, 48.59, 22.970);
}
}