use anyhow::{bail, Result};
use crate::LinearRgb;
#[derive(Debug, Clone)]
pub struct Hsl {
data: Vec<[f32; 3]>,
width: usize,
height: usize,
}
impl Hsl {
pub fn new(data: Vec<[f32; 3]>, width: usize, height: usize) -> Result<Self> {
if data.len() != width * height {
bail!("Data length does not match specified dimensions");
}
Ok(Self {
data,
width,
height,
})
}
#[must_use]
#[inline(always)]
pub fn data(&self) -> &[[f32; 3]] {
&self.data
}
#[must_use]
#[inline(always)]
pub fn data_mut(&mut self) -> &mut [[f32; 3]] {
&mut self.data
}
#[must_use]
#[inline(always)]
pub const fn width(&self) -> usize {
self.width
}
#[must_use]
#[inline(always)]
pub const fn height(&self) -> usize {
self.height
}
}
impl From<LinearRgb> for Hsl {
fn from(lrgb: LinearRgb) -> Self {
let mut data = lrgb.data;
for pix in &mut data {
*pix = lrgb_to_hsl(*pix);
}
Hsl {
data,
width: lrgb.width,
height: lrgb.height,
}
}
}
impl From<Hsl> for LinearRgb {
fn from(hsl: Hsl) -> Self {
let mut data = hsl.data;
for pix in &mut data {
*pix = hsl_to_lrgb(*pix);
}
LinearRgb {
data,
width: hsl.width,
height: hsl.height,
}
}
}
#[inline(always)]
#[allow(clippy::many_single_char_names)]
fn lrgb_to_hsl(rgb: [f32; 3]) -> [f32; 3] {
let x_max = rgb[0].max(rgb[1]).max(rgb[2]);
let x_min = rgb[0].min(rgb[1]).min(rgb[2]);
let v = x_max;
let c = x_max - x_min;
let l = (x_max + x_min) / 2.0;
let h = if c.abs() < f32::EPSILON {
0.0
} else if (v - rgb[0]).abs() < f32::EPSILON {
60.0 * ((rgb[1] - rgb[2]) / c)
} else if (v - rgb[1]).abs() < f32::EPSILON {
60.0 * (2.0 + (rgb[2] - rgb[0]) / c)
} else {
60.0 * (4.0 + (rgb[0] - rgb[1]) / c)
};
let s = if l.abs() < f32::EPSILON || (l - 1.0).abs() < f32::EPSILON {
0.0
} else {
(2.0 * (v - l)) / (1.0 - (2.0 * l - 1.0).abs())
};
[h, s, l]
}
#[inline(always)]
fn hsl_to_lrgb(hsl: [f32; 3]) -> [f32; 3] {
let c = (1.0 - (2.0 * hsl[2] - 1.0).abs()) * hsl[1];
let h_prime = hsl[0] / 60.0;
let x = c * (1.0 - (h_prime % 2.0 - 1.0).abs());
let (r1, g1, b1) = if (0.0..1.0).contains(&h_prime) {
(c, x, 0.0)
} else if (1.0..2.0).contains(&h_prime) {
(x, c, 0.0)
} else if (2.0..3.0).contains(&h_prime) {
(0.0, c, x)
} else if (3.0..4.0).contains(&h_prime) {
(0.0, x, c)
} else if (4.0..5.0).contains(&h_prime) {
(x, 0.0, c)
} else {
(c, 0.0, x)
};
let m = hsl[2] - c / 2.0;
[r1 + m, g1 + m, b1 + m]
}