[color] add HSVA conversion
This commit is contained in:
parent
847f18248f
commit
fc3582fbe1
1 changed files with 112 additions and 0 deletions
|
@ -200,3 +200,115 @@ fn test_srgba_conversion() {
|
|||
assert_eq!(srgb_byte_from_linear(l), b);
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// Hue, saturation, value, alpha. All in the range [0, 1].
|
||||
/// No premultiplied alpha.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||
pub struct Hsva {
|
||||
/// hue 0-1
|
||||
pub h: f32,
|
||||
/// saturation 0-1
|
||||
pub s: f32,
|
||||
/// value 0-1
|
||||
pub v: f32,
|
||||
/// alpha 0-1
|
||||
pub a: f32,
|
||||
}
|
||||
|
||||
impl Hsva {
|
||||
pub fn new(h: f32, s: f32, v: f32, a: f32) -> Self {
|
||||
Self { h, s, v, a }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Hsva> for Rgba {
|
||||
fn from(hsva: Hsva) -> Rgba {
|
||||
let Hsva { h, s, v, a } = hsva;
|
||||
let (r, g, b) = rgb_from_hsv((h, s, v));
|
||||
Rgba::new(a * r, a * g, a * b, a)
|
||||
}
|
||||
}
|
||||
impl From<Rgba> for Hsva {
|
||||
fn from(rgba: Rgba) -> Hsva {
|
||||
#![allow(clippy::many_single_char_names)]
|
||||
let Rgba([r, g, b, a]) = rgba;
|
||||
if a == 0.0 {
|
||||
Hsva::default()
|
||||
} else {
|
||||
let (h, s, v) = hsv_from_rgb((r / a, g / a, b / a));
|
||||
Hsva { h, s, v, a }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Hsva> for Srgba {
|
||||
fn from(hsva: Hsva) -> Srgba {
|
||||
Srgba::from(Rgba::from(hsva))
|
||||
}
|
||||
}
|
||||
impl From<Srgba> for Hsva {
|
||||
fn from(srgba: Srgba) -> Hsva {
|
||||
Hsva::from(Rgba::from(srgba))
|
||||
}
|
||||
}
|
||||
|
||||
/// All ranges in 0-1, rgb is linear.
|
||||
pub fn hsv_from_rgb((r, g, b): (f32, f32, f32)) -> (f32, f32, f32) {
|
||||
#![allow(clippy::float_cmp)]
|
||||
#![allow(clippy::many_single_char_names)]
|
||||
let min = r.min(g.min(b));
|
||||
let max = r.max(g.max(b)); // value
|
||||
|
||||
let range = max - min;
|
||||
|
||||
let h = if max == min {
|
||||
0.0 // hue is undefined
|
||||
} else if max == r {
|
||||
(g - b) / (6.0 * range)
|
||||
} else if max == g {
|
||||
(b - r) / (6.0 * range) + 1.0 / 3.0
|
||||
} else {
|
||||
// max == b
|
||||
(r - g) / (6.0 * range) + 2.0 / 3.0
|
||||
};
|
||||
let h = (h + 1.0).fract(); // wrap
|
||||
let s = if max == 0.0 { 0.0 } else { 1.0 - min / max };
|
||||
(h, s, max)
|
||||
}
|
||||
|
||||
/// All ranges in 0-1, rgb is linear.
|
||||
pub fn rgb_from_hsv((h, s, v): (f32, f32, f32)) -> (f32, f32, f32) {
|
||||
#![allow(clippy::many_single_char_names)]
|
||||
let h = (h.fract() + 1.0).fract(); // wrap
|
||||
let s = clamp(s, 0.0..=1.0);
|
||||
|
||||
let f = h * 6.0 - (h * 6.0).floor();
|
||||
let p = v * (1.0 - s);
|
||||
let q = v * (1.0 - f * s);
|
||||
let t = v * (1.0 - (1.0 - f) * s);
|
||||
|
||||
match (h * 6.0).floor() as i32 % 6 {
|
||||
0 => (v, t, p),
|
||||
1 => (q, v, p),
|
||||
2 => (p, v, t),
|
||||
3 => (p, q, v),
|
||||
4 => (t, p, v),
|
||||
5 => (v, p, q),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hsv_roundtrip() {
|
||||
for r in 0..=255 {
|
||||
for g in 0..=255 {
|
||||
for b in 0..=255 {
|
||||
let srgba = Srgba::new(r, g, b, 255);
|
||||
let hsva = Hsva::from(srgba);
|
||||
assert_eq!(srgba, Srgba::from(hsva));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue