[mutter] Make shadows pretty and configurable
- From: Owen Taylor <otaylor src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [mutter] Make shadows pretty and configurable
- Date: Thu, 18 Nov 2010 14:48:25 +0000 (UTC)
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]