use crate::style::Color;
pub fn blend(fg: Color, bg: Color, alpha: f32) -> Color {
let alpha = alpha.clamp(0.0, 1.0);
let inv_alpha = 1.0 - alpha;
let r = (fg.r as f32 * alpha + bg.r as f32 * inv_alpha).round() as u8;
let g = (fg.g as f32 * alpha + bg.g as f32 * inv_alpha).round() as u8;
let b = (fg.b as f32 * alpha + bg.b as f32 * inv_alpha).round() as u8;
Color::rgba(r, g, b, fg.a)
}
pub fn blend_alpha(fg: Color, bg: Color) -> Color {
let fg_alpha = fg.a as f32 / 255.0;
let bg_alpha = bg.a as f32 / 255.0;
let out_alpha = fg_alpha + bg_alpha * (1.0 - fg_alpha);
if out_alpha < f32::EPSILON {
return Color::TRANSPARENT;
}
let r = (fg.r as f32 * fg_alpha + bg.r as f32 * bg_alpha * (1.0 - fg_alpha)) / out_alpha;
let g = (fg.g as f32 * fg_alpha + bg.g as f32 * bg_alpha * (1.0 - fg_alpha)) / out_alpha;
let b = (fg.b as f32 * fg_alpha + bg.b as f32 * bg_alpha * (1.0 - fg_alpha)) / out_alpha;
Color::rgba(
r.round() as u8,
g.round() as u8,
b.round() as u8,
(out_alpha * 255.0).round() as u8,
)
}
pub fn darken(color: Color, amount: f32) -> Color {
let amount = amount.clamp(0.0, 1.0);
let factor = 1.0 - amount;
let r = (color.r as f32 * factor).round() as u8;
let g = (color.g as f32 * factor).round() as u8;
let b = (color.b as f32 * factor).round() as u8;
Color::rgba(r, g, b, color.a)
}
pub fn lighten(color: Color, amount: f32) -> Color {
let amount = amount.clamp(0.0, 1.0);
let r = color.r as f32 + (255.0 - color.r as f32) * amount;
let g = color.g as f32 + (255.0 - color.g as f32) * amount;
let b = color.b as f32 + (255.0 - color.b as f32) * amount;
Color::rgba(r.round() as u8, g.round() as u8, b.round() as u8, color.a)
}
pub fn fade(color: Color, factor: f32) -> Color {
let new_alpha = (color.a as f32 * factor.clamp(0.0, 1.0)).round() as u8;
color.with_alpha(new_alpha)
}
pub fn contrast_color(color: Color) -> Color {
let luminance = relative_luminance(color);
if luminance > 0.5 {
Color::BLACK
} else {
Color::WHITE
}
}
pub fn relative_luminance(color: Color) -> f32 {
let r = srgb_to_linear(color.r as f32 / 255.0);
let g = srgb_to_linear(color.g as f32 / 255.0);
let b = srgb_to_linear(color.b as f32 / 255.0);
0.2126 * r + 0.7152 * g + 0.0722 * b
}
fn srgb_to_linear(value: f32) -> f32 {
if value <= 0.04045 {
value / 12.92
} else {
((value + 0.055) / 1.055).powf(2.4)
}
}
pub fn rgb_to_hsl(color: Color) -> (u16, u8, u8) {
let r = color.r as f32 / 255.0;
let g = color.g as f32 / 255.0;
let b = color.b as f32 / 255.0;
let max = r.max(g).max(b);
let min = r.min(g).min(b);
let l = (max + min) / 2.0;
if (max - min).abs() < f32::EPSILON {
return (0, 0, (l * 100.0) as u8);
}
let d = max - min;
let s = if l > 0.5 {
d / (2.0 - max - min)
} else {
d / (max + min)
};
let h = if (max - r).abs() < f32::EPSILON {
((g - b) / d + if g < b { 6.0 } else { 0.0 }) / 6.0
} else if (max - g).abs() < f32::EPSILON {
((b - r) / d + 2.0) / 6.0
} else {
((r - g) / d + 4.0) / 6.0
};
((h * 360.0) as u16, (s * 100.0) as u8, (l * 100.0) as u8)
}
pub fn hsl_to_rgb(h: u16, s: u8, l: u8) -> Color {
hsl_to_rgba(h, s, l, 255)
}
pub fn hsl_to_rgba(h: u16, s: u8, l: u8, a: u8) -> Color {
let h = h as f32 / 360.0;
let s = s as f32 / 100.0;
let l = l as f32 / 100.0;
if s.abs() < f32::EPSILON {
let v = (l * 255.0) as u8;
return Color::rgba(v, v, v, a);
}
let q = if l < 0.5 {
l * (1.0 + s)
} else {
l + s - l * s
};
let p = 2.0 * l - q;
let 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
};
let r = hue_to_rgb(p, q, h + 1.0 / 3.0);
let g = hue_to_rgb(p, q, h);
let b = hue_to_rgb(p, q, h - 1.0 / 3.0);
Color::rgba((r * 255.0) as u8, (g * 255.0) as u8, (b * 255.0) as u8, a)
}
pub fn adjust_hue(color: Color, degrees: i16) -> Color {
let (h, s, l) = rgb_to_hsl(color);
let new_h = ((h as i32 + degrees as i32).rem_euclid(360)) as u16;
hsl_to_rgba(new_h, s, l, color.a)
}
pub fn saturate(color: Color, amount: f32) -> Color {
let (h, s, l) = rgb_to_hsl(color);
let new_s = ((s as f32 + amount * 100.0).clamp(0.0, 100.0)) as u8;
hsl_to_rgba(h, new_s, l, color.a)
}
pub fn desaturate(color: Color, amount: f32) -> Color {
saturate(color, -amount)
}
pub fn grayscale(color: Color) -> Color {
let gray = (0.299 * color.r as f32 + 0.587 * color.g as f32 + 0.114 * color.b as f32) as u8;
Color::rgba(gray, gray, gray, color.a)
}
pub fn invert(color: Color) -> Color {
Color::rgba(255 - color.r, 255 - color.g, 255 - color.b, color.a)
}
pub fn gradient(from: Color, to: Color, steps: usize) -> Vec<Color> {
if steps == 0 {
return vec![];
}
if steps == 1 {
return vec![from];
}
(0..steps)
.map(|i| {
let t = i as f32 / (steps - 1) as f32;
let inv_t = 1.0 - t;
Color::rgba(
(from.r as f32 * inv_t + to.r as f32 * t).round() as u8,
(from.g as f32 * inv_t + to.g as f32 * t).round() as u8,
(from.b as f32 * inv_t + to.b as f32 * t).round() as u8,
(from.a as f32 * inv_t + to.a as f32 * t).round() as u8,
)
})
.collect()
}
pub fn gradient_at(from: Color, to: Color, t: f32) -> Color {
let t = t.clamp(0.0, 1.0);
let inv_t = 1.0 - t;
Color::rgba(
(from.r as f32 * inv_t + to.r as f32 * t).round() as u8,
(from.g as f32 * inv_t + to.g as f32 * t).round() as u8,
(from.b as f32 * inv_t + to.b as f32 * t).round() as u8,
(from.a as f32 * inv_t + to.a as f32 * t).round() as u8,
)
}
pub mod semantic {
use super::*;
pub const SUCCESS: Color = Color::rgb(76, 175, 80);
pub const WARNING: Color = Color::rgb(255, 152, 0);
pub const ERROR: Color = Color::rgb(244, 67, 54);
pub const INFO: Color = Color::rgb(33, 150, 243);
pub const MUTED: Color = Color::rgb(158, 158, 158);
}