Skip to content

Commit 2f56d1c

Browse files
committed
Do not convert hsl/hwb to RGB at parse time to not loose precision.
1 parent 85463cd commit 2f56d1c

File tree

2 files changed

+160
-16
lines changed

2 files changed

+160
-16
lines changed

src/color.rs

+140-16
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,122 @@ impl ToCss for RGBA {
182182
}
183183
}
184184

185+
#[derive(Clone, Copy, PartialEq, Debug)]
186+
pub struct Hsl {
187+
/// The hue component.
188+
pub hue: f32,
189+
/// The saturation component.
190+
pub saturation: f32,
191+
/// The lightness component.
192+
pub lightness: f32,
193+
/// The alpha component.
194+
pub alpha: f32,
195+
}
196+
197+
impl Hsl {
198+
pub fn new(hue: f32, saturation: f32, lightness: f32, alpha: f32) -> Self {
199+
Self {
200+
hue,
201+
saturation,
202+
lightness,
203+
alpha,
204+
}
205+
}
206+
}
207+
208+
impl ToCss for Hsl {
209+
fn to_css<W>(&self, dest: &mut W) -> fmt::Result
210+
where
211+
W: fmt::Write,
212+
{
213+
// HSL serializes to RGB, so we have to convert it.
214+
let (red, green, blue) = hsl_to_rgb(
215+
self.hue / 360.0, // Hue is expected in range [0..1].
216+
self.saturation,
217+
self.lightness,
218+
);
219+
220+
RGBA::from_floats(red, green, blue, self.alpha).to_css(dest)
221+
}
222+
}
223+
224+
#[cfg(feature = "serde")]
225+
impl Serialize for Hsl {
226+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
227+
where
228+
S: Serializer,
229+
{
230+
(self.hue, self.saturation, self.lightness, self.alpha).serialize(serializer)
231+
}
232+
}
233+
234+
#[cfg(feature = "serde")]
235+
impl<'de> Deserialize<'de> for Hsl {
236+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
237+
where
238+
D: Deserializer<'de>,
239+
{
240+
let (lightness, a, b, alpha) = Deserialize::deserialize(deserializer)?;
241+
Ok(Self::new(lightness, a, b, alpha))
242+
}
243+
}
244+
245+
#[derive(Clone, Copy, PartialEq, Debug)]
246+
pub struct Hwb {
247+
/// The hue component.
248+
pub hue: f32,
249+
/// The whiteness component.
250+
pub whiteness: f32,
251+
/// The blackness component.
252+
pub blackness: f32,
253+
/// The alpha component.
254+
pub alpha: f32,
255+
}
256+
257+
impl Hwb {
258+
pub fn new(hue: f32, whiteness: f32, blackness: f32, alpha: f32) -> Self {
259+
Self {
260+
hue,
261+
whiteness,
262+
blackness,
263+
alpha,
264+
}
265+
}
266+
}
267+
268+
impl ToCss for Hwb {
269+
fn to_css<W>(&self, dest: &mut W) -> fmt::Result
270+
where
271+
W: fmt::Write,
272+
{
273+
// HWB serializes to RGB, so we have to convert it.
274+
let (red, green, blue) = hwb_to_rgb(self.hue / 360.0, self.whiteness, self.blackness);
275+
276+
RGBA::from_floats(red, green, blue, self.alpha).to_css(dest)
277+
}
278+
}
279+
280+
#[cfg(feature = "serde")]
281+
impl Serialize for Hwb {
282+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
283+
where
284+
S: Serializer,
285+
{
286+
(self.hue, self.whiteness, self.blackness, self.alpha).serialize(serializer)
287+
}
288+
}
289+
290+
#[cfg(feature = "serde")]
291+
impl<'de> Deserialize<'de> for Hwb {
292+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
293+
where
294+
D: Deserializer<'de>,
295+
{
296+
let (lightness, whiteness, blackness, alpha) = Deserialize::deserialize(deserializer)?;
297+
Ok(Self::new(lightness, whiteness, blackness, alpha))
298+
}
299+
}
300+
185301
// NOTE: LAB and OKLAB is not declared inside the [impl_lab_like] macro,
186302
// because it causes cbindgen to ignore them.
187303

