[gtk+/wip/otte/rendernode: 110/110] gsk: Add GskInsetShadowNode



commit 227c61507bfd49ba636d95cb3dae81b9746f60fc
Author: Benjamin Otte <otte redhat com>
Date:   Mon Dec 19 05:13:42 2016 +0100

    gsk: Add GskInsetShadowNode
    
    And again lots of shadow code gets copied to GSK. But we're now almost
    at a stage where widget-factory does not use cairo nodes anymore.

 gsk/gskenums.h                         |    2 +
 gsk/gskrendernode.h                    |    8 +
 gsk/gskrendernodeimpl.c                |  471 ++++++++++++++++++++++++++++++++
 gtk/gtkcssshadowvalue.c                |   26 +-
 gtk/inspector/gtktreemodelrendernode.c |    1 +
 gtk/inspector/recorder.c               |    2 +
 6 files changed, 500 insertions(+), 10 deletions(-)
---
diff --git a/gsk/gskenums.h b/gsk/gskenums.h
index 9defa36..1653654 100644
--- a/gsk/gskenums.h
+++ b/gsk/gskenums.h
@@ -33,6 +33,7 @@
  *     linear gradient
  * @GSK_BORDER_NODE: A node stroking a border around an area
  * @GSK_TEXTURE_NODE: A node drawing a #GskTexture
+ * @GSK_INSET_SHADOW_NODE: A node drawing an inset shadow
  * @GSK_TRANSFORM_NODE: A node that renders its child after applying a
  *     matrix transform
  * @GSK_OPACITY_NODE: A node that changes the opacity of its child
