app: add support for projectables with an arbitrary bounding box

In GimpProjectable, replace gimp_projectable_get_size(), which only
returned a width and a height, with
gimp_projectable_get_bounding_box(), which returns a full
rectangle.  This allows projectables to have an arbitrary bounding
box, not limited to a (0, 0) top-left corner.

Adapt GimpProjection, creating a buffer with corresponding extent
to the projectable's bounding box.

Adapt GimpImage and GimpGroupLayer.
This commit is contained in:
Ell 2019-08-01 23:01:58 +03:00
parent 7a05d15f7e
commit 8ff43942d6
5 changed files with 170 additions and 115 deletions

View File

@ -179,6 +179,8 @@ static gboolean
gimp_group_layer_get_excludes_backdrop (GimpLayer *layer);
static const Babl * gimp_group_layer_get_format (GimpProjectable *projectable);
static GeglRectangle
gimp_group_layer_get_bounding_box (GimpProjectable *projectable);
static GeglNode * gimp_group_layer_get_graph (GimpProjectable *projectable);
static void gimp_group_layer_begin_render (GimpProjectable *projectable);
static void gimp_group_layer_end_render (GimpProjectable *projectable);
@ -313,7 +315,7 @@ gimp_projectable_iface_init (GimpProjectableInterface *iface)
iface->get_image = (GimpImage * (*) (GimpProjectable *)) gimp_item_get_image;
iface->get_format = gimp_group_layer_get_format;
iface->get_offset = (void (*) (GimpProjectable*, gint*, gint*)) gimp_item_get_offset;
iface->get_size = (void (*) (GimpProjectable*, gint*, gint*)) gimp_viewable_get_size;
iface->get_bounding_box = gimp_group_layer_get_bounding_box;
iface->get_graph = gimp_group_layer_get_graph;
iface->begin_render = gimp_group_layer_begin_render;
iface->end_render = gimp_group_layer_end_render;
@ -458,29 +460,16 @@ gimp_group_layer_get_size (GimpViewable *viewable,
gint *height)
{
GimpGroupLayerPrivate *private = GET_PRIVATE (viewable);
gboolean result;
if (private->reallocate_width != 0 &&
private->reallocate_height != 0)
/* return the size only if there are children ... */
if (! gimp_container_is_empty (private->children))
{
*width = private->reallocate_width;
*height = private->reallocate_height;
return TRUE;
return GIMP_VIEWABLE_CLASS (parent_class)->get_size (viewable,
width, height);
}
result = GIMP_VIEWABLE_CLASS (parent_class)->get_size (viewable,
width, height);
/* if the group is empty, return "no content" through
* gimp_viewable_get_size(), but make sure to set *width and *height anyway,
* so that the correct size is reported to the projection through
* gimp_projectable_get_size(). see issue #3134.
*/
if (gimp_container_is_empty (private->children))
result = FALSE;
return result;
/* ... otherwise, return "no content" */
return FALSE;
}
static GimpContainer *
@ -1345,6 +1334,30 @@ gimp_group_layer_get_format (GimpProjectable *projectable)
return get_projection_format (projectable, base_type, precision);
}
static GeglRectangle
gimp_group_layer_get_bounding_box (GimpProjectable *projectable)
{
GimpGroupLayerPrivate *private = GET_PRIVATE (projectable);
GeglRectangle bounding_box;
bounding_box.x = 0;
bounding_box.y = 0;
if (private->reallocate_width != 0 &&
private->reallocate_height != 0)
{
bounding_box.width = private->reallocate_width;
bounding_box.height = private->reallocate_height;
}
else
{
bounding_box.width = gimp_item_get_width (GIMP_ITEM (projectable));
bounding_box.height = gimp_item_get_height (GIMP_ITEM (projectable));
}
return bounding_box;
}
static GeglNode *
gimp_group_layer_get_graph (GimpProjectable *projectable)
{

View File

@ -194,6 +194,7 @@ static void
static void gimp_image_projectable_flush (GimpProjectable *projectable,
gboolean invalidate_preview);
static GeglRectangle gimp_image_get_bounding_box (GimpProjectable *projectable);
static GeglNode * gimp_image_get_graph (GimpProjectable *projectable);
static GimpImage * gimp_image_get_image (GimpProjectable *projectable);
static const Babl * gimp_image_get_proj_format (GimpProjectable *projectable);
@ -678,7 +679,7 @@ gimp_projectable_iface_init (GimpProjectableInterface *iface)
iface->flush = gimp_image_projectable_flush;
iface->get_image = gimp_image_get_image;
iface->get_format = gimp_image_get_proj_format;
iface->get_size = (void (*) (GimpProjectable*, gint*, gint*)) gimp_image_get_size;
iface->get_bounding_box = gimp_image_get_bounding_box;
iface->get_graph = gimp_image_get_graph;
iface->invalidate_preview = (void (*) (GimpProjectable*)) gimp_viewable_invalidate_preview;
}
@ -1479,6 +1480,16 @@ gimp_image_srgb_to_pixel (GimpPickable *pickable,
color, format, pixel);
}
static GeglRectangle
gimp_image_get_bounding_box (GimpProjectable *projectable)
{
GimpImage *image = GIMP_IMAGE (projectable);
return *GEGL_RECTANGLE (0, 0,
gimp_image_get_width (image),
gimp_image_get_height (image));
}
static GeglNode *
gimp_image_get_graph (GimpProjectable *projectable)
{

View File

@ -90,10 +90,8 @@ gimp_projectable_default_init (GimpProjectableInterface *iface)
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (GimpProjectableInterface, bounds_changed),
NULL, NULL,
gimp_marshal_VOID__INT_INT_INT_INT,
G_TYPE_NONE, 4,
G_TYPE_INT,
G_TYPE_INT,
gimp_marshal_VOID__INT_INT,
G_TYPE_NONE, 2,
G_TYPE_INT,
G_TYPE_INT);
}
@ -135,14 +133,12 @@ gimp_projectable_structure_changed (GimpProjectable *projectable)
void
gimp_projectable_bounds_changed (GimpProjectable *projectable,
gint old_x,
gint old_y,
gint old_width,
gint old_height)
gint old_y)
{
g_return_if_fail (GIMP_IS_PROJECTABLE (projectable));
g_signal_emit (projectable, projectable_signals[BOUNDS_CHANGED], 0,
old_x, old_y, old_width, old_height);
old_x, old_y);
}
GimpImage *
@ -195,24 +191,20 @@ gimp_projectable_get_offset (GimpProjectable *projectable,
iface->get_offset (projectable, x, y);
}
void
gimp_projectable_get_size (GimpProjectable *projectable,
gint *width,
gint *height)
GeglRectangle
gimp_projectable_get_bounding_box (GimpProjectable *projectable)
{
GimpProjectableInterface *iface;
GeglRectangle result = {};
g_return_if_fail (GIMP_IS_PROJECTABLE (projectable));
g_return_if_fail (width != NULL);
g_return_if_fail (height != NULL);
g_return_val_if_fail (GIMP_IS_PROJECTABLE (projectable), result);
iface = GIMP_PROJECTABLE_GET_INTERFACE (projectable);
*width = 0;
*height = 0;
if (iface->get_bounding_box)
result = iface->get_bounding_box (projectable);
if (iface->get_size)
iface->get_size (projectable, width, height);
return result;
}
GeglNode *

