[mutter] MetaShadowFactory: Add the ability to top-fade a shadow



commit 8eb31944a52279c6259c1197e7e5908d07184df9
Author: Owen W. Taylor <otaylor fishsoup net>
Date:   Thu Nov 4 17:02:23 2010 -0400

    MetaShadowFactory: Add the ability to top-fade a shadow
    
    For attached modal dialogs, we want the shadow to fade out at the top
    as if the window was glued to the parent at the top. Add a
    shadow-top-fade property to MetaWindowActor and the corresponding
    parameter to meta_shadow_factory_get_shadow().
    
    The internal implementation of MetaShadow is adjusted to work
    in terms of an "inner border" and "outer border" instead of doing
    the calculations in terms of an aggregate border and the spread
    of the shadow. The old way of doing things gets clumsy when the
    top_fade distance is added in as well.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=592382

 src/compositor/meta-shadow-factory.c |  298 ++++++++++++++++++++++------------
 src/compositor/meta-shadow-factory.h |    3 +-
 src/compositor/meta-window-actor.c   |   31 ++++-
 3 files changed, 227 insertions(+), 105 deletions(-)
---
diff --git a/src/compositor/meta-shadow-factory.c b/src/compositor/meta-shadow-factory.c
index 394de11..30e82b8 100644
--- a/src/compositor/meta-shadow-factory.c
+++ b/src/compositor/meta-shadow-factory.c
@@ -54,6 +54,7 @@ struct _MetaShadowCacheKey
 {
   MetaWindowShape *shape;
   int radius;
+  int top_fade;
 };
 
 struct _MetaShadow
@@ -65,11 +66,20 @@ struct _MetaShadow
   CoglHandle texture;
   CoglHandle material;
 
-  int spread;
-  int border_top;
-  int border_right;
-  int border_bottom;
-  int border_left;
+  /* The outer order is the distance the shadow extends outside the window
+   * shape; the inner border is the unscaled portion inside the window
+   * shape */
+  int outer_border_top;
+  int inner_border_top;
+  int outer_border_right;
+  int inner_border_right;
+  int outer_border_bottom;
+  int inner_border_bottom;
+  int outer_border_left;
+  int inner_border_left;
+
+  guint scale_width : 1;
+  guint scale_height : 1;
 };
 
 struct _MetaShadowFactory
@@ -84,7 +94,7 @@ meta_shadow_cache_key_hash (gconstpointer val)
 {
   const MetaShadowCacheKey *key = val;
 
-  return 59 * key->radius + 67 * meta_window_shape_hash (key->shape);
+  return 59 * key->radius + 67 * key->top_fade + 73 * meta_window_shape_hash (key->shape);
 }
 
 static gboolean
