Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  gimpgrouplayer.c   Sprache: C

 

/* GIMP - The GNU Image Manipulation Program
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * GimpGroupLayer
 * Copyright (C) 2009  Michael Natterer <mitch@gimp.org>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */


#include "config.h"

#include <string.h>

#include <gdk-pixbuf/gdk-pixbuf.h>
#include <gegl.h>

#include "libgimpbase/gimpbase.h"
#include "libgimpmath/gimpmath.h"

#include "core-types.h"

#include "gegl/gimp-babl.h"
#include "gegl/gimp-gegl-loops.h"

#include "gimpdrawable-filters.h"
#include "gimpgrouplayer.h"
#include "gimpgrouplayerundo.h"
#include "gimpimage.h"
#include "gimpimage-undo.h"
#include "gimpimage-undo-push.h"
#include "gimplayerstack.h"
#include "gimpobjectqueue.h"
#include "gimppickable.h"
#include "gimpprogress.h"
#include "gimpprojectable.h"
#include "gimpprojection.h"

#include "gimp-intl.h"


typedef struct _GimpGroupLayerPrivate GimpGroupLayerPrivate;

struct _GimpGroupLayerPrivate
{
  GimpContainer  *children;
  GimpProjection *projection;
  GeglNode       *source_node;
  GeglNode       *parent_source_node;
  GeglNode       *graph;
  GeglNode       *offset_node;
  GeglRectangle   bounding_box;
  gint            suspend_resize;
  gint            suspend_mask;
  GeglBuffer     *suspended_mask_buffer;
  GeglRectangle   suspended_mask_bounds;
  gint            direct_update;
  gint            transforming;
  gboolean        expanded;
  gboolean        pass_through;

  /*  hackish temp states to make the projection/tiles stuff work  */
  const Babl     *convert_format;
  gboolean        reallocate_projection;
};

#define GET_PRIVATE(item) \
  ((GimpGroupLayerPrivate *) gimp_group_layer_get_instance_private ((GimpGroupLayer *) (item)))


static void            gimp_projectable_iface_init   (GimpProjectableInterface  *iface);
static void            gimp_pickable_iface_init      (GimpPickableInterface     *iface);

static void            gimp_group_layer_finalize     (GObject         *object);
static void            gimp_group_layer_set_property (GObject         *object,
                                                      guint            property_id,
                                                      const GValue    *value,
                                                      GParamSpec      *pspec);
static void            gimp_group_layer_get_property (GObject         *object,
                                                      guint            property_id,
                                                      GValue          *value,
                                                      GParamSpec      *pspec);

static gint64          gimp_group_layer_get_memsize  (GimpObject      *object,
                                                      gint64          *gui_size);

static void        gimp_group_layer_ancestry_changed (GimpViewable    *viewable);
static gboolean        gimp_group_layer_get_size     (GimpViewable    *viewable,
                                                      gint            *width,
                                                      gint            *height);
static GimpContainer * gimp_group_layer_get_children (GimpViewable    *viewable);
static gboolean        gimp_group_layer_get_expanded (GimpViewable    *viewable);
static void            gimp_group_layer_set_expanded (GimpViewable    *viewable,
                                                      gboolean         expanded);

static gboolean  gimp_group_layer_is_position_locked (GimpItem        *item,
                                                      GimpItem       **locked_item,
                                                      gboolean         check_children);
static GimpItem      * gimp_group_layer_duplicate    (GimpItem        *item,
                                                      GType            new_type);
static void            gimp_group_layer_convert      (GimpItem        *item,
                                                      GimpImage       *dest_image,
                                                      GType            old_type);
static void         gimp_group_layer_start_transform (GimpItem        *item,
                                                      gboolean         push_undo);
static void           gimp_group_layer_end_transform (GimpItem        *item,
                                                      gboolean         push_undo);
static void            gimp_group_layer_resize       (GimpItem        *item,
                                                      GimpContext     *context,
                                                      GimpFillType     fill_type,
                                                      gint             new_width,
                                                      gint             new_height,
                                                      gint             offset_x,
                                                      gint             offset_y);
static GimpTransformResize
                       gimp_group_layer_get_clip     (GimpItem        *item,
                                                      GimpTransformResize clip_result);

static gint64      gimp_group_layer_estimate_memsize (GimpDrawable      *drawable,
                                                      GimpComponentType  component_type,
                                                      gint               width,
                                                      gint               height);
static void            gimp_group_layer_update_all   (GimpDrawable      *drawable);

static void            gimp_group_layer_translate    (GimpLayer       *layer,
                                                      gint             offset_x,
                                                      gint             offset_y);
static void            gimp_group_layer_scale        (GimpLayer       *layer,
                                                      gint             new_width,
                                                      gint             new_height,
                                                      gint             new_offset_x,
                                                      gint             new_offset_y,
                                                      GimpInterpolationType  interp_type,
                                                      GimpProgress    *progress);
static void            gimp_group_layer_flip         (GimpLayer       *layer,
                                                      GimpContext     *context,
                                                      GimpOrientationType flip_type,
                                                      gdouble          axis,
                                                      gboolean         clip_result);
static void            gimp_group_layer_rotate       (GimpLayer       *layer,
                                                      GimpContext     *context,
                                                      GimpRotationType rotate_type,
                                                      gdouble          center_x,
                                                      gdouble          center_y,
                                                      gboolean         clip_result);
static void            gimp_group_layer_transform    (GimpLayer       *layer,
                                                      GimpContext     *context,
                                                      const GimpMatrix3 *matrix,
                                                      GimpTransformDirection direction,
                                                      GimpInterpolationType  interpolation_type,
                                                      GimpTransformResize clip_result,
                                                      GimpProgress      *progress,
                                                      gboolean           push_undo);
static void            gimp_group_layer_convert_type (GimpLayer         *layer,
                                                      GimpImage         *dest_image,
                                                      const Babl        *new_format,
                                                      GimpColorProfile  *src_profile,
                                                      GimpColorProfile  *dest_profile,
                                                      GeglDitherMethod   layer_dither_type,
                                                      GeglDitherMethod   mask_dither_type,
                                                      gboolean           push_undo,
                                                      GimpProgress      *progress);
static GeglNode   * gimp_group_layer_get_source_node (GimpDrawable      *drawable);

static void         gimp_group_layer_filters_changed (GimpDrawable      *drawable);

static void         gimp_group_layer_opacity_changed (GimpLayer         *layer);
static void  gimp_group_layer_effective_mode_changed (GimpLayer         *layer);
static void
          gimp_group_layer_excludes_backdrop_changed (GimpLayer         *layer);
static GeglRectangle
                   gimp_group_layer_get_bounding_box (GimpLayer         *layer);
static void      gimp_group_layer_get_effective_mode (GimpLayer         *layer,
                                                      GimpLayerMode          *mode,
                                                      GimpLayerColorSpace    *blend_space,
                                                      GimpLayerColorSpace    *composite_space,
                                                      GimpLayerCompositeMode *composite_mode);
static gboolean
              gimp_group_layer_get_excludes_backdrop (GimpLayer         *layer);

static const Babl    * gimp_group_layer_get_format   (GimpProjectable *projectable);
static GeglRectangle
       gimp_group_layer_projectable_get_bounding_box (GimpProjectable *projectable);
static GeglNode      * gimp_group_layer_get_graph    (GimpProjectable *projectable);
static void            gimp_group_layer_begin_render (GimpProjectable *projectable);
static void            gimp_group_layer_end_render   (GimpProjectable *projectable);

static void          gimp_group_layer_pickable_flush (GimpPickable    *pickable);
static gdouble       gimp_group_layer_get_opacity_at (GimpPickable    *pickable,
                                                      gint             x,
                                                      gint             y);


static void            gimp_group_layer_child_add    (GimpContainer   *container,
                                                      GimpLayer       *child,
                                                      GimpGroupLayer  *group);
static void            gimp_group_layer_child_remove (GimpContainer   *container,
                                                      GimpLayer       *child,
                                                      GimpGroupLayer  *group);
static void            gimp_group_layer_child_move   (GimpLayer       *child,
                                                      GParamSpec      *pspec,
                                                      GimpGroupLayer  *group);
static void            gimp_group_layer_child_resize (GimpLayer       *child,
                                                      GimpGroupLayer  *group);
static void    gimp_group_layer_child_active_changed (GimpLayer       *child,
                                                      GimpGroupLayer  *group);
static void
       gimp_group_layer_child_effective_mode_changed (GimpLayer       *child,
                                                      GimpGroupLayer  *group);
static void
    gimp_group_layer_child_excludes_backdrop_changed (GimpLayer       *child,
                                                      GimpGroupLayer  *group);

static void            gimp_group_layer_flush        (GimpGroupLayer  *group);
static void            gimp_group_layer_update       (GimpGroupLayer  *group);
static void            gimp_group_layer_update_size  (GimpGroupLayer  *group);
static void        gimp_group_layer_update_mask_size (GimpGroupLayer  *group);
static void      gimp_group_layer_update_source_node (GimpGroupLayer  *group);
static void        gimp_group_layer_update_mode_node (GimpGroupLayer  *group);

static void            gimp_group_layer_stack_update (GimpDrawableStack *stack,
                                                      gint               x,
                                                      gint               y,
                                                      gint               width,
                                                      gint               height,
                                                      GimpGroupLayer    *group);
