#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Color {
pub r: u8,
pub g: u8,
pub b: u8,
pub a: u8,
}
impl Color {
#[must_use]
pub const fn rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
Self { r, g, b, a }
}
#[must_use]
pub const fn rgb(r: u8, g: u8, b: u8) -> Self {
Self::rgba(r, g, b, 255)
}
#[must_use]
pub const fn to_rgba(&self) -> [u8; 4] {
[self.r, self.g, self.b, self.a]
}
#[must_use]
pub fn lerp(&self, other: &Self, t: f32) -> Self {
let t = t.clamp(0.0, 1.0);
let r = (f32::from(self.r) * (1.0 - t) + f32::from(other.r) * t) as u8;
let g = (f32::from(self.g) * (1.0 - t) + f32::from(other.g) * t) as u8;
let b = (f32::from(self.b) * (1.0 - t) + f32::from(other.b) * t) as u8;
let a = (f32::from(self.a) * (1.0 - t) + f32::from(other.a) * t) as u8;
Self::rgba(r, g, b, a)
}
pub fn from_hex(hex: &str) -> Result<Self, String> {
let hex = hex.trim_start_matches('#');
match hex.len() {
6 => {
let r = u8::from_str_radix(&hex[0..2], 16).map_err(|e| e.to_string())?;
let g = u8::from_str_radix(&hex[2..4], 16).map_err(|e| e.to_string())?;
let b = u8::from_str_radix(&hex[4..6], 16).map_err(|e| e.to_string())?;
Ok(Self::rgb(r, g, b))
}
8 => {
let r = u8::from_str_radix(&hex[0..2], 16).map_err(|e| e.to_string())?;
let g = u8::from_str_radix(&hex[2..4], 16).map_err(|e| e.to_string())?;
let b = u8::from_str_radix(&hex[4..6], 16).map_err(|e| e.to_string())?;
let a = u8::from_str_radix(&hex[6..8], 16).map_err(|e| e.to_string())?;
Ok(Self::rgba(r, g, b, a))
}
_ => Err(format!("Invalid hex color length: {}", hex.len())),
}
}
pub const WHITE: Self = Self::rgb(255, 255, 255);
pub const BLACK: Self = Self::rgb(0, 0, 0);
pub const RED: Self = Self::rgb(255, 0, 0);
pub const GREEN: Self = Self::rgb(0, 255, 0);
pub const BLUE: Self = Self::rgb(0, 0, 255);
pub const YELLOW: Self = Self::rgb(255, 255, 0);
pub const CYAN: Self = Self::rgb(0, 255, 255);
pub const MAGENTA: Self = Self::rgb(255, 0, 255);
pub const GRAY: Self = Self::rgb(128, 128, 128);
pub const ORANGE: Self = Self::rgb(255, 165, 0);
pub const PURPLE: Self = Self::rgb(128, 0, 128);
pub const PINK: Self = Self::rgb(255, 192, 203);
pub const BROWN: Self = Self::rgb(165, 42, 42);
pub const REBECCAPURPLE: Self = Self::rgb(102, 51, 153);
pub const TRANSPARENT: Self = Self::rgba(0, 0, 0, 0);
}
pub struct Palette {
colors: Vec<Color>,
}
impl Palette {
#[must_use]
pub fn new(colors: Vec<Color>) -> Self {
Self { colors }
}
#[must_use]
pub fn get(&self, index: usize) -> Color {
self.colors[index % self.colors.len()]
}
#[must_use]
pub fn default() -> Self {
Self::new(vec![
Color::from_hex("#1f77b4").unwrap(), Color::from_hex("#ff7f0e").unwrap(), Color::from_hex("#2ca02c").unwrap(), Color::from_hex("#d62728").unwrap(), Color::from_hex("#9467bd").unwrap(), Color::from_hex("#8c564b").unwrap(), Color::from_hex("#e377c2").unwrap(), Color::from_hex("#7f7f7f").unwrap(), ])
}
#[must_use]
pub fn colorblind_safe() -> Self {
Self::new(vec![
Color::from_hex("#E69F00").unwrap(), Color::from_hex("#56B4E9").unwrap(), Color::from_hex("#009E73").unwrap(), Color::from_hex("#F0E442").unwrap(), Color::from_hex("#0072B2").unwrap(), Color::from_hex("#D55E00").unwrap(), Color::from_hex("#CC79A7").unwrap(), ])
}
}
impl Default for Palette {
fn default() -> Self {
Self::default()
}
}
#[derive(Debug, Clone)]
pub struct Colormap {
colors: Vec<Color>,
name: String,
}
impl Colormap {
#[must_use]
pub fn new(name: impl Into<String>, colors: Vec<Color>) -> Self {
Self {
name: name.into(),
colors,
}
}
#[must_use]
pub fn name(&self) -> &str {
&self.name
}
#[must_use]
pub fn get(&self, t: f32) -> Color {
let t = t.clamp(0.0, 1.0);
if self.colors.is_empty() {
return Color::BLACK;
}
if self.colors.len() == 1 {
return self.colors[0];
}
let scaled = t * (self.colors.len() - 1) as f32;
let index = scaled.floor() as usize;
let frac = scaled - index as f32;
if index >= self.colors.len() - 1 {
return self.colors[self.colors.len() - 1];
}
self.colors[index].lerp(&self.colors[index + 1], frac)
}
#[must_use]
pub fn viridis() -> Self {
Self::new(
"viridis",
vec![
Color::from_hex("#440154").unwrap(),
Color::from_hex("#482878").unwrap(),
Color::from_hex("#3e4989").unwrap(),
Color::from_hex("#31688e").unwrap(),
Color::from_hex("#26828e").unwrap(),
Color::from_hex("#1f9e89").unwrap(),
Color::from_hex("#35b779").unwrap(),
Color::from_hex("#6ece58").unwrap(),
Color::from_hex("#b5de2b").unwrap(),
Color::from_hex("#fde725").unwrap(),
],
)
}
#[must_use]
pub fn plasma() -> Self {
Self::new(
"plasma",
vec![
Color::from_hex("#0d0887").unwrap(),
Color::from_hex("#46039f").unwrap(),
Color::from_hex("#7201a8").unwrap(),
Color::from_hex("#9c179e").unwrap(),
Color::from_hex("#bd3786").unwrap(),
Color::from_hex("#d8576b").unwrap(),
Color::from_hex("#ed7953").unwrap(),
Color::from_hex("#fb9f3a").unwrap(),
Color::from_hex("#fdca26").unwrap(),
Color::from_hex("#f0f921").unwrap(),
],
)
}
#[must_use]
pub fn inferno() -> Self {
Self::new(
"inferno",
vec![
Color::from_hex("#000004").unwrap(),
Color::from_hex("#1b0c41").unwrap(),
Color::from_hex("#4a0c6b").unwrap(),
Color::from_hex("#781c6d").unwrap(),
Color::from_hex("#a52c60").unwrap(),
Color::from_hex("#cf4446").unwrap(),
Color::from_hex("#ed6925").unwrap(),
Color::from_hex("#fb9b06").unwrap(),
Color::from_hex("#f7d13d").unwrap(),
Color::from_hex("#fcffa4").unwrap(),
],
)
}
#[must_use]
pub fn magma() -> Self {
Self::new(
"magma",
vec![
Color::from_hex("#000004").unwrap(),
Color::from_hex("#180f3d").unwrap(),
Color::from_hex("#440f76").unwrap(),
Color::from_hex("#721f81").unwrap(),
Color::from_hex("#9e2f7f").unwrap(),
Color::from_hex("#cd4071").unwrap(),
Color::from_hex("#f1605d").unwrap(),
Color::from_hex("#fd9668").unwrap(),
Color::from_hex("#feca8d").unwrap(),
Color::from_hex("#fcfdbf").unwrap(),
],
)
}
#[must_use]
pub fn coolwarm() -> Self {
Self::new(
"coolwarm",
vec![
Color::from_hex("#3b4cc0").unwrap(),
Color::from_hex("#7396d3").unwrap(),
Color::from_hex("#a5c5e7").unwrap(),
Color::from_hex("#dddddd").unwrap(),
Color::from_hex("#edac9c").unwrap(),
Color::from_hex("#da746a").unwrap(),
Color::from_hex("#b40426").unwrap(),
],
)
}
#[must_use]
pub fn jet() -> Self {
Self::new(
"jet",
vec![
Color::from_hex("#00007f").unwrap(),
Color::from_hex("#0000ff").unwrap(),
Color::from_hex("#007fff").unwrap(),
Color::from_hex("#00ffff").unwrap(),
Color::from_hex("#7fff7f").unwrap(),
Color::from_hex("#ffff00").unwrap(),
Color::from_hex("#ff7f00").unwrap(),
Color::from_hex("#ff0000").unwrap(),
Color::from_hex("#7f0000").unwrap(),
],
)
}
#[must_use]
pub fn grayscale() -> Self {
Self::new("grayscale", vec![Color::BLACK, Color::WHITE])
}
}