@@ -482,6 +598,10 @@ pub enum Color {
482598
CurrentColor,
483599
/// Specify sRGB colors directly by their red/green/blue/alpha chanels.
484600
Rgba(RGBA),
601+
/// Specifies a color in sRGB using hue, saturation and lightness components.
602+
Hsl(Hsl),
603+
/// Specifies a color in sRGB using hue, whiteness and blackness components.
604+
Hwb(Hwb),
485605
/// Specifies a CIELAB color by CIE Lightness and its a- and b-axis hue
486606
/// coordinates (red/green-ness, and yellow/blue-ness) using the CIE LAB
487607
/// rectangular coordinate model.
@@ -508,6 +628,8 @@ impl ToCss for Color {
508628
match *self {
509629
Color::CurrentColor => dest.write_str("currentcolor"),
510630
Color::Rgba(rgba) => rgba.to_css(dest),
631+
Color::Hsl(hsl) => hsl.to_css(dest),
632+
Color::Hwb(hwb) => hwb.to_css(dest),
511633
Color::Lab(lab) => lab.to_css(dest),
512634
Color::Lch(lch) => lch.to_css(dest),
513635
Color::Oklab(lab) => lab.to_css(dest),
@@ -666,6 +788,12 @@ pub trait FromParsedColor {
666788
/// Construct a new color from red, green, blue and alpha components.
667789
fn from_rgba(red: u8, green: u8, blue: u8, alpha: f32) -> Self;
668790

791+
/// Construct a new color from hue, saturation, lightness and alpha components.
792+
fn from_hsl(hue: f32, saturation: f32, lightness: f32, alpha: f32) -> Self;
793+
794+
/// Construct a new color from hue, blackness, whiteness and alpha components.
795+
fn from_hwb(hue: f32, whiteness: f32, blackness: f32, alpha: f32) -> Self;
796+
669797
/// Construct a new color from the `lab` notation.
670798
fn from_lab(lightness: f32, a: f32, b: f32, alpha: f32) -> Self;
671799

@@ -725,6 +853,16 @@ impl FromParsedColor for Color {
725853
Color::Rgba(RGBA::new(red, green, blue, alpha))
726854
}
727855

856+
#[inline]
857+
fn from_hsl(hue: f32, saturation: f32, lightness: f32, alpha: f32) -> Self {
858+
Color::Hsl(Hsl::new(hue, saturation, lightness, alpha))
859+
}
860+
861+
#[inline]
862+
fn from_hwb(hue: f32, whiteness: f32, blackness: f32, alpha: f32) -> Self {
863+
Color::Hwb(Hwb::new(hue, whiteness, blackness, alpha))
864+
}
865+
728866
#[inline]
729867
fn from_lab(lightness: f32, a: f32, b: f32, alpha: f32) -> Self {
730868
Color::Lab(Lab::new(lightness, a, b, alpha))
@@ -1102,14 +1240,7 @@ where
11021240
let saturation = saturation.clamp(0.0, 1.0);
11031241
let lightness = lightness.clamp(0.0, 1.0);
11041242

1105-
let (red, green, blue) = hsl_to_rgb(hue / 360.0, saturation, lightness);
1106-
1107-
Ok(P::Output::from_rgba(
1108-
clamp_unit_f32(red),
1109-
clamp_unit_f32(green),
1110-
clamp_unit_f32(blue),
1111-
alpha,
1112-
))
1243+
Ok(P::Output::from_hsl(hue, saturation, lightness, alpha))
11131244
}
11141245

11151246
/// Parses hwb syntax.
@@ -1135,14 +1266,7 @@ where
11351266
let whiteness = whiteness.clamp(0.0, 1.0);
11361267
let blackness = blackness.clamp(0.0, 1.0);
11371268

1138-
let (red, green, blue) = hwb_to_rgb(hue / 360.0, whiteness, blackness);
1139-
1140-
Ok(P::Output::from_rgba(
1141-
clamp_unit_f32(red),
1142-
clamp_unit_f32(green),
1143-
clamp_unit_f32(blue),
1144-
alpha,
1145-
))
1269+
Ok(P::Output::from_hwb(hue, whiteness, blackness, alpha))
11461270
}
11471271

11481272
/// https://drafts.csswg.org/css-color-4/#hwb-to-rgb

src/tests.rs

+20
Original file line numberDiff line numberDiff line change
@@ -900,6 +900,8 @@ impl ToJson for Color {
900900
Color::Rgba(ref rgba) => {
901901
json!([rgba.red, rgba.green, rgba.blue, rgba.alpha])
902902
}
903+
Color::Hsl(ref c) => json!([c.hue, c.saturation, c.lightness, c.alpha]),
904+
Color::Hwb(ref c) => json!([c.hue, c.whiteness, c.blackness, c.alpha]),
903905
Color::Lab(ref c) => json!([c.lightness, c.a, c.b, c.alpha]),
904906
Color::Lch(ref c) => json!([c.lightness, c.chroma, c.hue, c.alpha]),
905907
Color::Oklab(ref c) => json!([c.lightness, c.a, c.b, c.alpha]),
@@ -1517,6 +1519,8 @@ fn generic_parser() {
15171519
enum OutputType {
15181520
CurrentColor,
15191521
Rgba(u8, u8, u8, f32),
1522+
Hsl(f32, f32, f32, f32),
1523+
Hwb(f32, f32, f32, f32),
15201524
Lab(f32, f32, f32, f32),
15211525
Lch(f32, f32, f32, f32),
15221526
Oklab(f32, f32, f32, f32),
@@ -1533,6 +1537,14 @@ fn generic_parser() {
15331537
OutputType::Rgba(red, green, blue, alpha)
15341538
}
15351539

1540+
fn from_hsl(hue: f32, saturation: f32, lightness: f32, alpha: f32) -> Self {
1541+
OutputType::Hsl(hue, saturation, lightness, alpha)
1542+
}
1543+
1544+
fn from_hwb(hue: f32, blackness: f32, whiteness: f32, alpha: f32) -> Self {
1545+
OutputType::Hwb(hue, blackness, whiteness, alpha)
1546+
}
1547+
15361548
fn from_lab(lightness: f32, a: f32, b: f32, alpha: f32) -> Self {
15371549
OutputType::Lab(lightness, a, b, alpha)
15381550
}
@@ -1570,6 +1582,14 @@ fn generic_parser() {
15701582
("currentColor", OutputType::CurrentColor),
15711583
("rgb(1, 2, 3)", OutputType::Rgba(1, 2, 3, 1.0)),
15721584
("rgba(1, 2, 3, 0.4)", OutputType::Rgba(1, 2, 3, 0.4)),
1585+
(
1586+
"hsla(45deg, 20%, 30%, 0.4)",
1587+
OutputType::Hsl(45.0, 0.2, 0.3, 0.4),
1588+
),
1589+
(
1590+
"hwb(45deg 20% 30% / 0.4)",
1591+
OutputType::Hwb(45.0, 0.2, 0.3, 0.4),
1592+
),
15731593
(
15741594
"lab(100 20 30 / 0.4)",
15751595
OutputType::Lab(100.0, 20.0, 30.0, 0.4),

0 commit comments

Comments
 (0)