Eine aufbereitete Darstellung der Quelle

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

Benutzer

Impressum gimpimage.c   Sprache: C

 

/* GIMP - The GNU Image Manipulation Program
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * 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 <time.h>

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

#include "libgimpcolor/gimpcolor.h"
#include "libgimpmath/gimpmath.h"
#include "libgimpbase/gimpbase.h"
#include "libgimpconfig/gimpconfig.h"

#include "core-types.h"

#include "config/gimpcoreconfig.h"

#include "operations/layer-modes/gimp-layer-modes.h"

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

#include "gimp.h"
#include "gimp-memsize.h"
#include "gimp-parasites.h"
#include "gimp-utils.h"
#include "gimpcontext.h"
#include "gimpdrawable-filters.h"
#include "gimpdrawable-floating-selection.h"
#include "gimpdrawablefilter.h"
#include "gimpdrawablestack.h"
#include "gimpgrid.h"
#include "gimperror.h"
#include "gimpguide.h"
#include "gimpidtable.h"
#include "gimpimage.h"
#include "gimpimage-color-profile.h"
#include "gimpimage-colormap.h"
#include "gimpimage-guides.h"
#include "gimpimage-item-list.h"
#include "gimpimage-metadata.h"
#include "gimpimage-sample-points.h"
#include "gimpimage-preview.h"
#include "gimpimage-private.h"
#include "gimpimage-quick-mask.h"
#include "gimpimage-symmetry.h"
#include "gimpimage-undo.h"
#include "gimpimage-undo-push.h"
#include "gimpitemlist.h"
#include "gimpitemtree.h"
#include "gimplayer.h"
#include "gimplayer-floating-selection.h"
#include "gimplayermask.h"
#include "gimplayerstack.h"
#include "gimplinklayer.h"
#include "gimpmarshal.h"
#include "gimppalette.h"
#include "gimpparasitelist.h"
#include "gimppickable.h"
#include "gimpprojectable.h"
#include "gimpprojection.h"
#include "gimpsamplepoint.h"
#include "gimpselection.h"
#include "gimpsymmetry.h"
#include "gimptempbuf.h"
#include "gimptemplate.h"
#include "gimpundostack.h"

#include "text/gimptextlayer.h"

#include "path/gimppath.h"
#include "path/gimpvectorlayer.h"

#include "gimp-log.h"
#include "gimp-intl.h"


#ifdef DEBUG
#define TRC(x) g_printerr x
#else
#define TRC(x)
#endif


enum
{
  MODE_CHANGED,
  PRECISION_CHANGED,
  ALPHA_CHANGED,
  FLOATING_SELECTION_CHANGED,
  SELECTED_CHANNELS_CHANGED,
  SELECTED_PATHS_CHANGED,
  SELECTED_LAYERS_CHANGED,
  COMPONENT_VISIBILITY_CHANGED,
  COMPONENT_ACTIVE_CHANGED,
  MASK_CHANGED,
  RESOLUTION_CHANGED,
  SIZE_CHANGED_DETAILED,
  UNIT_CHANGED,
  QUICK_MASK_CHANGED,
  SELECTION_INVALIDATE,
  CLEAN,
  DIRTY,
  SAVING,
  SAVED,
  EXPORTED,
  GUIDE_ADDED,
  GUIDE_REMOVED,
  GUIDE_MOVED,
  SAMPLE_POINT_ADDED,
  SAMPLE_POINT_REMOVED,
  SAMPLE_POINT_MOVED,
  PARASITE_ATTACHED,
  PARASITE_DETACHED,
  COLORMAP_CHANGED,
  UNDO_EVENT,
  ITEM_SETS_CHANGED,
  DISPLAY_COUNT_CHANGED,
  LAST_SIGNAL
};

enum
{
  PROP_0,
  PROP_GIMP,
  PROP_ID,
  PROP_WIDTH,
  PROP_HEIGHT,
  PROP_BASE_TYPE,
  PROP_PRECISION,
  PROP_METADATA,
  PROP_BUFFER,
  PROP_SYMMETRY,
  PROP_CONVERTING,
};


/*  local function prototypes  */

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

static void     gimp_image_constructed           (GObject           *object);
static void     gimp_image_set_property          (GObject           *object,
                                                  guint              property_id,
                                                  const GValue      *value,
                                                  GParamSpec        *pspec);
static void     gimp_image_get_property          (GObject           *object,
                                                  guint              property_id,
                                                  GValue            *value,
                                                  GParamSpec        *pspec);
static void     gimp_image_dispose               (GObject           *object);
static void     gimp_image_finalize              (GObject           *object);

static void     gimp_image_name_changed          (GimpObject        *object);
static gint64   gimp_image_get_memsize           (GimpObject        *object,
                                                  gint64            *gui_size);

static gboolean gimp_image_get_size              (GimpViewable      *viewable,
                                                  gint              *width,
                                                  gint              *height);
static void     gimp_image_size_changed          (GimpViewable      *viewable);
static gchar  * gimp_image_get_description       (GimpViewable      *viewable,
                                                  gchar            **tooltip);

static void     gimp_image_real_mode_changed     (GimpImage         *image);
static void     gimp_image_real_precision_changed(GimpImage         *image);
static void     gimp_image_real_resolution_changed(GimpImage        *image);
static void     gimp_image_real_size_changed_detailed
                                                 (GimpImage         *image,
                                                  gint               previous_origin_x,
                                                  gint               previous_origin_y,
                                                  gint               previous_width,
                                                  gint               previous_height);
static void     gimp_image_real_unit_changed     (GimpImage         *image);
static void     gimp_image_real_colormap_changed (GimpImage         *image,
                                                  gint               color_index);

static const guint8 *
        gimp_image_color_managed_get_icc_profile (GimpColorManaged  *managed,
                                                  gsize             *len);
static GimpColorProfile *
      gimp_image_color_managed_get_color_profile (GimpColorManaged  *managed);
static void
        gimp_image_color_managed_profile_changed (GimpColorManaged  *managed);

static GimpColorProfile *
      gimp_image_color_managed_get_simulation_profile     (GimpColorManaged  *managed);
static void
      gimp_image_color_managed_simulation_profile_changed (GimpColorManaged  *managed);

static GimpColorRenderingIntent
      gimp_image_color_managed_get_simulation_intent      (GimpColorManaged  *managed);
static void
      gimp_image_color_managed_simulation_intent_changed  (GimpColorManaged  *managed);

static gboolean
      gimp_image_color_managed_get_simulation_bpc         (GimpColorManaged  *managed);
static void
      gimp_image_color_managed_simulation_bpc_changed     (GimpColorManaged  *managed);

static void        gimp_image_projectable_flush  (GimpProjectable   *projectable,
                                                  gboolean           invalidate_preview);
static GeglRectangle gimp_image_get_bounding_box (GimpProjectable   *projectable);
static GeglNode   * gimp_image_get_graph         (GimpProjectable   *projectable);
static GimpImage  * gimp_image_get_image         (GimpProjectable   *projectable);
static const Babl * gimp_image_get_proj_format   (GimpProjectable   *projectable);

static void         gimp_image_pickable_flush    (GimpPickable      *pickable);
static GeglBuffer * gimp_image_get_buffer        (GimpPickable      *pickable);
static gboolean     gimp_image_get_pixel_at      (GimpPickable      *pickable,
                                                  gint               x,
                                                  gint               y,
                                                  const Babl        *format,
                                                  gpointer           pixel);
static gdouble      gimp_image_get_opacity_at    (GimpPickable      *pickable,
                                                  gint               x,
                                                  gint               y);
static void         gimp_image_get_pixel_average (GimpPickable      *pickable,
                                                  const GeglRectangle *rect,
                                                  const Babl        *format,
                                                  gpointer           pixel);

static void     gimp_image_projection_buffer_notify
                                                 (GimpProjection    *projection,
                                                  const GParamSpec  *pspec,
                                                  GimpImage         *image);
static void     gimp_image_mask_update           (GimpDrawable      *drawable,
                                                  gint               x,
                                                  gint               y,
                                                  gint               width,
                                                  gint               height,
                                                  GimpImage         *image);
static void     gimp_image_layers_changed        (GimpContainer     *container,
                                                  GimpChannel       *channel,
                                                  GimpImage         *image);
static void     gimp_image_layer_offset_changed  (GimpDrawable      *drawable,
                                                  const GParamSpec  *pspec,
                                                  GimpImage         *image);
static void     gimp_image_layer_bounding_box_changed
                                                 (GimpDrawable      *drawable,
                                                  GimpImage         *image);
static void     gimp_image_layer_alpha_changed   (GimpDrawable      *drawable,
                                                  GimpImage         *image);
static void     gimp_image_channel_add           (GimpContainer     *container,
                                                  GimpChannel       *channel,
                                                  GimpImage         *image);
static void     gimp_image_channel_remove        (GimpContainer     *container,
                                                  GimpChannel       *channel,
                                                  GimpImage         *image);
static void     gimp_image_channel_name_changed  (GimpChannel       *channel,
                                                  GimpImage         *image);
static void     gimp_image_channel_color_changed (GimpChannel       *channel,
                                                  GimpImage         *image);

static void     gimp_image_selected_layers_notify   (GimpItemTree      *tree,
                                                     const GParamSpec  *pspec,
                                                     GimpImage         *image);
static void     gimp_image_selected_channels_notify (GimpItemTree      *tree,
                                                     const GParamSpec  *pspec,
                                                     GimpImage         *image);
static void     gimp_image_selected_paths_notify    (GimpItemTree      *tree,
                                                     const GParamSpec  *pspec,
                                                     GimpImage         *image);

static void     gimp_image_freeze_bounding_box   (GimpImage         *image);
static void     gimp_image_thaw_bounding_box     (GimpImage         *image);
static void     gimp_image_update_bounding_box   (GimpImage         *image);

static gint     gimp_image_layer_stack_cmp         (GList           *layers1,
                                                    GList           *layers2);
static void gimp_image_rec_remove_layer_stack_dups (GimpImage       *image,
                                                    GSList          *start);
static void     gimp_image_clean_layer_stack       (GimpImage       *image);
static void     gimp_image_rec_filter_remove_undo  (GimpImage       *image,
                                                    GimpLayer       *layer);
static void     gimp_image_remove_from_layer_stack (GimpImage       *image,
                                                    GimpLayer       *layer);
static gint     gimp_image_selected_is_descendant  (GimpViewable    *selected,
                                                    GimpViewable    *viewable);

G_DEFINE_TYPE_WITH_CODE (GimpImage, gimp_image, GIMP_TYPE_VIEWABLE,
                         G_ADD_PRIVATE (GimpImage)
                         G_IMPLEMENT_INTERFACE (GIMP_TYPE_COLOR_MANAGED,
                                                gimp_color_managed_iface_init)
                         G_IMPLEMENT_INTERFACE (GIMP_TYPE_PROJECTABLE,
                                                gimp_projectable_iface_init)
                         G_IMPLEMENT_INTERFACE (GIMP_TYPE_PICKABLE,
                                                gimp_pickable_iface_init))

#define parent_class gimp_image_parent_class

static guint gimp_image_signals[LAST_SIGNAL] = { 0 };


static void
gimp_image_class_init (GimpImageClass *klass)
{
  GObjectClass      *object_class      = G_OBJECT_CLASS (klass);
  GimpObjectClass   *gimp_object_class = GIMP_OBJECT_CLASS (klass);
  GimpViewableClass *viewable_class    = GIMP_VIEWABLE_CLASS (klass);

  gimp_image_signals[MODE_CHANGED] =
    g_signal_new ("mode-changed",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (GimpImageClass, mode_changed),
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 0);

  gimp_image_signals[PRECISION_CHANGED] =
    g_signal_new ("precision-changed",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (GimpImageClass, precision_changed),
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 0);

  gimp_image_signals[ALPHA_CHANGED] =
    g_signal_new ("alpha-changed",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (GimpImageClass, alpha_changed),
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 0);

  gimp_image_signals[FLOATING_SELECTION_CHANGED] =
    g_signal_new ("floating-selection-changed",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (GimpImageClass, floating_selection_changed),
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 0);

  gimp_image_signals[SELECTED_LAYERS_CHANGED] =
    g_signal_new ("selected-layers-changed",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (GimpImageClass, selected_layers_changed),
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 0);

  gimp_image_signals[SELECTED_CHANNELS_CHANGED] =
    g_signal_new ("selected-channels-changed",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (GimpImageClass, selected_channels_changed),
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 0);

  gimp_image_signals[SELECTED_PATHS_CHANGED] =
    g_signal_new ("selected-paths-changed",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (GimpImageClass, selected_paths_changed),
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 0);

  gimp_image_signals[COMPONENT_VISIBILITY_CHANGED] =
    g_signal_new ("component-visibility-changed",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (GimpImageClass, component_visibility_changed),
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 1,
                  GIMP_TYPE_CHANNEL_TYPE);

  gimp_image_signals[COMPONENT_ACTIVE_CHANGED] =
    g_signal_new ("component-active-changed",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (GimpImageClass, component_active_changed),
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 1,
                  GIMP_TYPE_CHANNEL_TYPE);

  gimp_image_signals[MASK_CHANGED] =
    g_signal_new ("mask-changed",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (GimpImageClass, mask_changed),
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 0);

  gimp_image_signals[RESOLUTION_CHANGED] =
    g_signal_new ("resolution-changed",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (GimpImageClass, resolution_changed),
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 0);

  gimp_image_signals[SIZE_CHANGED_DETAILED] =
    g_signal_new ("size-changed-detailed",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (GimpImageClass, size_changed_detailed),
                  NULL, NULL,
                  gimp_marshal_VOID__INT_INT_INT_INT,
                  G_TYPE_NONE, 4,
                  G_TYPE_INT,
                  G_TYPE_INT,
                  G_TYPE_INT,
                  G_TYPE_INT);

  gimp_image_signals[UNIT_CHANGED] =
    g_signal_new ("unit-changed",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (GimpImageClass, unit_changed),
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 0);

  gimp_image_signals[QUICK_MASK_CHANGED] =
    g_signal_new ("quick-mask-changed",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (GimpImageClass, quick_mask_changed),
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 0);

  gimp_image_signals[SELECTION_INVALIDATE] =
    g_signal_new ("selection-invalidate",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (GimpImageClass, selection_invalidate),
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 0);

  gimp_image_signals[CLEAN] =
    g_signal_new ("clean",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (GimpImageClass, clean),
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 1,
                  GIMP_TYPE_DIRTY_MASK);

  gimp_image_signals[DIRTY] =
    g_signal_new ("dirty",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (GimpImageClass, dirty),
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 1,
                  GIMP_TYPE_DIRTY_MASK);

  gimp_image_signals[SAVING] =
    g_signal_new ("saving",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (GimpImageClass, saving),
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 0);

  gimp_image_signals[SAVED] =
    g_signal_new ("saved",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (GimpImageClass, saved),
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 1,
                  G_TYPE_FILE);

  gimp_image_signals[EXPORTED] =
    g_signal_new ("exported",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (GimpImageClass, exported),
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 1,
                  G_TYPE_FILE);

  gimp_image_signals[GUIDE_ADDED] =
    g_signal_new ("guide-added",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (GimpImageClass, guide_added),
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 1,
                  GIMP_TYPE_GUIDE);

  gimp_image_signals[GUIDE_REMOVED] =
    g_signal_new ("guide-removed",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (GimpImageClass, guide_removed),
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 1,
                  GIMP_TYPE_GUIDE);

  gimp_image_signals[GUIDE_MOVED] =
    g_signal_new ("guide-moved",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (GimpImageClass, guide_moved),
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 1,
                  GIMP_TYPE_GUIDE);

  gimp_image_signals[SAMPLE_POINT_ADDED] =
    g_signal_new ("sample-point-added",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (GimpImageClass, sample_point_added),
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 1,
                  GIMP_TYPE_SAMPLE_POINT);

  gimp_image_signals[SAMPLE_POINT_REMOVED] =
    g_signal_new ("sample-point-removed",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (GimpImageClass, sample_point_removed),
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 1,
                  GIMP_TYPE_SAMPLE_POINT);

  gimp_image_signals[SAMPLE_POINT_MOVED] =
    g_signal_new ("sample-point-moved",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (GimpImageClass, sample_point_moved),
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 1,
                  GIMP_TYPE_SAMPLE_POINT);

  gimp_image_signals[PARASITE_ATTACHED] =
    g_signal_new ("parasite-attached",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (GimpImageClass, parasite_attached),
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 1,
                  G_TYPE_STRING);

  gimp_image_signals[PARASITE_DETACHED] =
    g_signal_new ("parasite-detached",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (GimpImageClass, parasite_detached),
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 1,
                  G_TYPE_STRING);

  gimp_image_signals[COLORMAP_CHANGED] =
    g_signal_new ("colormap-changed",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (GimpImageClass, colormap_changed),
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 1,
                  G_TYPE_INT);

  gimp_image_signals[UNDO_EVENT] =
    g_signal_new ("undo-event",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (GimpImageClass, undo_event),
                  NULL, NULL,
                  gimp_marshal_VOID__ENUM_OBJECT,
                  G_TYPE_NONE, 2,
                  GIMP_TYPE_UNDO_EVENT,
                  GIMP_TYPE_UNDO);

  gimp_image_signals[ITEM_SETS_CHANGED] =
    g_signal_new ("item-sets-changed",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (GimpImageClass, item_sets_changed),
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 1,
                  G_TYPE_GTYPE);

  gimp_image_signals[DISPLAY_COUNT_CHANGED] =
    g_signal_new ("display-count-changed",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (GimpImageClass, display_count_changed),
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 1,
                  G_TYPE_INT);

  object_class->constructed           = gimp_image_constructed;
  object_class->set_property          = gimp_image_set_property;
  object_class->get_property          = gimp_image_get_property;
  object_class->dispose               = gimp_image_dispose;
  object_class->finalize              = gimp_image_finalize;

  gimp_object_class->name_changed     = gimp_image_name_changed;
  gimp_object_class->get_memsize      = gimp_image_get_memsize;

  viewable_class->default_icon_name   = "gimp-image";
  viewable_class->default_name        = _("Image");
  viewable_class->get_size            = gimp_image_get_size;
  viewable_class->size_changed        = gimp_image_size_changed;
  viewable_class->get_preview_size    = gimp_image_get_preview_size;
  viewable_class->get_popup_size      = gimp_image_get_popup_size;
  viewable_class->get_new_preview     = gimp_image_get_new_preview;
  viewable_class->get_new_pixbuf      = gimp_image_get_new_pixbuf;
  viewable_class->get_description     = gimp_image_get_description;

  klass->mode_changed                 = gimp_image_real_mode_changed;
  klass->precision_changed            = gimp_image_real_precision_changed;
  klass->alpha_changed                = NULL;
  klass->floating_selection_changed   = NULL;
  klass->selected_layers_changed      = NULL;
  klass->selected_channels_changed    = NULL;
  klass->selected_paths_changed       = NULL;
  klass->component_visibility_changed = NULL;
  klass->component_active_changed     = NULL;
  klass->mask_changed                 = NULL;
  klass->resolution_changed           = gimp_image_real_resolution_changed;
  klass->size_changed_detailed        = gimp_image_real_size_changed_detailed;
  klass->unit_changed                 = gimp_image_real_unit_changed;
  klass->quick_mask_changed           = NULL;
  klass->selection_invalidate         = NULL;

  klass->clean                        = NULL;
  klass->dirty                        = NULL;
  klass->saving                       = NULL;
  klass->saved                        = NULL;
  klass->exported                     = NULL;
  klass->guide_added                  = NULL;
  klass->guide_removed                = NULL;
  klass->guide_moved                  = NULL;
  klass->sample_point_added           = NULL;
  klass->sample_point_removed         = NULL;
  klass->sample_point_moved           = NULL;
  klass->parasite_attached            = NULL;
  klass->parasite_detached            = NULL;
  klass->colormap_changed             = gimp_image_real_colormap_changed;
  klass->undo_event                   = NULL;

  g_object_class_install_property (object_class, PROP_GIMP,
                                   g_param_spec_object ("gimp", NULL, NULL,
                                                        GIMP_TYPE_GIMP,
                                                        GIMP_PARAM_READWRITE |
                                                        G_PARAM_CONSTRUCT_ONLY));

  g_object_class_install_property (object_class, PROP_ID,
                                   g_param_spec_int ("id", NULL, NULL,
                                                     0, G_MAXINT, 0,
                                                     GIMP_PARAM_READABLE));

  g_object_class_install_property (object_class, PROP_WIDTH,
                                   g_param_spec_int ("width", NULL, NULL,
                                                     1, GIMP_MAX_IMAGE_SIZE, 1,
                                                     GIMP_PARAM_READWRITE |
                                                     G_PARAM_CONSTRUCT));

  g_object_class_install_property (object_class, PROP_HEIGHT,
                                   g_param_spec_int ("height", NULL, NULL,
                                                     1, GIMP_MAX_IMAGE_SIZE, 1,
                                                     GIMP_PARAM_READWRITE |
                                                     G_PARAM_CONSTRUCT));

  g_object_class_install_property (object_class, PROP_BASE_TYPE,
                                   g_param_spec_enum ("base-type", NULL, NULL,
                                                      GIMP_TYPE_IMAGE_BASE_TYPE,
                                                      GIMP_RGB,
                                                      GIMP_PARAM_READWRITE |
                                                      G_PARAM_CONSTRUCT));

  g_object_class_install_property (object_class, PROP_PRECISION,
                                   g_param_spec_enum ("precision", NULL, NULL,
                                                      GIMP_TYPE_PRECISION,
                                                      GIMP_PRECISION_U8_NON_LINEAR,
                                                      GIMP_PARAM_READWRITE |
                                                      G_PARAM_CONSTRUCT));

  g_object_class_install_property (object_class, PROP_METADATA,
                                   g_param_spec_object ("metadata", NULL, NULL,
                                                        GEXIV2_TYPE_METADATA,
                                                        GIMP_PARAM_READABLE));

  g_object_class_override_property (object_class, PROP_BUFFER, "buffer");

  g_object_class_install_property (object_class, PROP_SYMMETRY,
                                   g_param_spec_gtype ("symmetry",
                                                       NULL, _("Symmetry"),
                                                       GIMP_TYPE_SYMMETRY,
                                                       GIMP_PARAM_READWRITE |
                                                       G_PARAM_CONSTRUCT));

  g_object_class_install_property (object_class, PROP_CONVERTING,
                                   g_param_spec_boolean ("converting",
                                                         NULL, NULL,
                                                         FALSE,
                                                         GIMP_PARAM_READWRITE));
}

static void
gimp_color_managed_iface_init (GimpColorManagedInterface *iface)
{
  iface->get_icc_profile            = gimp_image_color_managed_get_icc_profile;
  iface->get_color_profile          = gimp_image_color_managed_get_color_profile;
  iface->profile_changed            = gimp_image_color_managed_profile_changed;
  iface->get_simulation_profile     = gimp_image_color_managed_get_simulation_profile;
  iface->simulation_profile_changed = gimp_image_color_managed_simulation_profile_changed;
  iface->get_simulation_intent      = gimp_image_color_managed_get_simulation_intent;
  iface->simulation_intent_changed  = gimp_image_color_managed_simulation_intent_changed;
  iface->get_simulation_bpc         = gimp_image_color_managed_get_simulation_bpc;
  iface->simulation_bpc_changed     = gimp_image_color_managed_simulation_bpc_changed;
}

