2009-08-04 05:24:46 +08:00
|
|
|
/* 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 <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "config.h"
|
|
|
|
|
|
|
|
#include <string.h>
|
|
|
|
|
2013-10-15 07:58:39 +08:00
|
|
|
#include <gdk-pixbuf/gdk-pixbuf.h>
|
2009-08-04 05:24:46 +08:00
|
|
|
#include <gegl.h>
|
|
|
|
|
2009-08-24 05:00:32 +08:00
|
|
|
#include "libgimpbase/gimpbase.h"
|
2009-08-25 20:26:26 +08:00
|
|
|
#include "libgimpmath/gimpmath.h"
|
2009-08-24 05:00:32 +08:00
|
|
|
|
2009-08-04 05:24:46 +08:00
|
|
|
#include "core-types.h"
|
|
|
|
|
2014-06-15 05:12:22 +08:00
|
|
|
#include "gegl/gimp-babl.h"
|
|
|
|
|
2009-08-04 05:24:46 +08:00
|
|
|
#include "gimpgrouplayer.h"
|
2018-02-06 03:53:38 +08:00
|
|
|
#include "gimpgrouplayerundo.h"
|
2009-08-04 05:24:46 +08:00
|
|
|
#include "gimpimage.h"
|
Bug 51112 - Support layer masks on layer groups
Add layer-mask support for group layers. Group-layer masks work
similarly to ordinary-layer masks, with the following
considerations:
The group's mask size is the same as group's size (i.e., the
bounding box of its children) at all times. When the group's size
changes, the mask is cropped to the new size -- areas of the mask
that fall outside of the new bounds are discarded and their data is
lost (sans undo), and newly added areas are filled with black (and
hence are transparent by default).
The new gimp_group_layer_{suspend,resume}_mask() functions can be
used to modify this behavior. Between the outermost pair of
suspend/resume calls, the old mask data is remembered, and is used
to fill the newly added areas while cropping the mask when the
group is resized. We override GimpItem::{start,end}_move() for
GimpLayer, to call these functions (suspend() in start_move(), and
resume() in end_move()) for each of the layer's ancestors.
As a result, while moving a layer, or a set of layers, atomically,
such as while dragging with the move tool, or moving linked layers,
the ancestors' mask data is not lost, and is only discarded at the
end of the operation.
This commit also takes care of properly handling undo for group-
layer mask crops, properly invalidating the image when the group
layer's mask is shown, and enabling the mask actions for group
layers (obviously :).
2018-02-06 00:19:18 +08:00
|
|
|
#include "gimpimage-undo.h"
|
2009-09-07 19:04:55 +08:00
|
|
|
#include "gimpimage-undo-push.h"
|
2017-05-09 04:02:37 +08:00
|
|
|
#include "gimplayerstack.h"
|
2009-08-24 22:03:21 +08:00
|
|
|
#include "gimppickable.h"
|
|
|
|
#include "gimpprojectable.h"
|
|
|
|
#include "gimpprojection.h"
|
2009-08-04 05:24:46 +08:00
|
|
|
|
|
|
|
#include "gimp-intl.h"
|
|
|
|
|
|
|
|
|
2011-02-04 05:29:25 +08:00
|
|
|
typedef struct _GimpGroupLayerPrivate GimpGroupLayerPrivate;
|
|
|
|
|
|
|
|
struct _GimpGroupLayerPrivate
|
|
|
|
{
|
|
|
|
GimpContainer *children;
|
|
|
|
GimpProjection *projection;
|
2017-04-22 06:04:49 +08:00
|
|
|
GeglNode *source_node;
|
|
|
|
GeglNode *parent_source_node;
|
2011-02-04 05:29:25 +08:00
|
|
|
GeglNode *graph;
|
|
|
|
GeglNode *offset_node;
|
|
|
|
gint suspend_resize;
|
Bug 51112 - Support layer masks on layer groups
Add layer-mask support for group layers. Group-layer masks work
similarly to ordinary-layer masks, with the following
considerations:
The group's mask size is the same as group's size (i.e., the
bounding box of its children) at all times. When the group's size
changes, the mask is cropped to the new size -- areas of the mask
that fall outside of the new bounds are discarded and their data is
lost (sans undo), and newly added areas are filled with black (and
hence are transparent by default).
The new gimp_group_layer_{suspend,resume}_mask() functions can be
used to modify this behavior. Between the outermost pair of
suspend/resume calls, the old mask data is remembered, and is used
to fill the newly added areas while cropping the mask when the
group is resized. We override GimpItem::{start,end}_move() for
GimpLayer, to call these functions (suspend() in start_move(), and
resume() in end_move()) for each of the layer's ancestors.
As a result, while moving a layer, or a set of layers, atomically,
such as while dragging with the move tool, or moving linked layers,
the ancestors' mask data is not lost, and is only discarded at the
end of the operation.
This commit also takes care of properly handling undo for group-
layer mask crops, properly invalidating the image when the group
layer's mask is shown, and enabling the mask actions for group
layers (obviously :).
2018-02-06 00:19:18 +08:00
|
|
|
gint suspend_mask;
|
|
|
|
GeglBuffer *suspended_mask_buffer;
|
|
|
|
GeglRectangle suspended_mask_bounds;
|
|
|
|
gint moving;
|
2011-09-26 03:57:20 +08:00
|
|
|
gboolean expanded;
|
2017-12-03 04:25:04 +08:00
|
|
|
gboolean pass_through;
|
2011-03-09 01:04:23 +08:00
|
|
|
|
|
|
|
/* hackish temp states to make the projection/tiles stuff work */
|
2012-03-30 05:19:27 +08:00
|
|
|
const Babl *convert_format;
|
2011-03-09 01:04:23 +08:00
|
|
|
gboolean reallocate_projection;
|
|
|
|
gint reallocate_width;
|
|
|
|
gint reallocate_height;
|
2011-02-04 05:29:25 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
#define GET_PRIVATE(item) G_TYPE_INSTANCE_GET_PRIVATE (item, \
|
|
|
|
GIMP_TYPE_GROUP_LAYER, \
|
|
|
|
GimpGroupLayerPrivate)
|
|
|
|
|
|
|
|
|
2009-08-24 22:03:21 +08:00
|
|
|
static void gimp_projectable_iface_init (GimpProjectableInterface *iface);
|
2011-08-20 21:01:46 +08:00
|
|
|
static void gimp_pickable_iface_init (GimpPickableInterface *iface);
|
2009-08-24 22:03:21 +08:00
|
|
|
|
2011-02-04 05:29:25 +08:00
|
|
|
static void gimp_group_layer_finalize (GObject *object);
|
2009-08-24 22:03:21 +08:00
|
|
|
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);
|
|
|
|
|
2017-12-02 22:55:15 +08:00
|
|
|
static void gimp_group_layer_ancestry_changed (GimpViewable *viewable);
|
2011-03-09 01:04:23 +08:00
|
|
|
static gboolean gimp_group_layer_get_size (GimpViewable *viewable,
|
|
|
|
gint *width,
|
|
|
|
gint *height);
|
2009-08-24 22:03:21 +08:00
|
|
|
static GimpContainer * gimp_group_layer_get_children (GimpViewable *viewable);
|
2011-09-26 03:57:20 +08:00
|
|
|
static gboolean gimp_group_layer_get_expanded (GimpViewable *viewable);
|
|
|
|
static void gimp_group_layer_set_expanded (GimpViewable *viewable,
|
|
|
|
gboolean expanded);
|
2009-08-24 22:03:21 +08:00
|
|
|
|
2016-05-20 05:51:44 +08:00
|
|
|
static gboolean gimp_group_layer_is_position_locked (GimpItem *item);
|
2009-08-24 22:03:21 +08:00
|
|
|
static GimpItem * gimp_group_layer_duplicate (GimpItem *item,
|
|
|
|
GType new_type);
|
2009-08-25 20:23:42 +08:00
|
|
|
static void gimp_group_layer_convert (GimpItem *item,
|
2015-08-16 21:47:43 +08:00
|
|
|
GimpImage *dest_image,
|
|
|
|
GType old_type);
|
Bug 51112 - Support layer masks on layer groups
Add layer-mask support for group layers. Group-layer masks work
similarly to ordinary-layer masks, with the following
considerations:
The group's mask size is the same as group's size (i.e., the
bounding box of its children) at all times. When the group's size
changes, the mask is cropped to the new size -- areas of the mask
that fall outside of the new bounds are discarded and their data is
lost (sans undo), and newly added areas are filled with black (and
hence are transparent by default).
The new gimp_group_layer_{suspend,resume}_mask() functions can be
used to modify this behavior. Between the outermost pair of
suspend/resume calls, the old mask data is remembered, and is used
to fill the newly added areas while cropping the mask when the
group is resized. We override GimpItem::{start,end}_move() for
GimpLayer, to call these functions (suspend() in start_move(), and
resume() in end_move()) for each of the layer's ancestors.
As a result, while moving a layer, or a set of layers, atomically,
such as while dragging with the move tool, or moving linked layers,
the ancestors' mask data is not lost, and is only discarded at the
end of the operation.
This commit also takes care of properly handling undo for group-
layer mask crops, properly invalidating the image when the group
layer's mask is shown, and enabling the mask actions for group
layers (obviously :).
2018-02-06 00:19:18 +08:00
|
|
|
static void gimp_group_layer_start_move (GimpItem *item,
|
|
|
|
gboolean push_undo);
|
|
|
|
static void gimp_group_layer_end_move (GimpItem *item,
|
|
|
|
gboolean push_undo);
|
2018-02-06 03:53:38 +08:00
|
|
|
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);
|
2017-06-17 08:15:56 +08:00
|
|
|
|
|
|
|
static gint64 gimp_group_layer_estimate_memsize (GimpDrawable *drawable,
|
|
|
|
GimpComponentType component_type,
|
|
|
|
gint width,
|
|
|
|
gint height);
|
|
|
|
|
|
|
|
static void gimp_group_layer_translate (GimpLayer *layer,
|
2009-08-25 01:33:29 +08:00
|
|
|
gint offset_x,
|
2017-06-17 08:15:56 +08:00
|
|
|
gint offset_y);
|
|
|
|
static void gimp_group_layer_scale (GimpLayer *layer,
|
2009-08-25 01:33:29 +08:00
|
|
|
gint new_width,
|
|
|
|
gint new_height,
|
|
|
|
gint new_offset_x,
|
|
|
|
gint new_offset_y,
|
|
|
|
GimpInterpolationType interp_type,
|
|
|
|
GimpProgress *progress);
|
2017-06-17 08:15:56 +08:00
|
|
|
static void gimp_group_layer_flip (GimpLayer *layer,
|
2009-08-25 01:33:29 +08:00
|
|
|
GimpContext *context,
|
|
|
|
GimpOrientationType flip_type,
|
|
|
|
gdouble axis,
|
|
|
|
gboolean clip_result);
|
2017-06-17 08:15:56 +08:00
|
|
|
static void gimp_group_layer_rotate (GimpLayer *layer,
|
2009-08-25 01:33:29 +08:00
|
|
|
GimpContext *context,
|
|
|
|
GimpRotationType rotate_type,
|
|
|
|
gdouble center_x,
|
|
|
|
gdouble center_y,
|
|
|
|
gboolean clip_result);
|
2017-06-17 08:15:56 +08:00
|
|
|
static void gimp_group_layer_transform (GimpLayer *layer,
|
2009-08-25 01:33:29 +08:00
|
|
|
GimpContext *context,
|
|
|
|
const GimpMatrix3 *matrix,
|
|
|
|
GimpTransformDirection direction,
|
|
|
|
GimpInterpolationType interpolation_type,
|
|
|
|
GimpTransformResize clip_result,
|
2017-06-17 08:15:56 +08:00
|
|
|
GimpProgress *progress);
|
|
|
|
static void gimp_group_layer_convert_type (GimpLayer *layer,
|
2009-09-12 04:01:31 +08:00
|
|
|
GimpImage *dest_image,
|
2012-10-14 04:56:32 +08:00
|
|
|
const Babl *new_format,
|
2016-04-29 06:42:42 +08:00
|
|
|
GimpColorProfile *dest_profile,
|
2016-11-08 03:41:39 +08:00
|
|
|
GeglDitherMethod layer_dither_type,
|
|
|
|
GeglDitherMethod mask_dither_type,
|
2015-10-22 04:22:30 +08:00
|
|
|
gboolean push_undo,
|
|
|
|
GimpProgress *progress);
|
2017-04-22 06:04:49 +08:00
|
|
|
static GeglNode * gimp_group_layer_get_source_node (GimpDrawable *drawable);
|
|
|
|
|
2017-12-07 03:06:16 +08:00
|
|
|
static void gimp_group_layer_opacity_changed (GimpLayer *layer);
|
|
|
|
static void gimp_group_layer_effective_mode_changed (GimpLayer *layer);
|
2017-05-09 03:06:00 +08:00
|
|
|
static void
|
|
|
|
gimp_group_layer_excludes_backdrop_changed (GimpLayer *layer);
|
2017-12-07 03:06:16 +08:00
|
|
|
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);
|
2017-05-09 03:06:00 +08:00
|
|
|
static gboolean
|
|
|
|
gimp_group_layer_get_excludes_backdrop (GimpLayer *layer);
|
2009-08-28 01:43:45 +08:00
|
|
|
|
2012-03-30 03:09:37 +08:00
|
|
|
static const Babl * gimp_group_layer_get_format (GimpProjectable *projectable);
|
2009-08-24 22:03:21 +08:00
|
|
|
static GeglNode * gimp_group_layer_get_graph (GimpProjectable *projectable);
|
2017-04-23 02:28:54 +08:00
|
|
|
static void gimp_group_layer_begin_render (GimpProjectable *projectable);
|
|
|
|
static void gimp_group_layer_end_render (GimpProjectable *projectable);
|
|
|
|
|
2012-04-21 22:05:49 +08:00
|
|
|
static gdouble gimp_group_layer_get_opacity_at (GimpPickable *pickable,
|
2011-08-20 21:01:46 +08:00
|
|
|
gint x,
|
|
|
|
gint y);
|
|
|
|
|
2009-08-24 22:03:21 +08:00
|
|
|
|
|
|
|
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);
|
app: add GimpFilter::active property; move ::visible to GimpItem
Add an "active" property to GimpFilter, which replaces its
"visible" property. The new property assumes the lower-level role
"visible" had -- controlling whether the filter has any effect as
part of its parent filter-stack.
Add a "visible" property to GimpItem, separate from the "active"
property, which assumes the higher-level role "visible" had --
controlling whether the item is considered "visible", as per the
GUI. By default, the item's "visible" property is bound to the
filter's "active" property, so that changes in visibility directly
affect the filter's "activeness"; this binding can be controlled
using the new gimp_item_bind_visible_to_active() function.
This distinction is currently necessary for floating selections.
Floating selection layers must not be active in their parent stack,
regardless of their visibility, in particular, so that their mode
node doesn't hide the entire backdrop when their composite mode
excludes the backdrop (i.e., when it's dst-atop or src-in).
Instead, their visibility should affect the activeness of the
floating-selection filter of the drawable they're attached to.
This is handled by the next commit.
2017-12-06 02:46:50 +08:00
|
|
|
static void gimp_group_layer_child_active_changed (GimpLayer *child,
|
2017-05-09 03:06:00 +08:00
|
|
|
GimpGroupLayer *group);
|
2017-12-07 03:06:16 +08:00
|
|
|
static void
|
|
|
|
gimp_group_layer_child_effective_mode_changed (GimpLayer *child,
|
|
|
|
GimpGroupLayer *group);
|
2017-05-09 03:06:00 +08:00
|
|
|
static void
|
|
|
|
gimp_group_layer_child_excludes_backdrop_changed (GimpLayer *child,
|
|
|
|
GimpGroupLayer *group);
|
2009-08-24 22:03:21 +08:00
|
|
|
|
2017-12-02 23:15:39 +08:00
|
|
|
static void gimp_group_layer_flush (GimpGroupLayer *group);
|
2009-09-07 19:04:55 +08:00
|
|
|
static void gimp_group_layer_update (GimpGroupLayer *group);
|
|
|
|
static void gimp_group_layer_update_size (GimpGroupLayer *group);
|
2018-02-07 22:48:00 +08:00
|
|
|
static void gimp_group_layer_update_mask_size (GimpGroupLayer *group);
|
2017-04-22 06:04:49 +08:00
|
|
|
static void gimp_group_layer_update_source_node (GimpGroupLayer *group);
|
2017-05-09 03:06:00 +08:00
|
|
|
static void gimp_group_layer_update_mode_node (GimpGroupLayer *group);
|
2009-08-24 22:03:21 +08:00
|
|
|
|
|
|
|
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_IMPLEMENT_INTERFACE (GIMP_TYPE_PROJECTABLE,
|
2011-08-20 21:01:46 +08:00
|
|
|
gimp_projectable_iface_init)
|
|
|
|
G_IMPLEMENT_INTERFACE (GIMP_TYPE_PICKABLE,
|
|
|
|
gimp_pickable_iface_init))
|
|
|
|
|
2009-08-04 05:24:46 +08:00
|
|
|
|
|
|
|
#define parent_class gimp_group_layer_parent_class
|
|
|
|
|
|
|
|
|
2017-12-07 03:06:16 +08:00
|
|
|
/* disable pass-through groups strength-reduction to normal groups.
|
|
|
|
* see gimp_group_layer_get_effective_mode().
|
|
|
|
*/
|
|
|
|
static gboolean no_pass_through_strength_reduction = FALSE;
|
|
|
|
|
|
|
|
|
2009-08-04 05:24:46 +08:00
|
|
|
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);
|
2009-08-28 01:43:45 +08:00
|
|
|
GimpDrawableClass *drawable_class = GIMP_DRAWABLE_CLASS (klass);
|
2017-06-17 08:15:56 +08:00
|
|
|
GimpLayerClass *layer_class = GIMP_LAYER_CLASS (klass);
|
2009-08-04 05:24:46 +08:00
|
|
|
|
2017-05-09 03:06:00 +08:00
|
|
|
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";
|
2017-12-02 22:55:15 +08:00
|
|
|
viewable_class->ancestry_changed = gimp_group_layer_ancestry_changed;
|
2017-05-09 03:06:00 +08:00
|
|
|
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 51112 - Support layer masks on layer groups
Add layer-mask support for group layers. Group-layer masks work
similarly to ordinary-layer masks, with the following
considerations:
The group's mask size is the same as group's size (i.e., the
bounding box of its children) at all times. When the group's size
changes, the mask is cropped to the new size -- areas of the mask
that fall outside of the new bounds are discarded and their data is
lost (sans undo), and newly added areas are filled with black (and
hence are transparent by default).
The new gimp_group_layer_{suspend,resume}_mask() functions can be
used to modify this behavior. Between the outermost pair of
suspend/resume calls, the old mask data is remembered, and is used
to fill the newly added areas while cropping the mask when the
group is resized. We override GimpItem::{start,end}_move() for
GimpLayer, to call these functions (suspend() in start_move(), and
resume() in end_move()) for each of the layer's ancestors.
As a result, while moving a layer, or a set of layers, atomically,
such as while dragging with the move tool, or moving linked layers,
the ancestors' mask data is not lost, and is only discarded at the
end of the operation.
This commit also takes care of properly handling undo for group-
layer mask crops, properly invalidating the image when the group
layer's mask is shown, and enabling the mask actions for group
layers (obviously :).
2018-02-06 00:19:18 +08:00
|
|
|
item_class->start_move = gimp_group_layer_start_move;
|
|
|
|
item_class->end_move = gimp_group_layer_end_move;
|
2018-02-06 03:53:38 +08:00
|
|
|
item_class->resize = gimp_group_layer_resize;
|
2017-05-09 03:06:00 +08:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
2017-12-07 03:06:16 +08:00
|
|
|
layer_class->opacity_changed = gimp_group_layer_opacity_changed;
|
|
|
|
layer_class->effective_mode_changed = gimp_group_layer_effective_mode_changed;
|
2017-05-09 03:06:00 +08:00
|
|
|
layer_class->excludes_backdrop_changed = gimp_group_layer_excludes_backdrop_changed;
|
2017-12-07 03:06:16 +08:00
|
|
|
layer_class->mask_changed = gimp_group_layer_mask_changed;
|
2017-05-09 03:06:00 +08:00
|
|
|
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;
|
2017-12-07 03:06:16 +08:00
|
|
|
layer_class->get_effective_mode = gimp_group_layer_get_effective_mode;
|
2017-05-09 03:06:00 +08:00
|
|
|
layer_class->get_excludes_backdrop = gimp_group_layer_get_excludes_backdrop;
|
2011-02-04 05:29:25 +08:00
|
|
|
|
|
|
|
g_type_class_add_private (klass, sizeof (GimpGroupLayerPrivate));
|
2017-12-07 03:06:16 +08:00
|
|
|
|
|
|
|
if (g_getenv ("GIMP_NO_PASS_THROUGH_STRENGTH_REDUCTION"))
|
|
|
|
no_pass_through_strength_reduction = TRUE;
|
2009-08-04 05:24:46 +08:00
|
|
|
}
|
|
|
|
|
2009-08-24 22:03:21 +08:00
|
|
|
static void
|
|
|
|
gimp_projectable_iface_init (GimpProjectableInterface *iface)
|
|
|
|
{
|
|
|
|
iface->get_image = (GimpImage * (*) (GimpProjectable *)) gimp_item_get_image;
|
2012-03-30 03:09:37 +08:00
|
|
|
iface->get_format = gimp_group_layer_get_format;
|
2009-08-24 22:03:21 +08:00
|
|
|
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;
|
2017-04-23 02:28:54 +08:00
|
|
|
iface->begin_render = gimp_group_layer_begin_render;
|
|
|
|
iface->end_render = gimp_group_layer_end_render;
|
2009-08-24 22:03:21 +08:00
|
|
|
iface->invalidate_preview = (void (*) (GimpProjectable*)) gimp_viewable_invalidate_preview;
|
|
|
|
}
|
|
|
|
|
2011-08-20 21:01:46 +08:00
|
|
|
static void
|
|
|
|
gimp_pickable_iface_init (GimpPickableInterface *iface)
|
|
|
|
{
|
|
|
|
iface->get_opacity_at = gimp_group_layer_get_opacity_at;
|
|
|
|
}
|
|
|
|
|
2009-08-04 05:24:46 +08:00
|
|
|
static void
|
2009-08-24 06:34:12 +08:00
|
|
|
gimp_group_layer_init (GimpGroupLayer *group)
|
2009-08-04 05:24:46 +08:00
|
|
|
{
|
2011-02-04 05:29:25 +08:00
|
|
|
GimpGroupLayerPrivate *private = GET_PRIVATE (group);
|
2009-08-24 05:00:32 +08:00
|
|
|
|
2017-05-09 04:02:37 +08:00
|
|
|
private->children = gimp_layer_stack_new (GIMP_TYPE_LAYER);
|
2011-09-26 03:57:20 +08:00
|
|
|
private->expanded = TRUE;
|
2011-02-04 05:29:25 +08:00
|
|
|
|
|
|
|
g_signal_connect (private->children, "add",
|
2009-08-24 05:00:32 +08:00
|
|
|
G_CALLBACK (gimp_group_layer_child_add),
|
2009-08-24 06:34:12 +08:00
|
|
|
group);
|
2011-02-04 05:29:25 +08:00
|
|
|
g_signal_connect (private->children, "remove",
|
2009-08-24 05:00:32 +08:00
|
|
|
G_CALLBACK (gimp_group_layer_child_remove),
|
2009-08-24 06:34:12 +08:00
|
|
|
group);
|
2009-08-24 05:00:32 +08:00
|
|
|
|
2011-02-04 05:29:25 +08:00
|
|
|
gimp_container_add_handler (private->children, "notify::offset-x",
|
2009-08-24 05:00:32 +08:00
|
|
|
G_CALLBACK (gimp_group_layer_child_move),
|
2009-08-24 06:34:12 +08:00
|
|
|
group);
|
2011-02-04 05:29:25 +08:00
|
|
|
gimp_container_add_handler (private->children, "notify::offset-y",
|
2009-08-24 05:00:32 +08:00
|
|
|
G_CALLBACK (gimp_group_layer_child_move),
|
2009-08-24 06:34:12 +08:00
|
|
|
group);
|
2011-02-04 05:29:25 +08:00
|
|
|
gimp_container_add_handler (private->children, "size-changed",
|
2009-08-24 05:00:32 +08:00
|
|
|
G_CALLBACK (gimp_group_layer_child_resize),
|
2009-08-24 06:34:12 +08:00
|
|
|
group);
|
app: add GimpFilter::active property; move ::visible to GimpItem
Add an "active" property to GimpFilter, which replaces its
"visible" property. The new property assumes the lower-level role
"visible" had -- controlling whether the filter has any effect as
part of its parent filter-stack.
Add a "visible" property to GimpItem, separate from the "active"
property, which assumes the higher-level role "visible" had --
controlling whether the item is considered "visible", as per the
GUI. By default, the item's "visible" property is bound to the
filter's "active" property, so that changes in visibility directly
affect the filter's "activeness"; this binding can be controlled
using the new gimp_item_bind_visible_to_active() function.
This distinction is currently necessary for floating selections.
Floating selection layers must not be active in their parent stack,
regardless of their visibility, in particular, so that their mode
node doesn't hide the entire backdrop when their composite mode
excludes the backdrop (i.e., when it's dst-atop or src-in).
Instead, their visibility should affect the activeness of the
floating-selection filter of the drawable they're attached to.
This is handled by the next commit.
2017-12-06 02:46:50 +08:00
|
|
|
gimp_container_add_handler (private->children, "active-changed",
|
|
|
|
G_CALLBACK (gimp_group_layer_child_active_changed),
|
2017-05-09 03:06:00 +08:00
|
|
|
group);
|
2017-12-07 03:06:16 +08:00
|
|
|
gimp_container_add_handler (private->children, "effective-mode-changed",
|
|
|
|
G_CALLBACK (gimp_group_layer_child_effective_mode_changed),
|
|
|
|
group);
|
2017-05-09 03:06:00 +08:00
|
|
|
gimp_container_add_handler (private->children, "excludes-backdrop-changed",
|
|
|
|
G_CALLBACK (gimp_group_layer_child_excludes_backdrop_changed),
|
|
|
|
group);
|
2009-08-24 22:03:21 +08:00
|
|
|
|
2011-02-04 05:29:25 +08:00
|
|
|
g_signal_connect (private->children, "update",
|
2009-08-24 22:03:21 +08:00
|
|
|
G_CALLBACK (gimp_group_layer_stack_update),
|
|
|
|
group);
|
|
|
|
|
2011-02-04 05:29:25 +08:00
|
|
|
private->projection = gimp_projection_new (GIMP_PROJECTABLE (group));
|
2017-12-02 22:55:15 +08:00
|
|
|
gimp_projection_set_priority (private->projection, 1);
|
2009-08-24 22:03:21 +08:00
|
|
|
|
2011-02-04 05:29:25 +08:00
|
|
|
g_signal_connect (private->projection, "update",
|
2009-08-24 22:03:21 +08:00
|
|
|
G_CALLBACK (gimp_group_layer_proj_update),
|
|
|
|
group);
|
2009-08-04 05:24:46 +08:00
|
|
|
}
|
|
|
|
|
2011-02-04 05:29:25 +08:00
|
|
|
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);
|
|
|
|
|
2017-07-16 00:38:01 +08:00
|
|
|
g_clear_object (&private->children);
|
2011-02-04 05:29:25 +08:00
|
|
|
}
|
|
|
|
|
2017-07-16 00:38:01 +08:00
|
|
|
g_clear_object (&private->projection);
|
2017-04-22 06:04:49 +08:00
|
|
|
g_clear_object (&private->source_node);
|
2017-07-16 00:38:01 +08:00
|
|
|
g_clear_object (&private->graph);
|
2011-02-04 05:29:25 +08:00
|
|
|
|
|
|
|
G_OBJECT_CLASS (parent_class)->finalize (object);
|
|
|
|
}
|
|
|
|
|
2009-08-24 02:02:36 +08:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-08-04 05:24:46 +08:00
|
|
|
static gint64
|
|
|
|
gimp_group_layer_get_memsize (GimpObject *object,
|
|
|
|
gint64 *gui_size)
|
|
|
|
{
|
2011-02-04 05:29:25 +08:00
|
|
|
GimpGroupLayerPrivate *private = GET_PRIVATE (object);
|
|
|
|
gint64 memsize = 0;
|
2009-08-04 05:24:46 +08:00
|
|
|
|
2011-02-04 05:29:25 +08:00
|
|
|
memsize += gimp_object_get_memsize (GIMP_OBJECT (private->children), gui_size);
|
|
|
|
memsize += gimp_object_get_memsize (GIMP_OBJECT (private->projection), gui_size);
|
2009-08-04 05:24:46 +08:00
|
|
|
|
|
|
|
return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object,
|
|
|
|
gui_size);
|
|
|
|
}
|
|
|
|
|
2017-12-02 22:55:15 +08:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2011-03-09 01:04:23 +08:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2017-01-16 03:58:32 +08:00
|
|
|
/* 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;
|
2011-03-09 01:04:23 +08:00
|
|
|
}
|
|
|
|
|
2009-08-04 05:24:46 +08:00
|
|
|
static GimpContainer *
|
|
|
|
gimp_group_layer_get_children (GimpViewable *viewable)
|
|
|
|
{
|
2011-02-04 05:29:25 +08:00
|
|
|
return GET_PRIVATE (viewable)->children;
|
2011-09-26 03:57:20 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
2017-10-22 23:50:37 +08:00
|
|
|
GimpGroupLayerPrivate *private = GET_PRIVATE (viewable);
|
2011-09-26 03:57:20 +08:00
|
|
|
|
2017-10-22 23:50:37 +08:00
|
|
|
if (private->expanded != expanded)
|
|
|
|
{
|
|
|
|
private->expanded = expanded;
|
|
|
|
|
|
|
|
gimp_viewable_expanded_changed (viewable);
|
|
|
|
}
|
2009-08-04 05:24:46 +08:00
|
|
|
}
|
|
|
|
|
2012-11-09 18:17:25 +08:00
|
|
|
static gboolean
|
2016-05-20 05:51:44 +08:00
|
|
|
gimp_group_layer_is_position_locked (GimpItem *item)
|
2012-11-09 18:17:25 +08:00
|
|
|
{
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2009-08-04 05:24:46 +08:00
|
|
|
static GimpItem *
|
|
|
|
gimp_group_layer_duplicate (GimpItem *item,
|
|
|
|
GType new_type)
|
|
|
|
{
|
|
|
|
GimpItem *new_item;
|
|
|
|
|
2009-09-22 00:41:54 +08:00
|
|
|
g_return_val_if_fail (g_type_is_a (new_type, GIMP_TYPE_DRAWABLE), NULL);
|
2009-08-04 05:24:46 +08:00
|
|
|
|
|
|
|
new_item = GIMP_ITEM_CLASS (parent_class)->duplicate (item, new_type);
|
|
|
|
|
|
|
|
if (GIMP_IS_GROUP_LAYER (new_item))
|
|
|
|
{
|
2011-02-04 05:29:25 +08:00
|
|
|
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;
|
2009-08-04 05:24:46 +08:00
|
|
|
|
2009-09-22 04:36:26 +08:00
|
|
|
gimp_group_layer_suspend_resize (new_group, FALSE);
|
|
|
|
|
2011-02-04 05:29:25 +08:00
|
|
|
for (list = gimp_item_stack_get_item_iter (GIMP_ITEM_STACK (private->children));
|
2009-08-04 05:24:46 +08:00
|
|
|
list;
|
2009-08-27 18:26:14 +08:00
|
|
|
list = g_list_next (list))
|
2009-08-04 05:24:46 +08:00
|
|
|
{
|
2009-08-25 22:05:39 +08:00
|
|
|
GimpItem *child = list->data;
|
|
|
|
GimpItem *new_child;
|
|
|
|
GimpLayerMask *mask;
|
2009-08-04 05:24:46 +08:00
|
|
|
|
|
|
|
new_child = gimp_item_duplicate (child, G_TYPE_FROM_INSTANCE (child));
|
|
|
|
|
2009-08-25 22:05:39 +08:00
|
|
|
gimp_object_set_name (GIMP_OBJECT (new_child),
|
2009-09-01 04:47:18 +08:00
|
|
|
gimp_object_get_name (child));
|
2009-08-25 22:05:39 +08:00
|
|
|
|
|
|
|
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),
|
2009-09-01 04:47:18 +08:00
|
|
|
gimp_object_get_name (mask));
|
2009-08-25 22:05:39 +08:00
|
|
|
}
|
|
|
|
|
2009-08-04 06:14:53 +08:00
|
|
|
gimp_viewable_set_parent (GIMP_VIEWABLE (new_child),
|
2009-08-24 06:34:12 +08:00
|
|
|
GIMP_VIEWABLE (new_group));
|
2009-08-04 06:14:53 +08:00
|
|
|
|
2011-02-04 05:29:25 +08:00
|
|
|
gimp_container_insert (new_private->children,
|
2009-08-04 05:24:46 +08:00
|
|
|
GIMP_OBJECT (new_child),
|
2009-08-27 18:26:14 +08:00
|
|
|
position++);
|
2009-08-04 05:24:46 +08:00
|
|
|
}
|
2009-09-22 04:36:26 +08:00
|
|
|
|
2011-03-09 01:04:23 +08:00
|
|
|
/* force the projection to reallocate itself */
|
|
|
|
GET_PRIVATE (new_group)->reallocate_projection = TRUE;
|
2009-09-22 04:36:26 +08:00
|
|
|
|
|
|
|
gimp_group_layer_resume_resize (new_group, FALSE);
|
2009-08-04 05:24:46 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return new_item;
|
|
|
|
}
|
|
|
|
|
2009-08-25 20:23:42 +08:00
|
|
|
static void
|
|
|
|
gimp_group_layer_convert (GimpItem *item,
|
2015-08-16 21:47:43 +08:00
|
|
|
GimpImage *dest_image,
|
|
|
|
GType old_type)
|
2009-08-25 20:23:42 +08:00
|
|
|
{
|
2011-02-04 05:29:25 +08:00
|
|
|
GimpGroupLayerPrivate *private = GET_PRIVATE (item);
|
|
|
|
GList *list;
|
2009-08-25 20:23:42 +08:00
|
|
|
|
2011-02-04 05:29:25 +08:00
|
|
|
for (list = gimp_item_stack_get_item_iter (GIMP_ITEM_STACK (private->children));
|
2009-08-25 20:23:42 +08:00
|
|
|
list;
|
|
|
|
list = g_list_next (list))
|
|
|
|
{
|
|
|
|
GimpItem *child = list->data;
|
|
|
|
|
2015-08-16 21:47:43 +08:00
|
|
|
GIMP_ITEM_GET_CLASS (child)->convert (child, dest_image,
|
|
|
|
G_TYPE_FROM_INSTANCE (child));
|
2009-08-25 20:23:42 +08:00
|
|
|
}
|
|
|
|
|
2015-08-16 21:47:43 +08:00
|
|
|
GIMP_ITEM_CLASS (parent_class)->convert (item, dest_image, old_type);
|
2009-08-25 20:23:42 +08:00
|
|
|
}
|
|
|
|
|
Bug 51112 - Support layer masks on layer groups
Add layer-mask support for group layers. Group-layer masks work
similarly to ordinary-layer masks, with the following
considerations:
The group's mask size is the same as group's size (i.e., the
bounding box of its children) at all times. When the group's size
changes, the mask is cropped to the new size -- areas of the mask
that fall outside of the new bounds are discarded and their data is
lost (sans undo), and newly added areas are filled with black (and
hence are transparent by default).
The new gimp_group_layer_{suspend,resume}_mask() functions can be
used to modify this behavior. Between the outermost pair of
suspend/resume calls, the old mask data is remembered, and is used
to fill the newly added areas while cropping the mask when the
group is resized. We override GimpItem::{start,end}_move() for
GimpLayer, to call these functions (suspend() in start_move(), and
resume() in end_move()) for each of the layer's ancestors.
As a result, while moving a layer, or a set of layers, atomically,
such as while dragging with the move tool, or moving linked layers,
the ancestors' mask data is not lost, and is only discarded at the
end of the operation.
This commit also takes care of properly handling undo for group-
layer mask crops, properly invalidating the image when the group
layer's mask is shown, and enabling the mask actions for group
layers (obviously :).
2018-02-06 00:19:18 +08:00
|
|
|
static void
|
|
|
|
gimp_group_layer_start_move (GimpItem *item,
|
|
|
|
gboolean push_undo)
|
|
|
|
{
|
|
|
|
GimpGroupLayerPrivate *private = GET_PRIVATE (item);
|
|
|
|
|
2018-02-07 22:48:00 +08:00
|
|
|
g_return_if_fail (private->suspend_mask == 0);
|
|
|
|
|
Bug 51112 - Support layer masks on layer groups
Add layer-mask support for group layers. Group-layer masks work
similarly to ordinary-layer masks, with the following
considerations:
The group's mask size is the same as group's size (i.e., the
bounding box of its children) at all times. When the group's size
changes, the mask is cropped to the new size -- areas of the mask
that fall outside of the new bounds are discarded and their data is
lost (sans undo), and newly added areas are filled with black (and
hence are transparent by default).
The new gimp_group_layer_{suspend,resume}_mask() functions can be
used to modify this behavior. Between the outermost pair of
suspend/resume calls, the old mask data is remembered, and is used
to fill the newly added areas while cropping the mask when the
group is resized. We override GimpItem::{start,end}_move() for
GimpLayer, to call these functions (suspend() in start_move(), and
resume() in end_move()) for each of the layer's ancestors.
As a result, while moving a layer, or a set of layers, atomically,
such as while dragging with the move tool, or moving linked layers,
the ancestors' mask data is not lost, and is only discarded at the
end of the operation.
This commit also takes care of properly handling undo for group-
layer mask crops, properly invalidating the image when the group
layer's mask is shown, and enabling the mask actions for group
layers (obviously :).
2018-02-06 00:19:18 +08:00
|
|
|
private->moving++;
|
|
|
|
|
|
|
|
if (GIMP_ITEM_CLASS (parent_class)->start_move)
|
|
|
|
GIMP_ITEM_CLASS (parent_class)->start_move (item, push_undo);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
gimp_group_layer_end_move (GimpItem *item,
|
|
|
|
gboolean push_undo)
|
|
|
|
{
|
|
|
|
GimpGroupLayerPrivate *private = GET_PRIVATE (item);
|
|
|
|
|
2018-02-07 22:48:00 +08:00
|
|
|
g_return_if_fail (private->suspend_mask == 0);
|
|
|
|
g_return_if_fail (private->moving > 0);
|
|
|
|
|
Bug 51112 - Support layer masks on layer groups
Add layer-mask support for group layers. Group-layer masks work
similarly to ordinary-layer masks, with the following
considerations:
The group's mask size is the same as group's size (i.e., the
bounding box of its children) at all times. When the group's size
changes, the mask is cropped to the new size -- areas of the mask
that fall outside of the new bounds are discarded and their data is
lost (sans undo), and newly added areas are filled with black (and
hence are transparent by default).
The new gimp_group_layer_{suspend,resume}_mask() functions can be
used to modify this behavior. Between the outermost pair of
suspend/resume calls, the old mask data is remembered, and is used
to fill the newly added areas while cropping the mask when the
group is resized. We override GimpItem::{start,end}_move() for
GimpLayer, to call these functions (suspend() in start_move(), and
resume() in end_move()) for each of the layer's ancestors.
As a result, while moving a layer, or a set of layers, atomically,
such as while dragging with the move tool, or moving linked layers,
the ancestors' mask data is not lost, and is only discarded at the
end of the operation.
This commit also takes care of properly handling undo for group-
layer mask crops, properly invalidating the image when the group
layer's mask is shown, and enabling the mask actions for group
layers (obviously :).
2018-02-06 00:19:18 +08:00
|
|
|
if (GIMP_ITEM_CLASS (parent_class)->end_move)
|
|
|
|
GIMP_ITEM_CLASS (parent_class)->end_move (item, push_undo);
|
|
|
|
|
|
|
|
private->moving--;
|
2018-02-07 22:48:00 +08:00
|
|
|
|
|
|
|
if (private->moving == 0)
|
|
|
|
gimp_group_layer_update_mask_size (GIMP_GROUP_LAYER (item));
|
Bug 51112 - Support layer masks on layer groups
Add layer-mask support for group layers. Group-layer masks work
similarly to ordinary-layer masks, with the following
considerations:
The group's mask size is the same as group's size (i.e., the
bounding box of its children) at all times. When the group's size
changes, the mask is cropped to the new size -- areas of the mask
that fall outside of the new bounds are discarded and their data is
lost (sans undo), and newly added areas are filled with black (and
hence are transparent by default).
The new gimp_group_layer_{suspend,resume}_mask() functions can be
used to modify this behavior. Between the outermost pair of
suspend/resume calls, the old mask data is remembered, and is used
to fill the newly added areas while cropping the mask when the
group is resized. We override GimpItem::{start,end}_move() for
GimpLayer, to call these functions (suspend() in start_move(), and
resume() in end_move()) for each of the layer's ancestors.
As a result, while moving a layer, or a set of layers, atomically,
such as while dragging with the move tool, or moving linked layers,
the ancestors' mask data is not lost, and is only discarded at the
end of the operation.
This commit also takes care of properly handling undo for group-
layer mask crops, properly invalidating the image when the group
layer's mask is shown, and enabling the mask actions for group
layers (obviously :).
2018-02-06 00:19:18 +08:00
|
|
|
}
|
|
|
|
|
2018-02-06 03:53:38 +08:00
|
|
|
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
|
|
|
|
* GimpLayer doesn't resize the mask. instead, we temporarily decrement
|
|
|
|
* private->moving, so that mask resizing is handled by
|
|
|
|
* gimp_group_layer_update_size().
|
|
|
|
*/
|
|
|
|
g_return_if_fail (private->moving > 0);
|
|
|
|
private->moving--;
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
private->moving++;
|
|
|
|
}
|
|
|
|
|
2017-06-17 08:15:56 +08:00
|
|
|
static gint64
|
|
|
|
gimp_group_layer_estimate_memsize (GimpDrawable *drawable,
|
|
|
|
GimpComponentType component_type,
|
|
|
|
gint width,
|
|
|
|
gint height)
|
2009-08-25 01:33:29 +08:00
|
|
|
{
|
2017-06-17 08:15:56 +08:00
|
|
|
GimpGroupLayerPrivate *private = GET_PRIVATE (drawable);
|
2011-02-04 05:29:25 +08:00
|
|
|
GList *list;
|
2017-06-17 08:15:56 +08:00
|
|
|
GimpImageBaseType base_type;
|
|
|
|
gint64 memsize = 0;
|
2009-09-07 19:04:55 +08:00
|
|
|
|
2011-02-04 05:29:25 +08:00
|
|
|
for (list = gimp_item_stack_get_item_iter (GIMP_ITEM_STACK (private->children));
|
2009-08-25 01:33:29 +08:00
|
|
|
list;
|
|
|
|
list = g_list_next (list))
|
|
|
|
{
|
2017-06-17 08:15:56 +08:00
|
|
|
GimpDrawable *child = list->data;
|
|
|
|
gint child_width;
|
|
|
|
gint child_height;
|
2009-08-25 01:33:29 +08:00
|
|
|
|
2017-06-17 08:15:56 +08:00
|
|
|
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);
|
2009-08-25 01:33:29 +08:00
|
|
|
}
|
|
|
|
|
2017-06-17 08:15:56 +08:00
|
|
|
base_type = gimp_drawable_get_base_type (drawable);
|
2009-08-25 01:33:29 +08:00
|
|
|
|
2017-06-17 08:15:56 +08:00
|
|
|
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);
|
|
|
|
GList *list;
|
2009-08-25 01:33:29 +08:00
|
|
|
|
2017-06-17 08:15:56 +08:00
|
|
|
/* don't push an undo here because undo will call us again */
|
|
|
|
gimp_group_layer_suspend_resize (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;
|
2009-08-25 01:33:29 +08:00
|
|
|
|
2017-06-17 08:15:56 +08:00
|
|
|
/* don't push an undo here because undo will call us again */
|
|
|
|
gimp_item_translate (child, offset_x, offset_y, FALSE);
|
2009-08-25 01:33:29 +08:00
|
|
|
}
|
2009-09-07 19:04:55 +08:00
|
|
|
|
|
|
|
/* don't push an undo here because undo will call us again */
|
|
|
|
gimp_group_layer_resume_resize (group, FALSE);
|
2009-08-25 01:33:29 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
2017-06-17 08:15:56 +08:00
|
|
|
gimp_group_layer_scale (GimpLayer *layer,
|
2009-08-25 01:33:29 +08:00
|
|
|
gint new_width,
|
|
|
|
gint new_height,
|
|
|
|
gint new_offset_x,
|
|
|
|
gint new_offset_y,
|
|
|
|
GimpInterpolationType interpolation_type,
|
|
|
|
GimpProgress *progress)
|
|
|
|
{
|
2017-06-17 08:15:56 +08:00
|
|
|
GimpGroupLayer *group = GIMP_GROUP_LAYER (layer);
|
|
|
|
GimpGroupLayerPrivate *private = GET_PRIVATE (layer);
|
|
|
|
GimpItem *item = GIMP_ITEM (layer);
|
2011-02-04 05:29:25 +08:00
|
|
|
GList *list;
|
|
|
|
gdouble width_factor;
|
|
|
|
gdouble height_factor;
|
|
|
|
gint old_offset_x;
|
|
|
|
gint old_offset_y;
|
2009-08-25 20:26:26 +08:00
|
|
|
|
|
|
|
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);
|
2009-08-25 01:33:29 +08:00
|
|
|
|
2009-09-07 19:04:55 +08:00
|
|
|
gimp_group_layer_suspend_resize (group, TRUE);
|
|
|
|
|
2011-02-04 05:29:25 +08:00
|
|
|
list = gimp_item_stack_get_item_iter (GIMP_ITEM_STACK (private->children));
|
2009-08-27 17:53:47 +08:00
|
|
|
|
|
|
|
while (list)
|
2009-08-25 01:33:29 +08:00
|
|
|
{
|
|
|
|
GimpItem *child = list->data;
|
2009-08-25 20:26:26 +08:00
|
|
|
gint child_width;
|
|
|
|
gint child_height;
|
|
|
|
gint child_offset_x;
|
|
|
|
gint child_offset_y;
|
|
|
|
|
2009-08-27 17:53:47 +08:00
|
|
|
list = g_list_next (list);
|
|
|
|
|
2009-08-25 20:26:26 +08:00
|
|
|
child_width = ROUND (width_factor * gimp_item_get_width (child));
|
|
|
|
child_height = ROUND (height_factor * gimp_item_get_height (child));
|
|
|
|
child_offset_x = ROUND (width_factor * (gimp_item_get_offset_x (child) -
|
|
|
|
old_offset_x));
|
|
|
|
child_offset_y = ROUND (height_factor * (gimp_item_get_offset_y (child) -
|
|
|
|
old_offset_y));
|
|
|
|
|
|
|
|
child_offset_x += new_offset_x;
|
|
|
|
child_offset_y += new_offset_y;
|
2009-08-25 01:33:29 +08:00
|
|
|
|
2009-08-27 17:53:47 +08:00
|
|
|
if (child_width > 0 && child_height > 0)
|
|
|
|
{
|
|
|
|
gimp_item_scale (child,
|
|
|
|
child_width, child_height,
|
|
|
|
child_offset_x, child_offset_y,
|
|
|
|
interpolation_type, progress);
|
|
|
|
}
|
|
|
|
else if (gimp_item_is_attached (item))
|
|
|
|
{
|
|
|
|
gimp_image_remove_layer (gimp_item_get_image (item),
|
|
|
|
GIMP_LAYER (child),
|
|
|
|
TRUE, NULL);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2011-02-04 05:29:25 +08:00
|
|
|
gimp_container_remove (private->children, GIMP_OBJECT (child));
|
2009-08-27 17:53:47 +08:00
|
|
|
}
|
2009-08-25 01:33:29 +08:00
|
|
|
}
|
|
|
|
|
2009-09-07 19:04:55 +08:00
|
|
|
gimp_group_layer_resume_resize (group, TRUE);
|
2009-08-25 01:33:29 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
2017-06-17 08:15:56 +08:00
|
|
|
gimp_group_layer_flip (GimpLayer *layer,
|
2009-08-25 01:33:29 +08:00
|
|
|
GimpContext *context,
|
|
|
|
GimpOrientationType flip_type,
|
|
|
|
gdouble axis,
|
|
|
|
gboolean clip_result)
|
|
|
|
{
|
2017-06-17 08:15:56 +08:00
|
|
|
GimpGroupLayer *group = GIMP_GROUP_LAYER (layer);
|
|
|
|
GimpGroupLayerPrivate *private = GET_PRIVATE (layer);
|
2011-02-04 05:29:25 +08:00
|
|
|
GList *list;
|
2009-08-25 01:33:29 +08:00
|
|
|
|
2009-09-07 19:04:55 +08:00
|
|
|
gimp_group_layer_suspend_resize (group, TRUE);
|
|
|
|
|
2011-02-04 05:29:25 +08:00
|
|
|
for (list = gimp_item_stack_get_item_iter (GIMP_ITEM_STACK (private->children));
|
2009-08-25 01:33:29 +08:00
|
|
|
list;
|
|
|
|
list = g_list_next (list))
|
|
|
|
{
|
|
|
|
GimpItem *child = list->data;
|
|
|
|
|
|
|
|
gimp_item_flip (child, context,
|
|
|
|
flip_type, axis, clip_result);
|
|
|
|
}
|
|
|
|
|
2009-09-07 19:04:55 +08:00
|
|
|
gimp_group_layer_resume_resize (group, TRUE);
|
2009-08-25 01:33:29 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
2017-06-17 08:15:56 +08:00
|
|
|
gimp_group_layer_rotate (GimpLayer *layer,
|
2009-08-25 01:33:29 +08:00
|
|
|
GimpContext *context,
|
|
|
|
GimpRotationType rotate_type,
|
|
|
|
gdouble center_x,
|
|
|
|
gdouble center_y,
|
|
|
|
gboolean clip_result)
|
|
|
|
{
|
2017-06-17 08:15:56 +08:00
|
|
|
GimpGroupLayer *group = GIMP_GROUP_LAYER (layer);
|
|
|
|
GimpGroupLayerPrivate *private = GET_PRIVATE (layer);
|
2011-02-04 05:29:25 +08:00
|
|
|
GList *list;
|
2009-08-25 01:33:29 +08:00
|
|
|
|
2009-09-07 19:04:55 +08:00
|
|
|
gimp_group_layer_suspend_resize (group, TRUE);
|
|
|
|
|
2011-02-04 05:29:25 +08:00
|
|
|
for (list = gimp_item_stack_get_item_iter (GIMP_ITEM_STACK (private->children));
|
2009-08-25 01:33:29 +08:00
|
|
|
list;
|
|
|
|
list = g_list_next (list))
|
|
|
|
{
|
|
|
|
GimpItem *child = list->data;
|
|
|
|
|
|
|
|
gimp_item_rotate (child, context,
|
|
|
|
rotate_type, center_x, center_y, clip_result);
|
|
|
|
}
|
|
|
|
|
2009-09-07 19:04:55 +08:00
|
|
|
gimp_group_layer_resume_resize (group, TRUE);
|
2009-08-25 01:33:29 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
2017-06-17 08:15:56 +08:00
|
|
|
gimp_group_layer_transform (GimpLayer *layer,
|
2009-08-25 01:33:29 +08:00
|
|
|
GimpContext *context,
|
|
|
|
const GimpMatrix3 *matrix,
|
|
|
|
GimpTransformDirection direction,
|
|
|
|
GimpInterpolationType interpolation_type,
|
|
|
|
GimpTransformResize clip_result,
|
|
|
|
GimpProgress *progress)
|
|
|
|
{
|
2017-06-17 08:15:56 +08:00
|
|
|
GimpGroupLayer *group = GIMP_GROUP_LAYER (layer);
|
|
|
|
GimpGroupLayerPrivate *private = GET_PRIVATE (layer);
|
2011-02-04 05:29:25 +08:00
|
|
|
GList *list;
|
2009-08-25 01:33:29 +08:00
|
|
|
|
2009-09-07 19:04:55 +08:00
|
|
|
gimp_group_layer_suspend_resize (group, TRUE);
|
|
|
|
|
2011-02-04 05:29:25 +08:00
|
|
|
for (list = gimp_item_stack_get_item_iter (GIMP_ITEM_STACK (private->children));
|
2009-08-25 01:33:29 +08:00
|
|
|
list;
|
|
|
|
list = g_list_next (list))
|
|
|
|
{
|
|
|
|
GimpItem *child = list->data;
|
|
|
|
|
|
|
|
gimp_item_transform (child, context,
|
|
|
|
matrix, direction,
|
2013-05-31 07:15:32 +08:00
|
|
|
interpolation_type,
|
2009-08-25 01:33:29 +08:00
|
|
|
clip_result, progress);
|
|
|
|
}
|
|
|
|
|
2009-09-07 19:04:55 +08:00
|
|
|
gimp_group_layer_resume_resize (group, TRUE);
|
2009-08-25 01:33:29 +08:00
|
|
|
}
|
|
|
|
|
2012-03-30 05:19:27 +08:00
|
|
|
static const Babl *
|
|
|
|
get_projection_format (GimpProjectable *projectable,
|
2012-09-29 02:58:40 +08:00
|
|
|
GimpImageBaseType base_type,
|
|
|
|
GimpPrecision precision)
|
2012-03-30 05:19:27 +08:00
|
|
|
{
|
2012-04-22 23:07:35 +08:00
|
|
|
GimpImage *image = gimp_item_get_image (GIMP_ITEM (projectable));
|
|
|
|
|
2012-03-30 05:19:27 +08:00
|
|
|
switch (base_type)
|
|
|
|
{
|
|
|
|
case GIMP_RGB:
|
|
|
|
case GIMP_INDEXED:
|
2012-09-29 02:58:40 +08:00
|
|
|
return gimp_image_get_format (image, GIMP_RGB, precision, TRUE);
|
2012-03-30 05:19:27 +08:00
|
|
|
|
|
|
|
case GIMP_GRAY:
|
2012-09-29 02:58:40 +08:00
|
|
|
return gimp_image_get_format (image, GIMP_GRAY, precision, TRUE);
|
2012-03-30 05:19:27 +08:00
|
|
|
}
|
|
|
|
|
2018-01-22 19:33:39 +08:00
|
|
|
g_return_val_if_reached (NULL);
|
2012-03-30 05:19:27 +08:00
|
|
|
}
|
|
|
|
|
2009-09-12 04:01:31 +08:00
|
|
|
static void
|
2017-06-17 08:15:56 +08:00
|
|
|
gimp_group_layer_convert_type (GimpLayer *layer,
|
2016-11-08 03:41:39 +08:00
|
|
|
GimpImage *dest_image,
|
|
|
|
const Babl *new_format,
|
|
|
|
GimpColorProfile *dest_profile,
|
|
|
|
GeglDitherMethod layer_dither_type,
|
|
|
|
GeglDitherMethod mask_dither_type,
|
|
|
|
gboolean push_undo,
|
|
|
|
GimpProgress *progress)
|
2009-09-12 04:01:31 +08:00
|
|
|
{
|
2017-06-17 08:15:56 +08:00
|
|
|
GimpGroupLayer *group = GIMP_GROUP_LAYER (layer);
|
|
|
|
GimpGroupLayerPrivate *private = GET_PRIVATE (layer);
|
2012-03-22 05:17:54 +08:00
|
|
|
GeglBuffer *buffer;
|
2009-09-12 04:01:31 +08:00
|
|
|
|
2009-09-14 01:24:19 +08:00
|
|
|
if (push_undo)
|
|
|
|
{
|
|
|
|
GimpImage *image = gimp_item_get_image (GIMP_ITEM (group));
|
|
|
|
|
|
|
|
gimp_image_undo_push_group_layer_convert (image, NULL, group);
|
|
|
|
}
|
|
|
|
|
2017-09-19 02:31:21 +08:00
|
|
|
/* 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));
|
|
|
|
|
2012-03-30 05:19:27 +08:00
|
|
|
/* Need to temporarily set the projectable's format to the new
|
|
|
|
* values so the projection will create its tiles with the right
|
|
|
|
* depth
|
2009-09-12 04:01:31 +08:00
|
|
|
*/
|
2016-10-04 07:39:15 +08:00
|
|
|
private->convert_format =
|
2017-06-17 08:15:56 +08:00
|
|
|
get_projection_format (GIMP_PROJECTABLE (group),
|
2016-10-04 07:39:15 +08:00
|
|
|
gimp_babl_format_get_base_type (new_format),
|
|
|
|
gimp_babl_format_get_precision (new_format));
|
2017-06-17 08:15:56 +08:00
|
|
|
gimp_projectable_structure_changed (GIMP_PROJECTABLE (group));
|
2017-12-02 23:15:39 +08:00
|
|
|
gimp_group_layer_flush (group);
|
2009-09-12 04:01:31 +08:00
|
|
|
|
2012-03-30 05:19:27 +08:00
|
|
|
buffer = gimp_pickable_get_buffer (GIMP_PICKABLE (private->projection));
|
2012-03-22 05:17:54 +08:00
|
|
|
|
2017-06-17 08:15:56 +08:00
|
|
|
gimp_drawable_set_buffer_full (GIMP_DRAWABLE (group),
|
2012-03-22 05:17:54 +08:00
|
|
|
FALSE, NULL,
|
2012-03-22 06:37:16 +08:00
|
|
|
buffer,
|
2017-06-17 08:15:56 +08:00
|
|
|
gimp_item_get_offset_x (GIMP_ITEM (group)),
|
|
|
|
gimp_item_get_offset_y (GIMP_ITEM (group)));
|
2012-03-22 05:17:54 +08:00
|
|
|
|
2012-03-30 05:19:27 +08:00
|
|
|
/* reset, the actual format is right now */
|
|
|
|
private->convert_format = NULL;
|
2017-09-19 02:31:21 +08:00
|
|
|
|
|
|
|
private->reallocate_width = 0;
|
|
|
|
private->reallocate_height = 0;
|
2009-09-12 04:01:31 +08:00
|
|
|
}
|
|
|
|
|
2017-04-22 06:04:49 +08:00
|
|
|
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
|
2017-12-07 03:06:16 +08:00
|
|
|
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)
|
2017-04-22 06:04:49 +08:00
|
|
|
{
|
2017-12-03 04:25:04 +08:00
|
|
|
GimpGroupLayer *group = GIMP_GROUP_LAYER (layer);
|
|
|
|
GimpGroupLayerPrivate *private = GET_PRIVATE (layer);
|
2017-12-07 03:06:16 +08:00
|
|
|
GimpLayerMode mode;
|
2017-12-03 04:25:04 +08:00
|
|
|
gboolean pass_through;
|
|
|
|
|
2017-12-07 03:06:16 +08:00
|
|
|
gimp_layer_get_effective_mode (layer, &mode, NULL, NULL, NULL);
|
|
|
|
|
|
|
|
pass_through = (mode == GIMP_LAYER_MODE_PASS_THROUGH);
|
2017-12-03 04:25:04 +08:00
|
|
|
|
|
|
|
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;
|
2017-04-22 06:04:49 +08:00
|
|
|
|
|
|
|
gimp_group_layer_update_source_node (group);
|
2017-05-09 03:06:00 +08:00
|
|
|
gimp_group_layer_update_mode_node (group);
|
2017-04-22 06:04:49 +08:00
|
|
|
|
2017-12-07 03:06:16 +08:00
|
|
|
if (GIMP_LAYER_CLASS (parent_class)->effective_mode_changed)
|
|
|
|
GIMP_LAYER_CLASS (parent_class)->effective_mode_changed (layer);
|
2017-04-22 06:04:49 +08:00
|
|
|
}
|
|
|
|
|
2017-05-09 03:06:00 +08:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2017-12-07 03:06:16 +08:00
|
|
|
static void
|
|
|
|
gimp_group_layer_mask_changed (GimpLayer *layer)
|
|
|
|
{
|
Bug 51112 - Support layer masks on layer groups
Add layer-mask support for group layers. Group-layer masks work
similarly to ordinary-layer masks, with the following
considerations:
The group's mask size is the same as group's size (i.e., the
bounding box of its children) at all times. When the group's size
changes, the mask is cropped to the new size -- areas of the mask
that fall outside of the new bounds are discarded and their data is
lost (sans undo), and newly added areas are filled with black (and
hence are transparent by default).
The new gimp_group_layer_{suspend,resume}_mask() functions can be
used to modify this behavior. Between the outermost pair of
suspend/resume calls, the old mask data is remembered, and is used
to fill the newly added areas while cropping the mask when the
group is resized. We override GimpItem::{start,end}_move() for
GimpLayer, to call these functions (suspend() in start_move(), and
resume() in end_move()) for each of the layer's ancestors.
As a result, while moving a layer, or a set of layers, atomically,
such as while dragging with the move tool, or moving linked layers,
the ancestors' mask data is not lost, and is only discarded at the
end of the operation.
This commit also takes care of properly handling undo for group-
layer mask crops, properly invalidating the image when the group
layer's mask is shown, and enabling the mask actions for group
layers (obviously :).
2018-02-06 00:19:18 +08:00
|
|
|
g_warn_if_fail (GET_PRIVATE (layer)->suspend_mask == 0);
|
|
|
|
|
2017-12-07 03:06:16 +08:00
|
|
|
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 src-over, 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_SRC_OVER)
|
|
|
|
{
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2017-05-09 03:06:00 +08:00
|
|
|
static gboolean
|
|
|
|
gimp_group_layer_get_excludes_backdrop (GimpLayer *layer)
|
|
|
|
{
|
2017-12-03 04:25:04 +08:00
|
|
|
GimpGroupLayerPrivate *private = GET_PRIVATE (layer);
|
|
|
|
|
|
|
|
if (private->pass_through)
|
2017-05-09 03:06:00 +08:00
|
|
|
{
|
2017-12-03 04:25:04 +08:00
|
|
|
GList *list;
|
2017-05-09 03:06:00 +08:00
|
|
|
|
|
|
|
for (list = gimp_item_stack_get_item_iter (GIMP_ITEM_STACK (private->children));
|
|
|
|
list;
|
|
|
|
list = g_list_next (list))
|
|
|
|
{
|
app: add GimpFilter::active property; move ::visible to GimpItem
Add an "active" property to GimpFilter, which replaces its
"visible" property. The new property assumes the lower-level role
"visible" had -- controlling whether the filter has any effect as
part of its parent filter-stack.
Add a "visible" property to GimpItem, separate from the "active"
property, which assumes the higher-level role "visible" had --
controlling whether the item is considered "visible", as per the
GUI. By default, the item's "visible" property is bound to the
filter's "active" property, so that changes in visibility directly
affect the filter's "activeness"; this binding can be controlled
using the new gimp_item_bind_visible_to_active() function.
This distinction is currently necessary for floating selections.
Floating selection layers must not be active in their parent stack,
regardless of their visibility, in particular, so that their mode
node doesn't hide the entire backdrop when their composite mode
excludes the backdrop (i.e., when it's dst-atop or src-in).
Instead, their visibility should affect the activeness of the
floating-selection filter of the drawable they're attached to.
This is handled by the next commit.
2017-12-06 02:46:50 +08:00
|
|
|
GimpFilter *child = list->data;
|
2017-05-09 03:06:00 +08:00
|
|
|
|
app: add GimpFilter::active property; move ::visible to GimpItem
Add an "active" property to GimpFilter, which replaces its
"visible" property. The new property assumes the lower-level role
"visible" had -- controlling whether the filter has any effect as
part of its parent filter-stack.
Add a "visible" property to GimpItem, separate from the "active"
property, which assumes the higher-level role "visible" had --
controlling whether the item is considered "visible", as per the
GUI. By default, the item's "visible" property is bound to the
filter's "active" property, so that changes in visibility directly
affect the filter's "activeness"; this binding can be controlled
using the new gimp_item_bind_visible_to_active() function.
This distinction is currently necessary for floating selections.
Floating selection layers must not be active in their parent stack,
regardless of their visibility, in particular, so that their mode
node doesn't hide the entire backdrop when their composite mode
excludes the backdrop (i.e., when it's dst-atop or src-in).
Instead, their visibility should affect the activeness of the
floating-selection filter of the drawable they're attached to.
This is handled by the next commit.
2017-12-06 02:46:50 +08:00
|
|
|
if (gimp_filter_get_active (child) &&
|
2017-05-09 03:06:00 +08:00
|
|
|
gimp_layer_get_excludes_backdrop (GIMP_LAYER (child)))
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
return GIMP_LAYER_CLASS (parent_class)->get_excludes_backdrop (layer);
|
|
|
|
}
|
|
|
|
|
2012-03-30 03:09:37 +08:00
|
|
|
static const Babl *
|
|
|
|
gimp_group_layer_get_format (GimpProjectable *projectable)
|
|
|
|
{
|
2012-03-30 05:19:27 +08:00
|
|
|
GimpGroupLayerPrivate *private = GET_PRIVATE (projectable);
|
|
|
|
GimpImageBaseType base_type;
|
2012-09-29 02:58:40 +08:00
|
|
|
GimpPrecision precision;
|
2012-03-30 03:09:37 +08:00
|
|
|
|
2012-03-30 05:19:27 +08:00
|
|
|
if (private->convert_format)
|
|
|
|
return private->convert_format;
|
2012-03-30 03:09:37 +08:00
|
|
|
|
2012-04-07 07:51:08 +08:00
|
|
|
base_type = gimp_drawable_get_base_type (GIMP_DRAWABLE (projectable));
|
2012-09-29 02:58:40 +08:00
|
|
|
precision = gimp_drawable_get_precision (GIMP_DRAWABLE (projectable));
|
2012-03-30 03:09:37 +08:00
|
|
|
|
2012-09-29 02:58:40 +08:00
|
|
|
return get_projection_format (projectable, base_type, precision);
|
2012-03-30 03:09:37 +08:00
|
|
|
}
|
|
|
|
|
2009-08-24 22:03:21 +08:00
|
|
|
static GeglNode *
|
|
|
|
gimp_group_layer_get_graph (GimpProjectable *projectable)
|
|
|
|
{
|
2011-02-04 05:29:25 +08:00
|
|
|
GimpGroupLayer *group = GIMP_GROUP_LAYER (projectable);
|
|
|
|
GimpGroupLayerPrivate *private = GET_PRIVATE (projectable);
|
2017-04-22 06:04:49 +08:00
|
|
|
GeglNode *input;
|
2011-02-04 05:29:25 +08:00
|
|
|
GeglNode *layers_node;
|
|
|
|
GeglNode *output;
|
|
|
|
gint off_x;
|
|
|
|
gint off_y;
|
2009-08-25 04:01:16 +08:00
|
|
|
|
2011-02-04 05:29:25 +08:00
|
|
|
if (private->graph)
|
|
|
|
return private->graph;
|
2009-08-25 04:01:16 +08:00
|
|
|
|
2011-02-04 05:29:25 +08:00
|
|
|
private->graph = gegl_node_new ();
|
2009-08-25 04:01:16 +08:00
|
|
|
|
2017-04-22 06:04:49 +08:00
|
|
|
input = gegl_node_get_input_proxy (private->graph, "input");
|
|
|
|
|
2009-08-25 04:01:16 +08:00
|
|
|
layers_node =
|
2013-04-11 10:12:10 +08:00
|
|
|
gimp_filter_stack_get_graph (GIMP_FILTER_STACK (private->children));
|
2009-08-25 04:01:16 +08:00
|
|
|
|
2011-02-04 05:29:25 +08:00
|
|
|
gegl_node_add_child (private->graph, layers_node);
|
2009-08-24 22:03:21 +08:00
|
|
|
|
2017-04-22 06:04:49 +08:00
|
|
|
gegl_node_connect_to (input, "output",
|
|
|
|
layers_node, "input");
|
|
|
|
|
2009-08-25 04:01:16 +08:00
|
|
|
gimp_item_get_offset (GIMP_ITEM (group), &off_x, &off_y);
|
|
|
|
|
2011-02-04 05:29:25 +08:00
|
|
|
private->offset_node = gegl_node_new_child (private->graph,
|
|
|
|
"operation", "gegl:translate",
|
|
|
|
"x", (gdouble) -off_x,
|
|
|
|
"y", (gdouble) -off_y,
|
|
|
|
NULL);
|
2009-08-25 04:01:16 +08:00
|
|
|
|
2011-02-04 05:29:25 +08:00
|
|
|
gegl_node_connect_to (layers_node, "output",
|
|
|
|
private->offset_node, "input");
|
2009-08-25 04:01:16 +08:00
|
|
|
|
2011-02-04 05:29:25 +08:00
|
|
|
output = gegl_node_get_output_proxy (private->graph, "output");
|
2009-08-25 04:01:16 +08:00
|
|
|
|
2011-02-04 05:29:25 +08:00
|
|
|
gegl_node_connect_to (private->offset_node, "output",
|
|
|
|
output, "input");
|
2009-08-25 04:01:16 +08:00
|
|
|
|
2011-02-04 05:29:25 +08:00
|
|
|
return private->graph;
|
2009-08-24 22:03:21 +08:00
|
|
|
}
|
|
|
|
|
2017-04-23 02:28:54 +08:00
|
|
|
static void
|
|
|
|
gimp_group_layer_begin_render (GimpProjectable *projectable)
|
|
|
|
{
|
|
|
|
GimpGroupLayerPrivate *private = GET_PRIVATE (projectable);
|
|
|
|
|
|
|
|
if (private->source_node == NULL)
|
|
|
|
return;
|
|
|
|
|
2017-12-03 04:25:04 +08:00
|
|
|
if (private->pass_through)
|
|
|
|
gegl_node_disconnect (private->graph, "input");
|
2017-04-23 02:28:54 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
gimp_group_layer_end_render (GimpProjectable *projectable)
|
|
|
|
{
|
|
|
|
GimpGroupLayerPrivate *private = GET_PRIVATE (projectable);
|
|
|
|
|
|
|
|
if (private->source_node == NULL)
|
|
|
|
return;
|
|
|
|
|
2017-12-03 04:25:04 +08:00
|
|
|
if (private->pass_through)
|
2017-04-23 02:28:54 +08:00
|
|
|
{
|
|
|
|
GeglNode *input;
|
|
|
|
|
|
|
|
input = gegl_node_get_input_proxy (private->source_node, "input");
|
|
|
|
|
|
|
|
gegl_node_connect_to (input, "output",
|
|
|
|
private->graph, "input");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-04-21 22:05:49 +08:00
|
|
|
static gdouble
|
2011-08-20 21:01:46 +08:00
|
|
|
gimp_group_layer_get_opacity_at (GimpPickable *pickable,
|
|
|
|
gint x,
|
|
|
|
gint y)
|
|
|
|
{
|
|
|
|
/* Only consider child layers as having content */
|
2012-04-21 22:05:49 +08:00
|
|
|
|
|
|
|
return GIMP_OPACITY_TRANSPARENT;
|
2011-08-20 21:01:46 +08:00
|
|
|
}
|
|
|
|
|
2009-08-24 22:03:21 +08:00
|
|
|
|
|
|
|
/* public functions */
|
|
|
|
|
2009-08-04 05:24:46 +08:00
|
|
|
GimpLayer *
|
|
|
|
gimp_group_layer_new (GimpImage *image)
|
|
|
|
{
|
2009-08-24 06:34:12 +08:00
|
|
|
GimpGroupLayer *group;
|
2012-04-07 06:46:59 +08:00
|
|
|
const Babl *format;
|
2009-08-04 05:24:46 +08:00
|
|
|
|
|
|
|
g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
|
|
|
|
|
2012-04-07 06:46:59 +08:00
|
|
|
format = gimp_image_get_layer_format (image, TRUE);
|
2009-08-04 05:24:46 +08:00
|
|
|
|
2011-02-01 19:47:24 +08:00
|
|
|
group = GIMP_GROUP_LAYER (gimp_drawable_new (GIMP_TYPE_GROUP_LAYER,
|
|
|
|
image, NULL,
|
|
|
|
0, 0, 1, 1,
|
2012-04-07 06:46:59 +08:00
|
|
|
format));
|
2009-08-04 05:24:46 +08:00
|
|
|
|
2009-08-24 06:34:12 +08:00
|
|
|
return GIMP_LAYER (group);
|
2009-08-04 05:24:46 +08:00
|
|
|
}
|
2009-08-24 05:00:32 +08:00
|
|
|
|
2011-02-04 05:29:25 +08:00
|
|
|
GimpProjection *
|
|
|
|
gimp_group_layer_get_projection (GimpGroupLayer *group)
|
|
|
|
{
|
|
|
|
g_return_val_if_fail (GIMP_IS_GROUP_LAYER (group), NULL);
|
|
|
|
|
|
|
|
return GET_PRIVATE (group)->projection;
|
|
|
|
}
|
|
|
|
|
2009-09-07 19:04:55 +08:00
|
|
|
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)
|
Bug 51112 - Support layer masks on layer groups
Add layer-mask support for group layers. Group-layer masks work
similarly to ordinary-layer masks, with the following
considerations:
The group's mask size is the same as group's size (i.e., the
bounding box of its children) at all times. When the group's size
changes, the mask is cropped to the new size -- areas of the mask
that fall outside of the new bounds are discarded and their data is
lost (sans undo), and newly added areas are filled with black (and
hence are transparent by default).
The new gimp_group_layer_{suspend,resume}_mask() functions can be
used to modify this behavior. Between the outermost pair of
suspend/resume calls, the old mask data is remembered, and is used
to fill the newly added areas while cropping the mask when the
group is resized. We override GimpItem::{start,end}_move() for
GimpLayer, to call these functions (suspend() in start_move(), and
resume() in end_move()) for each of the layer's ancestors.
As a result, while moving a layer, or a set of layers, atomically,
such as while dragging with the move tool, or moving linked layers,
the ancestors' mask data is not lost, and is only discarded at the
end of the operation.
This commit also takes care of properly handling undo for group-
layer mask crops, properly invalidating the image when the group
layer's mask is shown, and enabling the mask actions for group
layers (obviously :).
2018-02-06 00:19:18 +08:00
|
|
|
gimp_image_undo_push_group_layer_suspend_resize (gimp_item_get_image (item),
|
|
|
|
NULL, group);
|
2009-09-07 19:04:55 +08:00
|
|
|
|
2011-02-04 05:29:25 +08:00
|
|
|
GET_PRIVATE (group)->suspend_resize++;
|
2009-09-07 19:04:55 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
gimp_group_layer_resume_resize (GimpGroupLayer *group,
|
|
|
|
gboolean push_undo)
|
|
|
|
{
|
2011-02-04 05:29:25 +08:00
|
|
|
GimpGroupLayerPrivate *private;
|
|
|
|
GimpItem *item;
|
2018-02-06 03:53:38 +08:00
|
|
|
GimpItem *mask = NULL;
|
|
|
|
GeglBuffer *mask_buffer;
|
|
|
|
GeglRectangle mask_bounds;
|
|
|
|
GimpUndo *undo;
|
2009-09-07 19:04:55 +08:00
|
|
|
|
|
|
|
g_return_if_fail (GIMP_IS_GROUP_LAYER (group));
|
2011-02-04 05:29:25 +08:00
|
|
|
|
|
|
|
private = GET_PRIVATE (group);
|
|
|
|
|
|
|
|
g_return_if_fail (private->suspend_resize > 0);
|
2009-09-07 19:04:55 +08:00
|
|
|
|
|
|
|
item = GIMP_ITEM (group);
|
|
|
|
|
|
|
|
if (! gimp_item_is_attached (item))
|
|
|
|
push_undo = FALSE;
|
|
|
|
|
|
|
|
if (push_undo)
|
2018-02-06 03:53:38 +08:00
|
|
|
{
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2009-09-07 19:04:55 +08:00
|
|
|
|
2011-02-04 05:29:25 +08:00
|
|
|
private->suspend_resize--;
|
2009-09-07 19:04:55 +08:00
|
|
|
|
2011-02-04 05:29:25 +08:00
|
|
|
if (private->suspend_resize == 0)
|
2009-09-07 19:04:55 +08:00
|
|
|
{
|
|
|
|
gimp_group_layer_update_size (group);
|
2018-02-06 03:53:38 +08:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
2009-09-07 19:04:55 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Bug 51112 - Support layer masks on layer groups
Add layer-mask support for group layers. Group-layer masks work
similarly to ordinary-layer masks, with the following
considerations:
The group's mask size is the same as group's size (i.e., the
bounding box of its children) at all times. When the group's size
changes, the mask is cropped to the new size -- areas of the mask
that fall outside of the new bounds are discarded and their data is
lost (sans undo), and newly added areas are filled with black (and
hence are transparent by default).
The new gimp_group_layer_{suspend,resume}_mask() functions can be
used to modify this behavior. Between the outermost pair of
suspend/resume calls, the old mask data is remembered, and is used
to fill the newly added areas while cropping the mask when the
group is resized. We override GimpItem::{start,end}_move() for
GimpLayer, to call these functions (suspend() in start_move(), and
resume() in end_move()) for each of the layer's ancestors.
As a result, while moving a layer, or a set of layers, atomically,
such as while dragging with the move tool, or moving linked layers,
the ancestors' mask data is not lost, and is only discarded at the
end of the operation.
This commit also takes care of properly handling undo for group-
layer mask crops, properly invalidating the image when the group
layer's mask is shown, and enabling the mask actions for group
layers (obviously :).
2018-02-06 00:19:18 +08:00
|
|
|
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);
|
|
|
|
|
|
|
|
if (! gimp_item_is_attached (item))
|
|
|
|
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);
|
|
|
|
|
|
|
|
if (! gimp_item_is_attached (item))
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2009-08-24 05:00:32 +08:00
|
|
|
|
|
|
|
/* private functions */
|
|
|
|
|
|
|
|
static void
|
|
|
|
gimp_group_layer_child_add (GimpContainer *container,
|
|
|
|
GimpLayer *child,
|
|
|
|
GimpGroupLayer *group)
|
|
|
|
{
|
2009-09-07 19:04:55 +08:00
|
|
|
gimp_group_layer_update (group);
|
2017-05-09 03:06:00 +08:00
|
|
|
|
2017-12-07 03:06:16 +08:00
|
|
|
if (gimp_filter_get_active (GIMP_FILTER (child)))
|
2017-05-09 03:06:00 +08:00
|
|
|
{
|
2017-12-07 03:06:16 +08:00
|
|
|
gimp_layer_update_effective_mode (GIMP_LAYER (group));
|
|
|
|
|
|
|
|
if (gimp_layer_get_excludes_backdrop (child))
|
|
|
|
gimp_layer_update_excludes_backdrop (GIMP_LAYER (group));
|
2017-05-09 03:06:00 +08:00
|
|
|
}
|
2009-08-24 05:00:32 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
gimp_group_layer_child_remove (GimpContainer *container,
|
|
|
|
GimpLayer *child,
|
|
|
|
GimpGroupLayer *group)
|
|
|
|
{
|
2009-09-07 19:04:55 +08:00
|
|
|
gimp_group_layer_update (group);
|
2017-05-09 03:06:00 +08:00
|
|
|
|
2017-12-07 03:06:16 +08:00
|
|
|
if (gimp_filter_get_active (GIMP_FILTER (child)))
|
2017-05-09 03:06:00 +08:00
|
|
|
{
|
2017-12-07 03:06:16 +08:00
|
|
|
gimp_layer_update_effective_mode (GIMP_LAYER (group));
|
|
|
|
|
|
|
|
if (gimp_layer_get_excludes_backdrop (child))
|
|
|
|
gimp_layer_update_excludes_backdrop (GIMP_LAYER (group));
|
2017-05-09 03:06:00 +08:00
|
|
|
}
|
2009-08-24 05:00:32 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
gimp_group_layer_child_move (GimpLayer *child,
|
|
|
|
GParamSpec *pspec,
|
|
|
|
GimpGroupLayer *group)
|
|
|
|
{
|
2009-09-07 19:04:55 +08:00
|
|
|
gimp_group_layer_update (group);
|
2009-08-24 05:00:32 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
gimp_group_layer_child_resize (GimpLayer *child,
|
|
|
|
GimpGroupLayer *group)
|
|
|
|
{
|
2009-09-07 19:04:55 +08:00
|
|
|
gimp_group_layer_update (group);
|
|
|
|
}
|
|
|
|
|
2017-05-09 03:06:00 +08:00
|
|
|
static void
|
app: add GimpFilter::active property; move ::visible to GimpItem
Add an "active" property to GimpFilter, which replaces its
"visible" property. The new property assumes the lower-level role
"visible" had -- controlling whether the filter has any effect as
part of its parent filter-stack.
Add a "visible" property to GimpItem, separate from the "active"
property, which assumes the higher-level role "visible" had --
controlling whether the item is considered "visible", as per the
GUI. By default, the item's "visible" property is bound to the
filter's "active" property, so that changes in visibility directly
affect the filter's "activeness"; this binding can be controlled
using the new gimp_item_bind_visible_to_active() function.
This distinction is currently necessary for floating selections.
Floating selection layers must not be active in their parent stack,
regardless of their visibility, in particular, so that their mode
node doesn't hide the entire backdrop when their composite mode
excludes the backdrop (i.e., when it's dst-atop or src-in).
Instead, their visibility should affect the activeness of the
floating-selection filter of the drawable they're attached to.
This is handled by the next commit.
2017-12-06 02:46:50 +08:00
|
|
|
gimp_group_layer_child_active_changed (GimpLayer *child,
|
|
|
|
GimpGroupLayer *group)
|
2017-05-09 03:06:00 +08:00
|
|
|
{
|
2017-12-07 03:06:16 +08:00
|
|
|
gimp_layer_update_effective_mode (GIMP_LAYER (group));
|
|
|
|
|
2017-05-09 03:06:00 +08:00
|
|
|
if (gimp_layer_get_excludes_backdrop (child))
|
|
|
|
gimp_layer_update_excludes_backdrop (GIMP_LAYER (group));
|
|
|
|
}
|
|
|
|
|
2017-12-07 03:06:16 +08:00
|
|
|
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));
|
|
|
|
}
|
|
|
|
|
2017-05-09 03:06:00 +08:00
|
|
|
static void
|
|
|
|
gimp_group_layer_child_excludes_backdrop_changed (GimpLayer *child,
|
|
|
|
GimpGroupLayer *group)
|
|
|
|
{
|
app: add GimpFilter::active property; move ::visible to GimpItem
Add an "active" property to GimpFilter, which replaces its
"visible" property. The new property assumes the lower-level role
"visible" had -- controlling whether the filter has any effect as
part of its parent filter-stack.
Add a "visible" property to GimpItem, separate from the "active"
property, which assumes the higher-level role "visible" had --
controlling whether the item is considered "visible", as per the
GUI. By default, the item's "visible" property is bound to the
filter's "active" property, so that changes in visibility directly
affect the filter's "activeness"; this binding can be controlled
using the new gimp_item_bind_visible_to_active() function.
This distinction is currently necessary for floating selections.
Floating selection layers must not be active in their parent stack,
regardless of their visibility, in particular, so that their mode
node doesn't hide the entire backdrop when their composite mode
excludes the backdrop (i.e., when it's dst-atop or src-in).
Instead, their visibility should affect the activeness of the
floating-selection filter of the drawable they're attached to.
This is handled by the next commit.
2017-12-06 02:46:50 +08:00
|
|
|
if (gimp_filter_get_active (GIMP_FILTER (child)))
|
2017-05-09 03:06:00 +08:00
|
|
|
gimp_layer_update_excludes_backdrop (GIMP_LAYER (group));
|
|
|
|
}
|
|
|
|
|
2017-12-02 23:15:39 +08:00
|
|
|
static void
|
|
|
|
gimp_group_layer_flush (GimpGroupLayer *group)
|
|
|
|
{
|
2017-12-03 04:25:04 +08:00
|
|
|
GimpGroupLayerPrivate *private = GET_PRIVATE (group);
|
|
|
|
|
|
|
|
if (private->pass_through)
|
2017-12-02 23:15:39 +08:00
|
|
|
{
|
|
|
|
/* 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
|
|
|
|
{
|
|
|
|
/* 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)
|
|
|
|
*/
|
2017-12-03 04:25:04 +08:00
|
|
|
gimp_pickable_flush (GIMP_PICKABLE (private->projection));
|
2017-12-02 23:15:39 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-09-07 19:04:55 +08:00
|
|
|
static void
|
|
|
|
gimp_group_layer_update (GimpGroupLayer *group)
|
|
|
|
{
|
2011-02-04 05:29:25 +08:00
|
|
|
if (GET_PRIVATE (group)->suspend_resize == 0)
|
2009-09-07 19:04:55 +08:00
|
|
|
{
|
|
|
|
gimp_group_layer_update_size (group);
|
|
|
|
}
|
2009-08-24 05:00:32 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
2009-08-24 06:34:12 +08:00
|
|
|
gimp_group_layer_update_size (GimpGroupLayer *group)
|
2009-08-24 05:00:32 +08:00
|
|
|
{
|
2011-02-04 05:29:25 +08:00
|
|
|
GimpGroupLayerPrivate *private = GET_PRIVATE (group);
|
|
|
|
GimpItem *item = GIMP_ITEM (group);
|
2018-02-07 22:48:00 +08:00
|
|
|
GimpLayer *layer = GIMP_LAYER (group);
|
|
|
|
GimpItem *mask = GIMP_ITEM (gimp_layer_get_mask (layer));
|
2011-02-04 05:29:25 +08:00
|
|
|
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;
|
Bug 51112 - Support layer masks on layer groups
Add layer-mask support for group layers. Group-layer masks work
similarly to ordinary-layer masks, with the following
considerations:
The group's mask size is the same as group's size (i.e., the
bounding box of its children) at all times. When the group's size
changes, the mask is cropped to the new size -- areas of the mask
that fall outside of the new bounds are discarded and their data is
lost (sans undo), and newly added areas are filled with black (and
hence are transparent by default).
The new gimp_group_layer_{suspend,resume}_mask() functions can be
used to modify this behavior. Between the outermost pair of
suspend/resume calls, the old mask data is remembered, and is used
to fill the newly added areas while cropping the mask when the
group is resized. We override GimpItem::{start,end}_move() for
GimpLayer, to call these functions (suspend() in start_move(), and
resume() in end_move()) for each of the layer's ancestors.
As a result, while moving a layer, or a set of layers, atomically,
such as while dragging with the move tool, or moving linked layers,
the ancestors' mask data is not lost, and is only discarded at the
end of the operation.
This commit also takes care of properly handling undo for group-
layer mask crops, properly invalidating the image when the group
layer's mask is shown, and enabling the mask actions for group
layers (obviously :).
2018-02-06 00:19:18 +08:00
|
|
|
gboolean size_changed;
|
2018-02-07 22:48:00 +08:00
|
|
|
gboolean resize_mask;
|
2011-02-04 05:29:25 +08:00
|
|
|
GList *list;
|
|
|
|
|
|
|
|
for (list = gimp_item_stack_get_item_iter (GIMP_ITEM_STACK (private->children));
|
2009-08-24 05:00:32 +08:00
|
|
|
list;
|
|
|
|
list = g_list_next (list))
|
|
|
|
{
|
|
|
|
GimpItem *child = list->data;
|
2017-01-16 03:58:32 +08:00
|
|
|
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;
|
|
|
|
}
|
2009-08-24 05:00:32 +08:00
|
|
|
|
|
|
|
if (first)
|
|
|
|
{
|
|
|
|
x = gimp_item_get_offset_x (child);
|
|
|
|
y = gimp_item_get_offset_y (child);
|
2017-01-16 03:58:32 +08:00
|
|
|
width = child_width;
|
|
|
|
height = child_height;
|
2009-08-24 05:00:32 +08:00
|
|
|
|
|
|
|
first = FALSE;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
gimp_rectangle_union (x, y, width, height,
|
|
|
|
gimp_item_get_offset_x (child),
|
|
|
|
gimp_item_get_offset_y (child),
|
2017-01-16 03:58:32 +08:00
|
|
|
child_width,
|
|
|
|
child_height,
|
2009-08-24 05:00:32 +08:00
|
|
|
&x, &y, &width, &height);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Bug 51112 - Support layer masks on layer groups
Add layer-mask support for group layers. Group-layer masks work
similarly to ordinary-layer masks, with the following
considerations:
The group's mask size is the same as group's size (i.e., the
bounding box of its children) at all times. When the group's size
changes, the mask is cropped to the new size -- areas of the mask
that fall outside of the new bounds are discarded and their data is
lost (sans undo), and newly added areas are filled with black (and
hence are transparent by default).
The new gimp_group_layer_{suspend,resume}_mask() functions can be
used to modify this behavior. Between the outermost pair of
suspend/resume calls, the old mask data is remembered, and is used
to fill the newly added areas while cropping the mask when the
group is resized. We override GimpItem::{start,end}_move() for
GimpLayer, to call these functions (suspend() in start_move(), and
resume() in end_move()) for each of the layer's ancestors.
As a result, while moving a layer, or a set of layers, atomically,
such as while dragging with the move tool, or moving linked layers,
the ancestors' mask data is not lost, and is only discarded at the
end of the operation.
This commit also takes care of properly handling undo for group-
layer mask crops, properly invalidating the image when the group
layer's mask is shown, and enabling the mask actions for group
layers (obviously :).
2018-02-06 00:19:18 +08:00
|
|
|
size_changed = (x != old_x ||
|
|
|
|
y != old_y ||
|
|
|
|
width != old_width ||
|
|
|
|
height != old_height);
|
|
|
|
|
2018-02-07 22:48:00 +08:00
|
|
|
resize_mask = mask && size_changed;
|
Bug 51112 - Support layer masks on layer groups
Add layer-mask support for group layers. Group-layer masks work
similarly to ordinary-layer masks, with the following
considerations:
The group's mask size is the same as group's size (i.e., the
bounding box of its children) at all times. When the group's size
changes, the mask is cropped to the new size -- areas of the mask
that fall outside of the new bounds are discarded and their data is
lost (sans undo), and newly added areas are filled with black (and
hence are transparent by default).
The new gimp_group_layer_{suspend,resume}_mask() functions can be
used to modify this behavior. Between the outermost pair of
suspend/resume calls, the old mask data is remembered, and is used
to fill the newly added areas while cropping the mask when the
group is resized. We override GimpItem::{start,end}_move() for
GimpLayer, to call these functions (suspend() in start_move(), and
resume() in end_move()) for each of the layer's ancestors.
As a result, while moving a layer, or a set of layers, atomically,
such as while dragging with the move tool, or moving linked layers,
the ancestors' mask data is not lost, and is only discarded at the
end of the operation.
This commit also takes care of properly handling undo for group-
layer mask crops, properly invalidating the image when the group
layer's mask is shown, and enabling the mask actions for group
layers (obviously :).
2018-02-06 00:19:18 +08:00
|
|
|
|
|
|
|
/* if we show the mask, invalidate the old mask area */
|
2018-02-07 22:48:00 +08:00
|
|
|
if (resize_mask && gimp_layer_get_show_mask (layer))
|
Bug 51112 - Support layer masks on layer groups
Add layer-mask support for group layers. Group-layer masks work
similarly to ordinary-layer masks, with the following
considerations:
The group's mask size is the same as group's size (i.e., the
bounding box of its children) at all times. When the group's size
changes, the mask is cropped to the new size -- areas of the mask
that fall outside of the new bounds are discarded and their data is
lost (sans undo), and newly added areas are filled with black (and
hence are transparent by default).
The new gimp_group_layer_{suspend,resume}_mask() functions can be
used to modify this behavior. Between the outermost pair of
suspend/resume calls, the old mask data is remembered, and is used
to fill the newly added areas while cropping the mask when the
group is resized. We override GimpItem::{start,end}_move() for
GimpLayer, to call these functions (suspend() in start_move(), and
resume() in end_move()) for each of the layer's ancestors.
As a result, while moving a layer, or a set of layers, atomically,
such as while dragging with the move tool, or moving linked layers,
the ancestors' mask data is not lost, and is only discarded at the
end of the operation.
This commit also takes care of properly handling undo for group-
layer mask crops, properly invalidating the image when the group
layer's mask is shown, and enabling the mask actions for group
layers (obviously :).
2018-02-06 00:19:18 +08:00
|
|
|
{
|
|
|
|
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)
|
2009-08-24 05:00:32 +08:00
|
|
|
{
|
2017-11-25 01:21:42 +08:00
|
|
|
/* 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.
|
2014-05-23 02:46:13 +08:00
|
|
|
*/
|
|
|
|
if (private->offset_node)
|
|
|
|
gegl_node_set (private->offset_node,
|
|
|
|
"x", (gdouble) -x,
|
|
|
|
"y", (gdouble) -y,
|
|
|
|
NULL);
|
|
|
|
|
2017-12-02 23:15:39 +08:00
|
|
|
/* update our offset *before* calling gimp_pickable_get_buffer(), so
|
2017-11-25 01:21:42 +08:00
|
|
|
* 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);
|
|
|
|
|
2011-03-09 01:04:23 +08:00
|
|
|
if (private->reallocate_projection ||
|
|
|
|
width != old_width ||
|
2009-08-28 05:07:38 +08:00
|
|
|
height != old_height)
|
2009-08-24 05:00:32 +08:00
|
|
|
{
|
2012-03-30 05:19:27 +08:00
|
|
|
GeglBuffer *buffer;
|
2009-08-24 05:00:32 +08:00
|
|
|
|
2011-03-09 01:04:23 +08:00
|
|
|
private->reallocate_projection = FALSE;
|
|
|
|
|
|
|
|
/* temporarily change the return values of gimp_viewable_get_size()
|
|
|
|
* so the projection allocates itself correctly
|
2009-08-28 05:07:38 +08:00
|
|
|
*/
|
2011-03-09 01:04:23 +08:00
|
|
|
private->reallocate_width = width;
|
|
|
|
private->reallocate_height = height;
|
2009-08-24 22:03:21 +08:00
|
|
|
|
|
|
|
gimp_projectable_structure_changed (GIMP_PROJECTABLE (group));
|
2017-12-02 23:15:39 +08:00
|
|
|
gimp_group_layer_flush (group);
|
2009-08-24 22:03:21 +08:00
|
|
|
|
2012-03-30 05:19:27 +08:00
|
|
|
buffer = gimp_pickable_get_buffer (GIMP_PICKABLE (private->projection));
|
2012-03-22 05:17:54 +08:00
|
|
|
|
|
|
|
gimp_drawable_set_buffer_full (GIMP_DRAWABLE (group),
|
|
|
|
FALSE, NULL,
|
|
|
|
buffer,
|
|
|
|
x, y);
|
|
|
|
|
2012-03-30 05:19:27 +08:00
|
|
|
/* reset, the actual size is correct now */
|
|
|
|
private->reallocate_width = 0;
|
|
|
|
private->reallocate_height = 0;
|
2009-08-24 05:00:32 +08:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2009-09-23 03:39:32 +08:00
|
|
|
/* invalidate the entire projection since the position of
|
2009-09-03 03:37:07 +08:00
|
|
|
* the children relative to each other might have changed
|
|
|
|
* in a way that happens to leave the group's width and
|
|
|
|
* height the same
|
|
|
|
*/
|
|
|
|
gimp_projectable_invalidate (GIMP_PROJECTABLE (group),
|
|
|
|
x, y, width, height);
|
2015-06-23 16:24:25 +08:00
|
|
|
|
2017-12-02 23:15:39 +08:00
|
|
|
gimp_group_layer_flush (group);
|
2009-08-24 05:00:32 +08:00
|
|
|
}
|
|
|
|
}
|
Bug 51112 - Support layer masks on layer groups
Add layer-mask support for group layers. Group-layer masks work
similarly to ordinary-layer masks, with the following
considerations:
The group's mask size is the same as group's size (i.e., the
bounding box of its children) at all times. When the group's size
changes, the mask is cropped to the new size -- areas of the mask
that fall outside of the new bounds are discarded and their data is
lost (sans undo), and newly added areas are filled with black (and
hence are transparent by default).
The new gimp_group_layer_{suspend,resume}_mask() functions can be
used to modify this behavior. Between the outermost pair of
suspend/resume calls, the old mask data is remembered, and is used
to fill the newly added areas while cropping the mask when the
group is resized. We override GimpItem::{start,end}_move() for
GimpLayer, to call these functions (suspend() in start_move(), and
resume() in end_move()) for each of the layer's ancestors.
As a result, while moving a layer, or a set of layers, atomically,
such as while dragging with the move tool, or moving linked layers,
the ancestors' mask data is not lost, and is only discarded at the
end of the operation.
This commit also takes care of properly handling undo for group-
layer mask crops, properly invalidating the image when the group
layer's mask is shown, and enabling the mask actions for group
layers (obviously :).
2018-02-06 00:19:18 +08:00
|
|
|
|
2018-02-07 22:48:00 +08:00
|
|
|
/* resize the mask if not moving (in which case, GimpLayer takes care of the
|
|
|
|
* mask)
|
|
|
|
*/
|
|
|
|
if (resize_mask && ! private->moving)
|
|
|
|
gimp_group_layer_update_mask_size (group);
|
|
|
|
|
Bug 51112 - Support layer masks on layer groups
Add layer-mask support for group layers. Group-layer masks work
similarly to ordinary-layer masks, with the following
considerations:
The group's mask size is the same as group's size (i.e., the
bounding box of its children) at all times. When the group's size
changes, the mask is cropped to the new size -- areas of the mask
that fall outside of the new bounds are discarded and their data is
lost (sans undo), and newly added areas are filled with black (and
hence are transparent by default).
The new gimp_group_layer_{suspend,resume}_mask() functions can be
used to modify this behavior. Between the outermost pair of
suspend/resume calls, the old mask data is remembered, and is used
to fill the newly added areas while cropping the mask when the
group is resized. We override GimpItem::{start,end}_move() for
GimpLayer, to call these functions (suspend() in start_move(), and
resume() in end_move()) for each of the layer's ancestors.
As a result, while moving a layer, or a set of layers, atomically,
such as while dragging with the move tool, or moving linked layers,
the ancestors' mask data is not lost, and is only discarded at the
end of the operation.
This commit also takes care of properly handling undo for group-
layer mask crops, properly invalidating the image when the group
layer's mask is shown, and enabling the mask actions for group
layers (obviously :).
2018-02-06 00:19:18 +08:00
|
|
|
/* if we show the mask, invalidate the new mask area */
|
2018-02-07 22:48:00 +08:00
|
|
|
if (resize_mask && gimp_layer_get_show_mask (layer))
|
Bug 51112 - Support layer masks on layer groups
Add layer-mask support for group layers. Group-layer masks work
similarly to ordinary-layer masks, with the following
considerations:
The group's mask size is the same as group's size (i.e., the
bounding box of its children) at all times. When the group's size
changes, the mask is cropped to the new size -- areas of the mask
that fall outside of the new bounds are discarded and their data is
lost (sans undo), and newly added areas are filled with black (and
hence are transparent by default).
The new gimp_group_layer_{suspend,resume}_mask() functions can be
used to modify this behavior. Between the outermost pair of
suspend/resume calls, the old mask data is remembered, and is used
to fill the newly added areas while cropping the mask when the
group is resized. We override GimpItem::{start,end}_move() for
GimpLayer, to call these functions (suspend() in start_move(), and
resume() in end_move()) for each of the layer's ancestors.
As a result, while moving a layer, or a set of layers, atomically,
such as while dragging with the move tool, or moving linked layers,
the ancestors' mask data is not lost, and is only discarded at the
end of the operation.
This commit also takes care of properly handling undo for group-
layer mask crops, properly invalidating the image when the group
layer's mask is shown, and enabling the mask actions for group
layers (obviously :).
2018-02-06 00:19:18 +08:00
|
|
|
{
|
|
|
|
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));
|
|
|
|
}
|
2009-08-24 05:00:32 +08:00
|
|
|
}
|
2009-08-24 22:03:21 +08:00
|
|
|
|
2018-02-07 22:48:00 +08:00
|
|
|
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,
|
|
|
|
©_bounds.x,
|
|
|
|
©_bounds.y,
|
|
|
|
©_bounds.width,
|
|
|
|
©_bounds.height);
|
|
|
|
|
|
|
|
if (intersect)
|
|
|
|
{
|
|
|
|
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);
|
|
|
|
|
|
|
|
g_object_unref (buffer);
|
|
|
|
}
|
|
|
|
|
2017-04-22 06:04:49 +08:00
|
|
|
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");
|
|
|
|
|
2017-12-03 04:25:04 +08:00
|
|
|
if (private->pass_through)
|
2017-04-22 06:04:49 +08:00
|
|
|
{
|
|
|
|
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");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-09 03:06:00 +08:00
|
|
|
static void
|
|
|
|
gimp_group_layer_update_mode_node (GimpGroupLayer *group)
|
|
|
|
{
|
2017-12-03 04:25:04 +08:00
|
|
|
GimpGroupLayerPrivate *private = GET_PRIVATE (group);
|
|
|
|
GeglNode *node;
|
|
|
|
GeglNode *input;
|
|
|
|
GeglNode *mode_node;
|
2017-05-09 03:06:00 +08:00
|
|
|
|
|
|
|
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));
|
|
|
|
|
2017-12-03 04:25:04 +08:00
|
|
|
if (private->pass_through &&
|
2017-05-09 03:06:00 +08:00
|
|
|
gimp_layer_get_excludes_backdrop (GIMP_LAYER (group)))
|
|
|
|
{
|
|
|
|
gegl_node_disconnect (mode_node, "input");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
gegl_node_connect_to (input, "output",
|
|
|
|
mode_node, "input");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-08-24 22:03:21 +08:00
|
|
|
static void
|
|
|
|
gimp_group_layer_stack_update (GimpDrawableStack *stack,
|
|
|
|
gint x,
|
|
|
|
gint y,
|
|
|
|
gint width,
|
|
|
|
gint height,
|
|
|
|
GimpGroupLayer *group)
|
|
|
|
{
|
2017-12-03 04:25:04 +08:00
|
|
|
GimpGroupLayerPrivate *private = GET_PRIVATE (group);
|
|
|
|
|
2009-08-26 19:06:55 +08:00
|
|
|
#if 0
|
|
|
|
g_printerr ("%s (%s) %d, %d (%d, %d)\n",
|
2009-09-01 04:47:18 +08:00
|
|
|
G_STRFUNC, gimp_object_get_name (group),
|
2009-08-26 19:06:55 +08:00
|
|
|
x, y, width, height);
|
|
|
|
#endif
|
|
|
|
|
2017-12-02 23:15:39 +08:00
|
|
|
/* 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);
|
|
|
|
|
2017-12-03 04:25:04 +08:00
|
|
|
if (private->pass_through)
|
2017-04-22 06:04:49 +08:00
|
|
|
{
|
|
|
|
/* 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);
|
|
|
|
}
|
2009-08-24 22:03:21 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
gimp_group_layer_proj_update (GimpProjection *proj,
|
|
|
|
gboolean now,
|
|
|
|
gint x,
|
|
|
|
gint y,
|
|
|
|
gint width,
|
|
|
|
gint height,
|
|
|
|
GimpGroupLayer *group)
|
|
|
|
{
|
2017-12-03 04:25:04 +08:00
|
|
|
GimpGroupLayerPrivate *private = GET_PRIVATE (group);
|
|
|
|
|
2009-08-26 19:06:55 +08:00
|
|
|
#if 0
|
|
|
|
g_printerr ("%s (%s) %d, %d (%d, %d)\n",
|
2009-09-01 04:47:18 +08:00
|
|
|
G_STRFUNC, gimp_object_get_name (group),
|
2009-08-26 19:06:55 +08:00
|
|
|
x, y, width, height);
|
|
|
|
#endif
|
|
|
|
|
2017-12-03 04:25:04 +08:00
|
|
|
if (! private->pass_through)
|
2017-04-22 06:04:49 +08:00
|
|
|
{
|
|
|
|
/* 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);
|
|
|
|
}
|
2009-08-24 22:03:21 +08:00
|
|
|
}
|