use std::error::Error;
use std::fmt;
use std::num::ParseIntError;
use std::str::FromStr;
const A: f32 = -0.14861;
const B: f32 = 1.78277;
const C: f32 = -0.29227;
const D: f32 = -0.90649;
const E: f32 = 1.97294;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Color(pub [f32; 3]);
pub(crate) struct Cubehelix(pub [f32; 3]);
impl Default for Color {
fn default() -> Self {
Self([1.; 3])
}
}
#[derive(Debug, PartialEq)]
pub enum ParseError {
ParseIntError(ParseIntError),
InvalidLength(usize),
}
impl From<ParseIntError> for ParseError {
fn from(value: ParseIntError) -> Self {
Self::ParseIntError(value)
}
}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::ParseIntError(error) => error.fmt(f),
Self::InvalidLength(length) => {
f.write_str(&format!(
"Expected color string of length 3 or 6 characters excluding '#' (length found: {})",
length
))
}
}
}
}
impl Error for ParseError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
Self::ParseIntError(error) => Some(error),
Self::InvalidLength(_) => None,
}
}
}
impl FromStr for Color {
type Err = ParseError;
fn from_str(string: &str) -> Result<Self, Self::Err> {
let string = string.strip_prefix("#").unwrap_or(string);
match string.len() {
3 => Ok(Color([
u8::from_str_radix(&string[0..1].repeat(2), 16)? as f32 / 255.,
u8::from_str_radix(&string[1..2].repeat(2), 16)? as f32 / 255.,
u8::from_str_radix(&string[2..3].repeat(2), 16)? as f32 / 255.,
])),
6 => Ok(Color([
u8::from_str_radix(&string[0..2], 16)? as f32 / 255.,
u8::from_str_radix(&string[2..4], 16)? as f32 / 255.,
u8::from_str_radix(&string[4..6], 16)? as f32 / 255.,
])),
size => Err(ParseError::InvalidLength(size)),
}
}
}
impl fmt::Display for Color {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let [r, g, b] = self.0;
write!(
f,
"#{:02x}{:02x}{:02x}",
(255. * r) as u8,
(255. * g) as u8,
(255. * b) as u8
)
}
}
impl From<Color> for String {
fn from(color: Color) -> String {
let [r, g, b] = color.0;
let r = r.clamp(0., 1.);
let g = g.clamp(0., 1.);
let b = b.clamp(0., 1.);
format!(
"#{:02x}{:02x}{:02x}",
(255. * r) as u8,
(255. * g) as u8,
(255. * b) as u8
)
}
}
impl From<Color> for [f32; 3] {
fn from(color: Color) -> [f32; 3] {
color.0
}
}
impl From<Cubehelix> for Color {
fn from(cubehelix: Cubehelix) -> Color {
let [h, s, l] = cubehelix.0;
let h = if h.is_nan() {
0.
} else {
(h + 120.).to_radians()
};
let a = if s.is_nan() { 0. } else { s * l * (1. - l) };
let cosh = h.cos();
let sinh = h.sin();
Color([
(l + a * (A * cosh + B * sinh)).clamp(0., 1.),
(l + a * (C * cosh + D * sinh)).clamp(0., 1.),
(l + a * (E * cosh)).clamp(0., 1.),
])
}
}
#[derive(Clone)]
pub enum Interpolator {
Linear { a: f32, d: f32 },
Constant { value: f32 },
}
impl Interpolator {
#[inline]
pub(crate) fn interpolate(&self, t: f32) -> f32 {
match self {
Self::Linear { a, d } => a + t * d,
Self::Constant { value } => *value,
}
}
}
#[inline]
pub fn color(a: f32, b: f32) -> Interpolator {
let d = b - a;
if !d.is_nan() && d != 0. {
Interpolator::Linear { a, d }
} else {
Interpolator::Constant {
value: if a.is_nan() { b } else { a },
}
}
}