add matplotlib and simple gradient colormaps
This commit is contained in:
444
src/colormaps.rs
444
src/colormaps.rs
File diff suppressed because one or more lines are too long
201
src/lib.rs
201
src/lib.rs
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user