static void
gimp_projectable_iface_init (GimpProjectableInterface *iface)
{
  iface->flush              = gimp_image_projectable_flush;
  iface->get_image          = gimp_image_get_image;
  iface->get_format         = gimp_image_get_proj_format;
  iface->get_bounding_box   = gimp_image_get_bounding_box;
  iface->get_graph          = gimp_image_get_graph;
  iface->invalidate_preview = (void (*) (GimpProjectable*)) gimp_viewable_invalidate_preview;
}

static void
gimp_pickable_iface_init (GimpPickableInterface *iface)
{
  iface->flush                 = gimp_image_pickable_flush;
  iface->get_image             = (GimpImage  * (*) (GimpPickable *pickable)) gimp_image_get_image;
  iface->get_format            = (const Babl * (*) (GimpPickable *pickable)) gimp_image_get_proj_format;
  iface->get_format_with_alpha = (const Babl * (*) (GimpPickable *pickable)) gimp_image_get_proj_format;
  iface->get_buffer            = gimp_image_get_buffer;
  iface->get_pixel_at          = gimp_image_get_pixel_at;
  iface->get_opacity_at        = gimp_image_get_opacity_at;
  iface->get_pixel_average     = gimp_image_get_pixel_average;
}

static void
gimp_image_init (GimpImage *image)
{
  GimpImagePrivate *private = gimp_image_get_instance_private (image);
  gint              i;

  image->priv = private;

  private->ID                  = 0;

  private->load_proc           = NULL;
  private->save_proc           = NULL;

  private->width               = 0;
  private->height              = 0;
  private->xresolution         = 1.0;
  private->yresolution         = 1.0;
  private->resolution_set      = FALSE;
  private->resolution_unit     = gimp_unit_inch ();
  private->base_type           = GIMP_RGB;
  private->precision           = GIMP_PRECISION_U8_NON_LINEAR;
  private->new_layer_mode      = -1;

  private->show_all            = 0;
  private->bounding_box.x      = 0;
  private->bounding_box.y      = 0;
  private->bounding_box.width  = 0;
  private->bounding_box.height = 0;
  private->pickable_buffer     = NULL;

  private->palette             = NULL;

  private->metadata            = NULL;

  private->simulation_profile  = NULL;
  private->simulation_intent   = GIMP_COLOR_RENDERING_INTENT_RELATIVE_COLORIMETRIC;
  private->simulation_bpc      = FALSE;

  private->dirty               = 1;
  private->dirty_time          = 0;
  private->undo_freeze_count   = 0;

  private->export_dirty        = 1;

  private->instance_count      = 0;
  private->disp_count          = 0;

  private->tattoo_state        = 0;

  private->projection          = gimp_projection_new (GIMP_PROJECTABLE (image));

  private->symmetries          = NULL;
  private->active_symmetry     = NULL;

  private->guides              = NULL;
  private->grid                = NULL;
  private->sample_points       = NULL;

  private->layers              = gimp_item_tree_new (image,
                                                     GIMP_TYPE_LAYER_STACK,
                                                     GIMP_TYPE_LAYER);
  private->channels            = gimp_item_tree_new (image,
                                                     GIMP_TYPE_DRAWABLE_STACK,
                                                     GIMP_TYPE_CHANNEL);
  private->paths               = gimp_item_tree_new (image,
                                                     GIMP_TYPE_ITEM_STACK,
                                                     GIMP_TYPE_PATH);
  private->layer_stack         = NULL;

  private->stored_layer_sets   = NULL;
  private->stored_channel_sets = NULL;
  private->stored_path_sets    = NULL;

  g_signal_connect (private->projection, "notify::buffer",
                    G_CALLBACK (gimp_image_projection_buffer_notify),
                    image);

  g_signal_connect (private->layers, "notify::selected-items",
                    G_CALLBACK (gimp_image_selected_layers_notify),
                    image);
  g_signal_connect (private->channels, "notify::selected-items",
                    G_CALLBACK (gimp_image_selected_channels_notify),
                    image);
  g_signal_connect (private->paths, "notify::selected-items",
                    G_CALLBACK (gimp_image_selected_paths_notify),
                    image);

  g_signal_connect_swapped (private->layers->container, "update",
                            G_CALLBACK (gimp_image_invalidate),
                            image);

  private->layer_offset_x_handler =
    gimp_container_add_handler (private->layers->container, "notify::offset-x",
                                G_CALLBACK (gimp_image_layer_offset_changed),
                                image);
  private->layer_offset_y_handler =
    gimp_container_add_handler (private->layers->container, "notify::offset-y",
                                G_CALLBACK (gimp_image_layer_offset_changed),
                                image);
  private->layer_bounding_box_handler =
    gimp_container_add_handler (private->layers->container, "bounding-box-changed",
                                G_CALLBACK (gimp_image_layer_bounding_box_changed),
                                image);
  private->layer_alpha_handler =
    gimp_container_add_handler (private->layers->container, "alpha-changed",
                                G_CALLBACK (gimp_image_layer_alpha_changed),
                                image);

  g_signal_connect (private->layers->container, "add",
                    G_CALLBACK (gimp_image_layers_changed),
                    image);
  g_signal_connect (private->layers->container, "remove",
                    G_CALLBACK (gimp_image_layers_changed),
                    image);

  g_signal_connect_swapped (private->channels->container, "update",
                            G_CALLBACK (gimp_image_invalidate),
                            image);

  private->channel_name_changed_handler =
    gimp_container_add_handler (private->channels->container, "name-changed",
                                G_CALLBACK (gimp_image_channel_name_changed),
                                image);
  private->channel_color_changed_handler =
    gimp_container_add_handler (private->channels->container, "color-changed",
                                G_CALLBACK (gimp_image_channel_color_changed),
                                image);

  g_signal_connect (private->channels->container, "add",
                    G_CALLBACK (gimp_image_channel_add),
                    image);
  g_signal_connect (private->channels->container, "remove",
                    G_CALLBACK (gimp_image_channel_remove),
                    image);

  private->floating_sel        = NULL;
  private->selection_mask      = NULL;

  private->parasites           = gimp_parasite_list_new ();

  for (i = 0; i < MAX_CHANNELS; i++)
    {
      private->visible[i] = TRUE;
      private->active[i]  = TRUE;
    }

  private->quick_mask_state    = FALSE;
  private->quick_mask_inverted = FALSE;
  /* Quick mask color set to sRGB red with half opacity by default. */
  private->quick_mask_color    = gegl_color_new ("red");
  gimp_color_set_alpha (private->quick_mask_color, 0.5);

  private->undo_stack          = gimp_undo_stack_new (image);
  private->redo_stack          = gimp_undo_stack_new (image);
  private->group_count         = 0;
  private->pushing_undo_group  = GIMP_UNDO_GROUP_NONE;

  private->flush_accum.alpha_changed              = FALSE;
  private->flush_accum.mask_changed               = FALSE;
  private->flush_accum.floating_selection_changed = FALSE;
  private->flush_accum.preview_invalidated        = FALSE;
}

static void
gimp_image_constructed (GObject *object)
{
  GimpImage        *image   = GIMP_IMAGE (object);
  GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image);
  GimpChannel      *selection;
  GimpCoreConfig   *config;
  GimpTemplate     *template;

  G_OBJECT_CLASS (parent_class)->constructed (object);

  gimp_assert (GIMP_IS_GIMP (image->gimp));

  config = image->gimp->config;

  private->ID = gimp_id_table_insert (image->gimp->image_table, image);

  template = config->default_image;

  private->xresolution     = gimp_template_get_resolution_x (template);
  private->yresolution     = gimp_template_get_resolution_y (template);
  private->resolution_unit = gimp_template_get_resolution_unit (template);

  private->grid = gimp_config_duplicate (GIMP_CONFIG (config->default_grid));

  g_clear_object (&private->quick_mask_color);
  private->quick_mask_color = gegl_color_duplicate (config->quick_mask_color);

  gimp_image_update_bounding_box (image);

  if (private->base_type == GIMP_INDEXED)
    gimp_image_colormap_init (image);

  selection = gimp_selection_new (image,
                                  gimp_image_get_width  (image),
                                  gimp_image_get_height (image));
  gimp_image_take_mask (image, selection);

  g_signal_connect_object (config, "notify::transparency-type",
                           G_CALLBACK (gimp_item_stack_invalidate_previews),
                           private->layers->container, G_CONNECT_SWAPPED);
  g_signal_connect_object (config, "notify::transparency-size",
                           G_CALLBACK (gimp_item_stack_invalidate_previews),
                           private->layers->container, G_CONNECT_SWAPPED);
  g_signal_connect_object (config, "notify::transparency-custom-color1",
                           G_CALLBACK (gimp_item_stack_invalidate_previews),
                           private->layers->container, G_CONNECT_SWAPPED);
  g_signal_connect_object (config, "notify::transparency-custom-color2",
                           G_CALLBACK (gimp_item_stack_invalidate_previews),
                           private->layers->container, G_CONNECT_SWAPPED);
  g_signal_connect_object (config, "notify::layer-previews",
                           G_CALLBACK (gimp_viewable_size_changed),
                           image, G_CONNECT_SWAPPED);
  g_signal_connect_object (config, "notify::group-layer-previews",
                           G_CALLBACK (gimp_viewable_size_changed),
                           image, G_CONNECT_SWAPPED);

  gimp_container_add (image->gimp->images, GIMP_OBJECT (image));
}

static void
gimp_image_set_property (GObject      *object,
                         guint         property_id,
                         const GValue *value,
                         GParamSpec   *pspec)
{
  GimpImage        *image   = GIMP_IMAGE (object);
  GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image);

  switch (property_id)
    {
    case PROP_GIMP:
      image->gimp = g_value_get_object (value);
      break;

    case PROP_WIDTH:
      private->width = g_value_get_int (value);
      break;
    case PROP_HEIGHT:
      private->height = g_value_get_int (value);
      break;

    case PROP_BASE_TYPE:
      private->base_type = g_value_get_enum (value);
      _gimp_image_free_color_transforms (image);
      break;

    case PROP_PRECISION:
      private->precision = g_value_get_enum (value);
      _gimp_image_free_color_transforms (image);
      break;

    case PROP_SYMMETRY:
      {
        GList *iter;
        GType  type = g_value_get_gtype (value);

        if (private->active_symmetry)
          g_object_set (private->active_symmetry,
                        "active", FALSE,
                        NULL);
        private->active_symmetry = NULL;

        for (iter = private->symmetries; iter; iter = g_list_next (iter))
          {
            GimpSymmetry *sym = iter->data;

            if (type == G_TYPE_FROM_INSTANCE (sym))
              private->active_symmetry = iter->data;
          }

        if (! private->active_symmetry &&
            g_type_is_a (type, GIMP_TYPE_SYMMETRY))
          {
            GimpSymmetry *sym = gimp_image_symmetry_new (image, type);

            gimp_image_symmetry_add (image, sym);
            g_object_unref (sym);

            private->active_symmetry = sym;
          }

        if (private->active_symmetry)
          g_object_set (private->active_symmetry,
                        "active", TRUE,
                        NULL);
      }
      break;

    case PROP_CONVERTING:
      private->converting = g_value_get_boolean (value);
      break;

    case PROP_ID:
    case PROP_METADATA:
    case PROP_BUFFER:
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

static void
gimp_image_get_property (GObject    *object,
                         guint       property_id,
                         GValue     *value,
                         GParamSpec *pspec)
{
  GimpImage        *image   = GIMP_IMAGE (object);
  GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image);

  switch (property_id)
    {
    case PROP_GIMP:
      g_value_set_object (value, image->gimp);
      break;
    case PROP_ID:
      g_value_set_int (value, private->ID);
      break;
    case PROP_WIDTH:
      g_value_set_int (value, private->width);
      break;
    case PROP_HEIGHT:
      g_value_set_int (value, private->height);
      break;
    case PROP_BASE_TYPE:
      g_value_set_enum (value, private->base_type);
      break;
    case PROP_PRECISION:
      g_value_set_enum (value, private->precision);
      break;
    case PROP_METADATA:
      g_value_set_object (value, gimp_image_get_metadata (image));
      break;
    case PROP_BUFFER:
      g_value_set_object (value, gimp_image_get_buffer (GIMP_PICKABLE (image)));
      break;
    case PROP_SYMMETRY:
      g_value_set_gtype (value,
                         private->active_symmetry ?
                         G_TYPE_FROM_INSTANCE (private->active_symmetry) :
                         G_TYPE_NONE);
      break;
    case PROP_CONVERTING:
      g_value_set_boolean (value, private->converting);
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

static void
gimp_image_dispose (GObject *object)
{
  GimpImage        *image   = GIMP_IMAGE (object);
  GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image);

  if (private->palette)
    gimp_image_colormap_dispose (image);

  gimp_image_undo_free (image);

  g_list_free_full (private->stored_layer_sets,   g_object_unref);
  g_list_free_full (private->stored_channel_sets, g_object_unref);
  g_list_free_full (private->stored_path_sets,    g_object_unref);

  g_signal_handlers_disconnect_by_func (private->layers->container,
                                        gimp_image_invalidate,
                                        image);

  gimp_container_remove_handler (private->layers->container,
                                 private->layer_offset_x_handler);
  gimp_container_remove_handler (private->layers->container,
                                 private->layer_offset_y_handler);
  gimp_container_remove_handler (private->layers->container,
                                 private->layer_bounding_box_handler);
  gimp_container_remove_handler (private->layers->container,
                                 private->layer_alpha_handler);

  g_signal_handlers_disconnect_by_func (private->layers->container,
                                        gimp_image_layers_changed,
                                        image);

  g_signal_handlers_disconnect_by_func (private->channels->container,
                                        gimp_image_invalidate,
                                        image);

  gimp_container_remove_handler (private->channels->container,
                                 private->channel_name_changed_handler);
  gimp_container_remove_handler (private->channels->container,
                                 private->channel_color_changed_handler);

  g_signal_handlers_disconnect_by_func (private->channels->container,
                                        gimp_image_channel_add,
                                        image);
  g_signal_handlers_disconnect_by_func (private->channels->container,
                                        gimp_image_channel_remove,
                                        image);

  g_object_run_dispose (G_OBJECT (private->layers));
  g_object_run_dispose (G_OBJECT (private->channels));
  g_object_run_dispose (G_OBJECT (private->paths));

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

static void
gimp_image_finalize (GObject *object)
{
  GimpImage        *image   = GIMP_IMAGE (object);
  GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image);

  g_clear_object (&private->projection);
  g_clear_object (&private->graph);
  private->visible_mask = NULL;

  if (private->palette)
    gimp_image_colormap_free (image);

  _gimp_image_free_color_profile (image);

  g_clear_object (&private->pickable_buffer);
  g_clear_object (&private->metadata);
  g_clear_object (&private->file);
  g_clear_object (&private->imported_file);
  g_clear_object (&private->exported_file);
  g_clear_object (&private->save_a_copy_file);
  g_clear_object (&private->untitled_file);
  g_clear_object (&private->layers);
  g_clear_object (&private->channels);
  g_clear_object (&private->paths);
  g_clear_object (&private->quick_mask_color);
  g_clear_pointer (&private->quick_mask_selected, g_list_free);

  if (private->layer_stack)
    {
      g_slist_free_full (private->layer_stack,
                         (GDestroyNotify) g_list_free);
      private->layer_stack = NULL;
    }

  g_clear_object (&private->selection_mask);
  g_clear_object (&private->parasites);

  if (private->guides)
    {
      g_list_free_full (private->guides, (GDestroyNotify) g_object_unref);
      private->guides = NULL;
    }

  if (private->symmetries)
    {
      g_list_free_full (private->symmetries, g_object_unref);
      private->symmetries = NULL;
    }

  g_clear_object (&private->grid);

  if (private->sample_points)
    {
      g_list_free_full (private->sample_points,
                        (GDestroyNotify) g_object_unref);
      private->sample_points = NULL;
    }

  g_clear_object (&private->undo_stack);
  g_clear_object (&private->redo_stack);

  if (image->gimp && image->gimp->image_table)
    {
      gimp_id_table_remove (image->gimp->image_table, private->ID);
      image->gimp = NULL;
    }

  g_clear_pointer (&private->display_name, g_free);
  g_clear_pointer (&private->display_path, g_free);

  G_OBJECT_CLASS (parent_class)->finalize (object);

  /* Don't just free the list silently. It is supposed to be empty.
   * Instead just assert this fact to warn on improper management of
   * hidden items.
   */

  if (private->hidden_items != NULL)
    {
      g_warning ("%s: the hidden items list should be empty (%d items remaining).",
                 G_STRFUNC, g_list_length (private->hidden_items));
      g_list_free (private->hidden_items);
    }
}

static void
gimp_image_name_changed (GimpObject *object)
{
  GimpImage        *image   = GIMP_IMAGE (object);
  GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image);
  const gchar      *name;

  if (GIMP_OBJECT_CLASS (parent_class)->name_changed)
    GIMP_OBJECT_CLASS (parent_class)->name_changed (object);

  g_clear_pointer (&private->display_name, g_free);
  g_clear_pointer (&private->display_path, g_free);

  /* We never want the empty string as a name, so change empty strings
   * to NULL strings (without emitting the "name-changed" signal
   * again)
   */

  name = gimp_object_get_name (object);
  if (name && strlen (name) == 0)
    {
      gimp_object_name_free (object);
      name = NULL;
    }

  g_clear_object (&private->file);

  if (name)
    private->file = g_file_new_for_uri (name);
}

static gint64
gimp_image_get_memsize (GimpObject *object,
                        gint64     *gui_size)
{
  GimpImage        *image   = GIMP_IMAGE (object);
  GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image);
  gint64            memsize = 0;

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

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

  memsize += gimp_g_list_get_memsize (gimp_image_get_guides (image),
                                      sizeof (GimpGuide));

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

  memsize += gimp_g_list_get_memsize (gimp_image_get_sample_points (image),
                                      sizeof (GimpSamplePoint));

  memsize += gimp_object_get_memsize (GIMP_OBJECT (private->layers),
                                      gui_size);
  memsize += gimp_object_get_memsize (GIMP_OBJECT (private->channels),
                                      gui_size);
  memsize += gimp_object_get_memsize (GIMP_OBJECT (private->paths),
                                      gui_size);

  memsize += gimp_g_slist_get_memsize (private->layer_stack, 0);

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

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

  memsize += gimp_object_get_memsize (GIMP_OBJECT (private->undo_stack),
                                      gui_size);
  memsize += gimp_object_get_memsize (GIMP_OBJECT (private->redo_stack),
                                      gui_size);

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

static gboolean
gimp_image_get_size (GimpViewable *viewable,
                     gint         *width,
                     gint         *height)
{
  GimpImage *image = GIMP_IMAGE (viewable);

  *width  = gimp_image_get_width  (image);
  *height = gimp_image_get_height (image);

  return TRUE;
}

static void
gimp_image_size_changed (GimpViewable *viewable)
{
  GimpImage *image = GIMP_IMAGE (viewable);
  GList     *all_items;
  GList     *list;

  if (GIMP_VIEWABLE_CLASS (parent_class)->size_changed)
    GIMP_VIEWABLE_CLASS (parent_class)->size_changed (viewable);

  all_items = gimp_image_get_layer_list (image);
  for (list = all_items; list; list = g_list_next (list))
    {
      GimpLayerMask *mask = gimp_layer_get_mask (GIMP_LAYER (list->data));

      gimp_viewable_size_changed (GIMP_VIEWABLE (list->data));

      if (mask)
        gimp_viewable_size_changed (GIMP_VIEWABLE (mask));
    }
  g_list_free (all_items);

  all_items = gimp_image_get_channel_list (image);
  g_list_free_full (all_items, (GDestroyNotify) gimp_viewable_size_changed);

  all_items = gimp_image_get_path_list (image);
  g_list_free_full (all_items, (GDestroyNotify) gimp_viewable_size_changed);

  gimp_viewable_size_changed (GIMP_VIEWABLE (gimp_image_get_mask (image)));

  gimp_image_metadata_update_pixel_size (image);

  g_clear_object (&GIMP_IMAGE_GET_PRIVATE (image)->pickable_buffer);

  gimp_image_update_bounding_box (image);
}

static gchar *
gimp_image_get_description (GimpViewable  *viewable,
                            gchar        **tooltip)
{
  GimpImage *image = GIMP_IMAGE (viewable);

  if (tooltip)
    *tooltip = g_strdup (gimp_image_get_display_path (image));

  return g_strdup_printf ("%s-%d",
                          gimp_image_get_display_name (image),
                          gimp_image_get_id (image));
}

static void
gimp_image_real_mode_changed (GimpImage *image)
{
  gimp_projectable_structure_changed (GIMP_PROJECTABLE (image));
}

static void
gimp_image_real_precision_changed (GimpImage *image)
{
  gimp_image_metadata_update_bits_per_sample (image);

  gimp_projectable_structure_changed (GIMP_PROJECTABLE (image));
}

static void
gimp_image_real_resolution_changed (GimpImage *image)
{
  gimp_image_metadata_update_resolution (image);
}

static void
gimp_image_real_size_changed_detailed (GimpImage *image,
                                       gint       previous_origin_x,
                                       gint       previous_origin_y,
                                       gint       previous_width,
                                       gint       previous_height)
{
  /* Whenever GimpImage::size-changed-detailed is emitted, so is
   * GimpViewable::size-changed. Clients choose what signal to listen
   * to depending on how much info they need.
   */

  gimp_viewable_size_changed (GIMP_VIEWABLE (image));
}

static void
gimp_image_real_unit_changed (GimpImage *image)
{
  gimp_image_metadata_update_resolution (image);
}

static void
gimp_image_real_colormap_changed (GimpImage *image,
                                  gint       color_index)
{
  GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image);

  gimp_image_colormap_update_formats (image);

  if (gimp_image_get_base_type (image) == GIMP_INDEXED)
    {
      /* A colormap alteration affects the whole image */
      gimp_image_invalidate_all (image);

      gimp_item_stack_invalidate_previews (GIMP_ITEM_STACK (private->layers->container));
    }
}

static const guint8 *
gimp_image_color_managed_get_icc_profile (GimpColorManaged *managed,
                                          gsize            *len)
{
  return gimp_image_get_icc_profile (GIMP_IMAGE (managed), len);
}

static GimpColorProfile *
gimp_image_color_managed_get_color_profile (GimpColorManaged *managed)
{
  GimpImage        *image = GIMP_IMAGE (managed);
  GimpColorProfile *profile;

  profile = gimp_image_get_color_profile (image);

  if (! profile)
    profile = gimp_image_get_builtin_color_profile (image);

  return profile;
}

static void
gimp_image_color_managed_profile_changed (GimpColorManaged *managed)
{
  GimpImage     *image  = GIMP_IMAGE (managed);
  GimpItemStack *layers = GIMP_ITEM_STACK (gimp_image_get_layers (image));

  gimp_image_metadata_update_colorspace (image);

  gimp_projectable_structure_changed (GIMP_PROJECTABLE (image));
  gimp_viewable_invalidate_preview (GIMP_VIEWABLE (image));
  gimp_item_stack_profile_changed (layers);
}

static GimpColorProfile *
gimp_image_color_managed_get_simulation_profile (GimpColorManaged *managed)
{
  GimpImage        *image = GIMP_IMAGE (managed);
  GimpColorProfile *profile;

  profile = gimp_image_get_simulation_profile (image);

  return profile;
}

static void
gimp_image_color_managed_simulation_profile_changed (GimpColorManaged *managed)
{
  GimpImage *image = GIMP_IMAGE (managed);

  gimp_projectable_structure_changed (GIMP_PROJECTABLE (image));
  gimp_viewable_invalidate_preview (GIMP_VIEWABLE (image));
}