static void            gimp_group_layer_proj_update  (GimpProjection    *proj,
                                                      gboolean           now,
                                                      gint               x,
                                                      gint               y,
                                                      gint               width,
                                                      gint               height,
                                                      GimpGroupLayer    *group);


G_DEFINE_TYPE_WITH_CODE (GimpGroupLayer, gimp_group_layer, GIMP_TYPE_LAYER,
                         G_ADD_PRIVATE (GimpGroupLayer)
                         G_IMPLEMENT_INTERFACE (GIMP_TYPE_PROJECTABLE,
                                                gimp_projectable_iface_init)
                         G_IMPLEMENT_INTERFACE (GIMP_TYPE_PICKABLE,
                                                gimp_pickable_iface_init))


#define parent_class gimp_group_layer_parent_class


/* disable pass-through groups strength-reduction to normal groups.
 * see gimp_group_layer_get_effective_mode().
 */

static gboolean no_pass_through_strength_reduction = FALSE;


static void
gimp_group_layer_class_init (GimpGroupLayerClass *klass)
{
  GObjectClass      *object_class      = G_OBJECT_CLASS (klass);
  GimpObjectClass   *gimp_object_class = GIMP_OBJECT_CLASS (klass);
  GimpViewableClass *viewable_class    = GIMP_VIEWABLE_CLASS (klass);
  GimpItemClass     *item_class        = GIMP_ITEM_CLASS (klass);
  GimpDrawableClass *drawable_class    = GIMP_DRAWABLE_CLASS (klass);
  GimpLayerClass    *layer_class       = GIMP_LAYER_CLASS (klass);

  object_class->set_property             = gimp_group_layer_set_property;
  object_class->get_property             = gimp_group_layer_get_property;
  object_class->finalize                 = gimp_group_layer_finalize;

  gimp_object_class->get_memsize         = gimp_group_layer_get_memsize;

  viewable_class->default_icon_name      = "gimp-group-layer";
  viewable_class->default_name           = _("Layer Group");
  viewable_class->ancestry_changed       = gimp_group_layer_ancestry_changed;
  viewable_class->get_size               = gimp_group_layer_get_size;
  viewable_class->get_children           = gimp_group_layer_get_children;
  viewable_class->set_expanded           = gimp_group_layer_set_expanded;
  viewable_class->get_expanded           = gimp_group_layer_get_expanded;

  item_class->is_position_locked         = gimp_group_layer_is_position_locked;
  item_class->duplicate                  = gimp_group_layer_duplicate;
  item_class->convert                    = gimp_group_layer_convert;
  item_class->start_transform            = gimp_group_layer_start_transform;
  item_class->end_transform              = gimp_group_layer_end_transform;
  item_class->resize                     = gimp_group_layer_resize;
  item_class->get_clip                   = gimp_group_layer_get_clip;

  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->update_all             = gimp_group_layer_update_all;
  drawable_class->get_source_node        = gimp_group_layer_get_source_node;
  drawable_class->filters_changed        = gimp_group_layer_filters_changed;

  layer_class->opacity_changed           = gimp_group_layer_opacity_changed;
  layer_class->effective_mode_changed    = gimp_group_layer_effective_mode_changed;
  layer_class->excludes_backdrop_changed = gimp_group_layer_excludes_backdrop_changed;
  layer_class->translate                 = gimp_group_layer_translate;
  layer_class->scale                     = gimp_group_layer_scale;
  layer_class->flip                      = gimp_group_layer_flip;
  layer_class->rotate                    = gimp_group_layer_rotate;
  layer_class->transform                 = gimp_group_layer_transform;
  layer_class->convert_type              = gimp_group_layer_convert_type;
  layer_class->get_bounding_box          = gimp_group_layer_get_bounding_box;
  layer_class->get_effective_mode        = gimp_group_layer_get_effective_mode;
  layer_class->get_excludes_backdrop     = gimp_group_layer_get_excludes_backdrop;

  if (g_getenv ("GIMP_NO_PASS_THROUGH_STRENGTH_REDUCTION"))
    no_pass_through_strength_reduction = TRUE;
}

static void
gimp_projectable_iface_init (GimpProjectableInterface *iface)
{
  iface->get_image          = (GimpImage * (*) (GimpProjectable *)) gimp_item_get_image;
  iface->get_format         = gimp_group_layer_get_format;
  iface->get_offset         = (void (*) (GimpProjectable*, gint*, gint*)) gimp_item_get_offset;
  iface->get_bounding_box   = gimp_group_layer_projectable_get_bounding_box;
  iface->get_graph          = gimp_group_layer_get_graph;
  iface->begin_render       = gimp_group_layer_begin_render;
  iface->end_render         = gimp_group_layer_end_render;
  iface->invalidate_preview = (void (*) (GimpProjectable*)) gimp_viewable_invalidate_preview;
}

static void
gimp_pickable_iface_init (GimpPickableInterface *iface)
{
  iface->flush          = gimp_group_layer_pickable_flush;
  iface->get_opacity_at = gimp_group_layer_get_opacity_at;
}

static void
gimp_group_layer_init (GimpGroupLayer *group)
{
  GimpGroupLayerPrivate *private = GET_PRIVATE (group);

  private->children = gimp_layer_stack_new (GIMP_TYPE_LAYER);
  private->expanded = TRUE;

  g_signal_connect (private->children, "add",
                    G_CALLBACK (gimp_group_layer_child_add),
                    group);
  g_signal_connect (private->children, "remove",
                    G_CALLBACK (gimp_group_layer_child_remove),
                    group);

  gimp_container_add_handler (private->children, "notify::offset-x",
                              G_CALLBACK (gimp_group_layer_child_move),
                              group);
  gimp_container_add_handler (private->children, "notify::offset-y",
                              G_CALLBACK (gimp_group_layer_child_move),
                              group);
  gimp_container_add_handler (private->children, "size-changed",
                              G_CALLBACK (gimp_group_layer_child_resize),
                              group);
  gimp_container_add_handler (private->children, "bounding-box-changed",
                              G_CALLBACK (gimp_group_layer_child_resize),
                              group);
  gimp_container_add_handler (private->children, "active-changed",
                              G_CALLBACK (gimp_group_layer_child_active_changed),
                              group);
  gimp_container_add_handler (private->children, "effective-mode-changed",
                              G_CALLBACK (gimp_group_layer_child_effective_mode_changed),
                              group);
  gimp_container_add_handler (private->children, "excludes-backdrop-changed",
                              G_CALLBACK (gimp_group_layer_child_excludes_backdrop_changed),
                              group);

  g_signal_connect (private->children, "update",
                    G_CALLBACK (gimp_group_layer_stack_update),
                    group);

  private->projection = gimp_projection_new (GIMP_PROJECTABLE (group));
  gimp_projection_set_priority (private->projection, 1);

  g_signal_connect (private->projection, "update",
                    G_CALLBACK (gimp_group_layer_proj_update),
                    group);
}

static void
gimp_group_layer_finalize (GObject *object)
{
  GimpGroupLayerPrivate *private = GET_PRIVATE (object);

  if (private->children)
    {
      g_signal_handlers_disconnect_by_func (private->children,
                                            gimp_group_layer_child_add,
                                            object);
      g_signal_handlers_disconnect_by_func (private->children,
                                            gimp_group_layer_child_remove,
                                            object);
      g_signal_handlers_disconnect_by_func (private->children,
                                            gimp_group_layer_stack_update,
                                            object);

      /* this is particularly important to avoid reallocating the projection
       * in response to a "bounding-box-changed" signal, which can be emitted
       * during layer destruction.  see issue #4584.
       */

      gimp_container_remove_handlers_by_data (private->children, object);

      g_clear_object (&private->children);
    }

  g_clear_object (&private->projection);
  g_clear_object (&private->source_node);
  g_clear_object (&private->graph);

  G_OBJECT_CLASS (parent_class)->finalize (object);
}

