diff options
| author | Owen W. Taylor <otaylor@fishsoup.net> | 2010-10-18 15:34:08 -0400 |
|---|---|---|
| committer | Owen W. Taylor <otaylor@fishsoup.net> | 2010-11-18 09:47:56 -0500 |
| commit | 9f4942e9a7d1fa06c6972a69eb386ff0075d3128 (patch) | |
| tree | 2d6ae7b5c11d0cea3140315b1885c564af393bca /src/compositor/region-utils.c | |
| parent | 982a10ac44cb09b51ea18a6515cc374a15a2f5af (diff) | |
| download | mutter-9f4942e9a7d1fa06c6972a69eb386ff0075d3128.tar.gz | |
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
Diffstat (limited to 'src/compositor/region-utils.c')
| -rw-r--r-- | src/compositor/region-utils.c | 336 |
1 files changed, 336 insertions, 0 deletions
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 <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; +} |