View File

@ -45,9 +45,7 @@ struct _GimpProjectableInterface
void (* structure_changed) (GimpProjectable *projectable);
void (* bounds_changed) (GimpProjectable *projectable,
gint old_x,
gint old_y,
gint old_width,
gint old_height);
gint old_y);
/* virtual functions */
GimpImage * (* get_image) (GimpProjectable *projectable);
@ -55,9 +53,7 @@ struct _GimpProjectableInterface
void (* get_offset) (GimpProjectable *projectable,
gint *x,
gint *y);
void (* get_size) (GimpProjectable *projectable,
gint *width,
gint *height);
GeglRectangle (* get_bounding_box) (GimpProjectable *projectable);
GeglNode * (* get_graph) (GimpProjectable *projectable);
void (* begin_render) (GimpProjectable *projectable);
void (* end_render) (GimpProjectable *projectable);
@ -77,18 +73,14 @@ void gimp_projectable_flush (GimpProjectable *projectable,
void gimp_projectable_structure_changed (GimpProjectable *projectable);
void gimp_projectable_bounds_changed (GimpProjectable *projectable,
gint old_x,
gint old_y,
gint old_width,
gint old_height);
gint old_y);
GimpImage * gimp_projectable_get_image (GimpProjectable *projectable);
const Babl * gimp_projectable_get_format (GimpProjectable *projectable);
void gimp_projectable_get_offset (GimpProjectable *projectable,
gint *x,
gint *y);
void gimp_projectable_get_size (GimpProjectable *projectable,
gint *width,
gint *height);
GeglRectangle gimp_projectable_get_bounding_box (GimpProjectable *projectable);
GeglNode * gimp_projectable_get_graph (GimpProjectable *projectable);
void gimp_projectable_begin_render (GimpProjectable *projectable);
void gimp_projectable_end_render (GimpProjectable *projectable);

