PIKApp/app/core/pikagrouplayer.c

2324 lines
83 KiB
C
Raw Normal View History

2023-09-26 00:35:21 +02:00
/* PIKA - Photo and Image Kooker Application
* a rebranding of The GNU Image Manipulation Program (created with heckimp)
* A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
*
* Original copyright, applying to most contents (license remains unchanged):
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* PikaGroupLayer
* Copyright (C) 2009 Michael Natterer <mitch@gimp.org>
*
* 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 3 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 <https://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <string.h>
#include <gdk-pixbuf/gdk-pixbuf.h>
#include <gegl.h>
#include "libpikabase/pikabase.h"
#include "libpikamath/pikamath.h"
#include "core-types.h"
#include "gegl/pika-babl.h"
#include "gegl/pika-gegl-loops.h"
#include "pikadrawable-filters.h"
#include "pikagrouplayer.h"
#include "pikagrouplayerundo.h"
#include "pikaimage.h"
#include "pikaimage-undo.h"
#include "pikaimage-undo-push.h"
#include "pikalayerstack.h"
#include "pikaobjectqueue.h"
#include "pikapickable.h"
#include "pikaprogress.h"
#include "pikaprojectable.h"
#include "pikaprojection.h"
#include "pika-intl.h"
typedef struct _PikaGroupLayerPrivate PikaGroupLayerPrivate;
struct _PikaGroupLayerPrivate
{
PikaContainer *children;
PikaProjection *projection;
GeglNode *source_node;
GeglNode *parent_source_node;
GeglNode *graph;
GeglNode *offset_node;
GeglRectangle bounding_box;
gint suspend_resize;
gint suspend_mask;
GeglBuffer *suspended_mask_buffer;
GeglRectangle suspended_mask_bounds;
gint direct_update;
gint transforming;
gboolean expanded;
gboolean pass_through;
/* hackish temp states to make the projection/tiles stuff work */
const Babl *convert_format;
gboolean reallocate_projection;
};
#define GET_PRIVATE(item) ((PikaGroupLayerPrivate *) pika_group_layer_get_instance_private ((PikaGroupLayer *) (item)))
static void pika_projectable_iface_init (PikaProjectableInterface *iface);
static void pika_pickable_iface_init (PikaPickableInterface *iface);
static void pika_group_layer_finalize (GObject *object);
static void pika_group_layer_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec);
static void pika_group_layer_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec);
static gint64 pika_group_layer_get_memsize (PikaObject *object,
gint64 *gui_size);
static void pika_group_layer_ancestry_changed (PikaViewable *viewable);
static gboolean pika_group_layer_get_size (PikaViewable *viewable,
gint *width,
gint *height);
static PikaContainer * pika_group_layer_get_children (PikaViewable *viewable);
static gboolean pika_group_layer_get_expanded (PikaViewable *viewable);
static void pika_group_layer_set_expanded (PikaViewable *viewable,
gboolean expanded);
static gboolean pika_group_layer_is_position_locked (PikaItem *item,
PikaItem **locked_item,
gboolean check_children);
static PikaItem * pika_group_layer_duplicate (PikaItem *item,
GType new_type);
static void pika_group_layer_convert (PikaItem *item,
PikaImage *dest_image,
GType old_type);
static void pika_group_layer_start_transform (PikaItem *item,
gboolean push_undo);
static void pika_group_layer_end_transform (PikaItem *item,
gboolean push_undo);
static void pika_group_layer_resize (PikaItem *item,
PikaContext *context,
PikaFillType fill_type,
gint new_width,
gint new_height,
gint offset_x,
gint offset_y);
static PikaTransformResize
pika_group_layer_get_clip (PikaItem *item,
PikaTransformResize clip_result);
static gint64 pika_group_layer_estimate_memsize (PikaDrawable *drawable,
PikaComponentType component_type,
gint width,
gint height);
static void pika_group_layer_update_all (PikaDrawable *drawable);
static void pika_group_layer_translate (PikaLayer *layer,
gint offset_x,
gint offset_y);
static void pika_group_layer_scale (PikaLayer *layer,
gint new_width,
gint new_height,
gint new_offset_x,
gint new_offset_y,
PikaInterpolationType interp_type,
PikaProgress *progress);
static void pika_group_layer_flip (PikaLayer *layer,
PikaContext *context,
PikaOrientationType flip_type,
gdouble axis,
gboolean clip_result);
static void pika_group_layer_rotate (PikaLayer *layer,
PikaContext *context,
PikaRotationType rotate_type,
gdouble center_x,
gdouble center_y,
gboolean clip_result);
static void pika_group_layer_transform (PikaLayer *layer,
PikaContext *context,
const PikaMatrix3 *matrix,
PikaTransformDirection direction,
PikaInterpolationType interpolation_type,
PikaTransformResize clip_result,
PikaProgress *progress);
static void pika_group_layer_convert_type (PikaLayer *layer,
PikaImage *dest_image,
const Babl *new_format,
PikaColorProfile *src_profile,
PikaColorProfile *dest_profile,
GeglDitherMethod layer_dither_type,
GeglDitherMethod mask_dither_type,
gboolean push_undo,
PikaProgress *progress);
static GeglNode * pika_group_layer_get_source_node (PikaDrawable *drawable);
static void pika_group_layer_opacity_changed (PikaLayer *layer);
static void pika_group_layer_effective_mode_changed (PikaLayer *layer);
static void
pika_group_layer_excludes_backdrop_changed (PikaLayer *layer);
static GeglRectangle
pika_group_layer_get_bounding_box (PikaLayer *layer);
static void pika_group_layer_get_effective_mode (PikaLayer *layer,
PikaLayerMode *mode,
PikaLayerColorSpace *blend_space,
PikaLayerColorSpace *composite_space,
PikaLayerCompositeMode *composite_mode);
static gboolean
pika_group_layer_get_excludes_backdrop (PikaLayer *layer);
static const Babl * pika_group_layer_get_format (PikaProjectable *projectable);
static GeglRectangle
pika_group_layer_projectable_get_bounding_box (PikaProjectable *projectable);
static GeglNode * pika_group_layer_get_graph (PikaProjectable *projectable);
static void pika_group_layer_begin_render (PikaProjectable *projectable);
static void pika_group_layer_end_render (PikaProjectable *projectable);
static void pika_group_layer_pickable_flush (PikaPickable *pickable);
static gdouble pika_group_layer_get_opacity_at (PikaPickable *pickable,
gint x,
gint y);
static void pika_group_layer_child_add (PikaContainer *container,
PikaLayer *child,
PikaGroupLayer *group);
static void pika_group_layer_child_remove (PikaContainer *container,
PikaLayer *child,
PikaGroupLayer *group);
static void pika_group_layer_child_move (PikaLayer *child,
GParamSpec *pspec,
PikaGroupLayer *group);
static void pika_group_layer_child_resize (PikaLayer *child,
PikaGroupLayer *group);
static void pika_group_layer_child_active_changed (PikaLayer *child,
PikaGroupLayer *group);
static void
pika_group_layer_child_effective_mode_changed (PikaLayer *child,
PikaGroupLayer *group);
static void
pika_group_layer_child_excludes_backdrop_changed (PikaLayer *child,
PikaGroupLayer *group);
static void pika_group_layer_flush (PikaGroupLayer *group);
static void pika_group_layer_update (PikaGroupLayer *group);
static void pika_group_layer_update_size (PikaGroupLayer *group);
static void pika_group_layer_update_mask_size (PikaGroupLayer *group);
static void pika_group_layer_update_source_node (PikaGroupLayer *group);
static void pika_group_layer_update_mode_node (PikaGroupLayer *group);
static void pika_group_layer_stack_update (PikaDrawableStack *stack,
gint x,
gint y,
gint width,
gint height,
PikaGroupLayer *group);
static void pika_group_layer_proj_update (PikaProjection *proj,
gboolean now,
gint x,
gint y,
gint width,
gint height,
PikaGroupLayer *group);
G_DEFINE_TYPE_WITH_CODE (PikaGroupLayer, pika_group_layer, PIKA_TYPE_LAYER,
G_ADD_PRIVATE (PikaGroupLayer)
G_IMPLEMENT_INTERFACE (PIKA_TYPE_PROJECTABLE,
pika_projectable_iface_init)
G_IMPLEMENT_INTERFACE (PIKA_TYPE_PICKABLE,
pika_pickable_iface_init))
#define parent_class pika_group_layer_parent_class
/* disable pass-through groups strength-reduction to normal groups.
* see pika_group_layer_get_effective_mode().
*/
static gboolean no_pass_through_strength_reduction = FALSE;
static void
pika_group_layer_class_init (PikaGroupLayerClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
PikaObjectClass *pika_object_class = PIKA_OBJECT_CLASS (klass);
PikaViewableClass *viewable_class = PIKA_VIEWABLE_CLASS (klass);
PikaItemClass *item_class = PIKA_ITEM_CLASS (klass);
PikaDrawableClass *drawable_class = PIKA_DRAWABLE_CLASS (klass);
PikaLayerClass *layer_class = PIKA_LAYER_CLASS (klass);
object_class->set_property = pika_group_layer_set_property;
object_class->get_property = pika_group_layer_get_property;
object_class->finalize = pika_group_layer_finalize;
pika_object_class->get_memsize = pika_group_layer_get_memsize;
viewable_class->default_icon_name = "pika-group-layer";
viewable_class->ancestry_changed = pika_group_layer_ancestry_changed;
viewable_class->get_size = pika_group_layer_get_size;
viewable_class->get_children = pika_group_layer_get_children;
viewable_class->set_expanded = pika_group_layer_set_expanded;
viewable_class->get_expanded = pika_group_layer_get_expanded;
item_class->is_position_locked = pika_group_layer_is_position_locked;
item_class->duplicate = pika_group_layer_duplicate;
item_class->convert = pika_group_layer_convert;
item_class->start_transform = pika_group_layer_start_transform;
item_class->end_transform = pika_group_layer_end_transform;
item_class->resize = pika_group_layer_resize;
item_class->get_clip = pika_group_layer_get_clip;
item_class->default_name = _("Layer Group");
item_class->rename_desc = C_("undo-type", "Rename Layer Group");
item_class->translate_desc = C_("undo-type", "Move Layer Group");
item_class->scale_desc = C_("undo-type", "Scale Layer Group");
item_class->resize_desc = C_("undo-type", "Resize Layer Group");
item_class->flip_desc = C_("undo-type", "Flip Layer Group");
item_class->rotate_desc = C_("undo-type", "Rotate Layer Group");
item_class->transform_desc = C_("undo-type", "Transform Layer Group");
drawable_class->estimate_memsize = pika_group_layer_estimate_memsize;
drawable_class->update_all = pika_group_layer_update_all;
drawable_class->get_source_node = pika_group_layer_get_source_node;
layer_class->opacity_changed = pika_group_layer_opacity_changed;
layer_class->effective_mode_changed = pika_group_layer_effective_mode_changed;
layer_class->excludes_backdrop_changed = pika_group_layer_excludes_backdrop_changed;
layer_class->translate = pika_group_layer_translate;
layer_class->scale = pika_group_layer_scale;
layer_class->flip = pika_group_layer_flip;
layer_class->rotate = pika_group_layer_rotate;
layer_class->transform = pika_group_layer_transform;
layer_class->convert_type = pika_group_layer_convert_type;
layer_class->get_bounding_box = pika_group_layer_get_bounding_box;
layer_class->get_effective_mode = pika_group_layer_get_effective_mode;
layer_class->get_excludes_backdrop = pika_group_layer_get_excludes_backdrop;
if (g_getenv ("PIKA_NO_PASS_THROUGH_STRENGTH_REDUCTION"))
no_pass_through_strength_reduction = TRUE;
}
static void
pika_projectable_iface_init (PikaProjectableInterface *iface)
{
iface->get_image = (PikaImage * (*) (PikaProjectable *)) pika_item_get_image;
iface->get_format = pika_group_layer_get_format;
iface->get_offset = (void (*) (PikaProjectable*, gint*, gint*)) pika_item_get_offset;
iface->get_bounding_box = pika_group_layer_projectable_get_bounding_box;
iface->get_graph = pika_group_layer_get_graph;
iface->begin_render = pika_group_layer_begin_render;
iface->end_render = pika_group_layer_end_render;
iface->invalidate_preview = (void (*) (PikaProjectable*)) pika_viewable_invalidate_preview;
}
static void
pika_pickable_iface_init (PikaPickableInterface *iface)
{
iface->flush = pika_group_layer_pickable_flush;
iface->get_opacity_at = pika_group_layer_get_opacity_at;
}
static void
pika_group_layer_init (PikaGroupLayer *group)
{
PikaGroupLayerPrivate *private = GET_PRIVATE (group);
private->children = pika_layer_stack_new (PIKA_TYPE_LAYER);
private->expanded = TRUE;
g_signal_connect (private->children, "add",
G_CALLBACK (pika_group_layer_child_add),
group);
g_signal_connect (private->children, "remove",
G_CALLBACK (pika_group_layer_child_remove),
group);
pika_container_add_handler (private->children, "notify::offset-x",
G_CALLBACK (pika_group_layer_child_move),
group);
pika_container_add_handler (private->children, "notify::offset-y",
G_CALLBACK (pika_group_layer_child_move),
group);
pika_container_add_handler (private->children, "size-changed",
G_CALLBACK (pika_group_layer_child_resize),
group);
pika_container_add_handler (private->children, "bounding-box-changed",
G_CALLBACK (pika_group_layer_child_resize),
group);
pika_container_add_handler (private->children, "active-changed",
G_CALLBACK (pika_group_layer_child_active_changed),
group);
pika_container_add_handler (private->children, "effective-mode-changed",
G_CALLBACK (pika_group_layer_child_effective_mode_changed),
group);
pika_container_add_handler (private->children, "excludes-backdrop-changed",
G_CALLBACK (pika_group_layer_child_excludes_backdrop_changed),
group);
g_signal_connect (private->children, "update",
G_CALLBACK (pika_group_layer_stack_update),
group);
private->projection = pika_projection_new (PIKA_PROJECTABLE (group));
pika_projection_set_priority (private->projection, 1);
g_signal_connect (private->projection, "update",
G_CALLBACK (pika_group_layer_proj_update),
group);
}
static void
pika_group_layer_finalize (GObject *object)
{
PikaGroupLayerPrivate *private = GET_PRIVATE (object);
if (private->children)
{
g_signal_handlers_disconnect_by_func (private->children,
pika_group_layer_child_add,
object);
g_signal_handlers_disconnect_by_func (private->children,
pika_group_layer_child_remove,
object);
g_signal_handlers_disconnect_by_func (private->children,
pika_group_layer_stack_update,
object);
/* this is particularly important to avoid reallocating the projection
* in response to a "bounding-box-changed" signal, which can be emitted
* during layer destruction. see issue #4584.
*/
pika_container_remove_handlers_by_data (private->children, object);
g_clear_object (&private->children);
}
g_clear_object (&private->projection);
g_clear_object (&private->source_node);
g_clear_object (&private->graph);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static void
pika_group_layer_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
switch (property_id)
{
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
pika_group_layer_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
switch (property_id)
{
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static gint64
pika_group_layer_get_memsize (PikaObject *object,
gint64 *gui_size)
{
PikaGroupLayerPrivate *private = GET_PRIVATE (object);
gint64 memsize = 0;
memsize += pika_object_get_memsize (PIKA_OBJECT (private->children), gui_size);
memsize += pika_object_get_memsize (PIKA_OBJECT (private->projection), gui_size);
return memsize + PIKA_OBJECT_CLASS (parent_class)->get_memsize (object,
gui_size);
}
static void
pika_group_layer_ancestry_changed (PikaViewable *viewable)
{
PikaGroupLayerPrivate *private = GET_PRIVATE (viewable);
pika_projection_set_priority (private->projection,
pika_viewable_get_depth (viewable) + 1);
PIKA_VIEWABLE_CLASS (parent_class)->ancestry_changed (viewable);
}
static gboolean
pika_group_layer_get_size (PikaViewable *viewable,
gint *width,
gint *height)
{
PikaGroupLayerPrivate *private = GET_PRIVATE (viewable);
/* return the size only if there are children ... */
if (! pika_container_is_empty (private->children))
{
return PIKA_VIEWABLE_CLASS (parent_class)->get_size (viewable,
width, height);
}
/* ... otherwise, return "no content" */
return FALSE;
}
static PikaContainer *
pika_group_layer_get_children (PikaViewable *viewable)
{
return GET_PRIVATE (viewable)->children;
}
static gboolean
pika_group_layer_get_expanded (PikaViewable *viewable)
{
PikaGroupLayer *group = PIKA_GROUP_LAYER (viewable);
return GET_PRIVATE (group)->expanded;
}
static void
pika_group_layer_set_expanded (PikaViewable *viewable,
gboolean expanded)
{
PikaGroupLayerPrivate *private = GET_PRIVATE (viewable);
if (private->expanded != expanded)
{
private->expanded = expanded;
pika_viewable_expanded_changed (viewable);
}
}
static gboolean
pika_group_layer_is_position_locked (PikaItem *item,
PikaItem **locked_item,
gboolean check_children)
{
/* Lock position is particular because a locked child locks the group
* too.
*/
if (check_children)
{
PikaGroupLayerPrivate *private = GET_PRIVATE (item);
GList *list;
for (list = pika_item_stack_get_item_iter (PIKA_ITEM_STACK (private->children));
list;
list = g_list_next (list))
{
PikaItem *child = list->data;
if (pika_item_get_lock_position (child))
{
if (locked_item)
*locked_item = child;
return TRUE;
}
else if (PIKA_IS_GROUP_LAYER (child) &&
pika_group_layer_is_position_locked (child,
locked_item,
TRUE))
{
return TRUE;
}
}
}
/* And a locked parent locks the group too! Which is handled by parent
* implementation of the method.
*/
return PIKA_ITEM_CLASS (parent_class)->is_position_locked (item,
locked_item,
FALSE);
}
static PikaItem *
pika_group_layer_duplicate (PikaItem *item,
GType new_type)
{
PikaItem *new_item;
g_return_val_if_fail (g_type_is_a (new_type, PIKA_TYPE_DRAWABLE), NULL);
new_item = PIKA_ITEM_CLASS (parent_class)->duplicate (item, new_type);
if (PIKA_IS_GROUP_LAYER (new_item))
{
PikaGroupLayerPrivate *private = GET_PRIVATE (item);
PikaGroupLayer *new_group = PIKA_GROUP_LAYER (new_item);
PikaGroupLayerPrivate *new_private = GET_PRIVATE (new_item);
gint position = 0;
GList *list;
pika_group_layer_suspend_resize (new_group, FALSE);
for (list = pika_item_stack_get_item_iter (PIKA_ITEM_STACK (private->children));
list;
list = g_list_next (list))
{
PikaItem *child = list->data;
PikaItem *new_child;
PikaLayerMask *mask;
new_child = pika_item_duplicate (child, G_TYPE_FROM_INSTANCE (child));
pika_object_set_name (PIKA_OBJECT (new_child),
pika_object_get_name (child));
mask = pika_layer_get_mask (PIKA_LAYER (child));
if (mask)
{
PikaLayerMask *new_mask;
new_mask = pika_layer_get_mask (PIKA_LAYER (new_child));
pika_object_set_name (PIKA_OBJECT (new_mask),
pika_object_get_name (mask));
}
pika_viewable_set_parent (PIKA_VIEWABLE (new_child),
PIKA_VIEWABLE (new_group));
pika_container_insert (new_private->children,
PIKA_OBJECT (new_child),
position++);
}
/* force the projection to reallocate itself */
GET_PRIVATE (new_group)->reallocate_projection = TRUE;
pika_group_layer_resume_resize (new_group, FALSE);
}
return new_item;
}
static void
pika_group_layer_convert (PikaItem *item,
PikaImage *dest_image,
GType old_type)
{
PikaGroupLayerPrivate *private = GET_PRIVATE (item);
GList *list;
for (list = pika_item_stack_get_item_iter (PIKA_ITEM_STACK (private->children));
list;
list = g_list_next (list))
{
PikaItem *child = list->data;
PIKA_ITEM_GET_CLASS (child)->convert (child, dest_image,
G_TYPE_FROM_INSTANCE (child));
}
PIKA_ITEM_CLASS (parent_class)->convert (item, dest_image, old_type);
}
static void
pika_group_layer_start_transform (PikaItem *item,
gboolean push_undo)
{
_pika_group_layer_start_transform (PIKA_GROUP_LAYER (item), push_undo);
if (PIKA_ITEM_CLASS (parent_class)->start_transform)
PIKA_ITEM_CLASS (parent_class)->start_transform (item, push_undo);
}
static void
pika_group_layer_end_transform (PikaItem *item,
gboolean push_undo)
{
if (PIKA_ITEM_CLASS (parent_class)->end_transform)
PIKA_ITEM_CLASS (parent_class)->end_transform (item, push_undo);
_pika_group_layer_end_transform (PIKA_GROUP_LAYER (item), push_undo);
}
static void
pika_group_layer_resize (PikaItem *item,
PikaContext *context,
PikaFillType fill_type,
gint new_width,
gint new_height,
gint offset_x,
gint offset_y)
{
PikaGroupLayer *group = PIKA_GROUP_LAYER (item);
PikaGroupLayerPrivate *private = GET_PRIVATE (item);
GList *list;
gint x, y;
/* we implement PikaItem::resize(), instead of PikaLayer::resize(), so that
* PikaLayer doesn't resize the mask. note that pika_item_resize() calls
* pika_item_{start,end}_move(), and not pika_item_{start,end}_transform(),
* so that mask resizing is handled by pika_group_layer_update_size().
*/
x = pika_item_get_offset_x (item) - offset_x;
y = pika_item_get_offset_y (item) - offset_y;
pika_group_layer_suspend_resize (group, TRUE);
list = pika_item_stack_get_item_iter (PIKA_ITEM_STACK (private->children));
while (list)
{
PikaItem *child = list->data;
gint child_width;
gint child_height;
gint child_x;
gint child_y;
list = g_list_next (list);
if (pika_rectangle_intersect (x,
y,
new_width,
new_height,
pika_item_get_offset_x (child),
pika_item_get_offset_y (child),
pika_item_get_width (child),
pika_item_get_height (child),
&child_x,
&child_y,
&child_width,
&child_height))
{
gint child_offset_x = pika_item_get_offset_x (child) - child_x;
gint child_offset_y = pika_item_get_offset_y (child) - child_y;
pika_item_resize (child, context, fill_type,
child_width, child_height,
child_offset_x, child_offset_y);
}
else if (pika_item_is_attached (item))
{
pika_image_remove_layer (pika_item_get_image (item),
PIKA_LAYER (child),
TRUE, NULL);
}
else
{
pika_container_remove (private->children, PIKA_OBJECT (child));
}
}
pika_group_layer_resume_resize (group, TRUE);
}
static PikaTransformResize
pika_group_layer_get_clip (PikaItem *item,
PikaTransformResize clip_result)
{
/* TODO: add clipping support, by clipping all sublayers as a unit, instead
* of individually.
*/
return PIKA_TRANSFORM_RESIZE_ADJUST;
}
static gint64
pika_group_layer_estimate_memsize (PikaDrawable *drawable,
PikaComponentType component_type,
gint width,
gint height)
{
PikaGroupLayerPrivate *private = GET_PRIVATE (drawable);
GList *list;
PikaImageBaseType base_type;
gint64 memsize = 0;
for (list = pika_item_stack_get_item_iter (PIKA_ITEM_STACK (private->children));
list;
list = g_list_next (list))
{
PikaDrawable *child = list->data;
gint child_width;
gint child_height;
child_width = (pika_item_get_width (PIKA_ITEM (child)) *
width /
pika_item_get_width (PIKA_ITEM (drawable)));
child_height = (pika_item_get_height (PIKA_ITEM (child)) *
height /
pika_item_get_height (PIKA_ITEM (drawable)));
memsize += pika_drawable_estimate_memsize (child,
component_type,
child_width,
child_height);
}
base_type = pika_drawable_get_base_type (drawable);
memsize += pika_projection_estimate_memsize (base_type, component_type,
width, height);
return memsize +
PIKA_DRAWABLE_CLASS (parent_class)->estimate_memsize (drawable,
component_type,
width, height);
}
static void
pika_group_layer_update_all (PikaDrawable *drawable)
{
PikaGroupLayerPrivate *private = GET_PRIVATE (drawable);
GList *list;
/* redirect stack updates to the drawable, rather than to the projection */
private->direct_update++;
for (list = pika_item_stack_get_item_iter (PIKA_ITEM_STACK (private->children));
list;
list = g_list_next (list))
{
PikaFilter *child = list->data;
if (pika_filter_get_active (child))
pika_drawable_update_all (PIKA_DRAWABLE (child));
}
/* redirect stack updates back to the projection */
private->direct_update--;
}
static void
pika_group_layer_translate (PikaLayer *layer,
gint offset_x,
gint offset_y)
{
PikaGroupLayer *group = PIKA_GROUP_LAYER (layer);
PikaGroupLayerPrivate *private = GET_PRIVATE (layer);
gint x, y;
GList *list;
/* don't use pika_group_layer_suspend_resize(), but rather increment
* private->suspend_resize directly, since we're translating the group layer
* here, rather than relying on pika_group_layer_update_size() to do it.
*/
private->suspend_resize++;
/* redirect stack updates to the drawable, rather than to the projection */
private->direct_update++;
/* translate the child layers *before* updating the group's offset, so that,
* if this is a nested group, the parent's bounds still reflect the original
* layer positions. This prevents the original area of the child layers,
* which is updated as part of their translation, from being clipped to the
* post-translation parent bounds (see issue #3484). The new area of the
* child layers, which is likewise updated as part of translation, *may* get
* clipped to the old parent bounds, but the corresponding region will be
* updated anyway when the parent is resized, once we update the group's
* offset.
*/
for (list = pika_item_stack_get_item_iter (PIKA_ITEM_STACK (private->children));
list;
list = g_list_next (list))
{
PikaItem *child = list->data;
/* don't push an undo here because undo will call us again */
pika_item_translate (child, offset_x, offset_y, FALSE);
}
pika_item_get_offset (PIKA_ITEM (group), &x, &y);
x += offset_x;
y += offset_y;
/* update the offset node */
if (private->offset_node)
gegl_node_set (private->offset_node,
"x", (gdouble) -x,
"y", (gdouble) -y,
NULL);
/* invalidate the selection boundary because of a layer modification */
pika_drawable_invalidate_boundary (PIKA_DRAWABLE (layer));
/* update the group layer offset */
pika_item_set_offset (PIKA_ITEM (group), x, y);
/* redirect stack updates back to the projection */
private->direct_update--;
/* don't use pika_group_layer_resume_resize(), but rather decrement
* private->suspend_resize directly, so that pika_group_layer_update_size()
* isn't called.
*/
private->suspend_resize--;
}
static void
pika_group_layer_scale (PikaLayer *layer,
gint new_width,
gint new_height,
gint new_offset_x,
gint new_offset_y,
PikaInterpolationType interpolation_type,
PikaProgress *progress)
{
PikaGroupLayer *group = PIKA_GROUP_LAYER (layer);
PikaGroupLayerPrivate *private = GET_PRIVATE (layer);
PikaItem *item = PIKA_ITEM (layer);
PikaObjectQueue *queue = NULL;
GList *list;
gdouble width_factor;
gdouble height_factor;
gint old_offset_x;
gint old_offset_y;
width_factor = (gdouble) new_width / (gdouble) pika_item_get_width (item);
height_factor = (gdouble) new_height / (gdouble) pika_item_get_height (item);
old_offset_x = pika_item_get_offset_x (item);
old_offset_y = pika_item_get_offset_y (item);
if (progress)
{
queue = pika_object_queue_new (progress);
progress = PIKA_PROGRESS (queue);
pika_object_queue_push_container (queue, private->children);
}
pika_group_layer_suspend_resize (group, TRUE);
list = pika_item_stack_get_item_iter (PIKA_ITEM_STACK (private->children));
while (list)
{
PikaItem *child = list->data;
list = g_list_next (list);
if (queue)
pika_object_queue_pop (queue);
if (! pika_item_scale_by_factors_with_origin (child,
width_factor, height_factor,
old_offset_x, old_offset_y,
new_offset_x, new_offset_y,
interpolation_type,
progress))
{
/* new width or height are 0; remove item */
if (pika_item_is_attached (item))
{
pika_image_remove_layer (pika_item_get_image (item),
PIKA_LAYER (child),
TRUE, NULL);
}
else
{
pika_container_remove (private->children, PIKA_OBJECT (child));
}
}
}
pika_group_layer_resume_resize (group, TRUE);
g_clear_object (&queue);
}
static void
pika_group_layer_flip (PikaLayer *layer,
PikaContext *context,
PikaOrientationType flip_type,
gdouble axis,
gboolean clip_result)
{
PikaGroupLayer *group = PIKA_GROUP_LAYER (layer);
PikaGroupLayerPrivate *private = GET_PRIVATE (layer);
GList *list;
pika_group_layer_suspend_resize (group, TRUE);
for (list = pika_item_stack_get_item_iter (PIKA_ITEM_STACK (private->children));
list;
list = g_list_next (list))
{
PikaItem *child = list->data;
pika_item_flip (child, context,
flip_type, axis, clip_result);
}
pika_group_layer_resume_resize (group, TRUE);
}
static void
pika_group_layer_rotate (PikaLayer *layer,
PikaContext *context,
PikaRotationType rotate_type,
gdouble center_x,
gdouble center_y,
gboolean clip_result)
{
PikaGroupLayer *group = PIKA_GROUP_LAYER (layer);
PikaGroupLayerPrivate *private = GET_PRIVATE (layer);
GList *list;
pika_group_layer_suspend_resize (group, TRUE);
for (list = pika_item_stack_get_item_iter (PIKA_ITEM_STACK (private->children));
list;
list = g_list_next (list))
{
PikaItem *child = list->data;
pika_item_rotate (child, context,
rotate_type, center_x, center_y, clip_result);
}
pika_group_layer_resume_resize (group, TRUE);
}
static void
pika_group_layer_transform (PikaLayer *layer,
PikaContext *context,
const PikaMatrix3 *matrix,
PikaTransformDirection direction,
PikaInterpolationType interpolation_type,
PikaTransformResize clip_result,
PikaProgress *progress)
{
PikaGroupLayer *group = PIKA_GROUP_LAYER (layer);
PikaGroupLayerPrivate *private = GET_PRIVATE (layer);
PikaObjectQueue *queue = NULL;
GList *list;
if (progress)
{
queue = pika_object_queue_new (progress);
progress = PIKA_PROGRESS (queue);
pika_object_queue_push_container (queue, private->children);
}
pika_group_layer_suspend_resize (group, TRUE);
for (list = pika_item_stack_get_item_iter (PIKA_ITEM_STACK (private->children));
list;
list = g_list_next (list))
{
PikaItem *child = list->data;
if (queue)
pika_object_queue_pop (queue);
pika_item_transform (child, context,
matrix, direction,
interpolation_type,
clip_result, progress);
}
pika_group_layer_resume_resize (group, TRUE);
g_clear_object (&queue);
}
static const Babl *
get_projection_format (PikaProjectable *projectable,
PikaImageBaseType base_type,
PikaPrecision precision)
{
PikaImage *image = pika_item_get_image (PIKA_ITEM (projectable));
switch (base_type)
{
case PIKA_RGB:
case PIKA_INDEXED:
return pika_image_get_format (image, PIKA_RGB, precision, TRUE,
pika_image_get_layer_space (image));
case PIKA_GRAY:
return pika_image_get_format (image, PIKA_GRAY, precision, TRUE,
pika_image_get_layer_space (image));
}
g_return_val_if_reached (NULL);
}
static void
pika_group_layer_convert_type (PikaLayer *layer,
PikaImage *dest_image,
const Babl *new_format,
PikaColorProfile *src_profile,
PikaColorProfile *dest_profile,
GeglDitherMethod layer_dither_type,
GeglDitherMethod mask_dither_type,
gboolean push_undo,
PikaProgress *progress)
{
PikaGroupLayer *group = PIKA_GROUP_LAYER (layer);
PikaGroupLayerPrivate *private = GET_PRIVATE (layer);
GeglBuffer *buffer;
if (push_undo)
{
PikaImage *image = pika_item_get_image (PIKA_ITEM (group));
pika_image_undo_push_group_layer_convert (image, NULL, group);
}
/* Need to temporarily set the projectable's format to the new
* values so the projection will create its tiles with the right
* depth
*/
private->convert_format =
get_projection_format (PIKA_PROJECTABLE (group),
pika_babl_format_get_base_type (new_format),
pika_babl_format_get_precision (new_format));
pika_projectable_structure_changed (PIKA_PROJECTABLE (group));
pika_group_layer_flush (group);
buffer = pika_pickable_get_buffer (PIKA_PICKABLE (private->projection));
pika_drawable_set_buffer_full (PIKA_DRAWABLE (group),
FALSE, NULL,
buffer, NULL,
TRUE);
/* reset, the actual format is right now */
private->convert_format = NULL;
}
static GeglNode *
pika_group_layer_get_source_node (PikaDrawable *drawable)
{
PikaGroupLayerPrivate *private = GET_PRIVATE (drawable);
GeglNode *input;
g_warn_if_fail (private->source_node == NULL);
private->source_node = gegl_node_new ();
input = gegl_node_get_input_proxy (private->source_node, "input");
private->parent_source_node =
PIKA_DRAWABLE_CLASS (parent_class)->get_source_node (drawable);
gegl_node_add_child (private->source_node, private->parent_source_node);
g_object_unref (private->parent_source_node);
if (gegl_node_has_pad (private->parent_source_node, "input"))
{
gegl_node_link (input, private->parent_source_node);
}
/* make sure we have a graph */
(void) pika_group_layer_get_graph (PIKA_PROJECTABLE (drawable));
gegl_node_add_child (private->source_node, private->graph);
pika_group_layer_update_source_node (PIKA_GROUP_LAYER (drawable));
return g_object_ref (private->source_node);
}
static void
pika_group_layer_opacity_changed (PikaLayer *layer)
{
pika_layer_update_effective_mode (layer);
if (PIKA_LAYER_CLASS (parent_class)->opacity_changed)
PIKA_LAYER_CLASS (parent_class)->opacity_changed (layer);
}
static void
pika_group_layer_effective_mode_changed (PikaLayer *layer)
{
PikaGroupLayer *group = PIKA_GROUP_LAYER (layer);
PikaGroupLayerPrivate *private = GET_PRIVATE (layer);
PikaLayerMode mode;
gboolean pass_through;
gboolean update_bounding_box = FALSE;
pika_layer_get_effective_mode (layer, &mode, NULL, NULL, NULL);
pass_through = (mode == PIKA_LAYER_MODE_PASS_THROUGH);
if (pass_through != private->pass_through)
{
if (private->pass_through && ! pass_through)
{
/* when switching from pass-through mode to a non-pass-through mode,
* flush the pickable in order to make sure the projection's buffer
* gets properly invalidated synchronously, so that it can be used
* as a source for the rest of the composition.
*/
pika_pickable_flush (PIKA_PICKABLE (private->projection));
}
private->pass_through = pass_through;
update_bounding_box = TRUE;
}
pika_group_layer_update_source_node (group);
pika_group_layer_update_mode_node (group);
if (update_bounding_box)
pika_drawable_update_bounding_box (PIKA_DRAWABLE (group));
if (PIKA_LAYER_CLASS (parent_class)->effective_mode_changed)
PIKA_LAYER_CLASS (parent_class)->effective_mode_changed (layer);
}
static void
pika_group_layer_excludes_backdrop_changed (PikaLayer *layer)
{
PikaGroupLayer *group = PIKA_GROUP_LAYER (layer);
pika_group_layer_update_source_node (group);
pika_group_layer_update_mode_node (group);
if (PIKA_LAYER_CLASS (parent_class)->excludes_backdrop_changed)
PIKA_LAYER_CLASS (parent_class)->excludes_backdrop_changed (layer);
}
static GeglRectangle
pika_group_layer_get_bounding_box (PikaLayer *layer)
{
PikaGroupLayerPrivate *private = GET_PRIVATE (layer);
/* for pass-through groups, use the group's calculated bounding box, instead
* of the source-node's bounding box, since we don't update the bounding box
* on all events that may affect the latter, and since it includes the
* bounding box of the backdrop. this means we can't attach filters that may
* affect the bounding box to a pass-through group (since their effect weon't
* be reflected by the group's bounding box), but attaching filters to pass-
* through groups makes little sense anyway.
*/
if (private->pass_through)
return private->bounding_box;
else
return PIKA_LAYER_CLASS (parent_class)->get_bounding_box (layer);
}
static void
pika_group_layer_get_effective_mode (PikaLayer *layer,
PikaLayerMode *mode,
PikaLayerColorSpace *blend_space,
PikaLayerColorSpace *composite_space,
PikaLayerCompositeMode *composite_mode)
{
PikaGroupLayerPrivate *private = GET_PRIVATE (layer);
/* try to strength-reduce pass-through groups to normal groups, which are
* cheaper.
*/
if (pika_layer_get_mode (layer) == PIKA_LAYER_MODE_PASS_THROUGH &&
! no_pass_through_strength_reduction)
{
/* we perform the strength-reduction if:
*
* - the group has no active children;
*
* or,
*
* - the group has a single active child; or,
*
* - the effective mode of all the active children is normal, their
* effective composite mode is UNION, and their effective blend and
* composite spaces are equal;
*
* - and,
*
* - the group's opacity is 100%, and it has no mask (or the mask
* isn't applied); or,
*
* - the group's composite space equals the active children's
* composite space.
*/
GList *list;
gboolean reduce = TRUE;
gboolean first = TRUE;
*mode = PIKA_LAYER_MODE_NORMAL;
*blend_space = pika_layer_get_real_blend_space (layer);
*composite_space = pika_layer_get_real_composite_space (layer);
*composite_mode = pika_layer_get_real_composite_mode (layer);
for (list = pika_item_stack_get_item_iter (PIKA_ITEM_STACK (private->children));
list;
list = g_list_next (list))
{
PikaLayer *child = list->data;
if (! pika_filter_get_active (PIKA_FILTER (child)))
continue;
if (first)
{
pika_layer_get_effective_mode (child,
mode,
blend_space,
composite_space,
composite_mode);
if (*mode == PIKA_LAYER_MODE_NORMAL_LEGACY)
*mode = PIKA_LAYER_MODE_NORMAL;
first = FALSE;
}
else
{
PikaLayerMode other_mode;
PikaLayerColorSpace other_blend_space;
PikaLayerColorSpace other_composite_space;
PikaLayerCompositeMode other_composite_mode;
if (*mode != PIKA_LAYER_MODE_NORMAL ||
*composite_mode != PIKA_LAYER_COMPOSITE_UNION)
{
reduce = FALSE;
break;
}
pika_layer_get_effective_mode (child,
&other_mode,
&other_blend_space,
&other_composite_space,
&other_composite_mode);
if (other_mode == PIKA_LAYER_MODE_NORMAL_LEGACY)
other_mode = PIKA_LAYER_MODE_NORMAL;
if (other_mode != *mode ||
other_blend_space != *blend_space ||
other_composite_space != *composite_space ||
other_composite_mode != *composite_mode)
{
reduce = FALSE;
break;
}
}
}
if (reduce)
{
gboolean has_mask;
has_mask = pika_layer_get_mask (layer) &&
pika_layer_get_apply_mask (layer);
if (first ||
(pika_layer_get_opacity (layer) == PIKA_OPACITY_OPAQUE &&
! has_mask) ||
*composite_space == pika_layer_get_real_composite_space (layer))
{
/* strength reduction succeeded! */
return;
}
}
}
/* strength-reduction failed. chain up. */
PIKA_LAYER_CLASS (parent_class)->get_effective_mode (layer,
mode,
blend_space,
composite_space,
composite_mode);
}
static gboolean
pika_group_layer_get_excludes_backdrop (PikaLayer *layer)
{
PikaGroupLayerPrivate *private = GET_PRIVATE (layer);
if (private->pass_through)
{
GList *list;
for (list = pika_item_stack_get_item_iter (PIKA_ITEM_STACK (private->children));
list;
list = g_list_next (list))
{
PikaFilter *child = list->data;
if (pika_filter_get_active (child) &&
pika_layer_get_excludes_backdrop (PIKA_LAYER (child)))
return TRUE;
}
return FALSE;
}
else
return PIKA_LAYER_CLASS (parent_class)->get_excludes_backdrop (layer);
}
static const Babl *
pika_group_layer_get_format (PikaProjectable *projectable)
{
PikaGroupLayerPrivate *private = GET_PRIVATE (projectable);
PikaImageBaseType base_type;
PikaPrecision precision;
if (private->convert_format)
return private->convert_format;
base_type = pika_drawable_get_base_type (PIKA_DRAWABLE (projectable));
precision = pika_drawable_get_precision (PIKA_DRAWABLE (projectable));
return get_projection_format (projectable, base_type, precision);
}
static GeglRectangle
pika_group_layer_projectable_get_bounding_box (PikaProjectable *projectable)
{
PikaGroupLayerPrivate *private = GET_PRIVATE (projectable);
return private->bounding_box;
}
static GeglNode *
pika_group_layer_get_graph (PikaProjectable *projectable)
{
PikaGroupLayer *group = PIKA_GROUP_LAYER (projectable);
PikaGroupLayerPrivate *private = GET_PRIVATE (projectable);
GeglNode *input;
GeglNode *layers_node;
GeglNode *output;
gint off_x;
gint off_y;
if (private->graph)
return private->graph;
private->graph = gegl_node_new ();
input = gegl_node_get_input_proxy (private->graph, "input");
layers_node =
pika_filter_stack_get_graph (PIKA_FILTER_STACK (private->children));
gegl_node_add_child (private->graph, layers_node);
gegl_node_link (input, layers_node);
pika_item_get_offset (PIKA_ITEM (group), &off_x, &off_y);
private->offset_node = gegl_node_new_child (private->graph,
"operation", "gegl:translate",
"x", (gdouble) -off_x,
"y", (gdouble) -off_y,
NULL);
gegl_node_link (layers_node, private->offset_node);
output = gegl_node_get_output_proxy (private->graph, "output");
gegl_node_link (private->offset_node, output);
return private->graph;
}
static void
pika_group_layer_begin_render (PikaProjectable *projectable)
{
PikaGroupLayerPrivate *private = GET_PRIVATE (projectable);
if (private->source_node == NULL)
return;
if (private->pass_through)
gegl_node_disconnect (private->graph, "input");
}
static void
pika_group_layer_end_render (PikaProjectable *projectable)
{
PikaGroupLayerPrivate *private = GET_PRIVATE (projectable);
if (private->source_node == NULL)
return;
if (private->pass_through)
{
GeglNode *input;
input = gegl_node_get_input_proxy (private->source_node, "input");
gegl_node_link (input, private->graph);
}
}
static void
pika_group_layer_pickable_flush (PikaPickable *pickable)
{
PikaGroupLayerPrivate *private = GET_PRIVATE (pickable);
pika_pickable_flush (PIKA_PICKABLE (private->projection));
}
static gdouble
pika_group_layer_get_opacity_at (PikaPickable *pickable,
gint x,
gint y)
{
/* Only consider child layers as having content */
return PIKA_OPACITY_TRANSPARENT;
}
/* public functions */
PikaLayer *
pika_group_layer_new (PikaImage *image)
{
PikaGroupLayer *group;
const Babl *format;
g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL);
format = pika_image_get_layer_format (image, TRUE);
group = PIKA_GROUP_LAYER (pika_drawable_new (PIKA_TYPE_GROUP_LAYER,
image, NULL,
0, 0, 1, 1,
format));
pika_layer_set_mode (PIKA_LAYER (group),
pika_image_get_default_new_layer_mode (image),
FALSE);
return PIKA_LAYER (group);
}
PikaProjection *
pika_group_layer_get_projection (PikaGroupLayer *group)
{
g_return_val_if_fail (PIKA_IS_GROUP_LAYER (group), NULL);
return GET_PRIVATE (group)->projection;
}
void
pika_group_layer_suspend_resize (PikaGroupLayer *group,
gboolean push_undo)
{
PikaItem *item;
g_return_if_fail (PIKA_IS_GROUP_LAYER (group));
item = PIKA_ITEM (group);
if (! pika_item_is_attached (item))
push_undo = FALSE;
if (push_undo)
pika_image_undo_push_group_layer_suspend_resize (pika_item_get_image (item),
NULL, group);
GET_PRIVATE (group)->suspend_resize++;
}
void
pika_group_layer_resume_resize (PikaGroupLayer *group,
gboolean push_undo)
{
PikaGroupLayerPrivate *private;
PikaItem *item;
PikaItem *mask = NULL;
GeglBuffer *mask_buffer;
GeglRectangle mask_bounds;
PikaUndo *undo;
g_return_if_fail (PIKA_IS_GROUP_LAYER (group));
private = GET_PRIVATE (group);
g_return_if_fail (private->suspend_resize > 0);
item = PIKA_ITEM (group);
if (! pika_item_is_attached (item))
push_undo = FALSE;
if (push_undo)
{
undo =
pika_image_undo_push_group_layer_resume_resize (pika_item_get_image (item),
NULL, group);
/* if there were any {suspend,resume}_mask() calls during the time the
* group's size was suspended, the resume_mask() calls will not have seen
* any changes to the mask, and will therefore won't restore the mask
* during undo. if the group's bounding box did change while resize was
* suspended, and if there are no other {suspend,resume}_mask() blocks
* that will see the resized mask, we have to restore the mask during the
* resume_resize() undo.
*
* we ref the mask buffer here, and compare it to the mask buffer after
* updating the size.
*/
if (private->suspend_resize == 1 && private->suspend_mask == 0)
{
mask = PIKA_ITEM (pika_layer_get_mask (PIKA_LAYER (group)));
if (mask)
{
mask_buffer =
g_object_ref (pika_drawable_get_buffer (PIKA_DRAWABLE (mask)));
mask_bounds.x = pika_item_get_offset_x (mask);
mask_bounds.y = pika_item_get_offset_y (mask);
mask_bounds.width = pika_item_get_width (mask);
mask_bounds.height = pika_item_get_height (mask);
}
}
}
private->suspend_resize--;
if (private->suspend_resize == 0)
{
pika_group_layer_update_size (group);
if (mask)
{
/* if the mask changed, make sure it's restored during undo, as per
* the comment above.
*/
if (pika_drawable_get_buffer (PIKA_DRAWABLE (mask)) != mask_buffer)
{
g_return_if_fail (undo != NULL);
PIKA_GROUP_LAYER_UNDO (undo)->mask_buffer = mask_buffer;
PIKA_GROUP_LAYER_UNDO (undo)->mask_bounds = mask_bounds;
}
else
{
g_object_unref (mask_buffer);
}
}
}
}
void
pika_group_layer_suspend_mask (PikaGroupLayer *group,
gboolean push_undo)
{
PikaGroupLayerPrivate *private;
PikaItem *item;
g_return_if_fail (PIKA_IS_GROUP_LAYER (group));
private = GET_PRIVATE (group);
item = PIKA_ITEM (group);
/* avoid pushing an undo step if this is a nested suspend_mask() call, since
* the value of 'push_undo' in nested calls should be the same as that passed
* to the outermost call, and only pushing an undo step for the outermost
* call in this case is enough. we can't support cases where the values of
* 'push_undo' in nested calls are different in a meaningful way, and
* avoiding undo steps for nested calls prevents us from storing multiple
* references to the suspend mask buffer on the undo stack. while storing
* multiple references to the buffer doesn't waste any memory (since all the
* references are to the same buffer), it does cause the undo stack memory-
* usage estimation to overshoot, potentially resulting in undo steps being
* dropped unnecessarily.
*/
if (! pika_item_is_attached (item) || private->suspend_mask > 0)
push_undo = FALSE;
if (push_undo)
pika_image_undo_push_group_layer_suspend_mask (pika_item_get_image (item),
NULL, group);
if (private->suspend_mask == 0)
{
if (pika_layer_get_mask (PIKA_LAYER (group)))
{
PikaItem *mask = PIKA_ITEM (pika_layer_get_mask (PIKA_LAYER (group)));
private->suspended_mask_buffer =
g_object_ref (pika_drawable_get_buffer (PIKA_DRAWABLE (mask)));
private->suspended_mask_bounds.x = pika_item_get_offset_x (mask);
private->suspended_mask_bounds.y = pika_item_get_offset_y (mask);
private->suspended_mask_bounds.width = pika_item_get_width (mask);
private->suspended_mask_bounds.height = pika_item_get_height (mask);
}
else
{
private->suspended_mask_buffer = NULL;
}
}
private->suspend_mask++;
}
void
pika_group_layer_resume_mask (PikaGroupLayer *group,
gboolean push_undo)
{
PikaGroupLayerPrivate *private;
PikaItem *item;
g_return_if_fail (PIKA_IS_GROUP_LAYER (group));
private = GET_PRIVATE (group);
g_return_if_fail (private->suspend_mask > 0);
item = PIKA_ITEM (group);
/* avoid pushing an undo step if this is a nested resume_mask() call. see
* the comment in pika_group_layer_suspend_mask().
*/
if (! pika_item_is_attached (item) || private->suspend_mask > 1)
push_undo = FALSE;
if (push_undo)
pika_image_undo_push_group_layer_resume_mask (pika_item_get_image (item),
NULL, group);
private->suspend_mask--;
if (private->suspend_mask == 0)
g_clear_object (&private->suspended_mask_buffer);
}
/* protected functions */
void
_pika_group_layer_set_suspended_mask (PikaGroupLayer *group,
GeglBuffer *buffer,
const GeglRectangle *bounds)
{
PikaGroupLayerPrivate *private;
g_return_if_fail (PIKA_IS_GROUP_LAYER (group));
g_return_if_fail (buffer != NULL);
g_return_if_fail (bounds != NULL);
private = GET_PRIVATE (group);
g_return_if_fail (private->suspend_mask > 0);
g_object_ref (buffer);
g_clear_object (&private->suspended_mask_buffer);
private->suspended_mask_buffer = buffer;
private->suspended_mask_bounds = *bounds;
}
GeglBuffer *
_pika_group_layer_get_suspended_mask (PikaGroupLayer *group,
GeglRectangle *bounds)
{
PikaGroupLayerPrivate *private;
PikaLayerMask *mask;
g_return_val_if_fail (PIKA_IS_GROUP_LAYER (group), NULL);
g_return_val_if_fail (bounds != NULL, NULL);
private = GET_PRIVATE (group);
mask = pika_layer_get_mask (PIKA_LAYER (group));
g_return_val_if_fail (private->suspend_mask > 0, NULL);
if (mask &&
pika_drawable_get_buffer (PIKA_DRAWABLE (mask)) !=
private->suspended_mask_buffer)
{
*bounds = private->suspended_mask_bounds;
return private->suspended_mask_buffer;
}
return NULL;
}
void
_pika_group_layer_start_transform (PikaGroupLayer *group,
gboolean push_undo)
{
PikaGroupLayerPrivate *private;
PikaItem *item;
g_return_if_fail (PIKA_IS_GROUP_LAYER (group));
private = GET_PRIVATE (group);
item = PIKA_ITEM (group);
g_return_if_fail (private->suspend_mask == 0);
if (! pika_item_is_attached (item))
push_undo = FALSE;
if (push_undo)
pika_image_undo_push_group_layer_start_transform (pika_item_get_image (item),
NULL, group);
private->transforming++;
}
void
_pika_group_layer_end_transform (PikaGroupLayer *group,
gboolean push_undo)
{
PikaGroupLayerPrivate *private;
PikaItem *item;
g_return_if_fail (PIKA_IS_GROUP_LAYER (group));
private = GET_PRIVATE (group);
item = PIKA_ITEM (group);
g_return_if_fail (private->suspend_mask == 0);
g_return_if_fail (private->transforming > 0);
if (! pika_item_is_attached (item))
push_undo = FALSE;
if (push_undo)
pika_image_undo_push_group_layer_end_transform (pika_item_get_image (item),
NULL, group);
private->transforming--;
if (private->transforming == 0)
pika_group_layer_update_mask_size (PIKA_GROUP_LAYER (item));
}
/* private functions */
static void
pika_group_layer_child_add (PikaContainer *container,
PikaLayer *child,
PikaGroupLayer *group)
{
pika_group_layer_update (group);
if (pika_filter_get_active (PIKA_FILTER (child)))
{
pika_layer_update_effective_mode (PIKA_LAYER (group));
if (pika_layer_get_excludes_backdrop (child))
pika_layer_update_excludes_backdrop (PIKA_LAYER (group));
}
}
static void
pika_group_layer_child_remove (PikaContainer *container,
PikaLayer *child,
PikaGroupLayer *group)
{
pika_group_layer_update (group);
if (pika_filter_get_active (PIKA_FILTER (child)))
{
pika_layer_update_effective_mode (PIKA_LAYER (group));
if (pika_layer_get_excludes_backdrop (child))
pika_layer_update_excludes_backdrop (PIKA_LAYER (group));
}
}
static void
pika_group_layer_child_move (PikaLayer *child,
GParamSpec *pspec,
PikaGroupLayer *group)
{
pika_group_layer_update (group);
}
static void
pika_group_layer_child_resize (PikaLayer *child,
PikaGroupLayer *group)
{
pika_group_layer_update (group);
}
static void
pika_group_layer_child_active_changed (PikaLayer *child,
PikaGroupLayer *group)
{
pika_layer_update_effective_mode (PIKA_LAYER (group));
if (pika_layer_get_excludes_backdrop (child))
pika_layer_update_excludes_backdrop (PIKA_LAYER (group));
}
static void
pika_group_layer_child_effective_mode_changed (PikaLayer *child,
PikaGroupLayer *group)
{
if (pika_filter_get_active (PIKA_FILTER (child)))
pika_layer_update_effective_mode (PIKA_LAYER (group));
}
static void
pika_group_layer_child_excludes_backdrop_changed (PikaLayer *child,
PikaGroupLayer *group)
{
if (pika_filter_get_active (PIKA_FILTER (child)))
pika_layer_update_excludes_backdrop (PIKA_LAYER (group));
}
static void
pika_group_layer_flush (PikaGroupLayer *group)
{
PikaGroupLayerPrivate *private = GET_PRIVATE (group);
if (private->pass_through)
{
/* flush the projectable, not the pickable, because the source
* node of pass-through groups doesn't use the projection's
* buffer, hence there's no need to invalidate it synchronously.
*/
pika_projectable_flush (PIKA_PROJECTABLE (group), TRUE);
}
else
{
/* make sure we have a buffer, and stop any idle rendering, which is
* initiated when a new buffer is allocated. the call to
* pika_pickable_flush() below causes any pending idle rendering to
* finish synchronously, so this needs to happen before.
*/
pika_pickable_get_buffer (PIKA_PICKABLE (private->projection));
pika_projection_stop_rendering (private->projection);
/* flush the pickable not the projectable because flushing the
* pickable will finish all invalidation on the projection so it
* can be used as source (note that it will still be constructed
* when the actual read happens, so this it not a performance
* problem)
*/
pika_pickable_flush (PIKA_PICKABLE (private->projection));
}
}
static void
pika_group_layer_update (PikaGroupLayer *group)
{
if (GET_PRIVATE (group)->suspend_resize == 0)
{
pika_group_layer_update_size (group);
}
}
static void
pika_group_layer_update_size (PikaGroupLayer *group)
{
PikaGroupLayerPrivate *private = GET_PRIVATE (group);
PikaItem *item = PIKA_ITEM (group);
PikaLayer *layer = PIKA_LAYER (group);
PikaItem *mask = PIKA_ITEM (pika_layer_get_mask (layer));
GeglRectangle old_bounds;
GeglRectangle bounds;
GeglRectangle old_bounding_box;
GeglRectangle bounding_box;
gboolean first = TRUE;
gboolean size_changed;
gboolean resize_mask;
GList *list;
old_bounds.x = pika_item_get_offset_x (item);
old_bounds.y = pika_item_get_offset_y (item);
old_bounds.width = pika_item_get_width (item);
old_bounds.height = pika_item_get_height (item);
bounds.x = 0;
bounds.y = 0;
bounds.width = 1;
bounds.height = 1;
old_bounding_box = private->bounding_box;
bounding_box = bounds;
for (list = pika_item_stack_get_item_iter (PIKA_ITEM_STACK (private->children));
list;
list = g_list_next (list))
{
PikaItem *child = list->data;
GeglRectangle child_bounds;
GeglRectangle child_bounding_box;
if (! pika_viewable_get_size (PIKA_VIEWABLE (child),
&child_bounds.width, &child_bounds.height))
{
/* ignore children without content (empty group layers);
* see bug 777017
*/
continue;
}
pika_item_get_offset (child, &child_bounds.x, &child_bounds.y);
child_bounding_box =
pika_drawable_get_bounding_box (PIKA_DRAWABLE (child));
child_bounding_box.x += child_bounds.x;
child_bounding_box.y += child_bounds.y;
if (first)
{
bounds = child_bounds;
bounding_box = child_bounding_box;
first = FALSE;
}
else
{
gegl_rectangle_bounding_box (&bounds,
&bounds, &child_bounds);
gegl_rectangle_bounding_box (&bounding_box,
&bounding_box, &child_bounding_box);
}
}
bounding_box.x -= bounds.x;
bounding_box.y -= bounds.y;
size_changed = ! (gegl_rectangle_equal (&bounds, &old_bounds) &&
gegl_rectangle_equal (&bounding_box, &old_bounding_box));
resize_mask = mask && ! gegl_rectangle_equal (&bounds, &old_bounds);
/* if we show the mask, invalidate the old mask area */
if (resize_mask && pika_layer_get_show_mask (layer))
{
pika_drawable_update (PIKA_DRAWABLE (group),
pika_item_get_offset_x (mask) - old_bounds.x,
pika_item_get_offset_y (mask) - old_bounds.y,
pika_item_get_width (mask),
pika_item_get_height (mask));
}
if (private->reallocate_projection || size_changed)
{
GeglBuffer *buffer;
/* if the graph is already constructed, set the offset node's
* coordinates first, so the graph is in the right state when
* the projection is reallocated, see bug #730550.
*/
if (private->offset_node)
gegl_node_set (private->offset_node,
"x", (gdouble) -bounds.x,
"y", (gdouble) -bounds.y,
NULL);
/* update our offset *before* calling pika_pickable_get_buffer(), so
* that if our graph isn't constructed yet, the offset node picks
* up the right coordinates in pika_group_layer_get_graph().
*/
pika_item_set_offset (item, bounds.x, bounds.y);
/* update the bounding box before updating the projection, so that it
* picks up the right size.
*/
private->bounding_box = bounding_box;
if (private->reallocate_projection)
{
private->reallocate_projection = FALSE;
pika_projectable_structure_changed (PIKA_PROJECTABLE (group));
}
else
{
/* when there's no need to reallocate the projection, we call
* pika_projectable_bounds_changed(), rather than structure_chaned(),
* so that the projection simply copies the old content over to the
* new buffer with an offset, rather than re-renders the graph.
*/
pika_projectable_bounds_changed (PIKA_PROJECTABLE (group),
old_bounds.x, old_bounds.y);
}
buffer = pika_pickable_get_buffer (PIKA_PICKABLE (private->projection));
pika_drawable_set_buffer_full (PIKA_DRAWABLE (group),
FALSE, NULL,
buffer, &bounds,
FALSE /* don't update the drawable, the
* flush() below will take care of
* that.
*/);
pika_group_layer_flush (group);
}
/* resize the mask if not transforming (in which case, PikaLayer takes care
* of the mask)
*/
if (resize_mask && ! private->transforming)
pika_group_layer_update_mask_size (group);
/* if we show the mask, invalidate the new mask area */
if (resize_mask && pika_layer_get_show_mask (layer))
{
pika_drawable_update (PIKA_DRAWABLE (group),
pika_item_get_offset_x (mask) - bounds.x,
pika_item_get_offset_y (mask) - bounds.y,
pika_item_get_width (mask),
pika_item_get_height (mask));
}
}
static void
pika_group_layer_update_mask_size (PikaGroupLayer *group)
{
PikaGroupLayerPrivate *private = GET_PRIVATE (group);
PikaItem *item = PIKA_ITEM (group);
PikaItem *mask;
GeglBuffer *buffer;
GeglBuffer *mask_buffer;
GeglRectangle bounds;
GeglRectangle mask_bounds;
GeglRectangle copy_bounds;
gboolean intersect;
mask = PIKA_ITEM (pika_layer_get_mask (PIKA_LAYER (group)));
if (! mask)
return;
bounds.x = pika_item_get_offset_x (item);
bounds.y = pika_item_get_offset_y (item);
bounds.width = pika_item_get_width (item);
bounds.height = pika_item_get_height (item);
mask_bounds.x = pika_item_get_offset_x (mask);
mask_bounds.y = pika_item_get_offset_y (mask);
mask_bounds.width = pika_item_get_width (mask);
mask_bounds.height = pika_item_get_height (mask);
if (gegl_rectangle_equal (&bounds, &mask_bounds))
return;
buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0, bounds.width, bounds.height),
pika_drawable_get_format (PIKA_DRAWABLE (mask)));
if (private->suspended_mask_buffer)
{
/* copy the suspended mask into the new mask */
mask_buffer = private->suspended_mask_buffer;
mask_bounds = private->suspended_mask_bounds;
}
else
{
/* copy the old mask into the new mask */
mask_buffer = pika_drawable_get_buffer (PIKA_DRAWABLE (mask));
}
intersect = pika_rectangle_intersect (bounds.x,
bounds.y,
bounds.width,
bounds.height,
mask_bounds.x,
mask_bounds.y,
mask_bounds.width,
mask_bounds.height,
&copy_bounds.x,
&copy_bounds.y,
&copy_bounds.width,
&copy_bounds.height);
if (intersect)
{
pika_gegl_buffer_copy (mask_buffer,
GEGL_RECTANGLE (copy_bounds.x - mask_bounds.x,
copy_bounds.y - mask_bounds.y,
copy_bounds.width,
copy_bounds.height),
GEGL_ABYSS_NONE,
buffer,
GEGL_RECTANGLE (copy_bounds.x - bounds.x,
copy_bounds.y - bounds.y,
copy_bounds.width,
copy_bounds.height));
}
pika_drawable_set_buffer_full (PIKA_DRAWABLE (mask),
FALSE, NULL,
buffer, &bounds,
TRUE);
g_object_unref (buffer);
}
static void
pika_group_layer_update_source_node (PikaGroupLayer *group)
{
PikaGroupLayerPrivate *private = GET_PRIVATE (group);
GeglNode *input;
GeglNode *output;
if (private->source_node == NULL)
return;
input = gegl_node_get_input_proxy (private->source_node, "input");
output = gegl_node_get_output_proxy (private->source_node, "output");
if (private->pass_through)
{
gegl_node_link (input, private->graph);
gegl_node_link (private->graph, output);
}
else
{
gegl_node_disconnect (private->graph, "input");
gegl_node_link (private->parent_source_node, output);
}
}
static void
pika_group_layer_update_mode_node (PikaGroupLayer *group)
{
PikaGroupLayerPrivate *private = GET_PRIVATE (group);
GeglNode *node;
GeglNode *input;
GeglNode *mode_node;
node = pika_filter_get_node (PIKA_FILTER (group));
input = gegl_node_get_input_proxy (node, "input");
mode_node = pika_drawable_get_mode_node (PIKA_DRAWABLE (group));
if (private->pass_through &&
pika_layer_get_excludes_backdrop (PIKA_LAYER (group)))
{
gegl_node_disconnect (mode_node, "input");
}
else
{
gegl_node_link (input, mode_node);
}
}
static void
pika_group_layer_stack_update (PikaDrawableStack *stack,
gint x,
gint y,
gint width,
gint height,
PikaGroupLayer *group)
{
PikaGroupLayerPrivate *private = GET_PRIVATE (group);
#if 0
g_printerr ("%s (%s) %d, %d (%d, %d)\n",
G_STRFUNC, pika_object_get_name (group),
x, y, width, height);
#endif
if (! private->direct_update)
{
/* the layer stack's update signal speaks in image coordinates,
* pass to the projection as-is.
*/
pika_projectable_invalidate (PIKA_PROJECTABLE (group),
x, y, width, height);
pika_group_layer_flush (group);
}
if (private->direct_update || private->pass_through)
{
/* the layer stack's update signal speaks in image coordinates,
* transform to layer coordinates when emitting our own update signal.
*/
pika_drawable_update (PIKA_DRAWABLE (group),
x - pika_item_get_offset_x (PIKA_ITEM (group)),
y - pika_item_get_offset_y (PIKA_ITEM (group)),
width, height);
}
}
static void
pika_group_layer_proj_update (PikaProjection *proj,
gboolean now,
gint x,
gint y,
gint width,
gint height,
PikaGroupLayer *group)
{
PikaGroupLayerPrivate *private = GET_PRIVATE (group);
#if 0
g_printerr ("%s (%s) %d, %d (%d, %d)\n",
G_STRFUNC, pika_object_get_name (group),
x, y, width, height);
#endif
if (! private->pass_through)
{
/* TODO: groups can currently have a gegl:transform op attached as a filter
* when using a transform tool, in which case the updated region needs
* undergo the same transformation. more generally, when a drawable has
* filters they may influence the area affected by drawable updates.
*
* this needs to be addressed much more generally at some point, but for now
* we just resort to updating the entire group when it has a filter (i.e.,
* when it's being used with a transform tool). we restrict this to groups,
* and don't do this more generally in pika_drawable_update(), because this
* negatively impacts the performance of the warp tool, which does perform
* accurate drawable updates while using a filter.
*/
if (pika_drawable_has_filters (PIKA_DRAWABLE (group)))
{
width = -1;
height = -1;
}
/* the projection speaks in image coordinates, transform to layer
* coordinates when emitting our own update signal.
*/
pika_drawable_update (PIKA_DRAWABLE (group),
x - pika_item_get_offset_x (PIKA_ITEM (group)),
y - pika_item_get_offset_y (PIKA_ITEM (group)),
width, height);
}
}