static void
gimp_group_layer_set_property (GObject      *object,
                               guint         property_id,
                               const GValue *value,
                               GParamSpec   *pspec)
{
  switch (property_id)
    {
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

static void
gimp_group_layer_get_property (GObject    *object,
                               guint       property_id,
                               GValue     *value,
                               GParamSpec *pspec)
{
  switch (property_id)
    {
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

static gint64
gimp_group_layer_get_memsize (GimpObject *object,
                              gint64     *gui_size)
{
  GimpGroupLayerPrivate *private = GET_PRIVATE (object);
  gint64                 memsize = 0;

  memsize += gimp_object_get_memsize (GIMP_OBJECT (private->children), gui_size);
  memsize += gimp_object_get_memsize (GIMP_OBJECT (private->projection), gui_size);

  return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object,
                                                                  gui_size);
}

static void
gimp_group_layer_ancestry_changed (GimpViewable *viewable)
{
  GimpGroupLayerPrivate *private = GET_PRIVATE (viewable);

  gimp_projection_set_priority (private->projection,
                                gimp_viewable_get_depth (viewable) + 1);

  GIMP_VIEWABLE_CLASS (parent_class)->ancestry_changed (viewable);
}

static gboolean
gimp_group_layer_get_size (GimpViewable *viewable,
                           gint         *width,
                           gint         *height)
{
  GimpGroupLayerPrivate *private = GET_PRIVATE (viewable);

  /*  return the size only if there are children ...  */
  if (! gimp_container_is_empty (private->children))
    {
      return GIMP_VIEWABLE_CLASS (parent_class)->get_size (viewable,
                                                           width, height);
    }

  /*  ... otherwise, return "no content"  */
  return FALSE;
}

static GimpContainer *
gimp_group_layer_get_children (GimpViewable *viewable)
{
  return GET_PRIVATE (viewable)->children;
}

static gboolean
gimp_group_layer_get_expanded (GimpViewable *viewable)
{
  GimpGroupLayer *group = GIMP_GROUP_LAYER (viewable);

  return GET_PRIVATE (group)->expanded;
}

static void
gimp_group_layer_set_expanded (GimpViewable *viewable,
                               gboolean      expanded)
{
  GimpGroupLayerPrivate *private = GET_PRIVATE (viewable);

  if (private->expanded != expanded)
    {
      private->expanded = expanded;

      gimp_viewable_expanded_changed (viewable);
    }
}

static gboolean
gimp_group_layer_is_position_locked (GimpItem  *item,
                                     GimpItem **locked_item,
                                     gboolean   check_children)
{
  /* Lock position is particular because a locked child locks the group
   * too.
   */

  if (check_children)
    {
      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_get_lock_position (child))
            {
              if (locked_item)
                *locked_item = child;

              return TRUE;
            }
          else if (GIMP_IS_GROUP_LAYER (child) &&
                   gimp_group_layer_is_position_locked (child,
                                                        locked_item,
                                                        TRUE))
            {
              return TRUE;
            }
        }
    }

  /* And a locked parent locks the group too! Which is handled by parent
   * implementation of the method.
   */

  return GIMP_ITEM_CLASS (parent_class)->is_position_locked (item,
                                                             locked_item,
                                                             FALSE);
}

static GimpItem *
gimp_group_layer_duplicate (GimpItem *item,
                            GType     new_type)
{
  GimpItem *new_item;

  g_return_val_if_fail (g_type_is_a (new_type, GIMP_TYPE_DRAWABLE), NULL);

  new_item = GIMP_ITEM_CLASS (parent_class)->duplicate (item, new_type);

  if (GIMP_IS_GROUP_LAYER (new_item))
    {
      GimpGroupLayerPrivate *private     = GET_PRIVATE (item);
      GimpGroupLayer        *new_group   = GIMP_GROUP_LAYER (new_item);
      GimpGroupLayerPrivate *new_private = GET_PRIVATE (new_item);
      gint                   position    = 0;
      GList                 *list;

      gimp_group_layer_suspend_resize (new_group, FALSE);

      for (list = gimp_item_stack_get_item_iter (GIMP_ITEM_STACK (private->children));
           list;
           list = g_list_next (list))
        {
          GimpItem      *child = list->data;
          GimpItem      *new_child;
          GimpLayerMask *mask;

          new_child = gimp_item_duplicate (child, G_TYPE_FROM_INSTANCE (child));

          gimp_object_set_name (GIMP_OBJECT (new_child),
                                gimp_object_get_name (child));

          mask = gimp_layer_get_mask (GIMP_LAYER (child));

          if (mask)
            {
              GimpLayerMask *new_mask;

              new_mask = gimp_layer_get_mask (GIMP_LAYER (new_child));

              gimp_object_set_name (GIMP_OBJECT (new_mask),
                                    gimp_object_get_name (mask));
            }

          gimp_viewable_set_parent (GIMP_VIEWABLE (new_child),
                                    GIMP_VIEWABLE (new_group));

          gimp_container_insert (new_private->children,
                                 GIMP_OBJECT (new_child),
                                 position++);
        }

      /*  force the projection to reallocate itself  */
      GET_PRIVATE (new_group)->reallocate_projection = TRUE;

      gimp_group_layer_resume_resize (new_group, FALSE);
    }

  return new_item;
}

static void
gimp_group_layer_convert (GimpItem  *item,
                          GimpImage *dest_image,
                          GType      old_type)
{
  GimpGroupLayerPrivate *private = GET_PRIVATE (item);
  GList                 *list;

  for (list = gimp_item_stack_get_item_iter (GIMP_ITEM_STACK (private->children));
       list;
       list = g_list_next (list))
    {
      GimpItem *child = list->data;

      GIMP_ITEM_GET_CLASS (child)->convert (child, dest_image,
                                            G_TYPE_FROM_INSTANCE (child));
    }

  GIMP_ITEM_CLASS (parent_class)->convert (item, dest_image, old_type);
}

static void
gimp_group_layer_start_transform (GimpItem *item,
                             gboolean  push_undo)
{
  _gimp_group_layer_start_transform (GIMP_GROUP_LAYER (item), push_undo);

  if (GIMP_ITEM_CLASS (parent_class)->start_transform)
    GIMP_ITEM_CLASS (parent_class)->start_transform (item, push_undo);
}

static void
gimp_group_layer_end_transform (GimpItem *item,
                           gboolean  push_undo)
{
  if (GIMP_ITEM_CLASS (parent_class)->end_transform)
    GIMP_ITEM_CLASS (parent_class)->end_transform (item, push_undo);

  _gimp_group_layer_end_transform (GIMP_GROUP_LAYER (item), push_undo);
}

static void
gimp_group_layer_resize (GimpItem     *item,
                         GimpContext  *context,
                         GimpFillType  fill_type,
                         gint          new_width,
                         gint          new_height,
                         gint          offset_x,
                         gint          offset_y)
{
  GimpGroupLayer        *group   = GIMP_GROUP_LAYER (item);
  GimpGroupLayerPrivate *private = GET_PRIVATE (item);
  GList                 *list;
  gint                   x, y;

  /* we implement GimpItem::resize(), instead of GimpLayer::resize(), so that
   * GimpLayer doesn't resize the mask.  note that gimp_item_resize() calls
   * gimp_item_{start,end}_move(), and not gimp_item_{start,end}_transform(),
   * so that mask resizing is handled by gimp_group_layer_update_size().
   */


  x = gimp_item_get_offset_x (item) - offset_x;
  y = gimp_item_get_offset_y (item) - offset_y;

  gimp_group_layer_suspend_resize (group, TRUE);

  list = gimp_item_stack_get_item_iter (GIMP_ITEM_STACK (private->children));

  while (list)
    {
      GimpItem *child = list->data;
      gint      child_width;
      gint      child_height;
      gint      child_x;
      gint      child_y;

      list = g_list_next (list);

      if (gimp_rectangle_intersect (x,
                                    y,
                                    new_width,
                                    new_height,
                                    gimp_item_get_offset_x (child),
                                    gimp_item_get_offset_y (child),
                                    gimp_item_get_width  (child),
                                    gimp_item_get_height (child),
                                    &child_x,
                                    &child_y,
                                    &child_width,
                                    &child_height))
        {
          gint child_offset_x = gimp_item_get_offset_x (child) - child_x;
          gint child_offset_y = gimp_item_get_offset_y (child) - child_y;

          gimp_item_resize (child, context, fill_type,
                            child_width, child_height,
                            child_offset_x, child_offset_y);
        }
      else if (gimp_item_is_attached (item))
        {
          gimp_image_remove_layer (gimp_item_get_image (item),
                                   GIMP_LAYER (child),
                                   TRUE, NULL);
        }
      else
        {
          gimp_container_remove (private->children, GIMP_OBJECT (child));
        }
    }

  gimp_group_layer_resume_resize (group, TRUE);
}

static GimpTransformResize
gimp_group_layer_get_clip (GimpItem            *item,
                           GimpTransformResize  clip_result)
{
  /* TODO: add clipping support, by clipping all sublayers as a unit, instead
   * of individually.
   */

  return GIMP_TRANSFORM_RESIZE_ADJUST;
}

static gint64
gimp_group_layer_estimate_memsize (GimpDrawable      *drawable,
                                   GimpComponentType  component_type,
                                   gint               width,
                                   gint               height)
{
  GimpGroupLayerPrivate *private = GET_PRIVATE (drawable);
  GList                 *list;
  GimpImageBaseType      base_type;
  gint64                 memsize = 0;

  for (list = gimp_item_stack_get_item_iter (GIMP_ITEM_STACK (private->children));
       list;
       list = g_list_next (list))
    {
      GimpDrawable *child = list->data;
      gint          child_width;
      gint          child_height;

      child_width  = (gimp_item_get_width (GIMP_ITEM (child)) *
                      width /
                      gimp_item_get_width (GIMP_ITEM (drawable)));
      child_height = (gimp_item_get_height (GIMP_ITEM (child)) *
                      height /
                      gimp_item_get_height (GIMP_ITEM (drawable)));

      memsize += gimp_drawable_estimate_memsize (child,
                                                 component_type,
                                                 child_width,
                                                 child_height);
    }

  base_type = gimp_drawable_get_base_type (drawable);

  memsize += gimp_projection_estimate_memsize (base_type, component_type,
                                               width, height);

  return memsize +
         GIMP_DRAWABLE_CLASS (parent_class)->estimate_memsize (drawable,
                                                               component_type,
                                                               width, height);
}

static void
gimp_group_layer_update_all (GimpDrawable *drawable)
{
  GimpGroupLayerPrivate *private = GET_PRIVATE (drawable);
  GList                 *list;

  /*  redirect stack updates to the drawable, rather than to the projection  */
  private->direct_update++;

  for (list = gimp_item_stack_get_item_iter (GIMP_ITEM_STACK (private->children));
       list;
       list = g_list_next (list))
    {
      GimpFilter *child = list->data;

      if (gimp_filter_get_active (child))
        gimp_drawable_update_all (GIMP_DRAWABLE (child));
    }

  /*  redirect stack updates back to the projection  */
  private->direct_update--;
}

static void
gimp_group_layer_translate (GimpLayer *layer,
                            gint       offset_x,
                            gint       offset_y)
{
  GimpGroupLayer        *group   = GIMP_GROUP_LAYER (layer);
  GimpGroupLayerPrivate *private = GET_PRIVATE (layer);
  gint                   x, y;
  GList                 *list;

  /*  don't use gimp_group_layer_suspend_resize(), but rather increment
   *  private->suspend_resize directly, since we're translating the group layer
   *  here, rather than relying on gimp_group_layer_update_size() to do it.
   */

  private->suspend_resize++;

  /*  redirect stack updates to the drawable, rather than to the projection  */
  private->direct_update++;

  /*  translate the child layers *before* updating the group's offset, so that,
   *  if this is a nested group, the parent's bounds still reflect the original
   *  layer positions.  This prevents the original area of the child layers,
   *  which is updated as part of their translation, from being clipped to the
   *  post-translation parent bounds (see issue #3484).  The new area of the
   *  child layers, which is likewise updated as part of translation, *may* get
   *  clipped to the old parent bounds, but the corresponding region will be
   *  updated anyway when the parent is resized, once we update the group's
   *  offset.
   */

  for (list = gimp_item_stack_get_item_iter (GIMP_ITEM_STACK (private->children));
       list;
       list = g_list_next (list))
    {
      GimpItem *child = list->data;

      /*  don't push an undo here because undo will call us again  */
      gimp_item_translate (child, offset_x, offset_y, FALSE);
    }

  gimp_item_get_offset (GIMP_ITEM (group), &x, &y);

  x += offset_x;
  y += offset_y;

  /*  update the offset node  */
  if (private->offset_node)
    gegl_node_set (private->offset_node,
                   "x", (gdouble) -x,
                   "y", (gdouble) -y,
                   NULL);

  /*  invalidate the selection boundary because of a layer modification  */
  gimp_drawable_invalidate_boundary (GIMP_DRAWABLE (layer));

  /*  update the group layer offset  */
  gimp_item_set_offset (GIMP_ITEM (group), x, y);

  /*  redirect stack updates back to the projection  */
  private->direct_update--;

  /*  don't use gimp_group_layer_resume_resize(), but rather decrement
   *  private->suspend_resize directly, so that gimp_group_layer_update_size()
   *  isn't called.
   */

  private->suspend_resize--;
}

static void
gimp_group_layer_scale (GimpLayer             *layer,
                        gint                   new_width,
                        gint                   new_height,
                        gint                   new_offset_x,
                        gint                   new_offset_y,
                        GimpInterpolationType  interpolation_type,
                        GimpProgress          *progress)
{
  GimpGroupLayer        *group   = GIMP_GROUP_LAYER (layer);
  GimpGroupLayerPrivate *private = GET_PRIVATE (layer);
  GimpItem              *item    = GIMP_ITEM (layer);
  GimpObjectQueue       *queue   = NULL;
  GList                 *list;
  gdouble                width_factor;
  gdouble                height_factor;
  gint                   old_offset_x;
  gint                   old_offset_y;

  width_factor  = (gdouble) new_width  / (gdouble) gimp_item_get_width  (item);
  height_factor = (gdouble) new_height / (gdouble) gimp_item_get_height (item);

  old_offset_x = gimp_item_get_offset_x (item);
  old_offset_y = gimp_item_get_offset_y (item);

  if (progress)
    {
      queue    = gimp_object_queue_new (progress);
      progress = GIMP_PROGRESS (queue);

      gimp_object_queue_push_container (queue, private->children);
    }

  gimp_group_layer_suspend_resize (group, TRUE);

  list = gimp_item_stack_get_item_iter (GIMP_ITEM_STACK (private->children));

  while (list)
    {
      GimpItem *child = list->data;

      list = g_list_next (list);

      if (queue)
        gimp_object_queue_pop (queue);

      if (! gimp_item_scale_by_factors_with_origin (child,
                                                    width_factor, height_factor,
                                                    old_offset_x, old_offset_y,
                                                    new_offset_x, new_offset_y,
                                                    interpolation_type,
                                                    progress))
        {
          /* new width or height are 0; remove item */
          if (gimp_item_is_attached (item))
            {
              gimp_image_remove_layer (gimp_item_get_image (item),
                                       GIMP_LAYER (child),
                                       TRUE, NULL);
            }
          else
            {
              gimp_container_remove (private->children, GIMP_OBJECT (child));
            }
        }
    }

  gimp_group_layer_resume_resize (group, TRUE);

  g_clear_object (&queue);
}

static void
gimp_group_layer_flip (GimpLayer           *layer,
                       GimpContext         *context,
                       GimpOrientationType  flip_type,
                       gdouble              axis,
                       gboolean             clip_result)
{
  GimpGroupLayer        *group   = GIMP_GROUP_LAYER (layer);
  GimpGroupLayerPrivate *private = GET_PRIVATE (layer);
  GList                 *list;

  gimp_group_layer_suspend_resize (group, TRUE);

  for (list = gimp_item_stack_get_item_iter (GIMP_ITEM_STACK (private->children));
       list;
       list = g_list_next (list))
    {
      GimpItem *child = list->data;

      gimp_item_flip (child, context,
                      flip_type, axis, clip_result);
    }

  gimp_group_layer_resume_resize (group, TRUE);
}

static void
gimp_group_layer_rotate (GimpLayer        *layer,
                         GimpContext      *context,
                         GimpRotationType  rotate_type,
                         gdouble           center_x,
                         gdouble           center_y,
                         gboolean          clip_result)
{
  GimpGroupLayer        *group   = GIMP_GROUP_LAYER (layer);
  GimpGroupLayerPrivate *private = GET_PRIVATE (layer);
  GList                 *list;

  gimp_group_layer_suspend_resize (group, TRUE);

  for (list = gimp_item_stack_get_item_iter (GIMP_ITEM_STACK (private->children));
       list;
       list = g_list_next (list))
    {
      GimpItem *child = list->data;

      gimp_item_rotate (child, context,
                        rotate_type, center_x, center_y, clip_result);
    }

  gimp_group_layer_resume_resize (group, TRUE);
}

static void
gimp_group_layer_transform (GimpLayer              *layer,
                            GimpContext            *context,
                            const GimpMatrix3      *matrix,
                            GimpTransformDirection  direction,
                            GimpInterpolationType   interpolation_type,
                            GimpTransformResize     clip_result,
                            GimpProgress           *progress,
                            gboolean                push_undo)
{
  GimpGroupLayer        *group   = GIMP_GROUP_LAYER (layer);
  GimpGroupLayerPrivate *private = GET_PRIVATE (layer);
  GimpObjectQueue       *queue   = NULL;
  GList                 *list;

  if (progress)
    {
      queue    = gimp_object_queue_new (progress);
      progress = GIMP_PROGRESS (queue);

      gimp_object_queue_push_container (queue, private->children);
    }

  gimp_group_layer_suspend_resize (group, TRUE);

  for (list = gimp_item_stack_get_item_iter (GIMP_ITEM_STACK (private->children));
       list;
       list = g_list_next (list))
    {
      GimpItem *child = list->data;

      if (queue)
        gimp_object_queue_pop (queue);

      gimp_item_transform (child, context,
                           matrix, direction,
                           interpolation_type,
                           clip_result, progress);
    }

  gimp_group_layer_resume_resize (group, TRUE);

  g_clear_object (&queue);
}

static const Babl *
get_projection_format (GimpProjectable   *projectable,
                       GimpImageBaseType  base_type,
                       GimpPrecision      precision)
{
  GimpImage *image = gimp_item_get_image (GIMP_ITEM (projectable));

  switch (base_type)
    {
    case GIMP_RGB:
    case GIMP_INDEXED:
      return gimp_image_get_format (image, GIMP_RGB, precision, TRUE,
                                    gimp_image_get_layer_space (image));

    case GIMP_GRAY:
      return gimp_image_get_format (image, GIMP_GRAY, precision, TRUE,
                                    gimp_image_get_layer_space (image));
    }

  g_return_val_if_reached (NULL);
}

static void
gimp_group_layer_convert_type (GimpLayer        *layer,
                               GimpImage        *dest_image,
                               const Babl       *new_format,
                               GimpColorProfile *src_profile,
                               GimpColorProfile *dest_profile,
                               GeglDitherMethod  layer_dither_type,
                               GeglDitherMethod  mask_dither_type,
                               gboolean          push_undo,
                               GimpProgress     *progress)
{
  GimpGroupLayer        *group   = GIMP_GROUP_LAYER (layer);
  GimpGroupLayerPrivate *private = GET_PRIVATE (layer);
  GeglBuffer            *buffer;

  if (push_undo)
    {
      GimpImage *image = gimp_item_get_image (GIMP_ITEM (group));

      gimp_image_undo_push_group_layer_convert (image, NULL, group);
    }

  /*  Need to temporarily set the projectable's format to the new
   *  values so the projection will create its tiles with the right
   *  depth
   */

  private->convert_format =
    get_projection_format (GIMP_PROJECTABLE (group),
                           gimp_babl_format_get_base_type (new_format),
                           gimp_babl_format_get_precision (new_format));
  gimp_projectable_structure_changed (GIMP_PROJECTABLE (group));
  gimp_group_layer_flush (group);

  buffer = gimp_pickable_get_buffer (GIMP_PICKABLE (private->projection));

  gimp_drawable_set_buffer_full (GIMP_DRAWABLE (group),
                                 FALSE, NULL,
                                 buffer, NULL,
                                 TRUE);

  /*  reset, the actual format is right now  */
  private->convert_format = NULL;
}

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_link (input, private->parent_source_node);
    }

  /* make sure we have a graph */
  (void) gimp_group_layer_get_graph (GIMP_PROJECTABLE (drawable));

  gegl_node_add_child (private->source_node, private->graph);

  gimp_group_layer_update_source_node (GIMP_GROUP_LAYER (drawable));

  return g_object_ref (private->source_node);
}

