/* -*- 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, see . */ #include "config.h" #include "backends/meta-monitor-transform.h" #include "compositor/region-utils.h" #include "core/boxes-private.h" #include #define META_REGION_MAX_STACK_RECTS 256 #define META_REGION_CREATE_RECTANGLE_ARRAY_SCOPED(n_rects, rects) \ g_autofree cairo_rectangle_int_t *G_PASTE(__n, __LINE__) = NULL; \ if (n_rects < META_REGION_MAX_STACK_RECTS) \ rects = g_newa (cairo_rectangle_int_t, n_rects); \ else \ rects = G_PASTE(__n, __LINE__) = g_new (cairo_rectangle_int_t, n_rects); /* 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 void meta_region_builder_init (MetaRegionBuilder *builder) { int i; for (i = 0; i < META_REGION_BUILDER_MAX_LEVELS; i++) builder->levels[i] = NULL; builder->n_levels = 1; } 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 < META_REGION_BUILDER_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; } } } } 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 + 1 < 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; } } cairo_region_t * meta_region_scale_double (cairo_region_t *region, double scale, MetaRoundingStrategy rounding_strategy) { int n_rects, i; cairo_rectangle_int_t *rects; cairo_region_t *scaled_region; g_return_val_if_fail (scale > 0.0, NULL); if (G_APPROX_VALUE (scale, 1.f, FLT_EPSILON)) return cairo_region_copy (region); n_rects = cairo_region_num_rectangles (region); META_REGION_CREATE_RECTANGLE_ARRAY_SCOPED (n_rects, rects); for (i = 0; i < n_rects; i++) { cairo_region_get_rectangle (region, i, &rects[i]); meta_rectangle_scale_double (&rects[i], scale, rounding_strategy, &rects[i]); } scaled_region = cairo_region_create_rectangles (rects, n_rects); return scaled_region; } cairo_region_t * meta_region_scale (cairo_region_t *region, int scale) { int n_rects, i; cairo_rectangle_int_t *rects; cairo_region_t *scaled_region; if (scale == 1) return cairo_region_copy (region); n_rects = cairo_region_num_rectangles (region); META_REGION_CREATE_RECTANGLE_ARRAY_SCOPED (n_rects, rects); for (i = 0; i < n_rects; i++) { cairo_region_get_rectangle (region, i, &rects[i]); rects[i].x *= scale; rects[i].y *= scale; rects[i].width *= scale; rects[i].height *= scale; } scaled_region = cairo_region_create_rectangles (rects, n_rects); return scaled_region; } 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; 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); last_x = extents.x; for (meta_region_iterator_init (&iter, region); !meta_region_iterator_at_end (&iter); meta_region_iterator_next (&iter)) { 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; } cairo_region_t * meta_region_transform (const cairo_region_t *region, MetaMonitorTransform transform, int width, int height) { int n_rects, i; cairo_rectangle_int_t *rects; cairo_region_t *transformed_region; if (transform == META_MONITOR_TRANSFORM_NORMAL) return cairo_region_copy (region); n_rects = cairo_region_num_rectangles (region); META_REGION_CREATE_RECTANGLE_ARRAY_SCOPED (n_rects, rects); for (i = 0; i < n_rects; i++) { cairo_region_get_rectangle (region, i, &rects[i]); meta_rectangle_transform (&rects[i], transform, width, height, &rects[i]); } transformed_region = cairo_region_create_rectangles (rects, n_rects); return transformed_region; } cairo_region_t * meta_region_crop_and_scale (cairo_region_t *region, graphene_rect_t *src_rect, int dst_width, int dst_height) { int n_rects, i; cairo_rectangle_int_t *rects; cairo_region_t *viewport_region; if (G_APPROX_VALUE (src_rect->size.width, dst_width, FLT_EPSILON) && G_APPROX_VALUE (src_rect->size.height, dst_height, FLT_EPSILON) && G_APPROX_VALUE (roundf (src_rect->origin.x), src_rect->origin.x, FLT_EPSILON) && G_APPROX_VALUE (roundf (src_rect->origin.y), src_rect->origin.y, FLT_EPSILON)) { viewport_region = cairo_region_copy (region); if (!G_APPROX_VALUE (src_rect->origin.x, 0, FLT_EPSILON) || !G_APPROX_VALUE (src_rect->origin.y, 0, FLT_EPSILON)) { cairo_region_translate (viewport_region, (int) src_rect->origin.x, (int) src_rect->origin.y); } return viewport_region; } n_rects = cairo_region_num_rectangles (region); META_REGION_CREATE_RECTANGLE_ARRAY_SCOPED (n_rects, rects); for (i = 0; i < n_rects; i++) { cairo_region_get_rectangle (region, i, &rects[i]); meta_rectangle_crop_and_scale (&rects[i], src_rect, dst_width, dst_height, &rects[i]); } viewport_region = cairo_region_create_rectangles (rects, n_rects); return viewport_region; } void meta_region_to_cairo_path (cairo_region_t *region, cairo_t *cr) { cairo_rectangle_int_t rect; int n_rects, i; n_rects = cairo_region_num_rectangles (region); for (i = 0; i < n_rects; i++) { cairo_region_get_rectangle (region, i, &rect); cairo_rectangle (cr, rect.x, rect.y, rect.width, rect.height); } } cairo_region_t * meta_region_apply_matrix_transform_expand (const cairo_region_t *region, graphene_matrix_t *transform) { int n_rects, i; cairo_rectangle_int_t *rects; cairo_region_t *transformed_region; if (graphene_matrix_is_identity (transform)) return cairo_region_copy (region); n_rects = cairo_region_num_rectangles (region); META_REGION_CREATE_RECTANGLE_ARRAY_SCOPED (n_rects, rects); for (i = 0; i < n_rects; i++) { graphene_rect_t transformed_rect, rect; cairo_rectangle_int_t int_rect; cairo_region_get_rectangle (region, i, &int_rect); rect = meta_rectangle_to_graphene_rect (&int_rect); graphene_matrix_transform_bounds (transform, &rect, &transformed_rect); meta_rectangle_from_graphene_rect (&transformed_rect, META_ROUNDING_STRATEGY_GROW, &rects[i]); } transformed_region = cairo_region_create_rectangles (rects, n_rects); return transformed_region; }