summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2025-06-19 17:20:45 +0200
committerLaurenz <laurmaedje@gmail.com>2025-06-20 17:32:37 +0200
commit4580daf307cb1ba66458fb46d9442b1183731ec2 (patch)
treebfe13587db10c858c4f5a638d216a38817dfbb7d
parentd821633f50f7f4c9edc49b6ac5e88d43802cb206 (diff)
More type-safe color conversions
-rw-r--r--crates/typst-library/src/text/raw.rs2
-rw-r--r--crates/typst-library/src/visualize/color.rs144
-rw-r--r--crates/typst-render/src/paint.rs4
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 f2485e16..e1f4cf13 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::<u8, u8>().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 24d8305c..5c657b4e 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<Color> {
Ok(if let Some(color) = args.find::<Color>()? {
- 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<Color> {
Ok(if let Some(color) = args.find::<Color>()? {
- 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<Color> {
Ok(if let Some(color) = args.find::<Color>()? {
- 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<Color> {
Ok(if let Some(color) = args.find::<Color>()? {
- 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::<Spanned<Str>>()? {
Self::from_str(&string.v).at(string.span)?
} else if let Some(color) = args.find::<Color>()? {
- 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<Color> {
Ok(if let Some(color) = args.find::<Color>()? {
- 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<Color> {
Ok(if let Some(color) = args.find::<Color>()? {
- 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<Color> {
Ok(if let Some(color) = args.find::<Color>()? {
- 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::<u8, u8>().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<Color> {
+ 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<Color> {
+ 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<Color> {
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 ce92fd6a..6eb9c582 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::<u8, u8>().into_components();
sk::ColorU8::from_rgba(r, g, b, a)
}