use crate::mathematical::{MathematicalMunsellConverter, MunsellSpecification, CieXyY};
use crate::munsell_converter_core::PythonMunsellConverter;
use crate::color_notation_parser::munsell_colour_to_munsell_specification;
use crate::munsell_color_science::munsell_specification_to_xyy;
use crate::error::{MunsellError, Result};
use palette::{Srgb, Hsl, Hsv, Xyz, convert::IntoColor, white_point::D65};
#[cfg(test)]
mod tests;
#[derive(Debug, Clone, PartialEq)]
pub struct CieLab {
pub l: f64, pub a: f64, pub b: f64, }
#[derive(Debug, Clone, PartialEq)]
pub struct HslColor {
pub h: f64, pub s: f64, pub l: f64, }
#[derive(Debug, Clone, PartialEq)]
pub struct HsvColor {
pub h: f64, pub s: f64, pub v: f64, }
#[derive(Debug, Clone)]
pub struct ColorFormats {
pub munsell: MunsellSpecification,
pub lab: CieLab,
pub srgb: [u8; 3],
pub hex: String,
pub hsl: HslColor,
pub hsv: HsvColor,
}
pub struct ReverseConverter {
_converter: MathematicalMunsellConverter,
python_converter: PythonMunsellConverter,
}
impl ReverseConverter {
pub fn new() -> Result<Self> {
Ok(Self {
_converter: MathematicalMunsellConverter::new()?,
python_converter: PythonMunsellConverter::new(),
})
}
pub fn with_converter(_converter: MathematicalMunsellConverter) -> Self {
Self {
_converter,
python_converter: PythonMunsellConverter::new(),
}
}
pub fn munsell_to_all_formats(&self, spec: &MunsellSpecification) -> Result<ColorFormats> {
let spec_array = self.munsell_spec_to_array(spec)?;
let xyy_array = munsell_specification_to_xyy(&spec_array)?;
let xyy = CieXyY { x: xyy_array[0], y: xyy_array[1], y_luminance: xyy_array[2] };
let xyz = self.xyy_to_xyz(&xyy)?;
let lab = self.xyz_to_lab(xyz)?;
let srgb = self.lab_to_srgb(&lab)?;
let hex = self.srgb_to_hex(srgb);
let hsl = self.srgb_to_hsl(srgb)?;
let hsv = self.srgb_to_hsv(srgb)?;
Ok(ColorFormats {
munsell: spec.clone(),
lab,
srgb,
hex,
hsl,
hsv,
})
}
pub fn munsell_to_lab(&self, spec: &MunsellSpecification) -> Result<CieLab> {
let spec_array = self.munsell_spec_to_array(spec)?;
let xyy_array = munsell_specification_to_xyy(&spec_array)?;
let xyy = CieXyY { x: xyy_array[0], y: xyy_array[1], y_luminance: xyy_array[2] };
let xyz = self.xyy_to_xyz(&xyy)?;
self.xyz_to_lab(xyz)
}
pub fn munsell_to_srgb(&self, spec: &MunsellSpecification) -> Result<[u8; 3]> {
let notation = self.spec_to_notation_string(spec)?;
let rgb_color = self.python_converter.munsell_to_srgb(¬ation)?;
Ok([rgb_color.r, rgb_color.g, rgb_color.b])
}
pub fn munsell_to_hex(&self, spec: &MunsellSpecification) -> Result<String> {
let srgb = self.munsell_to_srgb(spec)?;
Ok(self.srgb_to_hex(srgb))
}
pub fn munsell_to_hsl(&self, spec: &MunsellSpecification) -> Result<HslColor> {
let srgb = self.munsell_to_srgb(spec)?;
self.srgb_to_hsl(srgb)
}
pub fn munsell_to_hsv(&self, spec: &MunsellSpecification) -> Result<HsvColor> {
let srgb = self.munsell_to_srgb(spec)?;
self.srgb_to_hsv(srgb)
}
fn munsell_spec_to_array(&self, spec: &MunsellSpecification) -> Result<[f64; 4]> {
if spec.family == "N" {
return Ok([f64::NAN, spec.value, 0.0, f64::NAN]);
}
let code = match spec.family.as_str() {
"B" => 1,
"BG" => 2,
"G" => 3,
"GY" => 4,
"Y" => 5,
"YR" => 6,
"R" => 7,
"RP" => 8,
"P" => 9,
"PB" => 10,
_ => return Err(MunsellError::InvalidNotation {
notation: spec.family.clone(),
reason: "Invalid family code".to_string(),
}),
};
Ok([spec.hue, spec.value, spec.chroma, code as f64])
}
fn spec_to_notation_string(&self, spec: &MunsellSpecification) -> Result<String> {
if spec.family == "N" {
return Ok(format!("N {}", spec.value));
}
let hue_str = if spec.hue == spec.hue.floor() {
format!("{}", spec.hue as i32)
} else {
format!("{:.1}", spec.hue)
};
Ok(format!("{}{} {}/{}", hue_str, spec.family, spec.value, spec.chroma))
}
fn array_to_munsell_spec(&self, spec_array: [f64; 4]) -> Result<MunsellSpecification> {
if spec_array[0].is_nan() && spec_array[2].is_nan() {
return Ok(MunsellSpecification {
hue: 0.0,
family: "N".to_string(),
value: spec_array[1],
chroma: 0.0,
});
}
let family = match spec_array[3] as u8 {
1 => "B",
2 => "BG",
3 => "G",
4 => "GY",
5 => "Y",
6 => "YR",
7 => "R",
8 => "RP",
9 => "P",
10 => "PB",
code => return Err(MunsellError::InvalidNotation {
notation: code.to_string(),
reason: "Invalid family code".to_string(),
}),
};
Ok(MunsellSpecification {
hue: spec_array[0],
family: family.to_string(),
value: spec_array[1],
chroma: spec_array[2],
})
}
fn xyy_to_xyz(&self, xyy: &CieXyY) -> Result<[f64; 3]> {
if xyy.y == 0.0 {
Ok([0.0, 0.0, 0.0])
} else {
Ok([
xyy.x * xyy.y_luminance / xyy.y, xyy.y_luminance, (1.0 - xyy.x - xyy.y) * xyy.y_luminance / xyy.y, ])
}
}
fn xyz_to_lab(&self, xyz: [f64; 3]) -> Result<CieLab> {
const XN: f64 = 0.95047;
const YN: f64 = 1.00000;
const ZN: f64 = 1.08883;
let xr = xyz[0] / XN;
let yr = xyz[1] / YN;
let zr = xyz[2] / ZN;
let fx = if xr > 0.008856 { xr.powf(1.0/3.0) } else { (7.787 * xr) + (16.0/116.0) };
let fy = if yr > 0.008856 { yr.powf(1.0/3.0) } else { (7.787 * yr) + (16.0/116.0) };
let fz = if zr > 0.008856 { zr.powf(1.0/3.0) } else { (7.787 * zr) + (16.0/116.0) };
let l = 116.0 * fy - 16.0;
let a = 500.0 * (fx - fy);
let b = 200.0 * (fy - fz);
Ok(CieLab { l, a, b })
}
fn lab_to_srgb(&self, lab: &CieLab) -> Result<[u8; 3]> {
let xyz = self.lab_to_xyz(lab)?;
let xyz_color = Xyz::<D65, f64>::new(xyz[0], xyz[1], xyz[2]);
let srgb: Srgb<f64> = xyz_color.into_color();
let r = (srgb.red * 255.0).round().clamp(0.0, 255.0) as u8;
let g = (srgb.green * 255.0).round().clamp(0.0, 255.0) as u8;
let b = (srgb.blue * 255.0).round().clamp(0.0, 255.0) as u8;
Ok([r, g, b])
}
fn lab_to_xyz(&self, lab: &CieLab) -> Result<[f64; 3]> {
const XN: f64 = 0.95047;
const YN: f64 = 1.00000;
const ZN: f64 = 1.08883;
let fy = (lab.l + 16.0) / 116.0;
let fx = lab.a / 500.0 + fy;
let fz = fy - lab.b / 200.0;
let xr = if fx.powi(3) > 0.008856 { fx.powi(3) } else { (fx - 16.0/116.0) / 7.787 };
let yr = if lab.l > (7.787 * 0.008856 + 16.0/116.0) * 116.0 {
fy.powi(3)
} else {
lab.l / (116.0 * 7.787)
};
let zr = if fz.powi(3) > 0.008856 { fz.powi(3) } else { (fz - 16.0/116.0) / 7.787 };
Ok([xr * XN, yr * YN, zr * ZN])
}
fn srgb_to_hex(&self, srgb: [u8; 3]) -> String {
format!("#{:02X}{:02X}{:02X}", srgb[0], srgb[1], srgb[2])
}
fn srgb_to_hsl(&self, srgb: [u8; 3]) -> Result<HslColor> {
let srgb_color = Srgb::new(
srgb[0] as f64 / 255.0,
srgb[1] as f64 / 255.0,
srgb[2] as f64 / 255.0,
);
let hsl: Hsl<palette::encoding::Srgb, f64> = srgb_color.into_color();
Ok(HslColor {
h: hsl.hue.into_positive_degrees() as f64,
s: hsl.saturation as f64 * 100.0,
l: hsl.lightness as f64 * 100.0,
})
}
fn srgb_to_hsv(&self, srgb: [u8; 3]) -> Result<HsvColor> {
let srgb_color = Srgb::new(
srgb[0] as f64 / 255.0,
srgb[1] as f64 / 255.0,
srgb[2] as f64 / 255.0,
);
let hsv: Hsv<palette::encoding::Srgb, f64> = srgb_color.into_color();
Ok(HsvColor {
h: hsv.hue.into_positive_degrees() as f64,
s: hsv.saturation as f64 * 100.0,
v: hsv.value as f64 * 100.0,
})
}
}
impl Default for ReverseConverter {
fn default() -> Self {
Self::new().expect("Failed to create default ReverseConverter")
}
}
pub fn munsell_to_hex_string(munsell_notation: &str) -> Result<String> {
let spec = parse_munsell_notation(munsell_notation)?;
let converter = ReverseConverter::new()?;
converter.munsell_to_hex(&spec)
}
pub fn parse_munsell_notation(notation: &str) -> Result<MunsellSpecification> {
let spec_array = munsell_colour_to_munsell_specification(notation)?;
let converter = ReverseConverter::new()?;
converter.array_to_munsell_spec(spec_array)
}