static GimpColorRenderingIntent
gimp_image_color_managed_get_simulation_intent (GimpColorManaged *managed)
{
  GimpImage *image = GIMP_IMAGE (managed);

  return gimp_image_get_simulation_intent (image);
}

static void
gimp_image_color_managed_simulation_intent_changed (GimpColorManaged *managed)
{
  GimpImage *image = GIMP_IMAGE (managed);

  gimp_projectable_structure_changed (GIMP_PROJECTABLE (image));
  gimp_viewable_invalidate_preview (GIMP_VIEWABLE (image));
}

static gboolean
gimp_image_color_managed_get_simulation_bpc (GimpColorManaged *managed)
{
  GimpImage *image = GIMP_IMAGE (managed);

  return gimp_image_get_simulation_bpc (image);
}

static void
gimp_image_color_managed_simulation_bpc_changed (GimpColorManaged *managed)
{
  GimpImage *image = GIMP_IMAGE (managed);

  gimp_projectable_structure_changed (GIMP_PROJECTABLE (image));
  gimp_viewable_invalidate_preview (GIMP_VIEWABLE (image));
}

static void
gimp_image_projectable_flush (GimpProjectable *projectable,
                              gboolean         invalidate_preview)
{
  GimpImage        *image   = GIMP_IMAGE (projectable);
  GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image);

  if (private->flush_accum.alpha_changed)
    {
      gimp_image_alpha_changed (image);
      private->flush_accum.alpha_changed = FALSE;
    }

  if (private->flush_accum.mask_changed)
    {
      gimp_image_mask_changed (image);
      private->flush_accum.mask_changed = FALSE;
    }

  if (private->flush_accum.floating_selection_changed)
    {
      gimp_image_floating_selection_changed (image);
      private->flush_accum.floating_selection_changed = FALSE;
    }

  if (private->flush_accum.preview_invalidated)
    {
      /*  don't invalidate the preview here, the projection does this when
       *  it is completely constructed.
       */

      private->flush_accum.preview_invalidated = FALSE;
    }
}

static GimpImage *
gimp_image_get_image (GimpProjectable *projectable)
{
  return GIMP_IMAGE (projectable);
}

static const Babl *
gimp_image_get_proj_format (GimpProjectable *projectable)
{
  GimpImage        *image   = GIMP_IMAGE (projectable);
  GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image);

  switch (private->base_type)
    {
    case GIMP_RGB:
    case GIMP_INDEXED:
      return gimp_image_get_format (image, GIMP_RGB,
                                    gimp_image_get_precision (image), TRUE,
                                    gimp_image_get_layer_space (image));

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

  g_return_val_if_reached (NULL);
}

static void
gimp_image_pickable_flush (GimpPickable *pickable)
{
  GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (pickable);

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

static GeglBuffer *
gimp_image_get_buffer (GimpPickable *pickable)
{
  GimpImage        *image   = GIMP_IMAGE (pickable);
  GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image);

  if (! private->pickable_buffer)
    {
      GeglBuffer *buffer;

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

      if (! private->show_all)
        {
          private->pickable_buffer = g_object_ref (buffer);
        }
      else
        {
          private->pickable_buffer = gegl_buffer_create_sub_buffer (
            buffer,
            GEGL_RECTANGLE (0, 0,
                            gimp_image_get_width  (image),
                            gimp_image_get_height (image)));
        }
    }

  return private->pickable_buffer;
}

static gboolean
gimp_image_get_pixel_at (GimpPickable *pickable,
                         gint          x,
                         gint          y,
                         const Babl   *format,
                         gpointer      pixel)
{
  GimpImage        *image   = GIMP_IMAGE (pickable);
  GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image);

  if (x >= 0                            &&
      y >= 0                            &&
      x < gimp_image_get_width  (image) &&
      y < gimp_image_get_height (image))
    {
      return gimp_pickable_get_pixel_at (GIMP_PICKABLE (private->projection),
                                         x, y, format, pixel);
    }

  return FALSE;
}

static gdouble
gimp_image_get_opacity_at (GimpPickable *pickable,
                           gint          x,
                           gint          y)
{
  GimpImage        *image   = GIMP_IMAGE (pickable);
  GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image);

  if (x >= 0                            &&
      y >= 0                            &&
      x < gimp_image_get_width  (image) &&
      y < gimp_image_get_height (image))
    {
      return gimp_pickable_get_opacity_at (GIMP_PICKABLE (private->projection),
                                           x, y);
    }

  return FALSE;
}

static void
gimp_image_get_pixel_average (GimpPickable        *pickable,
                              const GeglRectangle *rect,
                              const Babl          *format,
                              gpointer             pixel)
{
  GeglBuffer *buffer = gimp_pickable_get_buffer (pickable);

  return gimp_gegl_average_color (buffer, rect, TRUE, GEGL_ABYSS_NONE, format,
                                  pixel);
}

static GeglRectangle
gimp_image_get_bounding_box (GimpProjectable *projectable)
{
  GimpImage *image = GIMP_IMAGE (projectable);

  return GIMP_IMAGE_GET_PRIVATE (image)->bounding_box;
}

static GeglNode *
gimp_image_get_graph (GimpProjectable *projectable)
{
  GimpImage         *image   = GIMP_IMAGE (projectable);
  GimpImagePrivate  *private = GIMP_IMAGE_GET_PRIVATE (image);
  GeglNode          *layers_node;
  GeglNode          *channels_node;
  GeglNode          *output;
  GimpComponentMask  mask;

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

  private->graph = gegl_node_new ();

  layers_node =
    gimp_filter_stack_get_graph (GIMP_FILTER_STACK (private->layers->container));

  gegl_node_add_child (private->graph, layers_node);

  mask = ~gimp_image_get_visible_mask (image) & GIMP_COMPONENT_MASK_ALL;

  private->visible_mask =
    gegl_node_new_child (private->graph,
                         "operation", "gimp:mask-components",
                         "mask",      mask,
                         "alpha",     1.0,
                         NULL);

  gegl_node_link (layers_node, private->visible_mask);

  channels_node =
    gimp_filter_stack_get_graph (GIMP_FILTER_STACK (private->channels->container));

  gegl_node_add_child (private->graph, channels_node);

  gegl_node_link (private->visible_mask, channels_node);

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

  gegl_node_link (channels_node, output);

  return private->graph;
}

static void
gimp_image_projection_buffer_notify (GimpProjection   *projection,
                                     const GParamSpec *pspec,
                                     GimpImage        *image)
{
  g_clear_object (&GIMP_IMAGE_GET_PRIVATE (image)->pickable_buffer);
}

static void
gimp_image_mask_update (GimpDrawable *drawable,
                        gint          x,
                        gint          y,
                        gint          width,
                        gint          height,
                        GimpImage    *image)
{
  GIMP_IMAGE_GET_PRIVATE (image)->flush_accum.mask_changed = TRUE;
}

static void
gimp_image_layers_changed (GimpContainer *container,
                           GimpChannel   *channel,
                           GimpImage     *image)
{
  gimp_image_update_bounding_box (image);
}

static void
gimp_image_layer_offset_changed (GimpDrawable     *drawable,
                                 const GParamSpec *pspec,
                                 GimpImage        *image)
{
  gimp_image_update_bounding_box (image);
}

static void
gimp_image_layer_bounding_box_changed (GimpDrawable *drawable,
                                       GimpImage    *image)
{
  gimp_image_update_bounding_box (image);
}

static void
gimp_image_layer_alpha_changed (GimpDrawable *drawable,
                                GimpImage    *image)
{
  GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image);

  if (gimp_container_get_n_children (private->layers->container) == 1)
    private->flush_accum.alpha_changed = TRUE;
}

static void
gimp_image_channel_add (GimpContainer *container,
                        GimpChannel   *channel,
                        GimpImage     *image)
{
  if (! strcmp (GIMP_IMAGE_QUICK_MASK_NAME,
                gimp_object_get_name (channel)))
    {
      gimp_image_set_quick_mask_state (image, TRUE);
    }
}

static void
gimp_image_channel_remove (GimpContainer *container,
                           GimpChannel   *channel,
                           GimpImage     *image)
{
  if (! strcmp (GIMP_IMAGE_QUICK_MASK_NAME,
                gimp_object_get_name (channel)))
    {
      gimp_image_set_quick_mask_state (image, FALSE);
    }
}

static void
gimp_image_channel_name_changed (GimpChannel *channel,
                                 GimpImage   *image)
{
  if (! strcmp (GIMP_IMAGE_QUICK_MASK_NAME,
                gimp_object_get_name (channel)))
    {
      gimp_image_set_quick_mask_state (image, TRUE);
    }
  else if (gimp_image_get_quick_mask_state (image) &&
           ! gimp_image_get_quick_mask (image))
    {
      gimp_image_set_quick_mask_state (image, FALSE);
    }
}

static void
gimp_image_channel_color_changed (GimpChannel *channel,
                                  GimpImage   *image)
{
  if (! strcmp (GIMP_IMAGE_QUICK_MASK_NAME,
                gimp_object_get_name (channel)))
    {
      g_clear_object (&(GIMP_IMAGE_GET_PRIVATE (image)->quick_mask_color));
      GIMP_IMAGE_GET_PRIVATE (image)->quick_mask_color = gegl_color_duplicate (channel->color);
    }
}

static void
gimp_image_selected_layers_notify (GimpItemTree     *tree,
                                   const GParamSpec *pspec,
                                   GimpImage        *image)
{
  GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image);
  GList            *layers  = gimp_image_get_selected_layers (image);

  if (layers)
    {
      /*  Configure the layer stack to reflect this change  */
      GSList *prev_layers;

      while ((prev_layers = g_slist_find_custom (private->layer_stack, layers,
                                                (GCompareFunc) gimp_image_layer_stack_cmp)))
        {
          g_list_free (prev_layers->data);
          private->layer_stack = g_slist_delete_link (private->layer_stack,
                                                      prev_layers);
        }
      private->layer_stack = g_slist_prepend (private->layer_stack, g_list_copy (layers));
    }

  if (layers && gimp_image_get_selected_channels (image))
    gimp_image_set_selected_channels (image, NULL);

  g_signal_emit (image, gimp_image_signals[SELECTED_LAYERS_CHANGED], 0);
}

static void
gimp_image_selected_channels_notify (GimpItemTree     *tree,
                                     const GParamSpec *pspec,
                                     GimpImage        *image)
{
  GList *channels = gimp_image_get_selected_channels (image);

  g_signal_emit (image, gimp_image_signals[SELECTED_CHANNELS_CHANGED], 0);

  if (channels && gimp_image_get_selected_layers (image))
    gimp_image_set_selected_layers (image, NULL);
}

static void
gimp_image_selected_paths_notify (GimpItemTree     *tree,
                                  const GParamSpec *pspec,
                                  GimpImage        *image)
{
  g_signal_emit (image, gimp_image_signals[SELECTED_PATHS_CHANGED], 0);
}

static void
gimp_image_freeze_bounding_box (GimpImage *image)
{
  GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image);

  private->bounding_box_freeze_count++;
}

static void
gimp_image_thaw_bounding_box (GimpImage *image)
{
  GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image);

  private->bounding_box_freeze_count--;

  if (private->bounding_box_freeze_count == 0 &&
      private->bounding_box_update_pending)
    {
      private->bounding_box_update_pending = FALSE;

      gimp_image_update_bounding_box (image);
    }
}

static void
gimp_image_update_bounding_box (GimpImage *image)
{
  GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image);
  GeglRectangle     bounding_box;

  if (private->bounding_box_freeze_count > 0)
    {
      private->bounding_box_update_pending = TRUE;

      return;
    }

  bounding_box.x      = 0;
  bounding_box.y      = 0;
  bounding_box.width  = gimp_image_get_width  (image);
  bounding_box.height = gimp_image_get_height (image);

  if (private->show_all)
    {
      GList *iter;

      for (iter = gimp_image_get_layer_iter (image);
           iter;
           iter = g_list_next (iter))
        {
          GimpLayer     *layer = iter->data;
          GeglRectangle  layer_bounding_box;
          gint           offset_x;
          gint           offset_y;

          gimp_item_get_offset (GIMP_ITEM (layer), &offset_x, &offset_y);

          layer_bounding_box = gimp_drawable_get_bounding_box (
            GIMP_DRAWABLE (layer));

          layer_bounding_box.x += offset_x;
          layer_bounding_box.y += offset_y;

          gegl_rectangle_bounding_box (&bounding_box,
                                       &bounding_box, &layer_bounding_box);
        }
    }

  if (! gegl_rectangle_equal (&bounding_box, &private->bounding_box))
    {
      private->bounding_box = bounding_box;

      gimp_projectable_bounds_changed (GIMP_PROJECTABLE (image), 0, 0);
    }
}

static gint
gimp_image_layer_stack_cmp (GList *layers1,
                            GList *layers2)
{
  if (g_list_length (layers1) != g_list_length (layers2))
    {
      /* We don't really need to order lists of layers, and only care
       * about identity.
       */

      return 1;
    }
  else
    {
      GList *iter;

      for (iter = layers1; iter; iter = iter->next)
        {
          if (! g_list_find (layers2, iter->data))
            return 1;
        }
      return 0;
    }
}

/**
 * gimp_image_rec_remove_layer_stack_dups:
 * @image:
 *
 * Recursively remove duplicates from the layer stack. You should not
 * call this directly, call gimp_image_clean_layer_stack() instead.
 */

static void
gimp_image_rec_remove_layer_stack_dups (GimpImage *image,
                                        GSList    *start)
{
  GimpImagePrivate *private;
  GSList           *dup_layers;

  g_return_if_fail (GIMP_IS_IMAGE (image));

  private = GIMP_IMAGE_GET_PRIVATE (image);

  if (start == NULL || start->next == NULL)
    return;

  while ((dup_layers = g_slist_find_custom (start->next, start->data,
                                            (GCompareFunc) gimp_image_layer_stack_cmp)))
    {
      g_list_free (dup_layers->data);
      /* We can safely remove the duplicate then search again because we
       * know that @start is never removed as we search after it.
       */

      private->layer_stack = g_slist_delete_link (private->layer_stack,
                                                  dup_layers);
    }

  gimp_image_rec_remove_layer_stack_dups (image, start->next);
}

/**
 * gimp_image_clean_layer_stack:
 * @image:
 *
 * Remove any duplicate and empty selections in the layer stack.
 */

static void
gimp_image_clean_layer_stack (GimpImage *image)
{
  GimpImagePrivate *private;

  g_return_if_fail (GIMP_IS_IMAGE (image));

  private = GIMP_IMAGE_GET_PRIVATE (image);

  /* First remove all empty layer lists. */
  private->layer_stack = g_slist_remove_all (private->layer_stack, NULL);
  /* Then remove all duplicates. */
  gimp_image_rec_remove_layer_stack_dups (image, private->layer_stack);
}

static void
gimp_image_rec_filter_remove_undo (GimpImage *image,
                                   GimpLayer *layer)
{
  GimpContainer *filters;

  if (gimp_viewable_get_children (GIMP_VIEWABLE (layer)))
    {
      GimpContainer *stack = gimp_viewable_get_children (GIMP_VIEWABLE (layer));
      GList         *children;
      GList         *iter;

      children = gimp_item_stack_get_item_iter (GIMP_ITEM_STACK (stack));

      for (iter = children; iter; iter = iter->next)
        {
          GimpLayer *child = iter->data;

          gimp_image_rec_filter_remove_undo (image, child);
        }
    }

  filters = gimp_drawable_get_filters (GIMP_DRAWABLE (layer));

  if (gimp_container_get_n_children (filters) > 0)
    {
      GList *filter_list;

      for (filter_list = GIMP_LIST (filters)->queue->tail; filter_list;
           filter_list = g_list_previous (filter_list))
        {
          if (GIMP_IS_DRAWABLE_FILTER (filter_list->data) &&
              ! gimp_drawable_filter_get_temporary (filter_list->data))
            {
              GimpDrawableFilter *filter = filter_list->data;

              gimp_image_undo_push_filter_remove (image,
                                                  _("Remove filter"),
                                                  GIMP_DRAWABLE (layer),
                                                  filter);
            }
        }
    }
}

static void
gimp_image_remove_from_layer_stack (GimpImage *image,
                                    GimpLayer *layer)
{
  GimpImagePrivate *private;
  GSList           *slist;

  g_return_if_fail (GIMP_IS_IMAGE (image));
  g_return_if_fail (GIMP_IS_LAYER (layer));

  private = GIMP_IMAGE_GET_PRIVATE (image);

  /* Remove layer itself from the MRU layer stack. */
  for (slist = private->layer_stack; slist; slist = slist->next)
    slist->data = g_list_remove (slist->data, layer);

  /*  Also remove all children of a group layer from the layer_stack  */
  if (gimp_viewable_get_children (GIMP_VIEWABLE (layer)))
    {
      GimpContainer *stack = gimp_viewable_get_children (GIMP_VIEWABLE (layer));
      GList         *children;
      GList         *list;

      children = gimp_item_stack_get_item_list (GIMP_ITEM_STACK (stack));

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

          for (slist = private->layer_stack; slist; slist = slist->next)
            slist->data = g_list_remove (slist->data, child);
        }

      g_list_free (children);
    }

  gimp_image_clean_layer_stack (image);
}

static gint
gimp_image_selected_is_descendant (GimpViewable *selected,
                                   GimpViewable *viewable)
{
  /* Used as a GCompareFunc to g_list_find_custom() in order to know if
   * one of the selected items is a descendant to @viewable.
   */

  if (gimp_viewable_is_ancestor (viewable, selected))
    return 0;
  else
    return 1;
}


/*  public functions  */

GimpImage *
gimp_image_new (Gimp              *gimp,
                gint               width,
                gint               height,
                GimpImageBaseType  base_type,
                GimpPrecision      precision)
{
  g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
  g_return_val_if_fail (gimp_babl_is_valid (base_type, precision), NULL);

  return g_object_new (GIMP_TYPE_IMAGE,
                       "gimp",      gimp,
                       "width",     width,
                       "height",    height,
                       "base-type", base_type,
                       "precision", precision,
                       NULL);
}

gint64
gimp_image_estimate_memsize (GimpImage         *image,
                             GimpComponentType  component_type,
                             gint               width,
                             gint               height)
{
  GList  *drawables;
  GList  *list;
  gint    current_width;
  gint    current_height;
  gint64  current_size;
  gint64  scalable_size = 0;
  gint64  scaled_size   = 0;
  gint64  new_size;

  g_return_val_if_fail (GIMP_IS_IMAGE (image), 0);

  current_width  = gimp_image_get_width (image);
  current_height = gimp_image_get_height (image);
  current_size   = gimp_object_get_memsize (GIMP_OBJECT (image), NULL);

  /*  the part of the image's memsize that scales linearly with the image  */
  drawables = gimp_image_item_list_get_list (image,
                                             GIMP_ITEM_TYPE_LAYERS |
                                             GIMP_ITEM_TYPE_CHANNELS,
                                             GIMP_ITEM_SET_ALL);

  gimp_image_item_list_filter (drawables);

  drawables = g_list_prepend (drawables, gimp_image_get_mask (image));

  for (list = drawables; list; list = g_list_next (list))
    {
      GimpDrawable *drawable = list->data;
      gdouble       drawable_width;
      gdouble       drawable_height;

      drawable_width  = gimp_item_get_width  (GIMP_ITEM (drawable));
      drawable_height = gimp_item_get_height (GIMP_ITEM (drawable));

      scalable_size += gimp_drawable_estimate_memsize (drawable,
                                                       gimp_drawable_get_component_type (drawable),
                                                       drawable_width,
                                                       drawable_height);

      scaled_size += gimp_drawable_estimate_memsize (drawable,
                                                     component_type,
                                                     drawable_width * width /
                                                     current_width,
                                                     drawable_height * height /
                                                     current_height);
    }

  g_list_free (drawables);

  scalable_size +=
    gimp_projection_estimate_memsize (gimp_image_get_base_type (image),
                                      gimp_image_get_component_type (image),
                                      gimp_image_get_width (image),
                                      gimp_image_get_height (image));

  scaled_size +=
    gimp_projection_estimate_memsize (gimp_image_get_base_type (image),
                                      component_type,
                                      width, height);

  GIMP_LOG (IMAGE_SCALE,
            "scalable_size = %"G_GINT64_FORMAT"  scaled_size = %"G_GINT64_FORMAT,
            scalable_size, scaled_size);

  new_size = current_size - scalable_size + scaled_size;

  return new_size;
}

GimpImageBaseType
gimp_image_get_base_type (GimpImage *image)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (image), -1);

  return GIMP_IMAGE_GET_PRIVATE (image)->base_type;
}

GimpComponentType
gimp_image_get_component_type (GimpImage *image)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (image), -1);

  return gimp_babl_component_type (GIMP_IMAGE_GET_PRIVATE (image)->precision);
}

GimpPrecision
gimp_image_get_precision (GimpImage *image)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (image), -1);

  return GIMP_IMAGE_GET_PRIVATE (image)->precision;
}

const Babl *
gimp_image_get_format (GimpImage         *image,
                       GimpImageBaseType  base_type,
                       GimpPrecision      precision,
                       gboolean           with_alpha,
                       const Babl        *space)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);

  switch (base_type)
    {
    case GIMP_RGB:
    case GIMP_GRAY:
      return gimp_babl_format (base_type, precision, with_alpha, space);

    case GIMP_INDEXED:
      if (precision == GIMP_PRECISION_U8_NON_LINEAR)
        {
          if (with_alpha)
            return gimp_image_colormap_get_rgba_format (image);
          else
            return gimp_image_colormap_get_rgb_format (image);
        }
    }

  g_return_val_if_reached (NULL);
}

const Babl *
gimp_image_get_layer_space (GimpImage *image)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);

  return GIMP_IMAGE_GET_PRIVATE (image)->layer_space;
}

const Babl *
gimp_image_get_layer_format (GimpImage *image,
                             gboolean   with_alpha)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);

  return gimp_image_get_format (image,
                                gimp_image_get_base_type (image),
                                gimp_image_get_precision (image),
                                with_alpha,
                                gimp_image_get_layer_space (image));
}

const Babl *
gimp_image_get_channel_format (GimpImage *image)
{
  GimpPrecision precision;

  g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);

  precision = gimp_image_get_precision (image);

  if (precision == GIMP_PRECISION_U8_NON_LINEAR)
    return gimp_image_get_format (image, GIMP_GRAY,
                                  gimp_image_get_precision (image),
                                  FALSE, NULL);

  return gimp_babl_mask_format (precision);
}

const Babl *
gimp_image_get_mask_format (GimpImage *image)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);

  return gimp_babl_mask_format (gimp_image_get_precision (image));
}

GimpLayerMode
gimp_image_get_default_new_layer_mode (GimpImage *image)
{
  GimpImagePrivate *private;

  g_return_val_if_fail (GIMP_IS_IMAGE (image), GIMP_LAYER_MODE_NORMAL);

  private = GIMP_IMAGE_GET_PRIVATE (image);

  if (private->new_layer_mode == -1)
    {
      GList *layers = gimp_image_get_layer_list (image);

      if (layers)
        {
          GList *list;

          for (list = layers; list; list = g_list_next (list))
            {
              GimpLayer     *layer = list->data;
              GimpLayerMode  mode  = gimp_layer_get_mode (layer);

              if (! gimp_layer_mode_is_legacy (mode))
                {
                  /*  any non-legacy layer switches the mode to non-legacy
                   */

                  private->new_layer_mode = GIMP_LAYER_MODE_NORMAL;
                  break;
                }
            }

          /*  only if all layers are legacy, the mode is also legacy
           */

          if (! list)
            private->new_layer_mode = GIMP_LAYER_MODE_NORMAL_LEGACY;

          g_list_free (layers);
        }
      else
        {
          /*  empty images are never considered legacy
           */

          private->new_layer_mode = GIMP_LAYER_MODE_NORMAL;
        }
    }

  return private->new_layer_mode;
}

