diff options
Diffstat (limited to '')
-rw-r--r-- | app/core/gimpimage.c | 5191 |
1 files changed, 5191 insertions, 0 deletions
diff --git a/app/core/gimpimage.c b/app/core/gimpimage.c new file mode 100644 index 0000000..9908d1b --- /dev/null +++ b/app/core/gimpimage.c @@ -0,0 +1,5191 @@ +/* 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, + PROP_CONVERTING, +}; + + +/* local function prototypes */ + +static void gimp_color_managed_iface_init (GimpColorManagedInterface *iface); +static void gimp_projectable_iface_init (GimpProjectableInterface *iface); +static void gimp_pickable_iface_init (GimpPickableInterface *iface); + +static void gimp_image_constructed (GObject *object); +static void gimp_image_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_image_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); +static void gimp_image_dispose (GObject *object); +static void gimp_image_finalize (GObject *object); + +static void gimp_image_name_changed (GimpObject *object); +static gint64 gimp_image_get_memsize (GimpObject *object, + gint64 *gui_size); + +static gboolean gimp_image_get_size (GimpViewable *viewable, + gint *width, + gint *height); +static void gimp_image_size_changed (GimpViewable *viewable); +static gchar * gimp_image_get_description (GimpViewable *viewable, + gchar **tooltip); + +static void gimp_image_real_mode_changed (GimpImage *image); +static void gimp_image_real_precision_changed(GimpImage *image); +static void gimp_image_real_resolution_changed(GimpImage *image); +static void gimp_image_real_size_changed_detailed + (GimpImage *image, + gint previous_origin_x, + gint previous_origin_y, + gint previous_width, + gint previous_height); +static void gimp_image_real_unit_changed (GimpImage *image); +static void gimp_image_real_colormap_changed (GimpImage *image, + gint color_index); + +static const guint8 * + gimp_image_color_managed_get_icc_profile (GimpColorManaged *managed, + gsize *len); +static GimpColorProfile * + gimp_image_color_managed_get_color_profile (GimpColorManaged *managed); +static void + gimp_image_color_managed_profile_changed (GimpColorManaged *managed); + +static 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)); + + g_object_class_install_property (object_class, PROP_CONVERTING, + g_param_spec_boolean ("converting", + NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE)); +} + +static void +gimp_color_managed_iface_init (GimpColorManagedInterface *iface) +{ + iface->get_icc_profile = gimp_image_color_managed_get_icc_profile; + iface->get_color_profile = gimp_image_color_managed_get_color_profile; + iface->profile_changed = gimp_image_color_managed_profile_changed; +} + +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_CONVERTING: + private->converting = g_value_get_boolean (value); + break; + + case PROP_ID: + case PROP_METADATA: + case PROP_BUFFER: + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_image_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpImage *image = GIMP_IMAGE (object); + GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image); + + switch (property_id) + { + case PROP_GIMP: + g_value_set_object (value, image->gimp); + break; + case PROP_ID: + g_value_set_int (value, private->ID); + break; + case PROP_WIDTH: + g_value_set_int (value, private->width); + break; + case PROP_HEIGHT: + g_value_set_int (value, private->height); + break; + case PROP_BASE_TYPE: + g_value_set_enum (value, private->base_type); + break; + case PROP_PRECISION: + g_value_set_enum (value, private->precision); + break; + case PROP_METADATA: + g_value_set_object (value, gimp_image_get_metadata (image)); + break; + case PROP_BUFFER: + g_value_set_object (value, gimp_image_get_buffer (GIMP_PICKABLE (image))); + break; + case PROP_SYMMETRY: + g_value_set_gtype (value, + private->active_symmetry ? + G_TYPE_FROM_INSTANCE (private->active_symmetry) : + G_TYPE_NONE); + break; + case PROP_CONVERTING: + g_value_set_boolean (value, private->converting); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_image_dispose (GObject *object) +{ + GimpImage *image = GIMP_IMAGE (object); + GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image); + + if (private->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 (©)) + gimp_image_undo_push_image_parasite (image, + C_("undo-type", "Attach Parasite to Image"), + ©); + + /* We used to push a cantundo on the stack here. This made the undo stack + * unusable (NULL on the stack) and prevented people from undoing after a + * save (since most save plug-ins attach an undoable comment parasite). + * Now we simply attach the parasite without pushing an undo. That way + * it's undoable but does not block the undo system. --Sven + */ + gimp_parasite_list_add (private->parasites, ©); + + if (push_undo && gimp_parasite_has_flag (©, GIMP_PARASITE_ATTACH_PARENT)) + { + gimp_parasite_shift_parent (©); + gimp_parasite_attach (image->gimp, ©); + } + + if (strcmp (name, GIMP_ICC_PROFILE_PARASITE_NAME) == 0) + _gimp_image_update_color_profile (image, parasite); + + 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); +} + +/* Sets the image into a "converting" state, which is there to warn other code + * (such as shell render code) that the image properties might be in an + * inconsistent state. For instance when converting to another precision with + * gimp_image_convert_precision(), the babl format may be updated first, and the + * profile later, after all drawables are converted. Rendering the image + * in-between would at best render broken previews (at worst, crash, e.g. + * because we depend on allocated data which might have become too small). + */ +void +gimp_image_set_converting (GimpImage *image, + gboolean converting) +{ + g_return_if_fail (GIMP_IS_IMAGE (image)); + + g_object_set (image, + "converting", converting, + NULL); +} + +gboolean +gimp_image_get_converting (GimpImage *image) +{ + GimpImagePrivate *private; + g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE); + + private = GIMP_IMAGE_GET_PRIVATE (image); + + return private->converting; +} |