View File

@ -163,8 +163,6 @@ static void
static void gimp_projection_projectable_bounds_changed (GimpProjectable *projectable,
gint old_x,
gint old_y,
gint old_w,
gint old_h,
GimpProjection *proj);
@ -365,10 +363,10 @@ gimp_projection_get_buffer (GimpPickable *pickable)
if (! proj->priv->buffer)
{
gint width;
gint height;
GeglRectangle bounding_box;
gimp_projectable_get_size (proj->priv->projectable, &width, &height);
bounding_box =
gimp_projectable_get_bounding_box (proj->priv->projectable);
gimp_projection_allocate_buffer (proj);
@ -379,7 +377,9 @@ gimp_projection_get_buffer (GimpPickable *pickable)
* image appear incrementally, but it keeps everything
* responsive.
*/
gimp_projection_add_update_area (proj, 0, 0, width, height);
gimp_projection_add_update_area (proj,
bounding_box.x, bounding_box.y,
bounding_box.width, bounding_box.height);
proj->priv->invalidate_preview = TRUE;
gimp_projection_flush (proj);
}
@ -565,18 +565,17 @@ gimp_projection_finish_draw (GimpProjection *proj)
static void
gimp_projection_allocate_buffer (GimpProjection *proj)
{
const Babl *format;
gint width;
gint height;
const Babl *format;
GeglRectangle bounding_box;
if (proj->priv->buffer)
return;
format = gimp_projection_get_format (GIMP_PICKABLE (proj));
gimp_projectable_get_size (proj->priv->projectable, &width, &height);
format = gimp_projection_get_format (GIMP_PICKABLE (proj));
bounding_box =
gimp_projectable_get_bounding_box (proj->priv->projectable);
proj->priv->buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0, width, height),
format);
proj->priv->buffer = gegl_buffer_new (&bounding_box, format);
proj->priv->validate_handler =
GIMP_TILE_HANDLER_VALIDATE (
@ -613,9 +612,9 @@ gimp_projection_add_update_area (GimpProjection *proj,
gint h)
{
cairo_rectangle_int_t rect;
gint width, height;
GeglRectangle bounding_box;
gimp_projectable_get_size (proj->priv->projectable, &width, &height);
bounding_box = gimp_projectable_get_bounding_box (proj->priv->projectable);
/* align the rectangle to the UPDATE_CHUNK_WIDTH x UPDATE_CHUNK_HEIGHT grid,
* to decrease the complexity of the update area.
@ -628,9 +627,8 @@ gimp_projection_add_update_area (GimpProjection *proj,
w -= x;
h -= y;
if (gimp_rectangle_intersect (x, y, w, h,
0, 0, width, height,
&rect.x, &rect.y, &rect.width, &rect.height))
if (gegl_rectangle_intersect ((GeglRectangle *) &rect,
GEGL_RECTANGLE (x, y, w, h), &bounding_box))
{
if (proj->priv->update_region)
cairo_region_union_rectangle (proj->priv->update_region, &rect);
@ -695,13 +693,13 @@ gimp_projection_update_priority_rect (GimpProjection *proj)
if (proj->priv->iter)
{
GeglRectangle rect;
GeglRectangle bounding_box;
gint off_x, off_y;
gint width, height;
rect = proj->priv->priority_rect;
gimp_projectable_get_offset (proj->priv->projectable, &off_x, &off_y);
gimp_projectable_get_size (proj->priv->projectable, &width, &height);
bounding_box = gimp_projectable_get_bounding_box (proj->priv->projectable);
/* subtract the projectable's offsets because the list of update
* areas is in tile-pyramid coordinates, but our external API is
@ -710,9 +708,7 @@ gimp_projection_update_priority_rect (GimpProjection *proj)
rect.x -= off_x;
rect.y -= off_y;
gegl_rectangle_intersect (&rect,
&rect,
GEGL_RECTANGLE (0, 0, width, height));
gegl_rectangle_intersect (&rect, &rect, &bounding_box);
gimp_chunk_iterator_set_priority_rect (proj->priv->iter, &rect);
}
@ -880,29 +876,29 @@ gimp_projection_paint_area (GimpProjection *proj,
gint w,
gint h)
{
gint off_x, off_y;
gint width, height;
gint off_x, off_y;
GeglRectangle bounding_box;
GeglRectangle rect;
gimp_projectable_get_offset (proj->priv->projectable, &off_x, &off_y);
gimp_projectable_get_size (proj->priv->projectable, &width, &height);
bounding_box = gimp_projectable_get_bounding_box (proj->priv->projectable);
if (gimp_rectangle_intersect (x, y, w, h,
0, 0, width, height,
&x, &y, &w, &h))
if (gegl_rectangle_intersect (&rect,
GEGL_RECTANGLE (x, y, w, h), &bounding_box))
{
if (now)
{
gimp_tile_handler_validate_validate (
proj->priv->validate_handler,
proj->priv->buffer,
GEGL_RECTANGLE (x, y, w, h),
&rect,
FALSE);
}
else
{
gimp_tile_handler_validate_invalidate (
proj->priv->validate_handler,
GEGL_RECTANGLE (x, y, w, h));
&rect);
}
/* add the projectable's offsets because the list of update areas
@ -911,10 +907,10 @@ gimp_projection_paint_area (GimpProjection *proj,
*/
g_signal_emit (proj, projection_signals[UPDATE], 0,
now,
x + off_x,
y + off_y,
w,
h);
rect.x + off_x,
rect.y + off_y,
rect.width,
rect.height);
}
}
@ -958,26 +954,31 @@ static void
gimp_projection_projectable_structure_changed (GimpProjectable *projectable,
GimpProjection *proj)
{
gint width, height;
GeglRectangle bounding_box;
gimp_projection_free_buffer (proj);
gimp_projectable_get_size (projectable, &width, &height);
bounding_box = gimp_projectable_get_bounding_box (projectable);
gimp_projection_add_update_area (proj, 0, 0, width, height);
gimp_projection_add_update_area (proj,
bounding_box.x, bounding_box.y,
bounding_box.width, bounding_box.height);
}
static void
gimp_projection_projectable_bounds_changed (GimpProjectable *projectable,
gint old_x,
gint old_y,
gint old_w,
gint old_h,
GimpProjection *proj)
{
GeglBuffer *old_buffer = proj->priv->buffer;
GimpTileHandlerValidate *old_validate_handler;
gint x, y, w, h;
GeglRectangle old_bounding_box;
GeglRectangle bounding_box;
GeglRectangle old_bounds;
GeglRectangle bounds;
GeglRectangle int_bounds;
gint x, y;
gint dx, dy;
if (! old_buffer)
@ -987,23 +988,34 @@ gimp_projection_projectable_bounds_changed (GimpProjectable *projectable,
return;
}
old_bounding_box = *gegl_buffer_get_extent (old_buffer);
gimp_projectable_get_offset (projectable, &x, &y);
gimp_projectable_get_size (projectable, &w, &h);
bounding_box = gimp_projectable_get_bounding_box (projectable);
if (x == old_x && y == old_y && w == old_w && h == old_h)
return;
if (x == old_x && y == old_y &&
gegl_rectangle_equal (&bounding_box, &old_bounding_box))
{
return;
}
if (! gimp_rectangle_intersect (x, y, w, h,
old_x, old_y, old_w, old_h,
NULL, NULL, NULL, NULL))
old_bounds = old_bounding_box;
old_bounds.x += old_x;
old_bounds.y += old_y;
bounds = bounding_box;
bounds.x += x;
bounds.y += y;
if (! gegl_rectangle_intersect (&int_bounds, &bounds, &old_bounds))
{
gimp_projection_projectable_structure_changed (projectable, proj);
return;
}
dx = old_x - x;
dy = old_y - y;
dx = x - old_x;
dy = y - old_y;
#if 1
/* FIXME: when there's an offset between the new bounds and the old bounds,
@ -1038,9 +1050,15 @@ gimp_projection_projectable_bounds_changed (GimpProjectable *projectable,
gimp_projection_allocate_buffer (proj);
gimp_tile_handler_validate_buffer_copy (old_buffer,
GEGL_RECTANGLE (0, 0, old_w, old_h),
GEGL_RECTANGLE (int_bounds.x - old_x,
int_bounds.y - old_y,
int_bounds.width,
int_bounds.height),
proj->priv->buffer,
GEGL_RECTANGLE (dx, dy, old_w, old_h));
GEGL_RECTANGLE (int_bounds.x - x,
int_bounds.y - y,
int_bounds.width,
int_bounds.height));
if (old_validate_handler)
{
@ -1053,20 +1071,49 @@ gimp_projection_projectable_bounds_changed (GimpProjectable *projectable,
if (proj->priv->update_region)
{
const cairo_rectangle_int_t bounds = {0, 0, w, h};
cairo_region_translate (proj->priv->update_region, dx, dy);
cairo_region_intersect_rectangle (proj->priv->update_region, &bounds);
cairo_region_translate (proj->priv->update_region, dx, dy);
cairo_region_intersect_rectangle (
proj->priv->update_region,
(const cairo_rectangle_int_t *) &bounding_box);
}
if (dx > 0)
gimp_projection_add_update_area (proj, 0, 0, dx, h);
if (dy > 0)
gimp_projection_add_update_area (proj, 0, 0, w, dy);
if (dx + old_w < w)
gimp_projection_add_update_area (proj, dx + old_w, 0, w - (dx + old_w), h);
if (dy + old_h < h)
gimp_projection_add_update_area (proj, 0, dy + old_h, w, h - (dy + old_h));
int_bounds.x -= x;
int_bounds.y -= y;
if (int_bounds.x > bounding_box.x)
{
gimp_projection_add_update_area (proj,
bounding_box.x,
bounding_box.y,
int_bounds.x - bounding_box.x,
bounding_box.height);
}
if (int_bounds.y > bounding_box.y)
{
gimp_projection_add_update_area (proj,
bounding_box.x,
bounding_box.y,
bounding_box.width,
int_bounds.y - bounding_box.y);
}
if (int_bounds.x + int_bounds.width < bounding_box.x + bounding_box.width)
{
gimp_projection_add_update_area (proj,
int_bounds.x + int_bounds.width,
bounding_box.y,
bounding_box.x + bounding_box.width -
(int_bounds.x + int_bounds.width),
bounding_box.height);
}
if (int_bounds.y + int_bounds.height < bounding_box.y + bounding_box.height)
{
gimp_projection_add_update_area (proj,
bounding_box.x,
int_bounds.y + int_bounds.height,
bounding_box.width,
bounding_box.y + bounding_box.height -
(int_bounds.y + int_bounds.height));
}
proj->priv->invalidate_preview = TRUE;
}