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::{
    ColorFormattable,
    Color,
    TextStyle,
    CustomColor,
    RGB,
    constants::ANSI_PATTERN
};

pub fn interpolate_color(start: &RGB, end: &RGB, steps: usize) -> Vec<RGB> {
    if steps < 2 {
        return vec![*start];
    }

    let mut colors: Vec<RGB> = Vec::with_capacity(steps);

    for i in 0..steps {
        let ratio: f32 = i as f32 / (steps - 1) as f32;

        let r: u8 = (start.r as f32 + (end.r as i16 - start.r as i16) as f32 * ratio).round() as u8;
        let g: u8 = (start.g as f32 + (end.g as i16 - start.g as i16) as f32 * ratio).round() as u8;
        let b: u8 = (start.b as f32 + (end.b as i16 - start.b as i16) as f32 * ratio).round() as u8;

        (&mut colors).push(RGB::new(r, g, b));
    }

    colors
}

pub fn generate_palette(base_color: &RGB, count: usize) -> Vec<RGB> {
    if count < 1 {
        return vec![];
    }

    if count == 1 {
        return vec![*base_color];
    }

    let lighter: RGB = RGB::new(base_color.r.saturating_add(50), base_color.g.saturating_add(50), base_color.b.saturating_add(50));
    let darker:  RGB = RGB::new(base_color.r.saturating_sub(50), base_color.g.saturating_sub(50), base_color.b.saturating_sub(50));

    interpolate_color(&darker, &lighter, count)
}

pub fn colorize(text: &str, color: impl ColorFormattable, background: Option<impl ColorFormattable>) -> String {
    let mut result: String = String::new();

    if let Some(bg) = background {
        (&mut result).push_str(&Color::new(bg, true).formatted());
    }

    (&mut result).push_str(&Color::new(color, false).formatted());
    (&mut result).push_str(text);
    (&mut result).push_str(&Color::new(TextStyle::Reset, false).formatted());

    result
}

pub fn strip_ansi(text: &str) -> String {
    ANSI_PATTERN.replace_all(text, "").to_string()
}

pub fn gradient(text: &str, start_color: &RGB, end_color: &RGB) -> String {
    if text.is_empty() {
        return String::new();
    }

    let chars: Vec<char> = text.chars().collect();
    let length: usize = chars.len();

    if length == 1 {
        return colorize(text, CustomColor::from(*start_color), None::<TextStyle>);
    }

    let mut result: String = String::new();

    for (i, ch) in chars.iter().enumerate() {
        let ratio: f32 = i as f32 / (length - 1) as f32;

        let r: u8 = (start_color.r as f32 + (end_color.r as i16 - start_color.r as i16) as f32 * ratio).round() as u8;
        let g: u8 = (start_color.g as f32 + (end_color.g as i16 - start_color.g as i16) as f32 * ratio).round() as u8;
        let b: u8 = (start_color.b as f32 + (end_color.b as i16 - start_color.b as i16) as f32 * ratio).round() as u8;

        let color: CustomColor = CustomColor::from(RGB::new(r, g, b));
        (&mut result).push_str(&colorize(&ch.to_string(), color, None::<TextStyle>));
    }

    result
}

pub fn percent_color<T: ColorFormattable>(percent: f32, thresholds: Option<(f32, f32)>, reverse: bool, low_color: T, mid_color: T, high_color: T) -> T {
    let (low, mid): (f32, f32) = thresholds.unwrap_or((50.0, 75.0));

    if reverse {
        if percent < low {
            high_color
        } else if percent < mid {
            mid_color
        } else {
            low_color
        }
    } else {
        if percent < low {
            low_color
        } else if percent < mid {
            mid_color
        } else {
            high_color
        }
    }
}