182 lines
5.6 KiB
Rust
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);
|