use image::Rgba;
#[derive(Clone, Copy, Debug, Default)]
pub struct Hsl {
pub h: u16,
pub s: u8,
pub l: u8,
}
impl From<Rgba<u8>> for Hsl {
fn from(rgb: Rgba<u8>) -> Self {
HslFloats::from_rgb(&rgb.0).into()
}
}
impl From<HslFloats> for Hsl {
fn from(hsl: HslFloats) -> Self {
Hsl {
h: hsl.h.round() as u16,
s: (hsl.s * 100.0) as u8,
l: (hsl.l * 100.0) as u8,
}
}
}
impl From<Hsl> for HslFloats {
fn from(hslq: Hsl) -> Self {
HslFloats {
h: hslq.h as f64,
s: hslq.s as f64 / 100.0,
l: hslq.l as f64 / 100.0,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Default)]
struct HslFloats {
pub h: f64,
pub s: f64,
pub l: f64,
}
impl HslFloats {
#[allow(clippy::float_cmp, clippy::many_single_char_names)]
pub fn from_rgb(rgb: &[u8]) -> HslFloats {
use std::cmp::{max, min};
let mut h: f64;
let s: f64;
let l: f64;
let (r, g, b) = (rgb[0], rgb[1], rgb[2]);
let max = max(max(r, g), b);
let min = min(min(r, g), b);
let (r, g, b) = (r as f64 / 255_f64, g as f64 / 255_f64, b as f64 / 255_f64);
let (min, max) = (min as f64 / 255_f64, max as f64 / 255_f64);
l = (max + min) / 2_f64;
let delta: f64 = max - min;
if delta == 0_f64 {
return HslFloats {
h: 0_f64,
s: 0_f64,
l,
};
}
if l < 0.5_f64 {
s = delta / (max + min);
} else {
s = delta / (2_f64 - max - min);
}
let r2 = (((max - r) / 6_f64) + (delta / 2_f64)) / delta;
let g2 = (((max - g) / 6_f64) + (delta / 2_f64)) / delta;
let b2 = (((max - b) / 6_f64) + (delta / 2_f64)) / delta;
h = match max {
x if x == r => b2 - g2,
x if x == g => (1_f64 / 3_f64) + r2 - b2,
_ => (2_f64 / 3_f64) + g2 - r2,
};
if h < 0 as f64 {
h += 1_f64;
} else if h > 1_f64 {
h -= 1_f64;
}
let h_degrees = (h * 360_f64 * 100_f64).round() / 100_f64;
HslFloats { h: h_degrees, s, l }
}
#[allow(unused, clippy::many_single_char_names)]
pub fn to_rgb(self) -> (u8, u8, u8) {
if self.s == 0.0 {
let l = percent_to_byte(self.l);
return (l, l, l);
}
let h = self.h / 360.0; let s = self.s;
let l = self.l;
let q = if l < 0.5 {
l * (1.0 + s)
} else {
l + s - (l * s)
};
let p = 2.0 * l - q;
(
percent_to_byte(hue_to_rgb(p, q, h + 1.0 / 3.0)),
percent_to_byte(hue_to_rgb(p, q, h)),
percent_to_byte(hue_to_rgb(p, q, h - 1.0 / 3.0)),
)
}
}
fn percent_to_byte(percent: f64) -> u8 {
(percent * 255.0).round() as u8
}
fn hue_to_rgb(p: f64, q: f64, t: f64) -> f64 {
let t = if t < 0.0 {
t + 1.0
} else if t > 1.0 {
t - 1.0
} else {
t
};
if t < 1.0 / 6.0 {
p + (q - p) * 6.0 * t
} else if t < 1.0 / 2.0 {
q
} else if t < 2.0 / 3.0 {
p + (q - p) * (2.0 / 3.0 - t) * 6.0
} else {
p
}
}
#[cfg(test)]
mod test {
use super::Hsl;
use image::Rgba;
#[test]
fn test_hsl() {
let rgba = Rgba([0x51, 0xff, 0x00, 0xff]);
let hsl: Hsl = rgba.into();
assert_eq!(hsl.h, 101);
}
}