pub mod maximum_chromas_data;
pub mod munsell_renotation_dataset;
pub mod color_ish;
pub mod achromatic;
pub mod iscc_nbs_colors;
pub mod iscc_nbs_polygons;
pub mod illuminants;
pub mod chromatic_adaptation;
pub mod centore_polyhedra;
pub use munsell_renotation_dataset::MUNSELL_RENOTATION_DATA;
pub use maximum_chromas_data::MAXIMUM_CHROMAS;
pub use color_ish::{
COLOR_TO_ISH_MAPPINGS, OVERLAY_TO_ISH_MAPPINGS,
get_color_ish, get_overlay_ish, is_overlay_name, construct_overlay_descriptor,
};
pub use achromatic::{ACHROMATIC_BOUNDARIES, get_achromatic_color_number, get_achromatic_color_name, is_achromatic_hue};
pub use iscc_nbs_colors::{ISCC_NBS_COLORS, IsccNbsColorEntry, get_color_by_number, color_entry_to_metadata, get_all_color_numbers};
pub use iscc_nbs_polygons::{PolygonDefinition, PolygonPoint, get_polygon_definitions};
pub use illuminants::*;
pub use chromatic_adaptation::*;
pub use centore_polyhedra::{get_polyhedron_data, get_sample_count, CENTORE_SAMPLE_COUNTS};
pub const MG_OXIDE_REFLECTANCE: f64 = 0.975;
pub const ASTM_D1535_COEFFICIENTS: [f64; 5] = [
1.1914, -0.22533, 0.23352, -0.020484, 0.00081939, ];
pub const ACHROMATIC_THRESHOLD: f64 = 1e-6;
pub const NEWTON_RAPHSON_TOLERANCE: f64 = 1e-10;
pub const NEWTON_RAPHSON_MAX_ITERATIONS: usize = 100;
pub const SRGB_GAMMA_THRESHOLD: f64 = 0.04045;
pub const SRGB_LINEAR_FACTOR: f64 = 12.92;
pub const SRGB_GAMMA_EXPONENT: f64 = 2.4;
pub const SRGB_GAMMA_OFFSET: f64 = 0.055;
pub const SRGB_GAMMA_SCALE: f64 = 1.055;
pub const SRGB_TO_XYZ_MATRIX: [[f64; 3]; 3] = [
[0.4124564, 0.3575761, 0.1804375], [0.2126729, 0.7151522, 0.0721750], [0.0193339, 0.1191920, 0.9503041], ];
pub const MUNSELL_HUE_FAMILIES: [&str; 10] = [
"R", "YR", "Y", "GY", "G", "BG", "B", "PB", "P", "RP"
];
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_astm_polynomial_coefficients() {
assert_eq!(ASTM_D1535_COEFFICIENTS.len(), 5);
assert!((ASTM_D1535_COEFFICIENTS[0] - 1.1914).abs() < 1e-6);
}
#[test]
fn test_illuminant_c_coordinates() {
assert!((ILLUMINANT_C[0] - 0.31006).abs() < 1e-6);
assert!((ILLUMINANT_C[1] - 0.31616).abs() < 1e-6);
}
#[test]
fn test_munsell_renotation_data_size() {
assert_eq!(MUNSELL_RENOTATION_DATA.len(), 4995);
}
#[test]
fn test_munsell_renotation_data_format() {
let first_entry = MUNSELL_RENOTATION_DATA[0];
let ((hue, value, chroma), (x, y, y_scaled)) = first_entry;
assert_eq!(hue, "2.5GY");
assert!((value - 0.2).abs() < 1e-6);
assert!((chroma - 2.0).abs() < 1e-6);
assert!(x > 0.0);
assert!(y > 0.0);
assert!(y_scaled > 0.0);
}
#[test]
fn test_y_scaling_applied() {
let entry = MUNSELL_RENOTATION_DATA[0];
let (_, (_, _, y_unscaled)) = entry;
let true_y = y_unscaled * MG_OXIDE_REFLECTANCE;
let expected_true_y = 0.237 * 0.975;
assert!((true_y - expected_true_y).abs() < 1e-4,
"Expected true Y: {}, got: {}, difference: {}",
expected_true_y, true_y, (true_y - expected_true_y).abs());
assert!((y_unscaled - 0.237).abs() < 1e-6,
"Expected unscaled Y: 0.237, got: {}", y_unscaled);
}
#[test]
fn test_constant_ranges_and_validity() {
assert!(ILLUMINANT_C[0] >= 0.0 && ILLUMINANT_C[0] <= 1.0);
assert!(ILLUMINANT_C[1] >= 0.0 && ILLUMINANT_C[1] <= 1.0);
assert!(ILLUMINANT_C[0] + ILLUMINANT_C[1] <= 1.0);
assert!(MG_OXIDE_REFLECTANCE > 0.9 && MG_OXIDE_REFLECTANCE < 1.0);
assert!(ACHROMATIC_THRESHOLD > 0.0);
assert!(ACHROMATIC_THRESHOLD < 0.1); assert!(NEWTON_RAPHSON_TOLERANCE > 0.0);
assert!(NEWTON_RAPHSON_TOLERANCE < 1e-5); assert!(NEWTON_RAPHSON_MAX_ITERATIONS > 10);
assert!(NEWTON_RAPHSON_MAX_ITERATIONS < 1000);
assert!(SRGB_GAMMA_THRESHOLD > 0.0 && SRGB_GAMMA_THRESHOLD < 0.1);
assert!(SRGB_LINEAR_FACTOR > 10.0 && SRGB_LINEAR_FACTOR < 15.0);
}
#[test]
fn test_astm_polynomial_all_coefficients() {
let expected_coeffs = [1.1914, -0.22533, 0.23352, -0.020484, 0.00081939];
for (i, (actual, expected)) in ASTM_D1535_COEFFICIENTS.iter().zip(expected_coeffs.iter()).enumerate() {
assert!((actual - expected).abs() < 1e-6,
"Coefficient {} mismatch: expected {}, got {}", i, expected, actual);
}
let v = 5.0;
let mut y = ASTM_D1535_COEFFICIENTS[0] * v +
ASTM_D1535_COEFFICIENTS[1] * v * v +
ASTM_D1535_COEFFICIENTS[2] * v * v * v +
ASTM_D1535_COEFFICIENTS[3] * v * v * v * v +
ASTM_D1535_COEFFICIENTS[4] * v * v * v * v * v;
y /= 100.0;
assert!(y > 0.15 && y < 0.25, "Y value {} for V=5 is unreasonable", y);
}
#[test]
fn test_renotation_data_completeness() {
let mut hue_families = std::collections::HashSet::new();
let mut all_hues = Vec::new();
for &((hue, _, _), _) in MUNSELL_RENOTATION_DATA.iter().take(50) { all_hues.push(hue);
if let Some(family) = extract_hue_family(hue) {
hue_families.insert(family);
}
}
let commonly_present = ["R", "Y", "G", "B", "P"];
let mut missing_families = Vec::new();
for &family in &commonly_present {
if !hue_families.contains(family) {
missing_families.push(family);
}
}
assert!(missing_families.len() <= 2,
"Too many core hue families missing: {:?}. Found families: {:?}",
missing_families, hue_families);
}
#[test]
fn test_renotation_data_value_ranges() {
let mut invalid_y_count = 0;
let mut max_y = 0.0f64;
for &((hue, value, chroma), (x, y, y_scaled)) in MUNSELL_RENOTATION_DATA.iter() {
assert!(value >= 0.0 && value <= 10.0, "Invalid value: {}", value);
assert!(chroma >= 0.0 && chroma <= 50.0, "Invalid chroma: {}", chroma);
assert!(x >= -0.3, "Unreasonable negative x chromaticity: {} for hue {} value {} chroma {}", x, hue, value, chroma);
if y > 1.0 {
invalid_y_count += 1;
if y > max_y {
max_y = y;
}
}
assert!(y >= -0.01 && y <= 3.0, "Unreasonable y chromaticity: {} for hue {} value {} chroma {}", y, hue, value, chroma);
assert!(y_scaled >= 0.0, "Invalid Y value: {}", y_scaled);
}
}
#[test]
fn test_renotation_data_sorting_and_uniqueness() {
let mut prev_entry: Option<(&str, f64, f64)> = None;
let mut duplicate_count = 0;
for &((hue, value, chroma), _) in MUNSELL_RENOTATION_DATA.iter().take(100) {
if let Some((prev_hue, prev_value, prev_chroma)) = prev_entry {
if hue == prev_hue && (value - prev_value).abs() < 1e-6 && (chroma - prev_chroma).abs() < 1e-6 {
duplicate_count += 1;
}
}
prev_entry = Some((hue, value, chroma));
}
assert!(duplicate_count < 10, "Too many duplicate entries: {}", duplicate_count);
}
#[test]
fn test_constant_precision() {
assert_eq!(format!("{:.5}", ILLUMINANT_C[0]), "0.31006");
assert_eq!(format!("{:.5}", ILLUMINANT_C[1]), "0.31616");
assert_eq!(format!("{:.3}", MG_OXIDE_REFLECTANCE), "0.975");
assert!(ACHROMATIC_THRESHOLD <= 1e-6);
assert!(NEWTON_RAPHSON_TOLERANCE <= 1e-10);
}
fn extract_hue_family(hue: &str) -> Option<String> {
let families = ["RP", "R", "YR", "Y", "GY", "G", "BG", "B", "PB", "P"];
for family in &families {
if hue.contains(family) {
return Some(family.to_string());
}
}
None
}
}