Files
colorcet/src/lib.rs
2025-08-23 22:28:29 +02:00

182 lines
5.6 KiB
Rust

//! Colorcet: a list of [colorcet](https://colorcet.com/),
//! [matplotlib](https://pypi.org/project/matplotlib/) and simple
//! gradient colormaps.
mod colormaps;
use colorgrad::{
BasisGradient, CatmullRomGradient, Color, Gradient, GradientBuilder, GradientBuilderError,
LinearGradient,
};
use num_traits::{Bounded, Float, FromPrimitive, NumCast, PrimInt, ToPrimitive};
use std::str::FromStr;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum ColorcetError {
#[error("no colormap with name {0} known")]
ColormapNotFound(String),
}
/// Struct holding f64 values defining the colormap.
///
/// # Examples
/// ```
/// use colorcet::ColorMap;
/// use colorgrad::{LinearGradient, Color};
///
/// // get colormap by name, add _r to reverse the colormap
/// let colormap: ColorMap = "glasbey".parse().unwrap();
/// let colormap: ColorMap = "glasbey_r".parse().unwrap();
/// let colormap: ColorMap = "red".parse().unwrap(); // black to red gradient
/// let colormap: ColorMap = "r".parse().unwrap(); // black to red gradient
/// let colormap: ColorMap = "r_r".parse().unwrap(); // red to black gradient
/// let colormap: ColorMap = "#5e6f7a".parse().unwrap();
/// let colormap: ColorMap = "#5e6f7a_r".parse().unwrap();
/// let vec_color: Vec<Color> = colormap.clone().try_into().unwrap();
/// let vec_css: Vec<String> = colormap.clone().try_into().unwrap();
/// let linear_gradient: LinearGradient = colormap.clone().try_into().unwrap();
/// let vec_float: Vec<[f64; 3]> = colormap.clone().get_rgb_float();
/// let vec_int: Vec<[u8; 3]> = colormap.get_rgb_int();
/// ```
#[derive(Clone, Debug)]
pub struct ColorMap(Vec<[f64; 3]>);
impl ColorMap {
/// get a vector of rgb values, scaled between 0.0 and 1.0
pub fn get_rgb_float<T>(&self) -> Vec<[T; 3]>
where
T: Float + NumCast,
{
self.0
.iter()
.map(|color| {
[
T::from(color[0]).unwrap(),
T::from(color[1]).unwrap(),
T::from(color[2]).unwrap(),
]
})
.collect()
}
/// get a vector of rgb values, scaled between T::MIN and T::MAX
pub fn get_rgb_int<T>(&self) -> Vec<[T; 3]>
where
T: Bounded + PrimInt + FromPrimitive + ToPrimitive,
{
let a: f64 = T::min_value().to_f64().unwrap();
let b: f64 = T::max_value().to_f64().unwrap();
let c = b - a;
self.0
.iter()
.map(|color| {
[
T::from_f64(((color[0] - a) / c).round()).unwrap(),
T::from_f64(((color[1] - a) / c).round()).unwrap(),
T::from_f64(((color[2] - a) / c).round()).unwrap(),
]
})
.collect()
}
/// get names of all colormaps defined in this crate
pub fn all_colormap_names() -> Vec<String> {
colormaps::ALIASES
.keys()
.chain(colormaps::COLOR_MAPS.keys())
.map(|k| k.to_string())
.collect()
}
}
impl FromStr for ColorMap {
type Err = ColorcetError;
/// find a colorcet colormap by name, add _r to reverse the colormap
fn from_str(s: &str) -> Result<Self, Self::Err> {
let name = s.to_lowercase();
let name = name.as_str();
let (name0, reverse) = if let Some(name) = name.strip_suffix("_r") {
(name, true)
} else {
(name, false)
};
if let Some(&alias) = colormaps::ALIASES.get(name0)
&& let Some(cmap) = colormaps::COLOR_MAPS.get(alias)
{
let mut cmap = cmap.to_vec();
if reverse {
cmap.reverse();
}
Ok(ColorMap(cmap))
} else if let Ok(cmap) = GradientBuilder::new()
.html_colors(&["#000000", {
match name0 {
"r" => "#ff0000",
"g" => "#008000",
"b" => "#0000ff",
"c" => "#00bfbf",
"m" => "#bf00bf",
"y" => "#bfbf00",
"k" => "#000000",
"w" => "#ffffff",
_ => name0,
}
}])
.build::<LinearGradient>()
{
let mut cmap: Vec<_> = cmap
.colors(256)
.into_iter()
.map(|c| [c.r as f64, c.g as f64, c.b as f64])
.collect();
if reverse {
cmap.reverse();
}
Ok(ColorMap(cmap))
} else {
Err(ColorcetError::ColormapNotFound(name.into()))
}
}
}
impl From<ColorMap> for Vec<Color> {
fn from(value: ColorMap) -> Self {
value
.get_rgb_float::<f32>()
.into_iter()
.map(|c| Color::from((c[0], c[1], c[2])))
.collect()
}
}
impl From<ColorMap> for Vec<String> {
/// convert ColorMap into a Vec of css color strings
fn from(value: ColorMap) -> Self {
value
.get_rgb_float::<f32>()
.into_iter()
.map(|c| Color::from((c[0], c[1], c[2])).to_css_hex())
.collect()
}
}
macro_rules! impl_to_gradient {
($($t:ty $(,)?)*) => {
$(
impl TryFrom<ColorMap> for $t {
type Error = GradientBuilderError;
fn try_from(value: ColorMap) -> Result<Self, Self::Error> {
let colors: Vec<Color> = value.into();
GradientBuilder::new().colors(&colors).build()
}
}
)*
};
}
impl_to_gradient!(LinearGradient, CatmullRomGradient, BasisGradient);