[mutter] Make shadows pretty and configurable



commit 9f4942e9a7d1fa06c6972a69eb386ff0075d3128
Author: Owen W. Taylor <otaylor fishsoup net>
Date:   Mon Oct 18 15:34:08 2010 -0400

    Make shadows pretty and configurable
    
    The current shadow code just uses a single fixed texture (the Gaussian
    blur of a rectangle with a fixed blur radius) for drawing all window
    shadows. This patch adds the ability
    
    * Implement efficient blurring of arbitrary regions by approximating
      a Gaussian blur with multiple box blurs.
    
    * Detect when multiple windows can use the same shadow texture by
      converting their shape into a size-invariant MetaWindowShape.
    
    * Add properties shadow-radius, shadow-x-offset, shadow-y-offset,
      shadow-opacity to allow the shadow for a window to be configured.
    
    * Add meta_window_actor_paint() and draw the shadow directly
      from there rather than using a child actor.
    
    * Remove TidyTextureFrame, which is no longer used
    
    https://bugzilla.gnome.org/show_bug.cgi?id=592382

 src/Makefile.am                          |   10 +-
 src/compositor/meta-shadow-factory.c     |  677 ++++++++++++++++++++++++++++++
 src/compositor/meta-shadow-factory.h     |   73 ++++
 src/compositor/meta-window-actor.c       |  342 ++++++++++++----
 src/compositor/meta-window-shape.c       |  254 +++++++++++
 src/compositor/meta-window-shape.h       |   60 +++
 src/compositor/region-utils.c            |  336 +++++++++++++++
 src/compositor/region-utils.h            |   76 ++++
 src/compositor/shadow.c                  |  350 ---------------
 src/compositor/shadow.h                  |    9 -
 src/compositor/tidy/tidy-texture-frame.c |  641 ----------------------------
 src/compositor/tidy/tidy-texture-frame.h |   84 ----
 12 files changed, 1750 insertions(+), 1162 deletions(-)
---
diff --git a/src/Makefile.am b/src/Makefile.am
index 09b90d5..154f6ad 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -28,6 +28,8 @@ mutter_SOURCES= 				\
 	compositor/meta-plugin.c		\
 	compositor/meta-plugin-manager.c	\
 	compositor/meta-plugin-manager.h	\
+	compositor/meta-shadow-factory.c	\
+	compositor/meta-shadow-factory.h	\
 	compositor/meta-shaped-texture.c	\
 	compositor/meta-shaped-texture.h	\
 	compositor/meta-texture-tower.c		\
@@ -36,10 +38,10 @@ mutter_SOURCES= 				\
 	compositor/meta-window-actor-private.h	\
 	compositor/meta-window-group.c		\
 	compositor/meta-window-group.h		\
-	compositor/shadow.c			\
-	compositor/shadow.h			\
-	compositor/tidy/tidy-texture-frame.c	\
-	compositor/tidy/tidy-texture-frame.h	\
+	compositor/meta-window-shape.c		\
+	compositor/meta-window-shape.h		\
+	compositor/region-utils.c		\
+	compositor/region-utils.h		\
 	include/compositor.h			\
 	include/meta-plugin.h			\
 	include/meta-window-actor.h		\