static void
gimp_group_layer_filters_changed (GimpDrawable *drawable)
{
  gimp_layer_update_effective_mode (GIMP_LAYER (drawable));

  if (GIMP_DRAWABLE_CLASS (parent_class)->filters_changed)
    GIMP_DRAWABLE_CLASS (parent_class)->filters_changed (drawable);
}

static void
gimp_group_layer_opacity_changed (GimpLayer *layer)
{
  gimp_layer_update_effective_mode (layer);

  if (GIMP_LAYER_CLASS (parent_class)->opacity_changed)
    GIMP_LAYER_CLASS (parent_class)->opacity_changed (layer);
}

static void
gimp_group_layer_effective_mode_changed (GimpLayer *layer)
{
  GimpGroupLayer        *group               = GIMP_GROUP_LAYER (layer);
  GimpGroupLayerPrivate *private             = GET_PRIVATE (layer);
  GimpLayerMode          mode;
  gboolean               pass_through;
  gboolean               update_bounding_box = FALSE;

  gimp_layer_get_effective_mode (layer, &mode, NULL, NULL, NULL);

  pass_through = (mode == GIMP_LAYER_MODE_PASS_THROUGH);

  if (pass_through != private->pass_through)
    {
      if (private->pass_through && ! pass_through)
        {
          /* when switching from pass-through mode to a non-pass-through mode,
           * flush the pickable in order to make sure the projection's buffer
           * gets properly invalidated synchronously, so that it can be used
           * as a source for the rest of the composition.
           */

          gimp_pickable_flush (GIMP_PICKABLE (private->projection));
        }

      private->pass_through = pass_through;

      update_bounding_box = TRUE;
    }

  gimp_group_layer_update_source_node (group);
  gimp_group_layer_update_mode_node (group);

  if (update_bounding_box)
    gimp_drawable_update_bounding_box (GIMP_DRAWABLE (group));

  if (GIMP_LAYER_CLASS (parent_class)->effective_mode_changed)
    GIMP_LAYER_CLASS (parent_class)->effective_mode_changed (layer);
}

