From 9f4942e9a7d1fa06c6972a69eb386ff0075d3128 Mon Sep 17 00:00:00 2001 From: "Owen W. Taylor" Date: Mon, 18 Oct 2010 15:34:08 -0400 Subject: [PATCH] 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(-) create mode 100644 src/compositor/meta-shadow-factory.c create mode 100644 src/compositor/meta-shadow-factory.h create mode 100644 src/compositor/meta-window-shape.c create mode 100644 src/compositor/meta-window-shape.h create mode 100644 src/compositor/region-utils.c create mode 100644 src/compositor/region-utils.h delete mode 100644 src/compositor/shadow.c delete mode 100644 src/compositor/shadow.h delete mode 100644 src/compositor/tidy/tidy-texture-frame.c delete mode 100644 src/compositor/tidy/tidy-texture-frame.h diff --git a/src/Makefile.am b/src/Makefile.am index 09b90d51a..154f6add7 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 000000000..394de11bf --- /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 +#include +#include + +#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 000000000..824a3ac86 --- /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 +#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 756d234f8..c7f63abae 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); - compositor = meta_display_get_compositor (display); + if (newv == priv->shadow_radius) + return; - clutter_actor_get_size (CLUTTER_ACTOR (self), &w, &h); + 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); - priv->shadow = meta_create_shadow_frame (compositor); + if (newv == priv->shadow_x_offset) + return; - clutter_actor_set_size (priv->shadow, w, h); + 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_container_add_actor (CLUTTER_CONTAINER (self), priv->shadow); - } + if (newv == priv->shadow_y_offset) + return; + + priv->shadow_y_offset = newv; + clutter_actor_queue_redraw (CLUTTER_ACTOR (self)); + } + break; + case PROP_SHADOW_OPACITY: + { + guint newv = g_value_get_uint (value); + + if (newv == priv->shadow_opacity) + return; + + 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) { @@ -585,19 +718,8 @@ meta_window_actor_has_shadow (MetaWindowActor *self) return FALSE; } - /* - * 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); + meta_window_actor_get_shape_bounds (self, &shape_bounds); - 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_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); - overlap = cairo_region_contains_rectangle (beneath_region, &shadow_rect); - - 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 000000000..258079339 --- /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 + +#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 000000000..1dd6c70fe --- /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 +#include + +/** + * 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 000000000..9cfc39385 --- /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 + +/* 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 000000000..d20105c99 --- /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 + +#include +#include + +/** + * 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__ */ diff --git a/src/compositor/shadow.c b/src/compositor/shadow.c deleted file mode 100644 index db8495bb0..000000000 --- a/src/compositor/shadow.c +++ /dev/null @@ -1,350 +0,0 @@ -/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ - -#define _GNU_SOURCE /* For M_PI */ - -#include - -#include "compositor-private.h" -#include "shadow.h" -#include "tidy/tidy-texture-frame.h" - -#define SHADOW_RADIUS 8 -#define SHADOW_OPACITY 0.9 -#define SHADOW_OFFSET_X (SHADOW_RADIUS) -#define SHADOW_OFFSET_Y (SHADOW_RADIUS) - -#define MAX_TILE_SZ 8 /* Must be <= shaddow radius */ -#define TILE_WIDTH (3*MAX_TILE_SZ) -#define TILE_HEIGHT (3*MAX_TILE_SZ) - -static unsigned char* shadow_gaussian_make_tile (void); - -ClutterActor * -meta_create_shadow_frame (MetaCompositor *compositor) -{ - ClutterActor *frame; - - if (!compositor->shadow_src) - { - guchar *data; - - data = shadow_gaussian_make_tile (); - - compositor->shadow_src = clutter_texture_new (); - - clutter_texture_set_from_rgb_data (CLUTTER_TEXTURE (compositor->shadow_src), - data, - TRUE, - TILE_WIDTH, - TILE_HEIGHT, - TILE_WIDTH*4, - 4, - 0, - NULL); - g_free (data); - } - - frame = tidy_texture_frame_new (CLUTTER_TEXTURE (compositor->shadow_src), - MAX_TILE_SZ, - MAX_TILE_SZ, - MAX_TILE_SZ, - MAX_TILE_SZ); - - clutter_actor_set_position (frame, - SHADOW_OFFSET_X , SHADOW_OFFSET_Y); - - return frame; -} - -typedef struct GaussianMap -{ - int size; - double * data; -} GaussianMap; - -static double -gaussian (double r, double x, double y) -{ - return ((1 / (sqrt (2 * M_PI * r))) * - exp ((- (x * x + y * y)) / (2 * r * r))); -} - - -static GaussianMap * -make_gaussian_map (double r) -{ - GaussianMap *c; - int size = ((int) ceil ((r * 3)) + 1) & ~1; - int center = size / 2; - int x, y; - double t = 0.0; - double g; - - c = g_malloc (sizeof (GaussianMap) + size * size * sizeof (double)); - c->size = size; - - c->data = (double *) (c + 1); - - for (y = 0; y < size; y++) - for (x = 0; x < size; x++) - { - g = gaussian (r, (double) (x - center), (double) (y - center)); - t += g; - c->data[y * size + x] = g; - } - - for (y = 0; y < size; y++) - for (x = 0; x < size; x++) - c->data[y*size + x] /= t; - - return c; -} - -static unsigned char -sum_gaussian (GaussianMap * map, double opacity, - int x, int y, int width, int height) -{ - int fx, fy; - double * g_data; - double * g_line = map->data; - int g_size = map->size; - int center = g_size / 2; - int fx_start, fx_end; - int fy_start, fy_end; - double v; - unsigned int r; - - /* - * Compute set of filter values which are "in range", - * that's the set with: - * 0 <= x + (fx-center) && x + (fx-center) < width && - * 0 <= y + (fy-center) && y + (fy-center) < height - * - * 0 <= x + (fx - center) x + fx - center < width - * center - x <= fx fx < width + center - x - */ - - fx_start = center - x; - if (fx_start < 0) - fx_start = 0; - fx_end = width + center - x; - if (fx_end > g_size) - fx_end = g_size; - - fy_start = center - y; - if (fy_start < 0) - fy_start = 0; - fy_end = height + center - y; - if (fy_end > g_size) - fy_end = g_size; - - g_line = g_line + fy_start * g_size + fx_start; - - v = 0; - for (fy = fy_start; fy < fy_end; fy++) - { - g_data = g_line; - g_line += g_size; - - for (fx = fx_start; fx < fx_end; fx++) - v += *g_data++; - } - if (v > 1) - v = 1; - - v *= (opacity * 255.0); - - r = (unsigned int) v; - - return (unsigned char) r; -} - -static unsigned char * -shadow_gaussian_make_tile () -{ - unsigned char * data; - int size; - int center; - int x, y; - unsigned char d; - int pwidth, pheight; - double opacity = SHADOW_OPACITY; - static GaussianMap * gaussian_map = NULL; - - struct _mypixel - { - unsigned char r; - unsigned char g; - unsigned char b; - unsigned char a; - } * _d; - - - if (!gaussian_map) - gaussian_map = - make_gaussian_map (SHADOW_RADIUS); - - size = gaussian_map->size; - center = size / 2; - - /* Top & bottom */ - - pwidth = MAX_TILE_SZ; - pheight = MAX_TILE_SZ; - - data = g_malloc0 (4 * TILE_WIDTH * TILE_HEIGHT); - - _d = (struct _mypixel*) data; - - /* N */ - for (y = 0; y < pheight; y++) - { - d = sum_gaussian (gaussian_map, opacity, - center, y - center, - TILE_WIDTH, TILE_HEIGHT); - for (x = 0; x < pwidth; x++) - { - _d[y*3*pwidth + x + pwidth].r = 0; - _d[y*3*pwidth + x + pwidth].g = 0; - _d[y*3*pwidth + x + pwidth].b = 0; - _d[y*3*pwidth + x + pwidth].a = d; - } - - } - - /* S */ - pwidth = MAX_TILE_SZ; - pheight = MAX_TILE_SZ; - - for (y = 0; y < pheight; y++) - { - d = sum_gaussian (gaussian_map, opacity, - center, y - center, - TILE_WIDTH, TILE_HEIGHT); - for (x = 0; x < pwidth; x++) - { - _d[(pheight-y-1)*3*pwidth + 6*pwidth*pheight + x + pwidth].r = 0; - _d[(pheight-y-1)*3*pwidth + 6*pwidth*pheight + x + pwidth].g = 0; - _d[(pheight-y-1)*3*pwidth + 6*pwidth*pheight + x + pwidth].b = 0; - _d[(pheight-y-1)*3*pwidth + 6*pwidth*pheight + x + pwidth].a = d; - } - - } - - - /* w */ - pwidth = MAX_TILE_SZ; - pheight = MAX_TILE_SZ; - - for (x = 0; x < pwidth; x++) - { - d = sum_gaussian (gaussian_map, opacity, - x - center, center, - TILE_WIDTH, TILE_HEIGHT); - for (y = 0; y < pheight; y++) - { - _d[y*3*pwidth + 3*pwidth*pheight + x].r = 0; - _d[y*3*pwidth + 3*pwidth*pheight + x].g = 0; - _d[y*3*pwidth + 3*pwidth*pheight + x].b = 0; - _d[y*3*pwidth + 3*pwidth*pheight + x].a = d; - } - - } - - /* E */ - for (x = 0; x < pwidth; x++) - { - d = sum_gaussian (gaussian_map, opacity, - x - center, center, - TILE_WIDTH, TILE_HEIGHT); - for (y = 0; y < pheight; y++) - { - _d[y*3*pwidth + 3*pwidth*pheight + (pwidth-x-1) + 2*pwidth].r = 0; - _d[y*3*pwidth + 3*pwidth*pheight + (pwidth-x-1) + 2*pwidth].g = 0; - _d[y*3*pwidth + 3*pwidth*pheight + (pwidth-x-1) + 2*pwidth].b = 0; - _d[y*3*pwidth + 3*pwidth*pheight + (pwidth-x-1) + 2*pwidth].a = d; - } - - } - - /* NW */ - pwidth = MAX_TILE_SZ; - pheight = MAX_TILE_SZ; - - for (x = 0; x < pwidth; x++) - for (y = 0; y < pheight; y++) - { - d = sum_gaussian (gaussian_map, opacity, - x-center, y-center, - TILE_WIDTH, TILE_HEIGHT); - - _d[y*3*pwidth + x].r = 0; - _d[y*3*pwidth + x].g = 0; - _d[y*3*pwidth + x].b = 0; - _d[y*3*pwidth + x].a = d; - } - - /* SW */ - for (x = 0; x < pwidth; x++) - for (y = 0; y < pheight; y++) - { - d = sum_gaussian (gaussian_map, opacity, - x-center, y-center, - TILE_WIDTH, TILE_HEIGHT); - - _d[(pheight-y-1)*3*pwidth + 6*pwidth*pheight + x].r = 0; - _d[(pheight-y-1)*3*pwidth + 6*pwidth*pheight + x].g = 0; - _d[(pheight-y-1)*3*pwidth + 6*pwidth*pheight + x].b = 0; - _d[(pheight-y-1)*3*pwidth + 6*pwidth*pheight + x].a = d; - } - - /* SE */ - for (x = 0; x < pwidth; x++) - for (y = 0; y < pheight; y++) - { - d = sum_gaussian (gaussian_map, opacity, - x-center, y-center, - TILE_WIDTH, TILE_HEIGHT); - - _d[(pheight-y-1)*3*pwidth + 6*pwidth*pheight + (pwidth-x-1) + - 2*pwidth].r = 0; - _d[(pheight-y-1)*3*pwidth + 6*pwidth*pheight + (pwidth-x-1) + - 2*pwidth].g = 0; - _d[(pheight-y-1)*3*pwidth + 6*pwidth*pheight + (pwidth-x-1) + - 2*pwidth].b = 0; - _d[(pheight-y-1)*3*pwidth + 6*pwidth*pheight + (pwidth-x-1) + - 2*pwidth].a = d; - } - - /* NE */ - for (x = 0; x < pwidth; x++) - for (y = 0; y < pheight; y++) - { - d = sum_gaussian (gaussian_map, opacity, - x-center, y-center, - TILE_WIDTH, TILE_HEIGHT); - - _d[y*3*pwidth + (pwidth - x - 1) + 2*pwidth].r = 0; - _d[y*3*pwidth + (pwidth - x - 1) + 2*pwidth].g = 0; - _d[y*3*pwidth + (pwidth - x - 1) + 2*pwidth].b = 0; - _d[y*3*pwidth + (pwidth - x - 1) + 2*pwidth].a = d; - } - - /* center */ - pwidth = MAX_TILE_SZ; - pheight = MAX_TILE_SZ; - - d = sum_gaussian (gaussian_map, opacity, - center, center, TILE_WIDTH, TILE_HEIGHT); - - for (x = 0; x < pwidth; x++) - for (y = 0; y < pheight; y++) - { - _d[y*3*pwidth + 3*pwidth*pheight + x + pwidth].r = 0; - _d[y*3*pwidth + 3*pwidth*pheight + x + pwidth].g = 0; - _d[y*3*pwidth + 3*pwidth*pheight + x + pwidth].b = 0; - _d[y*3*pwidth + 3*pwidth*pheight + x + pwidth].a = 0; - } - - return data; -} diff --git a/src/compositor/shadow.h b/src/compositor/shadow.h deleted file mode 100644 index 1277e237e..000000000 --- a/src/compositor/shadow.h +++ /dev/null @@ -1,9 +0,0 @@ -#ifndef SHADOW_H -#define SHADOW_H - -#include -#include "compositor.h" - -ClutterActor *meta_create_shadow_frame (MetaCompositor *compositor); - -#endif /* SHADOW_H */ diff --git a/src/compositor/tidy/tidy-texture-frame.c b/src/compositor/tidy/tidy-texture-frame.c deleted file mode 100644 index 7ba4671e6..000000000 --- a/src/compositor/tidy/tidy-texture-frame.c +++ /dev/null @@ -1,641 +0,0 @@ -/* tidy-texture-frame.h: Expandible texture actor - * - * Copyright (C) 2007 OpenedHand - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * Boston, MA 02111-1307, USA. - */ - -/** - * SECTION:tidy-texture-frame - * @short_description: Stretch a texture to fit the entire allocation - * - * #TidyTextureFrame - * - */ - -#include - -#include "tidy-texture-frame.h" - -#define TIDY_PARAM_READABLE \ - (G_PARAM_READABLE | \ - G_PARAM_STATIC_NICK | G_PARAM_STATIC_NAME | G_PARAM_STATIC_BLURB) - -#define TIDY_PARAM_READWRITE \ - (G_PARAM_READABLE | G_PARAM_WRITABLE | \ - G_PARAM_STATIC_NICK | G_PARAM_STATIC_NAME | G_PARAM_STATIC_BLURB) - -enum -{ - PROP_0, - - PROP_PARENT_TEXTURE, - - PROP_LEFT, - PROP_TOP, - PROP_RIGHT, - PROP_BOTTOM -}; - -G_DEFINE_TYPE (TidyTextureFrame, tidy_texture_frame, CLUTTER_TYPE_ACTOR); - -#define TIDY_TEXTURE_FRAME_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), TIDY_TYPE_TEXTURE_FRAME, TidyTextureFramePrivate)) - -struct _TidyTextureFramePrivate -{ - ClutterTexture *parent_texture; - - gfloat left; - gfloat top; - gfloat right; - gfloat bottom; - - CoglHandle material; - - guint needs_paint : 1; -}; - -static void -tidy_texture_frame_get_preferred_width (ClutterActor *self, - gfloat for_height, - gfloat *min_width_p, - gfloat *natural_width_p) -{ - TidyTextureFramePrivate *priv = TIDY_TEXTURE_FRAME (self)->priv; - - if (G_UNLIKELY (priv->parent_texture == NULL)) - { - if (min_width_p) - *min_width_p = 0; - - if (natural_width_p) - *natural_width_p = 0; - } - else - { - ClutterActorClass *klass; - - /* by directly querying the parent texture's class implementation - * we are going around any override mechanism the parent texture - * might have in place, and we ask directly for the original - * preferred width - */ - klass = CLUTTER_ACTOR_GET_CLASS (priv->parent_texture); - klass->get_preferred_width (CLUTTER_ACTOR (priv->parent_texture), - for_height, - min_width_p, - natural_width_p); - } -} - -static void -tidy_texture_frame_get_preferred_height (ClutterActor *self, - gfloat for_width, - gfloat *min_height_p, - gfloat *natural_height_p) -{ - TidyTextureFramePrivate *priv = TIDY_TEXTURE_FRAME (self)->priv; - - if (G_UNLIKELY (priv->parent_texture == NULL)) - { - if (min_height_p) - *min_height_p = 0; - - if (natural_height_p) - *natural_height_p = 0; - } - else - { - ClutterActorClass *klass; - - /* by directly querying the parent texture's class implementation - * we are going around any override mechanism the parent texture - * might have in place, and we ask directly for the original - * preferred height - */ - klass = CLUTTER_ACTOR_GET_CLASS (priv->parent_texture); - klass->get_preferred_height (CLUTTER_ACTOR (priv->parent_texture), - for_width, - min_height_p, - natural_height_p); - } -} - -static void -tidy_texture_frame_realize (ClutterActor *self) -{ - TidyTextureFramePrivate *priv = TIDY_TEXTURE_FRAME (self)->priv; - - if (priv->material != COGL_INVALID_HANDLE) - return; - - priv->material = cogl_material_new (); - - CLUTTER_ACTOR_SET_FLAGS (self, CLUTTER_ACTOR_REALIZED); -} - -static void -tidy_texture_frame_unrealize (ClutterActor *self) -{ - TidyTextureFramePrivate *priv = TIDY_TEXTURE_FRAME (self)->priv; - - if (priv->material == COGL_INVALID_HANDLE) - return; - - cogl_handle_unref (priv->material); - priv->material = COGL_INVALID_HANDLE; - - CLUTTER_ACTOR_UNSET_FLAGS (self, CLUTTER_ACTOR_REALIZED); -} - -static void -tidy_texture_frame_paint (ClutterActor *self) -{ - TidyTextureFramePrivate *priv = TIDY_TEXTURE_FRAME (self)->priv; - CoglHandle cogl_texture = COGL_INVALID_HANDLE; - ClutterActorBox box = { 0, }; - gfloat width, height; - gfloat tex_width, tex_height; - gfloat ex, ey; - gfloat tx1, ty1, tx2, ty2; - guint8 opacity; - - /* no need to paint stuff if we don't have a texture */ - if (G_UNLIKELY (priv->parent_texture == NULL)) - return; - - if (!priv->needs_paint) - return; - - /* parent texture may have been hidden, so need to make sure it gets - * realized - */ - if (!CLUTTER_ACTOR_IS_REALIZED (priv->parent_texture)) - clutter_actor_realize (CLUTTER_ACTOR (priv->parent_texture)); - - cogl_texture = clutter_texture_get_cogl_texture (priv->parent_texture); - if (cogl_texture == COGL_INVALID_HANDLE) - return; - - tex_width = cogl_texture_get_width (cogl_texture); - tex_height = cogl_texture_get_height (cogl_texture); - - clutter_actor_get_allocation_box (self, &box); - width = box.x2 - box.x1; - height = box.y2 - box.y1; - - tx1 = priv->left / tex_width; - tx2 = (tex_width - priv->right) / tex_width; - ty1 = priv->top / tex_height; - ty2 = (tex_height - priv->bottom) / tex_height; - - ex = width - priv->right; - if (ex < 0) - ex = priv->right; /* FIXME ? */ - - ey = height - priv->bottom; - if (ey < 0) - ey = priv->bottom; /* FIXME ? */ - - opacity = clutter_actor_get_paint_opacity (self); - - g_assert (priv->material != COGL_INVALID_HANDLE); - - /* set the source material using the parent texture's COGL handle */ - cogl_material_set_color4ub (priv->material, opacity, opacity, opacity, opacity); - cogl_material_set_layer (priv->material, 0, cogl_texture); - cogl_set_source (priv->material); - - /* top left corner */ - cogl_rectangle_with_texture_coords (0, 0, priv->left, priv->top, - 0.0, 0.0, - tx1, ty1); - - /* top middle */ - cogl_rectangle_with_texture_coords (priv->left, 0, ex, priv->top, - tx1, 0.0, - tx2, ty1); - - /* top right */ - cogl_rectangle_with_texture_coords (ex, 0, width, priv->top, - tx2, 0.0, - 1.0, ty1); - - /* mid left */ - cogl_rectangle_with_texture_coords (0, priv->top, priv->left, ey, - 0.0, ty1, - tx1, ty2); - - /* center */ - cogl_rectangle_with_texture_coords (priv->left, priv->top, ex, ey, - tx1, ty1, - tx2, ty2); - - /* mid right */ - cogl_rectangle_with_texture_coords (ex, priv->top, width, ey, - tx2, ty1, - 1.0, ty2); - - /* bottom left */ - cogl_rectangle_with_texture_coords (0, ey, priv->left, height, - 0.0, ty2, - tx1, 1.0); - - /* bottom center */ - cogl_rectangle_with_texture_coords (priv->left, ey, ex, height, - tx1, ty2, - tx2, 1.0); - - /* bottom right */ - cogl_rectangle_with_texture_coords (ex, ey, width, height, - tx2, ty2, - 1.0, 1.0); -} - -static inline void -tidy_texture_frame_set_frame_internal (TidyTextureFrame *frame, - gfloat left, - gfloat top, - gfloat right, - gfloat bottom) -{ - TidyTextureFramePrivate *priv = frame->priv; - GObject *gobject = G_OBJECT (frame); - gboolean changed = FALSE; - - g_object_freeze_notify (gobject); - - if (priv->top != top) - { - priv->top = top; - g_object_notify (gobject, "top"); - changed = TRUE; - } - - if (priv->right != right) - { - priv->right = right; - g_object_notify (gobject, "right"); - changed = TRUE; - } - - if (priv->bottom != bottom) - { - priv->bottom = bottom; - g_object_notify (gobject, "bottom"); - changed = TRUE; - } - - if (priv->left != left) - { - priv->left = left; - g_object_notify (gobject, "left"); - changed = TRUE; - } - - if (changed && CLUTTER_ACTOR_IS_VISIBLE (frame)) - clutter_actor_queue_redraw (CLUTTER_ACTOR (frame)); - - g_object_thaw_notify (gobject); -} - -static void -tidy_texture_frame_set_property (GObject *gobject, - guint prop_id, - const GValue *value, - GParamSpec *pspec) -{ - TidyTextureFrame *frame = TIDY_TEXTURE_FRAME (gobject); - TidyTextureFramePrivate *priv = frame->priv; - - switch (prop_id) - { - case PROP_PARENT_TEXTURE: - tidy_texture_frame_set_parent_texture (frame, - g_value_get_object (value)); - break; - - case PROP_TOP: - tidy_texture_frame_set_frame_internal (frame, - priv->left, - g_value_get_float (value), - priv->right, - priv->bottom); - break; - - case PROP_RIGHT: - tidy_texture_frame_set_frame_internal (frame, - priv->top, - g_value_get_float (value), - priv->bottom, - priv->left); - break; - - case PROP_BOTTOM: - tidy_texture_frame_set_frame_internal (frame, - priv->top, - priv->right, - g_value_get_float (value), - priv->left); - break; - - case PROP_LEFT: - tidy_texture_frame_set_frame_internal (frame, - priv->top, - priv->right, - priv->bottom, - g_value_get_float (value)); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); - break; - } -} - -static void -tidy_texture_frame_get_property (GObject *gobject, - guint prop_id, - GValue *value, - GParamSpec *pspec) -{ - TidyTextureFramePrivate *priv = TIDY_TEXTURE_FRAME (gobject)->priv; - - switch (prop_id) - { - case PROP_PARENT_TEXTURE: - g_value_set_object (value, priv->parent_texture); - break; - - case PROP_LEFT: - g_value_set_float (value, priv->left); - break; - - case PROP_TOP: - g_value_set_float (value, priv->top); - break; - - case PROP_RIGHT: - g_value_set_float (value, priv->right); - break; - - case PROP_BOTTOM: - g_value_set_float (value, priv->bottom); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); - break; - } -} - -static void -tidy_texture_frame_dispose (GObject *gobject) -{ - TidyTextureFramePrivate *priv = TIDY_TEXTURE_FRAME (gobject)->priv; - - if (priv->parent_texture) - { - g_object_unref (priv->parent_texture); - priv->parent_texture = NULL; - } - - if (priv->material) - { - cogl_handle_unref (priv->material); - priv->material = COGL_INVALID_HANDLE; - } - - G_OBJECT_CLASS (tidy_texture_frame_parent_class)->dispose (gobject); -} - -static void -tidy_texture_frame_class_init (TidyTextureFrameClass *klass) -{ - GObjectClass *gobject_class = G_OBJECT_CLASS (klass); - ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass); - GParamSpec *pspec; - - g_type_class_add_private (gobject_class, sizeof (TidyTextureFramePrivate)); - - actor_class->get_preferred_width = - tidy_texture_frame_get_preferred_width; - actor_class->get_preferred_height = - tidy_texture_frame_get_preferred_height; - actor_class->realize = tidy_texture_frame_realize; - actor_class->unrealize = tidy_texture_frame_unrealize; - actor_class->paint = tidy_texture_frame_paint; - - gobject_class->set_property = tidy_texture_frame_set_property; - gobject_class->get_property = tidy_texture_frame_get_property; - gobject_class->dispose = tidy_texture_frame_dispose; - - pspec = g_param_spec_object ("parent-texture", - "Parent Texture", - "The parent ClutterTexture", - CLUTTER_TYPE_TEXTURE, - TIDY_PARAM_READWRITE | - G_PARAM_CONSTRUCT); - g_object_class_install_property (gobject_class, PROP_PARENT_TEXTURE, pspec); - - pspec = g_param_spec_float ("left", - "Left", - "Left offset", - 0, G_MAXFLOAT, - 0, - TIDY_PARAM_READWRITE); - g_object_class_install_property (gobject_class, PROP_LEFT, pspec); - - pspec = g_param_spec_float ("top", - "Top", - "Top offset", - 0, G_MAXFLOAT, - 0, - TIDY_PARAM_READWRITE); - g_object_class_install_property (gobject_class, PROP_TOP, pspec); - - pspec = g_param_spec_float ("bottom", - "Bottom", - "Bottom offset", - 0, G_MAXFLOAT, - 0, - TIDY_PARAM_READWRITE); - g_object_class_install_property (gobject_class, PROP_BOTTOM, pspec); - - pspec = g_param_spec_float ("right", - "Right", - "Right offset", - 0, G_MAXFLOAT, - 0, - TIDY_PARAM_READWRITE); - g_object_class_install_property (gobject_class, PROP_RIGHT, pspec); -} - -static void -tidy_texture_frame_init (TidyTextureFrame *self) -{ - TidyTextureFramePrivate *priv; - - self->priv = priv = TIDY_TEXTURE_FRAME_GET_PRIVATE (self); - - priv->material = COGL_INVALID_HANDLE; -} - -/** - * tidy_texture_frame_new: - * @texture: a #ClutterTexture or %NULL - * @left: left margin preserving its content - * @top: top margin preserving its content - * @right: right margin preserving its content - * @bottom: bottom margin preserving its content - * - * A #TidyTextureFrame is a specialized texture that efficiently clones - * an area of the given @texture while keeping preserving portions of the - * same texture. - * - * A #TidyTextureFrame can be used to make a rectangular texture fit a - * given size without stretching its borders. - * - * Return value: the newly created #TidyTextureFrame - */ -ClutterActor* -tidy_texture_frame_new (ClutterTexture *texture, - gfloat left, - gfloat top, - gfloat right, - gfloat bottom) -{ - g_return_val_if_fail (texture == NULL || CLUTTER_IS_TEXTURE (texture), NULL); - - return g_object_new (TIDY_TYPE_TEXTURE_FRAME, - "parent-texture", texture, - "left", left, - "top", top, - "right", right, - "bottom", bottom, - NULL); -} - -ClutterTexture * -tidy_texture_frame_get_parent_texture (TidyTextureFrame *frame) -{ - g_return_val_if_fail (TIDY_IS_TEXTURE_FRAME (frame), NULL); - - return frame->priv->parent_texture; -} - -void -tidy_texture_frame_set_parent_texture (TidyTextureFrame *frame, - ClutterTexture *texture) -{ - TidyTextureFramePrivate *priv; - gboolean was_visible; - - g_return_if_fail (TIDY_IS_TEXTURE_FRAME (frame)); - g_return_if_fail (texture == NULL || CLUTTER_IS_TEXTURE (texture)); - - priv = frame->priv; - - was_visible = CLUTTER_ACTOR_IS_VISIBLE (frame); - - if (priv->parent_texture == texture) - return; - - if (priv->parent_texture) - { - g_object_unref (priv->parent_texture); - priv->parent_texture = NULL; - - if (was_visible) - clutter_actor_hide (CLUTTER_ACTOR (frame)); - } - - if (texture) - { - priv->parent_texture = g_object_ref (texture); - - if (was_visible && CLUTTER_ACTOR_IS_VISIBLE (priv->parent_texture)) - clutter_actor_show (CLUTTER_ACTOR (frame)); - } - - clutter_actor_queue_relayout (CLUTTER_ACTOR (frame)); - - g_object_notify (G_OBJECT (frame), "parent-texture"); -} - -void -tidy_texture_frame_set_frame (TidyTextureFrame *frame, - gfloat top, - gfloat right, - gfloat bottom, - gfloat left) -{ - g_return_if_fail (TIDY_IS_TEXTURE_FRAME (frame)); - - tidy_texture_frame_set_frame_internal (frame, top, right, bottom, left); -} - -void -tidy_texture_frame_get_frame (TidyTextureFrame *frame, - gfloat *top, - gfloat *right, - gfloat *bottom, - gfloat *left) -{ - TidyTextureFramePrivate *priv; - - g_return_if_fail (TIDY_IS_TEXTURE_FRAME (frame)); - - priv = frame->priv; - - if (top) - *top = priv->top; - - if (right) - *right = priv->right; - - if (bottom) - *bottom = priv->bottom; - - if (left) - *left = priv->left; -} - -/** - * tidy_texture_frame_set_needs_paint: - * @frame: a #TidyTextureframe - * @needs_paint: if %FALSE, the paint will be skipped - * - * Provides a hint to the texture frame that it is totally obscured - * and doesn't need to be painted. This would typically be called - * by a parent container if it detects the condition prior to - * painting its children and then unset afterwards. - * - * Since it is not supposed to have any effect on display, it does - * not queue a repaint. - */ -void -tidy_texture_frame_set_needs_paint (TidyTextureFrame *frame, - gboolean needs_paint) -{ - TidyTextureFramePrivate *priv; - - g_return_if_fail (TIDY_IS_TEXTURE_FRAME (frame)); - - priv = frame->priv; - - priv->needs_paint = needs_paint; -} diff --git a/src/compositor/tidy/tidy-texture-frame.h b/src/compositor/tidy/tidy-texture-frame.h deleted file mode 100644 index 71dd40c00..000000000 --- a/src/compositor/tidy/tidy-texture-frame.h +++ /dev/null @@ -1,84 +0,0 @@ -/* tidy-texture-frame.h: Expandible texture actor - * - * Copyright (C) 2007, 2008 OpenedHand Ltd - * Copyright (C) 2009 Intel Corp. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * Boston, MA 02111-1307, USA. - */ - -#ifndef _HAVE_TIDY_TEXTURE_FRAME_H -#define _HAVE_TIDY_TEXTURE_FRAME_H - -#include - -G_BEGIN_DECLS - -#define TIDY_TYPE_TEXTURE_FRAME (tidy_texture_frame_get_type ()) -#define TIDY_TEXTURE_FRAME(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), TIDY_TYPE_TEXTURE_FRAME, TidyTextureFrame)) -#define TIDY_TEXTURE_FRAME_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), TIDY_TYPE_TEXTURE_FRAME, TidyTextureFrameClass)) -#define TIDY_IS_TEXTURE_FRAME(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TIDY_TYPE_TEXTURE_FRAME)) -#define TIDY_IS_TEXTURE_FRAME_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TIDY_TYPE_TEXTURE_FRAME)) -#define TIDY_TEXTURE_FRAME_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), TIDY_TYPE_TEXTURE_FRAME, TidyTextureFrameClass)) - -typedef struct _TidyTextureFrame TidyTextureFrame; -typedef struct _TidyTextureFramePrivate TidyTextureFramePrivate; -typedef struct _TidyTextureFrameClass TidyTextureFrameClass; - -struct _TidyTextureFrame -{ - /*< private >*/ - ClutterActor parent_instance; - - TidyTextureFramePrivate *priv; -}; - -struct _TidyTextureFrameClass -{ - ClutterActorClass parent_class; - - /* padding for future expansion */ - void (*_clutter_box_1) (void); - void (*_clutter_box_2) (void); - void (*_clutter_box_3) (void); - void (*_clutter_box_4) (void); -}; - -GType tidy_texture_frame_get_type (void) G_GNUC_CONST; -ClutterActor * tidy_texture_frame_new (ClutterTexture *texture, - gfloat top, - gfloat right, - gfloat bottom, - gfloat left); -void tidy_texture_frame_set_parent_texture (TidyTextureFrame *frame, - ClutterTexture *texture); -ClutterTexture *tidy_texture_frame_get_parent_texture (TidyTextureFrame *frame); -void tidy_texture_frame_set_frame (TidyTextureFrame *frame, - gfloat top, - gfloat right, - gfloat bottom, - gfloat left); -void tidy_texture_frame_get_frame (TidyTextureFrame *frame, - gfloat *top, - gfloat *right, - gfloat *bottom, - gfloat *left); - -void tidy_texture_frame_set_needs_paint (TidyTextureFrame *frame, - gboolean needs_paint); - -G_END_DECLS - -#endif /* _HAVE_TIDY_TEXTURE_FRAME_H */