diff --git a/src/compositor/meta-shadow-factory.c b/src/compositor/meta-shadow-factory.c
new file mode 100644
index 0000000..394de11
--- /dev/null
+++ b/src/compositor/meta-shadow-factory.c
@@ -0,0 +1,677 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * MetaShadowFactory:
+ *
+ * Create and cache shadow textures for abritrary window shapes
+ *
+ * Copyright (C) 2010 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+#include <config.h>
+#include <math.h>
+#include <string.h>
+
+#include "meta-shadow-factory.h"
+#include "region-utils.h"
+
+/* This file implements blurring the shape of a window to produce a
+ * shadow texture. The details are discussed below; a quick summary
+ * of the optimizations we use:
+ *
+ * - If the window shape is along the lines of a rounded rectangle -
+ *   a rectangular center portion with stuff at the corners - then
+ *   the blur of this - the shadow - can also be represented as a
+ *   9-sliced texture and the same texture can be used for different
+ *   size.
+ *
+ * - We use the fact that a Gaussian blur is separable to do a
+ *   2D blur as 1D blur of the rows followed by a 1D blur of the
+ *   columns.
+ *
+ * - For better cache efficiency, we blur rows, transpose the image
+ *   in blocks, blur rows again, and then transpose back.
+ *
+ * - We approximate the 1D gaussian blur as 3 successive box filters.
+ */
+
+typedef struct _MetaShadowCacheKey MetaShadowCacheKey;
+
+struct _MetaShadowCacheKey
+{
+  MetaWindowShape *shape;
+  int radius;
+};
+
+struct _MetaShadow
+{
+  int ref_count;
+
+  MetaShadowFactory *factory;
+  MetaShadowCacheKey key;
+  CoglHandle texture;
+  CoglHandle material;
+
+  int spread;
+  int border_top;
+  int border_right;
+  int border_bottom;
+  int border_left;
+};
+
+struct _MetaShadowFactory
+{
+  /* MetaShadowCacheKey => MetaShadow; the shadows are not referenced
+   * by the factory, they are simply removed from the table when freed */
+  GHashTable *shadows;
+};
+
+static guint
+meta_shadow_cache_key_hash (gconstpointer val)
+{
+  const MetaShadowCacheKey *key = val;
+
+  return 59 * key->radius + 67 * meta_window_shape_hash (key->shape);
+}
+
+static gboolean
+meta_shadow_cache_key_equal (gconstpointer a,
+                             gconstpointer b)
+{
+  const MetaShadowCacheKey *key_a = a;
+  const MetaShadowCacheKey *key_b = b;
+
+  return (key_a->radius == key_b->radius &&
+          meta_window_shape_equal (key_a->shape, key_b->shape));
+}
+
+MetaShadow *
+meta_shadow_ref (MetaShadow *shadow)
+{
+  shadow->ref_count++;
+
+  return shadow;
+}
+
+void
+meta_shadow_unref (MetaShadow *shadow)
+{
+  shadow->ref_count--;
+  if (shadow->ref_count == 0)
+    {
+      if (shadow->factory)
+        {
+          g_hash_table_remove (shadow->factory->shadows,
+                               &shadow->key);
+        }
+
+      meta_window_shape_unref (shadow->key.shape);
+      cogl_handle_unref (shadow->texture);
+      cogl_handle_unref (shadow->material);
+
+      g_slice_free (MetaShadow, shadow);
+    }
+}
+
+/**
+ * meta_shadow_paint:
+ * @window_x: x position of the region to paint a shadow for
+ * @window_y: y position of the region to paint a shadow for
+ * @window_width: actual width of the region to paint a shadow for
+ * @window_height: actual height of the region to paint a shadow for
+ *
+ * Paints the shadow at the given position, for the specified actual
+ * size of the region. (Since a #MetaShadow can be shared between
+ * different sizes with the same extracted #MetaWindowShape the
+ * size needs to be passed in here.)
+ */
+void
+meta_shadow_paint (MetaShadow *shadow,
+                   int         window_x,
+                   int         window_y,
+                   int         window_width,
+                   int         window_height,
+                   guint8      opacity)
+{
+  float texture_width = cogl_texture_get_width (shadow->texture);
+  float texture_height = cogl_texture_get_height (shadow->texture);
+  int i, j;
+
+  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)
+    {
+      /* 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);
+    }
+  else
+    {
+      float src_x[4];
+      float src_y[4];
+      float dest_x[4];
+      float dest_y[4];
+
+      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_y[0] = 0.0;
+      src_y[1] = shadow->border_top / texture_height;
+      src_y[2] = (texture_height - shadow->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]);
+    }
+}
+
+/**
+ * meta_shadow_get_bounds:
+ * @shadow: a #MetaShadow
+ * @window_x: x position of the region to paint a shadow for
+ * @window_y: y position of the region to paint a shadow for
+ * @window_width: actual width of the region to paint a shadow for
+ * @window_height: actual height of the region to paint a shadow for
+ *
+ * Computes the bounds of the pixels that will be affected by
+ * meta_shadow_paints()
+ */
+void
+meta_shadow_get_bounds  (MetaShadow            *shadow,
+                         int                    window_x,
+                         int                    window_y,
+                         int                    window_width,
+                         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;
+}
+
+MetaShadowFactory *
+meta_shadow_factory_new (void)
+{
+  MetaShadowFactory *factory;
+
+  factory = g_slice_new0 (MetaShadowFactory);
+
+  factory->shadows = g_hash_table_new (meta_shadow_cache_key_hash,
+                                       meta_shadow_cache_key_equal);
+
+  return factory;
+}
+
+void
+meta_shadow_factory_free (MetaShadowFactory *factory)
+{
+  GHashTableIter iter;
+  gpointer key, value;
+
+  /* Detach from the shadows in the table so we won't try to
+   * remove them when they freed. */
+  g_hash_table_iter_init (&iter, factory->shadows);
+  while (g_hash_table_iter_next (&iter, &key, &value))
+    {
+      MetaShadow *shadow = key;
+      shadow->factory = NULL;
+    }
+
+  g_hash_table_destroy (factory->shadows);
+
+  g_slice_free (MetaShadowFactory, factory);
+}
+
+MetaShadowFactory *
+meta_shadow_factory_get_default (void)
+{
+  static MetaShadowFactory *factory;
+
+  if (factory == NULL)
+    factory = meta_shadow_factory_new ();
+
+  return factory;
+}
+
+/* We emulate a 1D Gaussian blur by using 3 consecutive box blurs;
+ * this produces a result that's within 3% of the original and can be
+ * implemented much faster for large filter sizes because of the
+ * efficiency of implementation of a box blur. Idea and formula
+ * for choosing the box blur size come from:
+ *
+ * http://www.w3.org/TR/SVG/filters.html#feGaussianBlurElement
+ *
+ * The 2D blur is then done by blurring the rows, flipping the
+ * image and blurring the columns. (This is possible because the
+ * Gaussian kernel is separable - it's the product of a horizontal
+ * blur and a vertical blur.)
+ */
+static int
+get_box_filter_size (int radius)
+{
+  return (int)(0.5 + radius * (0.75 * sqrt(2*M_PI)));
+}
+
+/* The "spread" of the filter is the number of pixels from an original
+ * pixel that it's blurred image extends. (A no-op blur that doesn't
+ * blur would have a spread of 0.) See comment in blur_rows() for why the
+ * odd and even cases are different
+ */
+static int
+get_shadow_spread (int radius)
+{
+  int d = get_box_filter_size (radius);
+
+  if (d % 2 == 1)
+    return 3 * (d / 2);
+  else
+    return 3 * (d / 2) - 1;
+}
+
+/* This applies a single box blur pass to a horizontal range of pixels;
+ * since the box blur has the same weight for all pixels, we can
+ * implement an efficient sliding window algorithm where we add
+ * in pixels coming into the window from the right and remove
+ * them when they leave the windw to the left.
+ *
+ * d is the filter width; for even d shift indicates how the blurred
+ * result is aligned with the original - does ' x ' go to ' yy' (shift=1)
+ * or 'yy ' (shift=-1)
+ */
+static void
+blur_xspan (guchar *row,
+            guchar *tmp_buffer,
+            int     row_width,
+            int     x0,
+            int     x1,
+            int     d,
+            int     shift)
+{
+  int offset;
+  int sum = 0;
+  int i;
+
+  if (d % 2 == 1)
+    offset = d / 2;
+  else
+    offset = (d - shift) / 2;
+
+  /* All the conditionals in here look slow, but the branches will
+   * be well predicted and there are enough different possibilities
+   * that trying to write this as a series of unconditional loops
+   * is hard and not an obvious win. The main slow down here seems
+   * to be the integer division for pixel; one possible optimization
+   * would be to accumulate into two 16-bit integer buffers and
+   * only divide down after all three passes. (SSE parallel implementation
+   * of the divide step is possible.)
+   */
+  for (i = x0 - d + offset; i < x1 + offset; i++)
+    {
+      if (i >= 0 && i < row_width)
+	sum += row[i];
+
+      if (i >= x0 + offset)
+	{
+	  if (i >= d)
+	    sum -= row[i - d];
+
+	  tmp_buffer[i - offset] = (sum + d / 2) / d;
+	}
+    }
+
+  memcpy(row + x0, tmp_buffer + x0, x1 - x0);
+}
+
+static void
+blur_rows (cairo_region_t   *convolve_region,
+           int               x_offset,
+           int               y_offset,
+	   guchar           *buffer,
+	   int               buffer_width,
+	   int               buffer_height,
+           int               d)
+{
+  int i, j;
+  int n_rectangles;
+  guchar *tmp_buffer;
+
+  tmp_buffer = g_malloc (buffer_width);
+
+  n_rectangles = cairo_region_num_rectangles (convolve_region);
+  for (i = 0; i < n_rectangles; i++)
+    {
+      cairo_rectangle_int_t rect;
+
+      cairo_region_get_rectangle (convolve_region, i, &rect);
+
+      for (j = y_offset + rect.y; j < y_offset + rect.y + rect.height; j++)
+	{
+	  guchar *row = buffer + j * buffer_width;
+	  int x0 = x_offset + rect.x;
+	  int x1 = x0 + rect.width;
+
+          /* We want to produce a symmetric blur that spreads a pixel
+           * equally far to the left and right. If d is odd that happens
+           * naturally, but for d even, we approximate by using a blur
+           * on either side and then a centered blur of size d + 1.
+           * (techique also from the SVG specification)
+           */
+	  if (d % 2 == 1)
+	    {
+	      blur_xspan (row, tmp_buffer, buffer_width, x0, x1, d, 0);
+	      blur_xspan (row, tmp_buffer, buffer_width, x0, x1, d, 0);
+	      blur_xspan (row, tmp_buffer, buffer_width, x0, x1, d, 0);
+	    }
+	  else
+	    {
+	      blur_xspan (row, tmp_buffer, buffer_width, x0, x1, d, 1);
+	      blur_xspan (row, tmp_buffer, buffer_width, x0, x1, d, -1);
+	      blur_xspan (row, tmp_buffer, buffer_width, x0, x1, d + 1, 0);
+	    }
+	}
+    }
+
+  g_free (tmp_buffer);
+}
+
+/* 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.
+ */
+static guchar *
+flip_buffer (guchar *buffer,
+	     int     width,
+             int     height)
+{
+  /* Working in blocks increases cache efficiency, compared to reading
+   * or writing an entire column at once */
+#define BLOCK_SIZE 16
+
+  if (width == height)
+    {
+      int i0, j0;
+
+      for (j0 = 0; j0 < height; j0 += BLOCK_SIZE)
+	for (i0 = 0; i0 <= j0; i0 += BLOCK_SIZE)
+	  {
+	    int max_j = MIN(j0 + BLOCK_SIZE, height);
+	    int max_i = MIN(i0 + BLOCK_SIZE, width);
+	    int i, j;
+
+	    if (i0 == j0)
+	      {
+		for (j = j0; j < max_j; j++)
+		  for (i = i0; i < j; i++)
+		    {
+		      guchar tmp = buffer[j * width + i];
+		      buffer[j * width + i] = buffer[i * width + j];
+		      buffer[i * width + j] = tmp;
+		    }
+	      }
+	    else
+	      {
+		for (j = j0; j < max_j; j++)
+		  for (i = i0; i < max_i; i++)
+		    {
+		      guchar tmp = buffer[j * width + i];
+		      buffer[j * width + i] = buffer[i * width + j];
+		      buffer[i * width + j] = tmp;
+		    }
+	      }
+	  }
+
+      return buffer;
+    }
+  else
+    {
+      guchar *new_buffer = g_malloc (height * width);
+      int i0, j0;
+
+      for (i0 = 0; i0 < width; i0 += BLOCK_SIZE)
+        for (j0 = 0; j0 < height; j0 += BLOCK_SIZE)
+	  {
+	    int max_j = MIN(j0 + BLOCK_SIZE, height);
+	    int max_i = MIN(i0 + BLOCK_SIZE, width);
+	    int i, j;
+
+            for (i = i0; i < max_i; i++)
+              for (j = j0; j < max_j; j++)
+		new_buffer[i * height + j] = buffer[j * width + i];
+	  }
+
+      g_free (buffer);
+
+      return new_buffer;
+    }
+#undef BLOCK_SIZE
+}
+
+static CoglHandle
+make_shadow (cairo_region_t *region,
+             int             radius)
+{
+  int d = get_box_filter_size (radius);
+  int spread = get_shadow_spread (radius);
+  CoglHandle result;
+  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 n_rectangles, j, k;
+
+  cairo_region_get_extents (region, &extents);
+
+  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;
+  buffer_height = (buffer_height + 3) & ~3;
+
+  /* Square buffer allows in-place swaps, which are roughly 70% faster, but we
+   * don't want to over-allocate too much memory.
+   */
+  if (buffer_height < buffer_width && buffer_height > (3 * buffer_width) / 4)
+    buffer_height = buffer_width;
+  if (buffer_width < buffer_height && buffer_width > (3 * buffer_height) / 4)
+    buffer_width = buffer_height;
+
+  buffer = g_malloc0 (buffer_width * buffer_height);
+
+  /* Blurring with multiple box-blur passes is fast, but (especially for
+   * 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);
+
+  /* Step 1: unblurred image */
+  n_rectangles = cairo_region_num_rectangles (region);
+  for (k = 0; k < n_rectangles; k++)
+    {
+      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);
+    }
+
+  /* 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);
+
+  /* Step 3: 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);
+
+  cairo_region_destroy (row_convolve_region);
+  cairo_region_destroy (column_convolve_region);
+  g_free (buffer);
+
+  return result;
+}
+
+/**
+ * meta_shadow_factory_get_shadow:
+ * @factory: a #MetaShadowFactory
+ * @shape: the size-invariant shape of the window's 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
+ *
+ * Gets the appropriate shadow object for drawing shadows for the
+ * specified window shape. The region that we are shadowing is specified
+ * as a combination of a size-invariant extracted shape and the size.
+ * In some cases, the same shadow object can be shared between sizes;
+ * in other cases a different shadow object is used for each size.
+ *
+ * Return value: (transfer full): a newly referenced #MetaShadow; unref with
+ *  meta_shadow_unref()
+ */
+MetaShadow *
+meta_shadow_factory_get_shadow (MetaShadowFactory  *factory,
+                                MetaWindowShape    *shape,
+                                int                 width,
+                                int                 height,
+                                int                 radius)
+{
+  MetaShadowCacheKey key;
+  MetaShadow *shadow;
+  cairo_region_t *region;
+  int spread;
+  int border_top, border_right, border_bottom, border_left;
+  gboolean cacheable;
+
+  /* Using a single shadow texture for different window sizes only works
+   * when there is a central scaled area that is greater than twice
+   * the spread of the gaussian blur we are applying to get to the
+   * shadow image.
+   *                         *********          ***********
+   *  /----------\         *###########*      *#############*
+   *  |          |   =>   **#*********#** => **#***********#**
+   *  |          |        **#**     **#**    **#**       **#**
+   *  |          |        **#*********#**    **#***********#**
+   *  \----------/         *###########*      *#############*
+   *                         **********         ************
+   *   Original                Blur            Stretched Blur
+   *
+   * For smaller sizes, we create a separate shadow image for each size;
+   * since we assume that there will be little reuse, we don't try to
+   * cache such images but just recreate them. (Since the current cache
+   * 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.)
+   */
+  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);
+
+  if (cacheable)
+    {
+      key.shape = shape;
+      key.radius = radius;
+
+      shadow = g_hash_table_lookup (factory->shadows, &key);
+      if (shadow)
+        return meta_shadow_ref (shadow);
+    }
+
+  shadow = g_slice_new0 (MetaShadow);
+
+  shadow->ref_count = 1;
+  shadow->factory = factory;
+  shadow->key.shape = meta_window_shape_ref (shape);
+  shadow->key.radius = radius;
+
+  shadow->spread = spread;
+
+  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;
+
+      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);
+    }
+
+  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)
+    g_hash_table_insert (factory->shadows, &shadow->key, shadow);
+
+  return shadow;
+}
diff --git a/src/compositor/meta-shadow-factory.h b/src/compositor/meta-shadow-factory.h
new file mode 100644
index 0000000..824a3ac
--- /dev/null
+++ b/src/compositor/meta-shadow-factory.h
@@ -0,0 +1,73 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * MetaShadowFactory:
+ *
+ * Create and cache shadow textures for arbitrary window shapes
+ *
+ * Copyright (C) 2010 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef __META_SHADOW_FACTORY_H__
+#define __META_SHADOW_FACTORY_H__
+
+#include <clutter/clutter.h>
+#include "meta-window-shape.h"
+
+/**
+ * MetaShadow:
+ * #MetaShadow holds a shadow texture along with information about how to
+ * apply that texture to draw a window texture. (E.g., it knows how big the
+ * unscaled borders are on each side of the shadow texture.)
+ */
+typedef struct _MetaShadow MetaShadow;
+
+MetaShadow *meta_shadow_ref         (MetaShadow            *shadow);
+void        meta_shadow_unref       (MetaShadow            *shadow);
+CoglHandle  meta_shadow_get_texture (MetaShadow            *shadow);
+void        meta_shadow_paint       (MetaShadow            *shadow,
+                                     int                    window_x,
+                                     int                    window_y,
+                                     int                    window_width,
+                                     int                    window_height,
+                                     guint8                 opacity);
+void        meta_shadow_get_bounds  (MetaShadow            *shadow,
+                                     int                    window_x,
+                                     int                    window_y,
+                                     int                    window_width,
+                                     int                    window_height,
+                                     cairo_rectangle_int_t *bounds);
+
+/**
+ * MetaShadowFactory:
+ * #MetaShadowFactory is used to create window shadows. It caches shadows internally
+ * so that multiple shadows created for the same shape with the same radius will
+ * share the same MetaShadow.
+ */
+typedef struct _MetaShadowFactory MetaShadowFactory;
+
+MetaShadowFactory *meta_shadow_factory_get_default (void);
+
+MetaShadowFactory *meta_shadow_factory_new        (void);
+void               meta_shadow_factory_free       (MetaShadowFactory *factory);
+MetaShadow *       meta_shadow_factory_get_shadow (MetaShadowFactory *factory,
+                                                   MetaWindowShape   *shape,
+                                                   int                width,
+                                                   int                height,
+                                                   int                radius);
+
+#endif /* __META_SHADOW_FACTORY_H__ */
diff --git a/src/compositor/meta-window-actor.c b/src/compositor/meta-window-actor.c
index 756d234..c7f63ab 100644
--- a/src/compositor/meta-window-actor.c
+++ b/src/compositor/meta-window-actor.c
@@ -17,10 +17,9 @@
 #include "xprops.h"
 
 #include "compositor-private.h"
