//! 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 = colormap.clone().try_into().unwrap(); /// let vec_css: Vec = 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(&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(&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 { 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 { 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::() { 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 for Vec { fn from(value: ColorMap) -> Self { value .get_rgb_float::() .into_iter() .map(|c| Color::from((c[0], c[1], c[2]))) .collect() } } impl From for Vec { /// convert ColorMap into a Vec of css color strings fn from(value: ColorMap) -> Self { value .get_rgb_float::() .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 for $t { type Error = GradientBuilderError; fn try_from(value: ColorMap) -> Result { let colors: Vec = value.into(); GradientBuilder::new().colors(&colors).build() } } )* }; } impl_to_gradient!(LinearGradient, CatmullRomGradient, BasisGradient);