@@ -94,7 +104,7 @@ meta_shadow_cache_key_equal (gconstpointer a,
   const MetaShadowCacheKey *key_a = a;
   const MetaShadowCacheKey *key_b = b;
 
-  return (key_a->radius == key_b->radius &&
+  return (key_a->radius == key_b->radius && key_a->top_fade == key_b->top_fade &&
           meta_window_shape_equal (key_a->shape, key_b->shape));
 }
 
@@ -149,56 +159,73 @@ meta_shadow_paint (MetaShadow *shadow,
   float texture_width = cogl_texture_get_width (shadow->texture);
   float texture_height = cogl_texture_get_height (shadow->texture);
   int i, j;
+  float src_x[4];
+  float src_y[4];
+  float dest_x[4];
+  float dest_y[4];
+  int n_x, n_y;
 
   cogl_material_set_color4ub (shadow->material,
                               opacity, opacity, opacity, opacity);
 
   cogl_set_source (shadow->material);
 
-  if (window_width + 2 * shadow->spread == shadow->border_left &&
-      window_height + 2 * shadow->spread == shadow->border_top)
+  if (shadow->scale_width)
     {
-      /* The non-scaled case - paint with a single rectangle */
-      cogl_rectangle_with_texture_coords (window_x - shadow->spread,
-                                          window_y - shadow->spread,
-                                          window_x + window_width + shadow->spread,
-                                          window_y + window_height + shadow->spread,
-                                          0.0, 0.0, 1.0, 1.0);
+      n_x = 3;
+
+      src_x[0] = 0.0;
+      src_x[1] = (shadow->inner_border_left + shadow->outer_border_left) / texture_width;
+      src_x[2] = (texture_width - (shadow->inner_border_right + shadow->outer_border_right)) / texture_width;
+      src_x[3] = 1.0;
+
+      dest_x[0] = window_x - shadow->outer_border_left;
+      dest_x[1] = window_x + shadow->inner_border_left;
+      dest_x[2] = window_x + window_width - shadow->inner_border_right;
+      dest_x[3] = window_x + window_width + shadow->outer_border_right;
     }
   else
     {
-      float src_x[4];
-      float src_y[4];
-      float dest_x[4];
-      float dest_y[4];
+      n_x = 1;
 
       src_x[0] = 0.0;
-      src_x[1] = shadow->border_left / texture_width;
-      src_x[2] = (texture_width - shadow->border_right) / texture_width;
-      src_x[3] = 1.0;
+      src_x[1] = 1.0;
+
+      dest_x[0] = window_x - shadow->outer_border_left;
+      dest_x[1] = window_x + window_width + shadow->outer_border_right;
+    }
+
+  if (shadow->scale_height)
+    {
+      n_y = 3;
 
       src_y[0] = 0.0;
-      src_y[1] = shadow->border_top / texture_height;
-      src_y[2] = (texture_height - shadow->border_bottom) / texture_height;
+      src_y[1] = (shadow->inner_border_top + shadow->outer_border_top) / texture_height;
+      src_y[2] = (texture_height - (shadow->inner_border_bottom + shadow->outer_border_bottom)) / texture_height;
       src_y[3] = 1.0;
 
-      dest_x[0] = window_x - shadow->spread;
-      dest_x[1] = window_x - shadow->spread + shadow->border_left;
-      dest_x[2] = window_x + window_width + shadow->spread - shadow->border_right;
-      dest_x[3] = window_x + window_width + shadow->spread;
-
-      dest_y[0] = window_y - shadow->spread;
-      dest_y[1] = window_y - shadow->spread + shadow->border_top;
-      dest_y[2] = window_y + window_height + shadow->spread - shadow->border_bottom;
-      dest_y[3] = window_y + window_height + shadow->spread;
-
-      for (j = 0; j < 3; j++)
-        for (i = 0; i < 3; i++)
-          cogl_rectangle_with_texture_coords (dest_x[i], dest_y[j],
-                                              dest_x[i + 1], dest_y[j + 1],
-                                              src_x[i], src_y[j],
-                                              src_x[i + 1], src_y[j + 1]);
+      dest_y[0] = window_y - shadow->outer_border_top;
+      dest_y[1] = window_y + shadow->inner_border_top;
+      dest_y[2] = window_y + window_height - shadow->inner_border_bottom;
+      dest_y[3] = window_y + window_height + shadow->outer_border_bottom;
     }
+  else
+    {
+      n_y = 1;
+
+      src_y[0] = 0.0;
+      src_y[1] = 1.0;
+
+      dest_y[0] = window_y - shadow->outer_border_top;
+      dest_y[1] = window_y + window_height + shadow->outer_border_bottom;
+    }
+
+  for (j = 0; j < n_y; j++)
+    for (i = 0; i < n_x; i++)
+      cogl_rectangle_with_texture_coords (dest_x[i], dest_y[j],
+                                          dest_x[i + 1], dest_y[j + 1],
+                                          src_x[i], src_y[j],
+                                          src_x[i + 1], src_y[j + 1]);
 }
 
 /**
@@ -220,10 +247,10 @@ meta_shadow_get_bounds  (MetaShadow            *shadow,
                          int                    window_height,
                          cairo_rectangle_int_t *bounds)
 {
-  bounds->x = window_x - shadow->spread;
-  bounds->y = window_x - shadow->spread;
-  bounds->width = window_width + 2 * shadow->spread;
-  bounds->height = window_width + 2 * shadow->spread;
+  bounds->x = window_x - shadow->outer_border_left;
+  bounds->y = window_x - shadow->outer_border_top;
+  bounds->width = window_width + shadow->outer_border_left + shadow->outer_border_right;
+  bounds->height = window_height + shadow->outer_border_top + shadow->outer_border_bottom;
 }
 
 MetaShadowFactory *
@@ -411,6 +438,19 @@ blur_rows (cairo_region_t   *convolve_region,
   g_free (tmp_buffer);
 }
 
+static void
+fade_bytes (guchar *bytes,
+            int     width,
+            int     distance,
+            int     total)
+{
+  guint32 multiplier = (distance * 0x10000 + 0x8000) / total;
+  int i;
+
+  for (i = 0; i < width; i++)
+    bytes[i] = (bytes[i] * multiplier) >> 16;
+}
+
 /* Swaps width and height. Either swaps in-place and returns the original
  * buffer or allocates a new buffer, frees the original buffer and returns
  * the new buffer.
@@ -483,25 +523,35 @@ flip_buffer (guchar *buffer,
 #undef BLOCK_SIZE
 }
 
-static CoglHandle
-make_shadow (cairo_region_t *region,
-             int             radius)
+static void
+make_shadow (MetaShadow     *shadow,
+             cairo_region_t *region)
 {
-  int d = get_box_filter_size (radius);
-  int spread = get_shadow_spread (radius);
-  CoglHandle result;
+  int d = get_box_filter_size (shadow->key.radius);
+  int spread = get_shadow_spread (shadow->key.radius);
   cairo_rectangle_int_t extents;
   cairo_region_t *row_convolve_region;
   cairo_region_t *column_convolve_region;
   guchar *buffer;
   int buffer_width;
   int buffer_height;
+  int x_offset;
+  int y_offset;
   int n_rectangles, j, k;
 
   cairo_region_get_extents (region, &extents);
 
-  buffer_width = extents.width +  2 * spread;
-  buffer_height = extents.height +  2 * spread;
+  /* In the case where top_fade >= 0 and the portion above the top
+   * edge of the shape will be cropped, it seems like we could create
+   * a smaller buffer and omit the top portion, but actually, in our
+   * multi-pass blur algorithm, the blur into the area above the window
+   * in the first pass will contribute back to the final pixel values
+   * for the top pixels, so we create a buffer as if we weren't cropping
+   * and only crop when creating the CoglTexture.
+   */
+
+  buffer_width = extents.width + 2 * spread;
+  buffer_height = extents.height + 2 * spread;
 
   /* Round up so we have aligned rows/columns */
   buffer_width = (buffer_width + 3) & ~3;
@@ -521,8 +571,12 @@ make_shadow (cairo_region_t *region,
    * large shadow sizes) we can improve efficiency by restricting the blur
    * to the region that actually needs to be blurred.
    */
-  row_convolve_region = meta_make_border_region (region, spread, 0, FALSE);
-  column_convolve_region = meta_make_border_region (region, spread, spread, TRUE);
+  row_convolve_region = meta_make_border_region (region, spread, spread, FALSE);
+  column_convolve_region = meta_make_border_region (region, 0, spread, TRUE);
+
+  /* Offsets between coordinates of the regions and coordinates in the buffer */
+  x_offset = spread;
+  y_offset = spread;
 
   /* Step 1: unblurred image */
   n_rectangles = cairo_region_num_rectangles (region);
@@ -531,35 +585,53 @@ make_shadow (cairo_region_t *region,
       cairo_rectangle_int_t rect;
 
       cairo_region_get_rectangle (region, k, &rect);
-      for (j = spread + rect.y; j < spread + rect.y + rect.height; j++)
-	memset (buffer + buffer_width * j + spread + rect.x, 255, rect.width);
+      for (j = y_offset + rect.y; j < y_offset + rect.y + rect.height; j++)
+	memset (buffer + buffer_width * j + x_offset + rect.x, 255, rect.width);
     }
 
-  /* Step 2: blur rows */
-  blur_rows (row_convolve_region, spread, spread, buffer, buffer_width, buffer_height, d);
-
   /* Step 2: swap rows and columns */
   buffer = flip_buffer (buffer, buffer_width, buffer_height);
 
   /* Step 3: blur rows (really columns) */
-  blur_rows (column_convolve_region, spread, spread, buffer, buffer_height, buffer_width, d);
+  blur_rows (column_convolve_region, y_offset, x_offset,
+             buffer, buffer_height, buffer_width,
+             d);
 
-  /* Step 3: swap rows and columns */
+  /* Step 4: swap rows and columns */
   buffer = flip_buffer (buffer, buffer_height, buffer_width);
 
-  result = cogl_texture_new_from_data (extents.width +  2 * spread,
-				       extents.height +  2 * spread,
-				       COGL_TEXTURE_NONE,
-				       COGL_PIXEL_FORMAT_A_8,
-				       COGL_PIXEL_FORMAT_ANY,
-				       buffer_width,
-				       buffer);
+  /* Step 5: blur rows */
+  blur_rows (row_convolve_region, x_offset, y_offset,
+             buffer, buffer_width, buffer_height,
+             d);
+
+  /* Step 6: fade out the top, if applicable */
+  if (shadow->key.top_fade >= 0)
+    {
+      for (j = y_offset; j < y_offset + MIN (shadow->key.top_fade, extents.height + shadow->outer_border_bottom); j++)
+        fade_bytes(buffer + j * buffer_width, buffer_width, j - y_offset, shadow->key.top_fade);
+    }
+
+  /* We offset the passed in pixels to crop off the extra area we allocated at the top
+   * in the case of top_fade >= 0. We also account for padding at the left for symmetry
+   * though that doesn't currently occur.
+   */
+  shadow->texture = cogl_texture_new_from_data (shadow->outer_border_left + extents.width + shadow->outer_border_right,
+                                                shadow->outer_border_top + extents.height + shadow->outer_border_bottom,
+                                                COGL_TEXTURE_NONE,
+                                                COGL_PIXEL_FORMAT_A_8,
+                                                COGL_PIXEL_FORMAT_ANY,
+                                                buffer_width,
+                                                (buffer +
+                                                 (y_offset - shadow->outer_border_top) * buffer_width +
+                                                 (x_offset - shadow->outer_border_left)));
 
   cairo_region_destroy (row_convolve_region);
   cairo_region_destroy (column_convolve_region);
   g_free (buffer);
 
-  return result;
+  shadow->material = cogl_material_new ();
+  cogl_material_set_layer (shadow->material, 0, shadow->texture);
 }
 
 /**
@@ -569,6 +641,8 @@ make_shadow (cairo_region_t *region,
  * @width: the actual width of the window's region
  * @width: the actual height of the window's region
  * @radius: the radius (gaussian standard deviation) of the shadow
+ * @top_fade: if >= 0, the shadow doesn't extend above the top
+ *   of the shape, and fades out over the given number of pixels
  *
  * Gets the appropriate shadow object for drawing shadows for the
  * specified window shape. The region that we are shadowing is specified
@@ -584,14 +658,19 @@ meta_shadow_factory_get_shadow (MetaShadowFactory  *factory,
                                 MetaWindowShape    *shape,
                                 int                 width,
                                 int                 height,
-                                int                 radius)
+                                int                 radius,
+                                int                 top_fade)
 {
   MetaShadowCacheKey key;
   MetaShadow *shadow;
   cairo_region_t *region;
   int spread;
-  int border_top, border_right, border_bottom, border_left;
+  int shape_border_top, shape_border_right, shape_border_bottom, shape_border_left;
+  int inner_border_top, inner_border_right, inner_border_bottom, inner_border_left;
+  int outer_border_top, outer_border_right, outer_border_bottom, outer_border_left;
+  gboolean scale_width, scale_height;
   gboolean cacheable;
+  int center_width, center_height;
 
   /* Using a single shadow texture for different window sizes only works
    * when there is a central scaled area that is greater than twice
@@ -612,21 +691,35 @@ meta_shadow_factory_get_shadow (MetaShadowFactory  *factory,
    * policy is to only keep around referenced shadows, there wouldn't
    * be any harm in caching them, it would just make the book-keeping
    * a bit tricker.)
+   *
+   * In the case where we are fading a the top, that also has to fit
+   * within the top unscaled border.
    */
   spread = get_shadow_spread (radius);
   meta_window_shape_get_borders (shape,
-                                 &border_top,
-                                 &border_right,
-                                 &border_bottom,
-                                 &border_left);
-
-  cacheable = (border_top + 2 * spread + border_bottom <= height &&
-               border_left + 2 * spread + border_right <= width);
+                                 &shape_border_top,
+                                 &shape_border_right,
+                                 &shape_border_bottom,
+                                 &shape_border_left);
+
+  inner_border_top = MAX (shape_border_top + spread, top_fade);
+  outer_border_top = top_fade >= 0 ? 0 : spread;
+  inner_border_right = shape_border_right + spread;
+  outer_border_right = spread;
+  inner_border_bottom = shape_border_bottom + spread;
+  outer_border_bottom = spread;
+  inner_border_left = shape_border_left + spread;
+  outer_border_left = spread;
+
+  scale_width = inner_border_left + inner_border_right <= width;
+  scale_height = inner_border_top + inner_border_bottom <= height;
+  cacheable = scale_width && scale_height;
 
   if (cacheable)
     {
       key.shape = shape;
       key.radius = radius;
+      key.top_fade = top_fade;
 
       shadow = g_hash_table_lookup (factory->shadows, &key);
       if (shadow)
@@ -639,35 +732,34 @@ meta_shadow_factory_get_shadow (MetaShadowFactory  *factory,
   shadow->factory = factory;
   shadow->key.shape = meta_window_shape_ref (shape);
   shadow->key.radius = radius;
+  shadow->key.top_fade = top_fade;
+
+  shadow->outer_border_top = outer_border_top;
+  shadow->inner_border_top = inner_border_top;
+  shadow->outer_border_right = outer_border_right;
+  shadow->inner_border_right = inner_border_right;
+  shadow->outer_border_bottom = outer_border_bottom;
+  shadow->inner_border_bottom = inner_border_bottom;
+  shadow->outer_border_left = outer_border_left;
+  shadow->inner_border_left = inner_border_left;
+
+  shadow->scale_width = scale_width;
+  if (scale_width)
+    center_width = inner_border_left + inner_border_right - (shape_border_left + shape_border_right);
+  else
+    center_width = width - (shape_border_left + shape_border_right);
 
-  shadow->spread = spread;
+  shadow->scale_height = scale_height;
+  if (scale_height)
+    center_height = inner_border_top + inner_border_bottom - (shape_border_top + shape_border_bottom);
+  else
+    center_height = height - (shape_border_top + shape_border_bottom);
 
-  if (cacheable)
-    {
-      shadow->border_top = border_top + 2 * spread;
-      shadow->border_right += border_right + 2 * spread;
-      shadow->border_bottom += border_bottom + 2 * spread;
-      shadow->border_left += border_left + 2 * spread;
+  g_assert (center_width >= 0 && center_height >= 0);
 
-      region = meta_window_shape_to_region (shape, 2 * spread, 2 * spread);
-    }
-  else
-    {
-      /* In the non-scaled case, we put the entire shadow into the
-       * upper-left-hand corner of the 9-slice */
-      shadow->border_top = height + 2 * spread;
-      shadow->border_right = 0;
-      shadow->border_bottom = 0;
-      shadow->border_left = width + 2 * spread;
-
-      region = meta_window_shape_to_region (shape,
-                                              width - border_left - border_right,
-                                              height - border_top - border_bottom);
-    }
+  region = meta_window_shape_to_region (shape, center_width, center_height);
+  make_shadow (shadow, region);
 
-  shadow->texture = make_shadow (region, radius);
-  shadow->material = cogl_material_new ();
-  cogl_material_set_layer (shadow->material, 0, shadow->texture);
   cairo_region_destroy (region);
 
   if (cacheable)
diff --git a/src/compositor/meta-shadow-factory.h b/src/compositor/meta-shadow-factory.h
index 824a3ac..8c2c336 100644
--- a/src/compositor/meta-shadow-factory.h
+++ b/src/compositor/meta-shadow-factory.h
@@ -68,6 +68,7 @@ MetaShadow *       meta_shadow_factory_get_shadow (MetaShadowFactory *factory,
                                                    MetaWindowShape   *shape,
                                                    int                width,
                                                    int                height,
-                                                   int                radius);
+                                                   int                radius,
+                                                   int                top_fade);
 
 #endif /* __META_SHADOW_FACTORY_H__ */
diff --git a/src/compositor/meta-window-actor.c b/src/compositor/meta-window-actor.c
index c7f63ab..ddedd51 100644
--- a/src/compositor/meta-window-actor.c
+++ b/src/compositor/meta-window-actor.c
@@ -52,6 +52,7 @@ struct _MetaWindowActorPrivate
   gint              freeze_count;
 
   gint              shadow_radius;
+  gint              shadow_top_fade;
   gint              shadow_x_offset;
   gint              shadow_y_offset;
 
@@ -97,6 +98,7 @@ enum
   PROP_X_WINDOW_ATTRIBUTES,
   PROP_NO_SHADOW,
   PROP_SHADOW_RADIUS,
+  PROP_SHADOW_TOP_FADE,
   PROP_SHADOW_X_OFFSET,
   PROP_SHADOW_Y_OFFSET,
   PROP_SHADOW_OPACITY
@@ -254,6 +256,17 @@ meta_window_actor_class_init (MetaWindowActorClass *klass)
                                    PROP_SHADOW_RADIUS,
                                    pspec);
 
