use core::marker::PhantomData;
#[allow(unused_imports)]
use num_traits::float::FloatCore;
#[allow(unused_imports)]
use num_traits::Euclid;
use super::{FromColor, LinearSrgb};
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct Hsv<M: HsvHueMap = HsvHueRainbow> {
pub hue: HsvHue<M>,
pub saturation: f32,
pub value: f32,
}
impl<M: HsvHueMap> Hsv<M> {
pub fn new(hue: f32, saturation: f32, value: f32) -> Self {
Self {
hue: HsvHue::new(hue),
saturation: saturation.clamp(0.0, 1.0),
value: value.clamp(0.0, 1.0),
}
}
pub fn from_hue(hue: HsvHue<M>, saturation: f32, value: f32) -> Self {
Self {
hue,
saturation: saturation.clamp(0.0, 1.0),
value: value.clamp(0.0, 1.0),
}
}
}
impl<M: HsvHueMap> FromColor<Hsv<M>> for LinearSrgb {
fn from_color(color: Hsv<M>) -> Self {
if color.saturation <= 0.0 {
let v = color.value;
return LinearSrgb::new(v, v, v);
}
if color.value <= 0.0 {
return LinearSrgb::new(0.0, 0.0, 0.0);
}
let rgb = color.hue.to_rgb();
if color.saturation >= 1.0 {
return LinearSrgb::new(
rgb.red * color.value,
rgb.green * color.value,
rgb.blue * color.value,
);
}
let gray = color.value;
let s = color.saturation;
LinearSrgb::new(
rgb.red * s * color.value + gray * (1.0 - s),
rgb.green * s * color.value + gray * (1.0 - s),
rgb.blue * s * color.value + gray * (1.0 - s),
)
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct HsvHue<M: HsvHueMap = HsvHueRainbow> {
map: PhantomData<M>,
inner: f32,
}
impl<M: HsvHueMap> HsvHue<M> {
pub fn new(hue: f32) -> Self {
Self {
map: PhantomData,
inner: Euclid::rem_euclid(&hue, &1.0),
}
}
pub fn inner(self) -> f32 {
self.inner
}
pub fn to_rgb(&self) -> LinearSrgb {
M::hue_to_rgb(self.inner)
}
}
pub trait HsvHueMap: Sized {
fn hue_to_rgb(hue: f32) -> LinearSrgb;
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct HsvHueSpectrum;
impl HsvHueMap for HsvHueSpectrum {
fn hue_to_rgb(hue: f32) -> LinearSrgb {
let h = hue * 3.0; let section = h.floor() as u8; let offset = h - h.floor();
let rise = offset;
let fall = 1.0 - offset;
match section % 3 {
0 => LinearSrgb::new(fall, rise, 0.0), 1 => LinearSrgb::new(0.0, fall, rise), 2 => LinearSrgb::new(rise, 0.0, fall), _ => unreachable!(), }
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct HsvHueRainbow;
impl HsvHueMap for HsvHueRainbow {
fn hue_to_rgb(hue: f32) -> LinearSrgb {
const FRAC_1_3: f32 = 0.333_333_33_f32;
const FRAC_2_3: f32 = 0.666_666_7_f32;
let h8 = hue * 8.0; let section = h8.floor() as u8; let pos = h8 - h8.floor();
match section % 8 {
0 => {
LinearSrgb::new(
1.0 - (pos * FRAC_1_3), pos * FRAC_1_3, 0.0, )
}
1 => {
LinearSrgb::new(
FRAC_2_3, FRAC_1_3 + (pos * FRAC_1_3), 0.0, )
}
2 => {
LinearSrgb::new(
FRAC_2_3 * (1.0 - pos), FRAC_2_3 + (pos * FRAC_1_3), 0.0, )
}
3 => {
LinearSrgb::new(
0.0, 1.0 - (pos * FRAC_1_3), pos * FRAC_1_3, )
}
4 => {
LinearSrgb::new(
0.0, FRAC_2_3 * (1.0 - pos), FRAC_1_3 + (pos * FRAC_2_3), )
}
5 => {
LinearSrgb::new(
pos * FRAC_1_3, 0.0, 1.0 - (pos * FRAC_1_3), )
}
6 => {
LinearSrgb::new(
FRAC_1_3 + (pos * FRAC_1_3), 0.0, FRAC_2_3 - (pos * FRAC_1_3), )
}
7 => {
LinearSrgb::new(
FRAC_2_3 + (pos * FRAC_1_3), 0.0, FRAC_1_3 * (1.0 - pos), )
}
_ => unreachable!(), }
}
}