void
gimp_image_unset_default_new_layer_mode (GimpImage *image)
{
  g_return_if_fail (GIMP_IS_IMAGE (image));

  GIMP_IMAGE_GET_PRIVATE (image)->new_layer_mode = -1;
}

gint
gimp_image_get_id (GimpImage *image)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (image), -1);

  return GIMP_IMAGE_GET_PRIVATE (image)->ID;
}

GimpImage *
gimp_image_get_by_id (Gimp *gimp,
                      gint  image_id)
{
  g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);

  if (gimp->image_table == NULL)
    return NULL;

  return (GimpImage *) gimp_id_table_lookup (gimp->image_table, image_id);
}

void
gimp_image_set_file (GimpImage *image,
                     GFile     *file)
{
  GimpImagePrivate *private;

  g_return_if_fail (GIMP_IS_IMAGE (image));
  g_return_if_fail (file == NULL || G_IS_FILE (file));

  private = GIMP_IMAGE_GET_PRIVATE (image);

  if (private->file != file)
    {
      gimp_object_take_name (GIMP_OBJECT (image),
                             file ? g_file_get_uri (file) : NULL);
    }
}

/**
 * gimp_image_get_untitled_file:
 *
 * Returns: A #GFile saying "Untitled" for newly created images.
 **/

GFile *
gimp_image_get_untitled_file (GimpImage *image)
{
  GimpImagePrivate *private;

  g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);

  private = GIMP_IMAGE_GET_PRIVATE (image);

  if (! private->untitled_file)
    private->untitled_file = g_file_new_for_uri (_("Untitled"));

  return private->untitled_file;
}

/**
 * gimp_image_get_file_or_untitled:
 * @image: A #GimpImage.
 *
 * Get the file of the XCF image, or the "Untitled" file if there is no file.
 *
 * Returns: A #GFile.
 **/

GFile *
gimp_image_get_file_or_untitled (GimpImage *image)
{
  GFile *file;

  g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);

  file = gimp_image_get_file (image);

  if (! file)
    file = gimp_image_get_untitled_file (image);

  return file;
}

/**
 * gimp_image_get_file:
 * @image: A #GimpImage.
 *
 * Get the file of the XCF image, or %NULL if there is no file.
 *
 * Returns: (nullable): The file, or %NULL.
 **/

GFile *
gimp_image_get_file (GimpImage *image)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);

  return GIMP_IMAGE_GET_PRIVATE (image)->file;
}

/**
 * gimp_image_get_imported_file:
 * @image: A #GimpImage.
 *
 * Returns: (nullable): The file of the imported image, or %NULL if the image
 * has been saved as XCF after it was imported.
 **/

GFile *
gimp_image_get_imported_file (GimpImage *image)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);

  return GIMP_IMAGE_GET_PRIVATE (image)->imported_file;
}

/**
 * gimp_image_get_exported_file:
 * @image: A #GimpImage.
 *
 * Returns: (nullable): The file of the image last exported from this XCF file,
 * or %NULL if the image has never been exported.
 **/

GFile *
gimp_image_get_exported_file (GimpImage *image)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);

  return GIMP_IMAGE_GET_PRIVATE (image)->exported_file;
}

/**
 * gimp_image_get_save_a_copy_file:
 * @image: A #GimpImage.
 *
 * Returns: The URI of the last copy that was saved of this XCF file.
 **/

GFile *
gimp_image_get_save_a_copy_file (GimpImage *image)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);

  return GIMP_IMAGE_GET_PRIVATE (image)->save_a_copy_file;
}

/**
 * gimp_image_get_any_file:
 * @image: A #GimpImage.
 *
 * Returns: The XCF file, the imported file, or the exported file, in
 * that order of precedence.
 **/

GFile *
gimp_image_get_any_file (GimpImage *image)
{
  GFile *file;

  g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);

  file = gimp_image_get_file (image);
  if (! file)
    {
      file = gimp_image_get_imported_file (image);
      if (! file)
        {
          file = gimp_image_get_exported_file (image);
        }
    }

  return file;
}

/**
 * gimp_image_set_imported_uri:
 * @image: A #GimpImage.
 * @file:
 *
 * Sets the URI this file was imported from.
 **/

void
gimp_image_set_imported_file (GimpImage *image,
                              GFile     *file)
{
  GimpImagePrivate *private;

  g_return_if_fail (GIMP_IS_IMAGE (image));
  g_return_if_fail (file == NULL || G_IS_FILE (file));

  private = GIMP_IMAGE_GET_PRIVATE (image);

  if (g_set_object (&private->imported_file, file))
    {
      gimp_object_name_changed (GIMP_OBJECT (image));
    }

  if (! private->resolution_set && file != NULL)
    {
      /* Unlike new files (which follow technological progress and will
       * use higher default resolution, or explicitly chosen templates),
       * imported files have a more backward-compatible value.
       *
       * 72 PPI is traditionally the default value when none other had
       * been explicitly set (for instance it is the default when no
       * resolution metadata was set in Exif version 2.32, and below,
       * standard). This historical value will only ever apply to loaded
       * images. New images will continue having more modern or
       * templated defaults.
       */

      private->xresolution     = 72.0;
      private->yresolution     = 72.0;
      private->resolution_unit = gimp_unit_inch ();
    }
}

/**
 * gimp_image_set_exported_file:
 * @image: A #GimpImage.
 * @file:
 *
 * Sets the file this image was last exported to. Note that saving as
 * XCF is not "exporting".
 **/

void
gimp_image_set_exported_file (GimpImage *image,
                              GFile     *file)
{
  GimpImagePrivate *private;

  g_return_if_fail (GIMP_IS_IMAGE (image));
  g_return_if_fail (file == NULL || G_IS_FILE (file));

  private = GIMP_IMAGE_GET_PRIVATE (image);

  if (g_set_object (&private->exported_file, file))
    {
      gimp_object_name_changed (GIMP_OBJECT (image));
    }
}

/**
 * gimp_image_set_save_a_copy_file:
 * @image: A #GimpImage.
 * @uri:
 *
 * Set the URI to the last copy this XCF file was saved to through the
 * "save a copy" action.
 **/

void
gimp_image_set_save_a_copy_file (GimpImage *image,
                                 GFile     *file)
{
  GimpImagePrivate *private;

  g_return_if_fail (GIMP_IS_IMAGE (image));
  g_return_if_fail (file == NULL || G_IS_FILE (file));

  private = GIMP_IMAGE_GET_PRIVATE (image);

  g_set_object (&private->save_a_copy_file, file);
}

static gchar *
gimp_image_format_display_uri (GimpImage *image,
                               gboolean   basename)
{
  const gchar *uri_format    = NULL;
  const gchar *export_status = NULL;
  GFile       *file          = NULL;
  GFile       *source        = NULL;
  GFile       *dest          = NULL;
  GFile       *display_file  = NULL;
  gboolean     is_imported;
  gboolean     is_exported;
  gchar       *display_uri   = NULL;
  gchar       *format_string;
  gchar       *tmp;

  g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);

  file   = gimp_image_get_file (image);
  source = gimp_image_get_imported_file (image);
  dest   = gimp_image_get_exported_file (image);

  is_imported = (source != NULL);
  is_exported = (dest   != NULL);

  if (file)
    {
      display_file = g_object_ref (file);
      uri_format   = "%s";
    }
  else
    {
      if (is_imported)
        display_file = source;

      /* Calculate filename suffix */
      if (! gimp_image_is_export_dirty (image))
        {
          if (is_exported)
            {
              display_file  = dest;
              export_status = _(" (exported)");
            }
          else if (is_imported)
            {
              export_status = _(" (overwritten)");
            }
          else
            {
              g_warning ("Unexpected code path, Save+export implementation is buggy!");
            }
        }
      else if (is_imported)
        {
          export_status = _(" (imported)");
        }

      if (display_file)
        display_file = gimp_file_with_new_extension (display_file, NULL);

      uri_format = "[%s]";
    }

  if (! display_file)
    display_file = g_object_ref (gimp_image_get_untitled_file (image));

  if (basename)
    display_uri = g_path_get_basename (gimp_file_get_utf8_name (display_file));
  else
    display_uri = g_strdup (gimp_file_get_utf8_name (display_file));

  g_object_unref (display_file);

  format_string = g_strconcat (uri_format, export_status, NULL);

  tmp = g_strdup_printf (format_string, display_uri);
  g_free (display_uri);
  display_uri = tmp;

  g_free (format_string);

  return display_uri;
}

const gchar *
gimp_image_get_display_name (GimpImage *image)
{
  GimpImagePrivate *private;

  g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);

  private = GIMP_IMAGE_GET_PRIVATE (image);

  if (! private->display_name)
    private->display_name = gimp_image_format_display_uri (image, TRUE);

  return private->display_name;
}

const gchar *
gimp_image_get_display_path (GimpImage *image)
{
  GimpImagePrivate *private;

  g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);

  private = GIMP_IMAGE_GET_PRIVATE (image);

  if (! private->display_path)
    private->display_path = gimp_image_format_display_uri (image, FALSE);

  return private->display_path;
}

void
gimp_image_set_load_proc (GimpImage           *image,
                          GimpPlugInProcedure *proc)
{
  g_return_if_fail (GIMP_IS_IMAGE (image));

  GIMP_IMAGE_GET_PRIVATE (image)->load_proc = proc;
}

GimpPlugInProcedure *
gimp_image_get_load_proc (GimpImage *image)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);

  return GIMP_IMAGE_GET_PRIVATE (image)->load_proc;
}

void
gimp_image_set_save_proc (GimpImage           *image,
                          GimpPlugInProcedure *proc)
{
  g_return_if_fail (GIMP_IS_IMAGE (image));

  GIMP_IMAGE_GET_PRIVATE (image)->save_proc = proc;
}

GimpPlugInProcedure *
gimp_image_get_save_proc (GimpImage *image)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);

  return GIMP_IMAGE_GET_PRIVATE (image)->save_proc;
}

void
gimp_image_set_export_proc (GimpImage           *image,
                            GimpPlugInProcedure *proc)
{
  g_return_if_fail (GIMP_IS_IMAGE (image));

  GIMP_IMAGE_GET_PRIVATE (image)->export_proc = proc;
}

GimpPlugInProcedure *
gimp_image_get_export_proc (GimpImage *image)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);

  return GIMP_IMAGE_GET_PRIVATE (image)->export_proc;
}

gint
gimp_image_get_xcf_version (GimpImage    *image,
                            gboolean      zlib_compression,
                            gint         *gimp_version,
                            const gchar **version_string,
                            gchar       **version_reason)
{
  GList       *items;
  GList       *list;
  GList       *reasons = NULL;
  gint         version = 0;  /* default to oldest */
  const gchar *enum_desc;

#define ADD_REASON(_reason)                                       \
  if (version_reason) {                                           \
    gchar *tmp = _reason;                                         \
    if (g_list_find_custom (reasons, tmp, (GCompareFunc) strcmp)) \
      g_free (tmp);                                               \
    else                                                          \
      reasons = g_list_prepend (reasons, tmp); }

  /* need version 1 for colormaps */
  if (gimp_image_get_colormap_palette (image))
    version = 1;

  items = gimp_image_get_layer_list (image);
  for (list = items; list; list = g_list_next (list))
    {
      GimpLayer     *layer = GIMP_LAYER (list->data);
      GimpContainer *filters;

      switch (gimp_layer_get_mode (layer))
        {
          /*  Modes that exist since ancient times  */
        case GIMP_LAYER_MODE_NORMAL_LEGACY:
        case GIMP_LAYER_MODE_DISSOLVE:
        case GIMP_LAYER_MODE_BEHIND_LEGACY:
        case GIMP_LAYER_MODE_MULTIPLY_LEGACY:
        case GIMP_LAYER_MODE_SCREEN_LEGACY:
        case GIMP_LAYER_MODE_OVERLAY_LEGACY:
        case GIMP_LAYER_MODE_DIFFERENCE_LEGACY:
        case GIMP_LAYER_MODE_ADDITION_LEGACY:
        case GIMP_LAYER_MODE_SUBTRACT_LEGACY:
        case GIMP_LAYER_MODE_DARKEN_ONLY_LEGACY:
        case GIMP_LAYER_MODE_LIGHTEN_ONLY_LEGACY:
        case GIMP_LAYER_MODE_HSV_HUE_LEGACY:
        case GIMP_LAYER_MODE_HSV_SATURATION_LEGACY:
        case GIMP_LAYER_MODE_HSL_COLOR_LEGACY:
        case GIMP_LAYER_MODE_HSV_VALUE_LEGACY:
        case GIMP_LAYER_MODE_DIVIDE_LEGACY:
        case GIMP_LAYER_MODE_DODGE_LEGACY:
        case GIMP_LAYER_MODE_BURN_LEGACY:
        case GIMP_LAYER_MODE_HARDLIGHT_LEGACY:
          break;

          /*  Since 2.6  */
        case GIMP_LAYER_MODE_SOFTLIGHT_LEGACY:
        case GIMP_LAYER_MODE_GRAIN_EXTRACT_LEGACY:
        case GIMP_LAYER_MODE_GRAIN_MERGE_LEGACY:
        case GIMP_LAYER_MODE_COLOR_ERASE_LEGACY:
          gimp_enum_get_value (GIMP_TYPE_LAYER_MODE,
                               gimp_layer_get_mode (layer),
                               NULL, NULL, &enum_desc, NULL);
          ADD_REASON (g_strdup_printf (_("Layer mode '%s' was added in %s"),
                                       enum_desc, "GIMP 2.6"));
          version = MAX (2, version);
          break;

          /*  Since 2.10  */
        case GIMP_LAYER_MODE_OVERLAY:
        case GIMP_LAYER_MODE_LCH_HUE:
        case GIMP_LAYER_MODE_LCH_CHROMA:
        case GIMP_LAYER_MODE_LCH_COLOR:
        case GIMP_LAYER_MODE_LCH_LIGHTNESS:
          gimp_enum_get_value (GIMP_TYPE_LAYER_MODE,
                               gimp_layer_get_mode (layer),
                               NULL, NULL, &enum_desc, NULL);
          ADD_REASON (g_strdup_printf (_("Layer mode '%s' was added in %s"),
                                       enum_desc, "GIMP 2.10"));
          version = MAX (9, version);
          break;

          /*  Since 2.10  */
        case GIMP_LAYER_MODE_NORMAL:
        case GIMP_LAYER_MODE_BEHIND:
        case GIMP_LAYER_MODE_MULTIPLY:
        case GIMP_LAYER_MODE_SCREEN:
        case GIMP_LAYER_MODE_DIFFERENCE:
        case GIMP_LAYER_MODE_ADDITION:
        case GIMP_LAYER_MODE_SUBTRACT:
        case GIMP_LAYER_MODE_DARKEN_ONLY:
        case GIMP_LAYER_MODE_LIGHTEN_ONLY:
        case GIMP_LAYER_MODE_HSV_HUE:
        case GIMP_LAYER_MODE_HSV_SATURATION:
        case GIMP_LAYER_MODE_HSL_COLOR:
        case GIMP_LAYER_MODE_HSV_VALUE:
        case GIMP_LAYER_MODE_DIVIDE:
        case GIMP_LAYER_MODE_DODGE:
        case GIMP_LAYER_MODE_BURN:
        case GIMP_LAYER_MODE_HARDLIGHT:
        case GIMP_LAYER_MODE_SOFTLIGHT:
        case GIMP_LAYER_MODE_GRAIN_EXTRACT:
        case GIMP_LAYER_MODE_GRAIN_MERGE:
        case GIMP_LAYER_MODE_VIVID_LIGHT:
        case GIMP_LAYER_MODE_PIN_LIGHT:
        case GIMP_LAYER_MODE_LINEAR_LIGHT:
        case GIMP_LAYER_MODE_HARD_MIX:
        case GIMP_LAYER_MODE_EXCLUSION:
        case GIMP_LAYER_MODE_LINEAR_BURN:
        case GIMP_LAYER_MODE_LUMA_DARKEN_ONLY:
        case GIMP_LAYER_MODE_LUMA_LIGHTEN_ONLY:
        case GIMP_LAYER_MODE_LUMINANCE:
        case GIMP_LAYER_MODE_COLOR_ERASE:
        case GIMP_LAYER_MODE_ERASE:
        case GIMP_LAYER_MODE_MERGE:
        case GIMP_LAYER_MODE_SPLIT:
        case GIMP_LAYER_MODE_PASS_THROUGH:
          gimp_enum_get_value (GIMP_TYPE_LAYER_MODE,
                               gimp_layer_get_mode (layer),
                               NULL, NULL, &enum_desc, NULL);
          ADD_REASON (g_strdup_printf (_("Layer mode '%s' was added in %s"),
                                       enum_desc, "GIMP 2.10"));
          version = MAX (10, version);
          break;

          /*  Just here instead of default so we get compiler warnings  */
        case GIMP_LAYER_MODE_REPLACE:
        case GIMP_LAYER_MODE_OVERWRITE:
        case GIMP_LAYER_MODE_ANTI_ERASE:
        case GIMP_LAYER_MODE_SEPARATOR:
          gimp_enum_get_value (GIMP_TYPE_LAYER_MODE,
                               gimp_layer_get_mode (layer),
                               NULL, NULL, &enum_desc, NULL);
          g_warning ("%s: layer mode '%s' should not be set on a layer.",
                     G_STRFUNC, enum_desc);
          break;
        }

      /* need version 3 for layer trees */
      if (gimp_viewable_get_children (GIMP_VIEWABLE (layer)))
        {
          ADD_REASON (g_strdup_printf (_("Layer groups were added in %s"),
                                       "GIMP 2.8"));
          version = MAX (3, version);

          /* need version 13 for group layers with masks */
          if (gimp_layer_get_mask (layer))
            {
              ADD_REASON (g_strdup_printf (_("Masks on layer groups were "
                                             "added in %s"), "GIMP 2.10"));
              version = MAX (13, version);
            }

          if (gimp_item_get_lock_position (GIMP_ITEM (layer)))
            {
              ADD_REASON (g_strdup_printf (_("Position locks on layer groups were added in %s"),
                                           "GIMP 3.0"));
              version = MAX (17, version);
            }

          if (gimp_layer_get_lock_alpha (layer))
            {
              ADD_REASON (g_strdup_printf (_("Alpha channel locks on layer groups were added in %s"),
                                           "GIMP 3.0"));
              version = MAX (17, version);
            }
        }

      if (gimp_item_get_lock_visibility (GIMP_ITEM (layer)))
        {
          ADD_REASON (g_strdup_printf (_("Visibility locks were added in %s"),
                                       "GIMP 3.0"));
          version = MAX (17, version);
        }

      if (GIMP_IS_TEXT_LAYER (layer) &&
          /* If we loaded an old XCF and didn't touch the text layers, then we
           * just resave the text layer as-is. No need to bump the XCF version.
           */

          ! GIMP_TEXT_LAYER (layer)->text_parasite_is_old)
        {
          ADD_REASON (g_strdup_printf (_("Format of font information in text layer was changed in %s"),
                                       "GIMP 3.0"));
          version = MAX (19, version);
        }

      filters = gimp_drawable_get_filters (GIMP_DRAWABLE (layer));
      if (gimp_container_get_n_children (filters) > 0)
        {
          ADD_REASON (g_strdup_printf (_("Layer effects were added in %s"),
                                       "GIMP 3.0"));
          /* Version 20 added layer effects; version 22 stored the effect
           * version as an additional field. Only ever store as XCF 22
           * when layer effects are used.
           */

          version = MAX (22, version);
        }

      if ((gimp_layer_get_real_blend_space (layer) == GIMP_LAYER_COLOR_SPACE_RGB_PERCEPTUAL)
        ||(gimp_layer_get_real_composite_space (layer) == GIMP_LAYER_COLOR_SPACE_RGB_PERCEPTUAL))
        {
          ADD_REASON (g_strdup_printf (_("A new perceptual blending space was added in %s"),
                                       "GIMP 3.0"));
          /* The blending space variant corresponding to SPACE_RGB_PERCEPTUAL in <3.0
           * corresponds to R'G'B'A which is NON_LINEAR in babl. Perceptual in babl is
           * R~G~B~A, >= 3.0 the code, comments and usage matches the existing enum value
           * as being NON_LINEAR and new layers created use the new integer value for
           * PERCEPTUAL.
           */

          version = MAX (23, version);
        }

      /* Need version 24 for vector layers. */
      if (GIMP_IS_VECTOR_LAYER (layer))
        {
          ADD_REASON (g_strdup_printf (_("Vector layers were added in %s"),
                                       "GIMP 3.2"));
          version = MAX (24, version);
        }

      /* Need version 25 for link layers. */
      if (GIMP_IS_LINK_LAYER (layer))
        {
          ADD_REASON (g_strdup_printf (_("Link layers were added in %s"),
                                       "GIMP 3.2"));
          version = MAX (25, version);
        }
    }
  g_list_free (items);

  items = gimp_image_get_channel_list (image);
  for (list = items; list; list = g_list_next (list))
    {
      GimpChannel *channel = GIMP_CHANNEL (list->data);

      if (gimp_item_get_lock_visibility (GIMP_ITEM (channel)))
        {
          ADD_REASON (g_strdup_printf (_("Visibility locks were added in %s"),
                                       "GIMP 3.0"));
          version = MAX (17, version);
        }
    }
  g_list_free (items);

  if (g_list_length (gimp_image_get_selected_paths (image)) > 1)
    {
      ADD_REASON (g_strdup_printf (_("Multiple path selection was "
                                     "added in %s"), "GIMP 3.0.0"));
      version = MAX (18, version);
    }

  items = gimp_image_get_path_list (image);
  for (list = items; list; list = g_list_next (list))
    {
      GimpPath *path = GIMP_PATH (list->data);

      if (gimp_item_get_color_tag (GIMP_ITEM (path)) != GIMP_COLOR_TAG_NONE)
        {
          ADD_REASON (g_strdup_printf (_("Storing color tags in path was "
                                         "added in %s"), "GIMP 3.0.0"));
          version = MAX (18, version);
        }
      if (gimp_item_get_lock_content (GIMP_ITEM (list->data)) ||
          gimp_item_get_lock_position (GIMP_ITEM (list->data)))
        {
          ADD_REASON (g_strdup_printf (_("Storing locks in path was "
                                         "added in %s"), "GIMP 3.0.0"));
          version = MAX (18, version);
        }
    }
  g_list_free (items);

  /* version 6 for new metadata has been dropped since they are
   * saved through parasites, which is compatible with older versions.
   */


  /* need version 7 for != 8-bit gamma images */
  if (gimp_image_get_precision (image) != GIMP_PRECISION_U8_NON_LINEAR)
    {
      ADD_REASON (g_strdup_printf (_("High bit-depth images were added "
                                     "in %s"), "GIMP 2.10"));
      version = MAX (7, version);
    }

  /* need version 12 for > 8-bit images for proper endian swapping */
  if (gimp_image_get_component_type (image) > GIMP_COMPONENT_TYPE_U8)
    {
      ADD_REASON (g_strdup_printf (_("Encoding of high bit-depth images was "
                                     "fixed in %s"), "GIMP 2.10"));
      version = MAX (12, version);
    }

  /* need version 8 for zlib compression */
  if (zlib_compression)
    {
      ADD_REASON (g_strdup_printf (_("Internal zlib compression was "
                                     "added in %s"), "GIMP 2.10"));
      version = MAX (8, version);
    }

  /* if version is 10 (lots of new layer modes), go to version 11 with
   * 64 bit offsets right away
   */

  if (version == 10)
    version = 11;

  /* use the image's in-memory size as an upper bound to estimate the
   * need for 64 bit file offsets inside the XCF, this is a *very*
   * conservative estimate and should never fail
   */

  if (gimp_object_get_memsize (GIMP_OBJECT (image), NULL) >= ((gint64) 1 << 32))
    {
      ADD_REASON (g_strdup_printf (_("Support for image files larger than "
                                     "4GB was added in %s"), "GIMP 2.10"));
      version = MAX (11, version);
    }

  if (g_list_length (gimp_image_get_selected_layers (image)) > 1)
    {
      ADD_REASON (g_strdup_printf (_("Multiple layer selection was "
                                     "added in %s"), "GIMP 3.0.0"));
      version = MAX (14, version);
    }

  if ((list = gimp_image_get_guides (image)))
    {
      for (; list; list = g_list_next (list))
        {
          gint32 position = gimp_guide_get_position (list->data);

          if (position < 0 ||
              (gimp_guide_get_orientation (list->data) == GIMP_ORIENTATION_HORIZONTAL &&
               position > gimp_image_get_height (image)) ||
              (gimp_guide_get_orientation (list->data) == GIMP_ORIENTATION_VERTICAL &&
               position > gimp_image_get_width (image)))
            {
              ADD_REASON (g_strdup_printf (_("Off-canvas guides "
                                             "added in %s"), "GIMP 3.0.0"));
              version = MAX (15, version);
              break;
            }
        }
    }

  if (gimp_image_get_stored_item_sets (image, GIMP_TYPE_LAYER) ||
      gimp_image_get_stored_item_sets (image, GIMP_TYPE_CHANNEL))
    {
      ADD_REASON (g_strdup_printf (_("Item set and pattern search in item's name were "
                                     "added in %s"), "GIMP 3.0.0"));
      version = MAX (16, version);
    }
  if (g_list_length (gimp_image_get_selected_channels (image)) > 1)
    {
      ADD_REASON (g_strdup_printf (_("Multiple channel selection was "
                                     "added in %s"), "GIMP 3.0.0"));
      version = MAX (16, version);
    }

  /* Note: user unit storage was changed in XCF 21, but we can still
   * easily save older XCF (we use the unit name for both singular and
   * plural forms). Therefore we don't bump the XCF version unnecessarily
   * and don't add any test.
   */


#undef ADD_REASON

  switch (version)
    {
    case 0:
    case 1:
    case 2:
      if (gimp_version)   *gimp_version   = 206;
      if (version_string) *version_string = "GIMP 2.6";
      break;

    case 3:
      if (gimp_version)   *gimp_version   = 208;
      if (version_string) *version_string = "GIMP 2.8";
      break;

    case 4:
    case 5:
    case 6:
    case 7:
    case 8:
    case 9:
    case 10:
    case 11:
    case 12:
    case 13:
      if (gimp_version)   *gimp_version   = 210;
      if (version_string) *version_string = "GIMP 2.10";
      break;
    case 14:
    case 15:
    case 16:
    case 17:
    case 18:
    case 19:
    case 20:
    case 21:
    case 22:
    case 23:
      if (gimp_version)   *gimp_version   = 300;
      if (version_string) *version_string = "GIMP 3.0";
      break;
    case 24:
    case 25:
      if (gimp_version)   *gimp_version   = 320;
      if (version_string) *version_string = "GIMP 3.2";
      break;
    }

  if (version_reason && reasons)
    {
      GString *reason = g_string_new (NULL);

      reasons = g_list_sort (reasons, (GCompareFunc) strcmp);

      for (list = reasons; list; list = g_list_next (list))
        {
          g_string_append (reason, list->data);
          if (g_list_next (list))
            g_string_append_c (reason, '\n');
        }

      *version_reason = g_string_free (reason, FALSE);
    }
  if (reasons)
    g_list_free_full (reasons, g_free);

  return version;
}

