From 4580daf307cb1ba66458fb46d9442b1183731ec2 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Thu, 19 Jun 2025 17:20:45 +0200 Subject: [PATCH] More type-safe color conversions --- crates/typst-library/src/text/raw.rs | 2 +- crates/typst-library/src/visualize/color.rs | 144 ++++++++++---------- crates/typst-render/src/paint.rs | 4 +- 3 files changed, 73 insertions(+), 77 deletions(-) diff --git a/crates/typst-library/src/text/raw.rs b/crates/typst-library/src/text/raw.rs index f2485e16b..e1f4cf13d 100644 --- a/crates/typst-library/src/text/raw.rs +++ b/crates/typst-library/src/text/raw.rs @@ -836,7 +836,7 @@ fn to_typst(synt::Color { r, g, b, a }: synt::Color) -> Color { } fn to_syn(color: Color) -> synt::Color { - let [r, g, b, a] = color.to_rgb().to_vec4_u8(); + let (r, g, b, a) = color.to_rgb().into_format::().into_components(); synt::Color { r, g, b, a } } diff --git a/crates/typst-library/src/visualize/color.rs b/crates/typst-library/src/visualize/color.rs index 24d8305cd..5c657b4e2 100644 --- a/crates/typst-library/src/visualize/color.rs +++ b/crates/typst-library/src/visualize/color.rs @@ -262,7 +262,7 @@ impl Color { color: Color, ) -> SourceResult { Ok(if let Some(color) = args.find::()? { - color.to_luma() + Color::Luma(color.to_luma()) } else { let Component(gray) = args.expect("gray component").unwrap_or(Component(Ratio::one())); @@ -318,7 +318,7 @@ impl Color { color: Color, ) -> SourceResult { Ok(if let Some(color) = args.find::()? { - color.to_oklab() + Color::Oklab(color.to_oklab()) } else { let RatioComponent(l) = args.expect("lightness component")?; let ChromaComponent(a) = args.expect("A component")?; @@ -374,7 +374,7 @@ impl Color { color: Color, ) -> SourceResult { Ok(if let Some(color) = args.find::()? { - color.to_oklch() + Color::Oklch(color.to_oklch()) } else { let RatioComponent(l) = args.expect("lightness component")?; let ChromaComponent(c) = args.expect("chroma component")?; @@ -434,7 +434,7 @@ impl Color { color: Color, ) -> SourceResult { Ok(if let Some(color) = args.find::()? { - color.to_linear_rgb() + Color::LinearRgb(color.to_linear_rgb()) } else { let Component(r) = args.expect("red component")?; let Component(g) = args.expect("green component")?; @@ -505,7 +505,7 @@ impl Color { Ok(if let Some(string) = args.find::>()? { Self::from_str(&string.v).at(string.span)? } else if let Some(color) = args.find::()? { - color.to_rgb() + Color::Rgb(color.to_rgb()) } else { let Component(r) = args.expect("red component")?; let Component(g) = args.expect("green component")?; @@ -565,7 +565,7 @@ impl Color { color: Color, ) -> SourceResult { Ok(if let Some(color) = args.find::()? { - color.to_cmyk() + Color::Cmyk(color.to_cmyk()) } else { let RatioComponent(c) = args.expect("cyan component")?; let RatioComponent(m) = args.expect("magenta component")?; @@ -622,7 +622,7 @@ impl Color { color: Color, ) -> SourceResult { Ok(if let Some(color) = args.find::()? { - color.to_hsl() + Color::Hsl(color.to_hsl()) } else { let h: Angle = args.expect("hue component")?; let Component(s) = args.expect("saturation component")?; @@ -679,7 +679,7 @@ impl Color { color: Color, ) -> SourceResult { Ok(if let Some(color) = args.find::()? { - color.to_hsv() + Color::Hsv(color.to_hsv()) } else { let h: Angle = args.expect("hue component")?; let Component(s) = args.expect("saturation component")?; @@ -830,7 +830,7 @@ impl Color { /// omitted if it is equal to `ff` (255 / 100%). #[func] pub fn to_hex(self) -> EcoString { - let [r, g, b, a] = self.to_rgb().to_vec4_u8(); + let (r, g, b, a) = self.to_rgb().into_format::().into_components(); if a != 255 { eco_format!("#{:02x}{:02x}{:02x}{:02x}", r, g, b, a) } else { @@ -886,20 +886,21 @@ impl Color { /// The factor to saturate the color by. factor: Ratio, ) -> SourceResult { + let f = factor.get() as f32; Ok(match self { - Self::Luma(_) => { - bail!( - span, "cannot saturate grayscale color"; - hint: "try converting your color to RGB first" - ); + Self::Luma(_) => bail!( + span, "cannot saturate grayscale color"; + hint: "try converting your color to RGB first" + ), + Self::Hsl(c) => Self::Hsl(c.saturate(f)), + Self::Hsv(c) => Self::Hsv(c.saturate(f)), + Self::Oklab(_) + | Self::Oklch(_) + | Self::LinearRgb(_) + | Self::Rgb(_) + | Self::Cmyk(_) => { + Color::Hsv(self.to_hsv().saturate(f)).to_space(self.space()) } - Self::Oklab(_) => self.to_hsv().saturate(span, factor)?.to_oklab(), - Self::Oklch(_) => self.to_hsv().saturate(span, factor)?.to_oklch(), - Self::LinearRgb(_) => self.to_hsv().saturate(span, factor)?.to_linear_rgb(), - Self::Rgb(_) => self.to_hsv().saturate(span, factor)?.to_rgb(), - Self::Cmyk(_) => self.to_hsv().saturate(span, factor)?.to_cmyk(), - Self::Hsl(c) => Self::Hsl(c.saturate(factor.get() as f32)), - Self::Hsv(c) => Self::Hsv(c.saturate(factor.get() as f32)), }) } @@ -911,20 +912,21 @@ impl Color { /// The factor to desaturate the color by. factor: Ratio, ) -> SourceResult { + let f = factor.get() as f32; Ok(match self { - Self::Luma(_) => { - bail!( - span, "cannot desaturate grayscale color"; - hint: "try converting your color to RGB first" - ); + Self::Luma(_) => bail!( + span, "cannot desaturate grayscale color"; + hint: "try converting your color to RGB first" + ), + Self::Hsl(c) => Self::Hsl(c.desaturate(f)), + Self::Hsv(c) => Self::Hsv(c.desaturate(f)), + Self::Oklab(_) + | Self::Oklch(_) + | Self::LinearRgb(_) + | Self::Rgb(_) + | Self::Cmyk(_) => { + Color::Hsv(self.to_hsv().desaturate(f)).to_space(self.space()) } - Self::Oklab(_) => self.to_hsv().desaturate(span, factor)?.to_oklab(), - Self::Oklch(_) => self.to_hsv().desaturate(span, factor)?.to_oklch(), - Self::LinearRgb(_) => self.to_hsv().desaturate(span, factor)?.to_linear_rgb(), - Self::Rgb(_) => self.to_hsv().desaturate(span, factor)?.to_rgb(), - Self::Cmyk(_) => self.to_hsv().desaturate(span, factor)?.to_cmyk(), - Self::Hsl(c) => Self::Hsl(c.desaturate(factor.get() as f32)), - Self::Hsv(c) => Self::Hsv(c.desaturate(factor.get() as f32)), }) } @@ -994,23 +996,17 @@ impl Color { ) -> SourceResult { Ok(match space { ColorSpace::Oklch => { - let Self::Oklch(oklch) = self.to_oklch() else { - unreachable!(); - }; + let oklch = self.to_oklch(); let rotated = oklch.shift_hue(angle.to_deg() as f32); Self::Oklch(rotated).to_space(self.space()) } ColorSpace::Hsl => { - let Self::Hsl(hsl) = self.to_hsl() else { - unreachable!(); - }; + let hsl = self.to_hsl(); let rotated = hsl.shift_hue(angle.to_deg() as f32); Self::Hsl(rotated).to_space(self.space()) } ColorSpace::Hsv => { - let Self::Hsv(hsv) = self.to_hsv() else { - unreachable!(); - }; + let hsv = self.to_hsv(); let rotated = hsv.shift_hue(angle.to_deg() as f32); Self::Hsv(rotated).to_space(self.space()) } @@ -1281,19 +1277,19 @@ impl Color { pub fn to_space(self, space: ColorSpace) -> Self { match space { - ColorSpace::Oklab => self.to_oklab(), - ColorSpace::Oklch => self.to_oklch(), - ColorSpace::Srgb => self.to_rgb(), - ColorSpace::LinearRgb => self.to_linear_rgb(), - ColorSpace::Hsl => self.to_hsl(), - ColorSpace::Hsv => self.to_hsv(), - ColorSpace::Cmyk => self.to_cmyk(), - ColorSpace::D65Gray => self.to_luma(), + ColorSpace::D65Gray => Self::Luma(self.to_luma()), + ColorSpace::Oklab => Self::Oklab(self.to_oklab()), + ColorSpace::Oklch => Self::Oklch(self.to_oklch()), + ColorSpace::Srgb => Self::Rgb(self.to_rgb()), + ColorSpace::LinearRgb => Self::LinearRgb(self.to_linear_rgb()), + ColorSpace::Cmyk => Self::Cmyk(self.to_cmyk()), + ColorSpace::Hsl => Self::Hsl(self.to_hsl()), + ColorSpace::Hsv => Self::Hsv(self.to_hsv()), } } - pub fn to_luma(self) -> Self { - Self::Luma(match self { + pub fn to_luma(self) -> Luma { + match self { Self::Luma(c) => c, Self::Oklab(c) => Luma::from_color(c), Self::Oklch(c) => Luma::from_color(c), @@ -1302,11 +1298,11 @@ impl Color { Self::Cmyk(c) => Luma::from_color(c.to_rgba()), Self::Hsl(c) => Luma::from_color(c), Self::Hsv(c) => Luma::from_color(c), - }) + } } - pub fn to_oklab(self) -> Self { - Self::Oklab(match self { + pub fn to_oklab(self) -> Oklab { + match self { Self::Luma(c) => Oklab::from_color(c), Self::Oklab(c) => c, Self::Oklch(c) => Oklab::from_color(c), @@ -1315,11 +1311,11 @@ impl Color { Self::Cmyk(c) => Oklab::from_color(c.to_rgba()), Self::Hsl(c) => Oklab::from_color(c), Self::Hsv(c) => Oklab::from_color(c), - }) + } } - pub fn to_oklch(self) -> Self { - Self::Oklch(match self { + pub fn to_oklch(self) -> Oklch { + match self { Self::Luma(c) => Oklch::from_color(c), Self::Oklab(c) => Oklch::from_color(c), Self::Oklch(c) => c, @@ -1328,11 +1324,11 @@ impl Color { Self::Cmyk(c) => Oklch::from_color(c.to_rgba()), Self::Hsl(c) => Oklch::from_color(c), Self::Hsv(c) => Oklch::from_color(c), - }) + } } - pub fn to_rgb(self) -> Self { - Self::Rgb(match self { + pub fn to_rgb(self) -> Rgb { + match self { Self::Luma(c) => Rgb::from_color(c), Self::Oklab(c) => Rgb::from_color(c), Self::Oklch(c) => Rgb::from_color(c), @@ -1341,11 +1337,11 @@ impl Color { Self::Cmyk(c) => Rgb::from_color(c.to_rgba()), Self::Hsl(c) => Rgb::from_color(c), Self::Hsv(c) => Rgb::from_color(c), - }) + } } - pub fn to_linear_rgb(self) -> Self { - Self::LinearRgb(match self { + pub fn to_linear_rgb(self) -> LinearRgb { + match self { Self::Luma(c) => LinearRgb::from_color(c), Self::Oklab(c) => LinearRgb::from_color(c), Self::Oklch(c) => LinearRgb::from_color(c), @@ -1354,11 +1350,11 @@ impl Color { Self::Cmyk(c) => LinearRgb::from_color(c.to_rgba()), Self::Hsl(c) => Rgb::from_color(c).into_linear(), Self::Hsv(c) => Rgb::from_color(c).into_linear(), - }) + } } - pub fn to_cmyk(self) -> Self { - Self::Cmyk(match self { + pub fn to_cmyk(self) -> Cmyk { + match self { Self::Luma(c) => Cmyk::from_luma(c), Self::Oklab(c) => Cmyk::from_rgba(Rgb::from_color(c)), Self::Oklch(c) => Cmyk::from_rgba(Rgb::from_color(c)), @@ -1367,11 +1363,11 @@ impl Color { Self::Cmyk(c) => c, Self::Hsl(c) => Cmyk::from_rgba(Rgb::from_color(c)), Self::Hsv(c) => Cmyk::from_rgba(Rgb::from_color(c)), - }) + } } - pub fn to_hsl(self) -> Self { - Self::Hsl(match self { + pub fn to_hsl(self) -> Hsl { + match self { Self::Luma(c) => Hsl::from_color(c), Self::Oklab(c) => Hsl::from_color(c), Self::Oklch(c) => Hsl::from_color(c), @@ -1380,11 +1376,11 @@ impl Color { Self::Cmyk(c) => Hsl::from_color(c.to_rgba()), Self::Hsl(c) => c, Self::Hsv(c) => Hsl::from_color(c), - }) + } } - pub fn to_hsv(self) -> Self { - Self::Hsv(match self { + pub fn to_hsv(self) -> Hsv { + match self { Self::Luma(c) => Hsv::from_color(c), Self::Oklab(c) => Hsv::from_color(c), Self::Oklch(c) => Hsv::from_color(c), @@ -1393,7 +1389,7 @@ impl Color { Self::Cmyk(c) => Hsv::from_color(c.to_rgba()), Self::Hsl(c) => Hsv::from_color(c), Self::Hsv(c) => c, - }) + } } } diff --git a/crates/typst-render/src/paint.rs b/crates/typst-render/src/paint.rs index ce92fd6a3..6eb9c5826 100644 --- a/crates/typst-render/src/paint.rs +++ b/crates/typst-render/src/paint.rs @@ -255,13 +255,13 @@ pub fn to_sk_paint<'a>( } pub fn to_sk_color(color: Color) -> sk::Color { - let [r, g, b, a] = color.to_rgb().to_vec4(); + let (r, g, b, a) = color.to_rgb().into_components(); sk::Color::from_rgba(r, g, b, a) .expect("components must always be in the range [0..=1]") } pub fn to_sk_color_u8(color: Color) -> sk::ColorU8 { - let [r, g, b, a] = color.to_rgb().to_vec4_u8(); + let (r, g, b, a) = color.to_rgb().into_format::().into_components(); sk::ColorU8::from_rgba(r, g, b, a) }