use crate::constants::*;
use crate::error::{MunsellError, Result};
use super::types::{MunsellSpecification, CieXyY};
use super::MathematicalMunsellConverter;
impl MathematicalMunsellConverter {
pub(super) fn luminance_to_munsell_value(&self, y: f64) -> Result<f64> {
if y <= 0.0 {
return Ok(0.0);
}
let y_scaled = y * 100.0;
if y_scaled >= 100.0 {
return Ok(10.0);
}
let mut v = 10.0 * y.sqrt();
for _ in 0..NEWTON_RAPHSON_MAX_ITERATIONS {
let f = self.astm_polynomial(v) - y_scaled;
let df = self.astm_polynomial_derivative(v);
if df.abs() < 1e-15 {
return Err(MunsellError::ConvergenceFailed);
}
let delta = f / df;
v -= delta;
if delta.abs() < NEWTON_RAPHSON_TOLERANCE {
return Ok(v.max(0.0).min(10.0));
}
}
Err(MunsellError::ConvergenceFailed)
}
pub(super) fn astm_polynomial(&self, v: f64) -> f64 {
let coeffs = &ASTM_D1535_COEFFICIENTS;
coeffs[0] * v +
coeffs[1] * v * v +
coeffs[2] * v * v * v +
coeffs[3] * v * v * v * v +
coeffs[4] * v * v * v * v * v
}
pub(super) fn astm_polynomial_derivative(&self, v: f64) -> f64 {
let coeffs = &ASTM_D1535_COEFFICIENTS;
coeffs[0] +
2.0 * coeffs[1] * v +
3.0 * coeffs[2] * v * v +
4.0 * coeffs[3] * v * v * v +
5.0 * coeffs[4] * v * v * v * v
}
pub(super) fn munsell_value_to_luminance(&self, value: f64) -> Result<f64> {
if value < 0.0 || value > 10.0 {
return Err(MunsellError::InvalidNotation {
notation: value.to_string(),
reason: "Munsell Value must be between 0.0 and 10.0".to_string(),
});
}
Ok(self.astm_polynomial(value) / 100.0)
}
pub fn munsell_specification_to_xyy(&self, spec: &MunsellSpecification) -> Result<CieXyY> {
if spec.family == "N" {
let y = self.munsell_value_to_luminance(spec.value)?;
return Ok(CieXyY {
x: ILLUMINANT_C[0],
y: ILLUMINANT_C[1],
y_luminance: y,
});
}
let hue_str = format!("{}{}", spec.hue, spec.family);
for entry in self.renotation_data {
let ((entry_hue, entry_value, entry_chroma), (x, y, luma)) = entry;
if entry_hue == &hue_str &&
(entry_value - spec.value).abs() < 0.1 &&
(entry_chroma - spec.chroma).abs() < 0.1 {
return Ok(CieXyY { x: *x, y: *y, y_luminance: *luma });
}
}
Err(MunsellError::InterpolationError {
message: format!("No matching renotation data for {}{} {:.1}/{:.1}",
spec.hue, spec.family, spec.value, spec.chroma),
})
}
pub fn format_munsell_notation(&self, spec: &MunsellSpecification) -> String {
if spec.family == "N" {
format!("N {:.1}", spec.value)
} else {
format!("{:.1}{} {:.1}/{:.1}", spec.hue, spec.family, spec.value, spec.chroma)
}
}
}