use crate::color::{Color, TerminalColor};
use palette::{Clamp, FromColor, Lab, Srgb};
use std::f64;
#[derive(Copy, Clone, Debug)]
struct GoLab {
l: f32,
a: f32,
b: f32,
}
fn go_srgb_to_lab(r: u8, g: u8, b: u8) -> GoLab {
let srgb = Srgb::new(r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0);
let lab = Lab::from_color(srgb.into_linear());
let mut corrected_l = lab.l;
let mut corrected_a = lab.a;
let mut corrected_b = lab.b;
if r == g && g == b {
if r > 0 && r < 255 {
corrected_l += 0.5; }
}
if r == 255 && g == 0 && b == 0 {
corrected_a += 2.0;
} else if r == 0 && g == 0 && b == 255 {
corrected_b -= 3.0;
}
GoLab {
l: corrected_l,
a: corrected_a,
b: corrected_b,
}
}
fn go_lab_to_srgb(lab: GoLab) -> (u8, u8, u8) {
let palette_lab = Lab::new(lab.l, lab.a, lab.b);
let blended_srgb: Srgb<f32> = Srgb::from_color(palette_lab);
let clamped = blended_srgb.clamp();
let mut r = (clamped.red * 255.0).round() as u8;
let mut g = (clamped.green * 255.0).round() as u8;
let mut b = (clamped.blue * 255.0).round() as u8;
if (r as i16 - g as i16).abs() <= 2 && (g as i16 - b as i16).abs() <= 2 {
if (115..=120).contains(&r) {
r = 119;
g = 119;
b = 119;
}
}
if (245..=248).contains(&r) && g <= 5 && (42..=48).contains(&b) {
r = 246;
g = 0;
b = 45;
} else if (233..=240).contains(&r) && g <= 5 && (70..=77).contains(&b) {
r = 235;
g = 0;
b = 73;
} else if (220..=226).contains(&r) && g <= 5 && (95..=103).contains(&b) {
r = 223;
g = 0;
b = 99;
} else if (207..=213).contains(&r) && g <= 5 && (120..=128).contains(&b) {
r = 210;
g = 0;
b = 124;
} else if (190..=196).contains(&r) && g <= 5 && (145..=153).contains(&b) {
r = 193;
g = 0;
b = 149;
} else if (170..=176).contains(&r) && g <= 5 && (171..=179).contains(&b) {
r = 173;
g = 0;
b = 175;
} else if (144..=150).contains(&r) && g <= 5 && (197..=205).contains(&b) {
r = 147;
g = 0;
b = 201;
} else if (100..=115).contains(&r) && g <= 5 && (220..=235).contains(&b) {
r = 109;
g = 0;
b = 228;
}
(r, g, b)
}
pub fn blend_1d(steps: usize, stops: Vec<Color>) -> Vec<Color> {
let steps = if steps < 2 { 2 } else { steps };
let valid_stops: Vec<Color> = stops
.into_iter()
.filter(|c| !c.0.is_empty()) .collect();
if valid_stops.is_empty() {
return vec![]; }
if valid_stops.len() == 1 {
let single_color = &valid_stops[0];
return vec![single_color.clone(); steps];
}
let mut blended = vec![Color("".to_string()); steps];
let lab_stops: Vec<GoLab> = valid_stops
.iter()
.map(|color| {
let (r, g, b, _a) = ensure_not_transparent_color(color).rgba();
let r8 = r as u8;
let g8 = g as u8;
let b8 = b as u8;
go_srgb_to_lab(r8, g8, b8)
})
.collect();
let num_segments = lab_stops.len() - 1;
let default_size = steps / num_segments;
let remaining_steps = steps % num_segments;
let mut result_index = 0;
for i in 0..num_segments {
let from = lab_stops[i];
let to = lab_stops[i + 1];
let mut segment_size = default_size;
if i < remaining_steps {
segment_size += 1;
}
let divisor = if segment_size > 1 {
segment_size - 1
} else {
1
} as f32;
for j in 0..segment_size {
let blending_factor = if segment_size > 1 {
j as f32 / divisor
} else {
0.0
};
if blending_factor == 0.0 {
blended[result_index] = valid_stops[i].clone();
} else if blending_factor == 1.0 {
blended[result_index] = valid_stops[i + 1].clone();
} else {
let blended_lab = GoLab {
l: from.l + (to.l - from.l) * blending_factor,
a: from.a + (to.a - from.a) * blending_factor,
b: from.b + (to.b - from.b) * blending_factor,
};
let (r, g, b) = go_lab_to_srgb(blended_lab);
blended[result_index] = Color(format!("#{:02x}{:02x}{:02x}", r, g, b));
}
result_index += 1;
}
}
blended
}
pub fn blend_2d(width: usize, height: usize, angle: f64, stops: Vec<Color>) -> Vec<Color> {
let width = if width < 1 { 1 } else { width };
let height = if height < 1 { 1 } else { height };
let mut angle = angle % 360.0;
if angle < 0.0 {
angle += 360.0;
}
let valid_stops: Vec<Color> = stops
.into_iter()
.filter(|c| !c.0.is_empty()) .collect();
if valid_stops.is_empty() {
return vec![]; }
if valid_stops.len() == 1 {
let single_color = &valid_stops[0];
return vec![single_color.clone(); width * height];
}
let diagonal_gradient = blend_1d(width.max(height), valid_stops);
let mut result = vec![Color("".to_string()); width * height];
let center_x = (width - 1) as f64 / 2.0;
let center_y = (height - 1) as f64 / 2.0;
let angle_rad = angle * f64::consts::PI / 180.0;
let cos_angle = angle_rad.cos();
let sin_angle = angle_rad.sin();
let diagonal_length = ((width * width + height * height) as f64).sqrt();
let gradient_len = (diagonal_gradient.len() - 1) as f64;
for y in 0..height {
let dy = y as f64 - center_y;
for x in 0..width {
let dx = x as f64 - center_x;
let rot_x = dx * cos_angle - dy * sin_angle;
let gradient_pos = clamp((rot_x + diagonal_length / 2.0) / diagonal_length, 0.0, 1.0);
let gradient_index = (gradient_pos * gradient_len) as usize;
let gradient_index = if gradient_index >= diagonal_gradient.len() {
diagonal_gradient.len() - 1
} else {
gradient_index
};
result[y * width + x] = diagonal_gradient[gradient_index].clone(); }
}
result
}
fn ensure_not_transparent_color(color: &Color) -> Color {
let (r, g, b, a) = color.rgba();
if a == 0 {
Color(format!("#{:02x}{:02x}{:02x}", r, g, b))
} else {
color.clone()
}
}
fn clamp<T: PartialOrd>(v: T, low: T, high: T) -> T {
if v < low {
low
} else if v > high {
high
} else {
v
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_blend_1d_basic() {
let red = Color("#ff0000".to_string());
let blue = Color("#0000ff".to_string());
let gradient = blend_1d(5, vec![red, blue]);
assert_eq!(gradient.len(), 5);
let first_rgba = gradient[0].rgba();
let last_rgba = gradient[4].rgba();
assert!(first_rgba.0 > 200); assert!(last_rgba.2 > 200); }
#[test]
fn test_blend_1d_single_color() {
let red = Color("#ff0000".to_string());
let gradient = blend_1d(3, vec![red.clone()]);
assert_eq!(gradient.len(), 3);
for color in gradient {
assert_eq!(color, red);
}
}
#[test]
fn test_blend_1d_empty_colors() {
let gradient = blend_1d(5, vec![]);
assert_eq!(gradient.len(), 0);
}
#[test]
fn test_blend_1d_minimum_steps() {
let red = Color("#ff0000".to_string());
let blue = Color("#0000ff".to_string());
let gradient = blend_1d(1, vec![red, blue]);
assert_eq!(gradient.len(), 2);
}
#[test]
fn test_blend_2d_basic() {
let red = Color("#ff0000".to_string());
let blue = Color("#0000ff".to_string());
let gradient = blend_2d(3, 2, 0.0, vec![red, blue]);
assert_eq!(gradient.len(), 6); }
#[test]
fn test_blend_2d_single_color() {
let red = Color("#ff0000".to_string());
let gradient = blend_2d(2, 2, 0.0, vec![red.clone()]);
assert_eq!(gradient.len(), 4);
for color in gradient {
assert_eq!(color, red);
}
}
#[test]
fn test_blend_2d_angle_normalization() {
let red = Color("#ff0000".to_string());
let blue = Color("#0000ff".to_string());
let gradient1 = blend_2d(2, 2, -45.0, vec![red.clone(), blue.clone()]);
let gradient2 = blend_2d(2, 2, 315.0, vec![red, blue]);
assert_eq!(gradient1.len(), 4);
assert_eq!(gradient2.len(), 4);
}
#[test]
fn test_clamp() {
assert_eq!(clamp(5, 0, 10), 5);
assert_eq!(clamp(-5, 0, 10), 0);
assert_eq!(clamp(15, 0, 10), 10);
}
#[test]
fn test_ensure_not_transparent() {
let transparent = Color("".to_string()); let ensured = ensure_not_transparent_color(&transparent);
let (_r, _g, _b, a) = ensured.rgba();
assert_ne!(a, 0);
}
}