More type-safe color conversions

This commit is contained in:
Laurenz 2025-06-19 17:20:45 +02:00
parent d821633f50
commit 4580daf307
3 changed files with 73 additions and 77 deletions

View File

@ -836,7 +836,7 @@ fn to_typst(synt::Color { r, g, b, a }: synt::Color) -> Color {
} }
fn to_syn(color: Color) -> synt::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 } synt::Color { r, g, b, a }
} }

View File

@ -262,7 +262,7 @@ impl Color {
color: Color, color: Color,
) -> SourceResult<Color> { ) -> SourceResult<Color> {
Ok(if let Some(color) = args.find::<Color>()? { Ok(if let Some(color) = args.find::<Color>()? {
color.to_luma() Color::Luma(color.to_luma())
} else { } else {
let Component(gray) = let Component(gray) =
args.expect("gray component").unwrap_or(Component(Ratio::one())); args.expect("gray component").unwrap_or(Component(Ratio::one()));
@ -318,7 +318,7 @@ impl Color {
color: Color, color: Color,
) -> SourceResult<Color> { ) -> SourceResult<Color> {
Ok(if let Some(color) = args.find::<Color>()? { Ok(if let Some(color) = args.find::<Color>()? {
color.to_oklab() Color::Oklab(color.to_oklab())
} else { } else {
let RatioComponent(l) = args.expect("lightness component")?; let RatioComponent(l) = args.expect("lightness component")?;
let ChromaComponent(a) = args.expect("A component")?; let ChromaComponent(a) = args.expect("A component")?;
@ -374,7 +374,7 @@ impl Color {
color: Color, color: Color,
) -> SourceResult<Color> { ) -> SourceResult<Color> {
Ok(if let Some(color) = args.find::<Color>()? { Ok(if let Some(color) = args.find::<Color>()? {
color.to_oklch() Color::Oklch(color.to_oklch())
} else { } else {
let RatioComponent(l) = args.expect("lightness component")?; let RatioComponent(l) = args.expect("lightness component")?;
let ChromaComponent(c) = args.expect("chroma component")?; let ChromaComponent(c) = args.expect("chroma component")?;
@ -434,7 +434,7 @@ impl Color {
color: Color, color: Color,
) -> SourceResult<Color> { ) -> SourceResult<Color> {
Ok(if let Some(color) = args.find::<Color>()? { Ok(if let Some(color) = args.find::<Color>()? {
color.to_linear_rgb() Color::LinearRgb(color.to_linear_rgb())
} else { } else {
let Component(r) = args.expect("red component")?; let Component(r) = args.expect("red component")?;
let Component(g) = args.expect("green component")?; let Component(g) = args.expect("green component")?;
@ -505,7 +505,7 @@ impl Color {
Ok(if let Some(string) = args.find::<Spanned<Str>>()? { Ok(if let Some(string) = args.find::<Spanned<Str>>()? {
Self::from_str(&string.v).at(string.span)? Self::from_str(&string.v).at(string.span)?
} else if let Some(color) = args.find::<Color>()? { } else if let Some(color) = args.find::<Color>()? {
color.to_rgb() Color::Rgb(color.to_rgb())
} else { } else {
let Component(r) = args.expect("red component")?; let Component(r) = args.expect("red component")?;
let Component(g) = args.expect("green component")?; let Component(g) = args.expect("green component")?;
@ -565,7 +565,7 @@ impl Color {
color: Color, color: Color,
) -> SourceResult<Color> { ) -> SourceResult<Color> {
Ok(if let Some(color) = args.find::<Color>()? { Ok(if let Some(color) = args.find::<Color>()? {
color.to_cmyk() Color::Cmyk(color.to_cmyk())
} else { } else {
let RatioComponent(c) = args.expect("cyan component")?; let RatioComponent(c) = args.expect("cyan component")?;
let RatioComponent(m) = args.expect("magenta component")?; let RatioComponent(m) = args.expect("magenta component")?;
@ -622,7 +622,7 @@ impl Color {
color: Color, color: Color,
) -> SourceResult<Color> { ) -> SourceResult<Color> {
Ok(if let Some(color) = args.find::<Color>()? { Ok(if let Some(color) = args.find::<Color>()? {
color.to_hsl() Color::Hsl(color.to_hsl())
} else { } else {
let h: Angle = args.expect("hue component")?; let h: Angle = args.expect("hue component")?;
let Component(s) = args.expect("saturation component")?; let Component(s) = args.expect("saturation component")?;
@ -679,7 +679,7 @@ impl Color {
color: Color, color: Color,
) -> SourceResult<Color> { ) -> SourceResult<Color> {
Ok(if let Some(color) = args.find::<Color>()? { Ok(if let Some(color) = args.find::<Color>()? {
color.to_hsv() Color::Hsv(color.to_hsv())
} else { } else {
let h: Angle = args.expect("hue component")?; let h: Angle = args.expect("hue component")?;
let Component(s) = args.expect("saturation component")?; let Component(s) = args.expect("saturation component")?;
@ -830,7 +830,7 @@ impl Color {
/// omitted if it is equal to `ff` (255 / 100%). /// omitted if it is equal to `ff` (255 / 100%).
#[func] #[func]
pub fn to_hex(self) -> EcoString { 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 { if a != 255 {
eco_format!("#{:02x}{:02x}{:02x}{:02x}", r, g, b, a) eco_format!("#{:02x}{:02x}{:02x}{:02x}", r, g, b, a)
} else { } else {
@ -886,20 +886,21 @@ impl Color {
/// The factor to saturate the color by. /// The factor to saturate the color by.
factor: Ratio, factor: Ratio,
) -> SourceResult<Color> { ) -> SourceResult<Color> {
let f = factor.get() as f32;
Ok(match self { Ok(match self {
Self::Luma(_) => { Self::Luma(_) => bail!(
bail!( span, "cannot saturate grayscale color";
span, "cannot saturate grayscale color"; hint: "try converting your color to RGB first"
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. /// The factor to desaturate the color by.
factor: Ratio, factor: Ratio,
) -> SourceResult<Color> { ) -> SourceResult<Color> {
let f = factor.get() as f32;
Ok(match self { Ok(match self {
Self::Luma(_) => { Self::Luma(_) => bail!(
bail!( span, "cannot desaturate grayscale color";
span, "cannot desaturate grayscale color"; hint: "try converting your color to RGB first"
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> { ) -> SourceResult<Color> {
Ok(match space { Ok(match space {
ColorSpace::Oklch => { ColorSpace::Oklch => {
let Self::Oklch(oklch) = self.to_oklch() else { let oklch = self.to_oklch();
unreachable!();
};
let rotated = oklch.shift_hue(angle.to_deg() as f32); let rotated = oklch.shift_hue(angle.to_deg() as f32);
Self::Oklch(rotated).to_space(self.space()) Self::Oklch(rotated).to_space(self.space())
} }
ColorSpace::Hsl => { ColorSpace::Hsl => {
let Self::Hsl(hsl) = self.to_hsl() else { let hsl = self.to_hsl();
unreachable!();
};
let rotated = hsl.shift_hue(angle.to_deg() as f32); let rotated = hsl.shift_hue(angle.to_deg() as f32);
Self::Hsl(rotated).to_space(self.space()) Self::Hsl(rotated).to_space(self.space())
} }
ColorSpace::Hsv => { ColorSpace::Hsv => {
let Self::Hsv(hsv) = self.to_hsv() else { let hsv = self.to_hsv();
unreachable!();
};
let rotated = hsv.shift_hue(angle.to_deg() as f32); let rotated = hsv.shift_hue(angle.to_deg() as f32);
Self::Hsv(rotated).to_space(self.space()) Self::Hsv(rotated).to_space(self.space())
} }
@ -1281,19 +1277,19 @@ impl Color {
pub fn to_space(self, space: ColorSpace) -> Self { pub fn to_space(self, space: ColorSpace) -> Self {
match space { match space {
ColorSpace::Oklab => self.to_oklab(), ColorSpace::D65Gray => Self::Luma(self.to_luma()),
ColorSpace::Oklch => self.to_oklch(), ColorSpace::Oklab => Self::Oklab(self.to_oklab()),
ColorSpace::Srgb => self.to_rgb(), ColorSpace::Oklch => Self::Oklch(self.to_oklch()),
ColorSpace::LinearRgb => self.to_linear_rgb(), ColorSpace::Srgb => Self::Rgb(self.to_rgb()),
ColorSpace::Hsl => self.to_hsl(), ColorSpace::LinearRgb => Self::LinearRgb(self.to_linear_rgb()),
ColorSpace::Hsv => self.to_hsv(), ColorSpace::Cmyk => Self::Cmyk(self.to_cmyk()),
ColorSpace::Cmyk => self.to_cmyk(), ColorSpace::Hsl => Self::Hsl(self.to_hsl()),
ColorSpace::D65Gray => self.to_luma(), ColorSpace::Hsv => Self::Hsv(self.to_hsv()),
} }
} }
pub fn to_luma(self) -> Self { pub fn to_luma(self) -> Luma {
Self::Luma(match self { match self {
Self::Luma(c) => c, Self::Luma(c) => c,
Self::Oklab(c) => Luma::from_color(c), Self::Oklab(c) => Luma::from_color(c),
Self::Oklch(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::Cmyk(c) => Luma::from_color(c.to_rgba()),
Self::Hsl(c) => Luma::from_color(c), Self::Hsl(c) => Luma::from_color(c),
Self::Hsv(c) => Luma::from_color(c), Self::Hsv(c) => Luma::from_color(c),
}) }
} }
pub fn to_oklab(self) -> Self { pub fn to_oklab(self) -> Oklab {
Self::Oklab(match self { match self {
Self::Luma(c) => Oklab::from_color(c), Self::Luma(c) => Oklab::from_color(c),
Self::Oklab(c) => c, Self::Oklab(c) => c,
Self::Oklch(c) => Oklab::from_color(c), Self::Oklch(c) => Oklab::from_color(c),
@ -1315,11 +1311,11 @@ impl Color {
Self::Cmyk(c) => Oklab::from_color(c.to_rgba()), Self::Cmyk(c) => Oklab::from_color(c.to_rgba()),
Self::Hsl(c) => Oklab::from_color(c), Self::Hsl(c) => Oklab::from_color(c),
Self::Hsv(c) => Oklab::from_color(c), Self::Hsv(c) => Oklab::from_color(c),
}) }
} }
pub fn to_oklch(self) -> Self { pub fn to_oklch(self) -> Oklch {
Self::Oklch(match self { match self {
Self::Luma(c) => Oklch::from_color(c), Self::Luma(c) => Oklch::from_color(c),
Self::Oklab(c) => Oklch::from_color(c), Self::Oklab(c) => Oklch::from_color(c),
Self::Oklch(c) => c, Self::Oklch(c) => c,
@ -1328,11 +1324,11 @@ impl Color {
Self::Cmyk(c) => Oklch::from_color(c.to_rgba()), Self::Cmyk(c) => Oklch::from_color(c.to_rgba()),
Self::Hsl(c) => Oklch::from_color(c), Self::Hsl(c) => Oklch::from_color(c),
Self::Hsv(c) => Oklch::from_color(c), Self::Hsv(c) => Oklch::from_color(c),
}) }
} }
pub fn to_rgb(self) -> Self { pub fn to_rgb(self) -> Rgb {
Self::Rgb(match self { match self {
Self::Luma(c) => Rgb::from_color(c), Self::Luma(c) => Rgb::from_color(c),
Self::Oklab(c) => Rgb::from_color(c), Self::Oklab(c) => Rgb::from_color(c),
Self::Oklch(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::Cmyk(c) => Rgb::from_color(c.to_rgba()),
Self::Hsl(c) => Rgb::from_color(c), Self::Hsl(c) => Rgb::from_color(c),
Self::Hsv(c) => Rgb::from_color(c), Self::Hsv(c) => Rgb::from_color(c),
}) }
} }
pub fn to_linear_rgb(self) -> Self { pub fn to_linear_rgb(self) -> LinearRgb {
Self::LinearRgb(match self { match self {
Self::Luma(c) => LinearRgb::from_color(c), Self::Luma(c) => LinearRgb::from_color(c),
Self::Oklab(c) => LinearRgb::from_color(c), Self::Oklab(c) => LinearRgb::from_color(c),
Self::Oklch(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::Cmyk(c) => LinearRgb::from_color(c.to_rgba()),
Self::Hsl(c) => Rgb::from_color(c).into_linear(), Self::Hsl(c) => Rgb::from_color(c).into_linear(),
Self::Hsv(c) => Rgb::from_color(c).into_linear(), Self::Hsv(c) => Rgb::from_color(c).into_linear(),
}) }
} }
pub fn to_cmyk(self) -> Self { pub fn to_cmyk(self) -> Cmyk {
Self::Cmyk(match self { match self {
Self::Luma(c) => Cmyk::from_luma(c), Self::Luma(c) => Cmyk::from_luma(c),
Self::Oklab(c) => Cmyk::from_rgba(Rgb::from_color(c)), Self::Oklab(c) => Cmyk::from_rgba(Rgb::from_color(c)),
Self::Oklch(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::Cmyk(c) => c,
Self::Hsl(c) => Cmyk::from_rgba(Rgb::from_color(c)), Self::Hsl(c) => Cmyk::from_rgba(Rgb::from_color(c)),
Self::Hsv(c) => Cmyk::from_rgba(Rgb::from_color(c)), Self::Hsv(c) => Cmyk::from_rgba(Rgb::from_color(c)),
}) }
} }
pub fn to_hsl(self) -> Self { pub fn to_hsl(self) -> Hsl {
Self::Hsl(match self { match self {
Self::Luma(c) => Hsl::from_color(c), Self::Luma(c) => Hsl::from_color(c),
Self::Oklab(c) => Hsl::from_color(c), Self::Oklab(c) => Hsl::from_color(c),
Self::Oklch(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::Cmyk(c) => Hsl::from_color(c.to_rgba()),
Self::Hsl(c) => c, Self::Hsl(c) => c,
Self::Hsv(c) => Hsl::from_color(c), Self::Hsv(c) => Hsl::from_color(c),
}) }
} }
pub fn to_hsv(self) -> Self { pub fn to_hsv(self) -> Hsv {
Self::Hsv(match self { match self {
Self::Luma(c) => Hsv::from_color(c), Self::Luma(c) => Hsv::from_color(c),
Self::Oklab(c) => Hsv::from_color(c), Self::Oklab(c) => Hsv::from_color(c),
Self::Oklch(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::Cmyk(c) => Hsv::from_color(c.to_rgba()),
Self::Hsl(c) => Hsv::from_color(c), Self::Hsl(c) => Hsv::from_color(c),
Self::Hsv(c) => c, Self::Hsv(c) => c,
}) }
} }
} }

View File

@ -255,13 +255,13 @@ pub fn to_sk_paint<'a>(
} }
pub fn to_sk_color(color: Color) -> sk::Color { 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) sk::Color::from_rgba(r, g, b, a)
.expect("components must always be in the range [0..=1]") .expect("components must always be in the range [0..=1]")
} }
pub fn to_sk_color_u8(color: Color) -> sk::ColorU8 { 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) sk::ColorU8::from_rgba(r, g, b, a)
} }