1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
//! HWB color space — hue / whiteness / blackness.
//!
//! Conversions track culori 4.0.2 (`node_modules/culori/src/hwb/`). HWB is
//! a direct reparameterization of HSV (Smith 1996); no XYZ trip is
//! involved. `to_xyz65` / `from_xyz65` compose with the [`Hsv`] hub
//! conversion (which itself goes via [`Rgb`]).
//!
//! When `w + b > 1` the color is achromatic and culori normalizes by
//! dividing both by their sum. Hue is powerless under that condition; the
//! NaN sentinel mirrors Hsv/Hsl when the source color is achromatic.
use crate::spaces::{Hsv, Rgb, Xyz65};
use crate::traits::ColorSpace;
/// HWB — hue (degrees, 0..360), whiteness (0..1), blackness (0..1). For
/// achromatic colors `h` is NaN.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Hwb {
/// Hue in degrees, NaN for achromatic colors.
pub h: f64,
/// Whiteness in 0..1.
pub w: f64,
/// Blackness in 0..1.
pub b: f64,
/// Optional alpha in 0..1.
pub alpha: Option<f64>,
}
impl ColorSpace for Hwb {
const MODE: &'static str = "hwb";
const CHANNELS: &'static [&'static str] = &["h", "w", "b"];
fn alpha(&self) -> Option<f64> {
self.alpha
}
fn with_alpha(self, alpha: Option<f64>) -> Self {
Self { alpha, ..self }
}
fn to_xyz65(&self) -> Xyz65 {
Hsv::from(*self).to_xyz65()
}
fn from_xyz65(xyz: Xyz65) -> Self {
Hsv::from_xyz65(xyz).into()
}
}
impl From<Hsv> for Hwb {
fn from(c: Hsv) -> Self {
// culori: w = (1 - s) * v, b = 1 - v. Hue passes through, including
// a NaN sentinel for achromatic colors (where culori omits `h`).
Self {
h: c.h,
w: (1.0 - c.s) * c.v,
b: 1.0 - c.v,
alpha: c.alpha,
}
}
}
impl From<Rgb> for Hwb {
fn from(c: Rgb) -> Self {
Hsv::from(c).into()
}
}
impl From<Hwb> for Rgb {
fn from(c: Hwb) -> Self {
Hsv::from(c).into()
}
}
impl From<Hwb> for Hsv {
fn from(c: Hwb) -> Self {
let mut w = c.w;
let mut b = c.b;
if w + b > 1.0 {
let sum = w + b;
w /= sum;
b /= sum;
}
let s = if b == 1.0 { 1.0 } else { 1.0 - w / (1.0 - b) };
Self {
h: c.h,
s,
v: 1.0 - b,
alpha: c.alpha,
}
}
}