/* -*- 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;
}