#![allow(clippy::excessive_precision)]
use crate::spaces::{Rgb, Xyz65};
use crate::traits::ColorSpace;
const M0: f64 = -0.14861;
const M1: f64 = 1.78277;
const M2: f64 = -0.29227;
const M3: f64 = -0.90649;
const M4: f64 = 1.97294;
const RAD_TO_DEG: f64 = 180.0 / std::f64::consts::PI;
const DEG_TO_RAD: f64 = std::f64::consts::PI / 180.0;
const DE: f64 = M3 * M4;
const BE: f64 = M1 * M4;
const BCAD: f64 = M1 * M2 - M0 * M3;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Cubehelix {
pub h: f64,
pub s: f64,
pub l: f64,
pub alpha: Option<f64>,
}
impl ColorSpace for Cubehelix {
const MODE: &'static str = "cubehelix";
const CHANNELS: &'static [&'static str] = &["h", "s", "l"];
fn alpha(&self) -> Option<f64> {
self.alpha
}
fn with_alpha(self, alpha: Option<f64>) -> Self {
Self { alpha, ..self }
}
fn to_xyz65(&self) -> Xyz65 {
Rgb::from(*self).to_xyz65()
}
fn from_xyz65(xyz: Xyz65) -> Self {
Rgb::from_xyz65(xyz).into()
}
}
impl From<Rgb> for Cubehelix {
fn from(c: Rgb) -> Self {
let Rgb { r, g, b, alpha } = c;
let l = (BCAD * b + r * DE - g * BE) / (BCAD + DE - BE);
let x = b - l;
let y = (M4 * (g - l) - M2 * x) / M3;
let s = if l == 0.0 || l == 1.0 {
f64::NAN
} else {
(x * x + y * y).sqrt() / (M4 * l * (1.0 - l))
};
let h = if s.is_nan() || s == 0.0 {
f64::NAN
} else {
y.atan2(x) * RAD_TO_DEG - 120.0
};
Self { h, s, l, alpha }
}
}
impl From<Cubehelix> for Rgb {
fn from(c: Cubehelix) -> Self {
let Cubehelix { h, s, l, alpha } = c;
let h_eff = if h.is_nan() { 0.0 } else { h };
let l_eff = if l.is_nan() { 0.0 } else { l };
let s_eff = if s.is_nan() { 0.0 } else { s };
let h_rad = (h_eff + 120.0) * DEG_TO_RAD;
let amp = s_eff * l_eff * (1.0 - l_eff);
let cosh = h_rad.cos();
let sinh = h_rad.sin();
Self {
r: l_eff + amp * (M0 * cosh + M1 * sinh),
g: l_eff + amp * (M2 * cosh + M3 * sinh),
b: l_eff + amp * (M4 * cosh),
alpha,
}
}
}