static void
gimp_group_layer_excludes_backdrop_changed (GimpLayer *layer)
{
  GimpGroupLayer *group = GIMP_GROUP_LAYER (layer);

  gimp_group_layer_update_source_node (group);
  gimp_group_layer_update_mode_node (group);

  if (GIMP_LAYER_CLASS (parent_class)->excludes_backdrop_changed)
    GIMP_LAYER_CLASS (parent_class)->excludes_backdrop_changed (layer);
}

static GeglRectangle
gimp_group_layer_get_bounding_box (GimpLayer *layer)
{
  GimpGroupLayerPrivate *private = GET_PRIVATE (layer);

  /* With #4634 were reported to us cases where the get_bounding_box()
   * was smaller than the calculated bounding box. But we also had the
   * opposite case, which is that the get_bounding_box() value was
   * bigger than the computed bounding_box (e.g. when having a
   * pass-through group layer with a single transparent layer, smaller
   * than the canvas: applying an effect only affects this layer,
   * whereas the actual render of the layer group contains the below
   * layers). Cf. #15004.
   *
   * XXX It feels like the parent's get_bounding_box() implementation
   * (which gets the source node bounding box) is bugged and should not
   * return what it does. This should likely be looked closer into.
   * For now, I get the union bounding box of both these results.
   *
   * Older explanation for #4634:
   * for pass-through groups, use the group's calculated bounding box, instead
   * of the source-node's bounding box, since we don't update the bounding box
   * on all events that may affect the latter, and since it includes the
   * bounding box of the backdrop.  this means we can't attach filters that may
   * affect the bounding box to a pass-through group (since their effect weon't
   * be reflected by the group's bounding box), but attaching filters to pass-
   * through groups makes little sense anyway.
   */

  if (private->pass_through)
    {
      GeglRectangle rect = GIMP_LAYER_CLASS (parent_class)->get_bounding_box (layer);

      gegl_rectangle_bounding_box (&rect, &private->bounding_box, &rect);

      return rect;
    }
  else
    {
      return GIMP_LAYER_CLASS (parent_class)->get_bounding_box (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 &&
      ! gimp_drawable_has_visible_filters (GIMP_DRAWABLE (layer)) &&
      ! no_pass_through_strength_reduction)
    {
      /* we perform the strength-reduction if:
       *
       *   - the group has no active children;
       *
       *   or,
       *
       *     - the group has a single active child; or,
       *
       *     - the effective mode of all the active children is normal, their
       *       effective composite mode is UNION, and their effective blend and
       *       composite spaces are equal;
       *
       *   - and,
       *
       *     - the group's opacity is 100%, and it has no mask (or the mask
       *       isn't applied); or,
       *
       *     - the group's composite space equals the active children's
       *       composite space.
       */


      GList    *list;
      gboolean  reduce = TRUE;
      gboolean  first  = TRUE;

      *mode            = GIMP_LAYER_MODE_NORMAL;
      *blend_space     = gimp_layer_get_real_blend_space (layer);
      *composite_space = gimp_layer_get_real_composite_space (layer);
      *composite_mode  = gimp_layer_get_real_composite_mode (layer);

      for (list = gimp_item_stack_get_item_iter (GIMP_ITEM_STACK (private->children));
           list;
           list = g_list_next (list))
        {
          GimpLayer *child = list->data;

          if (! gimp_filter_get_active (GIMP_FILTER (child)))
            continue;

          if (first)
            {
              gimp_layer_get_effective_mode (child,
                                             mode,
                                             blend_space,
                                             composite_space,
                                             composite_mode);

              if (*mode == GIMP_LAYER_MODE_NORMAL_LEGACY)
                *mode = GIMP_LAYER_MODE_NORMAL;

              first = FALSE;
            }
          else
            {
              GimpLayerMode          other_mode;
              GimpLayerColorSpace    other_blend_space;
              GimpLayerColorSpace    other_composite_space;
              GimpLayerCompositeMode other_composite_mode;

              if (*mode           != GIMP_LAYER_MODE_NORMAL ||
                  *composite_mode != GIMP_LAYER_COMPOSITE_UNION)
                {
                  reduce = FALSE;

                  break;
                }

              gimp_layer_get_effective_mode (child,
                                             &other_mode,
                                             &other_blend_space,
                                             &other_composite_space,
                                             &other_composite_mode);

              if (other_mode == GIMP_LAYER_MODE_NORMAL_LEGACY)
                other_mode = GIMP_LAYER_MODE_NORMAL;

              if (other_mode            != *mode            ||
                  other_blend_space     != *blend_space     ||
                  other_composite_space != *composite_space ||
                  other_composite_mode  != *composite_mode)
                {
                  reduce = FALSE;

                  break;
                }
            }
        }

      if (reduce)
        {
          gboolean has_mask;

          has_mask = gimp_layer_get_mask (layer) &&
                     gimp_layer_get_apply_mask (layer);

          if (first                                                  ||
              (gimp_layer_get_opacity (layer) == GIMP_OPACITY_OPAQUE &&
               ! has_mask)                                           ||
              *composite_space == gimp_layer_get_real_composite_space (layer))
            {
              /* strength reduction succeeded! */
              return;
            }
        }
    }

  /* strength-reduction failed.  chain up. */
  GIMP_LAYER_CLASS (parent_class)->get_effective_mode (layer,
                                                       mode,
                                                       blend_space,
                                                       composite_space,
                                                       composite_mode);
}

static gboolean
gimp_group_layer_get_excludes_backdrop (GimpLayer *layer)
{
  GimpGroupLayerPrivate *private = GET_PRIVATE (layer);

  if (private->pass_through)
    {
      GList *list;

      for (list = gimp_item_stack_get_item_iter (GIMP_ITEM_STACK (private->children));
           list;
           list = g_list_next (list))
        {
          GimpFilter *child = list->data;

          if (gimp_filter_get_active (child) &&
              gimp_layer_get_excludes_backdrop (GIMP_LAYER (child)))
            return TRUE;
        }

      return FALSE;
    }
  else
    return GIMP_LAYER_CLASS (parent_class)->get_excludes_backdrop (layer);
}

static const Babl *
gimp_group_layer_get_format (GimpProjectable *projectable)
{
  GimpGroupLayerPrivate *private = GET_PRIVATE (projectable);
  GimpImageBaseType      base_type;
  GimpPrecision          precision;

  if (private->convert_format)
    return private->convert_format;

  base_type = gimp_drawable_get_base_type (GIMP_DRAWABLE (projectable));
  precision = gimp_drawable_get_precision (GIMP_DRAWABLE (projectable));

  return get_projection_format (projectable, base_type, precision);
}

static GeglRectangle
gimp_group_layer_projectable_get_bounding_box (GimpProjectable *projectable)
{
  GimpGroupLayerPrivate *private = GET_PRIVATE (projectable);

  return private->bounding_box;
}

static GeglNode *
gimp_group_layer_get_graph (GimpProjectable *projectable)
{
  GimpGroupLayer        *group   = GIMP_GROUP_LAYER (projectable);
  GimpGroupLayerPrivate *private = GET_PRIVATE (projectable);
  GeglNode              *input;
  GeglNode              *layers_node;
  GeglNode              *output;
  gint                   off_x;
  gint                   off_y;

  if (private->graph)
    return private->graph;

  private->graph = gegl_node_new ();

  input = gegl_node_get_input_proxy (private->graph, "input");

  layers_node =
    gimp_filter_stack_get_graph (GIMP_FILTER_STACK (private->children));

  gegl_node_add_child (private->graph, layers_node);

  gegl_node_link (input, layers_node);

  gimp_item_get_offset (GIMP_ITEM (group), &off_x, &off_y);

  private->offset_node = gegl_node_new_child (private->graph,
                                              "operation", "gegl:translate",
                                              "x",         (gdouble) -off_x,
                                              "y",         (gdouble) -off_y,
                                              NULL);

  gegl_node_link (layers_node, private->offset_node);

  output = gegl_node_get_output_proxy (private->graph, "output");

  gegl_node_link (private->offset_node, output);

  return private->graph;
}

static void
gimp_group_layer_begin_render (GimpProjectable *projectable)
{
  GimpGroupLayerPrivate *private = GET_PRIVATE (projectable);

  if (private->source_node == NULL)
    return;

  if (private->pass_through)
    gegl_node_disconnect (private->graph, "input");
}

static void
gimp_group_layer_end_render (GimpProjectable *projectable)
{
  GimpGroupLayerPrivate *private = GET_PRIVATE (projectable);

  if (private->source_node == NULL)
    return;

  if (private->pass_through)
    {
      GeglNode *input;

      input = gegl_node_get_input_proxy (private->source_node, "input");

      gegl_node_link (input, private->graph);
    }
}

static void
gimp_group_layer_pickable_flush (GimpPickable *pickable)
{
  GimpGroupLayerPrivate *private = GET_PRIVATE (pickable);

  gimp_pickable_flush (GIMP_PICKABLE (private->projection));
}

static gdouble
gimp_group_layer_get_opacity_at (GimpPickable *pickable,
                                 gint          x,
                                 gint          y)
{
  /* Only consider child layers as having content */

  return GIMP_OPACITY_TRANSPARENT;
}


/*  public functions  */

GimpLayer *
gimp_group_layer_new (GimpImage *image)
{
  GimpGroupLayer *group;
  const Babl     *format;

  g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);

  format = gimp_image_get_layer_format (image, TRUE);

  group = GIMP_GROUP_LAYER (gimp_drawable_new (GIMP_TYPE_GROUP_LAYER,
                                               image, NULL,
                                               0, 0, 1, 1,
                                               format));

  gimp_layer_set_mode (GIMP_LAYER (group),
                       gimp_image_get_default_new_layer_mode (image),
                       FALSE);

  return GIMP_LAYER (group);
}

