#[derive(Debug, Clone, Copy)]
pub struct Illuminant {
pub name: &'static str,
pub exif_code: u16,
pub cct: u32,
pub x: f64,
pub y: f64,
}
impl Illuminant {
pub fn xyz(&self) -> [f64; 3] {
let big_x = self.x / self.y;
let big_z = (1.0 - self.x - self.y) / self.y;
[big_x, 1.0, big_z]
}
}
pub const ILLUMINANT_A: Illuminant = Illuminant {
name: "Standard Illuminant A",
exif_code: 17,
cct: 2856,
x: 0.44757,
y: 0.40745,
};
pub const D50: Illuminant = Illuminant {
name: "D50",
exif_code: 23,
cct: 5003,
x: 0.34567,
y: 0.35850,
};
pub const D55: Illuminant = Illuminant {
name: "D55",
exif_code: 20,
cct: 5503,
x: 0.33242,
y: 0.34743,
};
pub const D65: Illuminant = Illuminant {
name: "D65",
exif_code: 21,
cct: 6504,
x: 0.31271,
y: 0.32902,
};
pub const D75: Illuminant = Illuminant {
name: "D75",
exif_code: 22,
cct: 7504,
x: 0.29902,
y: 0.31485,
};
pub const COOL_WHITE_FLUORESCENT: Illuminant = Illuminant {
name: "Cool White Fluorescent",
exif_code: 15,
cct: 4150,
x: 0.37510,
y: 0.36714,
};
static ILLUMINANTS: &[Illuminant] = &[ILLUMINANT_A, COOL_WHITE_FLUORESCENT, D50, D55, D65, D75];
pub fn from_exif_code(code: u16) -> Option<&'static Illuminant> {
ILLUMINANTS.iter().find(|i| i.exif_code == code)
}
pub fn from_name(name: &str) -> Option<&'static Illuminant> {
let lower = name.to_lowercase();
ILLUMINANTS.iter().find(|i| i.name.to_lowercase() == lower)
}
pub fn nearest_by_cct(cct: u32) -> &'static Illuminant {
ILLUMINANTS
.iter()
.min_by_key(|i| (i.cct as i64 - cct as i64).unsigned_abs())
.expect("illuminant table is non-empty")
}
pub fn all_illuminants() -> &'static [Illuminant] {
ILLUMINANTS
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_d65_values() {
assert_eq!(D65.cct, 6504);
assert!((D65.x - 0.31271).abs() < 1e-5);
assert!((D65.y - 0.32902).abs() < 1e-5);
}
#[test]
fn test_d50_xyz() {
let xyz = D50.xyz();
assert!((xyz[0] - 0.9642).abs() < 0.01);
assert!((xyz[1] - 1.0).abs() < 1e-10);
assert!((xyz[2] - 0.8251).abs() < 0.01);
}
#[test]
fn test_exif_lookup() {
let d65 = from_exif_code(21).unwrap();
assert_eq!(d65.name, "D65");
let std_a = from_exif_code(17).unwrap();
assert_eq!(std_a.name, "Standard Illuminant A");
}
#[test]
fn test_unknown_exif_code() {
assert!(from_exif_code(255).is_none());
}
#[test]
fn test_nearest_cct() {
let nearest = nearest_by_cct(6500);
assert_eq!(nearest.name, "D65");
let nearest = nearest_by_cct(3000);
assert_eq!(nearest.name, "Standard Illuminant A");
let nearest = nearest_by_cct(5000);
assert_eq!(nearest.name, "D50");
}
#[test]
fn test_all_illuminants() {
let all = all_illuminants();
assert_eq!(all.len(), 6);
}
}