[gtk+/wip/gbsneto/css-blend-modes: 1/3] css: add background-blend-mode support



commit 5f224d8d3c3c1d6f84346a2fbd1ab0fa372a957a
Author: Georges Basile Stavracas Neto <georges stavracas gmail com>
Date:   Fri Jul 1 09:54:23 2016 -0300

    css: add background-blend-mode support
    
    CSS supports blend modes, in which a series of layers are
    merged together according to the given operation or set of
    operations.
    
    Support for blend modes landed on Cairo, which exposes all
    the commons and also the exquisites blend modes available.
    Adding support for blend modes, then, is just a matter of
    using the available Cairo operations.
    
    This patch adds the background-blend-mode CSS enum property,
    and adapts the background rendering code to blend the backgrounds
    using the available blend modes when they're set.

 gtk/gtkcssenumvalue.c         |   61 +++++++++++++++++++++++++++++++++++++++++
 gtk/gtkcssenumvalueprivate.h  |    4 +++
 gtk/gtkcssstylepropertyimpl.c |   28 +++++++++++++++++++
 gtk/gtkcsstypes.c             |   40 +++++++++++++++++++++++++++
 gtk/gtkcsstypesprivate.h      |   22 +++++++++++++++
 gtk/gtkrenderbackground.c     |   39 +++++++++++++++++++++++++-
 6 files changed, 193 insertions(+), 1 deletions(-)
---
diff --git a/gtk/gtkcssenumvalue.c b/gtk/gtkcssenumvalue.c
index fb480fc..276f9dd 100644
--- a/gtk/gtkcssenumvalue.c
+++ b/gtk/gtkcssenumvalue.c
@@ -126,6 +126,67 @@ _gtk_css_border_style_value_get (const GtkCssValue *value)
   return value->value;
 }
 
+/* GtkCssBlendMode */
+
+static const GtkCssValueClass GTK_CSS_VALUE_BLEND_MODE = {
+  gtk_css_value_enum_free,
+  gtk_css_value_enum_compute,
+  gtk_css_value_enum_equal,
+  gtk_css_value_enum_transition,
+  gtk_css_value_enum_print
+};
+
+static GtkCssValue blend_mode_values[] = {
+  { &GTK_CSS_VALUE_BLEND_MODE, 1, GTK_CSS_BLEND_MODE_COLOR, "color" },
+  { &GTK_CSS_VALUE_BLEND_MODE, 1, GTK_CSS_BLEND_MODE_COLOR_BURN, "color-burn" },
+  { &GTK_CSS_VALUE_BLEND_MODE, 1, GTK_CSS_BLEND_MODE_COLOR_DODGE, "color-dodge" },
+  { &GTK_CSS_VALUE_BLEND_MODE, 1, GTK_CSS_BLEND_MODE_DARKEN, "darken" },
+  { &GTK_CSS_VALUE_BLEND_MODE, 1, GTK_CSS_BLEND_MODE_DIFFERENCE, "difference" },
+  { &GTK_CSS_VALUE_BLEND_MODE, 1, GTK_CSS_BLEND_MODE_EXCLUSION, "exclusion" },
+  { &GTK_CSS_VALUE_BLEND_MODE, 1, GTK_CSS_BLEND_MODE_HARD_LIGHT, "hard-light" },
+  { &GTK_CSS_VALUE_BLEND_MODE, 1, GTK_CSS_BLEND_MODE_HUE, "hue" },
+  { &GTK_CSS_VALUE_BLEND_MODE, 1, GTK_CSS_BLEND_MODE_LIGHTEN, "lighten" },
+  { &GTK_CSS_VALUE_BLEND_MODE, 1, GTK_CSS_BLEND_MODE_LUMINOSITY, "luminosity" },
+  { &GTK_CSS_VALUE_BLEND_MODE, 1, GTK_CSS_BLEND_MODE_MULTIPLY, "multiply" },
+  { &GTK_CSS_VALUE_BLEND_MODE, 1, GTK_CSS_BLEND_MODE_NORMAL, "normal" },
+  { &GTK_CSS_VALUE_BLEND_MODE, 1, GTK_CSS_BLEND_MODE_OVERLAY, "overlay" },
+  { &GTK_CSS_VALUE_BLEND_MODE, 1, GTK_CSS_BLEND_MODE_SATURATE, "saturate" },
+  { &GTK_CSS_VALUE_BLEND_MODE, 1, GTK_CSS_BLEND_MODE_SCREEN, "screen" },
+  { &GTK_CSS_VALUE_BLEND_MODE, 1, GTK_CSS_BLEND_MODE_SOFT_LIGHT, "soft-light" }
+};
+
+GtkCssValue *
+_gtk_css_blend_mode_value_new (GtkCssBlendMode blend_mode)
+{
+  g_return_val_if_fail (blend_mode < G_N_ELEMENTS (blend_mode_values), NULL);
+
+  return _gtk_css_value_ref (&blend_mode_values[blend_mode]);
+}
+
+GtkCssValue *
+_gtk_css_blend_mode_value_try_parse (GtkCssParser *parser)
+{
+  guint i;
+
+  g_return_val_if_fail (parser != NULL, NULL);
+
+  for (i = 0; i < G_N_ELEMENTS (blend_mode_values); i++)
+    {
+      if (_gtk_css_parser_try (parser, blend_mode_values[i].name, TRUE))
+        return _gtk_css_value_ref (&blend_mode_values[i]);
+    }
+
+  return NULL;
+}
+
+GtkCssBlendMode
+_gtk_css_blend_mode_value_get (const GtkCssValue *value)
+{
+  g_return_val_if_fail (value->class == &GTK_CSS_VALUE_BLEND_MODE, GTK_CSS_BLEND_MODE_NORMAL);
+
+  return value->value;
+}
+
 /* GtkCssFontSize */
 
 static double
