add matplotlib and simple gradient colormaps

This commit is contained in:
Wim Pomp
2025-08-23 22:12:25 +02:00
parent 5ce9790118
commit cdaee07627
9 changed files with 811 additions and 27987 deletions

View File

@@ -1,6 +1,15 @@
//! Colorcet: a list of [colorcet](https://colorcet.com/),
//! [matplotlib](https://pypi.org/project/matplotlib/) and simple
//! gradient colormaps.
mod colormaps;
use std::ops::Sub;
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)]
@@ -9,67 +18,145 @@ pub enum ColorcetError {
ColormapNotFound(String),
}
pub trait Int: Sub + Sized {
const MIN: Self;
const MAX: Self;
/// Struct holding f64 values defining the colormap.
///
/// # Examples
/// ```
/// use colorcet::ColorMap;
/// use colorgrad::{LinearGradient, Color};
///
/// let colormap: ColorMap = "glasbey".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()
}
}
macro_rules! impl_int {
($($t:tt),+ $(,)?) => {
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", 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 Int for $t {
const MIN: Self = $t::MIN;
const MAX: Self = $t::MAX;
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_int!(
u8, i8, u16, i16, u32, i32, u64, i64, u128, i128, isize, usize
);
pub struct ColorMap([[f64; 3]; 256]);
impl ColorMap {
/// get a vector of rgb values, scaled between 0.0 and 1.0
pub fn get_colors_f64(&self) -> Vec<Vec<f64>> {
self.0.iter().map(|row| row.to_vec()).collect()
}
/// get a vector of rgb values, scaled between T::MIN and T::MAX
pub fn get_colors_int<T>(&self) -> Vec<Vec<T>>
where
T: Int + From<f64>,
f64: From<T>,
{
let a: f64 = T::MIN.into();
let b: f64 = T::MAX.into();
let c = b - a;
self.0
.iter()
.map(|row| row.iter().map(|&i| ((i - a) / c).round().into()).collect())
.collect()
}
}
/// find a colorcet colormap by name, add _r to reverse the colormap
pub fn get_named_colormap(name: &str) -> Result<ColorMap, ColorcetError> {
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_owned();
if reverse {
cmap.reverse();
}
Ok(ColorMap(cmap))
} else {
Err(ColorcetError::ColormapNotFound(name.into()))
}
}
impl_to_gradient!(LinearGradient, CatmullRomGradient, BasisGradient);