use num_traits::Float;
use std::fmt::{Display, Formatter, Result as FmtResult};
use crate::{
config::PRINT_BLOCK,
error::{
InterpolationError, Result, format_terminal_color, normalize_hue, safe_constant, validate_interpolation_factor,
validate_unit_component,
},
spaces::{Grey, GreyAlpha, Hsl, HslAlpha, HsvAlpha, Lab, LabAlpha, Rgb, RgbAlpha, Srgb, SrgbAlpha, Xyz, XyzAlpha},
traits::{Colour, Convert},
};
#[derive(Debug, Clone, Copy)]
pub struct Hsv<T: Float + Send + Sync> {
hue: T,
saturation: T,
value: T,
}
impl<T: Float + Send + Sync> Hsv<T> {
pub fn new(hue: T, saturation: T, value: T) -> Result<Self> {
let normalized_hue = normalize_hue(hue)?;
validate_unit_component(saturation, "saturation")?;
validate_unit_component(value, "value")?;
Ok(Self {
hue: normalized_hue,
saturation,
value,
})
}
pub const fn hue(&self) -> T {
self.hue
}
pub const fn saturation(&self) -> T {
self.saturation
}
pub const fn value(&self) -> T {
self.value
}
pub fn set_hue(&mut self, hue: T) -> Result<()> {
self.hue = normalize_hue(hue)?;
Ok(())
}
pub fn set_saturation(&mut self, saturation: T) -> Result<()> {
validate_unit_component(saturation, "saturation")?;
self.saturation = saturation;
Ok(())
}
pub fn set_value(&mut self, value: T) -> Result<()> {
validate_unit_component(value, "value")?;
self.value = value;
Ok(())
}
pub fn set_components(&mut self, hue: T, saturation: T, value: T) -> Result<()> {
let normalized_hue = normalize_hue(hue)?;
validate_unit_component(saturation, "saturation")?;
validate_unit_component(value, "value")?;
self.hue = normalized_hue;
self.saturation = saturation;
self.value = value;
Ok(())
}
}
impl<T: Float + Send + Sync> Colour<T, 3> for Hsv<T> {
fn from_hex(hex: &str) -> Result<Self> {
Rgb::from_hex(hex)?.to_hsv()
}
fn to_hex(&self) -> Result<String> {
self.to_rgb()?.to_hex()
}
fn from_bytes(bytes: [u8; 3]) -> Result<Self> {
Rgb::from_bytes(bytes)?.to_hsv()
}
fn to_bytes(self) -> Result<[u8; 3]> {
self.to_rgb()?.to_bytes()
}
fn lerp(lhs: &Self, rhs: &Self, t: T) -> Result<Self> {
validate_interpolation_factor(t)?;
let mut hue_diff = rhs.hue - lhs.hue;
let f180 = safe_constant::<u32, T>(180)?;
let f360 = safe_constant::<u32, T>(360)?;
if hue_diff > f180 {
hue_diff = hue_diff - f360;
} else if hue_diff < -f180 {
hue_diff = hue_diff + f360;
}
let mut hue = lhs.hue + t * hue_diff;
if hue < T::zero() {
hue = hue + f360;
} else if hue >= f360 {
hue = hue - f360;
}
if !hue.is_finite() {
return Err(InterpolationError::HueInterpolation {
hue1: lhs.hue.to_f64().unwrap_or(f64::NAN),
hue2: rhs.hue.to_f64().unwrap_or(f64::NAN),
}
.into());
}
let saturation = lhs.saturation * (T::one() - t) + rhs.saturation * t;
let value = lhs.value * (T::one() - t) + rhs.value * t;
Self::new(hue, saturation, value)
}
}
impl<T: Float + Send + Sync> Convert<T> for Hsv<T> {
fn to_grey(&self) -> Result<Grey<T>> {
Grey::new(self.value)
}
fn to_grey_alpha(&self) -> Result<GreyAlpha<T>> {
GreyAlpha::new(self.value, T::one())
}
fn to_hsl(&self) -> Result<Hsl<T>> {
let hue = self.hue;
let lightness = self.value * (safe_constant::<f64, T>(2.0)? - self.saturation) / safe_constant(2.0)?;
let saturation = if lightness.abs() < T::epsilon() || (lightness - T::one()).abs() < T::epsilon() {
T::zero()
} else {
let denominator = T::one() - (safe_constant::<f64, T>(2.0)? * lightness - T::one()).abs();
if denominator.abs() < T::epsilon() {
T::zero()
} else {
self.value * self.saturation / denominator
}
};
Hsl::new(hue, saturation, lightness)
}
fn to_hsl_alpha(&self) -> Result<HslAlpha<T>> {
let hsl = self.to_hsl()?;
HslAlpha::new(hsl.hue(), hsl.saturation(), hsl.lightness(), T::one())
}
fn to_hsv(&self) -> Result<Self> {
Ok(*self)
}
fn to_hsv_alpha(&self) -> Result<HsvAlpha<T>> {
HsvAlpha::new(self.hue, self.saturation, self.value, T::one())
}
fn to_lab(&self) -> Result<Lab<T>> {
self.to_xyz()?.to_lab()
}
fn to_lab_alpha(&self) -> Result<LabAlpha<T>> {
let lab = self.to_lab()?;
LabAlpha::new(lab.lightness(), lab.a_star(), lab.b_star(), T::one())
}
fn to_rgb(&self) -> Result<Rgb<T>> {
let h = self.hue;
let s = self.saturation;
let v = self.value;
if s.abs() < T::epsilon() {
return Rgb::new(v, v, v);
}
let c = v * s; let h_prime = h / safe_constant(60.0)?;
let x = c * (T::one() - ((h_prime % safe_constant(2.0)?) - T::one()).abs());
let m = v - c;
let (r_prime, g_prime, b_prime) = if h < safe_constant(60.0)? {
(c, x, T::zero())
} else if h < safe_constant(120.0)? {
(x, c, T::zero())
} else if h < safe_constant(180.0)? {
(T::zero(), c, x)
} else if h < safe_constant(240.0)? {
(T::zero(), x, c)
} else if h < safe_constant(300.0)? {
(x, T::zero(), c)
} else {
(c, T::zero(), x)
};
Rgb::new(r_prime + m, g_prime + m, b_prime + m)
}
fn to_rgb_alpha(&self) -> Result<RgbAlpha<T>> {
let rgb = self.to_rgb()?;
RgbAlpha::new(rgb.red(), rgb.green(), rgb.blue(), T::one())
}
fn to_srgb(&self) -> Result<Srgb<T>> {
let rgb = self.to_rgb()?;
let r_srgb = Srgb::gamma_encode(rgb.red())?;
let g_srgb = Srgb::gamma_encode(rgb.green())?;
let b_srgb = Srgb::gamma_encode(rgb.blue())?;
Srgb::new(r_srgb, g_srgb, b_srgb)
}
fn to_srgb_alpha(&self) -> Result<SrgbAlpha<T>> {
let srgb = self.to_srgb()?;
SrgbAlpha::new(srgb.red(), srgb.green(), srgb.blue(), T::one())
}
fn to_xyz(&self) -> Result<Xyz<T>> {
self.to_rgb()?.to_xyz()
}
fn to_xyz_alpha(&self) -> Result<XyzAlpha<T>> {
let xyz = self.to_xyz()?;
XyzAlpha::new(xyz.x(), xyz.y(), xyz.z(), T::one())
}
}
impl<T: Float + Send + Sync> Display for Hsv<T> {
fn fmt(&self, fmt: &mut Formatter<'_>) -> FmtResult {
let rgb = self.to_rgb()?;
let color_string = format_terminal_color(rgb.red(), rgb.green(), rgb.blue(), PRINT_BLOCK)?;
write!(fmt, "{color_string}")
}
}