Implement Oklch support (#2611)

This commit is contained in:
frozolotl 2023-11-10 10:31:06 +01:00 committed by GitHub
parent 7f0fcda376
commit d93ed1b3d8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 270 additions and 27 deletions

View File

@ -1223,6 +1223,11 @@ impl<'a> CompletionContext<'a> {
"oklab(${l}, ${a}, ${b}, ${alpha})", "oklab(${l}, ${a}, ${b}, ${alpha})",
"A custom Oklab color.", "A custom Oklab color.",
); );
self.snippet_completion(
"oklch()",
"oklch(${l}, ${chroma}, ${hue}, ${alpha})",
"A custom Oklch color.",
);
self.snippet_completion( self.snippet_completion(
"color.linear-rgb()", "color.linear-rgb()",
"color.linear-rgb(${r}, ${g}, ${b}, ${a})", "color.linear-rgb(${r}, ${g}, ${b}, ${a})",

View File

@ -65,6 +65,7 @@ fn prelude(global: &mut Scope) {
global.define("lime", Color::LIME); global.define("lime", Color::LIME);
global.define("luma", Color::luma_data()); global.define("luma", Color::luma_data());
global.define("oklab", Color::oklab_data()); global.define("oklab", Color::oklab_data());
global.define("oklch", Color::oklch_data());
global.define("rgb", Color::rgb_data()); global.define("rgb", Color::rgb_data());
global.define("cmyk", Color::cmyk_data()); global.define("cmyk", Color::cmyk_data());
global.define("range", Array::range_data()); global.define("range", Array::range_data());

View File

@ -107,6 +107,7 @@ impl ColorSpaces {
oklab.tint_ref(self.oklab(alloc)); oklab.tint_ref(self.oklab(alloc));
oklab.attrs().subtype(DeviceNSubtype::DeviceN); oklab.attrs().subtype(DeviceNSubtype::DeviceN);
} }
ColorSpace::Oklch => self.write(ColorSpace::Oklab, writer, alloc),
ColorSpace::Srgb => writer.icc_based(self.srgb(alloc)), ColorSpace::Srgb => writer.icc_based(self.srgb(alloc)),
ColorSpace::D65Gray => writer.icc_based(self.d65_gray(alloc)), ColorSpace::D65Gray => writer.icc_based(self.d65_gray(alloc)),
ColorSpace::LinearRgb => { ColorSpace::LinearRgb => {
@ -266,6 +267,9 @@ impl ColorEncode for ColorSpace {
let [h, s, v, _] = color.to_hsv().to_vec4(); let [h, s, v, _] = color.to_hsv().to_vec4();
[h / 360.0, s, v, 0.0] [h / 360.0, s, v, 0.0]
} }
ColorSpace::Oklch => {
unimplemented!("Oklch is always converted to Oklab first")
}
_ => color.to_vec4(), _ => color.to_vec4(),
} }
} }
@ -306,7 +310,8 @@ impl PaintEncode for Color {
let [l, _, _, _] = ColorSpace::D65Gray.encode(*self); let [l, _, _, _] = ColorSpace::D65Gray.encode(*self);
ctx.content.set_fill_color([l]); ctx.content.set_fill_color([l]);
} }
Color::Oklab(_) => { // Oklch is converted to Oklab.
Color::Oklab(_) | Color::Oklch(_) => {
ctx.parent.colors.oklab(&mut ctx.parent.alloc); ctx.parent.colors.oklab(&mut ctx.parent.alloc);
ctx.set_fill_color_space(OKLAB); ctx.set_fill_color_space(OKLAB);
@ -359,7 +364,8 @@ impl PaintEncode for Color {
let [l, _, _, _] = ColorSpace::D65Gray.encode(*self); let [l, _, _, _] = ColorSpace::D65Gray.encode(*self);
ctx.content.set_stroke_color([l]); ctx.content.set_stroke_color([l]);
} }
Color::Oklab(_) => { // Oklch is converted to Oklab.
Color::Oklab(_) | Color::Oklch(_) => {
ctx.parent.colors.oklab(&mut ctx.parent.alloc); ctx.parent.colors.oklab(&mut ctx.parent.alloc);
ctx.set_stroke_color_space(OKLAB); ctx.set_stroke_color_space(OKLAB);

View File

@ -1275,6 +1275,24 @@ impl ColorEncode for Color {
) )
} }
} }
Color::Oklch(oklch) => {
if oklch.alpha != 1.0 {
eco_format!(
"oklch({:.3}% {:.5} {:.3}deg / {:.3})",
oklch.l * 100.0,
oklch.chroma,
oklch.hue.into_degrees(),
oklch.alpha
)
} else {
eco_format!(
"oklch({:.3}% {:.5} {:.3}deg)",
oklch.l * 100.0,
oklch.chroma,
oklch.hue.into_degrees(),
)
}
}
Color::Hsl(hsl) => { Color::Hsl(hsl) => {
if hsl.alpha != 1.0 { if hsl.alpha != 1.0 {
eco_format!( eco_format!(

View File

@ -3,7 +3,9 @@ use std::str::FromStr;
use ecow::EcoVec; use ecow::EcoVec;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use palette::encoding::{self, Linear}; use palette::encoding::{self, Linear};
use palette::{Darken, Desaturate, FromColor, Lighten, RgbHue, Saturate, ShiftHue}; use palette::{
Darken, Desaturate, FromColor, Lighten, OklabHue, RgbHue, Saturate, ShiftHue,
};
use super::*; use super::*;
use crate::diag::{bail, error, At, SourceResult}; use crate::diag::{bail, error, At, SourceResult};
@ -12,6 +14,7 @@ use crate::syntax::{Span, Spanned};
// Type aliases for `palette` internal types in f32. // Type aliases for `palette` internal types in f32.
pub type Oklab = palette::oklab::Oklaba<f32>; pub type Oklab = palette::oklab::Oklaba<f32>;
pub type Oklch = palette::oklch::Oklcha<f32>;
pub type LinearRgba = palette::rgb::Rgba<Linear<encoding::Srgb>, f32>; pub type LinearRgba = palette::rgb::Rgba<Linear<encoding::Srgb>, f32>;
pub type Rgba = palette::rgb::Rgba<encoding::Srgb, f32>; pub type Rgba = palette::rgb::Rgba<encoding::Srgb, f32>;
pub type Hsl = palette::hsl::Hsla<encoding::Srgb, f32>; pub type Hsl = palette::hsl::Hsla<encoding::Srgb, f32>;
@ -28,6 +31,7 @@ const ANGLE_EPSILON: f32 = 1e-5;
/// - Device CMYK through [`cmyk` function]($color.cmyk) /// - Device CMYK through [`cmyk` function]($color.cmyk)
/// - D65 Gray through the [`luma` function]($color.luma) /// - D65 Gray through the [`luma` function]($color.luma)
/// - Oklab through the [`oklab` function]($color.oklab) /// - Oklab through the [`oklab` function]($color.oklab)
/// - Oklch through the [`oklch` function]($color.oklch)
/// - Linear RGB through the [`color.linear-rgb` function]($color.linear-rgb) /// - Linear RGB through the [`color.linear-rgb` function]($color.linear-rgb)
/// - HSL through the [`color.hsl` function]($color.hsl) /// - HSL through the [`color.hsl` function]($color.hsl)
/// - HSV through the [`color.hsv` function]($color.hsv) /// - HSV through the [`color.hsv` function]($color.hsv)
@ -153,8 +157,10 @@ const ANGLE_EPSILON: f32 = 1e-5;
pub enum Color { pub enum Color {
/// A 32-bit luma color. /// A 32-bit luma color.
Luma(Luma), Luma(Luma),
/// A 32-bit L*a*b* color in the Oklab color space. /// A 32-bit L\*a\*b\* color in the Oklab color space.
Oklab(Oklab), Oklab(Oklab),
/// A 32-bit LCh color in the Oklab color space.
Oklch(Oklch),
/// A 32-bit RGBA color. /// A 32-bit RGBA color.
Rgba(Rgba), Rgba(Rgba),
/// A 32-bit linear RGB color. /// A 32-bit linear RGB color.
@ -179,6 +185,12 @@ impl From<Oklab> for Color {
} }
} }
impl From<Oklch> for Color {
fn from(c: Oklch) -> Self {
Self::Oklch(c)
}
}
impl From<Rgba> for Color { impl From<Rgba> for Color {
fn from(c: Rgba) -> Self { fn from(c: Rgba) -> Self {
Self::Rgba(c) Self::Rgba(c)
@ -300,16 +312,16 @@ impl Color {
/// The real arguments (the other arguments are just for the docs, this /// The real arguments (the other arguments are just for the docs, this
/// function is a bit involved, so we parse the arguments manually). /// function is a bit involved, so we parse the arguments manually).
args: &mut Args, args: &mut Args,
/// The cyan component. /// The lightness component.
#[external] #[external]
lightness: RatioComponent, lightness: RatioComponent,
/// The magenta component. /// The a ("green/red") component.
#[external] #[external]
a: ABComponent, a: ABComponent,
/// The yellow component. /// The b ("blue/yellow") component.
#[external] #[external]
b: ABComponent, b: ABComponent,
/// The key component. /// The alpha component.
#[external] #[external]
alpha: RatioComponent, alpha: RatioComponent,
/// Alternatively: The color to convert to Oklab. /// Alternatively: The color to convert to Oklab.
@ -335,6 +347,68 @@ impl Color {
}) })
} }
/// Create an [Oklch](https://bottosson.github.io/posts/oklab/) color.
///
/// This color space is well suited for the following use cases:
/// - Color manipulation involving lightness, chroma, and hue
/// - Creating grayscale images with uniform perceived lightness
/// - Creating smooth and uniform color transition and gradients
///
/// A linear Oklch color is represented internally by an array of four
/// components:
/// - lightness ([`ratio`]($ratio))
/// - chroma ([`float`]($float) in the range `[-0.4..0.4]`)
/// - hue ([`angle`]($angle))
/// - alpha ([`ratio`]($ratio))
///
/// These components are also available using the
/// [`components`]($color.components) method.
///
/// ```example
/// #square(
/// fill: oklch(40%, 0.2, 160deg, 50%)
/// )
/// ```
#[func]
pub fn oklch(
/// The real arguments (the other arguments are just for the docs, this
/// function is a bit involved, so we parse the arguments manually).
args: &mut Args,
/// The lightness component.
#[external]
lightness: RatioComponent,
/// The chroma component.
#[external]
chroma: ABComponent,
/// The hue component.
#[external]
hue: Angle,
/// The alpha component.
#[external]
alpha: RatioComponent,
/// Alternatively: The color to convert to Oklch.
///
/// If this is given, the individual components should not be given.
#[external]
color: Color,
) -> SourceResult<Color> {
Ok(if let Some(color) = args.find::<Color>()? {
color.to_oklch()
} else {
let RatioComponent(l) = args.expect("lightness component")?;
let ABComponent(c) = args.expect("chroma component")?;
let h: Angle = args.expect("hue component")?;
let RatioComponent(alpha) =
args.eat()?.unwrap_or(RatioComponent(Ratio::one()));
Self::Oklch(Oklch::new(
l.get() as f32,
c.get() as f32,
OklabHue::from_degrees(h.to_deg() as f32),
alpha.get() as f32,
))
})
}
/// Create an RGB(A) color with linear luma. /// Create an RGB(A) color with linear luma.
/// ///
/// This color space is similar to sRGB, but with the distinction that the /// This color space is similar to sRGB, but with the distinction that the
@ -656,6 +730,7 @@ impl Color {
/// |-------------------------|-----------|------------|-----------|--------| /// |-------------------------|-----------|------------|-----------|--------|
/// | [`luma`]($color.luma) | Lightness | | | | /// | [`luma`]($color.luma) | Lightness | | | |
/// | [`oklab`]($color.oklab) | Lightness | `a` | `b` | Alpha | /// | [`oklab`]($color.oklab) | Lightness | `a` | `b` | Alpha |
/// | [`oklch`]($color.oklch) | Lightness | Chroma | Hue | Alpha |
/// | [`linear-rgb`]($color.linear-rgb) | Red | Green | Blue | Alpha | /// | [`linear-rgb`]($color.linear-rgb) | Red | Green | Blue | Alpha |
/// | [`rgb`]($color.rgb) | Red | Green | Blue | Alpha | /// | [`rgb`]($color.rgb) | Red | Green | Blue | Alpha |
/// | [`cmyk`]($color.cmyk) | Cyan | Magenta | Yellow | Key | /// | [`cmyk`]($color.cmyk) | Cyan | Magenta | Yellow | Key |
@ -697,6 +772,26 @@ impl Color {
] ]
} }
} }
Self::Oklch(c) => {
if alpha {
array![
Ratio::new(c.l as _),
(c.chroma as f64 * 1000.0).round() / 1000.0,
Angle::deg(
c.hue.into_degrees().rem_euclid(360.0 + ANGLE_EPSILON) as _
),
Ratio::new(c.alpha as _),
]
} else {
array![
Ratio::new(c.l as _),
(c.chroma as f64 * 1000.0).round() / 1000.0,
Angle::deg(
c.hue.into_degrees().rem_euclid(360.0 + ANGLE_EPSILON) as _
),
]
}
}
Self::LinearRgb(c) => { Self::LinearRgb(c) => {
if alpha { if alpha {
array![ array![
@ -779,8 +874,9 @@ impl Color {
} }
/// Returns the constructor function for this color's space: /// Returns the constructor function for this color's space:
/// - [`oklab`]($color.oklab)
/// - [`luma`]($color.luma) /// - [`luma`]($color.luma)
/// - [`oklab`]($color.oklab)
/// - [`oklch`]($color.oklch)
/// - [`linear-rgb`]($color.linear-rgb) /// - [`linear-rgb`]($color.linear-rgb)
/// - [`rgb`]($color.rgb) /// - [`rgb`]($color.rgb)
/// - [`cmyk`]($color.cmyk) /// - [`cmyk`]($color.cmyk)
@ -796,6 +892,7 @@ impl Color {
match self { match self {
Self::Luma(_) => ColorSpace::D65Gray, Self::Luma(_) => ColorSpace::D65Gray,
Self::Oklab(_) => ColorSpace::Oklab, Self::Oklab(_) => ColorSpace::Oklab,
Self::Oklch(_) => ColorSpace::Oklch,
Self::LinearRgb(_) => ColorSpace::LinearRgb, Self::LinearRgb(_) => ColorSpace::LinearRgb,
Self::Rgba(_) => ColorSpace::Srgb, Self::Rgba(_) => ColorSpace::Srgb,
Self::Cmyk(_) => ColorSpace::Cmyk, Self::Cmyk(_) => ColorSpace::Cmyk,
@ -828,6 +925,7 @@ impl Color {
match self { match self {
Self::Luma(c) => Self::Luma(c.lighten(factor)), Self::Luma(c) => Self::Luma(c.lighten(factor)),
Self::Oklab(c) => Self::Oklab(c.lighten(factor)), Self::Oklab(c) => Self::Oklab(c.lighten(factor)),
Self::Oklch(c) => Self::Oklch(c.lighten(factor)),
Self::LinearRgb(c) => Self::LinearRgb(c.lighten(factor)), Self::LinearRgb(c) => Self::LinearRgb(c.lighten(factor)),
Self::Rgba(c) => Self::Rgba(c.lighten(factor)), Self::Rgba(c) => Self::Rgba(c.lighten(factor)),
Self::Cmyk(c) => Self::Cmyk(c.lighten(factor)), Self::Cmyk(c) => Self::Cmyk(c.lighten(factor)),
@ -847,6 +945,7 @@ impl Color {
match self { match self {
Self::Luma(c) => Self::Luma(c.darken(factor)), Self::Luma(c) => Self::Luma(c.darken(factor)),
Self::Oklab(c) => Self::Oklab(c.darken(factor)), Self::Oklab(c) => Self::Oklab(c.darken(factor)),
Self::Oklch(c) => Self::Oklch(c.darken(factor)),
Self::LinearRgb(c) => Self::LinearRgb(c.darken(factor)), Self::LinearRgb(c) => Self::LinearRgb(c.darken(factor)),
Self::Rgba(c) => Self::Rgba(c.darken(factor)), Self::Rgba(c) => Self::Rgba(c.darken(factor)),
Self::Cmyk(c) => Self::Cmyk(c.darken(factor)), Self::Cmyk(c) => Self::Cmyk(c.darken(factor)),
@ -870,6 +969,7 @@ impl Color {
.with_hint("try converting your color to RGB first")); .with_hint("try converting your color to RGB first"));
} }
Self::Oklab(_) => self.to_hsv().saturate(span, factor)?.to_oklab(), 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::LinearRgb(_) => self.to_hsv().saturate(span, factor)?.to_linear_rgb(),
Self::Rgba(_) => self.to_hsv().saturate(span, factor)?.to_rgba(), Self::Rgba(_) => self.to_hsv().saturate(span, factor)?.to_rgba(),
Self::Cmyk(_) => self.to_hsv().saturate(span, factor)?.to_cmyk(), Self::Cmyk(_) => self.to_hsv().saturate(span, factor)?.to_cmyk(),
@ -893,6 +993,7 @@ impl Color {
.with_hint("try converting your color to RGB first")); .with_hint("try converting your color to RGB first"));
} }
Self::Oklab(_) => self.to_hsv().desaturate(span, factor)?.to_oklab(), 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::LinearRgb(_) => self.to_hsv().desaturate(span, factor)?.to_linear_rgb(),
Self::Rgba(_) => self.to_hsv().desaturate(span, factor)?.to_rgba(), Self::Rgba(_) => self.to_hsv().desaturate(span, factor)?.to_rgba(),
Self::Cmyk(_) => self.to_hsv().desaturate(span, factor)?.to_cmyk(), Self::Cmyk(_) => self.to_hsv().desaturate(span, factor)?.to_cmyk(),
@ -907,6 +1008,12 @@ impl Color {
match self { match self {
Self::Luma(c) => Self::Luma(Luma::new(1.0 - c.luma)), Self::Luma(c) => Self::Luma(Luma::new(1.0 - c.luma)),
Self::Oklab(c) => Self::Oklab(Oklab::new(c.l, -c.a, -c.b, c.alpha)), Self::Oklab(c) => Self::Oklab(Oklab::new(c.l, -c.a, -c.b, c.alpha)),
Self::Oklch(c) => Self::Oklch(Oklch::new(
c.l,
-c.chroma,
OklabHue::from_degrees(360.0 - c.hue.into_degrees()),
c.alpha,
)),
Self::LinearRgb(c) => Self::LinearRgb(LinearRgba::new( Self::LinearRgb(c) => Self::LinearRgb(LinearRgba::new(
1.0 - c.red, 1.0 - c.red,
1.0 - c.green, 1.0 - c.green,
@ -940,18 +1047,35 @@ impl Color {
span: Span, span: Span,
/// The angle to rotate the hue by. /// The angle to rotate the hue by.
angle: Angle, angle: Angle,
/// The color space used to rotate. By default, this happens in a perceptual
/// color space ([`oklch`]($color.oklch)).
#[named]
#[default(ColorSpace::Oklch)]
space: ColorSpace,
) -> SourceResult<Color> { ) -> SourceResult<Color> {
Ok(match self { Ok(match space {
Self::Luma(_) => { ColorSpace::Oklch => {
bail!(error!(span, "cannot rotate grayscale color") let Self::Oklch(oklch) = self.to_oklch() else {
.with_hint("try converting your color to RGB first")); unreachable!();
};
let rotated = oklch.shift_hue(angle.to_deg() as f32);
Self::Oklch(rotated).to_space(self.space())
} }
Self::Oklab(_) => self.to_hsv().rotate(span, angle)?.to_oklab(), ColorSpace::Hsl => {
Self::LinearRgb(_) => self.to_hsv().rotate(span, angle)?.to_linear_rgb(), let Self::Hsl(hsl) = self.to_hsl() else {
Self::Rgba(_) => self.to_hsv().rotate(span, angle)?.to_rgba(), unreachable!();
Self::Cmyk(_) => self.to_hsv().rotate(span, angle)?.to_cmyk(), };
Self::Hsl(c) => Self::Hsl(c.shift_hue(angle.to_deg() as f32)), let rotated = hsl.shift_hue(angle.to_deg() as f32);
Self::Hsv(c) => Self::Hsv(c.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 rotated = hsv.shift_hue(angle.to_deg() as f32);
Self::Hsv(rotated).to_space(self.space())
}
_ => bail!(span, "this colorspace does not support hue rotation"),
}) })
} }
@ -1009,6 +1133,7 @@ impl Color {
let m = acc.map(|v| v / total); let m = acc.map(|v| v / total);
Ok(match space { Ok(match space {
ColorSpace::Oklab => Color::Oklab(Oklab::new(m[0], m[1], m[2], m[3])), ColorSpace::Oklab => Color::Oklab(Oklab::new(m[0], m[1], m[2], m[3])),
ColorSpace::Oklch => Color::Oklch(Oklch::new(m[0], m[1], m[2], m[3])),
ColorSpace::Srgb => Color::Rgba(Rgba::new(m[0], m[1], m[2], m[3])), ColorSpace::Srgb => Color::Rgba(Rgba::new(m[0], m[1], m[2], m[3])),
ColorSpace::LinearRgb => { ColorSpace::LinearRgb => {
Color::LinearRgb(LinearRgba::new(m[0], m[1], m[2], m[3])) Color::LinearRgb(LinearRgba::new(m[0], m[1], m[2], m[3]))
@ -1050,6 +1175,7 @@ impl Color {
match self { match self {
Color::Luma(_) | Color::Cmyk(_) => None, Color::Luma(_) | Color::Cmyk(_) => None,
Color::Oklab(c) => Some(c.alpha), Color::Oklab(c) => Some(c.alpha),
Color::Oklch(c) => Some(c.alpha),
Color::Rgba(c) => Some(c.alpha), Color::Rgba(c) => Some(c.alpha),
Color::LinearRgb(c) => Some(c.alpha), Color::LinearRgb(c) => Some(c.alpha),
Color::Hsl(c) => Some(c.alpha), Color::Hsl(c) => Some(c.alpha),
@ -1062,6 +1188,7 @@ impl Color {
match &mut self { match &mut self {
Color::Luma(_) | Color::Cmyk(_) => {} Color::Luma(_) | Color::Cmyk(_) => {}
Color::Oklab(c) => c.alpha = alpha, Color::Oklab(c) => c.alpha = alpha,
Color::Oklch(c) => c.alpha = alpha,
Color::Rgba(c) => c.alpha = alpha, Color::Rgba(c) => c.alpha = alpha,
Color::LinearRgb(c) => c.alpha = alpha, Color::LinearRgb(c) => c.alpha = alpha,
Color::Hsl(c) => c.alpha = alpha, Color::Hsl(c) => c.alpha = alpha,
@ -1076,6 +1203,12 @@ impl Color {
match self { match self {
Color::Luma(c) => [c.luma; 4], Color::Luma(c) => [c.luma; 4],
Color::Oklab(c) => [c.l, c.a, c.b, c.alpha], Color::Oklab(c) => [c.l, c.a, c.b, c.alpha],
Color::Oklch(c) => [
c.l,
c.chroma,
c.hue.into_degrees().rem_euclid(360.0 + ANGLE_EPSILON),
c.alpha,
],
Color::Rgba(c) => [c.red, c.green, c.blue, c.alpha], Color::Rgba(c) => [c.red, c.green, c.blue, c.alpha],
Color::LinearRgb(c) => [c.red, c.green, c.blue, c.alpha], Color::LinearRgb(c) => [c.red, c.green, c.blue, c.alpha],
Color::Cmyk(c) => [c.c, c.m, c.y, c.k], Color::Cmyk(c) => [c.c, c.m, c.y, c.k],
@ -1102,6 +1235,7 @@ 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::Oklab => self.to_oklab(),
ColorSpace::Oklch => self.to_oklch(),
ColorSpace::Srgb => self.to_rgba(), ColorSpace::Srgb => self.to_rgba(),
ColorSpace::LinearRgb => self.to_linear_rgb(), ColorSpace::LinearRgb => self.to_linear_rgb(),
ColorSpace::Hsl => self.to_hsl(), ColorSpace::Hsl => self.to_hsl(),
@ -1115,6 +1249,7 @@ impl Color {
Self::Luma(match self { Self::Luma(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::Rgba(c) => Luma::from_color(c), Self::Rgba(c) => Luma::from_color(c),
Self::LinearRgb(c) => Luma::from_color(c), Self::LinearRgb(c) => Luma::from_color(c),
Self::Cmyk(c) => Luma::from_color(c.to_rgba()), Self::Cmyk(c) => Luma::from_color(c.to_rgba()),
@ -1127,6 +1262,7 @@ impl Color {
Self::Oklab(match self { Self::Oklab(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::Rgba(c) => Oklab::from_color(c), Self::Rgba(c) => Oklab::from_color(c),
Self::LinearRgb(c) => Oklab::from_color(c), Self::LinearRgb(c) => Oklab::from_color(c),
Self::Cmyk(c) => Oklab::from_color(c.to_rgba()), Self::Cmyk(c) => Oklab::from_color(c.to_rgba()),
@ -1135,10 +1271,24 @@ impl Color {
}) })
} }
pub fn to_oklch(self) -> Self {
Self::Oklch(match self {
Self::Luma(c) => Oklch::from_color(c),
Self::Oklab(c) => Oklch::from_color(c),
Self::Oklch(c) => c,
Self::Rgba(c) => Oklch::from_color(c),
Self::LinearRgb(c) => Oklch::from_color(c),
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_linear_rgb(self) -> Self { pub fn to_linear_rgb(self) -> Self {
Self::LinearRgb(match self { Self::LinearRgb(match self {
Self::Luma(c) => LinearRgba::from_color(c), Self::Luma(c) => LinearRgba::from_color(c),
Self::Oklab(c) => LinearRgba::from_color(c), Self::Oklab(c) => LinearRgba::from_color(c),
Self::Oklch(c) => LinearRgba::from_color(c),
Self::Rgba(c) => LinearRgba::from_color(c), Self::Rgba(c) => LinearRgba::from_color(c),
Self::LinearRgb(c) => c, Self::LinearRgb(c) => c,
Self::Cmyk(c) => LinearRgba::from_color(c.to_rgba()), Self::Cmyk(c) => LinearRgba::from_color(c.to_rgba()),
@ -1151,6 +1301,7 @@ impl Color {
Self::Rgba(match self { Self::Rgba(match self {
Self::Luma(c) => Rgba::from_color(c), Self::Luma(c) => Rgba::from_color(c),
Self::Oklab(c) => Rgba::from_color(c), Self::Oklab(c) => Rgba::from_color(c),
Self::Oklch(c) => Rgba::from_color(c),
Self::Rgba(c) => c, Self::Rgba(c) => c,
Self::LinearRgb(c) => Rgba::from_linear(c), Self::LinearRgb(c) => Rgba::from_linear(c),
Self::Cmyk(c) => c.to_rgba(), Self::Cmyk(c) => c.to_rgba(),
@ -1163,6 +1314,7 @@ impl Color {
Self::Cmyk(match self { Self::Cmyk(match self {
Self::Luma(c) => Cmyk::from_luma(c), Self::Luma(c) => Cmyk::from_luma(c),
Self::Oklab(c) => Cmyk::from_rgba(Rgba::from_color(c)), Self::Oklab(c) => Cmyk::from_rgba(Rgba::from_color(c)),
Self::Oklch(c) => Cmyk::from_rgba(Rgba::from_color(c)),
Self::Rgba(c) => Cmyk::from_rgba(c), Self::Rgba(c) => Cmyk::from_rgba(c),
Self::LinearRgb(c) => Cmyk::from_rgba(Rgba::from_linear(c)), Self::LinearRgb(c) => Cmyk::from_rgba(Rgba::from_linear(c)),
Self::Cmyk(c) => c, Self::Cmyk(c) => c,
@ -1175,6 +1327,7 @@ impl Color {
Self::Hsl(match self { Self::Hsl(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::Rgba(c) => Hsl::from_color(c), Self::Rgba(c) => Hsl::from_color(c),
Self::LinearRgb(c) => Hsl::from_color(Rgba::from_linear(c)), Self::LinearRgb(c) => Hsl::from_color(Rgba::from_linear(c)),
Self::Cmyk(c) => Hsl::from_color(c.to_rgba()), Self::Cmyk(c) => Hsl::from_color(c.to_rgba()),
@ -1187,6 +1340,7 @@ impl Color {
Self::Hsv(match self { Self::Hsv(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::Rgba(c) => Hsv::from_color(c), Self::Rgba(c) => Hsv::from_color(c),
Self::LinearRgb(c) => Hsv::from_color(Rgba::from_linear(c)), Self::LinearRgb(c) => Hsv::from_color(Rgba::from_linear(c)),
Self::Cmyk(c) => Hsv::from_color(c.to_rgba()), Self::Cmyk(c) => Hsv::from_color(c.to_rgba()),
@ -1246,6 +1400,30 @@ impl Repr for Color {
) )
} }
} }
Self::Oklch(c) => {
if c.alpha == 1.0 {
eco_format!(
"oklch({}, {}, {})",
Ratio::new(c.l as _).repr(),
format_float(c.chroma as _, Some(3), ""),
Angle::deg(
c.hue.into_degrees().rem_euclid(360.0 + ANGLE_EPSILON) as _
)
.repr()
)
} else {
eco_format!(
"oklch({}, {}, {}, {})",
Ratio::new(c.l as _).repr(),
format_float(c.chroma as _, Some(3), ""),
Angle::deg(
c.hue.into_degrees().rem_euclid(360.0 + ANGLE_EPSILON) as _
)
.repr(),
Ratio::new(c.alpha as _).repr(),
)
}
}
Self::Hsl(c) => { Self::Hsl(c) => {
if c.alpha == 1.0 { if c.alpha == 1.0 {
eco_format!( eco_format!(
@ -1308,6 +1486,7 @@ impl PartialEq for Color {
(a.luma * 255.0).round() as u8 == (b.luma * 255.0).round() as u8 (a.luma * 255.0).round() as u8 == (b.luma * 255.0).round() as u8
} }
(Self::Oklab(a), Self::Oklab(b)) => a == b, (Self::Oklab(a), Self::Oklab(b)) => a == b,
(Self::Oklch(a), Self::Oklch(b)) => a == b,
(Self::LinearRgb(a), Self::LinearRgb(b)) => a == b, (Self::LinearRgb(a), Self::LinearRgb(b)) => a == b,
(Self::Cmyk(a), Self::Cmyk(b)) => a == b, (Self::Cmyk(a), Self::Cmyk(b)) => a == b,
(Self::Hsl(a), Self::Hsl(b)) => a == b, (Self::Hsl(a), Self::Hsl(b)) => a == b,
@ -1468,12 +1647,15 @@ cast! {
v: Ratio => Self(v.get()), v: Ratio => Self(v.get()),
} }
/// A color space for mixing. /// A color space for color manipulation.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum ColorSpace { pub enum ColorSpace {
/// A perceptual color space. /// The perceptual Oklab color space.
Oklab, Oklab,
/// The perceptual Oklch color space.
Oklch,
/// The standard RGB color space. /// The standard RGB color space.
Srgb, Srgb,
@ -1497,6 +1679,7 @@ cast! {
ColorSpace, ColorSpace,
self => match self { self => match self {
Self::Oklab => Color::oklab_data(), Self::Oklab => Color::oklab_data(),
Self::Oklch => Color::oklch_data(),
Self::Srgb => Color::rgb_data(), Self::Srgb => Color::rgb_data(),
Self::D65Gray => Color::luma_data(), Self::D65Gray => Color::luma_data(),
Self::LinearRgb => Color::linear_rgb_data(), Self::LinearRgb => Color::linear_rgb_data(),
@ -1505,7 +1688,7 @@ cast! {
Self::Cmyk => Color::cmyk_data(), Self::Cmyk => Color::cmyk_data(),
}.into_value(), }.into_value(),
v: Value => { v: Value => {
let expected = "expected `rgb`, `luma`, `cmyk`, `oklab`, `color.linear-rgb`, `color.hsl`, or `color.hsv`"; let expected = "expected `rgb`, `luma`, `cmyk`, `oklab`, `oklch`, `color.linear-rgb`, `color.hsl`, or `color.hsv`";
let Value::Func(func) = v else { let Value::Func(func) = v else {
bail!("{expected}, found {}", v.ty()); bail!("{expected}, found {}", v.ty());
}; };
@ -1514,6 +1697,8 @@ cast! {
// whereas the `NativeFuncData` is not. // whereas the `NativeFuncData` is not.
if func == Color::oklab_data() { if func == Color::oklab_data() {
Self::Oklab Self::Oklab
} else if func == Color::oklch_data() {
Self::Oklch
} else if func == Color::rgb_data() { } else if func == Color::rgb_data() {
Self::Srgb Self::Srgb
} else if func == Color::luma_data() { } else if func == Color::luma_data() {

View File

@ -101,6 +101,7 @@ use crate::syntax::{Span, Spanned};
/// | Color space | Perceptually uniform? | /// | Color space | Perceptually uniform? |
/// | ------------------------------- |-----------------------| /// | ------------------------------- |-----------------------|
/// | [Oklab]($color.oklab) | *Yes* | /// | [Oklab]($color.oklab) | *Yes* |
/// | [Oklch]($color.oklch) | *Yes* |
/// | [sRGB]($color.rgb) | *No* | /// | [sRGB]($color.rgb) | *No* |
/// | [linear-RGB]($color.linear-rgb) | *Yes* | /// | [linear-RGB]($color.linear-rgb) | *Yes* |
/// | [CMYK]($color.cmyk) | *No* | /// | [CMYK]($color.cmyk) | *No* |
@ -113,6 +114,7 @@ use crate::syntax::{Span, Spanned};
/// >>> #set block(spacing: 0pt) /// >>> #set block(spacing: 0pt)
/// #let spaces = ( /// #let spaces = (
/// ("Oklab", color.oklab), /// ("Oklab", color.oklab),
/// ("Oklch", color.oklch),
/// ("linear-RGB", color.linear-rgb), /// ("linear-RGB", color.linear-rgb),
/// ("sRGB", color.rgb), /// ("sRGB", color.rgb),
/// ("CMYK", color.cmyk), /// ("CMYK", color.cmyk),

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

After

Width:  |  Height:  |  Size: 49 KiB

View File

@ -24,6 +24,7 @@
#box(square(size: 9pt, fill: col)) #box(square(size: 9pt, fill: col))
#box(square(size: 9pt, fill: rgb(col))) #box(square(size: 9pt, fill: rgb(col)))
#box(square(size: 9pt, fill: oklab(col))) #box(square(size: 9pt, fill: oklab(col)))
#box(square(size: 9pt, fill: oklch(col)))
#box(square(size: 9pt, fill: luma(col))) #box(square(size: 9pt, fill: luma(col)))
#box(square(size: 9pt, fill: cmyk(col))) #box(square(size: 9pt, fill: cmyk(col)))
#box(square(size: 9pt, fill: color.linear-rgb(col))) #box(square(size: 9pt, fill: color.linear-rgb(col)))
@ -34,16 +35,19 @@
// Test hue rotation // Test hue rotation
#let col = rgb(50%, 64%, 16%) #let col = rgb(50%, 64%, 16%)
// Oklch
#for x in range(0, 11) { #for x in range(0, 11) {
box(square(size: 9pt, fill: rgb(col).rotate(x * 36deg))) box(square(size: 9pt, fill: rgb(col).rotate(x * 36deg)))
} }
// HSL
#for x in range(0, 11) { #for x in range(0, 11) {
box(square(size: 9pt, fill: color.hsv(col).rotate(x * 36deg))) box(square(size: 9pt, fill: rgb(col).rotate(x * 36deg, space: color.hsl)))
} }
// HSV
#for x in range(0, 11) { #for x in range(0, 11) {
box(square(size: 9pt, fill: color.hsl(col).rotate(x * 36deg))) box(square(size: 9pt, fill: rgb(col).rotate(x * 36deg, space: color.hsv)))
} }
--- ---

View File

@ -108,6 +108,7 @@
#test-repr(luma(40).components(), (15.69%, )) #test-repr(luma(40).components(), (15.69%, ))
#test-repr(cmyk(4%, 5%, 6%, 7%).components(), (4%, 5%, 6%, 7%)) #test-repr(cmyk(4%, 5%, 6%, 7%).components(), (4%, 5%, 6%, 7%))
#test-repr(oklab(10%, 0.2, 0.3).components(), (10%, 0.2, 0.3, 100%)) #test-repr(oklab(10%, 0.2, 0.3).components(), (10%, 0.2, 0.3, 100%))
#test-repr(oklch(10%, 0.2, 90deg).components(), (10%, 0.2, 90deg, 100%))
#test-repr(color.linear-rgb(10%, 20%, 30%).components(), (10%, 20%, 30%, 100%)) #test-repr(color.linear-rgb(10%, 20%, 30%).components(), (10%, 20%, 30%, 100%))
#test-repr(color.hsv(10deg, 20%, 30%).components(), (10deg, 20%, 30%, 100%)) #test-repr(color.hsv(10deg, 20%, 30%).components(), (10deg, 20%, 30%, 100%))
#test-repr(color.hsl(10deg, 20%, 30%).components(), (10deg, 20%, 30%, 100%)) #test-repr(color.hsl(10deg, 20%, 30%).components(), (10deg, 20%, 30%, 100%))
@ -131,6 +132,9 @@
#test-repr(color.hsv(rgb(1, 2, 3)), color.hsv(-150deg, 66.67%, 1.18%)) #test-repr(color.hsv(rgb(1, 2, 3)), color.hsv(-150deg, 66.67%, 1.18%))
#test-repr(oklab(luma(40)).components(), (27.68%, 0.0, 0.0, 100%)) #test-repr(oklab(luma(40)).components(), (27.68%, 0.0, 0.0, 100%))
#test-repr(oklab(rgb(1, 2, 3)).components(), (8.23%, -0.004, -0.007, 100%)) #test-repr(oklab(rgb(1, 2, 3)).components(), (8.23%, -0.004, -0.007, 100%))
#test-repr(oklch(oklab(40%, 0.2, 0.2)).components(), (40%, 0.283, 45deg, 100%))
#test-repr(oklch(luma(40)).components(), (27.68%, 0.0, 72.49deg, 100%))
#test-repr(oklch(rgb(1, 2, 3)).components(), (8.23%, 0.008, 240.75deg, 100%))
--- ---
// Test gradient functions. // Test gradient functions.
@ -143,6 +147,7 @@
#test(gradient.linear(red, green, blue, space: rgb).sample(100%), blue) #test(gradient.linear(red, green, blue, space: rgb).sample(100%), blue)
#test(gradient.linear(red, green, space: rgb).space(), rgb) #test(gradient.linear(red, green, space: rgb).space(), rgb)
#test(gradient.linear(red, green, space: oklab).space(), oklab) #test(gradient.linear(red, green, space: oklab).space(), oklab)
#test(gradient.linear(red, green, space: oklch).space(), oklch)
#test(gradient.linear(red, green, space: cmyk).space(), cmyk) #test(gradient.linear(red, green, space: cmyk).space(), cmyk)
#test(gradient.linear(red, green, space: luma).space(), luma) #test(gradient.linear(red, green, space: luma).space(), luma)
#test(gradient.linear(red, green, space: color.linear-rgb).space(), color.linear-rgb) #test(gradient.linear(red, green, space: color.linear-rgb).space(), color.linear-rgb)

View File

@ -7,6 +7,7 @@
#blue \ #blue \
#color.linear-rgb(blue) \ #color.linear-rgb(blue) \
#oklab(blue) \ #oklab(blue) \
#oklch(blue) \
#cmyk(blue) \ #cmyk(blue) \
#color.hsl(blue) \ #color.hsl(blue) \
#color.hsv(blue) \ #color.hsv(blue) \

View File

@ -37,6 +37,7 @@
#test(rgb(rgb(10, 20, 30)).space(), rgb) #test(rgb(rgb(10, 20, 30)).space(), rgb)
#test(color.linear-rgb(rgb(10, 20, 30)).space(), color.linear-rgb) #test(color.linear-rgb(rgb(10, 20, 30)).space(), color.linear-rgb)
#test(oklab(rgb(10, 20, 30)).space(), oklab) #test(oklab(rgb(10, 20, 30)).space(), oklab)
#test(oklch(rgb(10, 20, 30)).space(), oklch)
#test(color.hsl(rgb(10, 20, 30)).space(), color.hsl) #test(color.hsl(rgb(10, 20, 30)).space(), color.hsl)
#test(color.hsv(rgb(10, 20, 30)).space(), color.hsv) #test(color.hsv(rgb(10, 20, 30)).space(), color.hsv)
#test(cmyk(rgb(10, 20, 30)).space(), cmyk) #test(cmyk(rgb(10, 20, 30)).space(), cmyk)
@ -45,6 +46,7 @@
#test(rgb(color.linear-rgb(10, 20, 30)).space(), rgb) #test(rgb(color.linear-rgb(10, 20, 30)).space(), rgb)
#test(color.linear-rgb(color.linear-rgb(10, 20, 30)).space(), color.linear-rgb) #test(color.linear-rgb(color.linear-rgb(10, 20, 30)).space(), color.linear-rgb)
#test(oklab(color.linear-rgb(10, 20, 30)).space(), oklab) #test(oklab(color.linear-rgb(10, 20, 30)).space(), oklab)
#test(oklch(color.linear-rgb(10, 20, 30)).space(), oklch)
#test(color.hsl(color.linear-rgb(10, 20, 30)).space(), color.hsl) #test(color.hsl(color.linear-rgb(10, 20, 30)).space(), color.hsl)
#test(color.hsv(color.linear-rgb(10, 20, 30)).space(), color.hsv) #test(color.hsv(color.linear-rgb(10, 20, 30)).space(), color.hsv)
#test(cmyk(color.linear-rgb(10, 20, 30)).space(), cmyk) #test(cmyk(color.linear-rgb(10, 20, 30)).space(), cmyk)
@ -53,14 +55,25 @@
#test(rgb(oklab(10%, 20%, 30%)).space(), rgb) #test(rgb(oklab(10%, 20%, 30%)).space(), rgb)
#test(color.linear-rgb(oklab(10%, 20%, 30%)).space(), color.linear-rgb) #test(color.linear-rgb(oklab(10%, 20%, 30%)).space(), color.linear-rgb)
#test(oklab(oklab(10%, 20%, 30%)).space(), oklab) #test(oklab(oklab(10%, 20%, 30%)).space(), oklab)
#test(oklch(oklab(10%, 20%, 30%)).space(), oklch)
#test(color.hsl(oklab(10%, 20%, 30%)).space(), color.hsl) #test(color.hsl(oklab(10%, 20%, 30%)).space(), color.hsl)
#test(color.hsv(oklab(10%, 20%, 30%)).space(), color.hsv) #test(color.hsv(oklab(10%, 20%, 30%)).space(), color.hsv)
#test(cmyk(oklab(10%, 20%, 30%)).space(), cmyk) #test(cmyk(oklab(10%, 20%, 30%)).space(), cmyk)
#test(luma(oklab(10%, 20%, 30%)).space(), luma) #test(luma(oklab(10%, 20%, 30%)).space(), luma)
#test(rgb(oklch(60%, 40%, 0deg)).space(), rgb)
#test(color.linear-rgb(oklch(60%, 40%, 0deg)).space(), color.linear-rgb)
#test(oklab(oklch(60%, 40%, 0deg)).space(), oklab)
#test(oklch(oklch(60%, 40%, 0deg)).space(), oklch)
#test(color.hsl(oklch(60%, 40%, 0deg)).space(), color.hsl)
#test(color.hsv(oklch(60%, 40%, 0deg)).space(), color.hsv)
#test(cmyk(oklch(60%, 40%, 0deg)).space(), cmyk)
#test(luma(oklch(60%, 40%, 0deg)).space(), luma)
#test(rgb(color.hsl(10deg, 20%, 30%)).space(), rgb) #test(rgb(color.hsl(10deg, 20%, 30%)).space(), rgb)
#test(color.linear-rgb(color.hsl(10deg, 20%, 30%)).space(), color.linear-rgb) #test(color.linear-rgb(color.hsl(10deg, 20%, 30%)).space(), color.linear-rgb)
#test(oklab(color.hsl(10deg, 20%, 30%)).space(), oklab) #test(oklab(color.hsl(10deg, 20%, 30%)).space(), oklab)
#test(oklch(color.hsl(10deg, 20%, 30%)).space(), oklch)
#test(color.hsl(color.hsl(10deg, 20%, 30%)).space(), color.hsl) #test(color.hsl(color.hsl(10deg, 20%, 30%)).space(), color.hsl)
#test(color.hsv(color.hsl(10deg, 20%, 30%)).space(), color.hsv) #test(color.hsv(color.hsl(10deg, 20%, 30%)).space(), color.hsv)
#test(cmyk(color.hsl(10deg, 20%, 30%)).space(), cmyk) #test(cmyk(color.hsl(10deg, 20%, 30%)).space(), cmyk)
@ -69,6 +82,7 @@
#test(rgb(color.hsv(10deg, 20%, 30%)).space(), rgb) #test(rgb(color.hsv(10deg, 20%, 30%)).space(), rgb)
#test(color.linear-rgb(color.hsv(10deg, 20%, 30%)).space(), color.linear-rgb) #test(color.linear-rgb(color.hsv(10deg, 20%, 30%)).space(), color.linear-rgb)
#test(oklab(color.hsv(10deg, 20%, 30%)).space(), oklab) #test(oklab(color.hsv(10deg, 20%, 30%)).space(), oklab)
#test(oklch(color.hsv(10deg, 20%, 30%)).space(), oklch)
#test(color.hsl(color.hsv(10deg, 20%, 30%)).space(), color.hsl) #test(color.hsl(color.hsv(10deg, 20%, 30%)).space(), color.hsl)
#test(color.hsv(color.hsv(10deg, 20%, 30%)).space(), color.hsv) #test(color.hsv(color.hsv(10deg, 20%, 30%)).space(), color.hsv)
#test(cmyk(color.hsv(10deg, 20%, 30%)).space(), cmyk) #test(cmyk(color.hsv(10deg, 20%, 30%)).space(), cmyk)
@ -77,6 +91,7 @@
#test(rgb(cmyk(10%, 20%, 30%, 40%)).space(), rgb) #test(rgb(cmyk(10%, 20%, 30%, 40%)).space(), rgb)
#test(color.linear-rgb(cmyk(10%, 20%, 30%, 40%)).space(), color.linear-rgb) #test(color.linear-rgb(cmyk(10%, 20%, 30%, 40%)).space(), color.linear-rgb)
#test(oklab(cmyk(10%, 20%, 30%, 40%)).space(), oklab) #test(oklab(cmyk(10%, 20%, 30%, 40%)).space(), oklab)
#test(oklch(cmyk(10%, 20%, 30%, 40%)).space(), oklch)
#test(color.hsl(cmyk(10%, 20%, 30%, 40%)).space(), color.hsl) #test(color.hsl(cmyk(10%, 20%, 30%, 40%)).space(), color.hsl)
#test(color.hsv(cmyk(10%, 20%, 30%, 40%)).space(), color.hsv) #test(color.hsv(cmyk(10%, 20%, 30%, 40%)).space(), color.hsv)
#test(cmyk(cmyk(10%, 20%, 30%, 40%)).space(), cmyk) #test(cmyk(cmyk(10%, 20%, 30%, 40%)).space(), cmyk)
@ -85,6 +100,7 @@
#test(rgb(luma(10%)).space(), rgb) #test(rgb(luma(10%)).space(), rgb)
#test(color.linear-rgb(luma(10%)).space(), color.linear-rgb) #test(color.linear-rgb(luma(10%)).space(), color.linear-rgb)
#test(oklab(luma(10%)).space(), oklab) #test(oklab(luma(10%)).space(), oklab)
#test(oklch(luma(10%)).space(), oklch)
#test(color.hsl(luma(10%)).space(), color.hsl) #test(color.hsl(luma(10%)).space(), color.hsl)
#test(color.hsv(luma(10%)).space(), color.hsv) #test(color.hsv(luma(10%)).space(), color.hsv)
#test(cmyk(luma(10%)).space(), cmyk) #test(cmyk(luma(10%)).space(), cmyk)
@ -130,15 +146,15 @@
#color.mix((red, 1, 2)) #color.mix((red, 1, 2))
--- ---
// Error: 31-38 expected `rgb`, `luma`, `cmyk`, `oklab`, `color.linear-rgb`, `color.hsl`, or `color.hsv`, found string // Error: 31-38 expected `rgb`, `luma`, `cmyk`, `oklab`, `oklch`, `color.linear-rgb`, `color.hsl`, or `color.hsv`, found string
#color.mix(red, green, space: "cyber") #color.mix(red, green, space: "cyber")
--- ---
// Error: 31-36 expected `rgb`, `luma`, `cmyk`, `oklab`, `color.linear-rgb`, `color.hsl`, or `color.hsv` // Error: 31-36 expected `rgb`, `luma`, `cmyk`, `oklab`, `oklch`, `color.linear-rgb`, `color.hsl`, or `color.hsv`
#color.mix(red, green, space: image) #color.mix(red, green, space: image)
--- ---
// Error: 31-41 expected `rgb`, `luma`, `cmyk`, `oklab`, `color.linear-rgb`, `color.hsl`, or `color.hsv` // Error: 31-41 expected `rgb`, `luma`, `cmyk`, `oklab`, `oklch`, `color.linear-rgb`, `color.hsl`, or `color.hsv`
#color.mix(red, green, space: calc.round) #color.mix(red, green, space: calc.round)
--- ---