+#include "meta-shadow-factory.h"
 #include "meta-shaped-texture.h"
 #include "meta-window-actor-private.h"
-#include "shadow.h"
-#include "tidy/tidy-texture-frame.h"
 
 struct _MetaWindowActorPrivate
 {
@@ -31,12 +30,13 @@ struct _MetaWindowActorPrivate
   MetaScreen       *screen;
 
   ClutterActor     *actor;
-  ClutterActor     *shadow;
+  MetaShadow       *shadow;
   Pixmap            back_pixmap;
 
   Damage            damage;
 
   guint8            opacity;
+  guint8            shadow_opacity;
 
   gchar *           desc;
 
@@ -46,8 +46,15 @@ struct _MetaWindowActorPrivate
    * texture */
   cairo_region_t   *bounding_region;
 
+  /* Extracted size-invariant shape used for shadows */
+  MetaWindowShape  *shadow_shape;
+
   gint              freeze_count;
 
+  gint              shadow_radius;
+  gint              shadow_x_offset;
+  gint              shadow_y_offset;
+
   /*
    * These need to be counters rather than flags, since more plugins
    * can implement same effect; the practicality of stacking effects
@@ -70,7 +77,9 @@ struct _MetaWindowActorPrivate
   guint		    received_damage        : 1;
 
   guint		    needs_pixmap           : 1;
-  guint		    needs_reshape          : 1;
+  guint             needs_reshape          : 1;
+  guint             recompute_shadow       : 1;
+  guint             paint_shadow           : 1;
   guint		    size_changed           : 1;
 
   guint		    needs_destroy	   : 1;
@@ -87,8 +96,16 @@ enum
   PROP_X_WINDOW,
   PROP_X_WINDOW_ATTRIBUTES,
   PROP_NO_SHADOW,
+  PROP_SHADOW_RADIUS,
+  PROP_SHADOW_X_OFFSET,
+  PROP_SHADOW_Y_OFFSET,
+  PROP_SHADOW_OPACITY
 };
 
+#define DEFAULT_SHADOW_RADIUS 12
+#define DEFAULT_SHADOW_X_OFFSET 0
+#define DEFAULT_SHADOW_Y_OFFSET 8
+
 static void meta_window_actor_dispose    (GObject *object);
 static void meta_window_actor_finalize   (GObject *object);
 static void meta_window_actor_constructed (GObject *object);
@@ -101,6 +118,8 @@ static void meta_window_actor_get_property (GObject      *object,
                                             GValue       *value,
                                             GParamSpec   *pspec);
 
+static void meta_window_actor_paint (ClutterActor *actor);
+
 static void     meta_window_actor_detach     (MetaWindowActor *self);
 static gboolean meta_window_actor_has_shadow (MetaWindowActor *self);
 
@@ -161,6 +180,7 @@ static void
 meta_window_actor_class_init (MetaWindowActorClass *klass)
 {
   GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
   GParamSpec   *pspec;
 
   g_type_class_add_private (klass, sizeof (MetaWindowActorPrivate));
@@ -171,6 +191,8 @@ meta_window_actor_class_init (MetaWindowActorClass *klass)
   object_class->get_property = meta_window_actor_get_property;
   object_class->constructed  = meta_window_actor_constructed;
 
+  actor_class->paint = meta_window_actor_paint;
+
   pspec = g_param_spec_object ("meta-window",
                                "MetaWindow",
                                "The displayed MetaWindow",
@@ -216,11 +238,52 @@ meta_window_actor_class_init (MetaWindowActorClass *klass)
                                 "No shadow",
                                 "Do not add shaddow to this window",
                                 FALSE,
-                                G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
+                                G_PARAM_READWRITE);
 
   g_object_class_install_property (object_class,
                                    PROP_NO_SHADOW,
                                    pspec);
+
+  pspec = g_param_spec_int ("shadow-radius",
+                            "Shadow Radius",
+                            "Radius (standard deviation of gaussian blur) of window's shadow",
+                            0, 128, DEFAULT_SHADOW_RADIUS,
+                            G_PARAM_READWRITE);
+
+  g_object_class_install_property (object_class,
+                                   PROP_SHADOW_RADIUS,
+                                   pspec);
+
+  pspec = g_param_spec_int ("shadow-x-offset",
+                            "Shadow X Offset",
+                            "Distance shadow is offset in the horizontal direction in pixels",
+                            G_MININT, G_MAXINT, DEFAULT_SHADOW_X_OFFSET,
+                            G_PARAM_READWRITE);
+
+  g_object_class_install_property (object_class,
+                                   PROP_SHADOW_X_OFFSET,
+                                   pspec);
+
+  pspec = g_param_spec_int ("shadow-y-offset",
+                            "Shadow Y Offset",
+                            "Distance shadow is offset in the vertical direction in piyels",
+                            G_MININT, G_MAXINT, DEFAULT_SHADOW_Y_OFFSET,
+                            G_PARAM_READWRITE);
+
+  g_object_class_install_property (object_class,
+                                   PROP_SHADOW_Y_OFFSET,
+                                   pspec);
+
+  pspec = g_param_spec_uint ("shadow-opacity",
+                             "Shadow Opacity",
+                             "Opacity of the window's shadow",
+                             0, 255,
+                             255,
+                             G_PARAM_READWRITE);
+
+  g_object_class_install_property (object_class,
+                                   PROP_SHADOW_OPACITY,
+                                   pspec);
 }
 
 static void
@@ -232,6 +295,11 @@ meta_window_actor_init (MetaWindowActor *self)
 						   META_TYPE_WINDOW_ACTOR,
 						   MetaWindowActorPrivate);
   priv->opacity = 0xff;
+  priv->shadow_radius = DEFAULT_SHADOW_RADIUS;
+  priv->shadow_x_offset = DEFAULT_SHADOW_X_OFFSET;
+  priv->shadow_y_offset = DEFAULT_SHADOW_Y_OFFSET;
+  priv->shadow_opacity = 0xff;
+  priv->paint_shadow = TRUE;
 }
 
 static void
@@ -291,18 +359,6 @@ window_decorated_notify (MetaWindow *mw,
 
   g_object_set (self, "x-window-attributes", &attrs, NULL);
 
-  if (priv->shadow)
-    {
-      ClutterActor *p = clutter_actor_get_parent (priv->shadow);
-
-      if (CLUTTER_IS_CONTAINER (p))
-        clutter_container_remove_actor (CLUTTER_CONTAINER (p), priv->shadow);
-      else
-        clutter_actor_unparent (priv->shadow);
-
-      priv->shadow = NULL;
-    }
-
   /*
    * Recreate the contents.
    */
@@ -344,13 +400,6 @@ meta_window_actor_constructed (GObject *object)
 
   meta_window_actor_update_opacity (self);
 
-  if (meta_window_actor_has_shadow (self))
-    {
-      priv->shadow = meta_create_shadow_frame (compositor);
-
-      clutter_container_add_actor (CLUTTER_CONTAINER (self), priv->shadow);
-    }
-
   if (!priv->actor)
     {
       priv->actor = meta_shaped_texture_new ();
@@ -407,6 +456,18 @@ meta_window_actor_dispose (GObject *object)
   meta_window_actor_clear_shape_region (self);
   meta_window_actor_clear_bounding_region (self);
 
+  if (priv->shadow != NULL)
+    {
+      meta_shadow_unref (priv->shadow);
+      priv->shadow = NULL;
+    }
+
+  if (priv->shadow_shape != NULL)
+    {
+      meta_window_shape_unref (priv->shadow_shape);
+      priv->shadow_shape = NULL;
+    }
+
   if (priv->damage != None)
     {
       meta_error_trap_push (display);
@@ -463,36 +524,60 @@ meta_window_actor_set_property (GObject      *object,
       break;
     case PROP_NO_SHADOW:
       {
-        gboolean oldv = priv->no_shadow ? TRUE : FALSE;
         gboolean newv = g_value_get_boolean (value);
 
-        if (oldv == newv)
+        if (newv == priv->no_shadow)
           return;
 
         priv->no_shadow = newv;
 
-        if (newv && priv->shadow)
-          {
-            clutter_container_remove_actor (CLUTTER_CONTAINER (object),
-                                            priv->shadow);
-            priv->shadow = NULL;
-          }
-        else if (!newv && !priv->shadow && meta_window_actor_has_shadow (self))
-          {
-            gfloat       w, h;
-            MetaDisplay *display = meta_screen_get_display (priv->screen);
-            MetaCompositor *compositor;
+        priv->recompute_shadow = TRUE;
+        clutter_actor_queue_redraw (CLUTTER_ACTOR (self));
+      }
+      break;
+    case PROP_SHADOW_RADIUS:
+      {
+        gint newv = g_value_get_int (value);
+
+        if (newv == priv->shadow_radius)
+          return;
+
+        priv->shadow_radius = 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);
+
+        if (newv == priv->shadow_x_offset)
+          return;
 
-            compositor = meta_display_get_compositor (display);
+        priv->shadow_x_offset = newv;
+        clutter_actor_queue_redraw (CLUTTER_ACTOR (self));
+      }
+      break;
+    case PROP_SHADOW_Y_OFFSET:
+      {
+        gint newv = g_value_get_int (value);
 
-            clutter_actor_get_size (CLUTTER_ACTOR (self), &w, &h);
+        if (newv == priv->shadow_y_offset)
+          return;
 
-            priv->shadow = meta_create_shadow_frame (compositor);
+        priv->shadow_y_offset = newv;
+        clutter_actor_queue_redraw (CLUTTER_ACTOR (self));
+      }
+      break;
+    case PROP_SHADOW_OPACITY:
+      {
+        guint newv = g_value_get_uint (value);
 
-            clutter_actor_set_size (priv->shadow, w, h);
+        if (newv == priv->shadow_opacity)
+          return;
 
-            clutter_container_add_actor (CLUTTER_CONTAINER (self), priv->shadow);
-          }
+        priv->shadow_opacity = newv;
+        clutter_actor_queue_redraw (CLUTTER_ACTOR (self));
       }
       break;
     default:
@@ -526,12 +611,57 @@ meta_window_actor_get_property (GObject      *object,
     case PROP_NO_SHADOW:
       g_value_set_boolean (value, priv->no_shadow);
       break;
+    case PROP_SHADOW_RADIUS:
+      g_value_set_int (value, priv->shadow_radius);
+      break;
+    case PROP_SHADOW_X_OFFSET:
+      g_value_set_int (value, priv->shadow_x_offset);
+      break;
+    case PROP_SHADOW_Y_OFFSET:
+      g_value_set_int (value, priv->shadow_y_offset);
+      break;
+    case PROP_SHADOW_OPACITY:
+      g_value_set_uint (value, priv->shadow_opacity);
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
     }
 }
 
+static void
+meta_window_actor_get_shape_bounds (MetaWindowActor       *self,
+                                    cairo_rectangle_int_t *bounds)
+{
+  MetaWindowActorPrivate *priv = self->priv;
+
+  if (priv->shaped)
+    cairo_region_get_extents (priv->shape_region, bounds);
+  else
+    cairo_region_get_extents (priv->bounding_region, bounds);
+}
+
+static void
+meta_window_actor_paint (ClutterActor *actor)
+{
+  MetaWindowActor *self = META_WINDOW_ACTOR (actor);
+  MetaWindowActorPrivate *priv = self->priv;
+
+  if (priv->shadow != NULL && priv->paint_shadow)
+    {
+      cairo_rectangle_int_t shape_bounds;
+      meta_window_actor_get_shape_bounds (self, &shape_bounds);
+
+      meta_shadow_paint (priv->shadow,
+                         priv->shadow_x_offset + shape_bounds.x,
+                         priv->shadow_y_offset + shape_bounds.y,
+                         shape_bounds.width,
+                         shape_bounds.height,
+                         (clutter_actor_get_paint_opacity (actor) * priv->shadow_opacity) / 255);
+    }
+
+  CLUTTER_ACTOR_CLASS (meta_window_actor_parent_class)->paint (actor);
+}
+
 static gboolean
 is_shaped (MetaDisplay *display, Window xwindow)
 {
@@ -560,10 +690,12 @@ meta_window_actor_has_shadow (MetaWindowActor *self)
   if (priv->no_shadow)
     return FALSE;
 
+  if (priv->shadow_radius == 0)
+    return FALSE;
+
   /*
    * Always put a shadow around windows with a frame - This should override
-   * the restriction about not putting a shadow around shaped windows
-   * as the frame might be the reason the window is shaped
+   * the restriction about not putting a shadow around ARGB windows.
    */
   if (priv->window)
     {
@@ -576,7 +708,8 @@ meta_window_actor_has_shadow (MetaWindowActor *self)
     }
 
   /*
-   * Do not add shadows to ARGB windows (since they are probably transparent)
+   * Do not add shadows to ARGB windows; eventually we should generate a
+   * shadow from the input shape for such windows.
    */
   if (priv->argb32 || priv->opacity != 0xff)
     {
@@ -586,18 +719,7 @@ meta_window_actor_has_shadow (MetaWindowActor *self)
     }
 
   /*
-   * Never put a shadow around shaped windows
-   */
-  if (priv->shaped)
-    {
-      meta_verbose ("Window 0x%x has no shadow as it is shaped\n",
-		    (guint)priv->xwindow);
-      return FALSE;
-    }
-
-  /*
    * Add shadows to override redirect windows (e.g., Gtk menus).
-   * This must have lower priority than window shape test.
    */
   if (priv->attrs.override_redirect)
     {
@@ -1366,9 +1488,26 @@ meta_window_actor_update_bounding_region (MetaWindowActor *self,
   MetaWindowActorPrivate *priv = self->priv;
   cairo_rectangle_int_t bounding_rectangle = { 0, 0, width, height };
 
+  if (priv->bounding_region != NULL)
+    {
+      cairo_rectangle_int_t old_bounding_rectangle;
+      cairo_region_get_extents (priv->bounding_region, &old_bounding_rectangle);
+
+      if (old_bounding_rectangle.width == width && old_bounding_rectangle.height == height)
+        return;
+    }
+
   meta_window_actor_clear_bounding_region (self);
 
   priv->bounding_region = cairo_region_create_rectangle (&bounding_rectangle);
+
+  /* When we're shaped, we use the shape region to generate the shadow; the shape
+   * region only changes when we get ShapeNotify event; but for unshaped windows
+   * we generate the shadow from the bounding region, so we need to recompute
+   * the shadow when the size changes.
+   */
+  if (!priv->shaped)
+    priv->recompute_shadow = TRUE;
 }
 
 static void
@@ -1500,8 +1639,8 @@ meta_window_actor_set_visible_region_beneath (MetaWindowActor *self,
 
   if (priv->shadow)
     {
-      cairo_rectangle_int_t shadow_rect;
-      ClutterActorBox box;
+      cairo_rectangle_int_t shape_bounds;
+      cairo_rectangle_int_t shadow_bounds;
       cairo_region_overlap_t overlap;
 
       /* We could compute an full clip region as we do for the window
@@ -1510,17 +1649,17 @@ meta_window_actor_set_visible_region_beneath (MetaWindowActor *self,
        * the shadow is completely obscured and doesn't need to be drawn
        * at all.
        */
-      clutter_actor_get_allocation_box (priv->shadow, &box);
-
-      shadow_rect.x = roundf (box.x1);
-      shadow_rect.y = roundf (box.y1);
-      shadow_rect.width = roundf (box.x2 - box.x1);
-      shadow_rect.height = roundf (box.y2 - box.y1);
+      meta_window_actor_get_shape_bounds (self, &shape_bounds);
 
-      overlap = cairo_region_contains_rectangle (beneath_region, &shadow_rect);
+      meta_shadow_get_bounds (priv->shadow,
+                              priv->shadow_x_offset + shape_bounds.x,
+                              priv->shadow_y_offset + shape_bounds.y,
+                              shape_bounds.width,
+                              shape_bounds.height,
+                              &shadow_bounds);
 
-      tidy_texture_frame_set_needs_paint (TIDY_TEXTURE_FRAME (priv->shadow),
-                                          overlap != CAIRO_REGION_OVERLAP_OUT);
+      overlap = cairo_region_contains_rectangle (beneath_region, &shadow_bounds);
+      priv->paint_shadow = overlap != CAIRO_REGION_OVERLAP_OUT;
     }
 }
 
@@ -1538,8 +1677,7 @@ meta_window_actor_reset_visible_regions (MetaWindowActor *self)
 
   meta_shaped_texture_set_clip_region (META_SHAPED_TEXTURE (priv->actor),
                                        NULL);
-  if (priv->shadow)
-    tidy_texture_frame_set_needs_paint (TIDY_TEXTURE_FRAME (priv->shadow), TRUE);
+  priv->paint_shadow = TRUE;
 }
 
 static void
@@ -1622,9 +1760,6 @@ check_needs_pixmap (MetaWindowActor *self)
                     "pixmap-height", &pxm_height,
                     NULL);
 
-      if (priv->shadow)
-        clutter_actor_set_size (priv->shadow, pxm_width, pxm_height);
-
       meta_window_actor_update_bounding_region (self, pxm_width, pxm_height);
 
       full = TRUE;
@@ -1635,6 +1770,58 @@ check_needs_pixmap (MetaWindowActor *self)
   priv->needs_pixmap = FALSE;
 }
 
+static void
+check_needs_shadow (MetaWindowActor *self)
+{
+  MetaWindowActorPrivate *priv = self->priv;
+  MetaShadow *old_shadow = NULL;
+  gboolean should_have_shadow;
+
+  if (!priv->mapped)
+    return;
+
+  /* Calling meta_window_actor_has_shadow() here at every pre-paint is cheap
+   * and avoids the need to explicitly handle window type changes, which
+   * we would do if tried to keep track of when we might be adding or removing
+   * a shadow more explicitly. We only keep track of changes to the *shape* of
+   * the shadow with priv->recompute_shadow.
+   */
+
+  should_have_shadow = meta_window_actor_has_shadow (self);
+
+  if (priv->shadow != NULL && (!should_have_shadow || priv->recompute_shadow))
+    {
+      old_shadow = priv->shadow;
+      priv->shadow = NULL;
+    }
+
+  if (priv->shadow == NULL && should_have_shadow)
+    {
+      MetaShadowFactory *factory = meta_shadow_factory_get_default ();
+      cairo_rectangle_int_t shape_bounds;
+
+      if (priv->shadow_shape == NULL)
+        {
+          if (priv->shaped)
+            priv->shadow_shape = meta_window_shape_new (priv->shape_region);
+          else
+            priv->shadow_shape = meta_window_shape_new (priv->bounding_region);
+        }
+
+      meta_window_actor_get_shape_bounds (self, &shape_bounds);
+
+      priv->shadow = meta_shadow_factory_get_shadow (factory,
+                                                     priv->shadow_shape,
+                                                     shape_bounds.width, shape_bounds.height,
+                                                     priv->shadow_radius);
+    }
+
+  if (old_shadow != NULL)
+    meta_shadow_unref (old_shadow);
+
+  priv->recompute_shadow = FALSE;
+}
+
 static gboolean
 is_frozen (MetaWindowActor *self)
 {
@@ -1733,6 +1920,7 @@ check_needs_reshape (MetaWindowActor *self)
 #endif
 
   priv->needs_reshape = FALSE;
+  priv->recompute_shadow = TRUE;
 }
 
 void
@@ -1743,6 +1931,11 @@ meta_window_actor_update_shape (MetaWindowActor   *self,
 
   priv->shaped = shaped;
   priv->needs_reshape = TRUE;
+  if (priv->shadow_shape != NULL)
+    {
+      meta_window_shape_unref (priv->shadow_shape);
+      priv->shadow_shape = NULL;
+    }
 
   clutter_actor_queue_redraw (priv->actor);
 }
@@ -1770,8 +1963,9 @@ meta_window_actor_pre_paint (MetaWindowActor *self)
       priv->received_damage = FALSE;
     }
 