GimpProjection *
gimp_group_layer_get_projection (GimpGroupLayer *group)
{
  g_return_val_if_fail (GIMP_IS_GROUP_LAYER (group), NULL);

  return GET_PRIVATE (group)->projection;
}

void
gimp_group_layer_suspend_resize (GimpGroupLayer *group,
                                 gboolean        push_undo)
{
  GimpItem *item;

  g_return_if_fail (GIMP_IS_GROUP_LAYER (group));

  item = GIMP_ITEM (group);

  if (! gimp_item_is_attached (item))
    push_undo = FALSE;

  if (push_undo)
    gimp_image_undo_push_group_layer_suspend_resize (gimp_item_get_image (item),
                                                     NULL, group);

  GET_PRIVATE (group)->suspend_resize++;
}

void
gimp_group_layer_resume_resize (GimpGroupLayer *group,
                                gboolean        push_undo)
{
  GimpGroupLayerPrivate *private;
  GimpItem              *item;
  GimpItem              *mask = NULL;
  GeglBuffer            *mask_buffer;
  GeglRectangle          mask_bounds;
  GimpUndo              *undo;

  g_return_if_fail (GIMP_IS_GROUP_LAYER (group));

  private = GET_PRIVATE (group);

  g_return_if_fail (private->suspend_resize > 0);

  item = GIMP_ITEM (group);

  if (! gimp_item_is_attached (item))
    push_undo = FALSE;

  if (push_undo)
    {
      undo =
        gimp_image_undo_push_group_layer_resume_resize (gimp_item_get_image (item),
                                                        NULL, group);

      /* if there were any {suspend,resume}_mask() calls during the time the
       * group's size was suspended, the resume_mask() calls will not have seen
       * any changes to the mask, and will therefore won't restore the mask
       * during undo.  if the group's bounding box did change while resize was
       * suspended, and if there are no other {suspend,resume}_mask() blocks
       * that will see the resized mask, we have to restore the mask during the
       * resume_resize() undo.
       *
       * we ref the mask buffer here, and compare it to the mask buffer after
       * updating the size.
       */

      if (private->suspend_resize == 1 && private->suspend_mask == 0)
        {
          mask = GIMP_ITEM (gimp_layer_get_mask (GIMP_LAYER (group)));

          if (mask)
            {
              mask_buffer =
                g_object_ref (gimp_drawable_get_buffer (GIMP_DRAWABLE (mask)));

              mask_bounds.x      = gimp_item_get_offset_x (mask);
              mask_bounds.y      = gimp_item_get_offset_y (mask);
              mask_bounds.width  = gimp_item_get_width    (mask);
              mask_bounds.height = gimp_item_get_height   (mask);
            }
        }
    }

  private->suspend_resize--;

  if (private->suspend_resize == 0)
    {
      gimp_group_layer_update_size (group);

      if (mask)
        {
          /* if the mask changed, make sure it's restored during undo, as per
           * the comment above.
           */

          if (gimp_drawable_get_buffer (GIMP_DRAWABLE (mask)) != mask_buffer)
            {
              g_return_if_fail (undo != NULL);

              GIMP_GROUP_LAYER_UNDO (undo)->mask_buffer = mask_buffer;
              GIMP_GROUP_LAYER_UNDO (undo)->mask_bounds = mask_bounds;
            }
          else
            {
              g_object_unref (mask_buffer);
            }
        }
    }
}

void
gimp_group_layer_suspend_mask (GimpGroupLayer *group,
                               gboolean        push_undo)
{
  GimpGroupLayerPrivate *private;
  GimpItem              *item;

  g_return_if_fail (GIMP_IS_GROUP_LAYER (group));

  private = GET_PRIVATE (group);
  item    = GIMP_ITEM (group);

  /* avoid pushing an undo step if this is a nested suspend_mask() call, since
   * the value of 'push_undo' in nested calls should be the same as that passed
   * to the outermost call, and only pushing an undo step for the outermost
   * call in this case is enough.  we can't support cases where the values of
   * 'push_undo' in nested calls are different in a meaningful way, and
   * avoiding undo steps for nested calls prevents us from storing multiple
   * references to the suspend mask buffer on the undo stack.  while storing
   * multiple references to the buffer doesn't waste any memory (since all the
   * references are to the same buffer), it does cause the undo stack memory-
   * usage estimation to overshoot, potentially resulting in undo steps being
   * dropped unnecessarily.
   */

  if (! gimp_item_is_attached (item) || private->suspend_mask > 0)
    push_undo = FALSE;

  if (push_undo)
    gimp_image_undo_push_group_layer_suspend_mask (gimp_item_get_image (item),
                                                   NULL, group);

  if (private->suspend_mask == 0)
    {
      if (gimp_layer_get_mask (GIMP_LAYER (group)))
        {
          GimpItem *mask = GIMP_ITEM (gimp_layer_get_mask (GIMP_LAYER (group)));

          private->suspended_mask_buffer =
            g_object_ref (gimp_drawable_get_buffer (GIMP_DRAWABLE (mask)));

          private->suspended_mask_bounds.x      = gimp_item_get_offset_x (mask);
          private->suspended_mask_bounds.y      = gimp_item_get_offset_y (mask);
          private->suspended_mask_bounds.width  = gimp_item_get_width    (mask);
          private->suspended_mask_bounds.height = gimp_item_get_height   (mask);
        }
      else
        {
          private->suspended_mask_buffer = NULL;
        }
    }

  private->suspend_mask++;
}

void
gimp_group_layer_resume_mask (GimpGroupLayer *group,
                              gboolean        push_undo)
{
  GimpGroupLayerPrivate *private;
  GimpItem              *item;

  g_return_if_fail (GIMP_IS_GROUP_LAYER (group));

  private = GET_PRIVATE (group);

  g_return_if_fail (private->suspend_mask > 0);

  item = GIMP_ITEM (group);

  /* avoid pushing an undo step if this is a nested resume_mask() call.  see
   * the comment in gimp_group_layer_suspend_mask().
   */

  if (! gimp_item_is_attached (item) || private->suspend_mask > 1)
    push_undo = FALSE;

  if (push_undo)
    gimp_image_undo_push_group_layer_resume_mask (gimp_item_get_image (item),
                                                  NULL, group);

  private->suspend_mask--;

  if (private->suspend_mask == 0)
    g_clear_object (&private->suspended_mask_buffer);
}