diff --git a/gtk/gtkcssenumvalueprivate.h b/gtk/gtkcssenumvalueprivate.h
index f8b37c2..deb145c 100644
--- a/gtk/gtkcssenumvalueprivate.h
+++ b/gtk/gtkcssenumvalueprivate.h
@@ -27,6 +27,10 @@
 
 G_BEGIN_DECLS
 
+GtkCssValue *   _gtk_css_blend_mode_value_new         (GtkCssBlendMode    blend_mode);
+GtkCssValue *   _gtk_css_blend_mode_value_try_parse   (GtkCssParser      *parser);
+GtkCssBlendMode _gtk_css_blend_mode_value_get         (const GtkCssValue *value);
+
 GtkCssValue *   _gtk_css_border_style_value_new       (GtkBorderStyle     border_style);
 GtkCssValue *   _gtk_css_border_style_value_try_parse (GtkCssParser      *parser);
 GtkBorderStyle  _gtk_css_border_style_value_get       (const GtkCssValue *value);
diff --git a/gtk/gtkcssstylepropertyimpl.c b/gtk/gtkcssstylepropertyimpl.c
index 138c50f..4f191e6 100644
--- a/gtk/gtkcssstylepropertyimpl.c
+++ b/gtk/gtkcssstylepropertyimpl.c
@@ -986,6 +986,24 @@ parse_border_width (GtkCssStyleProperty *property,
 }
 
 static GtkCssValue *
+blend_mode_value_parse_one (GtkCssParser        *parser)
+{
+  GtkCssValue *value = _gtk_css_blend_mode_value_try_parse (parser);
+
+  if (value == NULL)
+    _gtk_css_parser_error (parser, "unknown value for property");
+
+  return value;
+}
+
+static GtkCssValue *
+blend_mode_value_parse (GtkCssStyleProperty *property,
+                        GtkCssParser        *parser)
+{
+  return _gtk_css_array_value_parse (parser, blend_mode_value_parse_one);
+}
+
+static GtkCssValue *
 background_repeat_value_parse_one (GtkCssParser *parser)
 {
   GtkCssValue *value = _gtk_css_background_repeat_value_try_parse (parser);
@@ -1565,6 +1583,16 @@ _gtk_css_style_property_init_properties (void)
                                           background_image_value_assign,
                                           _gtk_css_array_value_new (_gtk_css_image_value_new (NULL)));
 
