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 std::num::ParseIntError;

use crate::constants::HEX_PATTERN;
use crate::error::ColorConversionError;
use crate::models::{HEX, HSL, HSV, RGB};

pub fn hex_to_rgb(hex_color: &HEX) -> Result<RGB, ColorConversionError> {
    let mut normalized: String = hex_color.normalized();

    if normalized.len() == 3 {
        normalized = normalized.chars().flat_map(|c: char| [c, c]).collect();
    }

    if !HEX_PATTERN.is_match(&normalized) {
        return Err(ColorConversionError::new("hex", "rgb", &hex_color.value));
    }

    let r: u8 = u8::from_str_radix(&normalized[0..2], 16).map_err(|_: ParseIntError| ColorConversionError::new("hex", "rgb", &hex_color.value))?;
    let g: u8 = u8::from_str_radix(&normalized[2..4], 16).map_err(|_: ParseIntError| ColorConversionError::new("hex", "rgb", &hex_color.value))?;
    let b: u8 = u8::from_str_radix(&normalized[4..6], 16).map_err(|_: ParseIntError| ColorConversionError::new("hex", "rgb", &hex_color.value))?;

    Ok(RGB::new(r, g, b))
}

pub fn hsl_to_rgb(hsl: &HSL) -> Result<RGB, ColorConversionError> {
    let (r, g, b): (f32, f32, f32) = hsl_to_rgb_float(hsl.h, hsl.s, hsl.l);
    Ok(RGB::new((r * 255.0).round() as u8, (g * 255.0).round() as u8, (b * 255.0).round() as u8))
}

pub fn hsv_to_rgb(hsv: &HSV) -> Result<RGB, ColorConversionError> {
    let (r, g, b): (f32, f32, f32) = hsv_to_rgb_float(hsv.h, hsv.s, hsv.v);
    Ok(RGB::new((r * 255.0).round() as u8, (g * 255.0).round() as u8, (b * 255.0).round() as u8))
}

fn hsl_to_rgb_float(h: f32, s: f32, l: f32) -> (f32, f32, f32) {
    if s == 0.0 {
        return (l, l, l);
    }

    let q: f32 = if l < 0.5 {
        l * (1.0 + s)
    } else {
        l + s - l * s
    };

    let p: f32 = 2.0 * l - q;

    let r: f32 = hue_to_rgb(p, q, h + 1.0 / 3.0);
    let g: f32 = hue_to_rgb(p, q, h);
    let b: f32 = hue_to_rgb(p, q, h - 1.0 / 3.0);

    (r, g, b)
}

fn hue_to_rgb(p: f32, q: f32, mut t: f32) -> f32 {
    if t < 0.0 {
        t += 1.0;
    }

    if t > 1.0 {
        t -= 1.0;
    }

    if t < 1.0 / 6.0 {
        return p + (q - p) * 6.0 * t;
    }

    if t < 1.0 / 2.0 {
        return q;
    }

    if t < 2.0 / 3.0 {
        return p + (q - p) * (2.0 / 3.0 - t) * 6.0;
    }

    p
}

fn hsv_to_rgb_float(h: f32, s: f32, v: f32) -> (f32, f32, f32) {
    let s: f32 = s / 100.0;
    let v: f32 = v / 100.0;
    let h: f32 = h / 60.0;

    let c: f32 = v * s;
    let x: f32 = c * (1.0 - ((h % 2.0) - 1.0).abs());
    let m: f32 = v - c;

    let (r, g, b): (f32, f32, f32) = match h as i32 {
        0 => (c, x, 0.0),
        1 => (x, c, 0.0),
        2 => (0.0, c, x),
        3 => (0.0, x, c),
        4 => (x, 0.0, c),

        _ => (c, 0.0, x)
    };

    (r + m, g + m, b + m)
}