Skip to content

Commit af78ba0

Browse files
authored
Auto merge of #326 - tiaanl:add-hsl-hwb, r=emilio
Do not convert hsl/hwb to RGB at parse time to not loose precision Parsing is not done in 2 forms as per the spec. Legacy and new syntax. For rgb and hsl where both are valid, first one is tried and if it fails, we try the other. We also store hsl and hwb values directly without converting them to rgba at parse time. The conversion only happens how before serializing.
2 parents caa0076 + 2f56d1c commit af78ba0

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)