+  pspec = g_param_spec_int ("shadow-top-fade",
+                            "Shadow Top Fade",
+                            "If >= 0, the shadow doesn't extend above the top "
+                            "of the window, and fades out over the given number of pixels",
+                            -1, G_MAXINT, -1,
+                            G_PARAM_READWRITE);
+
+  g_object_class_install_property (object_class,
+                                   PROP_SHADOW_TOP_FADE,
+                                   pspec);
+
   pspec = g_param_spec_int ("shadow-x-offset",
                             "Shadow X Offset",
                             "Distance shadow is offset in the horizontal direction in pixels",
@@ -296,6 +309,7 @@ meta_window_actor_init (MetaWindowActor *self)
 						   MetaWindowActorPrivate);
   priv->opacity = 0xff;
   priv->shadow_radius = DEFAULT_SHADOW_RADIUS;
+  priv->shadow_top_fade = -1;
   priv->shadow_x_offset = DEFAULT_SHADOW_X_OFFSET;
   priv->shadow_y_offset = DEFAULT_SHADOW_Y_OFFSET;
   priv->shadow_opacity = 0xff;
@@ -547,6 +561,18 @@ meta_window_actor_set_property (GObject      *object,
         clutter_actor_queue_redraw (CLUTTER_ACTOR (self));
       }
       break;