+  gtk_css_style_property_register        ("background-blend-mode",
+                                          GTK_CSS_PROPERTY_BACKGROUND_BLEND_MODE,
+                                          G_TYPE_NONE,
+                                          0,
+                                          GTK_CSS_AFFECTS_BACKGROUND,
+                                          blend_mode_value_parse,
+                                          NULL,
+                                          NULL,
+                                          _gtk_css_array_value_new (_gtk_css_blend_mode_value_new 
(GTK_CSS_BLEND_MODE_NORMAL)));
+
   gtk_css_style_property_register        ("border-image-source",
                                           GTK_CSS_PROPERTY_BORDER_IMAGE_SOURCE,
                                           CAIRO_GOBJECT_TYPE_PATTERN,
diff --git a/gtk/gtkcsstypes.c b/gtk/gtkcsstypes.c
index 45e6caf..9a701da 100644
--- a/gtk/gtkcsstypes.c
+++ b/gtk/gtkcsstypes.c
@@ -22,6 +22,46 @@
 #include "gtkcssnumbervalueprivate.h"
 #include "gtkstylecontextprivate.h"
 
+cairo_operator_t
+_gtk_css_blend_mode_get_operator (GtkCssBlendMode mode)
+{
+  switch (mode)
+    {
+    case GTK_CSS_BLEND_MODE_COLOR:
+      return CAIRO_OPERATOR_HSL_COLOR;
+    case GTK_CSS_BLEND_MODE_COLOR_BURN:
+      return CAIRO_OPERATOR_COLOR_BURN;
+    case GTK_CSS_BLEND_MODE_COLOR_DODGE:
+      return CAIRO_OPERATOR_COLOR_DODGE;
+    case GTK_CSS_BLEND_MODE_DARKEN:
+      return CAIRO_OPERATOR_DARKEN;
+    case GTK_CSS_BLEND_MODE_DIFFERENCE:
+      return CAIRO_OPERATOR_DIFFERENCE;
+    case GTK_CSS_BLEND_MODE_EXCLUSION:
+      return CAIRO_OPERATOR_EXCLUSION;
+    case GTK_CSS_BLEND_MODE_HARD_LIGHT:
+      return CAIRO_OPERATOR_HARD_LIGHT;
+    case GTK_CSS_BLEND_MODE_HUE:
+      return CAIRO_OPERATOR_HSL_HUE;
+    case GTK_CSS_BLEND_MODE_LIGHTEN:
+      return CAIRO_OPERATOR_LIGHTEN;
+    case GTK_CSS_BLEND_MODE_LUMINOSITY:
+      return CAIRO_OPERATOR_HSL_LUMINOSITY;
+    case GTK_CSS_BLEND_MODE_MULTIPLY:
+      return CAIRO_OPERATOR_MULTIPLY;
+    case GTK_CSS_BLEND_MODE_OVERLAY:
+      return CAIRO_OPERATOR_OVERLAY;
+    case GTK_CSS_BLEND_MODE_SATURATE:
+      return CAIRO_OPERATOR_SATURATE;
+    case GTK_CSS_BLEND_MODE_SCREEN:
+      return CAIRO_OPERATOR_SCREEN;
+
+    case GTK_CSS_BLEND_MODE_NORMAL:
+    default:
+      return CAIRO_OPERATOR_OVER;
+    }
+}
+
 GtkCssChange
 _gtk_css_change_for_sibling (GtkCssChange match)
 {
diff --git a/gtk/gtkcsstypesprivate.h b/gtk/gtkcsstypesprivate.h
index 3f5ce93..59f392a 100644
--- a/gtk/gtkcsstypesprivate.h
+++ b/gtk/gtkcsstypesprivate.h
@@ -198,6 +198,7 @@ enum { /*< skip >*/
   GTK_CSS_PROPERTY_OUTLINE_COLOR,
   GTK_CSS_PROPERTY_BACKGROUND_REPEAT,
   GTK_CSS_PROPERTY_BACKGROUND_IMAGE,
+  GTK_CSS_PROPERTY_BACKGROUND_BLEND_MODE,
   GTK_CSS_PROPERTY_BORDER_IMAGE_SOURCE,
   GTK_CSS_PROPERTY_BORDER_IMAGE_REPEAT,
   GTK_CSS_PROPERTY_BORDER_IMAGE_SLICE,
@@ -231,6 +232,25 @@ enum { /*< skip >*/
 };
 
 typedef enum /*< skip >*/ {
+  GTK_CSS_BLEND_MODE_COLOR,
+  GTK_CSS_BLEND_MODE_COLOR_BURN,
+  GTK_CSS_BLEND_MODE_COLOR_DODGE,
+  GTK_CSS_BLEND_MODE_DARKEN,
+  GTK_CSS_BLEND_MODE_DIFFERENCE,
+  GTK_CSS_BLEND_MODE_EXCLUSION,
+  GTK_CSS_BLEND_MODE_HARD_LIGHT,
+  GTK_CSS_BLEND_MODE_HUE,
+  GTK_CSS_BLEND_MODE_LIGHTEN,
+  GTK_CSS_BLEND_MODE_LUMINOSITY,
+  GTK_CSS_BLEND_MODE_MULTIPLY,
+  GTK_CSS_BLEND_MODE_NORMAL,
+  GTK_CSS_BLEND_MODE_OVERLAY,
+  GTK_CSS_BLEND_MODE_SATURATE,
+  GTK_CSS_BLEND_MODE_SCREEN,
+  GTK_CSS_BLEND_MODE_SOFT_LIGHT
+} GtkCssBlendMode;
+
+typedef enum /*< skip >*/ {
   GTK_CSS_IMAGE_BUILTIN_NONE,
   GTK_CSS_IMAGE_BUILTIN_CHECK,
   GTK_CSS_IMAGE_BUILTIN_CHECK_INCONSISTENT,
@@ -372,6 +392,8 @@ typedef enum /*< skip >*/ {
   GTK_CSS_MS,
 } GtkCssUnit;
 
+cairo_operator_t        _gtk_css_blend_mode_get_operator         (GtkCssBlendMode    mode);
+
 GtkCssChange            _gtk_css_change_for_sibling              (GtkCssChange       match);
 GtkCssChange            _gtk_css_change_for_child                (GtkCssChange       match);
 
diff --git a/gtk/gtkrenderbackground.c b/gtk/gtkrenderbackground.c
index a9a80f1..505dce3 100644
--- a/gtk/gtkrenderbackground.c
+++ b/gtk/gtkrenderbackground.c
@@ -304,10 +304,14 @@ gtk_css_style_render_background (GtkCssStyle      *style,
   GtkThemingBackground bg;
   gint idx;
   GtkCssValue *background_image;
+  GtkCssValue *blend_modes;
   GtkCssValue *box_shadow;
   const GdkRGBA *bg_color;
+  gint number_of_layers;
+  gint number_of_blends;
 
   background_image = gtk_css_style_get_value (style, GTK_CSS_PROPERTY_BACKGROUND_IMAGE);
+  blend_modes = gtk_css_style_get_value (style, GTK_CSS_PROPERTY_BACKGROUND_BLEND_MODE);
   bg_color = _gtk_css_rgba_value_get_rgba (gtk_css_style_get_value (style, 
GTK_CSS_PROPERTY_BACKGROUND_COLOR));
   box_shadow = gtk_css_style_get_value (style, GTK_CSS_PROPERTY_BOX_SHADOW);
 
@@ -332,11 +336,44 @@ gtk_css_style_render_background (GtkCssStyle      *style,
 
   _gtk_theming_background_paint_color (&bg, cr, bg_color, background_image);
 
-  for (idx = _gtk_css_array_value_get_n_values (background_image) - 1; idx >= 0; idx--)
+  number_of_layers = _gtk_css_array_value_get_n_values (background_image);
+  number_of_blends = _gtk_css_array_value_get_n_values (blend_modes);
+
+  for (idx = number_of_layers - 1; idx >= 0; idx--)
     {
+      GtkCssBlendMode blend_mode;
+      cairo_operator_t current_operator;
+
+      current_operator = cairo_get_operator (cr);
+      blend_mode = _gtk_css_blend_mode_value_get (_gtk_css_array_value_get_nth (blend_modes, 
--number_of_blends));
+
+      /*
+       * We only apply a CSS blend mode operator when:
+       *  - The current blend mode is different than the previous blend mode.
+       *  - When there's an actual blend mode to be set, i.e. it's not normal.
+       *  - We already have something drawed in the cairo_t.
+       *
+       * Since we almost never will have a background-blend-mode set, we can
+       * optimize it notifying the compiler about the unlikelyness of this
+       * check.
+       */
+      if (G_UNLIKELY (_gtk_css_blend_mode_get_operator (blend_mode) != current_operator &&
+                      blend_mode != GTK_CSS_BLEND_MODE_NORMAL &&
+                      idx < number_of_layers - 1))
+        {
+          cairo_set_operator (cr, _gtk_css_blend_mode_get_operator (blend_mode));
+        }
+
       _gtk_theming_background_paint_layer (&bg, idx, cr);
     }
 
+  /*
+   * Since this cairo_t can be shared with other widgets,
+   * we must reset the operator after all the backgrounds
+   * are properly rendered.
+   */
+  cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
+
   /* Inset shadows */
   _gtk_css_shadows_value_paint_box (box_shadow,
                                     cr,


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]