chromakitx 1.0.2

A comprehensive color manipulation library for Rust
Documentation
// SPDX-FileCopyrightText: 2023 CELESTIFYX Team
// SPDX-License-Identifier: GPL-3.0-or-later

use crate::{
    error::InvalidColorError,

    constants::{
        MIN_HSL_VALUE,
        MAX_HSL_VALUE,
        MIN_HSV_VALUE,
        MAX_HSV_VALUE,
        VALID_HEX_LENGTHS,
        VALID_HEX_CHARS
    }
};

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct RGB {
    pub r: u8,
    pub g: u8,
    pub b: u8
}

impl RGB {
    pub fn new(r: u8, g: u8, b: u8) -> Self {
        Self {
            r,
            g,
            b
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
pub struct HSL {
    pub h: f32,
    pub s: f32,
    pub l: f32
}

impl HSL {
    pub fn new(h: f32, s: f32, l: f32) -> Result<Self, InvalidColorError> {
        Self::validate_component(h, "Hue", MIN_HSL_VALUE, MAX_HSL_VALUE)?;
        Self::validate_component(s, "Saturation", MIN_HSL_VALUE, MAX_HSL_VALUE)?;
        Self::validate_component(l, "Lightness", MIN_HSL_VALUE, MAX_HSL_VALUE)?;

        Ok(Self {
            h,
            s,
            l
        })
    }

    fn validate_component(value: f32, name: &str, min: f32, max: f32) -> Result<(), InvalidColorError> {
        if !(min..=max).contains(&value) {
            return Err(InvalidColorError::new(value.to_string(), format!("{} must be {}-{}, got {}", name, min, max, value)));
        }

        Ok(())
    }
}

#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
pub struct HSV {
    pub h: f32,
    pub s: f32,
    pub v: f32
}

impl HSV {
    pub fn new(h: f32, s: f32, v: f32) -> Result<Self, InvalidColorError> {
        Self::validate(h, "Hue", MIN_HSV_VALUE, 360.0)?;
        Self::validate(s, "Saturation", MIN_HSV_VALUE, MAX_HSV_VALUE)?;
        Self::validate(v, "Value", MIN_HSV_VALUE, MAX_HSV_VALUE)?;

        Ok(Self {
            h,
            s,
            v
        })
    }

    fn validate(value: f32, name: &str, min: f32, max: f32) -> Result<(), InvalidColorError> {
        if !(min..=max).contains(&value) {
            return Err(InvalidColorError::new(value.to_string(), format!("{} must be {}-{}, got {}", name, min, max, value)));
        }

        Ok(())
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct HEX {
    pub value: String
}

impl HEX {
    pub fn new(value: impl ToString) -> Result<Self, InvalidColorError> {
        let val:        String = value.to_string();
        let normalized: String = val.trim_start_matches('#').to_uppercase();

        if !VALID_HEX_LENGTHS.contains(&normalized.len()) {
            return Err(InvalidColorError::new(&val, format!("Hex color must be 3 or 6 characters, got {}: {}", normalized.len(), val)));
        }

        if !normalized.chars().all(|c: char| VALID_HEX_CHARS.contains(c)) {
            return Err(InvalidColorError::new(&val, format!("Hex color contains invalid characters: {}", val)));
        }

        Ok(Self {
            value: val
        })
    }

    pub fn normalized(&self) -> String {
        self.value.trim_start_matches('#').to_uppercase()
    }
}