summaryrefslogtreecommitdiffstats
path: root/app/core/gimpimage.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 16:23:22 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 16:23:22 +0000
commite42129241681dde7adae7d20697e7b421682fbb4 (patch)
treeaf1fe815a5e639e68e59fabd8395ec69458b3e5e /app/core/gimpimage.c
parentInitial commit. (diff)
downloadgimp-e42129241681dde7adae7d20697e7b421682fbb4.tar.xz
gimp-e42129241681dde7adae7d20697e7b421682fbb4.zip
Adding upstream version 2.10.22.upstream/2.10.22upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'app/core/gimpimage.c')
-rw-r--r--app/core/gimpimage.c5146
1 files changed, 5146 insertions, 0 deletions
diff --git a/app/core/gimpimage.c b/app/core/gimpimage.c
new file mode 100644
index 0000000..a13c99a
--- /dev/null
+++ b/app/core/gimpimage.c
@@ -0,0 +1,5146 @@
+/* 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-floating-selection.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 "gimpitemtree.h"
+#include "gimplayer.h"
+#include "gimplayer-floating-selection.h"
+#include "gimplayermask.h"
+#include "gimplayerstack.h"
+#include "gimpmarshal.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 "vectors/gimpvectors.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,
+ ACTIVE_LAYER_CHANGED,
+ ACTIVE_CHANNEL_CHANGED,
+ ACTIVE_VECTORS_CHANGED,
+ LINKED_ITEMS_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,
+ LAST_SIGNAL
+};
+
+enum
+{
+ PROP_0,
+ PROP_GIMP,
+ PROP_ID,
+ PROP_WIDTH,
+ PROP_HEIGHT,
+ PROP_BASE_TYPE,
+ PROP_PRECISION,
+ PROP_METADATA,
+ PROP_BUFFER,
+ PROP_SYMMETRY
+};
+
+
+/* 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 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_pixel_to_srgb (GimpPickable *pickable,
+ const Babl *format,
+ gpointer pixel,
+ GimpRGB *color);
+static void gimp_image_srgb_to_pixel (GimpPickable *pickable,
+ const GimpRGB *color,
+ 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_active_layer_notify (GimpItemTree *tree,
+ const GParamSpec *pspec,
+ GimpImage *image);
+static void gimp_image_active_channel_notify (GimpItemTree *tree,
+ const GParamSpec *pspec,
+ GimpImage *image);
+static void gimp_image_active_vectors_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);
+
+
+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,
+ gimp_marshal_VOID__VOID,
+ 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,
+ gimp_marshal_VOID__VOID,
+ 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,
+ gimp_marshal_VOID__VOID,
+ 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,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ gimp_image_signals[ACTIVE_LAYER_CHANGED] =
+ g_signal_new ("active-layer-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpImageClass, active_layer_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ gimp_image_signals[ACTIVE_CHANNEL_CHANGED] =
+ g_signal_new ("active-channel-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpImageClass, active_channel_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ gimp_image_signals[ACTIVE_VECTORS_CHANGED] =
+ g_signal_new ("active-vectors-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpImageClass, active_vectors_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ gimp_image_signals[LINKED_ITEMS_CHANGED] =
+ g_signal_new ("linked-items-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpImageClass, linked_items_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ 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,
+ gimp_marshal_VOID__ENUM,
+ 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,
+ gimp_marshal_VOID__ENUM,
+ 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,
+ gimp_marshal_VOID__VOID,
+ 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,
+ gimp_marshal_VOID__VOID,
+ 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,
+ gimp_marshal_VOID__VOID,
+ 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,
+ gimp_marshal_VOID__VOID,
+ 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,
+ gimp_marshal_VOID__VOID,
+ 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,
+ gimp_marshal_VOID__FLAGS,
+ 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,
+ gimp_marshal_VOID__FLAGS,
+ 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,
+ gimp_marshal_VOID__VOID,
+ 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,
+ gimp_marshal_VOID__OBJECT,
+ 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,
+ gimp_marshal_VOID__OBJECT,
+ 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,
+ gimp_marshal_VOID__OBJECT,
+ 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,
+ gimp_marshal_VOID__OBJECT,
+ 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,
+ gimp_marshal_VOID__OBJECT,
+ 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,
+ gimp_marshal_VOID__OBJECT,
+ 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,
+ gimp_marshal_VOID__OBJECT,
+ 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,
+ gimp_marshal_VOID__OBJECT,
+ 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,
+ g_cclosure_marshal_VOID__STRING,
+ 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,
+ g_cclosure_marshal_VOID__STRING,
+ 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,
+ gimp_marshal_VOID__INT,
+ 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);
+
+ 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->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->active_layer_changed = NULL;
+ klass->active_channel_changed = NULL;
+ klass->active_vectors_changed = NULL;
+ klass->linked_items_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_GAMMA,
+ 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));
+}
+
+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;
+}
+
+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;
+ iface->pixel_to_srgb = gimp_image_pixel_to_srgb;
+ iface->srgb_to_pixel = gimp_image_srgb_to_pixel;
+}
+
+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_GAMMA;
+ 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->colormap = NULL;
+ private->n_colors = 0;
+ private->palette = NULL;
+
+ private->is_color_managed = TRUE;
+
+ private->metadata = NULL;
+
+ 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->vectors = gimp_item_tree_new (image,
+ GIMP_TYPE_ITEM_STACK,
+ GIMP_TYPE_VECTORS);
+ private->layer_stack = NULL;
+
+ g_signal_connect (private->projection, "notify::buffer",
+ G_CALLBACK (gimp_image_projection_buffer_notify),
+ image);
+
+ g_signal_connect (private->layers, "notify::active-item",
+ G_CALLBACK (gimp_image_active_layer_notify),
+ image);
+ g_signal_connect (private->channels, "notify::active-item",
+ G_CALLBACK (gimp_image_active_channel_notify),
+ image);
+ g_signal_connect (private->vectors, "notify::active-item",
+ G_CALLBACK (gimp_image_active_vectors_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;
+ gimp_rgba_set (&private->quick_mask_color, 1.0, 0.0, 0.0, 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));
+
+ private->quick_mask_color = 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::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_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;
+ 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->colormap)
+ gimp_image_colormap_dispose (image);
+
+ gimp_image_undo_free (image);
+
+ 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->vectors));
+
+ 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->colormap)
+ gimp_image_colormap_free (image);
+
+ if (private->color_profile)
+ _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->vectors);
+
+ if (private->layer_stack)
+ {
+ g_slist_free (private->layer_stack);
+ 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);
+}
+
+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;
+
+ if (gimp_image_get_colormap (image))
+ memsize += GIMP_IMAGE_COLORMAP_SIZE;
+
+ 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->vectors),
+ 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_vectors_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);
+
+ if (private->colormap && private->n_colors > 0)
+ {
+ babl_palette_set_palette (private->babl_palette_rgb,
+ gimp_babl_format (GIMP_RGB,
+ private->precision, FALSE),
+ private->colormap,
+ private->n_colors);
+ babl_palette_set_palette (private->babl_palette_rgba,
+ gimp_babl_format (GIMP_RGB,
+ private->precision, FALSE),
+ private->colormap,
+ private->n_colors);
+ }
+
+ 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 = NULL;
+
+ if (gimp_image_get_is_color_managed (image))
+ 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 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);
+
+ case GIMP_GRAY:
+ return gimp_image_get_format (image, GIMP_GRAY,
+ gimp_image_get_precision (image), TRUE);
+ }
+
+ 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 void
+gimp_image_pixel_to_srgb (GimpPickable *pickable,
+ const Babl *format,
+ gpointer pixel,
+ GimpRGB *color)
+{
+ gimp_image_color_profile_pixel_to_srgb (GIMP_IMAGE (pickable),
+ format, pixel, color);
+}
+
+static void
+gimp_image_srgb_to_pixel (GimpPickable *pickable,
+ const GimpRGB *color,
+ const Babl *format,
+ gpointer pixel)
+{
+ gimp_image_color_profile_srgb_to_pixel (GIMP_IMAGE (pickable),
+ color, 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_connect_to (layers_node, "output",
+ private->visible_mask, "input");
+
+ channels_node =
+ gimp_filter_stack_get_graph (GIMP_FILTER_STACK (private->channels->container));
+
+ gegl_node_add_child (private->graph, channels_node);
+
+ gegl_node_connect_to (private->visible_mask, "output",
+ channels_node, "input");
+
+ output = gegl_node_get_output_proxy (private->graph, "output");
+
+ gegl_node_connect_to (channels_node, "output",
+ output, "input");
+
+ 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)))
+ {
+ GIMP_IMAGE_GET_PRIVATE (image)->quick_mask_color = channel->color;
+ }
+}
+
+static void
+gimp_image_active_layer_notify (GimpItemTree *tree,
+ const GParamSpec *pspec,
+ GimpImage *image)
+{
+ GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image);
+ GimpLayer *layer = gimp_image_get_active_layer (image);
+
+ if (layer)
+ {
+ /* Configure the layer stack to reflect this change */
+ private->layer_stack = g_slist_remove (private->layer_stack, layer);
+ private->layer_stack = g_slist_prepend (private->layer_stack, layer);
+ }
+
+ g_signal_emit (image, gimp_image_signals[ACTIVE_LAYER_CHANGED], 0);
+
+ if (layer && gimp_image_get_active_channel (image))
+ gimp_image_set_active_channel (image, NULL);
+}
+
+static void
+gimp_image_active_channel_notify (GimpItemTree *tree,
+ const GParamSpec *pspec,
+ GimpImage *image)
+{
+ GimpChannel *channel = gimp_image_get_active_channel (image);
+
+ g_signal_emit (image, gimp_image_signals[ACTIVE_CHANNEL_CHANGED], 0);
+
+ if (channel && gimp_image_get_active_layer (image))
+ gimp_image_set_active_layer (image, NULL);
+}
+
+static void
+gimp_image_active_vectors_notify (GimpItemTree *tree,
+ const GParamSpec *pspec,
+ GimpImage *image)
+{
+ g_signal_emit (image, gimp_image_signals[ACTIVE_VECTORS_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);
+ }
+}
+
+
+/* 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)
+{
+ 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);
+
+ case GIMP_INDEXED:
+ if (precision == GIMP_PRECISION_U8_GAMMA)
+ {
+ 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_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);
+}
+
+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_GAMMA)
+ return gimp_image_get_format (image, GIMP_GRAY,
+ gimp_image_get_precision (image),
+ FALSE);
+
+ 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: 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: 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: 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 traditionnally 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 *layers;
+ 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 (image))
+ version = 1;
+
+ layers = gimp_image_get_layer_list (image);
+
+ for (list = layers; list; list = g_list_next (list))
+ {
+ GimpLayer *layer = GIMP_LAYER (list->data);
+
+ 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_ANTI_ERASE:
+ case GIMP_LAYER_MODE_SEPARATOR:
+ 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);
+ }
+ }
+ }
+
+ g_list_free (layers);
+
+ /* 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_GAMMA)
+ {
+ 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_precision (image) > GIMP_PRECISION_U8_GAMMA)
+ 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);
+ }
+
+#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;
+ }
+
+ 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 (unit > GIMP_UNIT_PIXEL);
+
+ 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), GIMP_UNIT_INCH);
+
+ 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);
+}
+
+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_active_channel (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_linked_items_changed (GimpImage *image)
+{
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+
+ g_signal_emit (image, gimp_image_signals[LINKED_ITEMS_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)
+{
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+ g_return_if_fail (color_index >= -1 &&
+ color_index < GIMP_IMAGE_GET_PRIVATE (image)->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++;
+}
+
+void
+gimp_image_dec_display_count (GimpImage *image)
+{
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+
+ 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,
+ gint *count)
+{
+ GimpImagePrivate *private;
+ 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_new (gchar *, *count);
+
+ 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_name (parasite);
+
+ if (strcmp (name, GIMP_ICC_PROFILE_PARASITE_NAME) == 0)
+ {
+ return gimp_image_validate_icc_parasite (image, parasite, NULL, error);
+ }
+ else if (strcmp (name, "gimp-comment") == 0)
+ {
+ const gchar *data = gimp_parasite_data (parasite);
+ gssize length = gimp_parasite_data_size (parasite);
+ gboolean valid = FALSE;
+
+ 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_name (parasite);
+
+ /* this is so ugly and is only for the PDB */
+ if (strcmp (name, GIMP_ICC_PROFILE_PARASITE_NAME) == 0)
+ {
+ GimpColorProfile *profile;
+ GimpColorProfile *builtin;
+
+ profile =
+ gimp_color_profile_new_from_icc_profile (gimp_parasite_data (parasite),
+ gimp_parasite_data_size (parasite),
+ 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 (&copy))
+ gimp_image_undo_push_image_parasite (image,
+ C_("undo-type", "Attach Parasite to Image"),
+ &copy);
+
+ /* 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, &copy);
+
+ if (push_undo && gimp_parasite_has_flag (&copy, GIMP_PARASITE_ATTACH_PARENT))
+ {
+ gimp_parasite_shift_parent (&copy);
+ gimp_parasite_attach (image->gimp, &copy);
+ }
+
+ if (strcmp (name, GIMP_ICC_PROFILE_PARASITE_NAME) == 0)
+ _gimp_image_update_color_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);
+
+ 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_vectors_by_tattoo (image, ltattoo))
+ retval = FALSE; /* Oopps duplicated tattoo in vectors */
+ }
+
+ g_list_free (all_items);
+
+ /* Now check that the channel and vectors 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_vectors_by_tattoo (image, ctattoo))
+ retval = FALSE; /* Oopps duplicated tattoo in vectors */
+ }
+
+ g_list_free (all_items);
+
+ /* Find the max tattoo value in the vectors */
+ all_items = gimp_image_get_vectors_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 / vectors */
+
+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_vectors_tree (GimpImage *image)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ return GIMP_IMAGE_GET_PRIVATE (image)->vectors;
+}
+
+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_vectors (GimpImage *image)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ return GIMP_IMAGE_GET_PRIVATE (image)->vectors->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_vectors (GimpImage *image)
+{
+ GimpItemStack *stack;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), 0);
+
+ stack = GIMP_ITEM_STACK (gimp_image_get_vectors (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_vectors_iter (GimpImage *image)
+{
+ GimpItemStack *stack;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ stack = GIMP_ITEM_STACK (gimp_image_get_vectors (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_vectors_list (GimpImage *image)
+{
+ GimpItemStack *stack;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ stack = GIMP_ITEM_STACK (gimp_image_get_vectors (image));
+
+ return gimp_item_stack_get_item_list (stack);
+}
+
+
+/* active drawable, layer, channel, vectors */
+
+GimpDrawable *
+gimp_image_get_active_drawable (GimpImage *image)
+{
+ GimpImagePrivate *private;
+ GimpItem *active_channel;
+ GimpItem *active_layer;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ active_channel = gimp_item_tree_get_active_item (private->channels);
+ active_layer = gimp_item_tree_get_active_item (private->layers);
+
+ /* If there is an active channel (a saved selection, etc.),
+ * we ignore the active layer
+ */
+ if (active_channel)
+ {
+ return GIMP_DRAWABLE (active_channel);
+ }
+ else if (active_layer)
+ {
+ GimpLayer *layer = GIMP_LAYER (active_layer);
+ GimpLayerMask *mask = gimp_layer_get_mask (layer);
+
+ if (mask && gimp_layer_get_edit_mask (layer))
+ return GIMP_DRAWABLE (mask);
+ else
+ return GIMP_DRAWABLE (layer);
+ }
+
+ return NULL;
+}
+
+GimpLayer *
+gimp_image_get_active_layer (GimpImage *image)
+{
+ GimpImagePrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ return GIMP_LAYER (gimp_item_tree_get_active_item (private->layers));
+}
+
+GimpChannel *
+gimp_image_get_active_channel (GimpImage *image)
+{
+ GimpImagePrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ return GIMP_CHANNEL (gimp_item_tree_get_active_item (private->channels));
+}
+
+GimpVectors *
+gimp_image_get_active_vectors (GimpImage *image)
+{
+ GimpImagePrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ return GIMP_VECTORS (gimp_item_tree_get_active_item (private->vectors));
+}
+
+GimpLayer *
+gimp_image_set_active_layer (GimpImage *image,
+ GimpLayer *layer)
+{
+ GimpImagePrivate *private;
+ GimpLayer *floating_sel;
+ GimpLayer *active_layer;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (layer == NULL || GIMP_IS_LAYER (layer), NULL);
+ g_return_val_if_fail (layer == NULL ||
+ (gimp_item_is_attached (GIMP_ITEM (layer)) &&
+ gimp_item_get_image (GIMP_ITEM (layer)) == image),
+ NULL);
+
+ 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 && layer != floating_sel)
+ return floating_sel;
+
+ active_layer = gimp_image_get_active_layer (image);
+
+ if (layer != active_layer)
+ {
+ /* Don't cache selection info for the previous active layer */
+ if (active_layer)
+ gimp_drawable_invalidate_boundary (GIMP_DRAWABLE (active_layer));
+
+ gimp_item_tree_set_active_item (private->layers, GIMP_ITEM (layer));
+ }
+
+ return gimp_image_get_active_layer (image);
+}
+
+GimpChannel *
+gimp_image_set_active_channel (GimpImage *image,
+ GimpChannel *channel)
+{
+ GimpImagePrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (channel == NULL || GIMP_IS_CHANNEL (channel), NULL);
+ g_return_val_if_fail (channel == NULL ||
+ (gimp_item_is_attached (GIMP_ITEM (channel)) &&
+ gimp_item_get_image (GIMP_ITEM (channel)) == image),
+ NULL);
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ /* Not if there is a floating selection */
+ if (channel && gimp_image_get_floating_selection (image))
+ return NULL;
+
+ if (channel != gimp_image_get_active_channel (image))
+ {
+ gimp_item_tree_set_active_item (private->channels, GIMP_ITEM (channel));
+ }
+
+ return gimp_image_get_active_channel (image);
+}
+
+GimpChannel *
+gimp_image_unset_active_channel (GimpImage *image)
+{
+ GimpImagePrivate *private;
+ GimpChannel *channel;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ channel = gimp_image_get_active_channel (image);
+
+ if (channel)
+ {
+ gimp_image_set_active_channel (image, NULL);
+
+ if (private->layer_stack)
+ gimp_image_set_active_layer (image, private->layer_stack->data);
+ }
+
+ return channel;
+}
+
+GimpVectors *
+gimp_image_set_active_vectors (GimpImage *image,
+ GimpVectors *vectors)
+{
+ GimpImagePrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (vectors == NULL || GIMP_IS_VECTORS (vectors), NULL);
+ g_return_val_if_fail (vectors == NULL ||
+ (gimp_item_is_attached (GIMP_ITEM (vectors)) &&
+ gimp_item_get_image (GIMP_ITEM (vectors)) == image),
+ NULL);
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ if (vectors != gimp_image_get_active_vectors (image))
+ {
+ gimp_item_tree_set_active_item (private->vectors, GIMP_ITEM (vectors));
+ }
+
+ return gimp_image_get_active_vectors (image);
+}
+
+
+/* layer, channel, vectors 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));
+}
+
+GimpVectors *
+gimp_image_get_vectors_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_vectors (image));
+
+ return GIMP_VECTORS (gimp_item_stack_get_item_by_tattoo (stack, tattoo));
+}
+
+
+/* layer, channel, vectors 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));
+}
+
+GimpVectors *
+gimp_image_get_vectors_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_vectors_tree (image);
+
+ return GIMP_VECTORS (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);
+}
+
+
+/* layers */
+
+gboolean
+gimp_image_add_layer (GimpImage *image,
+ GimpLayer *layer,
+ GimpLayer *parent,
+ gint position,
+ gboolean push_undo)
+{
+ GimpImagePrivate *private;
+ 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_active_layer (image));
+
+ gimp_item_tree_add_item (private->layers, GIMP_ITEM (layer),
+ GIMP_ITEM (parent), position);
+
+ gimp_image_set_active_layer (image, layer);
+
+ /* 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 (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,
+ GimpLayer *new_active)
+{
+ GimpImagePrivate *private;
+ GimpLayer *active_layer;
+ 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);
+ }
+
+ active_layer = gimp_image_get_active_layer (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_undo_push_layer_remove (image, undo_desc, layer,
+ gimp_layer_get_parent (layer),
+ gimp_item_get_index (GIMP_ITEM (layer)),
+ active_layer);
+
+ g_object_ref (layer);
+
+ /* Make sure we're not caching any old selection info */
+ if (layer == active_layer)
+ gimp_drawable_invalidate_boundary (GIMP_DRAWABLE (layer));
+
+ private->layer_stack = g_slist_remove (private->layer_stack, 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))
+ {
+ private->layer_stack = g_slist_remove (private->layer_stack,
+ list->data);
+ }
+
+ g_list_free (children);
+ }
+
+ new_active =
+ GIMP_LAYER (gimp_item_tree_remove_item (private->layers,
+ GIMP_ITEM (layer),
+ GIMP_ITEM (new_active)));
+
+ if (gimp_layer_is_floating_sel (layer))
+ {
+ /* If this was the floating selection, activate the underlying drawable
+ */
+ floating_sel_activate_drawable (layer);
+ }
+ else if (active_layer &&
+ (layer == active_layer ||
+ gimp_viewable_is_ancestor (GIMP_VIEWABLE (layer),
+ GIMP_VIEWABLE (active_layer))))
+ {
+ gimp_image_set_active_layer (image, new_active);
+ }
+
+ gimp_item_end_move (GIMP_ITEM (layer), push_undo);
+
+ g_object_unref (layer);
+
+ 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_active_layer (image, layers->data);
+
+ 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;
+
+ 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_active_channel (image));
+
+ gimp_item_tree_add_item (private->channels, GIMP_ITEM (channel),
+ GIMP_ITEM (parent), position);
+
+ gimp_image_set_active_channel (image, channel);
+
+ return TRUE;
+}
+
+void
+gimp_image_remove_channel (GimpImage *image,
+ GimpChannel *channel,
+ gboolean push_undo,
+ GimpChannel *new_active)
+{
+ GimpImagePrivate *private;
+ GimpChannel *active_channel;
+
+ 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);
+
+ active_channel = gimp_image_get_active_channel (image);
+
+ 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)),
+ active_channel);
+
+ g_object_ref (channel);
+
+ new_active =
+ GIMP_CHANNEL (gimp_item_tree_remove_item (private->channels,
+ GIMP_ITEM (channel),
+ GIMP_ITEM (new_active)));
+
+ if (active_channel &&
+ (channel == active_channel ||
+ gimp_viewable_is_ancestor (GIMP_VIEWABLE (channel),
+ GIMP_VIEWABLE (active_channel))))
+ {
+ if (new_active)
+ gimp_image_set_active_channel (image, new_active);
+ else
+ gimp_image_unset_active_channel (image);
+ }
+
+ gimp_item_end_move (GIMP_ITEM (channel), push_undo);
+
+ g_object_unref (channel);
+
+ if (push_undo)
+ gimp_image_undo_group_end (image);
+}
+
+
+/* vectors */
+
+gboolean
+gimp_image_add_vectors (GimpImage *image,
+ GimpVectors *vectors,
+ GimpVectors *parent,
+ gint position,
+ gboolean push_undo)
+{
+ GimpImagePrivate *private;
+
+ 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->vectors,
+ (GimpItem *) vectors,
+ (GimpItem **) &parent,
+ &position))
+ return FALSE;
+
+ if (push_undo)
+ gimp_image_undo_push_vectors_add (image, C_("undo-type", "Add Path"),
+ vectors,
+ gimp_image_get_active_vectors (image));
+
+ gimp_item_tree_add_item (private->vectors, GIMP_ITEM (vectors),
+ GIMP_ITEM (parent), position);
+
+ gimp_image_set_active_vectors (image, vectors);
+
+ return TRUE;
+}
+
+void
+gimp_image_remove_vectors (GimpImage *image,
+ GimpVectors *vectors,
+ gboolean push_undo,
+ GimpVectors *new_active)
+{
+ GimpImagePrivate *private;
+ GimpVectors *active_vectors;
+
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+ g_return_if_fail (GIMP_IS_VECTORS (vectors));
+ g_return_if_fail (gimp_item_is_attached (GIMP_ITEM (vectors)));
+ g_return_if_fail (gimp_item_get_image (GIMP_ITEM (vectors)) == 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 (vectors), push_undo);
+
+ active_vectors = gimp_image_get_active_vectors (image);
+
+ if (push_undo)
+ gimp_image_undo_push_vectors_remove (image, C_("undo-type", "Remove Path"), vectors,
+ gimp_vectors_get_parent (vectors),
+ gimp_item_get_index (GIMP_ITEM (vectors)),
+ active_vectors);
+
+ g_object_ref (vectors);
+
+ new_active =
+ GIMP_VECTORS (gimp_item_tree_remove_item (private->vectors,
+ GIMP_ITEM (vectors),
+ GIMP_ITEM (new_active)));
+
+ if (active_vectors &&
+ (vectors == active_vectors ||
+ gimp_viewable_is_ancestor (GIMP_VIEWABLE (vectors),
+ GIMP_VIEWABLE (active_vectors))))
+ {
+ gimp_image_set_active_vectors (image, new_active);
+ }
+
+ gimp_item_end_move (GIMP_ITEM (vectors), push_undo);
+
+ g_object_unref (vectors);
+
+ if (push_undo)
+ gimp_image_undo_group_end (image);
+}
+
+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
+ {
+ GimpDrawable *drawable = gimp_image_get_active_drawable (image);
+
+ if (drawable)
+ {
+ GimpItem *item = GIMP_ITEM (drawable);
+ 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;
+ }
+ }
+
+ 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);
+}