void
gimp_image_set_xcf_compression (GimpImage *image,
                                gboolean   compression)
{
  g_return_if_fail (GIMP_IS_IMAGE (image));

  GIMP_IMAGE_GET_PRIVATE (image)->xcf_compression = compression;
}

gboolean
gimp_image_get_xcf_compression (GimpImage *image)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);

  return GIMP_IMAGE_GET_PRIVATE (image)->xcf_compression;
}

void
gimp_image_set_resolution (GimpImage *image,
                           gdouble    xresolution,
                           gdouble    yresolution)
{
  GimpImagePrivate *private;

  g_return_if_fail (GIMP_IS_IMAGE (image));

  private = GIMP_IMAGE_GET_PRIVATE (image);

  /* don't allow to set the resolution out of bounds */
  if (xresolution < GIMP_MIN_RESOLUTION || xresolution > GIMP_MAX_RESOLUTION ||
      yresolution < GIMP_MIN_RESOLUTION || yresolution > GIMP_MAX_RESOLUTION)
    return;

  private->resolution_set = TRUE;

  if ((ABS (private->xresolution - xresolution) >= 1e-5) ||
      (ABS (private->yresolution - yresolution) >= 1e-5))
    {
      gimp_image_undo_push_image_resolution (image,
                                             C_("undo-type", "Change Image Resolution"));

      private->xresolution = xresolution;
      private->yresolution = yresolution;

      gimp_image_resolution_changed (image);
      gimp_image_size_changed_detailed (image,
                                        0,
                                        0,
                                        gimp_image_get_width (image),
                                        gimp_image_get_height (image));
    }
}

void
gimp_image_get_resolution (GimpImage *image,
                           gdouble   *xresolution,
                           gdouble   *yresolution)
{
  GimpImagePrivate *private;

  g_return_if_fail (GIMP_IS_IMAGE (image));
  g_return_if_fail (xresolution != NULL && yresolution != NULL);

  private = GIMP_IMAGE_GET_PRIVATE (image);

  *xresolution = private->xresolution;
  *yresolution = private->yresolution;
}

void
gimp_image_resolution_changed (GimpImage *image)
{
  g_return_if_fail (GIMP_IS_IMAGE (image));

  g_signal_emit (image, gimp_image_signals[RESOLUTION_CHANGED], 0);
}

void
gimp_image_set_unit (GimpImage *image,
                     GimpUnit  *unit)
{
  GimpImagePrivate *private;

  g_return_if_fail (GIMP_IS_IMAGE (image));
  g_return_if_fail (GIMP_IS_UNIT (unit));

  private = GIMP_IMAGE_GET_PRIVATE (image);

  if (private->resolution_unit != unit)
    {
      gimp_image_undo_push_image_resolution (image,
                                             C_("undo-type", "Change Image Unit"));

      private->resolution_unit = unit;
      gimp_image_unit_changed (image);
    }
}

GimpUnit *
gimp_image_get_unit (GimpImage *image)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);

  return GIMP_IMAGE_GET_PRIVATE (image)->resolution_unit;
}

void
gimp_image_unit_changed (GimpImage *image)
{
  g_return_if_fail (GIMP_IS_IMAGE (image));

  g_signal_emit (image, gimp_image_signals[UNIT_CHANGED], 0);
}

gint
gimp_image_get_width (GimpImage *image)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (image), 0);

  return GIMP_IMAGE_GET_PRIVATE (image)->width;
}

gint
gimp_image_get_height (GimpImage *image)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (image), 0);

  return GIMP_IMAGE_GET_PRIVATE (image)->height;
}

gboolean
gimp_image_has_alpha (GimpImage *image)
{
  GimpImagePrivate *private;
  GimpLayer        *layer;

  g_return_val_if_fail (GIMP_IS_IMAGE (image), TRUE);

  private = GIMP_IMAGE_GET_PRIVATE (image);

  layer = GIMP_LAYER (gimp_container_get_first_child (private->layers->container));

  return ((gimp_image_get_n_layers (image) > 1) ||
          (layer && gimp_drawable_has_alpha (GIMP_DRAWABLE (layer))));
}

gboolean
gimp_image_is_empty (GimpImage *image)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (image), TRUE);

  return gimp_container_is_empty (GIMP_IMAGE_GET_PRIVATE (image)->layers->container);
}

void
gimp_image_set_floating_selection (GimpImage *image,
                                   GimpLayer *floating_sel)
{
  GimpImagePrivate *private;

  g_return_if_fail (GIMP_IS_IMAGE (image));
  g_return_if_fail (floating_sel == NULL || GIMP_IS_LAYER (floating_sel));

  private = GIMP_IMAGE_GET_PRIVATE (image);

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

      private->flush_accum.floating_selection_changed = TRUE;
    }
}

GimpLayer *
gimp_image_get_floating_selection (GimpImage *image)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);

  return GIMP_IMAGE_GET_PRIVATE (image)->floating_sel;
}

void
gimp_image_floating_selection_changed (GimpImage *image)
{
  g_return_if_fail (GIMP_IS_IMAGE (image));

  g_signal_emit (image, gimp_image_signals[FLOATING_SELECTION_CHANGED], 0);
}

GimpChannel *
gimp_image_get_mask (GimpImage *image)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);

  return GIMP_IMAGE_GET_PRIVATE (image)->selection_mask;
}

void
gimp_image_mask_changed (GimpImage *image)
{
  g_return_if_fail (GIMP_IS_IMAGE (image));

  g_signal_emit (image, gimp_image_signals[MASK_CHANGED], 0);
}

/**
 * gimp_image_mask_intersect:
 * @image:   the #GimpImage
 * @items:   a list of #GimpItem
 * @x: (out) (optional): return location for x
 * @y: (out) (optional): return location for y
 * @width: (out) (optional): return location for the width
 * @height: (out) (optional): return location for the height
 *
 * Intersect the area of the @items and its image's selection mask.
 * The computed area is the bounding box of the selection intersection
 * within the image. These values are only returned if the function
 * returns %TRUE.
 *
 * Note that even though the items bounding box may not be empty, it is
 * possible to get a return value of %FALSE. Imagine disjoint items, one
 * on the left, one on the right, and a selection in the middle not
 * intersecting with any of the items.
 *
 * Returns: %TRUE if the selection intersects with any of the @items.
 */

gboolean
gimp_image_mask_intersect (GimpImage *image,
                           GList     *items,
                           gint      *x,
                           gint      *y,
                           gint      *width,
                           gint      *height)
{
  GimpChannel *selection;
  GList       *iter;
  gint         sel_x, sel_y, sel_width, sel_height;
  gint         x1 = G_MAXINT;
  gint         y1 = G_MAXINT;
  gint         x2 = G_MININT;
  gint         y2 = G_MININT;
  gboolean     intersect = FALSE;

  g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
  for (iter = items; iter; iter = iter->next)
    {
      g_return_val_if_fail (GIMP_IS_ITEM (iter->data), FALSE);
      g_return_val_if_fail (gimp_item_is_attached (iter->data), FALSE);
      g_return_val_if_fail (gimp_item_get_image (iter->data) == image, FALSE);
    }

  selection = gimp_image_get_mask (image);
  if (selection)
    gimp_item_bounds (GIMP_ITEM (selection),
                      &sel_x, &sel_y, &sel_width, &sel_height);

  for (iter = items; iter; iter = iter->next)
    {
      GimpItem *item = iter->data;
      gboolean  item_intersect;
      gint      tmp_x, tmp_y;
      gint      tmp_width, tmp_height;

      gimp_item_get_offset (item, &tmp_x, &tmp_y);

      /* check for is_empty() before intersecting so we ignore the
       * selection if it is suspended (like when stroking)
       */

      if (GIMP_ITEM (selection) != item       &&
          ! gimp_channel_is_empty (selection))
        {
          item_intersect = gimp_rectangle_intersect (sel_x, sel_y, sel_width, sel_height,
                                                     tmp_x, tmp_y,
                                                     gimp_item_get_width  (item),
                                                     gimp_item_get_height (item),
                                                     &tmp_x, &tmp_y,
                                                     &tmp_width, &tmp_height);
        }
      else
        {
          tmp_width  = gimp_item_get_width  (item);
          tmp_height = gimp_item_get_height (item);

          item_intersect = TRUE;
        }

      if (item_intersect)
        {
          x1 = MIN (x1, tmp_x);
          y1 = MIN (y1, tmp_y);
          x2 = MAX (x2, tmp_x + tmp_width);
          y2 = MAX (y2, tmp_y + tmp_height);

          intersect = TRUE;
        }
    }

  if (intersect)
    {
      if (x)      *x      = x1;
      if (y)      *y      = y1;
      if (width)  *width  = x2 - x1;
      if (height) *height = y2 - y1;
    }

  return intersect;
}

void
gimp_image_take_mask (GimpImage   *image,
                      GimpChannel *mask)
{
  GimpImagePrivate *private;

  g_return_if_fail (GIMP_IS_IMAGE (image));
  g_return_if_fail (GIMP_IS_SELECTION (mask));

  private = GIMP_IMAGE_GET_PRIVATE (image);

  if (private->selection_mask)
    g_object_unref (private->selection_mask);

  private->selection_mask = g_object_ref_sink (mask);

  g_signal_connect (private->selection_mask, "update",
                    G_CALLBACK (gimp_image_mask_update),
                    image);
}


/*  image components  */

const Babl *
gimp_image_get_component_format (GimpImage       *image,
                                 GimpChannelType  channel)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);

  switch (channel)
    {
    case GIMP_CHANNEL_RED:
      return gimp_babl_component_format (GIMP_RGB,
                                         gimp_image_get_precision (image),
                                         RED);

    case GIMP_CHANNEL_GREEN:
      return gimp_babl_component_format (GIMP_RGB,
                                         gimp_image_get_precision (image),
                                         GREEN);

    case GIMP_CHANNEL_BLUE:
      return gimp_babl_component_format (GIMP_RGB,
                                         gimp_image_get_precision (image),
                                         BLUE);

    case GIMP_CHANNEL_ALPHA:
      return gimp_babl_component_format (GIMP_RGB,
                                         gimp_image_get_precision (image),
                                         ALPHA);

    case GIMP_CHANNEL_GRAY:
      return gimp_babl_component_format (GIMP_GRAY,
                                         gimp_image_get_precision (image),
                                         GRAY);

    case GIMP_CHANNEL_INDEXED:
      return babl_format ("Y u8"); /* will extract grayscale, the best
                                    * we can do here */

    }

  return NULL;
}

gint
gimp_image_get_component_index (GimpImage       *image,
                                GimpChannelType  channel)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (image), -1);

  switch (channel)
    {
    case GIMP_CHANNEL_RED:     return RED;
    case GIMP_CHANNEL_GREEN:   return GREEN;
    case GIMP_CHANNEL_BLUE:    return BLUE;
    case GIMP_CHANNEL_GRAY:    return GRAY;
    case GIMP_CHANNEL_INDEXED: return INDEXED;
    case GIMP_CHANNEL_ALPHA:
      switch (gimp_image_get_base_type (image))
        {
        case GIMP_RGB:     return ALPHA;
        case GIMP_GRAY:    return ALPHA_G;
        case GIMP_INDEXED: return ALPHA_I;
        }
    }

  return -1;
}

void
gimp_image_set_component_active (GimpImage       *image,
                                 GimpChannelType  channel,
                                 gboolean         active)
{
  GimpImagePrivate *private;
  gint              index = -1;

  g_return_if_fail (GIMP_IS_IMAGE (image));

  private = GIMP_IMAGE_GET_PRIVATE (image);

  index = gimp_image_get_component_index (image, channel);

  if (index != -1 && active != private->active[index])
    {
      private->active[index] = active ? TRUE : FALSE;

      /*  If there is an active channel and we mess with the components,
       *  the active channel gets unset...
       */

      gimp_image_unset_selected_channels (image);

      g_signal_emit (image,
                     gimp_image_signals[COMPONENT_ACTIVE_CHANGED], 0,
                     channel);
    }
}

gboolean
gimp_image_get_component_active (GimpImage       *image,
                                 GimpChannelType  channel)
{
  gint index = -1;

  g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);

  index = gimp_image_get_component_index (image, channel);

  if (index != -1)
    return GIMP_IMAGE_GET_PRIVATE (image)->active[index];

  return FALSE;
}

void
gimp_image_get_active_array (GimpImage *image,
                             gboolean  *components)
{
  GimpImagePrivate *private;
  gint              i;

  g_return_if_fail (GIMP_IS_IMAGE (image));
  g_return_if_fail (components != NULL);

  private = GIMP_IMAGE_GET_PRIVATE (image);

  for (i = 0; i < MAX_CHANNELS; i++)
    components[i] = private->active[i];
}

GimpComponentMask
gimp_image_get_active_mask (GimpImage *image)
{
  GimpImagePrivate  *private;
  GimpComponentMask  mask = 0;

  g_return_val_if_fail (GIMP_IS_IMAGE (image), 0);

  private = GIMP_IMAGE_GET_PRIVATE (image);

  switch (gimp_image_get_base_type (image))
    {
    case GIMP_RGB:
      mask |= (private->active[RED])   ? GIMP_COMPONENT_MASK_RED   : 0;
      mask |= (private->active[GREEN]) ? GIMP_COMPONENT_MASK_GREEN : 0;
      mask |= (private->active[BLUE])  ? GIMP_COMPONENT_MASK_BLUE  : 0;
      mask |= (private->active[ALPHA]) ? GIMP_COMPONENT_MASK_ALPHA : 0;
      break;

    case GIMP_GRAY:
    case GIMP_INDEXED:
      mask |= (private->active[GRAY])    ? GIMP_COMPONENT_MASK_RED   : 0;
      mask |= (private->active[GRAY])    ? GIMP_COMPONENT_MASK_GREEN : 0;
      mask |= (private->active[GRAY])    ? GIMP_COMPONENT_MASK_BLUE  : 0;
      mask |= (private->active[ALPHA_G]) ? GIMP_COMPONENT_MASK_ALPHA : 0;
      break;
    }

  return mask;
}

void
gimp_image_set_component_visible (GimpImage       *image,
                                  GimpChannelType  channel,
                                  gboolean         visible)
{
  GimpImagePrivate *private;
  gint              index = -1;

  g_return_if_fail (GIMP_IS_IMAGE (image));

  private = GIMP_IMAGE_GET_PRIVATE (image);

  index = gimp_image_get_component_index (image, channel);

  if (index != -1 && visible != private->visible[index])
    {
      private->visible[index] = visible ? TRUE : FALSE;

      if (private->visible_mask)
        {
          GimpComponentMask mask;

          mask = ~gimp_image_get_visible_mask (image) & GIMP_COMPONENT_MASK_ALL;

          gegl_node_set (private->visible_mask,
                         "mask", mask,
                         NULL);
        }

      g_signal_emit (image,
                     gimp_image_signals[COMPONENT_VISIBILITY_CHANGED], 0,
                     channel);

      gimp_image_invalidate_all (image);
    }
}

gboolean
gimp_image_get_component_visible (GimpImage       *image,
                                  GimpChannelType  channel)
{
  gint index = -1;

  g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);

  index = gimp_image_get_component_index (image, channel);

  if (index != -1)
    return GIMP_IMAGE_GET_PRIVATE (image)->visible[index];

  return FALSE;
}

void
gimp_image_get_visible_array (GimpImage *image,
                              gboolean  *components)
{
  GimpImagePrivate *private;
  gint              i;

  g_return_if_fail (GIMP_IS_IMAGE (image));
  g_return_if_fail (components != NULL);

  private = GIMP_IMAGE_GET_PRIVATE (image);

  for (i = 0; i < MAX_CHANNELS; i++)
    components[i] = private->visible[i];
}

GimpComponentMask
gimp_image_get_visible_mask (GimpImage *image)
{
  GimpImagePrivate  *private;
  GimpComponentMask  mask = 0;

  g_return_val_if_fail (GIMP_IS_IMAGE (image), 0);

  private = GIMP_IMAGE_GET_PRIVATE (image);

  switch (gimp_image_get_base_type (image))
    {
    case GIMP_RGB:
      mask |= (private->visible[RED])   ? GIMP_COMPONENT_MASK_RED   : 0;
      mask |= (private->visible[GREEN]) ? GIMP_COMPONENT_MASK_GREEN : 0;
      mask |= (private->visible[BLUE])  ? GIMP_COMPONENT_MASK_BLUE  : 0;
      mask |= (private->visible[ALPHA]) ? GIMP_COMPONENT_MASK_ALPHA : 0;
      break;

    case GIMP_GRAY:
    case GIMP_INDEXED:
      mask |= (private->visible[GRAY])  ? GIMP_COMPONENT_MASK_RED   : 0;
      mask |= (private->visible[GRAY])  ? GIMP_COMPONENT_MASK_GREEN : 0;
      mask |= (private->visible[GRAY])  ? GIMP_COMPONENT_MASK_BLUE  : 0;
      mask |= (private->visible[ALPHA]) ? GIMP_COMPONENT_MASK_ALPHA : 0;
      break;
    }

  return mask;
}


/*  emitting image signals  */

void
gimp_image_mode_changed (GimpImage *image)
{
  g_return_if_fail (GIMP_IS_IMAGE (image));

  g_signal_emit (image, gimp_image_signals[MODE_CHANGED], 0);
}

void
gimp_image_precision_changed (GimpImage *image)
{
  g_return_if_fail (GIMP_IS_IMAGE (image));

  g_signal_emit (image, gimp_image_signals[PRECISION_CHANGED], 0);
}

void
gimp_image_alpha_changed (GimpImage *image)
{
  g_return_if_fail (GIMP_IS_IMAGE (image));

  g_signal_emit (image, gimp_image_signals[ALPHA_CHANGED], 0);
}

void
gimp_image_invalidate (GimpImage *image,
                       gint       x,
                       gint       y,
                       gint       width,
                       gint       height)
{
  g_return_if_fail (GIMP_IS_IMAGE (image));

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

  GIMP_IMAGE_GET_PRIVATE (image)->flush_accum.preview_invalidated = TRUE;
}

void
gimp_image_invalidate_all (GimpImage *image)
{
  const GeglRectangle *bounding_box;

  g_return_if_fail (GIMP_IS_IMAGE (image));

  bounding_box = &GIMP_IMAGE_GET_PRIVATE (image)->bounding_box;

  gimp_image_invalidate (image,
                         bounding_box->x,     bounding_box->y,
                         bounding_box->width, bounding_box->height);
}

void
gimp_image_guide_added (GimpImage *image,
                        GimpGuide *guide)
{
  g_return_if_fail (GIMP_IS_IMAGE (image));
  g_return_if_fail (GIMP_IS_GUIDE (guide));

  g_signal_emit (image, gimp_image_signals[GUIDE_ADDED], 0,
                 guide);
}

void
gimp_image_guide_removed (GimpImage *image,
                          GimpGuide *guide)
{
  g_return_if_fail (GIMP_IS_IMAGE (image));
  g_return_if_fail (GIMP_IS_GUIDE (guide));

  g_signal_emit (image, gimp_image_signals[GUIDE_REMOVED], 0,
                 guide);
}

