css: Implement color conversion

Implement gtk_css_color_convert, which is an attempt to
implement https://www.w3.org/TR/css-color-4/#color-conversion
for the color spaces we support.

Missing:
- white point adaptation
- gamut mapping
This commit is contained in:
Matthias Clasen 2024-05-31 09:36:36 -04:00
parent 5a85bc691e
commit ecc678454b
2 changed files with 220 additions and 0 deletions

View File

@ -59,6 +59,222 @@ gtk_css_color_init (GtkCssColor *color,
gtk_css_color_init_with_missing (color, color_space, values, missing);
}
/* }}} */
/* {{{ Color conversion */
static void
convert_to_rectangular (GtkCssColor *output)
{
float v[4];
switch (output->color_space)
{
case GTK_CSS_COLOR_SPACE_SRGB:
case GTK_CSS_COLOR_SPACE_SRGB_LINEAR:
case GTK_CSS_COLOR_SPACE_OKLAB:
break;
case GTK_CSS_COLOR_SPACE_HSL:
gtk_hsl_to_rgb (output->values[0],
output->values[1] / 100,
output->values[2] / 100,
&v[0], &v[1], &v[2]);
v[3] = output->values[3];
gtk_css_color_init (output, GTK_CSS_COLOR_SPACE_SRGB, v);
break;
case GTK_CSS_COLOR_SPACE_HWB:
gtk_hwb_to_rgb (output->values[0],
output->values[1] / 100,
output->values[2] / 100,
&v[0], &v[1], &v[2]);
v[3] = output->values[3];
gtk_css_color_init (output, GTK_CSS_COLOR_SPACE_SRGB, v);
break;
case GTK_CSS_COLOR_SPACE_OKLCH:
gtk_oklch_to_oklab (output->values[0],
output->values[1] / 100,
output->values[2] / 100,
&v[0], &v[1], &v[2]);
v[3] = output->values[3];
gtk_css_color_init (output, GTK_CSS_COLOR_SPACE_OKLAB, v);
break;
default:
g_assert_not_reached ();
}
}
static void
convert_to_linear (GtkCssColor *output)
{
float v[4];
g_assert (output->color_space == GTK_CSS_COLOR_SPACE_SRGB ||
output->color_space == GTK_CSS_COLOR_SPACE_SRGB_LINEAR ||
output->color_space == GTK_CSS_COLOR_SPACE_OKLAB);
if (output->color_space == GTK_CSS_COLOR_SPACE_SRGB)
{
gtk_rgb_to_linear_srgb (output->values[0],
output->values[1],
output->values[2],
&v[0], &v[1], &v[2]);
v[3] = output->values[3];
gtk_css_color_init (output, GTK_CSS_COLOR_SPACE_SRGB_LINEAR, v);
}
}
static void
convert_from_linear (GtkCssColor *output,
GtkCssColorSpace dest)
{
float v[4];
g_assert (output->color_space == GTK_CSS_COLOR_SPACE_SRGB_LINEAR ||
output->color_space == GTK_CSS_COLOR_SPACE_OKLAB);
switch (dest)
{
case GTK_CSS_COLOR_SPACE_SRGB:
case GTK_CSS_COLOR_SPACE_HSL:
case GTK_CSS_COLOR_SPACE_HWB:
gtk_linear_srgb_to_rgb (output->values[0],
output->values[1],
output->values[2],
&v[0], &v[1], &v[2]);
v[3] = output->values[3];
gtk_css_color_init (output, GTK_CSS_COLOR_SPACE_SRGB, v);
break;
case GTK_CSS_COLOR_SPACE_SRGB_LINEAR:
case GTK_CSS_COLOR_SPACE_OKLAB:
case GTK_CSS_COLOR_SPACE_OKLCH:
break;
default:
g_assert_not_reached ();
}
}
static void
convert_from_rectangular (GtkCssColor *output,
GtkCssColorSpace dest)
{
float v[4];
switch (dest)
{
case GTK_CSS_COLOR_SPACE_SRGB:
case GTK_CSS_COLOR_SPACE_SRGB_LINEAR:
case GTK_CSS_COLOR_SPACE_OKLAB:
g_assert (output->color_space == dest);
break;
case GTK_CSS_COLOR_SPACE_HSL:
g_assert (output->color_space == GTK_CSS_COLOR_SPACE_SRGB);
gtk_rgb_to_hsl (output->values[0],
output->values[1],
output->values[2],
&v[0], &v[1], &v[2]);
v[1] *= 100;
v[2] *= 100;
v[3] = output->values[3];
gtk_css_color_init (output, dest, v);
break;
case GTK_CSS_COLOR_SPACE_HWB:
g_assert (output->color_space == GTK_CSS_COLOR_SPACE_SRGB);
gtk_rgb_to_hwb (output->values[0],
output->values[1],
output->values[2],
&v[0], &v[1], &v[2]);
v[1] *= 100;
v[2] *= 100;
v[3] = output->values[3];
gtk_css_color_init (output, dest, v);
break;
case GTK_CSS_COLOR_SPACE_OKLCH:
g_assert (output->color_space == GTK_CSS_COLOR_SPACE_OKLAB);
gtk_oklab_to_oklch (output->values[0],
output->values[1],
output->values[2],
&v[0], &v[1], &v[2]);
v[3] = output->values[3];
gtk_css_color_init (output, dest, v);
break;
default:
g_assert_not_reached ();
}
}
static void
convert_linear_to_linear (GtkCssColor *output,
GtkCssColorSpace dest)
{
GtkCssColorSpace dest_linear;
float v[4];
if (dest == GTK_CSS_COLOR_SPACE_OKLCH)
dest_linear = GTK_CSS_COLOR_SPACE_OKLAB;
else
dest_linear = GTK_CSS_COLOR_SPACE_SRGB_LINEAR;
if (dest_linear == GTK_CSS_COLOR_SPACE_SRGB_LINEAR &&
output->color_space == GTK_CSS_COLOR_SPACE_OKLAB)
{
gtk_oklab_to_linear_srgb (output->values[0],
output->values[1],
output->values[2],
&v[0], &v[1], &v[2]);
v[3] = output->values[3];
gtk_css_color_init (output, dest_linear, v);
}
else if (dest_linear == GTK_CSS_COLOR_SPACE_OKLAB &&
output->color_space == GTK_CSS_COLOR_SPACE_SRGB_LINEAR)
{
gtk_linear_srgb_to_oklab (output->values[0],
output->values[1],
output->values[2],
&v[0], &v[1], &v[2]);
v[3] = output->values[3];
gtk_css_color_init (output, dest_linear, v);
}
g_assert (output->color_space == dest_linear);
}
/* See https://www.w3.org/TR/css-color-4/#color-conversion */
void
gtk_css_color_convert (const GtkCssColor *input,
GtkCssColorSpace dest,
GtkCssColor *output)
{
gtk_css_color_init_from_color (output, input);
convert_to_rectangular (output);
convert_to_linear (output);
/* FIXME: White point adaptation goes here */
g_assert (output->color_space == GTK_CSS_COLOR_SPACE_SRGB_LINEAR ||
output->color_space == GTK_CSS_COLOR_SPACE_OKLAB);
convert_linear_to_linear (output, dest);
convert_from_linear (output, dest);
/* FIXME: Gamut mapping goes here */
convert_from_rectangular (output, dest);
}
/* }}} */
/* vim:set foldmethod=marker expandtab: */

View File

@ -84,4 +84,8 @@ void gtk_css_color_init (GtkCssColor *color,
GtkCssColorSpace color_space,
const float values[4]);
void gtk_css_color_convert (const GtkCssColor *input,
GtkCssColorSpace dest,
GtkCssColor *output);
G_END_DECLS