+    case PROP_SHADOW_TOP_FADE:
+      {
+        gint newv = g_value_get_int (value);
+
+        if (newv == priv->shadow_top_fade)
+          return;
+
+        priv->shadow_top_fade = newv;
+        priv->recompute_shadow = TRUE;
+        clutter_actor_queue_redraw (CLUTTER_ACTOR (self));
+      }
+      break;
     case PROP_SHADOW_X_OFFSET:
       {
         gint newv = g_value_get_int (value);
@@ -614,6 +640,9 @@ meta_window_actor_get_property (GObject      *object,
     case PROP_SHADOW_RADIUS:
       g_value_set_int (value, priv->shadow_radius);
       break;
+    case PROP_SHADOW_TOP_FADE:
+      g_value_set_int (value, priv->shadow_top_fade);
+      break;
     case PROP_SHADOW_X_OFFSET:
       g_value_set_int (value, priv->shadow_x_offset);
       break;
@@ -1813,7 +1842,7 @@ check_needs_shadow (MetaWindowActor *self)
       priv->shadow = meta_shadow_factory_get_shadow (factory,
                                                      priv->shadow_shape,
                                                      shape_bounds.width, shape_bounds.height,
-                                                     priv->shadow_radius);
+                                                     priv->shadow_radius, priv->shadow_top_fade);
     }
 
   if (old_shadow != NULL)



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