void
gimp_image_guide_moved (GimpImage *image,
                        GimpGuide *guide)
{
  g_return_if_fail (GIMP_IS_IMAGE (image));
  g_return_if_fail (GIMP_IS_GUIDE (guide));

  g_signal_emit (image, gimp_image_signals[GUIDE_MOVED], 0,
                 guide);
}

void
gimp_image_sample_point_added (GimpImage       *image,
                               GimpSamplePoint *sample_point)
{
  g_return_if_fail (GIMP_IS_IMAGE (image));
  g_return_if_fail (GIMP_IS_SAMPLE_POINT (sample_point));

  g_signal_emit (image, gimp_image_signals[SAMPLE_POINT_ADDED], 0,
                 sample_point);
}

void
gimp_image_sample_point_removed (GimpImage       *image,
                                 GimpSamplePoint *sample_point)
{
  g_return_if_fail (GIMP_IS_IMAGE (image));
  g_return_if_fail (GIMP_IS_SAMPLE_POINT (sample_point));

  g_signal_emit (image, gimp_image_signals[SAMPLE_POINT_REMOVED], 0,
                 sample_point);
}

void
gimp_image_sample_point_moved (GimpImage       *image,
                               GimpSamplePoint *sample_point)
{
  g_return_if_fail (GIMP_IS_IMAGE (image));
  g_return_if_fail (GIMP_IS_SAMPLE_POINT (sample_point));

  g_signal_emit (image, gimp_image_signals[SAMPLE_POINT_MOVED], 0,
                 sample_point);
}

/**
 * gimp_image_size_changed_detailed:
 * @image:
 * @previous_origin_x:
 * @previous_origin_y:
 *
 * Emits the size-changed-detailed signal that is typically used to adjust the
 * position of the image in the display shell on various operations,
 * e.g. crop.
 *
 * This function makes sure that GimpViewable::size-changed is also emitted.
 **/

void
gimp_image_size_changed_detailed (GimpImage *image,
                                  gint       previous_origin_x,
                                  gint       previous_origin_y,
                                  gint       previous_width,
                                  gint       previous_height)
{
  g_return_if_fail (GIMP_IS_IMAGE (image));

  g_signal_emit (image, gimp_image_signals[SIZE_CHANGED_DETAILED], 0,
                 previous_origin_x,
                 previous_origin_y,
                 previous_width,
                 previous_height);
}

void
gimp_image_colormap_changed (GimpImage *image,
                             gint       color_index)
{
  GimpPalette *palette;
  gint         n_colors;

  g_return_if_fail (GIMP_IS_IMAGE (image));
  palette = GIMP_IMAGE_GET_PRIVATE (image)->palette;
  n_colors = palette ? gimp_palette_get_n_colors (palette) : 0;
  g_return_if_fail (color_index >= -1 && color_index < n_colors);

  g_signal_emit (image, gimp_image_signals[COLORMAP_CHANGED], 0,
                 color_index);
}

void
gimp_image_selection_invalidate (GimpImage *image)
{
  g_return_if_fail (GIMP_IS_IMAGE (image));

  g_signal_emit (image, gimp_image_signals[SELECTION_INVALIDATE], 0);
}

void
gimp_image_quick_mask_changed (GimpImage *image)
{
  g_return_if_fail (GIMP_IS_IMAGE (image));

  g_signal_emit (image, gimp_image_signals[QUICK_MASK_CHANGED], 0);
}

void
gimp_image_undo_event (GimpImage     *image,
                       GimpUndoEvent  event,
                       GimpUndo      *undo)
{
  g_return_if_fail (GIMP_IS_IMAGE (image));
  g_return_if_fail (((event == GIMP_UNDO_EVENT_UNDO_FREE   ||
                      event == GIMP_UNDO_EVENT_UNDO_FREEZE ||
                      event == GIMP_UNDO_EVENT_UNDO_THAW) && undo == NULL) ||
                    GIMP_IS_UNDO (undo));

  g_signal_emit (image, gimp_image_signals[UNDO_EVENT], 0, event, undo);
}


/*  dirty counters  */

/* NOTE about the image->dirty counter:
 *   If 0, then the image is clean (ie, copy on disk is the same as the one
 *      in memory).
 *   If positive, then that's the number of dirtying operations done
 *       on the image since the last save.
 *   If negative, then user has hit undo and gone back in time prior
 *       to the saved copy.  Hitting redo will eventually come back to
 *       the saved copy.
 *
 *   The image is dirty (ie, needs saving) if counter is non-zero.
 *
 *   If the counter is around 100000, this is due to undo-ing back
 *   before a saved version, then changing the image (thus destroying
 *   the redo stack).  Once this has happened, it's impossible to get
 *   the image back to the state on disk, since the redo info has been
 *   freed.  See gimpimage-undo.c for the gory details.
 */


/*
 * NEVER CALL gimp_image_dirty() directly!
 *
 * If your code has just dirtied the image, push an undo instead.
 * Failing that, push the trivial undo which tells the user the
 * command is not undoable: undo_push_cantundo() (But really, it would
 * be best to push a proper undo).  If you just dirty the image
 * without pushing an undo then the dirty count is increased, but
 * popping that many undo actions won't lead to a clean image.
 */


gint
gimp_image_dirty (GimpImage     *image,
                  GimpDirtyMask  dirty_mask)
{
  GimpImagePrivate *private;

  g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);

  private = GIMP_IMAGE_GET_PRIVATE (image);

  private->dirty++;
  private->export_dirty++;

  if (! private->dirty_time)
    private->dirty_time = time (NULL);

  g_signal_emit (image, gimp_image_signals[DIRTY], 0, dirty_mask);

  TRC (("dirty %d -> %d\n", private->dirty - 1, private->dirty));

  return private->dirty;
}

gint
gimp_image_clean (GimpImage     *image,
                  GimpDirtyMask  dirty_mask)
{
  GimpImagePrivate *private;

  g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);

  private = GIMP_IMAGE_GET_PRIVATE (image);

  private->dirty--;
  private->export_dirty--;

  g_signal_emit (image, gimp_image_signals[CLEAN], 0, dirty_mask);

  TRC (("clean %d -> %d\n", private->dirty + 1, private->dirty));

  return private->dirty;
}

void
gimp_image_clean_all (GimpImage *image)
{
  GimpImagePrivate *private;

  g_return_if_fail (GIMP_IS_IMAGE (image));

  private = GIMP_IMAGE_GET_PRIVATE (image);

  private->dirty      = 0;
  private->dirty_time = 0;

  g_signal_emit (image, gimp_image_signals[CLEAN], 0, GIMP_DIRTY_ALL);

  gimp_object_name_changed (GIMP_OBJECT (image));
}

void
gimp_image_export_clean_all (GimpImage *image)
{
  GimpImagePrivate *private;

  g_return_if_fail (GIMP_IS_IMAGE (image));

  private = GIMP_IMAGE_GET_PRIVATE (image);

  private->export_dirty = 0;

  g_signal_emit (image, gimp_image_signals[CLEAN], 0, GIMP_DIRTY_ALL);

  gimp_object_name_changed (GIMP_OBJECT (image));
}

/**
 * gimp_image_is_dirty:
 * @image:
 *
 * Returns: True if the image is dirty, false otherwise.
 **/

gint
gimp_image_is_dirty (GimpImage *image)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);

  return GIMP_IMAGE_GET_PRIVATE (image)->dirty != 0;
}

/**
 * gimp_image_is_export_dirty:
 * @image:
 *
 * Returns: True if the image export is dirty, false otherwise.
 **/

gboolean
gimp_image_is_export_dirty (GimpImage *image)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);

  return GIMP_IMAGE_GET_PRIVATE (image)->export_dirty != 0;
}

gint64
gimp_image_get_dirty_time (GimpImage *image)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (image), 0);

  return GIMP_IMAGE_GET_PRIVATE (image)->dirty_time;
}

/**
 * gimp_image_saving:
 * @image:
 *
 * Emits the "saving" signal, indicating that @image is about to be saved,
 * or exported.
 */

void
gimp_image_saving (GimpImage *image)
{
  g_return_if_fail (GIMP_IS_IMAGE (image));

  g_signal_emit (image, gimp_image_signals[SAVING], 0);
}

/**
 * gimp_image_saved:
 * @image:
 * @file:
 *
 * Emits the "saved" signal, indicating that @image was saved to the
 * location specified by @file.
 */

void
gimp_image_saved (GimpImage *image,
                  GFile     *file)
{
  g_return_if_fail (GIMP_IS_IMAGE (image));
  g_return_if_fail (G_IS_FILE (file));

  g_signal_emit (image, gimp_image_signals[SAVED], 0, file);
}

/**
 * gimp_image_exported:
 * @image:
 * @file:
 *
 * Emits the "exported" signal, indicating that @image was exported to the
 * location specified by @file.
 */

void
gimp_image_exported (GimpImage *image,
                     GFile     *file)
{
  g_return_if_fail (GIMP_IS_IMAGE (image));
  g_return_if_fail (G_IS_FILE (file));

  g_signal_emit (image, gimp_image_signals[EXPORTED], 0, file);
}


/*  flush this image's displays  */

void
gimp_image_flush (GimpImage *image)
{
  g_return_if_fail (GIMP_IS_IMAGE (image));

  gimp_projectable_flush (GIMP_PROJECTABLE (image),
                          GIMP_IMAGE_GET_PRIVATE (image)->flush_accum.preview_invalidated);
}


/*  display / instance counters  */

gint
gimp_image_get_display_count (GimpImage *image)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (image), 0);

  return GIMP_IMAGE_GET_PRIVATE (image)->disp_count;
}

void
gimp_image_inc_display_count (GimpImage *image)
{
  g_return_if_fail (GIMP_IS_IMAGE (image));

  GIMP_IMAGE_GET_PRIVATE (image)->disp_count++;
  g_signal_emit (image, gimp_image_signals[DISPLAY_COUNT_CHANGED], 0,
                 GIMP_IMAGE_GET_PRIVATE (image)->disp_count);
}

void
gimp_image_dec_display_count (GimpImage *image)
{
  g_return_if_fail (GIMP_IS_IMAGE (image));

  GIMP_IMAGE_GET_PRIVATE (image)->disp_count--;
  g_signal_emit (image, gimp_image_signals[DISPLAY_COUNT_CHANGED], 0,
                 GIMP_IMAGE_GET_PRIVATE (image)->disp_count);
}

gint
gimp_image_get_instance_count (GimpImage *image)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (image), 0);

  return GIMP_IMAGE_GET_PRIVATE (image)->instance_count;
}

void
gimp_image_inc_instance_count (GimpImage *image)
{
  g_return_if_fail (GIMP_IS_IMAGE (image));

  GIMP_IMAGE_GET_PRIVATE (image)->instance_count++;
}

void
gimp_image_inc_show_all_count (GimpImage *image)
{
  g_return_if_fail (GIMP_IS_IMAGE (image));

  GIMP_IMAGE_GET_PRIVATE (image)->show_all++;

  if (GIMP_IMAGE_GET_PRIVATE (image)->show_all == 1)
    {
      g_clear_object (&GIMP_IMAGE_GET_PRIVATE (image)->pickable_buffer);

      gimp_image_update_bounding_box (image);
    }
}

void
gimp_image_dec_show_all_count (GimpImage *image)
{
  g_return_if_fail (GIMP_IS_IMAGE (image));

  GIMP_IMAGE_GET_PRIVATE (image)->show_all--;

  if (GIMP_IMAGE_GET_PRIVATE (image)->show_all == 0)
    {
      g_clear_object (&GIMP_IMAGE_GET_PRIVATE (image)->pickable_buffer);

      gimp_image_update_bounding_box (image);
    }
}


/*  parasites  */

const GimpParasite *
gimp_image_parasite_find (GimpImage   *image,
                          const gchar *name)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);

  return gimp_parasite_list_find (GIMP_IMAGE_GET_PRIVATE (image)->parasites,
                                  name);
}

static void
list_func (gchar          *key,
           GimpParasite   *p,
           gchar        ***cur)
{
  *(*cur)++ = (gchar *) g_strdup (key);
}

gchar **
gimp_image_parasite_list (GimpImage *image)
{
  GimpImagePrivate  *private;
  gint               count;
  gchar            **list;
  gchar            **cur;

  g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);

  private = GIMP_IMAGE_GET_PRIVATE (image);

  count = gimp_parasite_list_length (private->parasites);
  cur = list = g_new0 (gchar *, count + 1);

  gimp_parasite_list_foreach (private->parasites, (GHFunc) list_func, &cur);

  return list;
}

gboolean
gimp_image_parasite_validate (GimpImage           *image,
                              const GimpParasite  *parasite,
                              GError             **error)
{
  const gchar *name;

  g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
  g_return_val_if_fail (parasite != NULL, FALSE);
  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

  name = gimp_parasite_get_name (parasite);

  if (strcmp (name, GIMP_ICC_PROFILE_PARASITE_NAME) == 0 ||
      strcmp (name, GIMP_SIMULATION_ICC_PROFILE_PARASITE_NAME) == 0)
    {
      return gimp_image_validate_icc_parasite (image, parasite,
                                               name, NULL, error);
    }
  else if (strcmp (name, "gimp-comment") == 0)
    {
      const gchar *data;
      guint32      length;
      gboolean     valid  = FALSE;

      data = gimp_parasite_get_data (parasite, &length);
      if (length > 0)
        {
          if (data[length - 1] == '\0')
            valid = g_utf8_validate (data, -1, NULL);
          else
            valid = g_utf8_validate (data, length, NULL);
        }

      if (! valid)
        {
          g_set_error (error, GIMP_ERROR, GIMP_FAILED,
                       _("'gimp-comment' parasite validation failed: "
                         "comment contains invalid UTF-8"));
          return FALSE;
        }
    }

  return TRUE;
}

void
gimp_image_parasite_attach (GimpImage          *image,
                            const GimpParasite *parasite,
                            gboolean            push_undo)
{
  GimpImagePrivate *private;
  GimpParasite      copy;
  const gchar      *name;

  g_return_if_fail (GIMP_IS_IMAGE (image));
  g_return_if_fail (parasite != NULL);

  private = GIMP_IMAGE_GET_PRIVATE (image);

  name = gimp_parasite_get_name (parasite);

  /*  this is so ugly and is only for the PDB  */
  if (strcmp (name, GIMP_ICC_PROFILE_PARASITE_NAME) == 0 ||
      strcmp (name, GIMP_SIMULATION_ICC_PROFILE_PARASITE_NAME) == 0)
    {
      GimpColorProfile *profile;
      GimpColorProfile *builtin;
      const guint8     *parasite_data;
      guint32           parasite_length;

      parasite_data = gimp_parasite_get_data (parasite, ¶site_length);
      profile =
        gimp_color_profile_new_from_icc_profile (parasite_data, parasite_length,
                                                 NULL);
      builtin = gimp_image_get_builtin_color_profile (image);

      if (gimp_color_profile_is_equal (profile, builtin))
        {
          /* setting the builtin profile is equal to removing the profile */
          gimp_image_parasite_detach (image, GIMP_ICC_PROFILE_PARASITE_NAME,
                                      push_undo);
          g_object_unref (profile);
          return;
        }

      g_object_unref (profile);
    }

  /*  make a temporary copy of the GimpParasite struct because
   *  gimp_parasite_shift_parent() changes it
   */

  copy = *parasite;

  /*  only set the dirty bit manually if we can be saved and the new
   *  parasite differs from the current one and we aren't undoable
   */

  if (push_undo && gimp_parasite_is_undoable (©))
    gimp_image_undo_push_image_parasite (image,
                                         C_("undo-type", "Attach Parasite to Image"),
                                         ©);

  /*  We used to push a cantundo on the stack here. This made the undo stack
   *  unusable (NULL on the stack) and prevented people from undoing after a
   *  save (since most save plug-ins attach an undoable comment parasite).
   *  Now we simply attach the parasite without pushing an undo. That way
   *  it's undoable but does not block the undo system.   --Sven
   */

  gimp_parasite_list_add (private->parasites, ©);

  if (push_undo && gimp_parasite_has_flag (©, GIMP_PARASITE_ATTACH_PARENT))
    {
      gimp_parasite_shift_parent (©);
      gimp_parasite_attach (image->gimp, ©);
    }

  if (strcmp (name, GIMP_ICC_PROFILE_PARASITE_NAME) == 0)
    _gimp_image_update_color_profile (image, parasite);

  if (strcmp (name, GIMP_SIMULATION_ICC_PROFILE_PARASITE_NAME) == 0)
    _gimp_image_update_simulation_profile (image, parasite);

  g_signal_emit (image, gimp_image_signals[PARASITE_ATTACHED], 0,
                 name);
}

void
gimp_image_parasite_detach (GimpImage   *image,
                            const gchar *name,
                            gboolean     push_undo)
{
  GimpImagePrivate   *private;
  const GimpParasite *parasite;

  g_return_if_fail (GIMP_IS_IMAGE (image));
  g_return_if_fail (name != NULL);

  private = GIMP_IMAGE_GET_PRIVATE (image);

  if (! (parasite = gimp_parasite_list_find (private->parasites, name)))
    return;

  if (push_undo && gimp_parasite_is_undoable (parasite))
    gimp_image_undo_push_image_parasite_remove (image,
                                                C_("undo-type", "Remove Parasite from Image"),
                                                name);

  gimp_parasite_list_remove (private->parasites, name);

  if (strcmp (name, GIMP_ICC_PROFILE_PARASITE_NAME) == 0)
    _gimp_image_update_color_profile (image, NULL);

  if (strcmp (name, GIMP_SIMULATION_ICC_PROFILE_PARASITE_NAME) == 0)
    _gimp_image_update_simulation_profile (image, NULL);

  g_signal_emit (image, gimp_image_signals[PARASITE_DETACHED], 0,
                 name);
}


/*  tattoos  */

GimpTattoo
gimp_image_get_new_tattoo (GimpImage *image)
{
  GimpImagePrivate *private;

  g_return_val_if_fail (GIMP_IS_IMAGE (image), 0);

  private = GIMP_IMAGE_GET_PRIVATE (image);

  private->tattoo_state++;

  if (G_UNLIKELY (private->tattoo_state == 0))
    g_warning ("%s: Tattoo state corrupted (integer overflow).", G_STRFUNC);

  return private->tattoo_state;
}

GimpTattoo
gimp_image_get_tattoo_state (GimpImage *image)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (image), 0);

  return GIMP_IMAGE_GET_PRIVATE (image)->tattoo_state;
}

gboolean
gimp_image_set_tattoo_state (GimpImage  *image,
                             GimpTattoo  val)
{
  GList      *all_items;
  GList      *list;
  gboolean    retval = TRUE;
  GimpTattoo  maxval = 0;

  g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);

  /* Check that the layer tattoos don't overlap with channel or vector ones */
  all_items = gimp_image_get_layer_list (image);

  for (list = all_items; list; list = g_list_next (list))
    {
      GimpTattoo ltattoo;

      ltattoo = gimp_item_get_tattoo (GIMP_ITEM (list->data));
      if (ltattoo > maxval)
        maxval = ltattoo;

      if (gimp_image_get_channel_by_tattoo (image, ltattoo))
        retval = FALSE; /* Oopps duplicated tattoo in channel */

      if (gimp_image_get_path_by_tattoo (image, ltattoo))
        retval = FALSE; /* Oopps duplicated tattoo in path */
    }

  g_list_free (all_items);

  /* Now check that the channel and path tattoos don't overlap */
  all_items = gimp_image_get_channel_list (image);

  for (list = all_items; list; list = g_list_next (list))
    {
      GimpTattoo ctattoo;

      ctattoo = gimp_item_get_tattoo (GIMP_ITEM (list->data));
      if (ctattoo > maxval)
        maxval = ctattoo;

      if (gimp_image_get_path_by_tattoo (image, ctattoo))
        retval = FALSE; /* Oopps duplicated tattoo in path */
    }

  g_list_free (all_items);

  /* Find the max tattoo value in the path */
  all_items = gimp_image_get_path_list (image);

  for (list = all_items; list; list = g_list_next (list))
    {
      GimpTattoo vtattoo;

      vtattoo = gimp_item_get_tattoo (GIMP_ITEM (list->data));
      if (vtattoo > maxval)
        maxval = vtattoo;
    }

  g_list_free (all_items);

  if (val < maxval)
    retval = FALSE;

  /* Must check if the state is valid */
  if (retval == TRUE)
    GIMP_IMAGE_GET_PRIVATE (image)->tattoo_state = val;

  return retval;
}


/*  projection  */

GimpProjection *
gimp_image_get_projection (GimpImage *image)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);

  return GIMP_IMAGE_GET_PRIVATE (image)->projection;
}


/*  layers / channels / path  */

GimpItemTree *
gimp_image_get_layer_tree (GimpImage *image)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);

  return GIMP_IMAGE_GET_PRIVATE (image)->layers;
}

GimpItemTree *
gimp_image_get_channel_tree (GimpImage *image)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);

  return GIMP_IMAGE_GET_PRIVATE (image)->channels;
}

GimpItemTree *
gimp_image_get_path_tree (GimpImage *image)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);

  return GIMP_IMAGE_GET_PRIVATE (image)->paths;
}

GimpContainer *
gimp_image_get_items (GimpImage *image,
                      GType      item_type)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);

  if (item_type == GIMP_TYPE_LAYER)
    {
      return gimp_image_get_layers (image);
    }
  else if (item_type == GIMP_TYPE_CHANNEL)
    {
      return gimp_image_get_channels (image);
    }
  else if (item_type == GIMP_TYPE_PATH)
    {
      return gimp_image_get_paths (image);
    }

  g_return_val_if_reached (NULL);
}

GimpContainer *
gimp_image_get_layers (GimpImage *image)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);

  return GIMP_IMAGE_GET_PRIVATE (image)->layers->container;
}

GimpContainer *
gimp_image_get_channels (GimpImage *image)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);

  return GIMP_IMAGE_GET_PRIVATE (image)->channels->container;
}

GimpContainer *
gimp_image_get_paths (GimpImage *image)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);

  return GIMP_IMAGE_GET_PRIVATE (image)->paths->container;
}

gint
gimp_image_get_n_layers (GimpImage *image)
{
  GimpItemStack *stack;

  g_return_val_if_fail (GIMP_IS_IMAGE (image), 0);

  stack = GIMP_ITEM_STACK (gimp_image_get_layers (image));

  return gimp_item_stack_get_n_items (stack);
}

gint
gimp_image_get_n_channels (GimpImage *image)
{
  GimpItemStack *stack;

  g_return_val_if_fail (GIMP_IS_IMAGE (image), 0);

  stack = GIMP_ITEM_STACK (gimp_image_get_channels (image));

  return gimp_item_stack_get_n_items (stack);
}

gint
gimp_image_get_n_paths (GimpImage *image)
{
  GimpItemStack *stack;

  g_return_val_if_fail (GIMP_IS_IMAGE (image), 0);

  stack = GIMP_ITEM_STACK (gimp_image_get_paths (image));

  return gimp_item_stack_get_n_items (stack);
}

GList *
gimp_image_get_layer_iter (GimpImage *image)
{
  GimpItemStack *stack;

  g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);

  stack = GIMP_ITEM_STACK (gimp_image_get_layers (image));

  return gimp_item_stack_get_item_iter (stack);
}

GList *
gimp_image_get_channel_iter (GimpImage *image)
{
  GimpItemStack *stack;

  g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);

  stack = GIMP_ITEM_STACK (gimp_image_get_channels (image));

  return gimp_item_stack_get_item_iter (stack);
}

GList *
gimp_image_get_path_iter (GimpImage *image)
{
  GimpItemStack *stack;

  g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);

  stack = GIMP_ITEM_STACK (gimp_image_get_paths (image));

  return gimp_item_stack_get_item_iter (stack);
}

