csscolorvalue: Implement color-mix()

This commit is contained in:
Alice Mikhaylenko 2024-06-03 20:05:42 +04:00
parent f5783f1637
commit 743329a3af

View File

@ -41,6 +41,7 @@ typedef enum {
COLOR_TYPE_LITERAL,
COLOR_TYPE_COLOR,
COLOR_TYPE_NAME,
COLOR_TYPE_COLOR_MIX,
COLOR_TYPE_SHADE,
COLOR_TYPE_ALPHA,
COLOR_TYPE_MIX,
@ -59,6 +60,16 @@ struct _GtkCssValue
char *name;
GtkCssColor color;
struct
{
GtkCssColorSpace color_space;
GtkCssHueInterpolation hue_interpolation;
GtkCssValue *color1;
GtkCssValue *color2;
float percentage1;
float percentage2;
} color_mix;
struct
{
GtkCssValue *color;
@ -85,6 +96,11 @@ gtk_css_value_color_free (GtkCssValue *color)
g_free (color->name);
break;
case COLOR_TYPE_COLOR_MIX:
gtk_css_value_unref (color->color_mix.color1);
gtk_css_value_unref (color->color_mix.color2);
break;
case COLOR_TYPE_SHADE:
gtk_css_value_unref (color->shade.color);
break;
@ -214,6 +230,16 @@ gtk_css_value_color_equal (const GtkCssValue *value1,
case COLOR_TYPE_NAME:
return g_str_equal (value1->name, value2->name);
case COLOR_TYPE_COLOR_MIX:
return value1->color_mix.color_space == value2->color_mix.color_space &&
value1->color_mix.hue_interpolation == value2->color_mix.hue_interpolation &&
value1->color_mix.percentage1 == value2->color_mix.percentage1 &&
value1->color_mix.percentage2 == value2->color_mix.percentage2 &&
gtk_css_value_equal (value1->color_mix.color1,
value2->color_mix.color1) &&
gtk_css_value_equal (value1->color_mix.color2,
value2->color_mix.color2);
case COLOR_TYPE_SHADE:
return value1->shade.factor == value2->shade.factor &&
gtk_css_value_equal (value1->shade.color,
@ -240,6 +266,50 @@ gtk_css_value_color_equal (const GtkCssValue *value1,
}
}
static void
normalize_color_mix_percentages (float p1,
float p2,
float *out_p1,
float *out_p2,
float *alpha_multiplier)
{
*alpha_multiplier = 1;
if (p1 < -0.5 && p2 < -0.5)
{
if (out_p1)
*out_p1 = 50;
if (out_p2)
*out_p2 = 50;
return;
}
if (p1 < -0.5)
{
if (out_p1)
*out_p1 = 100 - p2;
if (out_p2)
*out_p2 = p2;
return;
}
if (p2 < -0.5)
{
if (out_p1)
*out_p1 = p1;
if (out_p2)
*out_p2 = 100 - p1;
return;
}
if (out_p1)
*out_p1 = p1 / (p1 + p2) * 100;
if (out_p2)
*out_p2 = p2 / (p1 + p2) * 100;
if (alpha_multiplier)
*alpha_multiplier = MIN (1, (p1 + p2) * 0.01);
}
static GtkCssValue *
gtk_css_value_color_transition (GtkCssValue *start,
GtkCssValue *end,
@ -270,6 +340,56 @@ gtk_css_value_color_print (const GtkCssValue *value,
g_string_append (string, value->name);
break;
case COLOR_TYPE_COLOR_MIX:
{
char percent[G_ASCII_DTOSTR_BUF_SIZE];
float p1 = value->color_mix.percentage1;
float p2 = value->color_mix.percentage2;
gboolean p1_present = p1 > -0.5;
gboolean p2_present = p2 > -0.5;
gboolean defaults = (!p1_present || G_APPROX_VALUE (p1, 50, FLT_EPSILON)) &&
(!p2_present || G_APPROX_VALUE (p2, 50, FLT_EPSILON));
if (!p1_present && p2_present && !defaults)
{
p1 = 100 - p2;
p1_present = TRUE;
p2_present = FALSE;
}
g_string_append (string, "color-mix(");
gtk_css_color_interpolation_method_print (value->color_mix.color_space,
value->color_mix.hue_interpolation,
string);
g_string_append (string, ", ");
gtk_css_value_print (value->color_mix.color1, string);
if (p1_present && !defaults)
{
g_ascii_dtostr (percent, sizeof (percent), p1);
g_string_append_c (string, ' ');
g_string_append (string, percent);
g_string_append_c (string, '%');
}
g_string_append (string, ", ");
gtk_css_value_print (value->color_mix.color2, string);
if (p2_present && !defaults)
{
g_ascii_dtostr (percent, sizeof (percent), p2);
g_string_append_c (string, ' ');
g_string_append (string, percent);
g_string_append_c (string, '%');
}
g_string_append (string, ")");
}
break;
case COLOR_TYPE_SHADE:
g_string_append (string, "shade(");
gtk_css_value_print (value->shade.color, string);
@ -373,6 +493,51 @@ apply_mix (const GdkRGBA *in1,
}
}
static GtkCssValue *
apply_color_mix (GtkCssColorSpace in,
GtkCssHueInterpolation interp,
const GtkCssValue *value1,
const GtkCssValue *value2,
float percentage1,
float percentage2)
{
float p2, alpha_multiplier;
GtkCssColor color1, color2, output;
gboolean missing[4];
int i;
if (value1->type == COLOR_TYPE_LITERAL)
{
gtk_css_color_init (&color1, GTK_CSS_COLOR_SPACE_SRGB,
(float[4]) { value1->rgba.red, value1->rgba.green, value1->rgba.blue, value1->rgba.alpha });
}
else
color1 = value1->color;
if (value2->type == COLOR_TYPE_LITERAL)
{
gtk_css_color_init (&color2, GTK_CSS_COLOR_SPACE_SRGB,
(float[4]) { value2->rgba.red, value2->rgba.green, value2->rgba.blue, value2->rgba.alpha });
}
else
color2 = value2->color;
normalize_color_mix_percentages (percentage1, percentage2,
NULL, &p2, &alpha_multiplier);
gtk_css_color_interpolate (&color1, &color2, p2 * 0.01, in, interp, &output);
output.values[3] *= alpha_multiplier;
for (i = 0; i < 4; i++)
missing[i] = gtk_css_color_component_missing (&output, i);
return gtk_css_color_value_new_color (output.color_space,
FALSE,
output.values,
missing);
}
static GtkCssValue *
gtk_css_color_value_do_resolve (GtkCssValue *color,
GtkStyleProvider *provider,
@ -408,6 +573,24 @@ gtk_css_color_value_do_resolve (GtkCssValue *color,
}
break;
case COLOR_TYPE_COLOR_MIX:
{
GtkCssValue *val1, *val2;
val1 = gtk_css_color_value_do_resolve (color->color_mix.color1, provider, current, cycle_list);
val2 = gtk_css_color_value_do_resolve (color->color_mix.color2, provider, current, cycle_list);
value = apply_color_mix (color->color_mix.color_space,
color->color_mix.hue_interpolation,
val1, val2,
color->color_mix.percentage1,
color->color_mix.percentage2);
gtk_css_value_unref (val1);
gtk_css_value_unref (val2);
}
break;
case COLOR_TYPE_SHADE:
{
const GdkRGBA *c;
@ -623,6 +806,41 @@ gtk_css_color_value_new_alpha (GtkCssValue *color,
return value;
}
static GtkCssValue *
gtk_css_color_value_new_color_mix (GtkCssColorSpace color_space,
GtkCssHueInterpolation hue_interpolation,
GtkCssValue *color1,
GtkCssValue *color2,
float percentage1,
float percentage2)
{
GtkCssValue *value;
gtk_internal_return_val_if_fail (color1->class == &GTK_CSS_VALUE_COLOR, NULL);
gtk_internal_return_val_if_fail (color2->class == &GTK_CSS_VALUE_COLOR, NULL);
if (percentage1 > -0.5 && percentage2 > -0.5 && fabs (percentage1 + percentage2) < 0.001)
return NULL;
if (color1->is_computed && color2->is_computed)
{
return apply_color_mix (color_space, hue_interpolation,
color1, color2,
percentage1, percentage2);
}
value = gtk_css_value_new (GtkCssValue, &GTK_CSS_VALUE_COLOR);
value->type = COLOR_TYPE_COLOR_MIX;
value->color_mix.color_space = color_space;
value->color_mix.hue_interpolation = hue_interpolation;
value->color_mix.color1 = gtk_css_value_ref (color1);
value->color_mix.color2 = gtk_css_value_ref (color2);
value->color_mix.percentage1 = percentage1;
value->color_mix.percentage2 = percentage2;
return value;
}
static GtkCssValue *
gtk_css_color_value_new_mix (GtkCssValue *color1,
GtkCssValue *color2,
@ -684,7 +902,90 @@ gtk_css_color_value_can_parse (GtkCssParser *parser)
|| gtk_css_parser_has_function (parser, "hwb")
|| gtk_css_parser_has_function (parser, "oklab")
|| gtk_css_parser_has_function (parser, "oklch")
|| gtk_css_parser_has_function (parser, "color");
|| gtk_css_parser_has_function (parser, "color")
|| gtk_css_parser_has_function (parser, "color-mix");
}
typedef struct
{
GtkCssColorSpace color_space;
GtkCssHueInterpolation hue_interpolation;
GtkCssValue *color1;
GtkCssValue *color2;
float percentage1;
float percentage2;
} ColorMixData;
static gboolean
parse_color_mix_component (GtkCssParser *parser,
GtkCssValue **value,
float *percentage)
{
*percentage = -1;
if (gtk_css_color_value_can_parse (parser))
{
*value = gtk_css_color_value_parse (parser);
if (*value == NULL)
return FALSE;
}
if (gtk_css_number_value_can_parse (parser))
{
GtkCssValue *val;
val = gtk_css_number_value_parse (parser, GTK_CSS_PARSE_PERCENT | GTK_CSS_POSITIVE_ONLY);
if (val == NULL)
return FALSE;
*percentage = gtk_css_number_value_get_canonical (val, 100);
if (*percentage > 100)
{
gtk_css_parser_error_syntax (parser, "Values above 100%% are not allowed");
gtk_css_value_unref (val);
return FALSE;
}
gtk_css_value_unref (val);
}
if (*value == NULL)
{
*value = gtk_css_color_value_parse (parser);
if (*value == NULL)
return FALSE;
}
return TRUE;
}
static guint
parse_color_mix (GtkCssParser *parser,
guint arg,
gpointer data_)
{
ColorMixData *data = data_;
switch (arg)
{
case 0:
if (!gtk_css_color_interpolation_method_parse (parser, &data->color_space, &data->hue_interpolation))
return 0;
return 1;
case 1:
if (!parse_color_mix_component (parser, &data->color1, &data->percentage1))
return 0;
return 1;
case 2:
if (!parse_color_mix_component (parser, &data->color2, &data->percentage2))
return 0;
return 1;
default:
g_assert_not_reached ();
}
}
typedef struct
@ -695,9 +996,9 @@ typedef struct
} ColorFunctionData;
static guint
parse_color_mix (GtkCssParser *parser,
guint arg,
gpointer data_)
parse_legacy_mix (GtkCssParser *parser,
guint arg,
gpointer data_)
{
ColorFunctionData *data = data_;
@ -1485,6 +1786,31 @@ gtk_css_color_value_parse (GtkCssParser *parser)
return gtk_css_color_value_new_color (data.color_space, FALSE, data.values, data.missing);
}
else if (gtk_css_parser_has_function (parser, "color-mix"))
{
ColorMixData data;
data.color1 = NULL;
data.color2 = NULL;
if (gtk_css_parser_consume_function (parser, 3, 3, parse_color_mix, &data))
{
value = gtk_css_color_value_new_color_mix (data.color_space,
data.hue_interpolation,
data.color1,
data.color2,
data.percentage1,
data.percentage2);
}
else
{
value = NULL;
}
g_clear_pointer (&data.color1, gtk_css_value_unref);
g_clear_pointer (&data.color2, gtk_css_value_unref);
return value;
}
else if (gtk_css_parser_has_function (parser, "lighter"))
{
ColorFunctionData data = { NULL, };
@ -1537,7 +1863,7 @@ gtk_css_color_value_parse (GtkCssParser *parser)
{
ColorFunctionData data = { NULL, };
if (gtk_css_parser_consume_function (parser, 3, 3, parse_color_mix, &data))
if (gtk_css_parser_consume_function (parser, 3, 3, parse_legacy_mix, &data))
value = gtk_css_color_value_new_mix (data.color, data.color2, data.value);
else
value = NULL;