@@ -55,6 +56,7 @@ typedef enum {
   GSK_REPEATING_LINEAR_GRADIENT_NODE,
   GSK_BORDER_NODE,
   GSK_TEXTURE_NODE,
+  GSK_INSET_SHADOW_NODE,
   GSK_TRANSFORM_NODE,
   GSK_OPACITY_NODE,
   GSK_CLIP_NODE,
diff --git a/gsk/gskrendernode.h b/gsk/gskrendernode.h
index 1c46207..6fc8724 100644
--- a/gsk/gskrendernode.h
+++ b/gsk/gskrendernode.h
@@ -89,6 +89,14 @@ GskRenderNode *         gsk_border_node_new                     (const GskRounde
                                                                  const GdkRGBA             border_color[4]);
 
 GDK_AVAILABLE_IN_3_90
+GskRenderNode *         gsk_inset_shadow_node_new               (const GskRoundedRect     *outline,
+                                                                 const GdkRGBA            *color,
+                                                                 float                     dx,
+                                                                 float                     dy,
+                                                                 float                     spread,
+                                                                 float                     blur_radius);
+
+GDK_AVAILABLE_IN_3_90
 GskRenderNode *         gsk_cairo_node_new                      (const graphene_rect_t    *bounds);
 GDK_AVAILABLE_IN_3_90
 cairo_t *               gsk_cairo_node_get_draw_context         (GskRenderNode            *node,
diff --git a/gsk/gskrendernodeimpl.c b/gsk/gskrendernodeimpl.c
index a7453c6..2f68a11 100644
--- a/gsk/gskrendernodeimpl.c
+++ b/gsk/gskrendernodeimpl.c
@@ -565,6 +565,477 @@ gsk_texture_node_new (GskTexture            *texture,
   return &self->render_node;
 }
 
+/*** GSK_INSET_SHADOW_NODE ***/
+
+typedef struct _GskInsetShadowNode GskInsetShadowNode;
+
+struct _GskInsetShadowNode
+{
+  GskRenderNode render_node;
+
+  GskRoundedRect outline;
+  GdkRGBA color;
+  float dx;
+  float dy;
+  float spread;
+  float blur_radius;
+};
+
+static void
+gsk_inset_shadow_node_finalize (GskRenderNode *node)
+{
+}
+
+static void
+gsk_inset_shadow_node_make_immutable (GskRenderNode *node)
+{
+}
+
+static gboolean
+has_empty_clip (cairo_t *cr)
+{
+  double x1, y1, x2, y2;
+
+  cairo_clip_extents (cr, &x1, &y1, &x2, &y2);
+  return x1 == x2 && y1 == y2;
+}
+
+static void
+draw_shadow (cairo_t             *cr,
+             gboolean             inset,
+            GskRoundedRect      *box,
+            GskRoundedRect      *clip_box,
+             float                radius,
+             const GdkRGBA       *color,
+            GskBlurFlags         blur_flags)
+{
+  cairo_t *shadow_cr;
+  gboolean do_blur;
+
+  if (has_empty_clip (cr))
+    return;
+
+  gdk_cairo_set_source_rgba (cr, color);
+  do_blur = (blur_flags & (GSK_BLUR_X | GSK_BLUR_Y)) != 0;
+  if (do_blur)
+    shadow_cr = gsk_cairo_blur_start_drawing (cr, radius, blur_flags);
+  else
+    shadow_cr = cr;
+
+  cairo_set_fill_rule (shadow_cr, CAIRO_FILL_RULE_EVEN_ODD);
+  gsk_rounded_rect_path (box, shadow_cr);
+  if (inset)
+    cairo_rectangle (cr,
+                     clip_box->bounds.origin.x, clip_box->bounds.origin.y,
+                     clip_box->bounds.size.width, clip_box->bounds.size.height);
+
+  cairo_fill (shadow_cr);
+
+  if (do_blur)
+    gsk_cairo_blur_finish_drawing (shadow_cr, radius, color, blur_flags);
+}
+
+typedef struct {
+  float radius;
+  graphene_size_t corner;
+} CornerMask;
+
+typedef enum {
+  TOP,
+  RIGHT,
+  BOTTOM,
+  LEFT
+} Side;
+
+static guint
+corner_mask_hash (CornerMask *mask)
+{
+  return ((guint)mask->radius << 24) ^
+    ((guint)(mask->corner.width*4)) << 12 ^
+    ((guint)(mask->corner.height*4)) << 0;
+}
+
+static gboolean
+corner_mask_equal (CornerMask *mask1,
+                   CornerMask *mask2)
+{
+  return
+    mask1->radius == mask2->radius &&
+    mask1->corner.width == mask2->corner.width &&
+    mask1->corner.height == mask2->corner.height;
+}
+
+static void
+draw_shadow_corner (cairo_t               *cr,
+                    gboolean               inset,
+                    GskRoundedRect        *box,
+                    GskRoundedRect        *clip_box,
+                    float                  radius,
+                    const GdkRGBA         *color,
+                    GskCorner              corner,
+                    cairo_rectangle_int_t *drawn_rect)
+{
+  float clip_radius;
+  int x1, x2, x3, y1, y2, y3, x, y;
+  GskRoundedRect corner_box;
+  cairo_t *mask_cr;
+  cairo_surface_t *mask;
+  cairo_pattern_t *pattern;
+  cairo_matrix_t matrix;
+  float sx, sy;
+  static GHashTable *corner_mask_cache = NULL;
+  float max_other;
+  CornerMask key;
+  gboolean overlapped;
+
+  clip_radius = gsk_cairo_blur_compute_pixels (radius);
+
+  overlapped = FALSE;
+  if (corner == GSK_CORNER_TOP_LEFT || corner == GSK_CORNER_BOTTOM_LEFT)
+    {
+      x1 = floor (box->bounds.origin.x - clip_radius);
+      x2 = ceil (box->bounds.origin.x + box->corner[corner].width + clip_radius);
+      x = x1;
+      sx = 1;
+      max_other = MAX(box->corner[GSK_CORNER_TOP_RIGHT].width, box->corner[GSK_CORNER_BOTTOM_RIGHT].width);
+      x3 = floor (box->bounds.origin.x + box->bounds.size.width - max_other - clip_radius);
+      if (x2 > x3)
+        overlapped = TRUE;
+    }
+  else
+    {
+      x1 = floor (box->bounds.origin.x + box->bounds.size.width - box->corner[corner].width - clip_radius);
+      x2 = ceil (box->bounds.origin.x + box->bounds.size.width + clip_radius);
+      x = x2;
+      sx = -1;
+      max_other = MAX(box->corner[GSK_CORNER_TOP_LEFT].width, box->corner[GSK_CORNER_BOTTOM_LEFT].width);
+      x3 = ceil (box->bounds.origin.x + max_other + clip_radius);
+      if (x3 > x1)
+        overlapped = TRUE;
+    }
+
+  if (corner == GSK_CORNER_TOP_LEFT || corner == GSK_CORNER_TOP_RIGHT)
+    {
+      y1 = floor (box->bounds.origin.y - clip_radius);
+      y2 = ceil (box->bounds.origin.y + box->corner[corner].height + clip_radius);
+      y = y1;
+      sy = 1;
+      max_other = MAX(box->corner[GSK_CORNER_BOTTOM_LEFT].height, 
box->corner[GSK_CORNER_BOTTOM_RIGHT].height);
+      y3 = floor (box->bounds.origin.y + box->bounds.size.height - max_other - clip_radius);
+      if (y2 > y3)
+        overlapped = TRUE;
+    }
+  else
+    {
+      y1 = floor (box->bounds.origin.y + box->bounds.size.height - box->corner[corner].height - clip_radius);
+      y2 = ceil (box->bounds.origin.y + box->bounds.size.height + clip_radius);
+      y = y2;
+      sy = -1;
+      max_other = MAX(box->corner[GSK_CORNER_TOP_LEFT].height, box->corner[GSK_CORNER_TOP_RIGHT].height);
+      y3 = ceil (box->bounds.origin.y + max_other + clip_radius);
+      if (y3 > y1)
+        overlapped = TRUE;
+    }
+
+  drawn_rect->x = x1;
+  drawn_rect->y = y1;
+  drawn_rect->width = x2 - x1;
+  drawn_rect->height = y2 - y1;
+
+  cairo_rectangle (cr, x1, y1, x2 - x1, y2 - y1);
+  cairo_clip (cr);
+
+  if (inset || overlapped)
+    {
+      /* Fall back to generic path if inset or if the corner radius
+         runs into each other */
+      draw_shadow (cr, inset, box, clip_box, radius, color, GSK_BLUR_X | GSK_BLUR_Y);
+      return;
+    }
+
+  if (has_empty_clip (cr))
+    return;
+
+  /* At this point we're drawing a blurred outset corner. The only
+   * things that affect the output of the blurred mask in this case
+   * is:
+   *
+   * What corner this is, which defines the orientation (sx,sy)
+   * and position (x,y)
+   *
+   * The blur radius (which also defines the clip_radius)
+   *
+   * The the horizontal and vertical corner radius
+   *
+   * We apply the first position and orientation when drawing the
+   * mask, so we cache rendered masks based on the blur radius and the
+   * corner radius.
+   */
+  if (corner_mask_cache == NULL)
+    corner_mask_cache = g_hash_table_new_full ((GHashFunc)corner_mask_hash,
+                                               (GEqualFunc)corner_mask_equal,
+                                               g_free, (GDestroyNotify)cairo_surface_destroy);
+
+  key.radius = radius;
+  key.corner = box->corner[corner];
+
+  mask = g_hash_table_lookup (corner_mask_cache, &key);
+  if (mask == NULL)
+    {
+      mask = cairo_surface_create_similar_image (cairo_get_target (cr), CAIRO_FORMAT_A8,
+                                                 drawn_rect->width + clip_radius,
+                                                 drawn_rect->height + clip_radius);
+      mask_cr = cairo_create (mask);
+      gsk_rounded_rect_init_from_rect (&corner_box, &GRAPHENE_RECT_INIT (clip_radius, clip_radius, 
2*drawn_rect->width, 2*drawn_rect->height), 0);
+      corner_box.corner[0] = box->corner[corner];
+      gsk_rounded_rect_path (&corner_box, mask_cr);
+      cairo_fill (mask_cr);
+      gsk_cairo_blur_surface (mask, radius, GSK_BLUR_X | GSK_BLUR_Y);
+      cairo_destroy (mask_cr);
+      g_hash_table_insert (corner_mask_cache, g_memdup (&key, sizeof (key)), mask);
+    }
+
+  gdk_cairo_set_source_rgba (cr, color);
+  pattern = cairo_pattern_create_for_surface (mask);
+  cairo_matrix_init_identity (&matrix);
+  cairo_matrix_scale (&matrix, sx, sy);
+  cairo_matrix_translate (&matrix, -x, -y);
+  cairo_pattern_set_matrix (pattern, &matrix);
+  cairo_mask (cr, pattern);
+  cairo_pattern_destroy (pattern);
+}
+
+static void
+draw_shadow_side (cairo_t               *cr,
+                  gboolean               inset,
+                  GskRoundedRect        *box,
+                  GskRoundedRect        *clip_box,
+                  float                  radius,
+                  const GdkRGBA         *color,
+                  Side                   side,
+                  cairo_rectangle_int_t *drawn_rect)
+{
+  GskBlurFlags blur_flags = GSK_BLUR_REPEAT;
+  gdouble clip_radius;
+  int x1, x2, y1, y2;
+
+  clip_radius = gsk_cairo_blur_compute_pixels (radius);
+
+  if (side == TOP || side == BOTTOM)
+    {
+      blur_flags |= GSK_BLUR_Y;
+      x1 = floor (box->bounds.origin.x - clip_radius);
+      x2 = ceil (box->bounds.origin.x + box->bounds.size.width + clip_radius);
+    }
+  else if (side == LEFT)
+    {
+      x1 = floor (box->bounds.origin.x -clip_radius);
+      x2 = ceil (box->bounds.origin.x + clip_radius);
+    }
+  else
+    {
+      x1 = floor (box->bounds.origin.x + box->bounds.size.width -clip_radius);
+      x2 = ceil (box->bounds.origin.x + box->bounds.size.width + clip_radius);
+    }
+
+  if (side == LEFT || side == RIGHT)
+    {
+      blur_flags |= GSK_BLUR_X;
+      y1 = floor (box->bounds.origin.y - clip_radius);
+      y2 = ceil (box->bounds.origin.y + box->bounds.size.height + clip_radius);
+    }
+  else if (side == TOP)
+    {
+      y1 = floor (box->bounds.origin.y -clip_radius);
+      y2 = ceil (box->bounds.origin.y + clip_radius);
+    }
+  else
+    {
+      y1 = floor (box->bounds.origin.y + box->bounds.size.height -clip_radius);
+      y2 = ceil (box->bounds.origin.y + box->bounds.size.height + clip_radius);
+    }
+
+  drawn_rect->x = x1;
+  drawn_rect->y = y1;
+  drawn_rect->width = x2 - x1;
+  drawn_rect->height = y2 - y1;
+
+  cairo_rectangle (cr, x1, y1, x2 - x1, y2 - y1);
+  cairo_clip (cr);
+  draw_shadow (cr, inset, box, clip_box, radius, color, blur_flags);
+}
+
+static gboolean
+needs_blur (GskInsetShadowNode *self)
+{
+  /* The code doesn't actually do any blurring for radius 1, as it
+   * ends up with box filter size 1 */
+  if (self->blur_radius <= 1.0)
+    return FALSE;
+
+  return TRUE;
+}
+
+static void
+gsk_inset_shadow_node_draw (GskRenderNode *node,
+                            cairo_t       *cr)
+{
+  GskInsetShadowNode *self = (GskInsetShadowNode *) node;
+  GskRoundedRect box, clip_box;
+  int clip_radius;
+  //double x1c, y1c, x2c, y2c;
+
+  /* We don't need to draw invisible shadows */
+  if (gdk_rgba_is_clear (&self->color))
+    return;
+
+#if 0
+  cairo_clip_extents (cr, &x1c, &y1c, &x2c, &y2c);
+  if (!_gtk_rounded_box_intersects_rectangle (padding_box, x1c, y1c, x2c, y2c))
+    return;
+#endif
+
+  clip_radius = gsk_cairo_blur_compute_pixels (self->blur_radius);
+
+  cairo_save (cr);
+
+  gsk_rounded_rect_path (&self->outline, cr);
+  cairo_clip (cr);
+
+  gsk_rounded_rect_init_copy (&box, &self->outline);
+  gsk_rounded_rect_offset (&box, self->dx, self->dy);
+  gsk_rounded_rect_shrink (&box, self->spread, self->spread, self->spread, self->spread);
+
+  gsk_rounded_rect_init_copy (&clip_box, &self->outline);
+  gsk_rounded_rect_shrink (&clip_box, -clip_radius, -clip_radius, -clip_radius, -clip_radius);
+
+  if (!needs_blur (self))
+    draw_shadow (cr, TRUE, &box, &clip_box, self->blur_radius, &self->color, GSK_BLUR_NONE);
+  else
+    {
+      cairo_region_t *remaining;
+      cairo_rectangle_int_t r;
+      int i;
+
+      /* For the blurred case we divide the rendering into 9 parts,
+       * 4 of the corners, 4 for the horizonat/vertical lines and
+       * one for the interior. We make the non-interior parts
+       * large enought to fit the full radius of the blur, so that
+       * the interior part can be drawn solidly.
+       */
+
+      /* In the inset case we want to paint the whole clip-box.
+       * We could remove the part of "box" where the blur doesn't
+       * reach, but computing that is a bit tricky since the
+       * rounded corners are on the "inside" of it. */
+      r.x = floor (clip_box.bounds.origin.x);
+      r.y = floor (clip_box.bounds.origin.y);
+      r.width = ceil (clip_box.bounds.origin.x + clip_box.bounds.size.width) - r.x;
+      r.height = ceil (clip_box.bounds.origin.y + clip_box.bounds.size.height) - r.y;
+      remaining = cairo_region_create_rectangle (&r);
+
+      /* First do the corners of box */
+      for (i = 0; i < 4; i++)
+       {
+         cairo_save (cr);
+          /* Always clip with remaining to ensure we never draw any area twice */
+          gdk_cairo_region (cr, remaining);
+          cairo_clip (cr);
+         draw_shadow_corner (cr, TRUE, &box, &clip_box, self->blur_radius, &self->color, i, &r);
+         cairo_restore (cr);
+
+         /* We drew the region, remove it from remaining */
+         cairo_region_subtract_rectangle (remaining, &r);
+       }
+
+      /* Then the sides */
+      for (i = 0; i < 4; i++)
+       {
+         cairo_save (cr);
+          /* Always clip with remaining to ensure we never draw any area twice */
+          gdk_cairo_region (cr, remaining);
+          cairo_clip (cr);
+         draw_shadow_side (cr, TRUE, &box, &clip_box, self->blur_radius, &self->color, i, &r);
+         cairo_restore (cr);
+
+         /* We drew the region, remove it from remaining */
+         cairo_region_subtract_rectangle (remaining, &r);
+       }
+
+      /* Then the rest, which needs no blurring */
+
+      cairo_save (cr);
+      gdk_cairo_region (cr, remaining);
+      cairo_clip (cr);
+      draw_shadow (cr, TRUE, &box, &clip_box, self->blur_radius, &self->color, GSK_BLUR_NONE);
+      cairo_restore (cr);
+
+      cairo_region_destroy (remaining);
+    }
+
+  cairo_restore (cr);
+}
+
+static void
+gsk_inset_shadow_node_get_bounds (GskRenderNode   *node,
+                                  graphene_rect_t *bounds)
+{
+  GskInsetShadowNode *self = (GskInsetShadowNode *) node;
+
+  graphene_rect_init_from_rect (bounds, &self->outline.bounds); 
+}
+
+static const GskRenderNodeClass GSK_INSET_SHADOW_NODE_CLASS = {
+  GSK_INSET_SHADOW_NODE,
+  sizeof (GskInsetShadowNode),
+  "GskInsetShadowNode",
+  gsk_inset_shadow_node_finalize,
+  gsk_inset_shadow_node_make_immutable,
+  gsk_inset_shadow_node_draw,
+  gsk_inset_shadow_node_get_bounds
+};
+
+/**
+ * gsk_inset_shadow_node_new:
+ * @outline: outline of the region containing the shadow
+ * @color: color of the shadow
+ * @dx: horizontal offset of shadow
+ * @dy: vertical offset of shadow
+ * @spread: how far the shadow spreads towards the inside
+ * @blur_radius: how much blur to apply to the shadow
+ *
+ * Creates a #GskRenderNode that will render an inset shadow
+ * into the box given by @outline.
+ *
+ * Returns: A new #GskRenderNode
+ *
+ * Since: 3.90
+ */
+GskRenderNode *
+gsk_inset_shadow_node_new (const GskRoundedRect *outline,
+                           const GdkRGBA        *color,
+                           float                 dx,
+                           float                 dy,
+                           float                 spread,
+                           float                 blur_radius)
+{
+  GskInsetShadowNode *self;
+
+  g_return_val_if_fail (outline != NULL, NULL);
+  g_return_val_if_fail (color != NULL, NULL);
+
+  self = (GskInsetShadowNode *) gsk_render_node_new (&GSK_INSET_SHADOW_NODE_CLASS);
+
+  gsk_rounded_rect_init_copy (&self->outline, outline);
+  self->color = *color;
+  self->dx = dx;
+  self->dy = dy;
+  self->spread = spread;
+  self->blur_radius = blur_radius;
+
+  return &self->render_node;
+}
+
 /*** GSK_CAIRO_NODE ***/
 
 typedef struct _GskCairoNode GskCairoNode;
diff --git a/gtk/gtkcssshadowvalue.c b/gtk/gtkcssshadowvalue.c
index f132bfc..91b24bf 100644
--- a/gtk/gtkcssshadowvalue.c
+++ b/gtk/gtkcssshadowvalue.c
@@ -1066,7 +1066,9 @@ gtk_css_shadow_value_snapshot_inset (const GtkCssValue   *shadow,
                                      GtkSnapshot         *snapshot,
                                      const GskRoundedRect*padding_box)
 {
-  cairo_t *cr;
+  GskRoundedRect outline;
+  GskRenderNode *node;
+  double off_x, off_y;
 
   g_return_if_fail (shadow->class == &GTK_CSS_VALUE_SHADOW);
 
@@ -1074,14 +1076,18 @@ gtk_css_shadow_value_snapshot_inset (const GtkCssValue   *shadow,
   if (gdk_rgba_is_clear (_gtk_css_rgba_value_get_rgba (shadow->color)))
     return;
 
-  cr = gtk_snapshot_append_cairo_node (snapshot,
-                                       &GRAPHENE_RECT_INIT (
-                                          padding_box->bounds.origin.x,
-                                          padding_box->bounds.origin.y,
-                                          padding_box->bounds.size.width,
-                                          padding_box->bounds.size.height),
-                                       "Inset Shadow");
-  _gtk_css_shadow_value_paint_box (shadow, cr, padding_box);
-  cairo_destroy (cr);
+  gtk_snapshot_get_offset (snapshot, &off_x, &off_y);
+  gsk_rounded_rect_init_copy (&outline, padding_box);
+  gsk_rounded_rect_offset (&outline, off_x, off_y);
+
+  node = gsk_inset_shadow_node_new (&outline, 
+                                    _gtk_css_rgba_value_get_rgba (shadow->color),
+                                    _gtk_css_number_value_get (shadow->hoffset, 0),
+                                    _gtk_css_number_value_get (shadow->voffset, 0),
+                                    _gtk_css_number_value_get (shadow->spread, 0),
+                                    _gtk_css_number_value_get (shadow->radius, 0));
+  gsk_render_node_set_name (node, "Inset Shadow");
+  gtk_snapshot_append_node (snapshot, node);
+  gsk_render_node_unref (node);
 }
 
diff --git a/gtk/inspector/gtktreemodelrendernode.c b/gtk/inspector/gtktreemodelrendernode.c
index 6064f9c..32177fa 100644
--- a/gtk/inspector/gtktreemodelrendernode.c
+++ b/gtk/inspector/gtktreemodelrendernode.c
@@ -528,6 +528,7 @@ append_node (GtkTreeModelRenderNode *nodemodel,
     case GSK_LINEAR_GRADIENT_NODE:
     case GSK_REPEATING_LINEAR_GRADIENT_NODE:
     case GSK_BORDER_NODE:
+    case GSK_INSET_SHADOW_NODE:
       /* no children */
       break;
 
diff --git a/gtk/inspector/recorder.c b/gtk/inspector/recorder.c
index d1b4117..23f6d86 100644
--- a/gtk/inspector/recorder.c
+++ b/gtk/inspector/recorder.c
@@ -151,6 +151,8 @@ node_type_name (GskRenderNodeType type)
       return "Border";
     case GSK_TEXTURE_NODE:
       return "Texture";
+    case GSK_INSET_SHADOW_NODE:
+      return "Inset Shadow";
     case GSK_TRANSFORM_NODE:
       return "Transform";
     case GSK_OPACITY_NODE:


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