GList *
gimp_image_get_layer_list (GimpImage *image)
{
  GimpItemStack *stack;

  g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);

  stack = GIMP_ITEM_STACK (gimp_image_get_layers (image));

  return gimp_item_stack_get_item_list (stack);
}

GList *
gimp_image_get_channel_list (GimpImage *image)
{
  GimpItemStack *stack;

  g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);

  stack = GIMP_ITEM_STACK (gimp_image_get_channels (image));

  return gimp_item_stack_get_item_list (stack);
}

GList *
gimp_image_get_path_list (GimpImage *image)
{
  GimpItemStack *stack;

  g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);

  stack = GIMP_ITEM_STACK (gimp_image_get_paths (image));

  return gimp_item_stack_get_item_list (stack);
}


/*  active drawable, layer, channel, path  */

void
gimp_image_unset_selected_channels (GimpImage *image)
{
  GimpImagePrivate *private;
  GList            *channels;

  g_return_if_fail (GIMP_IS_IMAGE (image));

  private = GIMP_IMAGE_GET_PRIVATE (image);

  channels = gimp_image_get_selected_channels (image);

  if (channels)
    {
      gimp_image_set_selected_channels (image, NULL);

      if (private->layer_stack)
        gimp_image_set_selected_layers (image, private->layer_stack->data);
    }
}

/**
 * gimp_image_is_selected_drawable:
 * @image:
 * @drawable:
 *
 * Checks if @drawable belong to the list of currently selected
 * drawables. It doesn't mean this is the only selected drawable (if
 * this is what you want to check, use
 * gimp_image_equal_selected_drawables() with a list containing only
 * this drawable).
 *
 * Returns: %TRUE is @drawable is one of the selected drawables.
 */

gboolean
gimp_image_is_selected_drawable (GimpImage    *image,
                                 GimpDrawable *drawable)
{
  GList    *selected_drawables;
  gboolean  found;

  selected_drawables = gimp_image_get_selected_drawables (image);
  found = (g_list_find (selected_drawables, drawable) != NULL);
  g_list_free (selected_drawables);

  return found;
}

/**
 * gimp_image_equal_selected_drawables:
 * @image:
 * @drawables:
 *
 * Compare the list of @drawables with the selected drawables in @image
 * (i.e. the result of gimp_image_equal_selected_drawables()).
 * The order of the @drawables does not matter, only if the size and
 * contents of the list is the same.
 */

gboolean
gimp_image_equal_selected_drawables (GimpImage *image,
                                     GList     *drawables)
{
  GList    *selected_drawables;
  GList    *iter;
  gboolean  equal = FALSE;

  selected_drawables = gimp_image_get_selected_drawables (image);

  if (g_list_length (drawables) == g_list_length (selected_drawables))
    {
      equal = TRUE;

      for (iter = drawables; iter; iter = iter->next)
        if (! g_list_find (selected_drawables, iter->data))
          {
            equal = FALSE;
            break;
          }
    }
  g_list_free (selected_drawables);

  return equal;
}

GList *
gimp_image_get_selected_drawables (GimpImage *image)
{
  GimpImagePrivate *private;
  GList            *selected_channels;
  GList            *selected_layers;

  g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);

  private = GIMP_IMAGE_GET_PRIVATE (image);

  selected_channels = gimp_item_tree_get_selected_items (private->channels);
  selected_layers   = gimp_item_tree_get_selected_items (private->layers);

  /*  If there is an active channel (a saved selection, etc.),
   *  we ignore the active layer
   */

  if (selected_channels)
    {
      return g_list_copy (selected_channels);
    }
  else if (selected_layers)
    {
      selected_layers = g_list_copy (selected_layers);
      if (g_list_length (selected_layers) == 1)
        {
          /* As a special case, if only one layer is selected and mask
           * edit is in progress, we return the mask as selected
           * drawable instead of the layer.
           */

          GimpLayer     *layer = GIMP_LAYER (selected_layers->data);
          GimpLayerMask *mask  = gimp_layer_get_mask (layer);

          if (mask && gimp_layer_get_edit_mask (layer))
            selected_layers->data = mask;
        }
      return selected_layers;
    }

  return NULL;
}

GList *
gimp_image_get_selected_items (GimpImage *image,
                               GType      item_type)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);

  if (item_type == GIMP_TYPE_LAYER)
    {
      return gimp_image_get_selected_layers (image);
    }
  else if (item_type == GIMP_TYPE_CHANNEL)
    {
      return gimp_image_get_selected_channels (image);
    }
  else if (item_type == GIMP_TYPE_PATH)
    {
      return gimp_image_get_selected_paths (image);
    }

  g_return_val_if_reached (NULL);
}

GList *
gimp_image_get_selected_layers (GimpImage *image)
{
  GimpImagePrivate *private;

  g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);

  private = GIMP_IMAGE_GET_PRIVATE (image);

  return gimp_item_tree_get_selected_items (private->layers);
}

GList *
gimp_image_get_selected_channels (GimpImage *image)
{
  GimpImagePrivate *private;

  g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);

  private = GIMP_IMAGE_GET_PRIVATE (image);

  return gimp_item_tree_get_selected_items (private->channels);
}

GList *
gimp_image_get_selected_paths (GimpImage *image)
{
  GimpImagePrivate *private;

  g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);

  private = GIMP_IMAGE_GET_PRIVATE (image);

  return gimp_item_tree_get_selected_items (private->paths);
}

void
gimp_image_set_selected_items (GimpImage *image,
                               GType      item_type,
                               GList     *items)
{
  g_return_if_fail (GIMP_IS_IMAGE (image));

  if (g_type_is_a (item_type, GIMP_TYPE_LAYER))
    {
      gimp_image_set_selected_layers (image, items);
    }
  else if (g_type_is_a (item_type, GIMP_TYPE_CHANNEL))
    {
      gimp_image_set_selected_channels (image, items);
    }
  else if (g_type_is_a (item_type, GIMP_TYPE_PATH))
    {
      gimp_image_set_selected_paths (image, items);
    }
  else
    {
      g_return_if_reached ();
    }
}

void
gimp_image_set_selected_layers (GimpImage *image,
                                GList     *layers)
{
  GimpImagePrivate *private;
  GimpLayer        *floating_sel;
  GList            *selected_layers;
  GList            *layers2;
  GList            *iter;
  gboolean          selection_changed = TRUE;

  g_return_if_fail (GIMP_IS_IMAGE (image));

  layers2 = g_list_copy (layers);
  for (iter = layers; iter; iter = iter->next)
    {
      g_return_if_fail (GIMP_IS_LAYER (iter->data));
      g_return_if_fail (gimp_item_get_image (GIMP_ITEM (iter->data)) == image);

      /* Silently remove non-attached layers from selection. Do not
       * error out on it as it may happen for instance when selection
       * changes while in the process of removing a layer group.
       */

      if (! gimp_item_is_attached (GIMP_ITEM (iter->data)))
        layers2 = g_list_remove (layers2, iter->data);
    }

  private = GIMP_IMAGE_GET_PRIVATE (image);

  floating_sel = gimp_image_get_floating_selection (image);

  /*  Make sure the floating_sel always is the active layer  */
  if (floating_sel && (g_list_length (layers2) != 1 || layers2->data != floating_sel))
    {
      g_list_free (layers2);
      return;
    }

  selected_layers = gimp_image_get_selected_layers (image);

  if (g_list_length (layers2) == g_list_length (selected_layers))
    {
      selection_changed = FALSE;
      for (iter = layers2; iter; iter = iter->next)
        {
          if (g_list_find (selected_layers, iter->data) == NULL)
            {
              selection_changed = TRUE;
              break;
            }
        }
    }

  if (selection_changed)
    {
      GList *layers3;

      /*  Don't cache selection info for the previous active layer  */
      if (selected_layers)
        gimp_drawable_invalidate_boundary (GIMP_DRAWABLE (selected_layers->data));

      layers3 = g_list_copy (layers2);
      gimp_item_tree_set_selected_items (private->layers, layers2);

      /* We cannot edit masks with multiple selected layers. */
      if (g_list_length (layers3) > 1)
        {
          for (iter = layers3; iter; iter = iter->next)
            {
              if (gimp_layer_get_mask (iter->data))
                gimp_layer_set_edit_mask (iter->data, FALSE);
            }
        }

      g_list_free (layers3);
    }
  else
    {
      g_list_free (layers2);
    }
}

void
gimp_image_set_selected_channels (GimpImage *image,
                                  GList     *channels)
{
  GimpImagePrivate *private;
  GList            *iter;

  g_return_if_fail (GIMP_IS_IMAGE (image));

  for (iter = channels; iter; iter = iter->next)
    {
      g_return_if_fail (GIMP_IS_CHANNEL (iter->data));
      g_return_if_fail (gimp_item_is_attached (GIMP_ITEM (iter->data)) &&
                        gimp_item_get_image (GIMP_ITEM (iter->data)) == image);
    }
  private = GIMP_IMAGE_GET_PRIVATE (image);

  /*  Not if there is a floating selection  */
  if (g_list_length (channels) > 0 && gimp_image_get_floating_selection (image))
    return;

  gimp_item_tree_set_selected_items (private->channels, g_list_copy (channels));
}

void
gimp_image_set_selected_paths (GimpImage *image,
                               GList     *paths)
{
  GimpImagePrivate *private;
  GList            *iter;

  g_return_if_fail (GIMP_IS_IMAGE (image));

  for (iter = paths; iter; iter = iter->next)
    {
      g_return_if_fail (GIMP_IS_PATH (iter->data));
      g_return_if_fail (gimp_item_is_attached (GIMP_ITEM (iter->data)) &&
                        gimp_item_get_image (GIMP_ITEM (iter->data)) == image);
    }

  private = GIMP_IMAGE_GET_PRIVATE (image);

  gimp_item_tree_set_selected_items (private->paths, g_list_copy (paths));
}


/*  layer, channel, path by tattoo  */

GimpLayer *
gimp_image_get_layer_by_tattoo (GimpImage  *image,
                                GimpTattoo  tattoo)
{
  GimpItemStack *stack;

  g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);

  stack = GIMP_ITEM_STACK (gimp_image_get_layers (image));

  return GIMP_LAYER (gimp_item_stack_get_item_by_tattoo (stack, tattoo));
}

GimpChannel *
gimp_image_get_channel_by_tattoo (GimpImage  *image,
                                  GimpTattoo  tattoo)
{
  GimpItemStack *stack;

  g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);

  stack = GIMP_ITEM_STACK (gimp_image_get_channels (image));

  return GIMP_CHANNEL (gimp_item_stack_get_item_by_tattoo (stack, tattoo));
}

GimpPath *
gimp_image_get_path_by_tattoo (GimpImage  *image,
                               GimpTattoo  tattoo)
{
  GimpItemStack *stack;

  g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);

  stack = GIMP_ITEM_STACK (gimp_image_get_paths (image));

  return GIMP_PATH (gimp_item_stack_get_item_by_tattoo (stack, tattoo));
}


/*  layer, channel, path by name  */

GimpLayer *
gimp_image_get_layer_by_name (GimpImage   *image,
                              const gchar *name)
{
  GimpItemTree *tree;

  g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
  g_return_val_if_fail (name != NULL, NULL);

  tree = gimp_image_get_layer_tree (image);

  return GIMP_LAYER (gimp_item_tree_get_item_by_name (tree, name));
}

GimpChannel *
gimp_image_get_channel_by_name (GimpImage   *image,
                                const gchar *name)
{
  GimpItemTree *tree;

  g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
  g_return_val_if_fail (name != NULL, NULL);

  tree = gimp_image_get_channel_tree (image);

  return GIMP_CHANNEL (gimp_item_tree_get_item_by_name (tree, name));
}

GimpPath *
gimp_image_get_path_by_name (GimpImage   *image,
                             const gchar *name)
{
  GimpItemTree *tree;

  g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
  g_return_val_if_fail (name != NULL, NULL);

  tree = gimp_image_get_path_tree (image);

  return GIMP_PATH (gimp_item_tree_get_item_by_name (tree, name));
}


/*  items  */

gboolean
gimp_image_reorder_item (GimpImage   *image,
                         GimpItem    *item,
                         GimpItem    *new_parent,
                         gint         new_index,
                         gboolean     push_undo,
                         const gchar *undo_desc)
{
  GimpItemTree *tree;
  gboolean      result;

  g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
  g_return_val_if_fail (GIMP_IS_ITEM (item), FALSE);
  g_return_val_if_fail (gimp_item_get_image (item) == image, FALSE);

  tree = gimp_item_get_tree (item);

  g_return_val_if_fail (tree != NULL, FALSE);

  if (push_undo)
    {
      if (! undo_desc)
        undo_desc = GIMP_ITEM_GET_CLASS (item)->reorder_desc;

      gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_IMAGE_ITEM_REORDER,
                                   undo_desc);
    }

  gimp_image_freeze_bounding_box (image);

  gimp_item_start_move (item, push_undo);

  /*  item and new_parent are type-checked in GimpItemTree
   */

  result = gimp_item_tree_reorder_item (tree, item,
                                        new_parent, new_index,
                                        push_undo, undo_desc);

  gimp_item_end_move (item, push_undo);

  gimp_image_thaw_bounding_box (image);

  if (push_undo)
    gimp_image_undo_group_end (image);

  return result;
}

gboolean
gimp_image_raise_item (GimpImage *image,
                       GimpItem  *item,
                       GError    **error)
{
  gint index;

  g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
  g_return_val_if_fail (GIMP_IS_ITEM (item), FALSE);
  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

  index = gimp_item_get_index (item);

  g_return_val_if_fail (index != -1, FALSE);

  if (index == 0)
    {
      g_set_error_literal (error,  GIMP_ERROR, GIMP_FAILED,
                           GIMP_ITEM_GET_CLASS (item)->raise_failed);
      return FALSE;
    }

  return gimp_image_reorder_item (image, item,
                                  gimp_item_get_parent (item), index - 1,
                                  TRUE, GIMP_ITEM_GET_CLASS (item)->raise_desc);
}

gboolean
gimp_image_raise_item_to_top (GimpImage *image,
                              GimpItem  *item)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
  g_return_val_if_fail (GIMP_IS_ITEM (item), FALSE);

  return gimp_image_reorder_item (image, item,
                                  gimp_item_get_parent (item), 0,
                                  TRUE, GIMP_ITEM_GET_CLASS (item)->raise_to_top_desc);
}

gboolean
gimp_image_lower_item (GimpImage *image,
                       GimpItem  *item,
                       GError    **error)
{
  GimpContainer *container;
  gint           index;

  g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
  g_return_val_if_fail (GIMP_IS_ITEM (item), FALSE);
  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

  container = gimp_item_get_container (item);

  g_return_val_if_fail (container != NULL, FALSE);

  index = gimp_item_get_index (item);

  if (index == gimp_container_get_n_children (container) - 1)
    {
      g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED,
                           GIMP_ITEM_GET_CLASS (item)->lower_failed);
      return FALSE;
    }

  return gimp_image_reorder_item (image, item,
                                  gimp_item_get_parent (item), index + 1,
                                  TRUE, GIMP_ITEM_GET_CLASS (item)->lower_desc);
}

gboolean
gimp_image_lower_item_to_bottom (GimpImage *image,
                                  GimpItem *item)
{
  GimpContainer *container;
  gint           length;

  g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
  g_return_val_if_fail (GIMP_IS_ITEM (item), FALSE);

  container = gimp_item_get_container (item);

  g_return_val_if_fail (container != NULL, FALSE);

  length = gimp_container_get_n_children (container);

  return gimp_image_reorder_item (image, item,
                                  gimp_item_get_parent (item), length - 1,
                                  TRUE, GIMP_ITEM_GET_CLASS (item)->lower_to_bottom_desc);
}


gboolean
gimp_image_add_item (GimpImage *image,
                     GimpItem  *item,
                     GimpItem  *parent,
                     gint       position,
                     gboolean   push_undo)
{
  g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);

  if (GIMP_IS_LAYER (item))
    {
      return gimp_image_add_layer (image, GIMP_LAYER (item),
                                   GIMP_LAYER (parent),
                                   position, push_undo);
    }
  else if (GIMP_IS_CHANNEL (item))
    {
      return gimp_image_add_channel (image, GIMP_CHANNEL (item),
                                     GIMP_CHANNEL (parent),
                                     position, push_undo);
    }
  else if (GIMP_IS_PATH (item))
    {
      return gimp_image_add_path (image, GIMP_PATH (item),
                                  GIMP_PATH (parent),
                                  position, push_undo);
    }

  g_return_val_if_reached (FALSE);
}

void
gimp_image_remove_item (GimpImage *image,
                        GimpItem  *item,
                        gboolean   push_undo,
                        GList     *new_selected)
{
  g_return_if_fail (GIMP_IS_IMAGE (image));

  if (GIMP_IS_LAYER (item))
    {
      gimp_image_remove_layer (image, GIMP_LAYER (item),
                               push_undo, new_selected);
    }
  else if (GIMP_IS_CHANNEL (item))
    {
      gimp_image_remove_channel (image, GIMP_CHANNEL (item),
                                 push_undo, new_selected);
    }
  else if (GIMP_IS_PATH (item))
    {
      gimp_image_remove_path (image, GIMP_PATH (item),
                              push_undo, new_selected);
    }
  else
    {
      g_return_if_reached ();
    }
}


/*  layers  */

gboolean
gimp_image_add_layer (GimpImage *image,
                      GimpLayer *layer,
                      GimpLayer *parent,
                      gint       position,
                      gboolean   push_undo)
{
  GimpImagePrivate *private;
  GList            *selected_layers;
  gboolean          old_has_alpha;

  g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);

  private = GIMP_IMAGE_GET_PRIVATE (image);

  /*  item and parent are type-checked in GimpItemTree
   */

  if (! gimp_item_tree_get_insert_pos (private->layers,
                                       (GimpItem *) layer,
                                       (GimpItem **) &parent,
                                       &position))
    return FALSE;

  gimp_image_unset_default_new_layer_mode (image);

  /*  If there is a floating selection (and this isn't it!),
   *  make sure the insert position is greater than 0
   */

  if (parent == NULL && position == 0 &&
      gimp_image_get_floating_selection (image))
    position = 1;

  old_has_alpha = gimp_image_has_alpha (image);

  if (push_undo)
    gimp_image_undo_push_layer_add (image, C_("undo-type", "Add Layer"),
                                    layer,
                                    gimp_image_get_selected_layers (image));

  gimp_item_tree_add_item (private->layers, GIMP_ITEM (layer),
                           GIMP_ITEM (parent), position);

  selected_layers = g_list_prepend (NULL, layer);
  gimp_image_set_selected_layers (image, selected_layers);
  g_list_free (selected_layers);

  /*  If the layer is a floating selection, attach it to the drawable  */
  if (gimp_layer_is_floating_sel (layer))
    gimp_drawable_attach_floating_sel (gimp_layer_get_floating_sel_drawable (layer),
                                       layer);

  /* If the layer is a vector layer, also add its path to the image */
  if (gimp_item_is_vector_layer (GIMP_ITEM (layer)))
    {
      GimpPath *path = gimp_vector_layer_get_path (GIMP_VECTOR_LAYER (layer));

      if (path                                         &&
          (! gimp_item_is_attached (GIMP_ITEM (path))) &&
          gimp_item_get_image (GIMP_ITEM (path)) == image)
        {
          gimp_image_add_path (image, path, NULL, -1, FALSE);
        }

      if (! gimp_item_is_rasterized (GIMP_ITEM (layer)))
        gimp_vector_layer_refresh (GIMP_VECTOR_LAYER (layer));
    }

  if (old_has_alpha != gimp_image_has_alpha (image))
    private->flush_accum.alpha_changed = TRUE;

  return TRUE;
}

void
gimp_image_remove_layer (GimpImage *image,
                         GimpLayer *layer,
                         gboolean   push_undo,
                         GList     *new_selected)
{
  GimpImagePrivate *private;
  GList            *selected_layers;
  gboolean          old_has_alpha;
  const gchar      *undo_desc;

  g_return_if_fail (GIMP_IS_IMAGE (image));
  g_return_if_fail (GIMP_IS_LAYER (layer));
  g_return_if_fail (gimp_item_is_attached (GIMP_ITEM (layer)));
  g_return_if_fail (gimp_item_get_image (GIMP_ITEM (layer)) == image);

  private = GIMP_IMAGE_GET_PRIVATE (image);

  gimp_image_unset_default_new_layer_mode (image);

  if (push_undo)
    gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_IMAGE_ITEM_REMOVE,
                                 C_("undo-type", "Remove Layer"));

  gimp_item_start_move (GIMP_ITEM (layer), push_undo);

  if (gimp_drawable_get_floating_sel (GIMP_DRAWABLE (layer)))
    {
      if (! push_undo)
        {
          g_warning ("%s() was called from an undo function while the layer "
                     "had a floating selection. Please report this at "
                     "https://www.gimp.org/bugs/", G_STRFUNC);
          return;
        }

      gimp_image_remove_layer (image,
                               gimp_drawable_get_floating_sel (GIMP_DRAWABLE (layer)),
                               TRUE, NULL);
    }

  selected_layers = g_list_copy (gimp_image_get_selected_layers (image));

  old_has_alpha = gimp_image_has_alpha (image);

  if (gimp_layer_is_floating_sel (layer))
    {
      undo_desc = C_("undo-type", "Remove Floating Selection");

      gimp_drawable_detach_floating_sel (gimp_layer_get_floating_sel_drawable (layer));
    }
  else
    {
      undo_desc = C_("undo-type", "Remove Layer");
    }

  if (push_undo)
    {
      gimp_image_rec_filter_remove_undo (image, layer);
      gimp_image_undo_push_layer_remove (image, undo_desc, layer,
                                         gimp_layer_get_parent (layer),
                                         gimp_item_get_index (GIMP_ITEM (layer)),
                                         selected_layers);
    }

  g_object_ref (layer);

  /*  Make sure we're not caching any old selection info  */
  if (g_list_find (selected_layers, layer))
    gimp_drawable_invalidate_boundary (GIMP_DRAWABLE (layer));

  /* Remove layer and its children from the MRU layer stack. */
  gimp_image_remove_from_layer_stack (image, layer);

  new_selected = gimp_item_tree_remove_item (private->layers,
                                             GIMP_ITEM (layer),
                                             new_selected);

  if (gimp_layer_is_floating_sel (layer))
    {
      /*  If this was the floating selection, activate the underlying drawable
       */

      floating_sel_activate_drawable (layer);
    }
  else if (selected_layers &&
           (g_list_find (selected_layers, layer) ||
            g_list_find_custom (selected_layers, layer,
                                (GCompareFunc) gimp_image_selected_is_descendant)))
    {
      gimp_image_set_selected_layers (image, new_selected);
    }

  gimp_item_end_move (GIMP_ITEM (layer), push_undo);

  g_object_unref (layer);
  g_list_free (selected_layers);
  if (new_selected)
    g_list_free (new_selected);

  if (old_has_alpha != gimp_image_has_alpha (image))
    private->flush_accum.alpha_changed = TRUE;

  if (push_undo)
    gimp_image_undo_group_end (image);
}

