#![allow(dead_code)]
#[derive(Debug, Clone)]
pub struct RalColor {
pub code: u32,
pub name: String,
pub srgb: [u8; 3],
}
impl RalColor {
pub fn new(code: u32, name: impl Into<String>, srgb: [u8; 3]) -> Self {
Self {
code,
name: name.into(),
srgb,
}
}
pub fn hex(&self) -> String {
format!(
"#{:02X}{:02X}{:02X}",
self.srgb[0], self.srgb[1], self.srgb[2]
)
}
}
#[derive(Debug, Default, Clone)]
pub struct RalPalette {
pub colors: Vec<RalColor>,
}
impl RalPalette {
pub fn new() -> Self {
Self::default()
}
pub fn add(&mut self, color: RalColor) {
self.colors.push(color);
}
pub fn find_by_code(&self, code: u32) -> Option<&RalColor> {
self.colors.iter().find(|c| c.code == code)
}
pub fn count(&self) -> usize {
self.colors.len()
}
}
pub fn export_ral_csv(palette: &RalPalette) -> String {
let mut out = String::from("ral_code,name,hex\n");
for c in &palette.colors {
out.push_str(&format!("RAL {},{},{}\n", c.code, c.name, c.hex()));
}
out
}
pub fn closest_ral(palette: &RalPalette, target: [u8; 3]) -> Option<&RalColor> {
palette.colors.iter().min_by_key(|c| {
let dr = (c.srgb[0] as i32 - target[0] as i32).pow(2);
let dg = (c.srgb[1] as i32 - target[1] as i32).pow(2);
let db = (c.srgb[2] as i32 - target[2] as i32).pow(2);
dr + dg + db
})
}
pub fn validate_ral_palette(palette: &RalPalette) -> bool {
palette
.colors
.iter()
.all(|c| (1000..=9999).contains(&c.code))
}
pub fn sample_ral_palette() -> RalPalette {
let mut p = RalPalette::new();
p.add(RalColor::new(1000, "Green beige", [205, 186, 136]));
p.add(RalColor::new(3000, "Flame red", [171, 35, 40]));
p.add(RalColor::new(9016, "Traffic white", [241, 236, 225]));
p
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_palette_empty() {
assert_eq!(RalPalette::new().count(), 0);
}
#[test]
fn test_add_color() {
let mut p = RalPalette::new();
p.add(RalColor::new(3000, "Flame red", [171, 35, 40]));
assert_eq!(p.count(), 1);
}
#[test]
fn test_find_by_code_found() {
let p = sample_ral_palette();
assert!(p.find_by_code(3000).is_some());
}
#[test]
fn test_find_by_code_not_found() {
let p = RalPalette::new();
assert!(p.find_by_code(1234).is_none());
}
#[test]
fn test_hex_format() {
let c = RalColor::new(3000, "Flame red", [171, 35, 40]);
let h = c.hex();
assert!(h.starts_with('#') && h.len() == 7);
}
#[test]
fn test_export_csv_header() {
assert!(export_ral_csv(&RalPalette::new()).starts_with("ral_code"));
}
#[test]
fn test_closest_ral_empty() {
assert!(closest_ral(&RalPalette::new(), [0, 0, 0]).is_none());
}
#[test]
fn test_closest_ral_finds_result() {
let p = sample_ral_palette();
assert!(closest_ral(&p, [200, 180, 130]).is_some());
}
#[test]
fn test_validate_valid() {
assert!(validate_ral_palette(&sample_ral_palette()));
}
#[test]
fn test_validate_invalid_code() {
let mut p = RalPalette::new();
p.add(RalColor::new(99, "bad", [0, 0, 0]));
assert!(!validate_ral_palette(&p));
}
}