-  check_needs_reshape (self);
   check_needs_pixmap (self);
+  check_needs_reshape (self);
+  check_needs_shadow (self);
 }
 
 void
diff --git a/src/compositor/meta-window-shape.c b/src/compositor/meta-window-shape.c
new file mode 100644
index 0000000..2580793
--- /dev/null
+++ b/src/compositor/meta-window-shape.c
@@ -0,0 +1,254 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * MetaWindowShape
+ *
+ * Extracted invariant window shape
+ *
+ * Copyright (C) 2010 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+#include <string.h>
+
+#include "meta-window-shape.h"
+#include "region-utils.h"
+
+struct _MetaWindowShape
+{
+  guint ref_count;
+
+  int top, right, bottom, left;
+  int n_rectangles;
+  cairo_rectangle_int_t *rectangles;
+  guint hash;
+};
+
+MetaWindowShape *
+meta_window_shape_new (cairo_region_t *region)
+{
+  MetaWindowShape *shape;
+  MetaRegionIterator iter;
+  cairo_rectangle_int_t extents;
+  int max_yspan_y1 = 0;
+  int max_yspan_y2 = 0;
+  int max_xspan_x1 = -1;
+  int max_xspan_x2 = -1;
+  guint hash;
+
+  shape = g_slice_new0 (MetaWindowShape);
+  shape->ref_count = 1;
+
+  cairo_region_get_extents (region, &extents);
+
+  shape->n_rectangles = cairo_region_num_rectangles (region);
+
+  if (shape->n_rectangles == 0)
+    {
+      shape->rectangles = NULL;
+      shape->top = shape->right = shape->bottom = shape->left = 0;
+      shape->hash = 0;
+      return shape;
+    }
+
+  for (meta_region_iterator_init (&iter, region);
+       !meta_region_iterator_at_end (&iter);
+       meta_region_iterator_next (&iter))
+    {
+      int max_line_xspan_x1 = -1;
+      int max_line_xspan_x2 = -1;
+
+      if (iter.rectangle.width > max_line_xspan_x2 - max_line_xspan_x1)
+        {
+          max_line_xspan_x1 = iter.rectangle.x;
+          max_line_xspan_x2 = iter.rectangle.x + iter.rectangle.width;
+        }
+
+      if (iter.line_end)
+        {
+          if (iter.rectangle.height > max_yspan_y2 - max_yspan_y1)
+            {
+              max_yspan_y1 = iter.rectangle.y;
+              max_yspan_y2 = iter.rectangle.y + iter.rectangle.height;
+            }
+
+          if (max_xspan_x1 < 0) /* First line */
+            {
+              max_xspan_x1 = max_line_xspan_x1;
+              max_xspan_x2 = max_line_xspan_x2;
+            }
+          else
+            {
+              max_xspan_x1 = MAX (max_xspan_x1, max_line_xspan_x1);
+              max_xspan_x2 = MIN (max_xspan_x2, max_line_xspan_x2);
+
+              if (max_xspan_x2 < max_xspan_x1)
+                max_xspan_x2 = max_xspan_x1;
+            }
+        }
+    }
+
+#if 0
+  g_print ("xspan: %d -> %d, yspan: %d -> %d\n",
+           max_xspan_x1, max_xspan_x2,
+           max_yspan_y1, max_yspan_y2);
+#endif
+
+  shape->top = max_yspan_y1 - extents.y;
+  shape->right = extents.x + extents.width - max_xspan_x2;
+  shape->bottom = extents.y + extents.height - max_yspan_y2;
+  shape->left = max_xspan_x1 - extents.x;
+
+  shape->rectangles = g_new (cairo_rectangle_int_t, shape->n_rectangles);
+
+  hash = 0;
+  for (meta_region_iterator_init (&iter, region);
+       !meta_region_iterator_at_end (&iter);
+       meta_region_iterator_next (&iter))
+    {
+      int x1, x2, y1, y2;
+
+      x1 = iter.rectangle.x;
+      x2 = iter.rectangle.x + iter.rectangle.width;
+      y1 = iter.rectangle.y;
+      y2 = iter.rectangle.y + iter.rectangle.height;
+
+      if (x1 > max_xspan_x1)
+        x1 -= MIN (x1, max_xspan_x2 - 1) - max_xspan_x1;
+      if (x2 > max_xspan_x1)
+        x2 -= MIN (x2, max_xspan_x2 - 1) - max_xspan_x1;
+      if (y1 > max_yspan_y1)
+        y1 -= MIN (y1, max_yspan_y2 - 1) - max_yspan_y1;
+      if (y2 > max_yspan_y1)
+        y2 -= MIN (y2, max_yspan_y2 - 1) - max_yspan_y1;
+
+      shape->rectangles[iter.i].x = x1 - extents.x;
+      shape->rectangles[iter.i].y = y1 - extents.y;
+      shape->rectangles[iter.i].width = x2 - x1;
+      shape->rectangles[iter.i].height = y2 - y1;
+
+#if 0
+      g_print ("%d: +%d+%dx%dx%d => +%d+%dx%dx%d\n",
+               iter.i, iter.rectangle.x, iter.rectangle.y, iter.rectangle.width, iter.rectangle.height,
+               shape->rectangles[iter.i].x, shape->rectangles[iter.i].y,
+               hape->rectangles[iter.i].width, shape->rectangles[iter.i].height);
+#endif
+
+      hash = hash * 31 + x1 * 17 + x2 * 27 + y1 * 37 + y2 * 43;
+    }
+
+  shape->hash = hash;
+
+#if 0
+  g_print ("%d %d %d %d: %#x\n\n", shape->top, shape->right, shape->bottom, shape->left, shape->hash);
+#endif
+
+  return shape;
+}
+
+MetaWindowShape *
+meta_window_shape_ref (MetaWindowShape *shape)
+{
+  shape->ref_count++;
+
+  return shape;
+}
+
+void
+meta_window_shape_unref (MetaWindowShape *shape)
+{
+  shape->ref_count--;
+  if (shape->ref_count == 0)
+    {
+      g_free (shape->rectangles);
+      g_slice_free (MetaWindowShape, shape);
+    }
+}
+
+guint
+meta_window_shape_hash (MetaWindowShape *shape)
+{
+  return shape->hash;
+}
+
+gboolean
+meta_window_shape_equal (MetaWindowShape *shape_a,
+                         MetaWindowShape *shape_b)
+{
+  if (shape_a->n_rectangles != shape_b->n_rectangles)
+    return FALSE;
+
+  return memcmp (shape_a->rectangles, shape_b->rectangles,
+                 sizeof (cairo_rectangle_int_t) * shape_a->n_rectangles) == 0;
+}
+
+void
+meta_window_shape_get_borders (MetaWindowShape *shape,
+                               int             *border_top,
+                               int             *border_right,
+                               int             *border_bottom,
+                               int             *border_left)
+{
+  if (border_top)
+    *border_top = shape->top;
+  if (border_right)
+    *border_right = shape->right;
+  if (border_bottom)
+    *border_bottom = shape->bottom;
+  if (border_left)
+    *border_left = shape->left;
+}
+
+/**
+ * meta_window_shape_to_region:
+ * @shape: a #MetaWindowShape
+ * @center_width: size of the central region horizontally
+ * @center_height: size of the central region vertically
+ *
+ * Converts the shape to to a cairo_region_t using the given width
+ * and height for the central scaled region.
+ *
+ * Return value: a newly created region
+ */
+cairo_region_t *
+meta_window_shape_to_region (MetaWindowShape *shape,
+                             int              center_width,
+                             int              center_height)
+{
+  cairo_region_t *region;
+  int i;
+
+  region = cairo_region_create ();
+
+  for (i = 0; i < shape->n_rectangles; i++)
+    {
+      cairo_rectangle_int_t rect = shape->rectangles[i];
+
+      if (rect.x <= shape->left && rect.x + rect.width >= shape->left + 1)
+        rect.width += center_width;
+      else if (rect.x >= shape->left + 1)
+        rect.x += center_width;
+
+      if (rect.y <= shape->top && rect.y + rect.height >= shape->top + 1)
+        rect.height += center_height;
+      else if (rect.y >= shape->top + 1)
+        rect.y += center_height;
+
+      cairo_region_union_rectangle (region, &rect);
+    }
+
+  return region;
+}
+
diff --git a/src/compositor/meta-window-shape.h b/src/compositor/meta-window-shape.h
new file mode 100644
index 0000000..1dd6c70
--- /dev/null
+++ b/src/compositor/meta-window-shape.h
@@ -0,0 +1,60 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * MetaWindowShape
+ *
+ * Extracted invariant window shape
+ *
+ * Copyright (C) 2010 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef __META_WINDOW_SHAPE_H__
+#define __META_WINDOW_SHAPE_H__
+
+#include <cairo.h>
+#include <glib.h>
+
+/**
+ * MetaWindowShape:
+ * #MetaWindowShape represents a 9-sliced region with borders on all sides that
+ * are unscaled, and a constant central region that is scaled. For example,
+ * the regions representing two windows that are rounded rectangles,
+ * with the same corner radius but different sizes, have the
+ * same MetaWindowShape.
+ *
+ * #MetaWindowShape is designed to be used as part of a hash table key, so has
+ * efficient hash and equal functions.
+ */
+typedef struct _MetaWindowShape MetaWindowShape;
+
+MetaWindowShape *  meta_window_shape_new         (cairo_region_t  *region);
+MetaWindowShape *  meta_window_shape_ref         (MetaWindowShape *shape);
+void               meta_window_shape_unref       (MetaWindowShape *shape);
+guint              meta_window_shape_hash        (MetaWindowShape *shape);
+gboolean           meta_window_shape_equal       (MetaWindowShape *shape_a,
+                                                  MetaWindowShape *shape_b);
+void               meta_window_shape_get_borders (MetaWindowShape *shape,
+                                                  int             *border_top,
+                                                  int             *border_right,
+                                                  int             *border_bottom,
+                                                  int             *border_left);
+cairo_region_t    *meta_window_shape_to_region   (MetaWindowShape *shape,
+                                                  int              center_width,
+                                                  int              center_height);
+
+#endif /* __META_WINDOW_SHAPE_H __*/
+
diff --git a/src/compositor/region-utils.c b/src/compositor/region-utils.c
new file mode 100644
index 0000000..9cfc393
--- /dev/null
+++ b/src/compositor/region-utils.c
@@ -0,0 +1,336 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * Utilities for region manipulation
+ *
+ * Copyright (C) 2010 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include "region-utils.h"
+
+#include <math.h>
+
+/* MetaRegionBuilder */
+
+/* Various algorithms in this file require unioning together a set of rectangles
+ * that are unsorted or overlap; unioning such a set of rectangles 1-by-1
+ * using cairo_region_union_rectangle() produces O(N^2) behavior (if the union
+ * adds or removes rectangles in the middle of the region, then it has to
+ * move all the rectangles after that.) To avoid this behavior, MetaRegionBuilder
+ * creates regions for small groups of rectangles and merges them together in
+ * a binary tree.
+ *
+ * Possible improvement: From a glance at the code, accumulating all the rectangles
+ *  into a flat array and then calling the (not usefully documented)
+ *  cairo_region_create_rectangles() would have the same behavior and would be
+ *  simpler and a bit more efficient.
+ */
+
+/* Optimium performance seems to be with MAX_CHUNK_RECTANGLES=4; 8 is about 10% slower.
+ * But using 8 may be more robust to systems with slow malloc(). */
+#define MAX_CHUNK_RECTANGLES 8
+#define MAX_LEVELS 16
+
+typedef struct
+{
+  /* To merge regions in binary tree order, we need to keep track of
+   * the regions that we've already merged together at different
+   * levels of the tree. We fill in an array in the pattern:
+   *
+   * |a  |
+   * |b  |a  |
+   * |c  |   |ab |
+   * |d  |c  |ab |
+   * |e  |   |   |abcd|
+   */
+  cairo_region_t *levels[MAX_LEVELS];
+  int n_levels;
+} MetaRegionBuilder;
+
+static void
+meta_region_builder_init (MetaRegionBuilder *builder)
+{
+  int i;
+  for (i = 0; i < MAX_LEVELS; i++)
+    builder->levels[i] = NULL;
+  builder->n_levels = 1;
+}
+
+static void
+meta_region_builder_add_rectangle (MetaRegionBuilder *builder,
+                                   int                x,
+                                   int                y,
+                                   int                width,
+                                   int                height)
+{
+  cairo_rectangle_int_t rect;
+  int i;
+
+  if (builder->levels[0] == NULL)
+    builder->levels[0] = cairo_region_create ();
+
+  rect.x = x;
+  rect.y = y;
+  rect.width = width;
+  rect.height = height;
+
+  cairo_region_union_rectangle (builder->levels[0], &rect);
+  if (cairo_region_num_rectangles (builder->levels[0]) >= MAX_CHUNK_RECTANGLES)
+    {
+      for (i = 1; i < builder->n_levels + 1; i++)
+        {
+          if (builder->levels[i] == NULL)
+            {
+              if (i < MAX_LEVELS)
+                {
+                  builder->levels[i] = builder->levels[i - 1];
+                  builder->levels[i - 1] = NULL;
+                  if (i == builder->n_levels)
+                    builder->n_levels++;
+                }
+
+              break;
+            }
+          else
+            {
+              cairo_region_union (builder->levels[i], builder->levels[i - 1]);
+              cairo_region_destroy (builder->levels[i - 1]);
+              builder->levels[i - 1] = NULL;
+            }
+        }
+    }
+}
+
+static cairo_region_t *
+meta_region_builder_finish (MetaRegionBuilder *builder)
+{
+  cairo_region_t *result = NULL;
+  int i;
+
+  for (i = 0; i < builder->n_levels; i++)
+    {
+      if (builder->levels[i])
+        {
+          if (result == NULL)
+            result = builder->levels[i];
+          else
+            {
+              cairo_region_union(result, builder->levels[i]);
+              cairo_region_destroy (builder->levels[i]);
+            }
+        }
+    }
+
+  if (result == NULL)
+    result = cairo_region_create ();
+
+  return result;
+}
+
+
+/* MetaRegionIterator */
+
+void
+meta_region_iterator_init (MetaRegionIterator *iter,
+                           cairo_region_t     *region)
+{
+  iter->region = region;
+  iter->i = 0;
+  iter->n_rectangles = cairo_region_num_rectangles (region);
+  iter->line_start = TRUE;
+
+  if (iter->n_rectangles > 1)
+    {
+      cairo_region_get_rectangle (region, 0, &iter->rectangle);
+      cairo_region_get_rectangle (region, 1, &iter->next_rectangle);
+
+      iter->line_end = iter->next_rectangle.y != iter->rectangle.y;
+    }
+  else if (iter->n_rectangles > 0)
+    {
+      cairo_region_get_rectangle (region, 0, &iter->rectangle);
+      iter->line_end = TRUE;
+    }
+}
+
+gboolean
+meta_region_iterator_at_end (MetaRegionIterator *iter)
+{
+  return iter->i >= iter->n_rectangles;
+}
+
+void
+meta_region_iterator_next (MetaRegionIterator *iter)
+{
+  iter->i++;
+  iter->rectangle = iter->next_rectangle;
+  iter->line_start = iter->line_end;
+
+  if (iter->i < iter->n_rectangles)
+    {
+      cairo_region_get_rectangle (iter->region, iter->i + 1, &iter->next_rectangle);
+      iter->line_end = iter->next_rectangle.y != iter->rectangle.y;
+    }
+  else
+    {
+      iter->line_end = TRUE;
+    }
+}
+
+static void
+add_expanded_rect (MetaRegionBuilder  *builder,
+                   int                 x,
+                   int                 y,
+                   int                 width,
+                   int                 height,
+                   int                 x_amount,
+                   int                 y_amount,
+                   gboolean            flip)
+{
+  if (flip)
+    meta_region_builder_add_rectangle (builder,
+                                       y - y_amount, x - x_amount,
+                                       height + 2 * y_amount, width + 2 * x_amount);
+  else
+    meta_region_builder_add_rectangle (builder,
+                                       x - x_amount, y - y_amount,
+                                       width + 2 * x_amount, height + 2 * y_amount);
+}
+
+static cairo_region_t *
+expand_region (cairo_region_t *region,
+               int             x_amount,
+               int             y_amount,
+               gboolean        flip)
+{
+  MetaRegionBuilder builder;
+  int n;
+  int i;
+
+  meta_region_builder_init (&builder);
+
+  n = cairo_region_num_rectangles (region);
+  for (i = 0; i < n; i++)
+    {
+      cairo_rectangle_int_t rect;
+
+      cairo_region_get_rectangle (region, i, &rect);
+      add_expanded_rect (&builder,
+                         rect.x, rect.y, rect.width, rect.height,
+                         x_amount, y_amount, flip);
+    }
+
+  return meta_region_builder_finish (&builder);
+}
+
+/* This computes a (clipped version) of the inverse of the region
+ * and expands it by the given amount */
+static cairo_region_t *
+expand_region_inverse (cairo_region_t *region,
+                       int             x_amount,
+                       int             y_amount,
+                       gboolean        flip)
+{
+  MetaRegionBuilder builder;
+  MetaRegionIterator iter;
+  cairo_rectangle_int_t extents;
+  cairo_region_t *chunk;
+
+  int last_x;
+
+  meta_region_builder_init (&builder);
+
+  cairo_region_get_extents (region, &extents);
+  add_expanded_rect (&builder,
+                     extents.x, extents.y - 1, extents.width, 1,
+                     x_amount, y_amount, flip);
+  add_expanded_rect (&builder,
+                     extents.x - 1, extents.y, 1, extents.height,
+                     x_amount, y_amount, flip);
+  add_expanded_rect (&builder,
+                     extents.x + extents.width, extents.y, 1, extents.height,
+                     x_amount, y_amount, flip);
+  add_expanded_rect (&builder,
+                     extents.x, extents.y + extents.height, extents.width, 1,
+                     x_amount, y_amount, flip);
+
+  chunk = NULL;
+
+  last_x = extents.x;
+  for (meta_region_iterator_init (&iter, region);
+       !meta_region_iterator_at_end (&iter);
+       meta_region_iterator_next (&iter))
+    {
+      if (chunk == NULL)
+        chunk = cairo_region_create ();
+
+      if (iter.rectangle.x > last_x)
+        add_expanded_rect (&builder,
+                           last_x, iter.rectangle.y,
+                           iter.rectangle.x - last_x, iter.rectangle.height,
+                           x_amount, y_amount, flip);
+
+      if (iter.line_end)
+        {
+          if (extents.x + extents.width > iter.rectangle.x + iter.rectangle.width)
+            add_expanded_rect (&builder,
+                               iter.rectangle.x + iter.rectangle.width, iter.rectangle.y,
+                               (extents.x + extents.width) - (iter.rectangle.x + iter.rectangle.width), iter.rectangle.height,
+                               x_amount, y_amount, flip);
+          last_x = extents.x;
+        }
+      else
+        last_x = iter.rectangle.x + iter.rectangle.width;
+    }
+
+  return meta_region_builder_finish (&builder);
+}
+
+/**
+ * meta_make_border_region:
+ * @region: a #cairo_region_t
+ * @x_amount: distance from the border to extend horizontally
+ * @y_amount: distance from the border to extend vertically
+ * @flip: if true, the result is computed with x and y interchanged
+ *
+ * Computes the "border region" of a given region, which is roughly
+ * speaking the set of points near the boundary of the region.  If we
+ * define the operation of growing a region as computing the set of
+ * points within a given manhattan distance of the region, then the
+ * border is 'grow(region) intersect grow(inverse(region))'.
+ *
+ * If we create an image by filling the region with a solid color,
+ * the border is the region affected by blurring the region.
+ *
+ * Return value: a new region which is the border of the given region
+ */
+cairo_region_t *
+meta_make_border_region (cairo_region_t *region,
+                         int             x_amount,
+                         int             y_amount,
+                         gboolean        flip)
+{
+  cairo_region_t *border_region;
+  cairo_region_t *inverse_region;
+
+  border_region = expand_region (region, x_amount, y_amount, flip);
+  inverse_region = expand_region_inverse (region, x_amount, y_amount, flip);
+  cairo_region_intersect (border_region, inverse_region);
+  cairo_region_destroy (inverse_region);
+
+  return border_region;
+}
diff --git a/src/compositor/region-utils.h b/src/compositor/region-utils.h
new file mode 100644
index 0000000..d20105c
--- /dev/null
+++ b/src/compositor/region-utils.h
@@ -0,0 +1,76 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * Utilities for region manipulation
+ *
+ * Copyright (C) 2010 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef __META_REGION_UTILS_H__
+#define __META_REGION_UTILS_H__
+
+#include <clutter/clutter.h>
+
+#include <cairo.h>
+#include <glib.h>
+
+/**
+ * MetaRegionIterator:
+ * @region: region being iterated
+ * @rectangle: current rectangle
+ * @line_start: whether the current rectangle starts a horizontal band
+ * @line_end: whether the current rectangle ends a horizontal band
+ *
+ * cairo_region_t is a yx banded region; sometimes its useful to iterate through
+ * such a region treating the start and end of each horizontal band in a distinct
+ * fashion.
+ *
+ * Usage:
+ *
+ *  MetaRegionIterator iter;
+ *  for (meta_region_iterator_init (&iter, region);
+ *       !meta_region_iterator_at_end (&iter);
+ *       meta_region_iterator_next (&iter))
+ *  {
+ *    [ Use iter.rectangle, iter.line_start, iter.line_end ]
+ *  }
+ */
+typedef struct _MetaRegionIterator MetaRegionIterator;
+
+struct _MetaRegionIterator {
+  cairo_region_t *region;
+  cairo_rectangle_int_t rectangle;
+  gboolean line_start;
+  gboolean line_end;
+  int i;
+
+  /*< private >*/
+  int n_rectangles;
+  cairo_rectangle_int_t next_rectangle;
+};
+
+void     meta_region_iterator_init      (MetaRegionIterator *iter,
+                                         cairo_region_t     *region);
+gboolean meta_region_iterator_at_end    (MetaRegionIterator *iter);
+void     meta_region_iterator_next      (MetaRegionIterator *iter);
+
+cairo_region_t *meta_make_border_region (cairo_region_t *region,
+                                         int             x_amount,
+                                         int             y_amount,
+                                         gboolean        flip);
+
+#endif /* __META_REGION_UTILS_H__ */



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