void
gimp_image_add_layers (GimpImage   *image,
                       GList       *layers,
                       GimpLayer   *parent,
                       gint         position,
                       gint         x,
                       gint         y,
                       gint         width,
                       gint         height,
                       const gchar *undo_desc)
{
  GimpImagePrivate *private;
  GList            *list;
  gint              layers_x      = G_MAXINT;
  gint              layers_y      = G_MAXINT;
  gint              layers_width  = 0;
  gint              layers_height = 0;
  gint              offset_x;
  gint              offset_y;

  g_return_if_fail (GIMP_IS_IMAGE (image));
  g_return_if_fail (layers != NULL);

  private = GIMP_IMAGE_GET_PRIVATE (image);

  /*  item and parent are type-checked in GimpItemTree
   */

  if (! gimp_item_tree_get_insert_pos (private->layers,
                                       (GimpItem *) layers->data,
                                       (GimpItem **) &parent,
                                       &position))
    return;

  for (list = layers; list; list = g_list_next (list))
    {
      GimpItem *item = GIMP_ITEM (list->data);
      gint      off_x, off_y;

      gimp_item_get_offset (item, &off_x, &off_y);

      layers_x = MIN (layers_x, off_x);
      layers_y = MIN (layers_y, off_y);

      layers_width  = MAX (layers_width,
                           off_x + gimp_item_get_width (item)  - layers_x);
      layers_height = MAX (layers_height,
                           off_y + gimp_item_get_height (item) - layers_y);
    }

  offset_x = x + (width  - layers_width)  / 2 - layers_x;
  offset_y = y + (height - layers_height) / 2 - layers_y;

  gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_LAYER_ADD, undo_desc);

  for (list = layers; list; list = g_list_next (list))
    {
      GimpItem *new_item = GIMP_ITEM (list->data);

      gimp_item_translate (new_item, offset_x, offset_y, FALSE);

      gimp_image_add_layer (image, GIMP_LAYER (new_item),
                            parent, position, TRUE);
      position++;
    }

  if (layers)
    gimp_image_set_selected_layers (image, layers);

  gimp_image_undo_group_end (image);
}


/*  channels  */

gboolean
gimp_image_add_channel (GimpImage   *image,
                        GimpChannel *channel,
                        GimpChannel *parent,
                        gint         position,
                        gboolean     push_undo)
{
  GimpImagePrivate *private;
  GList            *channels;

  g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);

  private = GIMP_IMAGE_GET_PRIVATE (image);

  /*  item and parent are type-checked in GimpItemTree
   */

  if (! gimp_item_tree_get_insert_pos (private->channels,
                                       (GimpItem *) channel,
                                       (GimpItem **) &parent,
                                       &position))
    return FALSE;

  if (push_undo)
    gimp_image_undo_push_channel_add (image, C_("undo-type", "Add Channel"),
                                      channel,
                                      gimp_image_get_selected_channels (image));

  gimp_item_tree_add_item (private->channels, GIMP_ITEM (channel),
                           GIMP_ITEM (parent), position);

  channels = g_list_prepend (NULL, channel);
  gimp_image_set_selected_channels (image, channels);
  g_list_free (channels);

  return TRUE;
}

void
gimp_image_remove_channel (GimpImage   *image,
                           GimpChannel *channel,
                           gboolean     push_undo,
                           GList       *new_selected)
{
  GimpImagePrivate *private;
  GList            *selected_channels;

  g_return_if_fail (GIMP_IS_IMAGE (image));
  g_return_if_fail (GIMP_IS_CHANNEL (channel));
  g_return_if_fail (gimp_item_is_attached (GIMP_ITEM (channel)));
  g_return_if_fail (gimp_item_get_image (GIMP_ITEM (channel)) == image);

  if (push_undo)
    gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_IMAGE_ITEM_REMOVE,
                                 C_("undo-type", "Remove Channel"));

  gimp_item_start_move (GIMP_ITEM (channel), push_undo);

  if (gimp_drawable_get_floating_sel (GIMP_DRAWABLE (channel)))
    {
      if (! push_undo)
        {
          g_warning ("%s() was called from an undo function while the channel "
                     "had a floating selection. Please report this at "
                     "https://www.gimp.org/bugs/", G_STRFUNC);
          return;
        }

      gimp_image_remove_layer (image,
                               gimp_drawable_get_floating_sel (GIMP_DRAWABLE (channel)),
                               TRUE, NULL);
    }

  private = GIMP_IMAGE_GET_PRIVATE (image);

  selected_channels = gimp_image_get_selected_channels (image);
  selected_channels = g_list_copy (selected_channels);

  if (push_undo)
    gimp_image_undo_push_channel_remove (image, C_("undo-type", "Remove Channel"), channel,
                                         gimp_channel_get_parent (channel),
                                         gimp_item_get_index (GIMP_ITEM (channel)),
                                         selected_channels);

  g_object_ref (channel);

  new_selected = gimp_item_tree_remove_item (private->channels,
                                             GIMP_ITEM (channel),
                                             new_selected);

  if (selected_channels &&
      (g_list_find (selected_channels, channel) ||
       g_list_find_custom (selected_channels, channel,
                           (GCompareFunc) gimp_image_selected_is_descendant)))
    {
      if (new_selected)
        gimp_image_set_selected_channels (image, new_selected);
      else
        gimp_image_unset_selected_channels (image);
    }

  g_list_free (selected_channels);

  gimp_item_end_move (GIMP_ITEM (channel), push_undo);

  g_object_unref (channel);
  if (new_selected)
    g_list_free (new_selected);

  if (push_undo)
    gimp_image_undo_group_end (image);
}


/*  paths  */

gboolean
gimp_image_add_path (GimpImage   *image,
                     GimpPath    *path,
                     GimpPath    *parent,
                     gint         position,
                     gboolean     push_undo)
{
  GimpImagePrivate *private;
  GList            *list = NULL;

  g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);

  private = GIMP_IMAGE_GET_PRIVATE (image);

  /*  item and parent are type-checked in GimpItemTree
   */

  if (! gimp_item_tree_get_insert_pos (private->paths,
                                       (GimpItem *) path,
                                       (GimpItem **) &parent,
                                       &position))
    return FALSE;

  if (push_undo)
    gimp_image_undo_push_path_add (image, C_("undo-type", "Add Path"),
                                   path,
                                   gimp_image_get_selected_paths (image));

  gimp_item_tree_add_item (private->paths, GIMP_ITEM (path),
                           GIMP_ITEM (parent), position);

  if (path != NULL)
    list = g_list_prepend (NULL, path);

  gimp_image_set_selected_paths (image, list);

  g_list_free (list);

  return TRUE;
}

void
gimp_image_remove_path (GimpImage   *image,
                        GimpPath    *path,
                        gboolean     push_undo,
                        GList       *new_selected)
{
  GimpImagePrivate *private;
  GList            *selected_path;

  g_return_if_fail (GIMP_IS_IMAGE (image));
  g_return_if_fail (GIMP_IS_PATH (path));
  g_return_if_fail (gimp_item_is_attached (GIMP_ITEM (path)));
  g_return_if_fail (gimp_item_get_image (GIMP_ITEM (path)) == image);

  private = GIMP_IMAGE_GET_PRIVATE (image);

  if (push_undo)
    gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_IMAGE_ITEM_REMOVE,
                                 C_("undo-type", "Remove Path"));

  gimp_item_start_move (GIMP_ITEM (path), push_undo);

  selected_path = gimp_image_get_selected_paths (image);
  selected_path = g_list_copy (selected_path);

  if (push_undo)
    gimp_image_undo_push_path_remove (image, C_("undo-type", "Remove Path"), path,
                                      gimp_path_get_parent (path),
                                      gimp_item_get_index (GIMP_ITEM (path)),
                                      selected_path);

  g_object_ref (path);

  new_selected = gimp_item_tree_remove_item (private->paths,
                                             GIMP_ITEM (path),
                                             new_selected);

  if (selected_path &&
      (g_list_find (selected_path, path) ||
       g_list_find_custom (selected_path, path,
                           (GCompareFunc) gimp_image_selected_is_descendant)))
    {
      gimp_image_set_selected_paths (image, new_selected);
    }

  g_list_free (selected_path);

  gimp_item_end_move (GIMP_ITEM (path), push_undo);

  g_object_unref (path);
  if (new_selected)
    g_list_free (new_selected);

  if (push_undo)
    gimp_image_undo_group_end (image);
}


/*  item sets  */

/*
 * gimp_image_store_item_set:
 * @image:
 * @set: (transfer full): a set of items which @images takes ownership
 *                        of.
 *
 * Store a new set of @layers.
 * If a set with the same name and type existed, this call will silently
 * replace it with the new set of layers.
 */

void
gimp_image_store_item_set (GimpImage    *image,
                           GimpItemList *set)
{
  GimpImagePrivate  *private;
  GList            **stored_sets;
  GList             *iter;
  guint              signal;
  GType              item_type;

  g_return_if_fail (GIMP_IS_IMAGE (image));
  g_return_if_fail (GIMP_IS_ITEM_LIST (set));

  private = GIMP_IMAGE_GET_PRIVATE (image);

  signal    = gimp_image_signals[ITEM_SETS_CHANGED];
  item_type = gimp_item_list_get_item_type (set);
  if (item_type == GIMP_TYPE_LAYER)
    stored_sets = &private->stored_layer_sets;
  else if (item_type == GIMP_TYPE_CHANNEL)
    stored_sets = &private->stored_channel_sets;
  else if (item_type == GIMP_TYPE_PATH)
    stored_sets = &private->stored_path_sets;
  else
    g_return_if_reached ();

  for (iter = *stored_sets; iter; iter = iter->next)
    {
      gboolean         is_pattern;
      gboolean         is_pattern2;
      GimpSelectMethod pattern_syntax;
      GimpSelectMethod pattern_syntax2;

      is_pattern  = gimp_item_list_is_pattern (iter->data, &pattern_syntax);
      is_pattern2 = gimp_item_list_is_pattern (set, &pattern_syntax2);

      /* Remove a previous item set of same type and name. */
      if (is_pattern == is_pattern2 && (! is_pattern || pattern_syntax == pattern_syntax2) &&
          g_strcmp0 (gimp_object_get_name (iter->data), gimp_object_get_name (set)) == 0)
        break;
    }
  if (iter)
    {
      g_object_unref (iter->data);
      *stored_sets = g_list_delete_link (*stored_sets, iter);
    }

  *stored_sets = g_list_prepend (*stored_sets, set);
  g_signal_emit (image, signal, 0, item_type);
}

/*
 * @gimp_image_unlink_item_set:
 * @image:
 * @link_name:
 *
 * Remove the set of layers named @link_name.
 *
 * Returns: %TRUE if the set was removed, %FALSE if no sets with this
 *          name existed.
 */

gboolean
gimp_image_unlink_item_set (GimpImage    *image,
                            GimpItemList *set)
{
  GimpImagePrivate  *private;
  GList             *found;
  GList            **stored_sets;
  guint              signal;
  GType              item_type;
  gboolean           success;

  g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);

  private = GIMP_IMAGE_GET_PRIVATE (image);

  signal    = gimp_image_signals[ITEM_SETS_CHANGED];
  item_type = gimp_item_list_get_item_type (set);
  if (item_type == GIMP_TYPE_LAYER)
    stored_sets = &private->stored_layer_sets;
  else if (item_type == GIMP_TYPE_CHANNEL)
    stored_sets = &private->stored_channel_sets;
  else if (item_type == GIMP_TYPE_PATH)
    stored_sets = &private->stored_path_sets;
  else
    g_return_val_if_reached (FALSE);

  found = g_list_find (*stored_sets, set);
  success = (found != NULL);
  if (success)
    {
      *stored_sets = g_list_delete_link (*stored_sets, found);
      g_object_unref (set);
      g_signal_emit (image, signal, 0, item_type);
    }

  return success;
}

/*
 * @gimp_image_get_stored_item_sets:
 * @image:
 * @item_type:
 *
 * Returns: (transfer none): the list of all the layer sets (which you
 *          should not modify). Order of items is relevant.
 */

GList *
gimp_image_get_stored_item_sets (GimpImage *image,
                                 GType      item_type)
{
  GimpImagePrivate *private;

  g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);

  private = GIMP_IMAGE_GET_PRIVATE (image);

  if (item_type == GIMP_TYPE_LAYER)
    return private->stored_layer_sets;
  else if (item_type == GIMP_TYPE_CHANNEL)
    return private->stored_channel_sets;
  else if (item_type == GIMP_TYPE_PATH)
    return private->stored_path_sets;

  g_return_val_if_reached (FALSE);
}

/*
 * @gimp_image_select_item_set:
 * @image:
 * @set:
 *
 * Replace currently selected layers in @image with the layers belonging
 * to @set.
 */

void
gimp_image_select_item_set (GimpImage    *image,
                            GimpItemList *set)
{
  GList  *items;
  GError *error = NULL;

  g_return_if_fail (GIMP_IS_IMAGE (image));
  g_return_if_fail (GIMP_IS_ITEM_LIST (set));

  items = gimp_item_list_get_items (set, &error);

  if (! error)
    {
      GType item_type = gimp_item_list_get_item_type (set);

      if (item_type == GIMP_TYPE_LAYER)
        gimp_image_set_selected_layers (image, items);
      else if (item_type == GIMP_TYPE_CHANNEL)
        gimp_image_set_selected_channels (image, items);
      else if (item_type == GIMP_TYPE_PATH)
        gimp_image_set_selected_paths (image, items);
      else
        g_return_if_reached ();
    }

  g_list_free (items);
  g_clear_error (&error);
}

/*
 * @gimp_image_add_item_set:
 * @image:
 * @set:
 *
 * Add the layers belonging to @set to the items currently selected in
 * @image.
 */

void
gimp_image_add_item_set (GimpImage    *image,
                         GimpItemList *set)
{
  GList  *items;
  GError *error = NULL;

  g_return_if_fail (GIMP_IS_IMAGE (image));
  g_return_if_fail (GIMP_IS_ITEM_LIST (set));

  items = gimp_item_list_get_items (set, &error);

  if (! error)
    {
      GList *selected;
      GList *iter;
      GType  item_type = gimp_item_list_get_item_type (set);

      if (item_type == GIMP_TYPE_LAYER)
        selected = gimp_image_get_selected_layers (image);
      else if (item_type == GIMP_TYPE_CHANNEL)
        selected = gimp_image_get_selected_channels (image);
      else if (item_type == GIMP_TYPE_PATH)
        selected = gimp_image_get_selected_paths (image);
      else
        g_return_if_reached ();

      selected = g_list_copy (selected);
      for (iter = items; iter; iter = iter->next)
        {
          if (! g_list_find (selected, iter->data))
            selected = g_list_prepend (selected, iter->data);
        }

      if (item_type == GIMP_TYPE_LAYER)
        gimp_image_set_selected_layers (image, selected);
      else if (item_type == GIMP_TYPE_CHANNEL)
        gimp_image_set_selected_channels (image, selected);
      else if (item_type == GIMP_TYPE_PATH)
        gimp_image_set_selected_paths (image, items);

      g_list_free (selected);
    }
  g_clear_error (&error);
}

/*
 * @gimp_image_remove_item_set:
 * @image:
 * @set:
 *
 * Remove the layers belonging to the set named @link_name (which must
 * exist) from the layers currently selected in @image.
 *
 * Returns: %TRUE if the selection change is done (even if it turned out
 *          selected layers stay the same), %FALSE if no sets with this
 *          name existed.
 */

void
gimp_image_remove_item_set (GimpImage    *image,
                            GimpItemList *set)
{
  GList  *items;
  GError *error = NULL;

  g_return_if_fail (GIMP_IS_IMAGE (image));
  g_return_if_fail (GIMP_IS_ITEM_LIST (set));

  items = gimp_item_list_get_items (set, &error);

  if (! error)
    {
      GList *selected;
      GList *iter;
      GType  item_type = gimp_item_list_get_item_type (set);

      if (item_type == GIMP_TYPE_LAYER)
        selected = gimp_image_get_selected_layers (image);
      else if (item_type == GIMP_TYPE_CHANNEL)
        selected = gimp_image_get_selected_channels (image);
      else if (item_type == GIMP_TYPE_PATH)
        selected = gimp_image_get_selected_paths (image);
      else
        g_return_if_reached ();

      selected = g_list_copy (selected);
      for (iter = items; iter; iter = iter->next)
        {
          GList *remove;

          if ((remove = g_list_find (selected, iter->data)))
            selected = g_list_delete_link (selected, remove);
        }

      if (item_type == GIMP_TYPE_LAYER)
        gimp_image_set_selected_layers (image, selected);
      else if (item_type == GIMP_TYPE_CHANNEL)
        gimp_image_set_selected_channels (image, selected);
      else if (item_type == GIMP_TYPE_PATH)
        gimp_image_set_selected_paths (image, items);

      g_list_free (selected);
    }
  g_clear_error (&error);
}

/*
 * @gimp_image_intersect_item_set:
 * @image:
 * @set:
 *
 * Remove any layers from the layers currently selected in @image if
 * they don't also belong to the set named @link_name (which must
 * exist).
 *
 * Returns: %TRUE if the selection change is done (even if it turned out
 *          selected layers stay the same), %FALSE if no sets with this
 *          name existed.
 */

void
gimp_image_intersect_item_set (GimpImage    *image,
                               GimpItemList *set)
{
  GList *items;
  GError *error = NULL;

  g_return_if_fail (GIMP_IS_IMAGE (image));
  g_return_if_fail (GIMP_IS_ITEM_LIST (set));

  items = gimp_item_list_get_items (set, &error);

  if (! error)
    {
      GList *selected;
      GList *remove = NULL;
      GList *iter;
      GType  item_type = gimp_item_list_get_item_type (set);

      if (item_type == GIMP_TYPE_LAYER)
        selected = gimp_image_get_selected_layers (image);
      else if (item_type == GIMP_TYPE_CHANNEL)
        selected = gimp_image_get_selected_channels (image);
      else if (item_type == GIMP_TYPE_PATH)
        selected = gimp_image_get_selected_paths (image);
      else
        g_return_if_reached ();

      selected = g_list_copy (selected);

      /* Remove items in selected but not in items. */
      for (iter = selected; iter; iter = iter->next)
        {
          if (! g_list_find (items, iter->data))
            remove = g_list_prepend (remove, iter);
        }
      for (iter = remove; iter; iter = iter->next)
        selected = g_list_delete_link (selected, iter->data);
      g_list_free (remove);

      /* Finally select the intersection. */
      if (item_type == GIMP_TYPE_LAYER)
        gimp_image_set_selected_layers (image, selected);
      else if (item_type == GIMP_TYPE_CHANNEL)
        gimp_image_set_selected_channels (image, selected);
      else if (item_type == GIMP_TYPE_PATH)
        gimp_image_set_selected_paths (image, items);

      g_list_free (selected);
    }
  g_clear_error (&error);
}


/*  hidden items  */

/* Sometimes you want to create a channel or other types of drawables to
 * work on them without adding them to the public trees and to be
 * visible in the GUI. This is when you create hidden items. No undo
 * steps will ever be created either for any processing on these items.
 *
 * Note that you are expected to manage the hidden items properly. In
 * particular, once you are done with them, remove them with
 * gimp_image_remove_hidden_item() and free them.
 * @image is not assuming ownership of @item.
 */

gboolean
gimp_image_add_hidden_item (GimpImage *image,
                            GimpItem  *item)
{
  GimpImagePrivate *private;

  g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
  g_return_val_if_fail (GIMP_IS_ITEM (item), FALSE);
  g_return_val_if_fail (! gimp_item_is_attached (item), FALSE);
  g_return_val_if_fail (gimp_item_get_image (item) == image, FALSE);

  private = GIMP_IMAGE_GET_PRIVATE (image);

  private->hidden_items = g_list_prepend (private->hidden_items, item);

  return TRUE;
}

void
gimp_image_remove_hidden_item (GimpImage *image,
                               GimpItem  *item)
{
  GimpImagePrivate *private;

  g_return_if_fail (GIMP_IS_IMAGE (image));
  g_return_if_fail (GIMP_IS_ITEM (item));
  g_return_if_fail (gimp_item_get_image (item) == image);

  private = GIMP_IMAGE_GET_PRIVATE (image);

  g_return_if_fail (g_list_find (private->hidden_items, item) != NULL);

  private->hidden_items = g_list_remove (private->hidden_items, item);
}

gboolean
gimp_image_is_hidden_item (GimpImage *image,
                           GimpItem  *item)
{
  GimpImagePrivate *private;

  g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
  g_return_val_if_fail (GIMP_IS_ITEM (item), FALSE);
  g_return_val_if_fail (gimp_item_get_image (item) == image, FALSE);

  private = GIMP_IMAGE_GET_PRIVATE (image);

  return (g_list_find (private->hidden_items, item) != NULL);
}

gboolean
gimp_image_coords_in_active_pickable (GimpImage        *image,
                                      const GimpCoords *coords,
                                      gboolean          show_all,
                                      gboolean          sample_merged,
                                      gboolean          selected_only)
{
  gint     x, y;
  gboolean in_pickable = FALSE;

  g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);

  x = floor (coords->x);
  y = floor (coords->y);

  if (sample_merged)
    {
      if (show_all || (x >= 0 && x < gimp_image_get_width  (image) &&
                       y >= 0 && y < gimp_image_get_height (image)))
        {
          in_pickable = TRUE;
        }
    }
  else
    {
      GList *drawables = gimp_image_get_selected_drawables (image);
      GList *iter;

      for (iter = drawables; iter; iter = iter->next)
        {
          GimpItem *item = iter->data;
          gint      off_x, off_y;
          gint      d_x, d_y;

          gimp_item_get_offset (item, &off_x, &off_y);

          d_x = x - off_x;
          d_y = y - off_y;

          if (d_x >= 0 && d_x < gimp_item_get_width  (item) &&
              d_y >= 0 && d_y < gimp_item_get_height (item))
            {
              in_pickable = TRUE;
              break;
            }
        }
      g_list_free (drawables);
    }

  if (in_pickable && selected_only)
    {
      GimpChannel *selection = gimp_image_get_mask (image);

      if (! gimp_channel_is_empty (selection) &&
          ! gimp_pickable_get_opacity_at (GIMP_PICKABLE (selection),
                                          x, y))
        {
          in_pickable = FALSE;
        }
    }

  return in_pickable;
}

void
gimp_image_invalidate_previews (GimpImage *image)
{
  GimpItemStack *layers;
  GimpItemStack *channels;

  g_return_if_fail (GIMP_IS_IMAGE (image));

  layers   = GIMP_ITEM_STACK (gimp_image_get_layers (image));
  channels = GIMP_ITEM_STACK (gimp_image_get_channels (image));

  gimp_item_stack_invalidate_previews (layers);
  gimp_item_stack_invalidate_previews (channels);
}

/* Sets the image into a "converting" state, which is there to warn other code
 * (such as shell render code) that the image properties might be in an
 * inconsistent state. For instance when converting to another precision with
 * gimp_image_convert_precision(), the babl format may be updated first, and the
 * profile later, after all drawables are converted. Rendering the image
 * in-between would at best render broken previews (at worst, crash, e.g.
 * because we depend on allocated data which might have become too small).
 */

void
gimp_image_set_converting (GimpImage *image,
                           gboolean   converting)
{
  g_return_if_fail (GIMP_IS_IMAGE (image));

  g_object_set (image,
                "converting", converting,
                NULL);
}

gboolean
gimp_image_get_converting (GimpImage *image)
{
  GimpImagePrivate *private;
  g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);

  private = GIMP_IMAGE_GET_PRIVATE (image);

  return private->converting;
}

Messung
CHZG
81989491

¤ 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.0.191Bemerkung:  (vorverarbeitet am  2026-11-03) ¤

*Bot Zugriff






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=417287