/*  protected functions  */

void
_gimp_group_layer_set_suspended_mask (GimpGroupLayer      *group,
                                      GeglBuffer          *buffer,
                                      const GeglRectangle *bounds)
{
  GimpGroupLayerPrivate *private;

  g_return_if_fail (GIMP_IS_GROUP_LAYER (group));
  g_return_if_fail (buffer != NULL);
  g_return_if_fail (bounds != NULL);

  private = GET_PRIVATE (group);

  g_return_if_fail (private->suspend_mask > 0);

  g_object_ref (buffer);

  g_clear_object (&private->suspended_mask_buffer);

  private->suspended_mask_buffer = buffer;
  private->suspended_mask_bounds = *bounds;
}

GeglBuffer *
_gimp_group_layer_get_suspended_mask (GimpGroupLayer *group,
                                      GeglRectangle  *bounds)
{
  GimpGroupLayerPrivate *private;
  GimpLayerMask         *mask;

  g_return_val_if_fail (GIMP_IS_GROUP_LAYER (group), NULL);
  g_return_val_if_fail (bounds != NULL, NULL);

  private = GET_PRIVATE (group);
  mask    = gimp_layer_get_mask (GIMP_LAYER (group));

  g_return_val_if_fail (private->suspend_mask > 0, NULL);

  if (mask &&
      gimp_drawable_get_buffer (GIMP_DRAWABLE (mask)) !=
      private->suspended_mask_buffer)
    {
      *bounds = private->suspended_mask_bounds;

      return private->suspended_mask_buffer;
    }

  return NULL;
}

void
_gimp_group_layer_start_transform (GimpGroupLayer *group,
                                   gboolean        push_undo)
{
  GimpGroupLayerPrivate *private;
  GimpItem              *item;

  g_return_if_fail (GIMP_IS_GROUP_LAYER (group));

  private = GET_PRIVATE (group);
  item    = GIMP_ITEM (group);

  g_return_if_fail (private->suspend_mask == 0);

  if (! gimp_item_is_attached (item))
    push_undo = FALSE;

  if (push_undo)
    gimp_image_undo_push_group_layer_start_transform (gimp_item_get_image (item),
                                                      NULL, group);

  private->transforming++;
}

void
_gimp_group_layer_end_transform (GimpGroupLayer *group,
                                 gboolean        push_undo)
{
  GimpGroupLayerPrivate *private;
  GimpItem              *item;

  g_return_if_fail (GIMP_IS_GROUP_LAYER (group));

  private = GET_PRIVATE (group);
  item    = GIMP_ITEM (group);

  g_return_if_fail (private->suspend_mask == 0);
  g_return_if_fail (private->transforming > 0);

  if (! gimp_item_is_attached (item))
    push_undo = FALSE;

  if (push_undo)
    gimp_image_undo_push_group_layer_end_transform (gimp_item_get_image (item),
                                                    NULL, group);

  private->transforming--;

  if (private->transforming == 0)
    gimp_group_layer_update_mask_size (GIMP_GROUP_LAYER (item));
}


/*  private functions  */

static void
gimp_group_layer_child_add (GimpContainer  *container,
                            GimpLayer      *child,
                            GimpGroupLayer *group)
{
  gimp_group_layer_update (group);

  if (gimp_filter_get_active (GIMP_FILTER (child)))
    {
      gimp_layer_update_effective_mode (GIMP_LAYER (group));

      if (gimp_layer_get_excludes_backdrop (child))
        gimp_layer_update_excludes_backdrop (GIMP_LAYER (group));
    }
}

static void
gimp_group_layer_child_remove (GimpContainer  *container,
                               GimpLayer      *child,
                               GimpGroupLayer *group)
{
  gimp_group_layer_update (group);

  if (gimp_filter_get_active (GIMP_FILTER (child)))
    {
      gimp_layer_update_effective_mode (GIMP_LAYER (group));

      if (gimp_layer_get_excludes_backdrop (child))
        gimp_layer_update_excludes_backdrop (GIMP_LAYER (group));
    }
}

static void
gimp_group_layer_child_move (GimpLayer      *child,
                             GParamSpec     *pspec,
                             GimpGroupLayer *group)
{
  gimp_group_layer_update (group);
}

static void
gimp_group_layer_child_resize (GimpLayer      *child,
                               GimpGroupLayer *group)
{
  gimp_group_layer_update (group);
}

static void
gimp_group_layer_child_active_changed (GimpLayer      *child,
                                       GimpGroupLayer *group)
{
  gimp_layer_update_effective_mode (GIMP_LAYER (group));

  if (gimp_layer_get_excludes_backdrop (child))
    gimp_layer_update_excludes_backdrop (GIMP_LAYER (group));
}

static void
gimp_group_layer_child_effective_mode_changed (GimpLayer      *child,
                                               GimpGroupLayer *group)
{
  if (gimp_filter_get_active (GIMP_FILTER (child)))
    gimp_layer_update_effective_mode (GIMP_LAYER (group));
}

static void
gimp_group_layer_child_excludes_backdrop_changed (GimpLayer      *child,
                                                  GimpGroupLayer *group)
{
  if (gimp_filter_get_active (GIMP_FILTER (child)))
    gimp_layer_update_excludes_backdrop (GIMP_LAYER (group));
}

static void
gimp_group_layer_flush (GimpGroupLayer *group)
{
  GimpGroupLayerPrivate *private = GET_PRIVATE (group);

  if (private->pass_through)
    {
      /*  flush the projectable, not the pickable, because the source
       *  node of pass-through groups doesn't use the projection's
       *  buffer, hence there's no need to invalidate it synchronously.
       */

      gimp_projectable_flush (GIMP_PROJECTABLE (group), TRUE);
    }
  else
    {
      /* make sure we have a buffer, and stop any idle rendering, which is
       * initiated when a new buffer is allocated.  the call to
       * gimp_pickable_flush() below causes any pending idle rendering to
       * finish synchronously, so this needs to happen before.
       */

      gimp_pickable_get_buffer (GIMP_PICKABLE (private->projection));
      gimp_projection_stop_rendering (private->projection);

      /*  flush the pickable not the projectable because flushing the
       *  pickable will finish all invalidation on the projection so it
       *  can be used as source (note that it will still be constructed
       *  when the actual read happens, so this it not a performance
       *  problem)
       */

      gimp_pickable_flush (GIMP_PICKABLE (private->projection));
    }
}

static void
gimp_group_layer_update (GimpGroupLayer *group)
{
  if (GET_PRIVATE (group)->suspend_resize == 0)
    {
      gimp_group_layer_update_size (group);
    }
}

static void
gimp_group_layer_update_size (GimpGroupLayer *group)
{
  GimpGroupLayerPrivate *private = GET_PRIVATE (group);
  GimpItem              *item    = GIMP_ITEM (group);
  GimpLayer             *layer   = GIMP_LAYER (group);
  GimpItem              *mask    = GIMP_ITEM (gimp_layer_get_mask (layer));
  GeglRectangle          old_bounds;
  GeglRectangle          bounds;
  GeglRectangle          old_bounding_box;
  GeglRectangle          bounding_box;
  gboolean               first   = TRUE;
  gboolean               size_changed;
  gboolean               resize_mask;
  GList                 *list;

  old_bounds.x      = gimp_item_get_offset_x (item);
  old_bounds.y      = gimp_item_get_offset_y (item);
  old_bounds.width  = gimp_item_get_width    (item);
  old_bounds.height = gimp_item_get_height   (item);

  bounds.x          = 0;
  bounds.y          = 0;
  bounds.width      = 1;
  bounds.height     = 1;

  old_bounding_box  = private->bounding_box;
  bounding_box      = bounds;

  for (list = gimp_item_stack_get_item_iter (GIMP_ITEM_STACK (private->children));
       list;
       list = g_list_next (list))
    {
      GimpItem      *child = list->data;
      GeglRectangle  child_bounds;
      GeglRectangle  child_bounding_box;

      if (! gimp_viewable_get_size (GIMP_VIEWABLE (child),
                                    &child_bounds.width, &child_bounds.height))
        {
          /*  ignore children without content (empty group layers);
           *  see bug 777017
           */

          continue;
        }

      gimp_item_get_offset (child, &child_bounds.x, &child_bounds.y);

      child_bounding_box =
        gimp_drawable_get_bounding_box (GIMP_DRAWABLE (child));

      child_bounding_box.x += child_bounds.x;
      child_bounding_box.y += child_bounds.y;

      if (first)
        {
          bounds       = child_bounds;
          bounding_box = child_bounding_box;

          first = FALSE;
        }
      else
        {
          gegl_rectangle_bounding_box (&bounds,
                                       &bounds, &child_bounds);
          gegl_rectangle_bounding_box (&bounding_box,
                                       &bounding_box, &child_bounding_box);
        }
    }

  bounding_box.x -= bounds.x;
  bounding_box.y -= bounds.y;

  size_changed = ! (gegl_rectangle_equal (&bounds,       &old_bounds) &&
                    gegl_rectangle_equal (&bounding_box, &old_bounding_box));

  resize_mask = mask && ! gegl_rectangle_equal (&bounds, &old_bounds);

  /* if we show the mask, invalidate the old mask area */
  if (resize_mask && gimp_layer_get_show_mask (layer))
    {
      gimp_drawable_update (GIMP_DRAWABLE (group),
                            gimp_item_get_offset_x (mask) - old_bounds.x,
                            gimp_item_get_offset_y (mask) - old_bounds.y,
                            gimp_item_get_width    (mask),
                            gimp_item_get_height   (mask));
    }

  if (private->reallocate_projection || size_changed)
    {
      GeglBuffer *buffer;

      /* if the graph is already constructed, set the offset node's
       * coordinates first, so the graph is in the right state when
       * the projection is reallocated, see bug #730550.
       */

      if (private->offset_node)
        gegl_node_set (private->offset_node,
                       "x", (gdouble) -bounds.x,
                       "y", (gdouble) -bounds.y,
                       NULL);

      /* update our offset *before* calling gimp_pickable_get_buffer(), so
       * that if our graph isn't constructed yet, the offset node picks
       * up the right coordinates in gimp_group_layer_get_graph().
       */

      gimp_item_set_offset (item, bounds.x, bounds.y);

      /* update the bounding box before updating the projection, so that it
       * picks up the right size.
       */

      private->bounding_box = bounding_box;

      if (private->reallocate_projection)
        {
          private->reallocate_projection = FALSE;

          gimp_projectable_structure_changed (GIMP_PROJECTABLE (group));
        }
      else
        {
          /* when there's no need to reallocate the projection, we call
           * gimp_projectable_bounds_changed(), rather than structure_chaned(),
           * so that the projection simply copies the old content over to the
           * new buffer with an offset, rather than re-renders the graph.
           */

          gimp_projectable_bounds_changed (GIMP_PROJECTABLE (group),
                                           old_bounds.x, old_bounds.y);
        }

      buffer = gimp_pickable_get_buffer (GIMP_PICKABLE (private->projection));

      gimp_drawable_set_buffer_full (GIMP_DRAWABLE (group),
                                     FALSE, NULL,
                                     buffer, &bounds,
                                     FALSE /* don't update the drawable, the
                                            * flush() below will take care of
                                            * that.
                                            */
);

      gimp_group_layer_flush (group);
    }

  /* resize the mask if not transforming (in which case, GimpLayer takes care
   * of the mask)
   */

  if (resize_mask && ! private->transforming)
    gimp_group_layer_update_mask_size (group);

  /* if we show the mask, invalidate the new mask area */
  if (resize_mask && gimp_layer_get_show_mask (layer))
    {
      gimp_drawable_update (GIMP_DRAWABLE (group),
                            gimp_item_get_offset_x (mask) - bounds.x,
                            gimp_item_get_offset_y (mask) - bounds.y,
                            gimp_item_get_width    (mask),
                            gimp_item_get_height   (mask));
    }
}

