diff --git a/gtk/Makefile.am b/gtk/Makefile.am
index bdb489859e..b49b2d5fc9 100644
--- a/gtk/Makefile.am
+++ b/gtk/Makefile.am
@@ -401,6 +401,7 @@ gtk_private_h_sources = \
gtkcssdimensionvalueprivate.h \
gtkcsseasevalueprivate.h \
gtkcssenumvalueprivate.h \
+ gtkcssfiltervalueprivate.h \
gtkcssgadgetprivate.h \
gtkcssiconthemevalueprivate.h \
gtkcssimagebuiltinprivate.h \
@@ -668,6 +669,7 @@ gtk_base_c_sources = \
gtkcssdimensionvalue.c \
gtkcsseasevalue.c \
gtkcssenumvalue.c \
+ gtkcssfiltervalue.c \
gtkcssgadget.c \
gtkcssiconthemevalue.c \
gtkcssimage.c \
diff --git a/gtk/gtkcssfiltervalue.c b/gtk/gtkcssfiltervalue.c
new file mode 100644
index 0000000000..808d41f18c
--- /dev/null
+++ b/gtk/gtkcssfiltervalue.c
@@ -0,0 +1,863 @@
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 2017 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see .
+ */
+
+#include "config.h"
+
+#include "gtkcssbgsizevalueprivate.h"
+
+#include
+#include
+
+#include "gtkcssfiltervalueprivate.h"
+#include "gtkcssnumbervalueprivate.h"
+
+typedef union _GtkCssFilter GtkCssFilter;
+
+typedef enum {
+ GTK_CSS_FILTER_NONE,
+ GTK_CSS_FILTER_BLUR,
+ GTK_CSS_FILTER_BRIGHTNESS,
+ GTK_CSS_FILTER_CONTRAST,
+ GTK_CSS_FILTER_DROP_SHADOW,
+ GTK_CSS_FILTER_GRAYSCALE,
+ GTK_CSS_FILTER_HUE_ROTATE,
+ GTK_CSS_FILTER_INVERT,
+ GTK_CSS_FILTER_OPACITY,
+ GTK_CSS_FILTER_SATURATE,
+ GTK_CSS_FILTER_SEPIA
+} GtkCssFilterType;
+
+union _GtkCssFilter {
+ GtkCssFilterType type;
+ struct {
+ GtkCssFilterType type;
+ GtkCssValue *value;
+ } brightness, contrast, grayscale, hue_rotate, invert, opacity, saturate, sepia;
+};
+
+struct _GtkCssValue {
+ GTK_CSS_VALUE_BASE
+ guint n_filters;
+ GtkCssFilter filters[1];
+};
+
+static GtkCssValue * gtk_css_filter_value_alloc (guint n_values);
+static gboolean gtk_css_filter_value_is_none (const GtkCssValue *value);
+
+static void
+gtk_css_filter_clear (GtkCssFilter *filter)
+{
+ switch (filter->type)
+ {
+ case GTK_CSS_FILTER_BRIGHTNESS:
+ _gtk_css_value_unref (filter->brightness.value);
+ break;
+ case GTK_CSS_FILTER_CONTRAST:
+ _gtk_css_value_unref (filter->contrast.value);
+ break;
+ case GTK_CSS_FILTER_GRAYSCALE:
+ _gtk_css_value_unref (filter->grayscale.value);
+ break;
+ case GTK_CSS_FILTER_HUE_ROTATE:
+ _gtk_css_value_unref (filter->hue_rotate.value);
+ break;
+ case GTK_CSS_FILTER_INVERT:
+ _gtk_css_value_unref (filter->invert.value);
+ break;
+ case GTK_CSS_FILTER_OPACITY:
+ _gtk_css_value_unref (filter->opacity.value);
+ break;
+ case GTK_CSS_FILTER_SATURATE:
+ _gtk_css_value_unref (filter->saturate.value);
+ break;
+ case GTK_CSS_FILTER_SEPIA:
+ _gtk_css_value_unref (filter->sepia.value);
+ break;
+ case GTK_CSS_FILTER_NONE:
+ case GTK_CSS_FILTER_BLUR:
+ case GTK_CSS_FILTER_DROP_SHADOW:
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+}
+
+static void
+gtk_css_filter_init_identity (GtkCssFilter *filter,
+ GtkCssFilterType type)
+{
+ switch (type)
+ {
+ case GTK_CSS_FILTER_BRIGHTNESS:
+ filter->brightness.value = _gtk_css_number_value_new (1, GTK_CSS_NUMBER);
+ break;
+ case GTK_CSS_FILTER_CONTRAST:
+ filter->contrast.value = _gtk_css_number_value_new (1, GTK_CSS_NUMBER);
+ break;
+ case GTK_CSS_FILTER_GRAYSCALE:
+ filter->grayscale.value = _gtk_css_number_value_new (0, GTK_CSS_NUMBER);
+ break;
+ case GTK_CSS_FILTER_HUE_ROTATE:
+ filter->hue_rotate.value = _gtk_css_number_value_new (0, GTK_CSS_DEG);
+ break;
+ case GTK_CSS_FILTER_INVERT:
+ filter->invert.value = _gtk_css_number_value_new (0, GTK_CSS_NUMBER);
+ break;
+ case GTK_CSS_FILTER_OPACITY:
+ filter->opacity.value = _gtk_css_number_value_new (1, GTK_CSS_NUMBER);
+ break;
+ case GTK_CSS_FILTER_SATURATE:
+ filter->saturate.value = _gtk_css_number_value_new (1, GTK_CSS_NUMBER);
+ break;
+ case GTK_CSS_FILTER_SEPIA:
+ filter->sepia.value = _gtk_css_number_value_new (0, GTK_CSS_NUMBER);
+ break;
+ case GTK_CSS_FILTER_NONE:
+ case GTK_CSS_FILTER_BLUR:
+ case GTK_CSS_FILTER_DROP_SHADOW:
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ filter->type = type;
+}
+
+#define R 0.2126
+#define G 0.7152
+#define B 0.0722
+
+static void
+gtk_css_filter_get_matrix (const GtkCssFilter *filter,
+ graphene_matrix_t *matrix,
+ graphene_vec4_t *offset)
+{
+ double value;
+
+ switch (filter->type)
+ {
+ case GTK_CSS_FILTER_BRIGHTNESS:
+ value = _gtk_css_number_value_get (filter->brightness.value, 1.0);
+ graphene_matrix_init_scale (matrix, value, value, value);
+ graphene_vec4_init (offset, 0.0, 0.0, 0.0, 0.0);
+ break;
+
+ case GTK_CSS_FILTER_CONTRAST:
+ value = _gtk_css_number_value_get (filter->contrast.value, 1.0);
+ graphene_matrix_init_scale (matrix, value, value, value);
+ graphene_vec4_init (offset, 0.5 - 0.5 * value, 0.5 - 0.5 * value, 0.5 - 0.5 * value, 0.0);
+ break;
+
+ case GTK_CSS_FILTER_GRAYSCALE:
+ value = _gtk_css_number_value_get (filter->grayscale.value, 1.0);
+ graphene_matrix_init_from_float (matrix, (float[16]) {
+ 1.0 - (1.0 - R) * value, R * value, R * value, 0.0,
+ G * value, 1.0 - (1.0 - G) * value, G * value, 0.0,
+ B * value, B * value, 1.0 - (1.0 - B) * value, 0.0,
+ 0.0, 0.0, 0.0, 1.0
+ });
+ graphene_vec4_init (offset, 0.0, 0.0, 0.0, 0.0);
+ break;
+
+ case GTK_CSS_FILTER_HUE_ROTATE:
+ {
+ double c, s;
+ value = _gtk_css_number_value_get (filter->grayscale.value, 1.0) * G_PI / 180.0;
+ c = cos (value);
+ s = sin (value);
+ graphene_matrix_init_from_float (matrix, (float[16]) {
+ 0.213 + 0.787 * c - 0.213 * s,
+ 0.213 - 0.213 * c + 0.143 * s,
+ 0.213 - 0.213 * c - 0.787 * s,
+ 0,
+ 0.715 - 0.715 * c - 0.715 * s,
+ 0.715 + 0.285 * c + 0.140 * s,
+ 0.715 - 0.715 * c + 0.715 * s,
+ 0,
+ 0.072 - 0.072 * c + 0.928 * s,
+ 0.072 - 0.072 * c - 0.283 * s,
+ 0.072 + 0.928 * c + 0.072 * s,
+ 0,
+ 0, 0, 0, 1
+ });
+ graphene_vec4_init (offset, 0.0, 0.0, 0.0, 0.0);
+ }
+ break;
+
+ case GTK_CSS_FILTER_INVERT:
+ value = _gtk_css_number_value_get (filter->invert.value, 1.0);
+ graphene_matrix_init_scale (matrix, 1.0 - 2 * value, 1.0 - 2 * value, 1.0 - 2 * value);
+ graphene_vec4_init (offset, value, value, value, 0.0);
+ break;
+
+ case GTK_CSS_FILTER_OPACITY:
+ value = _gtk_css_number_value_get (filter->invert.value, 1.0);
+ graphene_matrix_init_from_float (matrix, (float[16]) {
+ 1.0, 0.0, 0.0, 0.0,
+ 0.0, 1.0, 0.0, 0.0,
+ 0.0, 0.0, 1.0, 0.0,
+ 0.0, 0.0, 0.0, value
+ });
+ graphene_vec4_init (offset, 0.0, 0.0, 0.0, 0.0);
+ break;
+
+ case GTK_CSS_FILTER_SATURATE:
+ value = _gtk_css_number_value_get (filter->saturate.value, 1.0);
+ graphene_matrix_init_from_float (matrix, (float[16]) {
+ R + (1.0 - R) * value, R - R * value, R - R * value, 0.0,
+ G - G * value, G + (1.0 - G) * value, G - G * value, 0.0,
+ B - B * value, B - B * value, B + (1.0 - B) * value, 0.0,
+ 0.0, 0.0, 0.0, 1.0
+ });
+ graphene_vec4_init (offset, 0.0, 0.0, 0.0, 0.0);
+ break;
+
+ case GTK_CSS_FILTER_SEPIA:
+ value = _gtk_css_number_value_get (filter->sepia.value, 1.0);
+ graphene_matrix_init_from_float (matrix, (float[16]) {
+ 1.0 - 0.607 * value, 0.349 * value, 0.272 * value, 0.0,
+ 0.769 * value, 1.0 - 0.314 * value, 0.534 * value, 0.0,
+ 0.189 * value, 0.168 * value, 1.0 - 0.869 * value, 0.0,
+ 0.0, 0.0, 0.0, 1.0
+ });
+ graphene_vec4_init (offset, 0.0, 0.0, 0.0, 0.0);
+ break;
+
+ case GTK_CSS_FILTER_NONE:
+ case GTK_CSS_FILTER_BLUR:
+ case GTK_CSS_FILTER_DROP_SHADOW:
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+}
+
+#undef R
+#undef G
+#undef B
+
+static void
+gtk_css_filter_value_compute_matrix (const GtkCssValue *value,
+ graphene_matrix_t *matrix,
+ graphene_vec4_t *offset)
+{
+ graphene_matrix_t m, m2;
+ graphene_vec4_t o, o2;
+ guint i;
+
+ gtk_css_filter_get_matrix (&value->filters[0], matrix, offset);
+
+ for (i = 1; i < value->n_filters; i++)
+ {
+ gtk_css_filter_get_matrix (&value->filters[i], &m, &o);
+
+ graphene_matrix_multiply (matrix, &m, &m2);
+ graphene_matrix_transform_vec4 (&m, offset, &o2);
+
+ graphene_matrix_init_from_matrix (matrix, &m2);
+ graphene_vec4_add (&o, &o2, offset);
+ }
+}
+
+static void
+gtk_css_value_filter_free (GtkCssValue *value)
+{
+ guint i;
+
+ for (i = 0; i < value->n_filters; i++)
+ {
+ gtk_css_filter_clear (&value->filters[i]);
+ }
+
+ g_slice_free1 (sizeof (GtkCssValue) + sizeof (GtkCssFilter) * (value->n_filters - 1), value);
+}
+
+/* returns TRUE if dest == src */
+static gboolean
+gtk_css_filter_compute (GtkCssFilter *dest,
+ GtkCssFilter *src,
+ guint property_id,
+ GtkStyleProviderPrivate *provider,
+ GtkCssStyle *style,
+ GtkCssStyle *parent_style)
+{
+ dest->type = src->type;
+
+ switch (src->type)
+ {
+ case GTK_CSS_FILTER_BRIGHTNESS:
+ dest->brightness.value = _gtk_css_value_compute (src->brightness.value, property_id, provider, style, parent_style);
+ return dest->brightness.value == src->brightness.value;
+
+ case GTK_CSS_FILTER_CONTRAST:
+ dest->contrast.value = _gtk_css_value_compute (src->contrast.value, property_id, provider, style, parent_style);
+ return dest->contrast.value == src->contrast.value;
+
+ case GTK_CSS_FILTER_GRAYSCALE:
+ dest->grayscale.value = _gtk_css_value_compute (src->grayscale.value, property_id, provider, style, parent_style);
+ return dest->grayscale.value == src->grayscale.value;
+
+ case GTK_CSS_FILTER_HUE_ROTATE:
+ dest->hue_rotate.value = _gtk_css_value_compute (src->hue_rotate.value, property_id, provider, style, parent_style);
+ return dest->hue_rotate.value == src->hue_rotate.value;
+
+ case GTK_CSS_FILTER_INVERT:
+ dest->invert.value = _gtk_css_value_compute (src->invert.value, property_id, provider, style, parent_style);
+ return dest->invert.value == src->invert.value;
+
+ case GTK_CSS_FILTER_OPACITY:
+ dest->opacity.value = _gtk_css_value_compute (src->opacity.value, property_id, provider, style, parent_style);
+ return dest->opacity.value == src->opacity.value;
+
+ case GTK_CSS_FILTER_SATURATE:
+ dest->saturate.value = _gtk_css_value_compute (src->saturate.value, property_id, provider, style, parent_style);
+ return dest->saturate.value == src->saturate.value;
+
+ case GTK_CSS_FILTER_SEPIA:
+ dest->sepia.value = _gtk_css_value_compute (src->sepia.value, property_id, provider, style, parent_style);
+ return dest->sepia.value == src->sepia.value;
+
+ case GTK_CSS_FILTER_NONE:
+ case GTK_CSS_FILTER_BLUR:
+ case GTK_CSS_FILTER_DROP_SHADOW:
+ default:
+ g_assert_not_reached ();
+ return FALSE;
+ }
+}
+
+static GtkCssValue *
+gtk_css_value_filter_compute (GtkCssValue *value,
+ guint property_id,
+ GtkStyleProviderPrivate *provider,
+ GtkCssStyle *style,
+ GtkCssStyle *parent_style)
+{
+ GtkCssValue *result;
+ gboolean changes;
+ guint i;
+
+ /* Special case the 99% case of "none" */
+ if (gtk_css_filter_value_is_none (value))
+ return _gtk_css_value_ref (value);
+
+ changes = FALSE;
+ result = gtk_css_filter_value_alloc (value->n_filters);
+
+ for (i = 0; i < value->n_filters; i++)
+ {
+ changes |= !gtk_css_filter_compute (&result->filters[i],
+ &value->filters[i],
+ property_id,
+ provider,
+ style,
+ parent_style);
+ }
+
+ if (!changes)
+ {
+ _gtk_css_value_unref (result);
+ result = _gtk_css_value_ref (value);
+ }
+
+ return result;
+}
+
+static gboolean
+gtk_css_filter_equal (const GtkCssFilter *filter1,
+ const GtkCssFilter *filter2)
+{
+ if (filter1->type != filter2->type)
+ return FALSE;
+
+ switch (filter1->type)
+ {
+ case GTK_CSS_FILTER_BRIGHTNESS:
+ return _gtk_css_value_equal (filter1->brightness.value, filter2->brightness.value);
+
+ case GTK_CSS_FILTER_CONTRAST:
+ return _gtk_css_value_equal (filter1->contrast.value, filter2->contrast.value);
+
+ case GTK_CSS_FILTER_GRAYSCALE:
+ return _gtk_css_value_equal (filter1->grayscale.value, filter2->grayscale.value);
+
+ case GTK_CSS_FILTER_HUE_ROTATE:
+ return _gtk_css_value_equal (filter1->hue_rotate.value, filter2->hue_rotate.value);
+
+ case GTK_CSS_FILTER_INVERT:
+ return _gtk_css_value_equal (filter1->invert.value, filter2->invert.value);
+
+ case GTK_CSS_FILTER_OPACITY:
+ return _gtk_css_value_equal (filter1->opacity.value, filter2->opacity.value);
+
+ case GTK_CSS_FILTER_SATURATE:
+ return _gtk_css_value_equal (filter1->saturate.value, filter2->saturate.value);
+
+ case GTK_CSS_FILTER_SEPIA:
+ return _gtk_css_value_equal (filter1->sepia.value, filter2->sepia.value);
+
+ case GTK_CSS_FILTER_NONE:
+ case GTK_CSS_FILTER_BLUR:
+ case GTK_CSS_FILTER_DROP_SHADOW:
+ default:
+ g_assert_not_reached ();
+ return FALSE;
+ }
+}
+
+static gboolean
+gtk_css_value_filter_equal (const GtkCssValue *value1,
+ const GtkCssValue *value2)
+{
+ const GtkCssValue *larger;
+ guint i, n;
+
+ n = MIN (value1->n_filters, value2->n_filters);
+ for (i = 0; i < n; i++)
+ {
+ if (!gtk_css_filter_equal (&value1->filters[i], &value2->filters[i]))
+ return FALSE;
+ }
+
+ larger = value1->n_filters > value2->n_filters ? value1 : value2;
+
+ for (; i < larger->n_filters; i++)
+ {
+ GtkCssFilter filter;
+
+ gtk_css_filter_init_identity (&filter, larger->filters[i].type);
+
+ if (!gtk_css_filter_equal (&larger->filters[i], &filter))
+ {
+ gtk_css_filter_clear (&filter);
+ return FALSE;
+ }
+
+ gtk_css_filter_clear (&filter);
+ }
+
+ return TRUE;
+}
+
+static void
+gtk_css_filter_transition (GtkCssFilter *result,
+ const GtkCssFilter *start,
+ const GtkCssFilter *end,
+ guint property_id,
+ double progress)
+{
+ result->type = start->type;
+
+ switch (start->type)
+ {
+ case GTK_CSS_FILTER_BRIGHTNESS:
+ result->brightness.value = _gtk_css_value_transition (start->brightness.value, end->brightness.value, property_id, progress);
+ break;
+
+ case GTK_CSS_FILTER_CONTRAST:
+ result->contrast.value = _gtk_css_value_transition (start->contrast.value, end->contrast.value, property_id, progress);
+ break;
+
+ case GTK_CSS_FILTER_GRAYSCALE:
+ result->grayscale.value = _gtk_css_value_transition (start->grayscale.value, end->grayscale.value, property_id, progress);
+ break;
+
+ case GTK_CSS_FILTER_HUE_ROTATE:
+ result->hue_rotate.value = _gtk_css_value_transition (start->hue_rotate.value, end->hue_rotate.value, property_id, progress);
+ break;
+
+ case GTK_CSS_FILTER_INVERT:
+ result->invert.value = _gtk_css_value_transition (start->invert.value, end->invert.value, property_id, progress);
+ break;
+
+ case GTK_CSS_FILTER_OPACITY:
+ result->opacity.value = _gtk_css_value_transition (start->opacity.value, end->opacity.value, property_id, progress);
+ break;
+
+ case GTK_CSS_FILTER_SATURATE:
+ result->saturate.value = _gtk_css_value_transition (start->saturate.value, end->saturate.value, property_id, progress);
+ break;
+
+ case GTK_CSS_FILTER_SEPIA:
+ result->sepia.value = _gtk_css_value_transition (start->sepia.value, end->sepia.value, property_id, progress);
+ break;
+
+ case GTK_CSS_FILTER_NONE:
+ case GTK_CSS_FILTER_BLUR:
+ case GTK_CSS_FILTER_DROP_SHADOW:
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+}
+
+static GtkCssValue *
+gtk_css_value_filter_transition (GtkCssValue *start,
+ GtkCssValue *end,
+ guint property_id,
+ double progress)
+{
+ GtkCssValue *result;
+ guint i, n;
+
+ if (gtk_css_filter_value_is_none (start))
+ {
+ if (gtk_css_filter_value_is_none (end))
+ return _gtk_css_value_ref (start);
+
+ n = 0;
+ }
+ else if (gtk_css_filter_value_is_none (end))
+ {
+ n = 0;
+ }
+ else
+ {
+ n = MIN (start->n_filters, end->n_filters);
+ }
+
+ /* Check filters are compatible. If not, transition between
+ * their result matrices.
+ */
+ for (i = 0; i < n; i++)
+ {
+ if (start->filters[i].type != end->filters[i].type)
+ {
+ /* XXX: can we improve this? */
+ return NULL;
+ }
+ }
+
+ result = gtk_css_filter_value_alloc (MAX (start->n_filters, end->n_filters));
+
+ for (i = 0; i < n; i++)
+ {
+ gtk_css_filter_transition (&result->filters[i],
+ &start->filters[i],
+ &end->filters[i],
+ property_id,
+ progress);
+ }
+
+ for (; i < start->n_filters; i++)
+ {
+ GtkCssFilter filter;
+
+ gtk_css_filter_init_identity (&filter, start->filters[i].type);
+ gtk_css_filter_transition (&result->filters[i],
+ &start->filters[i],
+ &filter,
+ property_id,
+ progress);
+ gtk_css_filter_clear (&filter);
+ }
+ for (; i < end->n_filters; i++)
+ {
+ GtkCssFilter filter;
+
+ gtk_css_filter_init_identity (&filter, end->filters[i].type);
+ gtk_css_filter_transition (&result->filters[i],
+ &filter,
+ &end->filters[i],
+ property_id,
+ progress);
+ gtk_css_filter_clear (&filter);
+ }
+
+ g_assert (i == MAX (start->n_filters, end->n_filters));
+
+ return result;
+}
+
+static void
+gtk_css_filter_print (const GtkCssFilter *filter,
+ GString *string)
+{
+ switch (filter->type)
+ {
+ case GTK_CSS_FILTER_BRIGHTNESS:
+ g_string_append (string, "brightness(");
+ _gtk_css_value_print (filter->brightness.value, string);
+ g_string_append (string, ")");
+ break;
+
+ case GTK_CSS_FILTER_CONTRAST:
+ g_string_append (string, "contrast(");
+ _gtk_css_value_print (filter->contrast.value, string);
+ g_string_append (string, ")");
+ break;
+
+ case GTK_CSS_FILTER_GRAYSCALE:
+ g_string_append (string, "grayscale(");
+ _gtk_css_value_print (filter->grayscale.value, string);
+ g_string_append (string, ")");
+ break;
+
+ case GTK_CSS_FILTER_HUE_ROTATE:
+ g_string_append (string, "hue-rotate(");
+ _gtk_css_value_print (filter->hue_rotate.value, string);
+ g_string_append (string, ")");
+ break;
+
+ case GTK_CSS_FILTER_INVERT:
+ g_string_append (string, "invert(");
+ _gtk_css_value_print (filter->invert.value, string);
+ g_string_append (string, ")");
+ break;
+
+ case GTK_CSS_FILTER_OPACITY:
+ g_string_append (string, "opacity(");
+ _gtk_css_value_print (filter->opacity.value, string);
+ g_string_append (string, ")");
+ break;
+
+ case GTK_CSS_FILTER_SATURATE:
+ g_string_append (string, "saturate(");
+ _gtk_css_value_print (filter->saturate.value, string);
+ g_string_append (string, ")");
+ break;
+
+ case GTK_CSS_FILTER_SEPIA:
+ g_string_append (string, "sepia(");
+ _gtk_css_value_print (filter->sepia.value, string);
+ g_string_append (string, ")");
+ break;
+
+ case GTK_CSS_FILTER_NONE:
+ case GTK_CSS_FILTER_BLUR:
+ case GTK_CSS_FILTER_DROP_SHADOW:
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+}
+
+static void
+gtk_css_value_filter_print (const GtkCssValue *value,
+ GString *string)
+{
+ guint i;
+
+ if (gtk_css_filter_value_is_none (value))
+ {
+ g_string_append (string, "none");
+ return;
+ }
+
+ for (i = 0; i < value->n_filters; i++)
+ {
+ if (i > 0)
+ g_string_append_c (string, ' ');
+
+ gtk_css_filter_print (&value->filters[i], string);
+ }
+}
+
+static const GtkCssValueClass GTK_CSS_VALUE_FILTER = {
+ gtk_css_value_filter_free,
+ gtk_css_value_filter_compute,
+ gtk_css_value_filter_equal,
+ gtk_css_value_filter_transition,
+ gtk_css_value_filter_print
+};
+
+static GtkCssValue none_singleton = { >K_CSS_VALUE_FILTER, 1, 0, { { GTK_CSS_FILTER_NONE } } };
+
+static GtkCssValue *
+gtk_css_filter_value_alloc (guint n_filters)
+{
+ GtkCssValue *result;
+
+ g_return_val_if_fail (n_filters > 0, NULL);
+
+ result = _gtk_css_value_alloc (>K_CSS_VALUE_FILTER, sizeof (GtkCssValue) + sizeof (GtkCssFilter) * (n_filters - 1));
+ result->n_filters = n_filters;
+
+ return result;
+}
+
+GtkCssValue *
+gtk_css_filter_value_new_none (void)
+{
+ return _gtk_css_value_ref (&none_singleton);
+}
+
+static gboolean
+gtk_css_filter_value_is_none (const GtkCssValue *value)
+{
+ return value->n_filters == 0;
+}
+
+static gboolean
+gtk_css_filter_parse (GtkCssFilter *filter,
+ GtkCssParser *parser)
+{
+ if (_gtk_css_parser_try (parser, "brightness(", TRUE))
+ {
+ filter->type = GTK_CSS_FILTER_BRIGHTNESS;
+
+ filter->brightness.value = _gtk_css_number_value_parse (parser, GTK_CSS_PARSE_NUMBER | GTK_CSS_PARSE_PERCENT);
+ if (filter->brightness.value == NULL)
+ return FALSE;
+ }
+ else if (_gtk_css_parser_try (parser, "contrast(", TRUE))
+ {
+ filter->type = GTK_CSS_FILTER_CONTRAST;
+
+ filter->contrast.value = _gtk_css_number_value_parse (parser, GTK_CSS_PARSE_NUMBER | GTK_CSS_PARSE_PERCENT);
+ if (filter->contrast.value == NULL)
+ return FALSE;
+ }
+ else if (_gtk_css_parser_try (parser, "grayscale(", TRUE))
+ {
+ filter->type = GTK_CSS_FILTER_GRAYSCALE;
+
+ filter->grayscale.value = _gtk_css_number_value_parse (parser, GTK_CSS_PARSE_NUMBER | GTK_CSS_PARSE_PERCENT);
+ if (filter->grayscale.value == NULL)
+ return FALSE;
+ }
+ else if (_gtk_css_parser_try (parser, "hue-rotate(", TRUE))
+ {
+ filter->type = GTK_CSS_FILTER_HUE_ROTATE;
+
+ filter->hue_rotate.value = _gtk_css_number_value_parse (parser, GTK_CSS_PARSE_ANGLE);
+ if (filter->hue_rotate.value == NULL)
+ return FALSE;
+ }
+ else if (_gtk_css_parser_try (parser, "invert(", TRUE))
+ {
+ filter->type = GTK_CSS_FILTER_INVERT;
+
+ filter->invert.value = _gtk_css_number_value_parse (parser, GTK_CSS_PARSE_NUMBER | GTK_CSS_PARSE_PERCENT);
+ if (filter->invert.value == NULL)
+ return FALSE;
+ }
+ else if (_gtk_css_parser_try (parser, "opacity(", TRUE))
+ {
+ filter->type = GTK_CSS_FILTER_OPACITY;
+
+ filter->opacity.value = _gtk_css_number_value_parse (parser, GTK_CSS_PARSE_NUMBER | GTK_CSS_PARSE_PERCENT);
+ if (filter->opacity.value == NULL)
+ return FALSE;
+ }
+ else if (_gtk_css_parser_try (parser, "saturate(", TRUE))
+ {
+ filter->type = GTK_CSS_FILTER_SATURATE;
+
+ filter->saturate.value = _gtk_css_number_value_parse (parser, GTK_CSS_PARSE_NUMBER | GTK_CSS_PARSE_PERCENT);
+ if (filter->saturate.value == NULL)
+ return FALSE;
+ }
+ else if (_gtk_css_parser_try (parser, "sepia(", TRUE))
+ {
+ filter->type = GTK_CSS_FILTER_SEPIA;
+
+ filter->sepia.value = _gtk_css_number_value_parse (parser, GTK_CSS_PARSE_NUMBER | GTK_CSS_PARSE_PERCENT);
+ if (filter->sepia.value == NULL)
+ return FALSE;
+ }
+ else
+ {
+ _gtk_css_parser_error (parser, "unknown syntax for filter");
+ return FALSE;
+ }
+
+ if (!_gtk_css_parser_try (parser, ")", TRUE))
+ {
+ gtk_css_filter_clear (filter);
+ _gtk_css_parser_error (parser, "Expected closing ')'");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+GtkCssValue *
+gtk_css_filter_value_parse (GtkCssParser *parser)
+{
+ GtkCssValue *value;
+ GArray *array;
+ guint i;
+
+ if (_gtk_css_parser_try (parser, "none", TRUE))
+ return gtk_css_filter_value_new_none ();
+
+ array = g_array_new (FALSE, FALSE, sizeof (GtkCssFilter));
+
+ do {
+ GtkCssFilter filter;
+
+ if (!gtk_css_filter_parse (&filter, parser))
+ {
+ for (i = 0; i < array->len; i++)
+ {
+ gtk_css_filter_clear (&g_array_index (array, GtkCssFilter, i));
+ }
+ g_array_free (array, TRUE);
+ return NULL;
+ }
+ g_array_append_val (array, filter);
+ } while (!_gtk_css_parser_begins_with (parser, ';'));
+
+ value = gtk_css_filter_value_alloc (array->len);
+ memcpy (value->filters, array->data, sizeof (GtkCssFilter) * array->len);
+
+ g_array_free (array, TRUE);
+
+ return value;
+}
+
+gboolean
+gtk_css_filter_value_get_color_matrix (const GtkCssValue *filter,
+ graphene_matrix_t *matrix,
+ graphene_vec4_t *offset)
+{
+ g_return_val_if_fail (filter->class == >K_CSS_VALUE_FILTER, FALSE);
+ g_return_val_if_fail (matrix != NULL, FALSE);
+
+ gtk_css_filter_value_compute_matrix (filter, matrix, offset);
+
+ return TRUE;
+}
+
+void
+gtk_css_filter_value_push_snapshot (const GtkCssValue *filter,
+ GtkSnapshot *snapshot)
+{
+ graphene_matrix_t matrix;
+ graphene_vec4_t offset;
+
+ if (gtk_css_filter_value_is_none (filter))
+ return;
+
+ gtk_css_filter_value_get_color_matrix (filter, &matrix, &offset);
+
+ gtk_snapshot_push_color_matrix (snapshot,
+ &matrix,
+ &offset,
+ "IconFilter<%u>", filter->n_filters);
+}
+
+void
+gtk_css_filter_value_pop_snapshot (const GtkCssValue *filter,
+ GtkSnapshot *snapshot)
+{
+ if (gtk_css_filter_value_is_none (filter))
+ return;
+
+ gtk_snapshot_pop_and_append (snapshot);
+}
diff --git a/gtk/gtkcssfiltervalueprivate.h b/gtk/gtkcssfiltervalueprivate.h
new file mode 100644
index 0000000000..796ba4255a
--- /dev/null
+++ b/gtk/gtkcssfiltervalueprivate.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright © 2017 Red Hat Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see .
+ *
+ * Authors: Benjamin Otte
+ */
+
+#ifndef __GTK_CSS_FILTER_VALUE_PRIVATE_H__
+#define __GTK_CSS_FILTER_VALUE_PRIVATE_H__
+
+#include "gtkcssparserprivate.h"
+#include "gtkcssvalueprivate.h"
+
+G_BEGIN_DECLS
+
+GtkCssValue * gtk_css_filter_value_new_none (void);
+GtkCssValue * gtk_css_filter_value_parse (GtkCssParser *parser);
+
+gboolean gtk_css_filter_value_get_color_matrix (const GtkCssValue *filter,
+ graphene_matrix_t *matrix,
+ graphene_vec4_t *offset);
+
+void gtk_css_filter_value_push_snapshot (const GtkCssValue *filter,
+ GtkSnapshot *snapshot);
+void gtk_css_filter_value_pop_snapshot (const GtkCssValue *filter,
+ GtkSnapshot *snapshot);
+
+G_END_DECLS
+
+#endif /* __GTK_CSS_FILTER_VALUE_PRIVATE_H__ */
diff --git a/gtk/gtkcssstylepropertyimpl.c b/gtk/gtkcssstylepropertyimpl.c
index 66f0586001..6229d2d5ef 100644
--- a/gtk/gtkcssstylepropertyimpl.c
+++ b/gtk/gtkcssstylepropertyimpl.c
@@ -44,6 +44,7 @@
#include "gtkcsscolorvalueprivate.h"
#include "gtkcsscornervalueprivate.h"
#include "gtkcsseasevalueprivate.h"
+#include "gtkcssfiltervalueprivate.h"
#include "gtkcssiconthemevalueprivate.h"
#include "gtkcssimageprivate.h"
#include "gtkcssimagebuiltinprivate.h"
@@ -562,6 +563,13 @@ transform_value_parse (GtkCssStyleProperty *property,
return _gtk_css_transform_value_parse (parser);
}
+static GtkCssValue *
+filter_value_parse (GtkCssStyleProperty *property,
+ GtkCssParser *parser)
+{
+ return gtk_css_filter_value_parse (parser);
+}
+
static GtkCssValue *
border_spacing_value_parse (GtkCssStyleProperty *property,
GtkCssParser *parser)
@@ -1454,6 +1462,14 @@ _gtk_css_style_property_init_properties (void)
transform_value_parse,
NULL,
_gtk_css_transform_value_new_none ());
+ gtk_css_style_property_register ("-gtk-icon-filter",
+ GTK_CSS_PROPERTY_ICON_FILTER,
+ G_TYPE_NONE,
+ GTK_STYLE_PROPERTY_ANIMATED,
+ GTK_CSS_AFFECTS_ICON | GTK_CSS_AFFECTS_SYMBOLIC_ICON | GTK_CSS_AFFECTS_CLIP,
+ filter_value_parse,
+ NULL,
+ gtk_css_filter_value_new_none ());
gtk_css_style_property_register ("border-spacing",
GTK_CSS_PROPERTY_BORDER_SPACING,
diff --git a/gtk/gtkcsstypesprivate.h b/gtk/gtkcsstypesprivate.h
index 87582e5137..885d9d472b 100644
--- a/gtk/gtkcsstypesprivate.h
+++ b/gtk/gtkcsstypesprivate.h
@@ -208,6 +208,7 @@ enum { /*< skip >*/
GTK_CSS_PROPERTY_ICON_SHADOW,
GTK_CSS_PROPERTY_ICON_STYLE,
GTK_CSS_PROPERTY_ICON_TRANSFORM,
+ GTK_CSS_PROPERTY_ICON_FILTER,
GTK_CSS_PROPERTY_BORDER_SPACING,
GTK_CSS_PROPERTY_MIN_WIDTH,
GTK_CSS_PROPERTY_MIN_HEIGHT,
diff --git a/gtk/gtkrendericon.c b/gtk/gtkrendericon.c
index 15f8397d3c..9cf0b6efba 100644
--- a/gtk/gtkrendericon.c
+++ b/gtk/gtkrendericon.c
@@ -21,6 +21,7 @@
#include "gtkrendericonprivate.h"
+#include "gtkcssfiltervalueprivate.h"
#include "gtkcssimagebuiltinprivate.h"
#include "gtkcssimagevalueprivate.h"
#include "gtkcssshadowsvalueprivate.h"
@@ -95,7 +96,7 @@ gtk_css_style_snapshot_icon (GtkCssStyle *style,
double height,
GtkCssImageBuiltinType builtin_type)
{
- const GtkCssValue *shadows_value, *transform;
+ const GtkCssValue *shadows_value, *transform_value, *filter_value;
graphene_matrix_t transform_matrix;
GtkCssImage *image;
GskShadow *shadows;
@@ -109,11 +110,14 @@ gtk_css_style_snapshot_icon (GtkCssStyle *style,
return;
shadows_value = gtk_css_style_get_value (style, GTK_CSS_PROPERTY_ICON_SHADOW);
- transform = gtk_css_style_get_value (style, GTK_CSS_PROPERTY_ICON_TRANSFORM);
+ transform_value = gtk_css_style_get_value (style, GTK_CSS_PROPERTY_ICON_TRANSFORM);
+ filter_value = gtk_css_style_get_value (style, GTK_CSS_PROPERTY_ICON_FILTER);
- if (!gtk_css_transform_value_get_matrix (transform, &transform_matrix))
+ if (!gtk_css_transform_value_get_matrix (transform_value, &transform_matrix))
return;
+ gtk_css_filter_value_push_snapshot (filter_value, snapshot);
+
shadows = gtk_css_shadows_value_get_shadows (shadows_value, &n_shadows);
if (shadows)
gtk_snapshot_push_shadow (snapshot, shadows, n_shadows, "IconShadow<%zu>", n_shadows);
@@ -144,6 +148,8 @@ gtk_css_style_snapshot_icon (GtkCssStyle *style,
gtk_snapshot_pop_and_append (snapshot);
g_free (shadows);
}
+
+ gtk_css_filter_value_pop_snapshot (filter_value, snapshot);
}
static gboolean
@@ -266,7 +272,7 @@ gtk_css_style_snapshot_icon_texture (GtkCssStyle *style,
GskTexture *texture,
double texture_scale)
{
- const GtkCssValue *shadows_value, *transform;
+ const GtkCssValue *shadows_value, *transform_value, *filter_value;
graphene_matrix_t transform_matrix;
graphene_rect_t bounds;
double width, height;
@@ -279,13 +285,16 @@ gtk_css_style_snapshot_icon_texture (GtkCssStyle *style,
g_return_if_fail (texture_scale > 0);
shadows_value = gtk_css_style_get_value (style, GTK_CSS_PROPERTY_ICON_SHADOW);
- transform = gtk_css_style_get_value (style, GTK_CSS_PROPERTY_ICON_TRANSFORM);
+ transform_value = gtk_css_style_get_value (style, GTK_CSS_PROPERTY_ICON_TRANSFORM);
+ filter_value = gtk_css_style_get_value (style, GTK_CSS_PROPERTY_ICON_FILTER);
width = gsk_texture_get_width (texture) / texture_scale;
height = gsk_texture_get_height (texture) / texture_scale;
- if (!gtk_css_transform_value_get_matrix (transform, &transform_matrix))
+ if (!gtk_css_transform_value_get_matrix (transform_value, &transform_matrix))
return;
+ gtk_css_filter_value_push_snapshot (filter_value, snapshot);
+
shadows = gtk_css_shadows_value_get_shadows (shadows_value, &n_shadows);
if (shadows)
gtk_snapshot_push_shadow (snapshot, shadows, n_shadows, "IconShadow<%zu>", n_shadows);
@@ -321,4 +330,6 @@ gtk_css_style_snapshot_icon_texture (GtkCssStyle *style,
gtk_snapshot_pop_and_append (snapshot);
g_free (shadows);
}
+
+ gtk_css_filter_value_pop_snapshot (filter_value, snapshot);
}