gimp/app/core/gimpgrouplayer.c

2192 lines
78 KiB
C
Raw Normal View History

/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* GimpGroupLayer
* 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 "libgimpbase/gimpbase.h"
#include "libgimpmath/gimpmath.h"
#include "core-types.h"
#include "gegl/gimp-babl.h"
#include "gegl/gimp-gegl-loops.h"
#include "gimpgrouplayer.h"
#include "gimpgrouplayerundo.h"
#include "gimpimage.h"
#include "gimpimage-undo.h"
#include "gimpimage-undo-push.h"
#include "gimplayerstack.h"
#include "gimpobjectqueue.h"
#include "gimppickable.h"
#include "gimpprogress.h"
#include "gimpprojectable.h"
#include "gimpprojection.h"
#include "gimp-intl.h"
typedef struct _GimpGroupLayerPrivate GimpGroupLayerPrivate;
struct _GimpGroupLayerPrivate
{
GimpContainer *children;
GimpProjection *projection;
GeglNode *source_node;
GeglNode *parent_source_node;
GeglNode *graph;
GeglNode *offset_node;
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;
gint reallocate_width;
gint reallocate_height;
};
#define GET_PRIVATE(item) ((GimpGroupLayerPrivate *) gimp_group_layer_get_instance_private ((GimpGroupLayer *) (item)))
static void gimp_projectable_iface_init (GimpProjectableInterface *iface);
static void gimp_pickable_iface_init (GimpPickableInterface *iface);
static void gimp_group_layer_finalize (GObject *object);
static void gimp_group_layer_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec);
static void gimp_group_layer_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec);
static gint64 gimp_group_layer_get_memsize (GimpObject *object,
gint64 *gui_size);
static void gimp_group_layer_ancestry_changed (GimpViewable *viewable);
static gboolean gimp_group_layer_get_size (GimpViewable *viewable,
gint *width,
gint *height);
static GimpContainer * gimp_group_layer_get_children (GimpViewable *viewable);
static gboolean gimp_group_layer_get_expanded (GimpViewable *viewable);
static void gimp_group_layer_set_expanded (GimpViewable *viewable,
gboolean expanded);
static gboolean gimp_group_layer_is_position_locked (GimpItem *item);
static GimpItem * gimp_group_layer_duplicate (GimpItem *item,
GType new_type);
static void gimp_group_layer_convert (GimpItem *item,
GimpImage *dest_image,
GType old_type);
Bug 795410 - Deleting a layer group and then undoing the deletion ... ... raises a CRITICAL gimp_item_{start,end}_move() currently serves two different purposes: It is used by GimpLayer to suspend/resume mask resizing of the layer's ancestors; this is necessary whenever an operation on a layer might affect the size of its ancestors. It is also used by GimpGroupLayer to suspend/resume its own mask resizing; this, on the other hand, is only necessary before applying one of the transformation functions to the group, so that mask modification is handled by GimpLayer. In other words, the effects of gimp_item_{start,end}_move() on group layers are only necessary in a subset of the cases in which these functions are used. While in itself this isn't a problem, it does cause issues when removing a group layer: gimp_image_remove_layer() calls gimp_item_start_move() before removing the layer, and gimp_item_end_move() afterwards. While the former function is called while the layer is still attached to the image, the latter function is called after the layer is no longer attached. Since GimpGroupLayer pushes an undo step in response to these calls, only the call to start_move() results in an undo step, while the call to end_move() doesn't, resulting in an unbalanced GIMP_UNDO_GROUP_LAYER_START_MOVE undo step on the stack. This causes problems when undoing the operation. Add gimp_item_{start,end}_transform() functions, and corresponding GimpItem::{start,end}_transform() virtual functions, which are more specialized versions of gimp_item_{start,end}_move(), which should be used instead of the former before/after transforming an item; in other cases, such as when removing ot reordering an item, gimp_item_{start,end}_move() should still be used. The default implementation of GimpItem::{start,end}_transform() calls gimp_item_{start,end}_move(), respectively, so subclasses that override these functions don't have to do that themselves. In GimpGroupLayer, override GimpItem::{start,end}_transform(), instead of GimpItem::{start,end}_move(), for the same purpose of suspending mask resize. This avoids these functions from being called when removing a layer group, fixing the bug.
2018-04-22 15:39:40 +08:00
static void gimp_group_layer_start_transform (GimpItem *item,
gboolean push_undo);
Bug 795410 - Deleting a layer group and then undoing the deletion ... ... raises a CRITICAL gimp_item_{start,end}_move() currently serves two different purposes: It is used by GimpLayer to suspend/resume mask resizing of the layer's ancestors; this is necessary whenever an operation on a layer might affect the size of its ancestors. It is also used by GimpGroupLayer to suspend/resume its own mask resizing; this, on the other hand, is only necessary before applying one of the transformation functions to the group, so that mask modification is handled by GimpLayer. In other words, the effects of gimp_item_{start,end}_move() on group layers are only necessary in a subset of the cases in which these functions are used. While in itself this isn't a problem, it does cause issues when removing a group layer: gimp_image_remove_layer() calls gimp_item_start_move() before removing the layer, and gimp_item_end_move() afterwards. While the former function is called while the layer is still attached to the image, the latter function is called after the layer is no longer attached. Since GimpGroupLayer pushes an undo step in response to these calls, only the call to start_move() results in an undo step, while the call to end_move() doesn't, resulting in an unbalanced GIMP_UNDO_GROUP_LAYER_START_MOVE undo step on the stack. This causes problems when undoing the operation. Add gimp_item_{start,end}_transform() functions, and corresponding GimpItem::{start,end}_transform() virtual functions, which are more specialized versions of gimp_item_{start,end}_move(), which should be used instead of the former before/after transforming an item; in other cases, such as when removing ot reordering an item, gimp_item_{start,end}_move() should still be used. The default implementation of GimpItem::{start,end}_transform() calls gimp_item_{start,end}_move(), respectively, so subclasses that override these functions don't have to do that themselves. In GimpGroupLayer, override GimpItem::{start,end}_transform(), instead of GimpItem::{start,end}_move(), for the same purpose of suspending mask resize. This avoids these functions from being called when removing a layer group, fixing the bug.
2018-04-22 15:39:40 +08:00
static void gimp_group_layer_end_transform (GimpItem *item,
gboolean push_undo);
static void gimp_group_layer_resize (GimpItem *item,
GimpContext *context,
GimpFillType fill_type,
gint new_width,
gint new_height,
gint offset_x,
gint offset_y);
static gint64 gimp_group_layer_estimate_memsize (GimpDrawable *drawable,
GimpComponentType component_type,
gint width,
gint height);
static void gimp_group_layer_translate (GimpLayer *layer,
gint offset_x,
gint offset_y);
static void gimp_group_layer_scale (GimpLayer *layer,
gint new_width,
gint new_height,
gint new_offset_x,
gint new_offset_y,
GimpInterpolationType interp_type,
GimpProgress *progress);
static void gimp_group_layer_flip (GimpLayer *layer,
GimpContext *context,
GimpOrientationType flip_type,
gdouble axis,
gboolean clip_result);
static void gimp_group_layer_rotate (GimpLayer *layer,
GimpContext *context,
GimpRotationType rotate_type,
gdouble center_x,
gdouble center_y,
gboolean clip_result);
static void gimp_group_layer_transform (GimpLayer *layer,
GimpContext *context,
const GimpMatrix3 *matrix,
GimpTransformDirection direction,
GimpInterpolationType interpolation_type,
GimpTransformResize clip_result,
GimpProgress *progress);
static void gimp_group_layer_convert_type (GimpLayer *layer,
GimpImage *dest_image,
const Babl *new_format,
GimpColorProfile *src_profile,
GimpColorProfile *dest_profile,
GeglDitherMethod layer_dither_type,
GeglDitherMethod mask_dither_type,
gboolean push_undo,
GimpProgress *progress);
static GeglNode * gimp_group_layer_get_source_node (GimpDrawable *drawable);
static void gimp_group_layer_opacity_changed (GimpLayer *layer);
static void gimp_group_layer_effective_mode_changed (GimpLayer *layer);
static void
gimp_group_layer_excludes_backdrop_changed (GimpLayer *layer);
static void gimp_group_layer_mask_changed (GimpLayer *layer);
static void gimp_group_layer_get_effective_mode (GimpLayer *layer,
GimpLayerMode *mode,
GimpLayerColorSpace *blend_space,
GimpLayerColorSpace *composite_space,
GimpLayerCompositeMode *composite_mode);
static gboolean
gimp_group_layer_get_excludes_backdrop (GimpLayer *layer);
static const Babl * gimp_group_layer_get_format (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);
static gdouble gimp_group_layer_get_opacity_at (GimpPickable *pickable,
gint x,
gint y);
static void gimp_group_layer_child_add (GimpContainer *container,
GimpLayer *child,
GimpGroupLayer *group);
static void gimp_group_layer_child_remove (GimpContainer *container,
GimpLayer *child,
GimpGroupLayer *group);
static void gimp_group_layer_child_move (GimpLayer *child,
GParamSpec *pspec,
GimpGroupLayer *group);
static void gimp_group_layer_child_resize (GimpLayer *child,
GimpGroupLayer *group);
static void gimp_group_layer_child_active_changed (GimpLayer *child,
GimpGroupLayer *group);
static void
gimp_group_layer_child_effective_mode_changed (GimpLayer *child,
GimpGroupLayer *group);
static void
gimp_group_layer_child_excludes_backdrop_changed (GimpLayer *child,
GimpGroupLayer *group);
static void gimp_group_layer_flush (GimpGroupLayer *group);
static void gimp_group_layer_update (GimpGroupLayer *group);
static void gimp_group_layer_update_size (GimpGroupLayer *group);
static void gimp_group_layer_update_mask_size (GimpGroupLayer *group);
static void gimp_group_layer_update_source_node (GimpGroupLayer *group);
static void gimp_group_layer_update_mode_node (GimpGroupLayer *group);
static void gimp_group_layer_stack_update (GimpDrawableStack *stack,
gint x,
gint y,
gint width,
gint height,
GimpGroupLayer *group);
static void gimp_group_layer_proj_update (GimpProjection *proj,
gboolean now,
gint x,
gint y,
gint width,
gint height,
GimpGroupLayer *group);
G_DEFINE_TYPE_WITH_CODE (GimpGroupLayer, gimp_group_layer, GIMP_TYPE_LAYER,
G_ADD_PRIVATE (GimpGroupLayer)
G_IMPLEMENT_INTERFACE (GIMP_TYPE_PROJECTABLE,
gimp_projectable_iface_init)
G_IMPLEMENT_INTERFACE (GIMP_TYPE_PICKABLE,
gimp_pickable_iface_init))
#define parent_class gimp_group_layer_parent_class
/* disable pass-through groups strength-reduction to normal groups.
* see gimp_group_layer_get_effective_mode().
*/
static gboolean no_pass_through_strength_reduction = FALSE;
static void
gimp_group_layer_class_init (GimpGroupLayerClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GimpObjectClass *gimp_object_class = GIMP_OBJECT_CLASS (klass);
GimpViewableClass *viewable_class = GIMP_VIEWABLE_CLASS (klass);
GimpItemClass *item_class = GIMP_ITEM_CLASS (klass);
GimpDrawableClass *drawable_class = GIMP_DRAWABLE_CLASS (klass);
GimpLayerClass *layer_class = GIMP_LAYER_CLASS (klass);
object_class->set_property = gimp_group_layer_set_property;
object_class->get_property = gimp_group_layer_get_property;
object_class->finalize = gimp_group_layer_finalize;
gimp_object_class->get_memsize = gimp_group_layer_get_memsize;
viewable_class->default_icon_name = "gimp-group-layer";
viewable_class->ancestry_changed = gimp_group_layer_ancestry_changed;
viewable_class->get_size = gimp_group_layer_get_size;
viewable_class->get_children = gimp_group_layer_get_children;
viewable_class->set_expanded = gimp_group_layer_set_expanded;
viewable_class->get_expanded = gimp_group_layer_get_expanded;
item_class->is_position_locked = gimp_group_layer_is_position_locked;
item_class->duplicate = gimp_group_layer_duplicate;
item_class->convert = gimp_group_layer_convert;
Bug 795410 - Deleting a layer group and then undoing the deletion ... ... raises a CRITICAL gimp_item_{start,end}_move() currently serves two different purposes: It is used by GimpLayer to suspend/resume mask resizing of the layer's ancestors; this is necessary whenever an operation on a layer might affect the size of its ancestors. It is also used by GimpGroupLayer to suspend/resume its own mask resizing; this, on the other hand, is only necessary before applying one of the transformation functions to the group, so that mask modification is handled by GimpLayer. In other words, the effects of gimp_item_{start,end}_move() on group layers are only necessary in a subset of the cases in which these functions are used. While in itself this isn't a problem, it does cause issues when removing a group layer: gimp_image_remove_layer() calls gimp_item_start_move() before removing the layer, and gimp_item_end_move() afterwards. While the former function is called while the layer is still attached to the image, the latter function is called after the layer is no longer attached. Since GimpGroupLayer pushes an undo step in response to these calls, only the call to start_move() results in an undo step, while the call to end_move() doesn't, resulting in an unbalanced GIMP_UNDO_GROUP_LAYER_START_MOVE undo step on the stack. This causes problems when undoing the operation. Add gimp_item_{start,end}_transform() functions, and corresponding GimpItem::{start,end}_transform() virtual functions, which are more specialized versions of gimp_item_{start,end}_move(), which should be used instead of the former before/after transforming an item; in other cases, such as when removing ot reordering an item, gimp_item_{start,end}_move() should still be used. The default implementation of GimpItem::{start,end}_transform() calls gimp_item_{start,end}_move(), respectively, so subclasses that override these functions don't have to do that themselves. In GimpGroupLayer, override GimpItem::{start,end}_transform(), instead of GimpItem::{start,end}_move(), for the same purpose of suspending mask resize. This avoids these functions from being called when removing a layer group, fixing the bug.
2018-04-22 15:39:40 +08:00
item_class->start_transform = gimp_group_layer_start_transform;
item_class->end_transform = gimp_group_layer_end_transform;
item_class->resize = gimp_group_layer_resize;
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 = gimp_group_layer_estimate_memsize;
drawable_class->get_source_node = gimp_group_layer_get_source_node;
layer_class->opacity_changed = gimp_group_layer_opacity_changed;
layer_class->effective_mode_changed = gimp_group_layer_effective_mode_changed;
layer_class->excludes_backdrop_changed = gimp_group_layer_excludes_backdrop_changed;
layer_class->mask_changed = gimp_group_layer_mask_changed;
layer_class->translate = gimp_group_layer_translate;
layer_class->scale = gimp_group_layer_scale;
layer_class->flip = gimp_group_layer_flip;
layer_class->rotate = gimp_group_layer_rotate;
layer_class->transform = gimp_group_layer_transform;
layer_class->convert_type = gimp_group_layer_convert_type;
layer_class->get_effective_mode = gimp_group_layer_get_effective_mode;
layer_class->get_excludes_backdrop = gimp_group_layer_get_excludes_backdrop;
if (g_getenv ("GIMP_NO_PASS_THROUGH_STRENGTH_REDUCTION"))
no_pass_through_strength_reduction = TRUE;
}
static void
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_graph = gimp_group_layer_get_graph;
iface->begin_render = gimp_group_layer_begin_render;
iface->end_render = gimp_group_layer_end_render;
iface->invalidate_preview = (void (*) (GimpProjectable*)) gimp_viewable_invalidate_preview;
}
static void
gimp_pickable_iface_init (GimpPickableInterface *iface)
{
iface->get_opacity_at = gimp_group_layer_get_opacity_at;
}
static void
gimp_group_layer_init (GimpGroupLayer *group)
{
GimpGroupLayerPrivate *private = GET_PRIVATE (group);
private->children = gimp_layer_stack_new (GIMP_TYPE_LAYER);
private->expanded = TRUE;
g_signal_connect (private->children, "add",
G_CALLBACK (gimp_group_layer_child_add),
group);
g_signal_connect (private->children, "remove",
G_CALLBACK (gimp_group_layer_child_remove),
group);
gimp_container_add_handler (private->children, "notify::offset-x",
G_CALLBACK (gimp_group_layer_child_move),
group);
gimp_container_add_handler (private->children, "notify::offset-y",
G_CALLBACK (gimp_group_layer_child_move),
group);
gimp_container_add_handler (private->children, "size-changed",
G_CALLBACK (gimp_group_layer_child_resize),
group);
gimp_container_add_handler (private->children, "active-changed",
G_CALLBACK (gimp_group_layer_child_active_changed),
group);
gimp_container_add_handler (private->children, "effective-mode-changed",
G_CALLBACK (gimp_group_layer_child_effective_mode_changed),
group);
gimp_container_add_handler (private->children, "excludes-backdrop-changed",
G_CALLBACK (gimp_group_layer_child_excludes_backdrop_changed),
group);
g_signal_connect (private->children, "update",
G_CALLBACK (gimp_group_layer_stack_update),
group);
private->projection = gimp_projection_new (GIMP_PROJECTABLE (group));
gimp_projection_set_priority (private->projection, 1);
g_signal_connect (private->projection, "update",
G_CALLBACK (gimp_group_layer_proj_update),
group);
}
static void
gimp_group_layer_finalize (GObject *object)
{
GimpGroupLayerPrivate *private = GET_PRIVATE (object);
if (private->children)
{
g_signal_handlers_disconnect_by_func (private->children,
gimp_group_layer_child_add,
object);
g_signal_handlers_disconnect_by_func (private->children,
gimp_group_layer_child_remove,
object);
g_signal_handlers_disconnect_by_func (private->children,
gimp_group_layer_stack_update,
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
gimp_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
gimp_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
gimp_group_layer_get_memsize (GimpObject *object,
gint64 *gui_size)
{
GimpGroupLayerPrivate *private = GET_PRIVATE (object);
gint64 memsize = 0;
memsize += gimp_object_get_memsize (GIMP_OBJECT (private->children), gui_size);
memsize += gimp_object_get_memsize (GIMP_OBJECT (private->projection), gui_size);
return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object,
gui_size);
}
static void
gimp_group_layer_ancestry_changed (GimpViewable *viewable)
{
GimpGroupLayerPrivate *private = GET_PRIVATE (viewable);
gimp_projection_set_priority (private->projection,
gimp_viewable_get_depth (viewable) + 1);
GIMP_VIEWABLE_CLASS (parent_class)->ancestry_changed (viewable);
}
static gboolean
gimp_group_layer_get_size (GimpViewable *viewable,
gint *width,
gint *height)
{
GimpGroupLayerPrivate *private = GET_PRIVATE (viewable);
if (private->reallocate_width != 0 &&
private->reallocate_height != 0)
{
*width = private->reallocate_width;
*height = private->reallocate_height;
return TRUE;
}
/* return the size only if there are children... */
if (gimp_item_stack_get_item_iter (GIMP_ITEM_STACK (private->children)))
return GIMP_VIEWABLE_CLASS (parent_class)->get_size (viewable,
width, height);
/* ...otherwise return "no content" */
return FALSE;
}
static GimpContainer *
gimp_group_layer_get_children (GimpViewable *viewable)
{
return GET_PRIVATE (viewable)->children;
}
static gboolean
gimp_group_layer_get_expanded (GimpViewable *viewable)
{
GimpGroupLayer *group = GIMP_GROUP_LAYER (viewable);
return GET_PRIVATE (group)->expanded;
}
static void
gimp_group_layer_set_expanded (GimpViewable *viewable,
gboolean expanded)
{
GimpGroupLayerPrivate *private = GET_PRIVATE (viewable);
if (private->expanded != expanded)
{
private->expanded = expanded;
gimp_viewable_expanded_changed (viewable);
}
}
static gboolean
gimp_group_layer_is_position_locked (GimpItem *item)
{
GimpGroupLayerPrivate *private = GET_PRIVATE (item);
GList *list;
for (list = gimp_item_stack_get_item_iter (GIMP_ITEM_STACK (private->children));
list;
list = g_list_next (list))
{
GimpItem *child = list->data;
if (gimp_item_is_position_locked (child))
return TRUE;
}
return GIMP_ITEM_CLASS (parent_class)->is_position_locked (item);
}
static GimpItem *
gimp_group_layer_duplicate (GimpItem *item,
GType new_type)
{
GimpItem *new_item;
g_return_val_if_fail (g_type_is_a (new_type, GIMP_TYPE_DRAWABLE), NULL);
new_item = GIMP_ITEM_CLASS (parent_class)->duplicate (item, new_type);
if (GIMP_IS_GROUP_LAYER (new_item))
{
GimpGroupLayerPrivate *private = GET_PRIVATE (item);
GimpGroupLayer *new_group = GIMP_GROUP_LAYER (new_item);
GimpGroupLayerPrivate *new_private = GET_PRIVATE (new_item);
gint position = 0;
GList *list;
gimp_group_layer_suspend_resize (new_group, FALSE);
for (list = gimp_item_stack_get_item_iter (GIMP_ITEM_STACK (private->children));
list;
list = g_list_next (list))
{
GimpItem *child = list->data;
GimpItem *new_child;
GimpLayerMask *mask;
new_child = gimp_item_duplicate (child, G_TYPE_FROM_INSTANCE (child));
gimp_object_set_name (GIMP_OBJECT (new_child),
gimp_object_get_name (child));
mask = gimp_layer_get_mask (GIMP_LAYER (child));
if (mask)
{
GimpLayerMask *new_mask;
new_mask = gimp_layer_get_mask (GIMP_LAYER (new_child));
gimp_object_set_name (GIMP_OBJECT (new_mask),
gimp_object_get_name (mask));
}
gimp_viewable_set_parent (GIMP_VIEWABLE (new_child),
GIMP_VIEWABLE (new_group));
gimp_container_insert (new_private->children,
GIMP_OBJECT (new_child),
position++);
}
/* force the projection to reallocate itself */
GET_PRIVATE (new_group)->reallocate_projection = TRUE;
gimp_group_layer_resume_resize (new_group, FALSE);
}
return new_item;
}
static void
gimp_group_layer_convert (GimpItem *item,
GimpImage *dest_image,
GType old_type)
{
GimpGroupLayerPrivate *private = GET_PRIVATE (item);
GList *list;
for (list = gimp_item_stack_get_item_iter (GIMP_ITEM_STACK (private->children));
list;
list = g_list_next (list))
{
GimpItem *child = list->data;
GIMP_ITEM_GET_CLASS (child)->convert (child, dest_image,
G_TYPE_FROM_INSTANCE (child));
}
GIMP_ITEM_CLASS (parent_class)->convert (item, dest_image, old_type);
}
static void
Bug 795410 - Deleting a layer group and then undoing the deletion ... ... raises a CRITICAL gimp_item_{start,end}_move() currently serves two different purposes: It is used by GimpLayer to suspend/resume mask resizing of the layer's ancestors; this is necessary whenever an operation on a layer might affect the size of its ancestors. It is also used by GimpGroupLayer to suspend/resume its own mask resizing; this, on the other hand, is only necessary before applying one of the transformation functions to the group, so that mask modification is handled by GimpLayer. In other words, the effects of gimp_item_{start,end}_move() on group layers are only necessary in a subset of the cases in which these functions are used. While in itself this isn't a problem, it does cause issues when removing a group layer: gimp_image_remove_layer() calls gimp_item_start_move() before removing the layer, and gimp_item_end_move() afterwards. While the former function is called while the layer is still attached to the image, the latter function is called after the layer is no longer attached. Since GimpGroupLayer pushes an undo step in response to these calls, only the call to start_move() results in an undo step, while the call to end_move() doesn't, resulting in an unbalanced GIMP_UNDO_GROUP_LAYER_START_MOVE undo step on the stack. This causes problems when undoing the operation. Add gimp_item_{start,end}_transform() functions, and corresponding GimpItem::{start,end}_transform() virtual functions, which are more specialized versions of gimp_item_{start,end}_move(), which should be used instead of the former before/after transforming an item; in other cases, such as when removing ot reordering an item, gimp_item_{start,end}_move() should still be used. The default implementation of GimpItem::{start,end}_transform() calls gimp_item_{start,end}_move(), respectively, so subclasses that override these functions don't have to do that themselves. In GimpGroupLayer, override GimpItem::{start,end}_transform(), instead of GimpItem::{start,end}_move(), for the same purpose of suspending mask resize. This avoids these functions from being called when removing a layer group, fixing the bug.
2018-04-22 15:39:40 +08:00
gimp_group_layer_start_transform (GimpItem *item,
gboolean push_undo)
{
Bug 795410 - Deleting a layer group and then undoing the deletion ... ... raises a CRITICAL gimp_item_{start,end}_move() currently serves two different purposes: It is used by GimpLayer to suspend/resume mask resizing of the layer's ancestors; this is necessary whenever an operation on a layer might affect the size of its ancestors. It is also used by GimpGroupLayer to suspend/resume its own mask resizing; this, on the other hand, is only necessary before applying one of the transformation functions to the group, so that mask modification is handled by GimpLayer. In other words, the effects of gimp_item_{start,end}_move() on group layers are only necessary in a subset of the cases in which these functions are used. While in itself this isn't a problem, it does cause issues when removing a group layer: gimp_image_remove_layer() calls gimp_item_start_move() before removing the layer, and gimp_item_end_move() afterwards. While the former function is called while the layer is still attached to the image, the latter function is called after the layer is no longer attached. Since GimpGroupLayer pushes an undo step in response to these calls, only the call to start_move() results in an undo step, while the call to end_move() doesn't, resulting in an unbalanced GIMP_UNDO_GROUP_LAYER_START_MOVE undo step on the stack. This causes problems when undoing the operation. Add gimp_item_{start,end}_transform() functions, and corresponding GimpItem::{start,end}_transform() virtual functions, which are more specialized versions of gimp_item_{start,end}_move(), which should be used instead of the former before/after transforming an item; in other cases, such as when removing ot reordering an item, gimp_item_{start,end}_move() should still be used. The default implementation of GimpItem::{start,end}_transform() calls gimp_item_{start,end}_move(), respectively, so subclasses that override these functions don't have to do that themselves. In GimpGroupLayer, override GimpItem::{start,end}_transform(), instead of GimpItem::{start,end}_move(), for the same purpose of suspending mask resize. This avoids these functions from being called when removing a layer group, fixing the bug.
2018-04-22 15:39:40 +08:00
_gimp_group_layer_start_transform (GIMP_GROUP_LAYER (item), push_undo);
Bug 795410 - Deleting a layer group and then undoing the deletion ... ... raises a CRITICAL gimp_item_{start,end}_move() currently serves two different purposes: It is used by GimpLayer to suspend/resume mask resizing of the layer's ancestors; this is necessary whenever an operation on a layer might affect the size of its ancestors. It is also used by GimpGroupLayer to suspend/resume its own mask resizing; this, on the other hand, is only necessary before applying one of the transformation functions to the group, so that mask modification is handled by GimpLayer. In other words, the effects of gimp_item_{start,end}_move() on group layers are only necessary in a subset of the cases in which these functions are used. While in itself this isn't a problem, it does cause issues when removing a group layer: gimp_image_remove_layer() calls gimp_item_start_move() before removing the layer, and gimp_item_end_move() afterwards. While the former function is called while the layer is still attached to the image, the latter function is called after the layer is no longer attached. Since GimpGroupLayer pushes an undo step in response to these calls, only the call to start_move() results in an undo step, while the call to end_move() doesn't, resulting in an unbalanced GIMP_UNDO_GROUP_LAYER_START_MOVE undo step on the stack. This causes problems when undoing the operation. Add gimp_item_{start,end}_transform() functions, and corresponding GimpItem::{start,end}_transform() virtual functions, which are more specialized versions of gimp_item_{start,end}_move(), which should be used instead of the former before/after transforming an item; in other cases, such as when removing ot reordering an item, gimp_item_{start,end}_move() should still be used. The default implementation of GimpItem::{start,end}_transform() calls gimp_item_{start,end}_move(), respectively, so subclasses that override these functions don't have to do that themselves. In GimpGroupLayer, override GimpItem::{start,end}_transform(), instead of GimpItem::{start,end}_move(), for the same purpose of suspending mask resize. This avoids these functions from being called when removing a layer group, fixing the bug.
2018-04-22 15:39:40 +08:00
if (GIMP_ITEM_CLASS (parent_class)->start_transform)
GIMP_ITEM_CLASS (parent_class)->start_transform (item, push_undo);
}
static void
Bug 795410 - Deleting a layer group and then undoing the deletion ... ... raises a CRITICAL gimp_item_{start,end}_move() currently serves two different purposes: It is used by GimpLayer to suspend/resume mask resizing of the layer's ancestors; this is necessary whenever an operation on a layer might affect the size of its ancestors. It is also used by GimpGroupLayer to suspend/resume its own mask resizing; this, on the other hand, is only necessary before applying one of the transformation functions to the group, so that mask modification is handled by GimpLayer. In other words, the effects of gimp_item_{start,end}_move() on group layers are only necessary in a subset of the cases in which these functions are used. While in itself this isn't a problem, it does cause issues when removing a group layer: gimp_image_remove_layer() calls gimp_item_start_move() before removing the layer, and gimp_item_end_move() afterwards. While the former function is called while the layer is still attached to the image, the latter function is called after the layer is no longer attached. Since GimpGroupLayer pushes an undo step in response to these calls, only the call to start_move() results in an undo step, while the call to end_move() doesn't, resulting in an unbalanced GIMP_UNDO_GROUP_LAYER_START_MOVE undo step on the stack. This causes problems when undoing the operation. Add gimp_item_{start,end}_transform() functions, and corresponding GimpItem::{start,end}_transform() virtual functions, which are more specialized versions of gimp_item_{start,end}_move(), which should be used instead of the former before/after transforming an item; in other cases, such as when removing ot reordering an item, gimp_item_{start,end}_move() should still be used. The default implementation of GimpItem::{start,end}_transform() calls gimp_item_{start,end}_move(), respectively, so subclasses that override these functions don't have to do that themselves. In GimpGroupLayer, override GimpItem::{start,end}_transform(), instead of GimpItem::{start,end}_move(), for the same purpose of suspending mask resize. This avoids these functions from being called when removing a layer group, fixing the bug.
2018-04-22 15:39:40 +08:00
gimp_group_layer_end_transform (GimpItem *item,
gboolean push_undo)
{
Bug 795410 - Deleting a layer group and then undoing the deletion ... ... raises a CRITICAL gimp_item_{start,end}_move() currently serves two different purposes: It is used by GimpLayer to suspend/resume mask resizing of the layer's ancestors; this is necessary whenever an operation on a layer might affect the size of its ancestors. It is also used by GimpGroupLayer to suspend/resume its own mask resizing; this, on the other hand, is only necessary before applying one of the transformation functions to the group, so that mask modification is handled by GimpLayer. In other words, the effects of gimp_item_{start,end}_move() on group layers are only necessary in a subset of the cases in which these functions are used. While in itself this isn't a problem, it does cause issues when removing a group layer: gimp_image_remove_layer() calls gimp_item_start_move() before removing the layer, and gimp_item_end_move() afterwards. While the former function is called while the layer is still attached to the image, the latter function is called after the layer is no longer attached. Since GimpGroupLayer pushes an undo step in response to these calls, only the call to start_move() results in an undo step, while the call to end_move() doesn't, resulting in an unbalanced GIMP_UNDO_GROUP_LAYER_START_MOVE undo step on the stack. This causes problems when undoing the operation. Add gimp_item_{start,end}_transform() functions, and corresponding GimpItem::{start,end}_transform() virtual functions, which are more specialized versions of gimp_item_{start,end}_move(), which should be used instead of the former before/after transforming an item; in other cases, such as when removing ot reordering an item, gimp_item_{start,end}_move() should still be used. The default implementation of GimpItem::{start,end}_transform() calls gimp_item_{start,end}_move(), respectively, so subclasses that override these functions don't have to do that themselves. In GimpGroupLayer, override GimpItem::{start,end}_transform(), instead of GimpItem::{start,end}_move(), for the same purpose of suspending mask resize. This avoids these functions from being called when removing a layer group, fixing the bug.
2018-04-22 15:39:40 +08:00
if (GIMP_ITEM_CLASS (parent_class)->end_transform)
GIMP_ITEM_CLASS (parent_class)->end_transform (item, push_undo);
Bug 795410 - Deleting a layer group and then undoing the deletion ... ... raises a CRITICAL gimp_item_{start,end}_move() currently serves two different purposes: It is used by GimpLayer to suspend/resume mask resizing of the layer's ancestors; this is necessary whenever an operation on a layer might affect the size of its ancestors. It is also used by GimpGroupLayer to suspend/resume its own mask resizing; this, on the other hand, is only necessary before applying one of the transformation functions to the group, so that mask modification is handled by GimpLayer. In other words, the effects of gimp_item_{start,end}_move() on group layers are only necessary in a subset of the cases in which these functions are used. While in itself this isn't a problem, it does cause issues when removing a group layer: gimp_image_remove_layer() calls gimp_item_start_move() before removing the layer, and gimp_item_end_move() afterwards. While the former function is called while the layer is still attached to the image, the latter function is called after the layer is no longer attached. Since GimpGroupLayer pushes an undo step in response to these calls, only the call to start_move() results in an undo step, while the call to end_move() doesn't, resulting in an unbalanced GIMP_UNDO_GROUP_LAYER_START_MOVE undo step on the stack. This causes problems when undoing the operation. Add gimp_item_{start,end}_transform() functions, and corresponding GimpItem::{start,end}_transform() virtual functions, which are more specialized versions of gimp_item_{start,end}_move(), which should be used instead of the former before/after transforming an item; in other cases, such as when removing ot reordering an item, gimp_item_{start,end}_move() should still be used. The default implementation of GimpItem::{start,end}_transform() calls gimp_item_{start,end}_move(), respectively, so subclasses that override these functions don't have to do that themselves. In GimpGroupLayer, override GimpItem::{start,end}_transform(), instead of GimpItem::{start,end}_move(), for the same purpose of suspending mask resize. This avoids these functions from being called when removing a layer group, fixing the bug.
2018-04-22 15:39:40 +08:00
_gimp_group_layer_end_transform (GIMP_GROUP_LAYER (item), push_undo);
}
static void
gimp_group_layer_resize (GimpItem *item,
GimpContext *context,
GimpFillType fill_type,
gint new_width,
gint new_height,
gint offset_x,
gint offset_y)
{
GimpGroupLayer *group = GIMP_GROUP_LAYER (item);
GimpGroupLayerPrivate *private = GET_PRIVATE (item);
GList *list;
gint x, y;
/* we implement GimpItem::resize(), instead of GimpLayer::resize(), so that
Bug 795410 - Deleting a layer group and then undoing the deletion ... ... raises a CRITICAL gimp_item_{start,end}_move() currently serves two different purposes: It is used by GimpLayer to suspend/resume mask resizing of the layer's ancestors; this is necessary whenever an operation on a layer might affect the size of its ancestors. It is also used by GimpGroupLayer to suspend/resume its own mask resizing; this, on the other hand, is only necessary before applying one of the transformation functions to the group, so that mask modification is handled by GimpLayer. In other words, the effects of gimp_item_{start,end}_move() on group layers are only necessary in a subset of the cases in which these functions are used. While in itself this isn't a problem, it does cause issues when removing a group layer: gimp_image_remove_layer() calls gimp_item_start_move() before removing the layer, and gimp_item_end_move() afterwards. While the former function is called while the layer is still attached to the image, the latter function is called after the layer is no longer attached. Since GimpGroupLayer pushes an undo step in response to these calls, only the call to start_move() results in an undo step, while the call to end_move() doesn't, resulting in an unbalanced GIMP_UNDO_GROUP_LAYER_START_MOVE undo step on the stack. This causes problems when undoing the operation. Add gimp_item_{start,end}_transform() functions, and corresponding GimpItem::{start,end}_transform() virtual functions, which are more specialized versions of gimp_item_{start,end}_move(), which should be used instead of the former before/after transforming an item; in other cases, such as when removing ot reordering an item, gimp_item_{start,end}_move() should still be used. The default implementation of GimpItem::{start,end}_transform() calls gimp_item_{start,end}_move(), respectively, so subclasses that override these functions don't have to do that themselves. In GimpGroupLayer, override GimpItem::{start,end}_transform(), instead of GimpItem::{start,end}_move(), for the same purpose of suspending mask resize. This avoids these functions from being called when removing a layer group, fixing the bug.
2018-04-22 15:39:40 +08:00
* GimpLayer doesn't resize the mask. note that gimp_item_resize() calls
* gimp_item_{start,end}_move(), and not gimp_item_{start,end}_transform(),
* so that mask resizing is handled by gimp_group_layer_update_size().
*/
x = gimp_item_get_offset_x (item) - offset_x;
y = gimp_item_get_offset_y (item) - offset_y;
gimp_group_layer_suspend_resize (group, TRUE);
list = gimp_item_stack_get_item_iter (GIMP_ITEM_STACK (private->children));
while (list)
{
GimpItem *child = list->data;
gint child_width;
gint child_height;
gint child_x;
gint child_y;
list = g_list_next (list);
if (gimp_rectangle_intersect (x,
y,
new_width,
new_height,
gimp_item_get_offset_x (child),
gimp_item_get_offset_y (child),
gimp_item_get_width (child),
gimp_item_get_height (child),
&child_x,
&child_y,
&child_width,
&child_height))
{
gint child_offset_x = gimp_item_get_offset_x (child) - child_x;
gint child_offset_y = gimp_item_get_offset_y (child) - child_y;
gimp_item_resize (child, context, fill_type,
child_width, child_height,
child_offset_x, child_offset_y);
}
else if (gimp_item_is_attached (item))
{
gimp_image_remove_layer (gimp_item_get_image (item),
GIMP_LAYER (child),
TRUE, NULL);
}
else
{
gimp_container_remove (private->children, GIMP_OBJECT (child));
}
}
gimp_group_layer_resume_resize (group, TRUE);
}
static gint64
gimp_group_layer_estimate_memsize (GimpDrawable *drawable,
GimpComponentType component_type,
gint width,
gint height)
{
GimpGroupLayerPrivate *private = GET_PRIVATE (drawable);
GList *list;
GimpImageBaseType base_type;
gint64 memsize = 0;
for (list = gimp_item_stack_get_item_iter (GIMP_ITEM_STACK (private->children));
list;
list = g_list_next (list))
{
GimpDrawable *child = list->data;
gint child_width;
gint child_height;
child_width = (gimp_item_get_width (GIMP_ITEM (child)) *
width /
gimp_item_get_width (GIMP_ITEM (drawable)));
child_height = (gimp_item_get_height (GIMP_ITEM (child)) *
height /
gimp_item_get_height (GIMP_ITEM (drawable)));
memsize += gimp_drawable_estimate_memsize (child,
component_type,
child_width,
child_height);
}
base_type = gimp_drawable_get_base_type (drawable);
memsize += gimp_projection_estimate_memsize (base_type, component_type,
width, height);
return memsize +
GIMP_DRAWABLE_CLASS (parent_class)->estimate_memsize (drawable,
component_type,
width, height);
}
static void
gimp_group_layer_translate (GimpLayer *layer,
gint offset_x,
gint offset_y)
{
GimpGroupLayer *group = GIMP_GROUP_LAYER (layer);
GimpGroupLayerPrivate *private = GET_PRIVATE (layer);
gint x, y;
GList *list;
/* don't use gimp_group_layer_suspend_resize(), but rather increment
* private->suspend_resize directly, since we're translating the group layer
* here, rather than relying on gimp_group_layer_update_size() to do it.
*/
private->suspend_resize++;
/* redirect stack updates to the drawable, rather than to the projection */
private->direct_update++;
gimp_item_get_offset (GIMP_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 */
gimp_drawable_invalidate_boundary (GIMP_DRAWABLE (layer));
/* update the group layer offset */
gimp_item_set_offset (GIMP_ITEM (group), x, y);
for (list = gimp_item_stack_get_item_iter (GIMP_ITEM_STACK (private->children));
list;
list = g_list_next (list))
{
GimpItem *child = list->data;
/* don't push an undo here because undo will call us again */
gimp_item_translate (child, offset_x, offset_y, FALSE);
}
/* redirect stack updates back to the projection */
private->direct_update--;
/* don't use gimp_group_layer_resume_resize(), but rather decrement
* private->suspend_resize directly, so that gimp_group_layer_update_size()
* isn't called.
*/
private->suspend_resize--;
}
static void
gimp_group_layer_scale (GimpLayer *layer,
gint new_width,
gint new_height,
gint new_offset_x,
gint new_offset_y,
GimpInterpolationType interpolation_type,
GimpProgress *progress)
{
GimpGroupLayer *group = GIMP_GROUP_LAYER (layer);
GimpGroupLayerPrivate *private = GET_PRIVATE (layer);
GimpItem *item = GIMP_ITEM (layer);
GimpObjectQueue *queue = NULL;
GList *list;
gdouble width_factor;
gdouble height_factor;
gint old_offset_x;
gint old_offset_y;
width_factor = (gdouble) new_width / (gdouble) gimp_item_get_width (item);
height_factor = (gdouble) new_height / (gdouble) gimp_item_get_height (item);
old_offset_x = gimp_item_get_offset_x (item);
old_offset_y = gimp_item_get_offset_y (item);
if (progress)
{
queue = gimp_object_queue_new (progress);
progress = GIMP_PROGRESS (queue);
gimp_object_queue_push_container (queue, private->children);
}
gimp_group_layer_suspend_resize (group, TRUE);
list = gimp_item_stack_get_item_iter (GIMP_ITEM_STACK (private->children));
while (list)
{
GimpItem *child = list->data;
list = g_list_next (list);
if (queue)
gimp_object_queue_pop (queue);
if (! gimp_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 (gimp_item_is_attached (item))
{
gimp_image_remove_layer (gimp_item_get_image (item),
GIMP_LAYER (child),
TRUE, NULL);
}
else
{
gimp_container_remove (private->children, GIMP_OBJECT (child));
}
}
}
gimp_group_layer_resume_resize (group, TRUE);
g_clear_object (&queue);
}
static void
gimp_group_layer_flip (GimpLayer *layer,
GimpContext *context,
GimpOrientationType flip_type,
gdouble axis,
gboolean clip_result)
{
GimpGroupLayer *group = GIMP_GROUP_LAYER (layer);
GimpGroupLayerPrivate *private = GET_PRIVATE (layer);
GList *list;
gimp_group_layer_suspend_resize (group, TRUE);
for (list = gimp_item_stack_get_item_iter (GIMP_ITEM_STACK (private->children));
list;
list = g_list_next (list))
{
GimpItem *child = list->data;
gimp_item_flip (child, context,
flip_type, axis, clip_result);
}
gimp_group_layer_resume_resize (group, TRUE);
}
static void
gimp_group_layer_rotate (GimpLayer *layer,
GimpContext *context,
GimpRotationType rotate_type,
gdouble center_x,
gdouble center_y,
gboolean clip_result)
{
GimpGroupLayer *group = GIMP_GROUP_LAYER (layer);
GimpGroupLayerPrivate *private = GET_PRIVATE (layer);
GList *list;
gimp_group_layer_suspend_resize (group, TRUE);
for (list = gimp_item_stack_get_item_iter (GIMP_ITEM_STACK (private->children));
list;
list = g_list_next (list))
{
GimpItem *child = list->data;
gimp_item_rotate (child, context,
rotate_type, center_x, center_y, clip_result);
}
gimp_group_layer_resume_resize (group, TRUE);
}
static void
gimp_group_layer_transform (GimpLayer *layer,
GimpContext *context,
const GimpMatrix3 *matrix,
GimpTransformDirection direction,
GimpInterpolationType interpolation_type,
GimpTransformResize clip_result,
GimpProgress *progress)
{
GimpGroupLayer *group = GIMP_GROUP_LAYER (layer);
GimpGroupLayerPrivate *private = GET_PRIVATE (layer);
GimpObjectQueue *queue = NULL;
GList *list;
if (progress)
{
queue = gimp_object_queue_new (progress);
progress = GIMP_PROGRESS (queue);
gimp_object_queue_push_container (queue, private->children);
}
gimp_group_layer_suspend_resize (group, TRUE);
for (list = gimp_item_stack_get_item_iter (GIMP_ITEM_STACK (private->children));
list;
list = g_list_next (list))
{
GimpItem *child = list->data;
if (queue)
gimp_object_queue_pop (queue);
gimp_item_transform (child, context,
matrix, direction,
interpolation_type,
clip_result, progress);
}
gimp_group_layer_resume_resize (group, TRUE);
g_clear_object (&queue);
}
static const Babl *
get_projection_format (GimpProjectable *projectable,
GimpImageBaseType base_type,
GimpPrecision precision)
{
GimpImage *image = gimp_item_get_image (GIMP_ITEM (projectable));
switch (base_type)
{
case GIMP_RGB:
case GIMP_INDEXED:
Initial space invasion commit in GIMP All babl formats now have a space equivalent to a color profile, determining the format's primaries and TRCs. This commit makes GIMP aware of this. libgimp: - enum GimpPrecision: rename GAMMA values to NON_LINEAR and keep GAMMA as deprecated aliases, add PERCEPTUAL values so we now have LINEAR, NON_LINEAR and PERCPTUAL for each encoding, matching the babl encoding variants RGB, R'G'B' and R~G~B~. - gimp_color_transform_can_gegl_copy() now returns TRUE if both profiles can return a babl space, increasing the amount of fast babl color conversions significantly. - TODO: no solution yet for getting libgimp drawable proxy buffers in the right format with space. plug-ins: - follow the GimpPrecision change. - TODO: everything else unchanged and partly broken or sub-optimal, like setting a new image's color profile too late. app: - add enum GimpTRCType { LINEAR, NON_LINEAR, PERCEPTUAL } as replacement for all "linear" booleans. - change gimp-babl functions to take babl spaces and GimpTRCType parameters and support all sorts of new perceptual ~ formats. - a lot of places changed in the early days of goat invasion didn't take advantage of gimp-babl utility functions and constructed formats manually. They all needed revisiting and many now use much simpler code calling gimp-babl API. - change gimp_babl_format_get_color_profile() to really extract a newly allocated color profile from the format, and add gimp_babl_get_builtin_color_profile() which does the same as gimp_babl_format_get_color_profile() did before. Visited all callers to decide whether they are looking for the format's actual profile, or for one of the builtin profiles, simplifying code that only needs builtin profiles. - drawables have a new get_space_api(), get_linear() is now get_trc(). - images now have a "layer space" and an API to get it, gimp_image_get_layer_format() returns formats in that space. - an image's layer space is created from the image's color profile, change gimpimage-color-profile to deal with that correctly - change many babl_format() calls to babl_format_with_space() and take the space from passed formats or drawables - add function gimp_layer_fix_format_space() which replaces the layer's buffer with one that has the image's layer format, but doesn't change pixel values - use gimp_layer_fix_format_space() to make sure layers loaded from XCF and created by plug-ins have the right space when added to the image, because it's impossible to always assign the right space upon layer creation - "assign color profile" and "discard color profile" now require use of gimp_layer_fix_format_space() too because the profile is now embedded in all formats via the space. Add gimp_image_assign_color_profile() which does all that and call it instead of a simple gimp_image_set_color_profile(), also from the PDB set-color-profile functions, which are essentially "assign" and "discard" calls. - generally, make sure a new image's color profile is set before adding layers to it, gimp_image_set_color_profile() is more than before considered know-what-you-are-doing API. - take special precaution in all places that call gimp_drawable_convert_type(), we now must pass a new_profile from all callers that convert layers within the same image (such as image_convert_type, image_convert_precision), because the layer's new space can't be determined from the image's layer format during the call. - change all "linear" properties to "trc", in all config objects like for levels and curves, in the histogram, in the widgets. This results in some GUI that now has three choices instead of two. TODO: we might want to reduce that back to two later. - keep "linear" boolean properties around as compat if needed for file pasring, but always convert the parsed parsed boolean to GimpTRCType. - TODO: the image's "enable color management" switch is currently broken, will fix that in another commit.
2018-07-21 20:23:01 +08:00
return gimp_image_get_format (image, GIMP_RGB, precision, TRUE,
gimp_image_get_layer_space (image));
case GIMP_GRAY:
Initial space invasion commit in GIMP All babl formats now have a space equivalent to a color profile, determining the format's primaries and TRCs. This commit makes GIMP aware of this. libgimp: - enum GimpPrecision: rename GAMMA values to NON_LINEAR and keep GAMMA as deprecated aliases, add PERCEPTUAL values so we now have LINEAR, NON_LINEAR and PERCPTUAL for each encoding, matching the babl encoding variants RGB, R'G'B' and R~G~B~. - gimp_color_transform_can_gegl_copy() now returns TRUE if both profiles can return a babl space, increasing the amount of fast babl color conversions significantly. - TODO: no solution yet for getting libgimp drawable proxy buffers in the right format with space. plug-ins: - follow the GimpPrecision change. - TODO: everything else unchanged and partly broken or sub-optimal, like setting a new image's color profile too late. app: - add enum GimpTRCType { LINEAR, NON_LINEAR, PERCEPTUAL } as replacement for all "linear" booleans. - change gimp-babl functions to take babl spaces and GimpTRCType parameters and support all sorts of new perceptual ~ formats. - a lot of places changed in the early days of goat invasion didn't take advantage of gimp-babl utility functions and constructed formats manually. They all needed revisiting and many now use much simpler code calling gimp-babl API. - change gimp_babl_format_get_color_profile() to really extract a newly allocated color profile from the format, and add gimp_babl_get_builtin_color_profile() which does the same as gimp_babl_format_get_color_profile() did before. Visited all callers to decide whether they are looking for the format's actual profile, or for one of the builtin profiles, simplifying code that only needs builtin profiles. - drawables have a new get_space_api(), get_linear() is now get_trc(). - images now have a "layer space" and an API to get it, gimp_image_get_layer_format() returns formats in that space. - an image's layer space is created from the image's color profile, change gimpimage-color-profile to deal with that correctly - change many babl_format() calls to babl_format_with_space() and take the space from passed formats or drawables - add function gimp_layer_fix_format_space() which replaces the layer's buffer with one that has the image's layer format, but doesn't change pixel values - use gimp_layer_fix_format_space() to make sure layers loaded from XCF and created by plug-ins have the right space when added to the image, because it's impossible to always assign the right space upon layer creation - "assign color profile" and "discard color profile" now require use of gimp_layer_fix_format_space() too because the profile is now embedded in all formats via the space. Add gimp_image_assign_color_profile() which does all that and call it instead of a simple gimp_image_set_color_profile(), also from the PDB set-color-profile functions, which are essentially "assign" and "discard" calls. - generally, make sure a new image's color profile is set before adding layers to it, gimp_image_set_color_profile() is more than before considered know-what-you-are-doing API. - take special precaution in all places that call gimp_drawable_convert_type(), we now must pass a new_profile from all callers that convert layers within the same image (such as image_convert_type, image_convert_precision), because the layer's new space can't be determined from the image's layer format during the call. - change all "linear" properties to "trc", in all config objects like for levels and curves, in the histogram, in the widgets. This results in some GUI that now has three choices instead of two. TODO: we might want to reduce that back to two later. - keep "linear" boolean properties around as compat if needed for file pasring, but always convert the parsed parsed boolean to GimpTRCType. - TODO: the image's "enable color management" switch is currently broken, will fix that in another commit.
2018-07-21 20:23:01 +08:00
return gimp_image_get_format (image, GIMP_GRAY, precision, TRUE,
gimp_image_get_layer_space (image));
}
g_return_val_if_reached (NULL);
}
static void
gimp_group_layer_convert_type (GimpLayer *layer,
GimpImage *dest_image,
const Babl *new_format,
GimpColorProfile *src_profile,
GimpColorProfile *dest_profile,
GeglDitherMethod layer_dither_type,
GeglDitherMethod mask_dither_type,
gboolean push_undo,
GimpProgress *progress)
{
GimpGroupLayer *group = GIMP_GROUP_LAYER (layer);
GimpGroupLayerPrivate *private = GET_PRIVATE (layer);
GeglBuffer *buffer;
if (push_undo)
{
GimpImage *image = gimp_item_get_image (GIMP_ITEM (group));
gimp_image_undo_push_group_layer_convert (image, NULL, group);
}
/* Force allocation of a same-size projection, in case the group
* layer is empty
*/
private->reallocate_width = gimp_item_get_width (GIMP_ITEM (group));
private->reallocate_height = gimp_item_get_height (GIMP_ITEM (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 (GIMP_PROJECTABLE (group),
gimp_babl_format_get_base_type (new_format),
gimp_babl_format_get_precision (new_format));
gimp_projectable_structure_changed (GIMP_PROJECTABLE (group));
gimp_group_layer_flush (group);
buffer = gimp_pickable_get_buffer (GIMP_PICKABLE (private->projection));
gimp_drawable_set_buffer_full (GIMP_DRAWABLE (group),
FALSE, NULL,
buffer,
gimp_item_get_offset_x (GIMP_ITEM (group)),
gimp_item_get_offset_y (GIMP_ITEM (group)),
TRUE);
/* reset, the actual format is right now */
private->convert_format = NULL;
private->reallocate_width = 0;
private->reallocate_height = 0;
}
static GeglNode *
gimp_group_layer_get_source_node (GimpDrawable *drawable)
{
GimpGroupLayerPrivate *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 =
GIMP_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_connect_to (input, "output",
private->parent_source_node, "input");
}
/* make sure we have a graph */
(void) gimp_group_layer_get_graph (GIMP_PROJECTABLE (drawable));
gegl_node_add_child (private->source_node, private->graph);
gimp_group_layer_update_source_node (GIMP_GROUP_LAYER (drawable));
return g_object_ref (private->source_node);
}
static void
gimp_group_layer_opacity_changed (GimpLayer *layer)
{
gimp_layer_update_effective_mode (layer);
if (GIMP_LAYER_CLASS (parent_class)->opacity_changed)
GIMP_LAYER_CLASS (parent_class)->opacity_changed (layer);
}
static void
gimp_group_layer_effective_mode_changed (GimpLayer *layer)
{
GimpGroupLayer *group = GIMP_GROUP_LAYER (layer);
GimpGroupLayerPrivate *private = GET_PRIVATE (layer);
GimpLayerMode mode;
gboolean pass_through;
gimp_layer_get_effective_mode (layer, &mode, NULL, NULL, NULL);
pass_through = (mode == GIMP_LAYER_MODE_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.
*/
gimp_pickable_flush (GIMP_PICKABLE (private->projection));
}
private->pass_through = pass_through;
gimp_group_layer_update_source_node (group);
gimp_group_layer_update_mode_node (group);
if (GIMP_LAYER_CLASS (parent_class)->effective_mode_changed)
GIMP_LAYER_CLASS (parent_class)->effective_mode_changed (layer);
}
static void
gimp_group_layer_excludes_backdrop_changed (GimpLayer *layer)
{
GimpGroupLayer *group = GIMP_GROUP_LAYER (layer);
gimp_group_layer_update_source_node (group);
gimp_group_layer_update_mode_node (group);
if (GIMP_LAYER_CLASS (parent_class)->excludes_backdrop_changed)
GIMP_LAYER_CLASS (parent_class)->excludes_backdrop_changed (layer);
}
static void
gimp_group_layer_mask_changed (GimpLayer *layer)
{
g_warn_if_fail (GET_PRIVATE (layer)->suspend_mask == 0);
gimp_layer_update_effective_mode (layer);
if (GIMP_LAYER_CLASS (parent_class)->mask_changed)
GIMP_LAYER_CLASS (parent_class)->mask_changed (layer);
}
static void
gimp_group_layer_get_effective_mode (GimpLayer *layer,
GimpLayerMode *mode,
GimpLayerColorSpace *blend_space,
GimpLayerColorSpace *composite_space,
GimpLayerCompositeMode *composite_mode)
{
GimpGroupLayerPrivate *private = GET_PRIVATE (layer);
/* try to strength-reduce pass-through groups to normal groups, which are
* cheaper.
*/
if (gimp_layer_get_mode (layer) == GIMP_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 group's composite space equals the active children's
* composite space.
*/
GList *list;
gboolean reduce = TRUE;
gboolean first = TRUE;
*mode = GIMP_LAYER_MODE_NORMAL;
*blend_space = gimp_layer_get_real_blend_space (layer);
*composite_space = gimp_layer_get_real_composite_space (layer);
*composite_mode = gimp_layer_get_real_composite_mode (layer);
for (list = gimp_item_stack_get_item_iter (GIMP_ITEM_STACK (private->children));
list;
list = g_list_next (list))
{
GimpLayer *child = list->data;
if (! gimp_filter_get_active (GIMP_FILTER (child)))
continue;
if (first)
{
gimp_layer_get_effective_mode (child,
mode,
blend_space,
composite_space,
composite_mode);
if (*mode == GIMP_LAYER_MODE_NORMAL_LEGACY)
*mode = GIMP_LAYER_MODE_NORMAL;
first = FALSE;
}
else
{
GimpLayerMode other_mode;
GimpLayerColorSpace other_blend_space;
GimpLayerColorSpace other_composite_space;
GimpLayerCompositeMode other_composite_mode;
if (*mode != GIMP_LAYER_MODE_NORMAL ||
*composite_mode != GIMP_LAYER_COMPOSITE_UNION)
{
reduce = FALSE;
break;
}
gimp_layer_get_effective_mode (child,
&other_mode,
&other_blend_space,
&other_composite_space,
&other_composite_mode);
if (other_mode == GIMP_LAYER_MODE_NORMAL_LEGACY)
other_mode = GIMP_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)
{
if (first ||
(gimp_layer_get_opacity (layer) == GIMP_OPACITY_OPAQUE &&
! gimp_layer_get_mask (layer)) ||
*composite_space == gimp_layer_get_real_composite_space (layer))
{
/* strength reduction succeeded! */
return;
}
}
}
/* strength-reduction failed. chain up. */
GIMP_LAYER_CLASS (parent_class)->get_effective_mode (layer,
mode,
blend_space,
composite_space,
composite_mode);
}
static gboolean
gimp_group_layer_get_excludes_backdrop (GimpLayer *layer)
{
GimpGroupLayerPrivate *private = GET_PRIVATE (layer);
if (private->pass_through)
{
GList *list;
for (list = gimp_item_stack_get_item_iter (GIMP_ITEM_STACK (private->children));
list;
list = g_list_next (list))
{
GimpFilter *child = list->data;
if (gimp_filter_get_active (child) &&
gimp_layer_get_excludes_backdrop (GIMP_LAYER (child)))
return TRUE;
}
return FALSE;
}
else
return GIMP_LAYER_CLASS (parent_class)->get_excludes_backdrop (layer);
}
static const Babl *
gimp_group_layer_get_format (GimpProjectable *projectable)
{
GimpGroupLayerPrivate *private = GET_PRIVATE (projectable);
GimpImageBaseType base_type;
GimpPrecision precision;
if (private->convert_format)
return private->convert_format;
base_type = gimp_drawable_get_base_type (GIMP_DRAWABLE (projectable));
precision = gimp_drawable_get_precision (GIMP_DRAWABLE (projectable));
return get_projection_format (projectable, base_type, precision);
}
static GeglNode *
gimp_group_layer_get_graph (GimpProjectable *projectable)
{
GimpGroupLayer *group = GIMP_GROUP_LAYER (projectable);
GimpGroupLayerPrivate *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 =
gimp_filter_stack_get_graph (GIMP_FILTER_STACK (private->children));
gegl_node_add_child (private->graph, layers_node);
gegl_node_connect_to (input, "output",
layers_node, "input");
gimp_item_get_offset (GIMP_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_connect_to (layers_node, "output",
private->offset_node, "input");
output = gegl_node_get_output_proxy (private->graph, "output");
gegl_node_connect_to (private->offset_node, "output",
output, "input");
return private->graph;
}
static void
gimp_group_layer_begin_render (GimpProjectable *projectable)
{
GimpGroupLayerPrivate *private = GET_PRIVATE (projectable);
if (private->source_node == NULL)
return;
if (private->pass_through)
gegl_node_disconnect (private->graph, "input");
}
static void
gimp_group_layer_end_render (GimpProjectable *projectable)
{
GimpGroupLayerPrivate *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_connect_to (input, "output",
private->graph, "input");
}
}
static gdouble
gimp_group_layer_get_opacity_at (GimpPickable *pickable,
gint x,
gint y)
{
/* Only consider child layers as having content */
return GIMP_OPACITY_TRANSPARENT;
}
/* public functions */
GimpLayer *
gimp_group_layer_new (GimpImage *image)
{
GimpGroupLayer *group;
const Babl *format;
g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
format = gimp_image_get_layer_format (image, TRUE);
group = GIMP_GROUP_LAYER (gimp_drawable_new (GIMP_TYPE_GROUP_LAYER,
image, NULL,
0, 0, 1, 1,
format));
gimp_layer_set_mode (GIMP_LAYER (group),
gimp_image_get_default_new_layer_mode (image),
FALSE);
return GIMP_LAYER (group);
}
GimpProjection *
gimp_group_layer_get_projection (GimpGroupLayer *group)
{
g_return_val_if_fail (GIMP_IS_GROUP_LAYER (group), NULL);
return GET_PRIVATE (group)->projection;
}
void
gimp_group_layer_suspend_resize (GimpGroupLayer *group,
gboolean push_undo)
{
GimpItem *item;
g_return_if_fail (GIMP_IS_GROUP_LAYER (group));
item = GIMP_ITEM (group);
if (! gimp_item_is_attached (item))
push_undo = FALSE;
if (push_undo)
gimp_image_undo_push_group_layer_suspend_resize (gimp_item_get_image (item),
NULL, group);
GET_PRIVATE (group)->suspend_resize++;
}
void
gimp_group_layer_resume_resize (GimpGroupLayer *group,
gboolean push_undo)
{
GimpGroupLayerPrivate *private;
GimpItem *item;
GimpItem *mask = NULL;
GeglBuffer *mask_buffer;
GeglRectangle mask_bounds;
GimpUndo *undo;
g_return_if_fail (GIMP_IS_GROUP_LAYER (group));
private = GET_PRIVATE (group);
g_return_if_fail (private->suspend_resize > 0);
item = GIMP_ITEM (group);
if (! gimp_item_is_attached (item))
push_undo = FALSE;
if (push_undo)
{
undo =
gimp_image_undo_push_group_layer_resume_resize (gimp_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 = GIMP_ITEM (gimp_layer_get_mask (GIMP_LAYER (group)));
if (mask)
{
mask_buffer =
g_object_ref (gimp_drawable_get_buffer (GIMP_DRAWABLE (mask)));
mask_bounds.x = gimp_item_get_offset_x (mask);
mask_bounds.y = gimp_item_get_offset_y (mask);
mask_bounds.width = gimp_item_get_width (mask);
mask_bounds.height = gimp_item_get_height (mask);
}
}
}
private->suspend_resize--;
if (private->suspend_resize == 0)
{
gimp_group_layer_update_size (group);
if (mask)
{
/* if the mask changed, make sure it's restored during undo, as per
* the comment above.
*/
if (gimp_drawable_get_buffer (GIMP_DRAWABLE (mask)) != mask_buffer)
{
g_return_if_fail (undo != NULL);
GIMP_GROUP_LAYER_UNDO (undo)->mask_buffer = mask_buffer;
GIMP_GROUP_LAYER_UNDO (undo)->mask_bounds = mask_bounds;
}
else
{
g_object_unref (mask_buffer);
}
}
}
}
void
gimp_group_layer_suspend_mask (GimpGroupLayer *group,
gboolean push_undo)
{
GimpGroupLayerPrivate *private;
GimpItem *item;
g_return_if_fail (GIMP_IS_GROUP_LAYER (group));
private = GET_PRIVATE (group);
item = GIMP_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 (! gimp_item_is_attached (item) || private->suspend_mask > 0)
push_undo = FALSE;
if (push_undo)
gimp_image_undo_push_group_layer_suspend_mask (gimp_item_get_image (item),
NULL, group);
if (private->suspend_mask == 0)
{
if (gimp_layer_get_mask (GIMP_LAYER (group)))
{
GimpItem *mask = GIMP_ITEM (gimp_layer_get_mask (GIMP_LAYER (group)));
private->suspended_mask_buffer =
g_object_ref (gimp_drawable_get_buffer (GIMP_DRAWABLE (mask)));
private->suspended_mask_bounds.x = gimp_item_get_offset_x (mask);
private->suspended_mask_bounds.y = gimp_item_get_offset_y (mask);
private->suspended_mask_bounds.width = gimp_item_get_width (mask);
private->suspended_mask_bounds.height = gimp_item_get_height (mask);
}
else
{
private->suspended_mask_buffer = NULL;
}
}
private->suspend_mask++;
}
void
gimp_group_layer_resume_mask (GimpGroupLayer *group,
gboolean push_undo)
{
GimpGroupLayerPrivate *private;
GimpItem *item;
g_return_if_fail (GIMP_IS_GROUP_LAYER (group));
private = GET_PRIVATE (group);
g_return_if_fail (private->suspend_mask > 0);
item = GIMP_ITEM (group);
/* avoid pushing an undo step if this is a nested resume_mask() call. see
* the comment in gimp_group_layer_suspend_mask().
*/
if (! gimp_item_is_attached (item) || private->suspend_mask > 1)
push_undo = FALSE;
if (push_undo)
gimp_image_undo_push_group_layer_resume_mask (gimp_item_get_image (item),
NULL, group);
private->suspend_mask--;
if (private->suspend_mask == 0)
g_clear_object (&private->suspended_mask_buffer);
}
/* protected functions */
void
_gimp_group_layer_set_suspended_mask (GimpGroupLayer *group,
GeglBuffer *buffer,
const GeglRectangle *bounds)
{
GimpGroupLayerPrivate *private;
g_return_if_fail (GIMP_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 *
_gimp_group_layer_get_suspended_mask (GimpGroupLayer *group,
GeglRectangle *bounds)
{
GimpGroupLayerPrivate *private;
GimpLayerMask *mask;
g_return_val_if_fail (GIMP_IS_GROUP_LAYER (group), NULL);
g_return_val_if_fail (bounds != NULL, NULL);
private = GET_PRIVATE (group);
mask = gimp_layer_get_mask (GIMP_LAYER (group));
g_return_val_if_fail (private->suspend_mask > 0, NULL);
if (mask &&
gimp_drawable_get_buffer (GIMP_DRAWABLE (mask)) !=
private->suspended_mask_buffer)
{
*bounds = private->suspended_mask_bounds;
return private->suspended_mask_buffer;
}
return NULL;
}
void
Bug 795410 - Deleting a layer group and then undoing the deletion ... ... raises a CRITICAL gimp_item_{start,end}_move() currently serves two different purposes: It is used by GimpLayer to suspend/resume mask resizing of the layer's ancestors; this is necessary whenever an operation on a layer might affect the size of its ancestors. It is also used by GimpGroupLayer to suspend/resume its own mask resizing; this, on the other hand, is only necessary before applying one of the transformation functions to the group, so that mask modification is handled by GimpLayer. In other words, the effects of gimp_item_{start,end}_move() on group layers are only necessary in a subset of the cases in which these functions are used. While in itself this isn't a problem, it does cause issues when removing a group layer: gimp_image_remove_layer() calls gimp_item_start_move() before removing the layer, and gimp_item_end_move() afterwards. While the former function is called while the layer is still attached to the image, the latter function is called after the layer is no longer attached. Since GimpGroupLayer pushes an undo step in response to these calls, only the call to start_move() results in an undo step, while the call to end_move() doesn't, resulting in an unbalanced GIMP_UNDO_GROUP_LAYER_START_MOVE undo step on the stack. This causes problems when undoing the operation. Add gimp_item_{start,end}_transform() functions, and corresponding GimpItem::{start,end}_transform() virtual functions, which are more specialized versions of gimp_item_{start,end}_move(), which should be used instead of the former before/after transforming an item; in other cases, such as when removing ot reordering an item, gimp_item_{start,end}_move() should still be used. The default implementation of GimpItem::{start,end}_transform() calls gimp_item_{start,end}_move(), respectively, so subclasses that override these functions don't have to do that themselves. In GimpGroupLayer, override GimpItem::{start,end}_transform(), instead of GimpItem::{start,end}_move(), for the same purpose of suspending mask resize. This avoids these functions from being called when removing a layer group, fixing the bug.
2018-04-22 15:39:40 +08:00
_gimp_group_layer_start_transform (GimpGroupLayer *group,
gboolean push_undo)
{
GimpGroupLayerPrivate *private;
GimpItem *item;
g_return_if_fail (GIMP_IS_GROUP_LAYER (group));
private = GET_PRIVATE (group);
item = GIMP_ITEM (group);
g_return_if_fail (private->suspend_mask == 0);
if (! gimp_item_is_attached (item))
push_undo = FALSE;
if (push_undo)
Bug 795410 - Deleting a layer group and then undoing the deletion ... ... raises a CRITICAL gimp_item_{start,end}_move() currently serves two different purposes: It is used by GimpLayer to suspend/resume mask resizing of the layer's ancestors; this is necessary whenever an operation on a layer might affect the size of its ancestors. It is also used by GimpGroupLayer to suspend/resume its own mask resizing; this, on the other hand, is only necessary before applying one of the transformation functions to the group, so that mask modification is handled by GimpLayer. In other words, the effects of gimp_item_{start,end}_move() on group layers are only necessary in a subset of the cases in which these functions are used. While in itself this isn't a problem, it does cause issues when removing a group layer: gimp_image_remove_layer() calls gimp_item_start_move() before removing the layer, and gimp_item_end_move() afterwards. While the former function is called while the layer is still attached to the image, the latter function is called after the layer is no longer attached. Since GimpGroupLayer pushes an undo step in response to these calls, only the call to start_move() results in an undo step, while the call to end_move() doesn't, resulting in an unbalanced GIMP_UNDO_GROUP_LAYER_START_MOVE undo step on the stack. This causes problems when undoing the operation. Add gimp_item_{start,end}_transform() functions, and corresponding GimpItem::{start,end}_transform() virtual functions, which are more specialized versions of gimp_item_{start,end}_move(), which should be used instead of the former before/after transforming an item; in other cases, such as when removing ot reordering an item, gimp_item_{start,end}_move() should still be used. The default implementation of GimpItem::{start,end}_transform() calls gimp_item_{start,end}_move(), respectively, so subclasses that override these functions don't have to do that themselves. In GimpGroupLayer, override GimpItem::{start,end}_transform(), instead of GimpItem::{start,end}_move(), for the same purpose of suspending mask resize. This avoids these functions from being called when removing a layer group, fixing the bug.
2018-04-22 15:39:40 +08:00
gimp_image_undo_push_group_layer_start_transform (gimp_item_get_image (item),
NULL, group);
Bug 795410 - Deleting a layer group and then undoing the deletion ... ... raises a CRITICAL gimp_item_{start,end}_move() currently serves two different purposes: It is used by GimpLayer to suspend/resume mask resizing of the layer's ancestors; this is necessary whenever an operation on a layer might affect the size of its ancestors. It is also used by GimpGroupLayer to suspend/resume its own mask resizing; this, on the other hand, is only necessary before applying one of the transformation functions to the group, so that mask modification is handled by GimpLayer. In other words, the effects of gimp_item_{start,end}_move() on group layers are only necessary in a subset of the cases in which these functions are used. While in itself this isn't a problem, it does cause issues when removing a group layer: gimp_image_remove_layer() calls gimp_item_start_move() before removing the layer, and gimp_item_end_move() afterwards. While the former function is called while the layer is still attached to the image, the latter function is called after the layer is no longer attached. Since GimpGroupLayer pushes an undo step in response to these calls, only the call to start_move() results in an undo step, while the call to end_move() doesn't, resulting in an unbalanced GIMP_UNDO_GROUP_LAYER_START_MOVE undo step on the stack. This causes problems when undoing the operation. Add gimp_item_{start,end}_transform() functions, and corresponding GimpItem::{start,end}_transform() virtual functions, which are more specialized versions of gimp_item_{start,end}_move(), which should be used instead of the former before/after transforming an item; in other cases, such as when removing ot reordering an item, gimp_item_{start,end}_move() should still be used. The default implementation of GimpItem::{start,end}_transform() calls gimp_item_{start,end}_move(), respectively, so subclasses that override these functions don't have to do that themselves. In GimpGroupLayer, override GimpItem::{start,end}_transform(), instead of GimpItem::{start,end}_move(), for the same purpose of suspending mask resize. This avoids these functions from being called when removing a layer group, fixing the bug.
2018-04-22 15:39:40 +08:00
private->transforming++;
}
void
Bug 795410 - Deleting a layer group and then undoing the deletion ... ... raises a CRITICAL gimp_item_{start,end}_move() currently serves two different purposes: It is used by GimpLayer to suspend/resume mask resizing of the layer's ancestors; this is necessary whenever an operation on a layer might affect the size of its ancestors. It is also used by GimpGroupLayer to suspend/resume its own mask resizing; this, on the other hand, is only necessary before applying one of the transformation functions to the group, so that mask modification is handled by GimpLayer. In other words, the effects of gimp_item_{start,end}_move() on group layers are only necessary in a subset of the cases in which these functions are used. While in itself this isn't a problem, it does cause issues when removing a group layer: gimp_image_remove_layer() calls gimp_item_start_move() before removing the layer, and gimp_item_end_move() afterwards. While the former function is called while the layer is still attached to the image, the latter function is called after the layer is no longer attached. Since GimpGroupLayer pushes an undo step in response to these calls, only the call to start_move() results in an undo step, while the call to end_move() doesn't, resulting in an unbalanced GIMP_UNDO_GROUP_LAYER_START_MOVE undo step on the stack. This causes problems when undoing the operation. Add gimp_item_{start,end}_transform() functions, and corresponding GimpItem::{start,end}_transform() virtual functions, which are more specialized versions of gimp_item_{start,end}_move(), which should be used instead of the former before/after transforming an item; in other cases, such as when removing ot reordering an item, gimp_item_{start,end}_move() should still be used. The default implementation of GimpItem::{start,end}_transform() calls gimp_item_{start,end}_move(), respectively, so subclasses that override these functions don't have to do that themselves. In GimpGroupLayer, override GimpItem::{start,end}_transform(), instead of GimpItem::{start,end}_move(), for the same purpose of suspending mask resize. This avoids these functions from being called when removing a layer group, fixing the bug.
2018-04-22 15:39:40 +08:00
_gimp_group_layer_end_transform (GimpGroupLayer *group,
gboolean push_undo)
{
GimpGroupLayerPrivate *private;
GimpItem *item;
g_return_if_fail (GIMP_IS_GROUP_LAYER (group));
private = GET_PRIVATE (group);
item = GIMP_ITEM (group);
g_return_if_fail (private->suspend_mask == 0);
Bug 795410 - Deleting a layer group and then undoing the deletion ... ... raises a CRITICAL gimp_item_{start,end}_move() currently serves two different purposes: It is used by GimpLayer to suspend/resume mask resizing of the layer's ancestors; this is necessary whenever an operation on a layer might affect the size of its ancestors. It is also used by GimpGroupLayer to suspend/resume its own mask resizing; this, on the other hand, is only necessary before applying one of the transformation functions to the group, so that mask modification is handled by GimpLayer. In other words, the effects of gimp_item_{start,end}_move() on group layers are only necessary in a subset of the cases in which these functions are used. While in itself this isn't a problem, it does cause issues when removing a group layer: gimp_image_remove_layer() calls gimp_item_start_move() before removing the layer, and gimp_item_end_move() afterwards. While the former function is called while the layer is still attached to the image, the latter function is called after the layer is no longer attached. Since GimpGroupLayer pushes an undo step in response to these calls, only the call to start_move() results in an undo step, while the call to end_move() doesn't, resulting in an unbalanced GIMP_UNDO_GROUP_LAYER_START_MOVE undo step on the stack. This causes problems when undoing the operation. Add gimp_item_{start,end}_transform() functions, and corresponding GimpItem::{start,end}_transform() virtual functions, which are more specialized versions of gimp_item_{start,end}_move(), which should be used instead of the former before/after transforming an item; in other cases, such as when removing ot reordering an item, gimp_item_{start,end}_move() should still be used. The default implementation of GimpItem::{start,end}_transform() calls gimp_item_{start,end}_move(), respectively, so subclasses that override these functions don't have to do that themselves. In GimpGroupLayer, override GimpItem::{start,end}_transform(), instead of GimpItem::{start,end}_move(), for the same purpose of suspending mask resize. This avoids these functions from being called when removing a layer group, fixing the bug.
2018-04-22 15:39:40 +08:00
g_return_if_fail (private->transforming > 0);
if (! gimp_item_is_attached (item))
push_undo = FALSE;
if (push_undo)
Bug 795410 - Deleting a layer group and then undoing the deletion ... ... raises a CRITICAL gimp_item_{start,end}_move() currently serves two different purposes: It is used by GimpLayer to suspend/resume mask resizing of the layer's ancestors; this is necessary whenever an operation on a layer might affect the size of its ancestors. It is also used by GimpGroupLayer to suspend/resume its own mask resizing; this, on the other hand, is only necessary before applying one of the transformation functions to the group, so that mask modification is handled by GimpLayer. In other words, the effects of gimp_item_{start,end}_move() on group layers are only necessary in a subset of the cases in which these functions are used. While in itself this isn't a problem, it does cause issues when removing a group layer: gimp_image_remove_layer() calls gimp_item_start_move() before removing the layer, and gimp_item_end_move() afterwards. While the former function is called while the layer is still attached to the image, the latter function is called after the layer is no longer attached. Since GimpGroupLayer pushes an undo step in response to these calls, only the call to start_move() results in an undo step, while the call to end_move() doesn't, resulting in an unbalanced GIMP_UNDO_GROUP_LAYER_START_MOVE undo step on the stack. This causes problems when undoing the operation. Add gimp_item_{start,end}_transform() functions, and corresponding GimpItem::{start,end}_transform() virtual functions, which are more specialized versions of gimp_item_{start,end}_move(), which should be used instead of the former before/after transforming an item; in other cases, such as when removing ot reordering an item, gimp_item_{start,end}_move() should still be used. The default implementation of GimpItem::{start,end}_transform() calls gimp_item_{start,end}_move(), respectively, so subclasses that override these functions don't have to do that themselves. In GimpGroupLayer, override GimpItem::{start,end}_transform(), instead of GimpItem::{start,end}_move(), for the same purpose of suspending mask resize. This avoids these functions from being called when removing a layer group, fixing the bug.
2018-04-22 15:39:40 +08:00
gimp_image_undo_push_group_layer_end_transform (gimp_item_get_image (item),
NULL, group);
Bug 795410 - Deleting a layer group and then undoing the deletion ... ... raises a CRITICAL gimp_item_{start,end}_move() currently serves two different purposes: It is used by GimpLayer to suspend/resume mask resizing of the layer's ancestors; this is necessary whenever an operation on a layer might affect the size of its ancestors. It is also used by GimpGroupLayer to suspend/resume its own mask resizing; this, on the other hand, is only necessary before applying one of the transformation functions to the group, so that mask modification is handled by GimpLayer. In other words, the effects of gimp_item_{start,end}_move() on group layers are only necessary in a subset of the cases in which these functions are used. While in itself this isn't a problem, it does cause issues when removing a group layer: gimp_image_remove_layer() calls gimp_item_start_move() before removing the layer, and gimp_item_end_move() afterwards. While the former function is called while the layer is still attached to the image, the latter function is called after the layer is no longer attached. Since GimpGroupLayer pushes an undo step in response to these calls, only the call to start_move() results in an undo step, while the call to end_move() doesn't, resulting in an unbalanced GIMP_UNDO_GROUP_LAYER_START_MOVE undo step on the stack. This causes problems when undoing the operation. Add gimp_item_{start,end}_transform() functions, and corresponding GimpItem::{start,end}_transform() virtual functions, which are more specialized versions of gimp_item_{start,end}_move(), which should be used instead of the former before/after transforming an item; in other cases, such as when removing ot reordering an item, gimp_item_{start,end}_move() should still be used. The default implementation of GimpItem::{start,end}_transform() calls gimp_item_{start,end}_move(), respectively, so subclasses that override these functions don't have to do that themselves. In GimpGroupLayer, override GimpItem::{start,end}_transform(), instead of GimpItem::{start,end}_move(), for the same purpose of suspending mask resize. This avoids these functions from being called when removing a layer group, fixing the bug.
2018-04-22 15:39:40 +08:00
private->transforming--;
Bug 795410 - Deleting a layer group and then undoing the deletion ... ... raises a CRITICAL gimp_item_{start,end}_move() currently serves two different purposes: It is used by GimpLayer to suspend/resume mask resizing of the layer's ancestors; this is necessary whenever an operation on a layer might affect the size of its ancestors. It is also used by GimpGroupLayer to suspend/resume its own mask resizing; this, on the other hand, is only necessary before applying one of the transformation functions to the group, so that mask modification is handled by GimpLayer. In other words, the effects of gimp_item_{start,end}_move() on group layers are only necessary in a subset of the cases in which these functions are used. While in itself this isn't a problem, it does cause issues when removing a group layer: gimp_image_remove_layer() calls gimp_item_start_move() before removing the layer, and gimp_item_end_move() afterwards. While the former function is called while the layer is still attached to the image, the latter function is called after the layer is no longer attached. Since GimpGroupLayer pushes an undo step in response to these calls, only the call to start_move() results in an undo step, while the call to end_move() doesn't, resulting in an unbalanced GIMP_UNDO_GROUP_LAYER_START_MOVE undo step on the stack. This causes problems when undoing the operation. Add gimp_item_{start,end}_transform() functions, and corresponding GimpItem::{start,end}_transform() virtual functions, which are more specialized versions of gimp_item_{start,end}_move(), which should be used instead of the former before/after transforming an item; in other cases, such as when removing ot reordering an item, gimp_item_{start,end}_move() should still be used. The default implementation of GimpItem::{start,end}_transform() calls gimp_item_{start,end}_move(), respectively, so subclasses that override these functions don't have to do that themselves. In GimpGroupLayer, override GimpItem::{start,end}_transform(), instead of GimpItem::{start,end}_move(), for the same purpose of suspending mask resize. This avoids these functions from being called when removing a layer group, fixing the bug.
2018-04-22 15:39:40 +08:00
if (private->transforming == 0)
gimp_group_layer_update_mask_size (GIMP_GROUP_LAYER (item));
}
/* private functions */
static void
gimp_group_layer_child_add (GimpContainer *container,
GimpLayer *child,
GimpGroupLayer *group)
{
gimp_group_layer_update (group);
if (gimp_filter_get_active (GIMP_FILTER (child)))
{
gimp_layer_update_effective_mode (GIMP_LAYER (group));
if (gimp_layer_get_excludes_backdrop (child))
gimp_layer_update_excludes_backdrop (GIMP_LAYER (group));
}
}
static void
gimp_group_layer_child_remove (GimpContainer *container,
GimpLayer *child,
GimpGroupLayer *group)
{
gimp_group_layer_update (group);
if (gimp_filter_get_active (GIMP_FILTER (child)))
{
gimp_layer_update_effective_mode (GIMP_LAYER (group));
if (gimp_layer_get_excludes_backdrop (child))
gimp_layer_update_excludes_backdrop (GIMP_LAYER (group));
}
}
static void
gimp_group_layer_child_move (GimpLayer *child,
GParamSpec *pspec,
GimpGroupLayer *group)
{
gimp_group_layer_update (group);
}
static void
gimp_group_layer_child_resize (GimpLayer *child,
GimpGroupLayer *group)
{
gimp_group_layer_update (group);
}
static void
gimp_group_layer_child_active_changed (GimpLayer *child,
GimpGroupLayer *group)
{
gimp_layer_update_effective_mode (GIMP_LAYER (group));
if (gimp_layer_get_excludes_backdrop (child))
gimp_layer_update_excludes_backdrop (GIMP_LAYER (group));
}
static void
gimp_group_layer_child_effective_mode_changed (GimpLayer *child,
GimpGroupLayer *group)
{
if (gimp_filter_get_active (GIMP_FILTER (child)))
gimp_layer_update_effective_mode (GIMP_LAYER (group));
}
static void
gimp_group_layer_child_excludes_backdrop_changed (GimpLayer *child,
GimpGroupLayer *group)
{
if (gimp_filter_get_active (GIMP_FILTER (child)))
gimp_layer_update_excludes_backdrop (GIMP_LAYER (group));
}
static void
gimp_group_layer_flush (GimpGroupLayer *group)
{
GimpGroupLayerPrivate *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.
*/
gimp_projectable_flush (GIMP_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
* gimp_pickable_flush() below causes any pending idle rendering to
* finish synchronously, so this needs to happen before.
*/
gimp_pickable_get_buffer (GIMP_PICKABLE (private->projection));
gimp_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)
*/
gimp_pickable_flush (GIMP_PICKABLE (private->projection));
}
}
static void
gimp_group_layer_update (GimpGroupLayer *group)
{
if (GET_PRIVATE (group)->suspend_resize == 0)
{
gimp_group_layer_update_size (group);
}
}
static void
gimp_group_layer_update_size (GimpGroupLayer *group)
{
GimpGroupLayerPrivate *private = GET_PRIVATE (group);
GimpItem *item = GIMP_ITEM (group);
GimpLayer *layer = GIMP_LAYER (group);
GimpItem *mask = GIMP_ITEM (gimp_layer_get_mask (layer));
gint old_x = gimp_item_get_offset_x (item);
gint old_y = gimp_item_get_offset_y (item);
gint old_width = gimp_item_get_width (item);
gint old_height = gimp_item_get_height (item);
gint x = 0;
gint y = 0;
gint width = 1;
gint height = 1;
gboolean first = TRUE;
gboolean size_changed;
gboolean resize_mask;
GList *list;
for (list = gimp_item_stack_get_item_iter (GIMP_ITEM_STACK (private->children));
list;
list = g_list_next (list))
{
GimpItem *child = list->data;
gint child_width;
gint child_height;
if (! gimp_viewable_get_size (GIMP_VIEWABLE (child),
&child_width, &child_height))
{
/* ignore children without content (empty group layers);
* see bug 777017
*/
continue;
}
if (first)
{
x = gimp_item_get_offset_x (child);
y = gimp_item_get_offset_y (child);
width = child_width;
height = child_height;
first = FALSE;
}
else
{
gimp_rectangle_union (x, y, width, height,
gimp_item_get_offset_x (child),
gimp_item_get_offset_y (child),
child_width,
child_height,
&x, &y, &width, &height);
}
}
size_changed = (x != old_x ||
y != old_y ||
width != old_width ||
height != old_height);
resize_mask = mask && size_changed;
/* if we show the mask, invalidate the old mask area */
if (resize_mask && gimp_layer_get_show_mask (layer))
{
gimp_drawable_update (GIMP_DRAWABLE (group),
gimp_item_get_offset_x (mask) - old_x,
gimp_item_get_offset_y (mask) - old_y,
gimp_item_get_width (mask),
gimp_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) -x,
"y", (gdouble) -y,
NULL);
/* update our offset *before* calling gimp_pickable_get_buffer(), so
* that if our graph isn't constructed yet, the offset node picks
* up the right coordinates in gimp_group_layer_get_graph().
*/
gimp_item_set_offset (item, x, y);
/* temporarily change the return values of gimp_viewable_get_size()
* so the projection allocates itself correctly
*/
private->reallocate_width = width;
private->reallocate_height = height;
if (private->reallocate_projection)
{
private->reallocate_projection = FALSE;
gimp_projectable_structure_changed (GIMP_PROJECTABLE (group));
}
else
{
/* when there's no need to reallocate the projection, we call
* gimp_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.
*/
gimp_projectable_bounds_changed (GIMP_PROJECTABLE (group),
old_x, old_y, old_width, old_height);
}
buffer = gimp_pickable_get_buffer (GIMP_PICKABLE (private->projection));
gimp_drawable_set_buffer_full (GIMP_DRAWABLE (group),
FALSE, NULL,
buffer,
x, y,
FALSE /* don't update the drawable, the
* flush() below will take care of
* that.
*/);
gimp_group_layer_flush (group);
/* reset, the actual size is correct now */
private->reallocate_width = 0;
private->reallocate_height = 0;
}
Bug 795410 - Deleting a layer group and then undoing the deletion ... ... raises a CRITICAL gimp_item_{start,end}_move() currently serves two different purposes: It is used by GimpLayer to suspend/resume mask resizing of the layer's ancestors; this is necessary whenever an operation on a layer might affect the size of its ancestors. It is also used by GimpGroupLayer to suspend/resume its own mask resizing; this, on the other hand, is only necessary before applying one of the transformation functions to the group, so that mask modification is handled by GimpLayer. In other words, the effects of gimp_item_{start,end}_move() on group layers are only necessary in a subset of the cases in which these functions are used. While in itself this isn't a problem, it does cause issues when removing a group layer: gimp_image_remove_layer() calls gimp_item_start_move() before removing the layer, and gimp_item_end_move() afterwards. While the former function is called while the layer is still attached to the image, the latter function is called after the layer is no longer attached. Since GimpGroupLayer pushes an undo step in response to these calls, only the call to start_move() results in an undo step, while the call to end_move() doesn't, resulting in an unbalanced GIMP_UNDO_GROUP_LAYER_START_MOVE undo step on the stack. This causes problems when undoing the operation. Add gimp_item_{start,end}_transform() functions, and corresponding GimpItem::{start,end}_transform() virtual functions, which are more specialized versions of gimp_item_{start,end}_move(), which should be used instead of the former before/after transforming an item; in other cases, such as when removing ot reordering an item, gimp_item_{start,end}_move() should still be used. The default implementation of GimpItem::{start,end}_transform() calls gimp_item_{start,end}_move(), respectively, so subclasses that override these functions don't have to do that themselves. In GimpGroupLayer, override GimpItem::{start,end}_transform(), instead of GimpItem::{start,end}_move(), for the same purpose of suspending mask resize. This avoids these functions from being called when removing a layer group, fixing the bug.
2018-04-22 15:39:40 +08:00
/* resize the mask if not transforming (in which case, GimpLayer takes care
* of the mask)
*/
Bug 795410 - Deleting a layer group and then undoing the deletion ... ... raises a CRITICAL gimp_item_{start,end}_move() currently serves two different purposes: It is used by GimpLayer to suspend/resume mask resizing of the layer's ancestors; this is necessary whenever an operation on a layer might affect the size of its ancestors. It is also used by GimpGroupLayer to suspend/resume its own mask resizing; this, on the other hand, is only necessary before applying one of the transformation functions to the group, so that mask modification is handled by GimpLayer. In other words, the effects of gimp_item_{start,end}_move() on group layers are only necessary in a subset of the cases in which these functions are used. While in itself this isn't a problem, it does cause issues when removing a group layer: gimp_image_remove_layer() calls gimp_item_start_move() before removing the layer, and gimp_item_end_move() afterwards. While the former function is called while the layer is still attached to the image, the latter function is called after the layer is no longer attached. Since GimpGroupLayer pushes an undo step in response to these calls, only the call to start_move() results in an undo step, while the call to end_move() doesn't, resulting in an unbalanced GIMP_UNDO_GROUP_LAYER_START_MOVE undo step on the stack. This causes problems when undoing the operation. Add gimp_item_{start,end}_transform() functions, and corresponding GimpItem::{start,end}_transform() virtual functions, which are more specialized versions of gimp_item_{start,end}_move(), which should be used instead of the former before/after transforming an item; in other cases, such as when removing ot reordering an item, gimp_item_{start,end}_move() should still be used. The default implementation of GimpItem::{start,end}_transform() calls gimp_item_{start,end}_move(), respectively, so subclasses that override these functions don't have to do that themselves. In GimpGroupLayer, override GimpItem::{start,end}_transform(), instead of GimpItem::{start,end}_move(), for the same purpose of suspending mask resize. This avoids these functions from being called when removing a layer group, fixing the bug.
2018-04-22 15:39:40 +08:00
if (resize_mask && ! private->transforming)
gimp_group_layer_update_mask_size (group);
/* if we show the mask, invalidate the new mask area */
if (resize_mask && gimp_layer_get_show_mask (layer))
{
gimp_drawable_update (GIMP_DRAWABLE (group),
gimp_item_get_offset_x (mask) - x,
gimp_item_get_offset_y (mask) - y,
gimp_item_get_width (mask),
gimp_item_get_height (mask));
}
}
static void
gimp_group_layer_update_mask_size (GimpGroupLayer *group)
{
GimpGroupLayerPrivate *private = GET_PRIVATE (group);
GimpItem *item = GIMP_ITEM (group);
GimpItem *mask;
GeglBuffer *buffer;
GeglBuffer *mask_buffer;
GeglRectangle bounds;
GeglRectangle mask_bounds;
GeglRectangle copy_bounds;
gboolean intersect;
mask = GIMP_ITEM (gimp_layer_get_mask (GIMP_LAYER (group)));
if (! mask)
return;
bounds.x = gimp_item_get_offset_x (item);
bounds.y = gimp_item_get_offset_y (item);
bounds.width = gimp_item_get_width (item);
bounds.height = gimp_item_get_height (item);
mask_bounds.x = gimp_item_get_offset_x (mask);
mask_bounds.y = gimp_item_get_offset_y (mask);
mask_bounds.width = gimp_item_get_width (mask);
mask_bounds.height = gimp_item_get_height (mask);
if (gegl_rectangle_equal (&bounds, &mask_bounds))
return;
buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0, bounds.width, bounds.height),
gimp_drawable_get_format (GIMP_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 = gimp_drawable_get_buffer (GIMP_DRAWABLE (mask));
}
intersect = gimp_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)
{
gimp_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));
}
gimp_drawable_set_buffer_full (GIMP_DRAWABLE (mask),
FALSE, NULL,
buffer, bounds.x, bounds.y,
TRUE);
g_object_unref (buffer);
}
static void
gimp_group_layer_update_source_node (GimpGroupLayer *group)
{
GimpGroupLayerPrivate *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_connect_to (input, "output",
private->graph, "input");
gegl_node_connect_to (private->graph, "output",
output, "input");
}
else
{
gegl_node_disconnect (private->graph, "input");
gegl_node_connect_to (private->parent_source_node, "output",
output, "input");
}
}
static void
gimp_group_layer_update_mode_node (GimpGroupLayer *group)
{
GimpGroupLayerPrivate *private = GET_PRIVATE (group);
GeglNode *node;
GeglNode *input;
GeglNode *mode_node;
node = gimp_filter_get_node (GIMP_FILTER (group));
input = gegl_node_get_input_proxy (node, "input");
mode_node = gimp_drawable_get_mode_node (GIMP_DRAWABLE (group));
if (private->pass_through &&
gimp_layer_get_excludes_backdrop (GIMP_LAYER (group)))
{
gegl_node_disconnect (mode_node, "input");
}
else
{
gegl_node_connect_to (input, "output",
mode_node, "input");
}
}
static void
gimp_group_layer_stack_update (GimpDrawableStack *stack,
gint x,
gint y,
gint width,
gint height,
GimpGroupLayer *group)
{
GimpGroupLayerPrivate *private = GET_PRIVATE (group);
#if 0
g_printerr ("%s (%s) %d, %d (%d, %d)\n",
G_STRFUNC, gimp_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.
*/
gimp_projectable_invalidate (GIMP_PROJECTABLE (group),
x, y, width, height);
gimp_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.
*/
gimp_drawable_update (GIMP_DRAWABLE (group),
x - gimp_item_get_offset_x (GIMP_ITEM (group)),
y - gimp_item_get_offset_y (GIMP_ITEM (group)),
width, height);
}
}
static void
gimp_group_layer_proj_update (GimpProjection *proj,
gboolean now,
gint x,
gint y,
gint width,
gint height,
GimpGroupLayer *group)
{
GimpGroupLayerPrivate *private = GET_PRIVATE (group);
#if 0
g_printerr ("%s (%s) %d, %d (%d, %d)\n",
G_STRFUNC, gimp_object_get_name (group),
x, y, width, height);
#endif
if (! private->pass_through)
{
/* the projection speaks in image coordinates, transform to layer
* coordinates when emitting our own update signal.
*/
gimp_drawable_update (GIMP_DRAWABLE (group),
x - gimp_item_get_offset_x (GIMP_ITEM (group)),
y - gimp_item_get_offset_y (GIMP_ITEM (group)),
width, height);
}
}