static void
gimp_group_layer_update_mask_size (GimpGroupLayer *group)
{
  GimpGroupLayerPrivate *private = GET_PRIVATE (group);
  GimpItem              *item    = GIMP_ITEM (group);
  GimpItem              *mask;
  GeglBuffer            *buffer;
  GeglBuffer            *mask_buffer;
  GeglRectangle          bounds;
  GeglRectangle          mask_bounds;
  GeglRectangle          copy_bounds;
  gboolean               intersect;

  mask = GIMP_ITEM (gimp_layer_get_mask (GIMP_LAYER (group)));

  if (! mask)
    return;

  bounds.x           = gimp_item_get_offset_x (item);
  bounds.y           = gimp_item_get_offset_y (item);
  bounds.width       = gimp_item_get_width    (item);
  bounds.height      = gimp_item_get_height   (item);

  mask_bounds.x      = gimp_item_get_offset_x (mask);
  mask_bounds.y      = gimp_item_get_offset_y (mask);
  mask_bounds.width  = gimp_item_get_width    (mask);
  mask_bounds.height = gimp_item_get_height   (mask);

  if (gegl_rectangle_equal (&bounds, &mask_bounds))
    return;

  buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0, bounds.width, bounds.height),
                            gimp_drawable_get_format (GIMP_DRAWABLE (mask)));

  if (private->suspended_mask_buffer)
    {
      /* copy the suspended mask into the new mask */
      mask_buffer = private->suspended_mask_buffer;
      mask_bounds = private->suspended_mask_bounds;
    }
  else
    {
      /* copy the old mask into the new mask */
      mask_buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (mask));
    }

  intersect = gimp_rectangle_intersect (bounds.x,
                                        bounds.y,
                                        bounds.width,
                                        bounds.height,
                                        mask_bounds.x,
                                        mask_bounds.y,
                                        mask_bounds.width,
                                        mask_bounds.height,
                                        ©_bounds.x,
                                        ©_bounds.y,
                                        ©_bounds.width,
                                        ©_bounds.height);

  if (intersect)
    {
      gimp_gegl_buffer_copy (mask_buffer,
                             GEGL_RECTANGLE (copy_bounds.x - mask_bounds.x,
                                             copy_bounds.y - mask_bounds.y,
                                             copy_bounds.width,
                                             copy_bounds.height),
                             GEGL_ABYSS_NONE,
                             buffer,
                             GEGL_RECTANGLE (copy_bounds.x - bounds.x,
                                             copy_bounds.y - bounds.y,
                                             copy_bounds.width,
                                             copy_bounds.height));
    }

  gimp_drawable_set_buffer_full (GIMP_DRAWABLE (mask),
                                 FALSE, NULL,
                                 buffer, &bounds,
                                 TRUE);

  g_object_unref (buffer);
}

static void
gimp_group_layer_update_source_node (GimpGroupLayer *group)
{
  GimpGroupLayerPrivate *private = GET_PRIVATE (group);
  GeglNode              *input;
  GeglNode              *output;

  if (private->source_node == NULL)
    return;

  input  = gegl_node_get_input_proxy  (private->source_node, "input");
  output = gegl_node_get_output_proxy (private->source_node, "output");

  if (private->pass_through)
    {
      gegl_node_link (input, private->graph);
      gegl_node_link (private->graph, output);
    }
  else
    {
      gegl_node_disconnect (private->graph, "input");

      gegl_node_link (private->parent_source_node, output);
    }
}

static void
gimp_group_layer_update_mode_node (GimpGroupLayer *group)
{
  GimpGroupLayerPrivate *private = GET_PRIVATE (group);
  GeglNode              *node;
  GeglNode              *input;
  GeglNode              *mode_node;

  node      = gimp_filter_get_node (GIMP_FILTER (group));
  input     = gegl_node_get_input_proxy (node, "input");
  mode_node = gimp_drawable_get_mode_node (GIMP_DRAWABLE (group));

  if (private->pass_through &&
      gimp_layer_get_excludes_backdrop (GIMP_LAYER (group)))
    {
      gegl_node_disconnect (mode_node, "input");
    }
  else
    {
      gegl_node_link (input, mode_node);
    }
}

static void
gimp_group_layer_stack_update (GimpDrawableStack *stack,
                               gint               x,
                               gint               y,
                               gint               width,
                               gint               height,
                               GimpGroupLayer    *group)
{
  GimpGroupLayerPrivate *private = GET_PRIVATE (group);

#if 0
  g_printerr ("%s (%s) %d, %d (%d, %d)\n",
              G_STRFUNC, gimp_object_get_name (group),
              x, y, width, height);
#endif

  if (! private->direct_update)
    {
      /*  the layer stack's update signal speaks in image coordinates,
       *  pass to the projection as-is.
       */

      gimp_projectable_invalidate (GIMP_PROJECTABLE (group),
                                   x, y, width, height);

      gimp_group_layer_flush (group);
    }

  if (private->direct_update || private->pass_through)
    {
      /*  the layer stack's update signal speaks in image coordinates,
       *  transform to layer coordinates when emitting our own update signal.
       */

      gimp_drawable_update (GIMP_DRAWABLE (group),
                            x - gimp_item_get_offset_x (GIMP_ITEM (group)),
                            y - gimp_item_get_offset_y (GIMP_ITEM (group)),
                            width, height);
    }
}

static void
gimp_group_layer_proj_update (GimpProjection *proj,
                              gboolean        now,
                              gint            x,
                              gint            y,
                              gint            width,
                              gint            height,
                              GimpGroupLayer *group)
{
  GimpGroupLayerPrivate *private = GET_PRIVATE (group);

#if 0
  g_printerr ("%s (%s) %d, %d (%d, %d)\n",
              G_STRFUNC, gimp_object_get_name (group),
              x, y, width, height);
#endif

  if (! private->pass_through)
    {
      /* TODO: groups can currently have a gegl:transform op attached as a filter
       * when using a transform tool, in which case the updated region needs
       * undergo the same transformation.  more generally, when a drawable has
       * filters they may influence the area affected by drawable updates.
       *
       * this needs to be addressed much more generally at some point, but for now
       * we just resort to updating the entire group when it has a filter (i.e.,
       * when it's being used with a transform tool).  we restrict this to groups,
       * and don't do this more generally in gimp_drawable_update(), because this
       * negatively impacts the performance of the warp tool, which does perform
       * accurate drawable updates while using a filter.
       */

      if (gimp_drawable_has_visible_filters (GIMP_DRAWABLE (group)))
        {
          width  = -1;
          height = -1;
        }

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

Messung
CHZG
81999892

¤ Dauer der Verarbeitung: 0.43 Sekunden  (vorverarbeitet am  2026-11-04) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover


Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Open Source Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Statistik
#Sources=1104856
#Domains=417315