/* PIKA - Photo and Image Kooker Application * a rebranding of The GNU Image Manipulation Program (created with heckimp) * A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio * * Original copyright, applying to most contents (license remains unchanged): * 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 . */ #include "config.h" #include #include #include #include #include #include #include "libpikacolor/pikacolor.h" #include "libpikamath/pikamath.h" #include "libpikabase/pikabase.h" #include "libpikaconfig/pikaconfig.h" #include "core-types.h" #include "config/pikacoreconfig.h" #include "operations/layer-modes/pika-layer-modes.h" #include "gegl/pika-babl.h" #include "gegl/pika-gegl-loops.h" #include "pika.h" #include "pika-memsize.h" #include "pika-parasites.h" #include "pika-utils.h" #include "pikacontext.h" #include "pikadrawable-floating-selection.h" #include "pikadrawablestack.h" #include "pikagrid.h" #include "pikaerror.h" #include "pikaguide.h" #include "pikaidtable.h" #include "pikaimage.h" #include "pikaimage-color-profile.h" #include "pikaimage-colormap.h" #include "pikaimage-guides.h" #include "pikaimage-item-list.h" #include "pikaimage-metadata.h" #include "pikaimage-sample-points.h" #include "pikaimage-preview.h" #include "pikaimage-private.h" #include "pikaimage-quick-mask.h" #include "pikaimage-symmetry.h" #include "pikaimage-undo.h" #include "pikaimage-undo-push.h" #include "pikaitemlist.h" #include "pikaitemtree.h" #include "pikalayer.h" #include "pikalayer-floating-selection.h" #include "pikalayermask.h" #include "pikalayerstack.h" #include "pikamarshal.h" #include "pikapalette.h" #include "pikaparasitelist.h" #include "pikapickable.h" #include "pikaprojectable.h" #include "pikaprojection.h" #include "pikasamplepoint.h" #include "pikaselection.h" #include "pikasymmetry.h" #include "pikatempbuf.h" #include "pikatemplate.h" #include "pikaundostack.h" #include "text/pikatextlayer.h" #include "vectors/pikavectors.h" #include "pika-log.h" #include "pika-intl.h" #ifdef DEBUG #define TRC(x) g_printerr x #else #define TRC(x) #endif enum { MODE_CHANGED, PRECISION_CHANGED, ALPHA_CHANGED, FLOATING_SELECTION_CHANGED, SELECTED_CHANNELS_CHANGED, SELECTED_VECTORS_CHANGED, SELECTED_LAYERS_CHANGED, COMPONENT_VISIBILITY_CHANGED, COMPONENT_ACTIVE_CHANGED, MASK_CHANGED, RESOLUTION_CHANGED, SIZE_CHANGED_DETAILED, UNIT_CHANGED, QUICK_MASK_CHANGED, SELECTION_INVALIDATE, CLEAN, DIRTY, SAVING, SAVED, EXPORTED, GUIDE_ADDED, GUIDE_REMOVED, GUIDE_MOVED, SAMPLE_POINT_ADDED, SAMPLE_POINT_REMOVED, SAMPLE_POINT_MOVED, PARASITE_ATTACHED, PARASITE_DETACHED, COLORMAP_CHANGED, UNDO_EVENT, LAYER_SETS_CHANGED, CHANNEL_SETS_CHANGED, VECTORS_SETS_CHANGED, LAST_SIGNAL }; enum { PROP_0, PROP_PIKA, PROP_ID, PROP_WIDTH, PROP_HEIGHT, PROP_BASE_TYPE, PROP_PRECISION, PROP_METADATA, PROP_BUFFER, PROP_SYMMETRY, PROP_CONVERTING, }; /* local function prototypes */ static void pika_color_managed_iface_init (PikaColorManagedInterface *iface); static void pika_projectable_iface_init (PikaProjectableInterface *iface); static void pika_pickable_iface_init (PikaPickableInterface *iface); static void pika_image_constructed (GObject *object); static void pika_image_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec); static void pika_image_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec); static void pika_image_dispose (GObject *object); static void pika_image_finalize (GObject *object); static void pika_image_name_changed (PikaObject *object); static gint64 pika_image_get_memsize (PikaObject *object, gint64 *gui_size); static gboolean pika_image_get_size (PikaViewable *viewable, gint *width, gint *height); static void pika_image_size_changed (PikaViewable *viewable); static gchar * pika_image_get_description (PikaViewable *viewable, gchar **tooltip); static void pika_image_real_mode_changed (PikaImage *image); static void pika_image_real_precision_changed(PikaImage *image); static void pika_image_real_resolution_changed(PikaImage *image); static void pika_image_real_size_changed_detailed (PikaImage *image, gint previous_origin_x, gint previous_origin_y, gint previous_width, gint previous_height); static void pika_image_real_unit_changed (PikaImage *image); static void pika_image_real_colormap_changed (PikaImage *image, gint color_index); static const guint8 * pika_image_color_managed_get_icc_profile (PikaColorManaged *managed, gsize *len); static PikaColorProfile * pika_image_color_managed_get_color_profile (PikaColorManaged *managed); static void pika_image_color_managed_profile_changed (PikaColorManaged *managed); static PikaColorProfile * pika_image_color_managed_get_simulation_profile (PikaColorManaged *managed); static void pika_image_color_managed_simulation_profile_changed (PikaColorManaged *managed); static PikaColorRenderingIntent pika_image_color_managed_get_simulation_intent (PikaColorManaged *managed); static void pika_image_color_managed_simulation_intent_changed (PikaColorManaged *managed); static gboolean pika_image_color_managed_get_simulation_bpc (PikaColorManaged *managed); static void pika_image_color_managed_simulation_bpc_changed (PikaColorManaged *managed); static void pika_image_projectable_flush (PikaProjectable *projectable, gboolean invalidate_preview); static GeglRectangle pika_image_get_bounding_box (PikaProjectable *projectable); static GeglNode * pika_image_get_graph (PikaProjectable *projectable); static PikaImage * pika_image_get_image (PikaProjectable *projectable); static const Babl * pika_image_get_proj_format (PikaProjectable *projectable); static void pika_image_pickable_flush (PikaPickable *pickable); static GeglBuffer * pika_image_get_buffer (PikaPickable *pickable); static gboolean pika_image_get_pixel_at (PikaPickable *pickable, gint x, gint y, const Babl *format, gpointer pixel); static gdouble pika_image_get_opacity_at (PikaPickable *pickable, gint x, gint y); static void pika_image_get_pixel_average (PikaPickable *pickable, const GeglRectangle *rect, const Babl *format, gpointer pixel); static void pika_image_pixel_to_rgb (PikaPickable *pickable, const Babl *format, gpointer pixel, PikaRGB *color); static void pika_image_rgb_to_pixel (PikaPickable *pickable, const PikaRGB *color, const Babl *format, gpointer pixel); static void pika_image_projection_buffer_notify (PikaProjection *projection, const GParamSpec *pspec, PikaImage *image); static void pika_image_mask_update (PikaDrawable *drawable, gint x, gint y, gint width, gint height, PikaImage *image); static void pika_image_layers_changed (PikaContainer *container, PikaChannel *channel, PikaImage *image); static void pika_image_layer_offset_changed (PikaDrawable *drawable, const GParamSpec *pspec, PikaImage *image); static void pika_image_layer_bounding_box_changed (PikaDrawable *drawable, PikaImage *image); static void pika_image_layer_alpha_changed (PikaDrawable *drawable, PikaImage *image); static void pika_image_channel_add (PikaContainer *container, PikaChannel *channel, PikaImage *image); static void pika_image_channel_remove (PikaContainer *container, PikaChannel *channel, PikaImage *image); static void pika_image_channel_name_changed (PikaChannel *channel, PikaImage *image); static void pika_image_channel_color_changed (PikaChannel *channel, PikaImage *image); static void pika_image_selected_layers_notify (PikaItemTree *tree, const GParamSpec *pspec, PikaImage *image); static void pika_image_selected_channels_notify (PikaItemTree *tree, const GParamSpec *pspec, PikaImage *image); static void pika_image_selected_vectors_notify (PikaItemTree *tree, const GParamSpec *pspec, PikaImage *image); static void pika_image_freeze_bounding_box (PikaImage *image); static void pika_image_thaw_bounding_box (PikaImage *image); static void pika_image_update_bounding_box (PikaImage *image); static gint pika_image_layer_stack_cmp (GList *layers1, GList *layers2); static void pika_image_rec_remove_layer_stack_dups (PikaImage *image, GSList *start); static void pika_image_clean_layer_stack (PikaImage *image); static void pika_image_remove_from_layer_stack (PikaImage *image, PikaLayer *layer); static gint pika_image_selected_is_descendant (PikaViewable *selected, PikaViewable *viewable); G_DEFINE_TYPE_WITH_CODE (PikaImage, pika_image, PIKA_TYPE_VIEWABLE, G_ADD_PRIVATE (PikaImage) G_IMPLEMENT_INTERFACE (PIKA_TYPE_COLOR_MANAGED, pika_color_managed_iface_init) G_IMPLEMENT_INTERFACE (PIKA_TYPE_PROJECTABLE, pika_projectable_iface_init) G_IMPLEMENT_INTERFACE (PIKA_TYPE_PICKABLE, pika_pickable_iface_init)) #define parent_class pika_image_parent_class static guint pika_image_signals[LAST_SIGNAL] = { 0 }; static void pika_image_class_init (PikaImageClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); PikaObjectClass *pika_object_class = PIKA_OBJECT_CLASS (klass); PikaViewableClass *viewable_class = PIKA_VIEWABLE_CLASS (klass); pika_image_signals[MODE_CHANGED] = g_signal_new ("mode-changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (PikaImageClass, mode_changed), NULL, NULL, NULL, G_TYPE_NONE, 0); pika_image_signals[PRECISION_CHANGED] = g_signal_new ("precision-changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (PikaImageClass, precision_changed), NULL, NULL, NULL, G_TYPE_NONE, 0); pika_image_signals[ALPHA_CHANGED] = g_signal_new ("alpha-changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (PikaImageClass, alpha_changed), NULL, NULL, NULL, G_TYPE_NONE, 0); pika_image_signals[FLOATING_SELECTION_CHANGED] = g_signal_new ("floating-selection-changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (PikaImageClass, floating_selection_changed), NULL, NULL, NULL, G_TYPE_NONE, 0); pika_image_signals[SELECTED_LAYERS_CHANGED] = g_signal_new ("selected-layers-changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (PikaImageClass, selected_layers_changed), NULL, NULL, NULL, G_TYPE_NONE, 0); pika_image_signals[SELECTED_CHANNELS_CHANGED] = g_signal_new ("selected-channels-changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (PikaImageClass, selected_channels_changed), NULL, NULL, NULL, G_TYPE_NONE, 0); pika_image_signals[SELECTED_VECTORS_CHANGED] = g_signal_new ("selected-vectors-changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (PikaImageClass, selected_vectors_changed), NULL, NULL, NULL, G_TYPE_NONE, 0); pika_image_signals[COMPONENT_VISIBILITY_CHANGED] = g_signal_new ("component-visibility-changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (PikaImageClass, component_visibility_changed), NULL, NULL, NULL, G_TYPE_NONE, 1, PIKA_TYPE_CHANNEL_TYPE); pika_image_signals[COMPONENT_ACTIVE_CHANGED] = g_signal_new ("component-active-changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (PikaImageClass, component_active_changed), NULL, NULL, NULL, G_TYPE_NONE, 1, PIKA_TYPE_CHANNEL_TYPE); pika_image_signals[MASK_CHANGED] = g_signal_new ("mask-changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (PikaImageClass, mask_changed), NULL, NULL, NULL, G_TYPE_NONE, 0); pika_image_signals[RESOLUTION_CHANGED] = g_signal_new ("resolution-changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (PikaImageClass, resolution_changed), NULL, NULL, NULL, G_TYPE_NONE, 0); pika_image_signals[SIZE_CHANGED_DETAILED] = g_signal_new ("size-changed-detailed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (PikaImageClass, size_changed_detailed), NULL, NULL, pika_marshal_VOID__INT_INT_INT_INT, G_TYPE_NONE, 4, G_TYPE_INT, G_TYPE_INT, G_TYPE_INT, G_TYPE_INT); pika_image_signals[UNIT_CHANGED] = g_signal_new ("unit-changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (PikaImageClass, unit_changed), NULL, NULL, NULL, G_TYPE_NONE, 0); pika_image_signals[QUICK_MASK_CHANGED] = g_signal_new ("quick-mask-changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (PikaImageClass, quick_mask_changed), NULL, NULL, NULL, G_TYPE_NONE, 0); pika_image_signals[SELECTION_INVALIDATE] = g_signal_new ("selection-invalidate", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (PikaImageClass, selection_invalidate), NULL, NULL, NULL, G_TYPE_NONE, 0); pika_image_signals[CLEAN] = g_signal_new ("clean", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (PikaImageClass, clean), NULL, NULL, NULL, G_TYPE_NONE, 1, PIKA_TYPE_DIRTY_MASK); pika_image_signals[DIRTY] = g_signal_new ("dirty", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (PikaImageClass, dirty), NULL, NULL, NULL, G_TYPE_NONE, 1, PIKA_TYPE_DIRTY_MASK); pika_image_signals[SAVING] = g_signal_new ("saving", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (PikaImageClass, saving), NULL, NULL, NULL, G_TYPE_NONE, 0); pika_image_signals[SAVED] = g_signal_new ("saved", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (PikaImageClass, saved), NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_FILE); pika_image_signals[EXPORTED] = g_signal_new ("exported", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (PikaImageClass, exported), NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_FILE); pika_image_signals[GUIDE_ADDED] = g_signal_new ("guide-added", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (PikaImageClass, guide_added), NULL, NULL, NULL, G_TYPE_NONE, 1, PIKA_TYPE_GUIDE); pika_image_signals[GUIDE_REMOVED] = g_signal_new ("guide-removed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (PikaImageClass, guide_removed), NULL, NULL, NULL, G_TYPE_NONE, 1, PIKA_TYPE_GUIDE); pika_image_signals[GUIDE_MOVED] = g_signal_new ("guide-moved", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (PikaImageClass, guide_moved), NULL, NULL, NULL, G_TYPE_NONE, 1, PIKA_TYPE_GUIDE); pika_image_signals[SAMPLE_POINT_ADDED] = g_signal_new ("sample-point-added", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (PikaImageClass, sample_point_added), NULL, NULL, NULL, G_TYPE_NONE, 1, PIKA_TYPE_SAMPLE_POINT); pika_image_signals[SAMPLE_POINT_REMOVED] = g_signal_new ("sample-point-removed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (PikaImageClass, sample_point_removed), NULL, NULL, NULL, G_TYPE_NONE, 1, PIKA_TYPE_SAMPLE_POINT); pika_image_signals[SAMPLE_POINT_MOVED] = g_signal_new ("sample-point-moved", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (PikaImageClass, sample_point_moved), NULL, NULL, NULL, G_TYPE_NONE, 1, PIKA_TYPE_SAMPLE_POINT); pika_image_signals[PARASITE_ATTACHED] = g_signal_new ("parasite-attached", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (PikaImageClass, parasite_attached), NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_STRING); pika_image_signals[PARASITE_DETACHED] = g_signal_new ("parasite-detached", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (PikaImageClass, parasite_detached), NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_STRING); pika_image_signals[COLORMAP_CHANGED] = g_signal_new ("colormap-changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (PikaImageClass, colormap_changed), NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_INT); pika_image_signals[UNDO_EVENT] = g_signal_new ("undo-event", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (PikaImageClass, undo_event), NULL, NULL, pika_marshal_VOID__ENUM_OBJECT, G_TYPE_NONE, 2, PIKA_TYPE_UNDO_EVENT, PIKA_TYPE_UNDO); pika_image_signals[LAYER_SETS_CHANGED] = g_signal_new ("layer-sets-changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (PikaImageClass, layer_sets_changed), NULL, NULL, NULL, G_TYPE_NONE, 0); pika_image_signals[CHANNEL_SETS_CHANGED] = g_signal_new ("channel-sets-changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (PikaImageClass, channel_sets_changed), NULL, NULL, NULL, G_TYPE_NONE, 0); pika_image_signals[VECTORS_SETS_CHANGED] = g_signal_new ("vectors-sets-changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (PikaImageClass, vectors_sets_changed), NULL, NULL, NULL, G_TYPE_NONE, 0); object_class->constructed = pika_image_constructed; object_class->set_property = pika_image_set_property; object_class->get_property = pika_image_get_property; object_class->dispose = pika_image_dispose; object_class->finalize = pika_image_finalize; pika_object_class->name_changed = pika_image_name_changed; pika_object_class->get_memsize = pika_image_get_memsize; viewable_class->default_icon_name = "pika-image"; viewable_class->get_size = pika_image_get_size; viewable_class->size_changed = pika_image_size_changed; viewable_class->get_preview_size = pika_image_get_preview_size; viewable_class->get_popup_size = pika_image_get_popup_size; viewable_class->get_new_preview = pika_image_get_new_preview; viewable_class->get_new_pixbuf = pika_image_get_new_pixbuf; viewable_class->get_description = pika_image_get_description; klass->mode_changed = pika_image_real_mode_changed; klass->precision_changed = pika_image_real_precision_changed; klass->alpha_changed = NULL; klass->floating_selection_changed = NULL; klass->selected_layers_changed = NULL; klass->selected_channels_changed = NULL; klass->selected_vectors_changed = NULL; klass->component_visibility_changed = NULL; klass->component_active_changed = NULL; klass->mask_changed = NULL; klass->resolution_changed = pika_image_real_resolution_changed; klass->size_changed_detailed = pika_image_real_size_changed_detailed; klass->unit_changed = pika_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 = pika_image_real_colormap_changed; klass->undo_event = NULL; g_object_class_install_property (object_class, PROP_PIKA, g_param_spec_object ("pika", NULL, NULL, PIKA_TYPE_PIKA, PIKA_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, PIKA_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_WIDTH, g_param_spec_int ("width", NULL, NULL, 1, PIKA_MAX_IMAGE_SIZE, 1, PIKA_PARAM_READWRITE | G_PARAM_CONSTRUCT)); g_object_class_install_property (object_class, PROP_HEIGHT, g_param_spec_int ("height", NULL, NULL, 1, PIKA_MAX_IMAGE_SIZE, 1, PIKA_PARAM_READWRITE | G_PARAM_CONSTRUCT)); g_object_class_install_property (object_class, PROP_BASE_TYPE, g_param_spec_enum ("base-type", NULL, NULL, PIKA_TYPE_IMAGE_BASE_TYPE, PIKA_RGB, PIKA_PARAM_READWRITE | G_PARAM_CONSTRUCT)); g_object_class_install_property (object_class, PROP_PRECISION, g_param_spec_enum ("precision", NULL, NULL, PIKA_TYPE_PRECISION, PIKA_PRECISION_U8_NON_LINEAR, PIKA_PARAM_READWRITE | G_PARAM_CONSTRUCT)); g_object_class_install_property (object_class, PROP_METADATA, g_param_spec_object ("metadata", NULL, NULL, GEXIV2_TYPE_METADATA, PIKA_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"), PIKA_TYPE_SYMMETRY, PIKA_PARAM_READWRITE | G_PARAM_CONSTRUCT)); g_object_class_install_property (object_class, PROP_CONVERTING, g_param_spec_boolean ("converting", NULL, NULL, FALSE, PIKA_PARAM_READWRITE)); } static void pika_color_managed_iface_init (PikaColorManagedInterface *iface) { iface->get_icc_profile = pika_image_color_managed_get_icc_profile; iface->get_color_profile = pika_image_color_managed_get_color_profile; iface->profile_changed = pika_image_color_managed_profile_changed; iface->get_simulation_profile = pika_image_color_managed_get_simulation_profile; iface->simulation_profile_changed = pika_image_color_managed_simulation_profile_changed; iface->get_simulation_intent = pika_image_color_managed_get_simulation_intent; iface->simulation_intent_changed = pika_image_color_managed_simulation_intent_changed; iface->get_simulation_bpc = pika_image_color_managed_get_simulation_bpc; iface->simulation_bpc_changed = pika_image_color_managed_simulation_bpc_changed; } static void pika_projectable_iface_init (PikaProjectableInterface *iface) { iface->flush = pika_image_projectable_flush; iface->get_image = pika_image_get_image; iface->get_format = pika_image_get_proj_format; iface->get_bounding_box = pika_image_get_bounding_box; iface->get_graph = pika_image_get_graph; iface->invalidate_preview = (void (*) (PikaProjectable*)) pika_viewable_invalidate_preview; } static void pika_pickable_iface_init (PikaPickableInterface *iface) { iface->flush = pika_image_pickable_flush; iface->get_image = (PikaImage * (*) (PikaPickable *pickable)) pika_image_get_image; iface->get_format = (const Babl * (*) (PikaPickable *pickable)) pika_image_get_proj_format; iface->get_format_with_alpha = (const Babl * (*) (PikaPickable *pickable)) pika_image_get_proj_format; iface->get_buffer = pika_image_get_buffer; iface->get_pixel_at = pika_image_get_pixel_at; iface->get_opacity_at = pika_image_get_opacity_at; iface->get_pixel_average = pika_image_get_pixel_average; iface->pixel_to_rgb = pika_image_pixel_to_rgb; iface->rgb_to_pixel = pika_image_rgb_to_pixel; } static void pika_image_init (PikaImage *image) { PikaImagePrivate *private = pika_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 = PIKA_UNIT_INCH; private->base_type = PIKA_RGB; private->precision = PIKA_PRECISION_U8_NON_LINEAR; private->new_layer_mode = -1; private->show_all = 0; private->bounding_box.x = 0; private->bounding_box.y = 0; private->bounding_box.width = 0; private->bounding_box.height = 0; private->pickable_buffer = NULL; private->palette = NULL; private->metadata = NULL; private->simulation_profile = NULL; private->simulation_intent = PIKA_COLOR_RENDERING_INTENT_RELATIVE_COLORIMETRIC; private->simulation_bpc = FALSE; private->dirty = 1; private->dirty_time = 0; private->undo_freeze_count = 0; private->export_dirty = 1; private->instance_count = 0; private->disp_count = 0; private->tattoo_state = 0; private->projection = pika_projection_new (PIKA_PROJECTABLE (image)); private->symmetries = NULL; private->active_symmetry = NULL; private->guides = NULL; private->grid = NULL; private->sample_points = NULL; private->layers = pika_item_tree_new (image, PIKA_TYPE_LAYER_STACK, PIKA_TYPE_LAYER); private->channels = pika_item_tree_new (image, PIKA_TYPE_DRAWABLE_STACK, PIKA_TYPE_CHANNEL); private->vectors = pika_item_tree_new (image, PIKA_TYPE_ITEM_STACK, PIKA_TYPE_VECTORS); private->layer_stack = NULL; private->stored_layer_sets = NULL; private->stored_channel_sets = NULL; private->stored_vectors_sets = NULL; g_signal_connect (private->projection, "notify::buffer", G_CALLBACK (pika_image_projection_buffer_notify), image); g_signal_connect (private->layers, "notify::selected-items", G_CALLBACK (pika_image_selected_layers_notify), image); g_signal_connect (private->channels, "notify::selected-items", G_CALLBACK (pika_image_selected_channels_notify), image); g_signal_connect (private->vectors, "notify::selected-items", G_CALLBACK (pika_image_selected_vectors_notify), image); g_signal_connect_swapped (private->layers->container, "update", G_CALLBACK (pika_image_invalidate), image); private->layer_offset_x_handler = pika_container_add_handler (private->layers->container, "notify::offset-x", G_CALLBACK (pika_image_layer_offset_changed), image); private->layer_offset_y_handler = pika_container_add_handler (private->layers->container, "notify::offset-y", G_CALLBACK (pika_image_layer_offset_changed), image); private->layer_bounding_box_handler = pika_container_add_handler (private->layers->container, "bounding-box-changed", G_CALLBACK (pika_image_layer_bounding_box_changed), image); private->layer_alpha_handler = pika_container_add_handler (private->layers->container, "alpha-changed", G_CALLBACK (pika_image_layer_alpha_changed), image); g_signal_connect (private->layers->container, "add", G_CALLBACK (pika_image_layers_changed), image); g_signal_connect (private->layers->container, "remove", G_CALLBACK (pika_image_layers_changed), image); g_signal_connect_swapped (private->channels->container, "update", G_CALLBACK (pika_image_invalidate), image); private->channel_name_changed_handler = pika_container_add_handler (private->channels->container, "name-changed", G_CALLBACK (pika_image_channel_name_changed), image); private->channel_color_changed_handler = pika_container_add_handler (private->channels->container, "color-changed", G_CALLBACK (pika_image_channel_color_changed), image); g_signal_connect (private->channels->container, "add", G_CALLBACK (pika_image_channel_add), image); g_signal_connect (private->channels->container, "remove", G_CALLBACK (pika_image_channel_remove), image); private->floating_sel = NULL; private->selection_mask = NULL; private->parasites = pika_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; pika_rgba_set (&private->quick_mask_color, 1.0, 0.0, 0.0, 0.5); private->undo_stack = pika_undo_stack_new (image); private->redo_stack = pika_undo_stack_new (image); private->group_count = 0; private->pushing_undo_group = PIKA_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 pika_image_constructed (GObject *object) { PikaImage *image = PIKA_IMAGE (object); PikaImagePrivate *private = PIKA_IMAGE_GET_PRIVATE (image); PikaChannel *selection; PikaCoreConfig *config; PikaTemplate *template; G_OBJECT_CLASS (parent_class)->constructed (object); pika_assert (PIKA_IS_PIKA (image->pika)); config = image->pika->config; private->ID = pika_id_table_insert (image->pika->image_table, image); template = config->default_image; private->xresolution = pika_template_get_resolution_x (template); private->yresolution = pika_template_get_resolution_y (template); private->resolution_unit = pika_template_get_resolution_unit (template); private->grid = pika_config_duplicate (PIKA_CONFIG (config->default_grid)); private->quick_mask_color = config->quick_mask_color; pika_image_update_bounding_box (image); if (private->base_type == PIKA_INDEXED) pika_image_colormap_init (image); selection = pika_selection_new (image, pika_image_get_width (image), pika_image_get_height (image)); pika_image_take_mask (image, selection); g_signal_connect_object (config, "notify::transparency-type", G_CALLBACK (pika_item_stack_invalidate_previews), private->layers->container, G_CONNECT_SWAPPED); g_signal_connect_object (config, "notify::transparency-size", G_CALLBACK (pika_item_stack_invalidate_previews), private->layers->container, G_CONNECT_SWAPPED); g_signal_connect_object (config, "notify::transparency-custom-color1", G_CALLBACK (pika_item_stack_invalidate_previews), private->layers->container, G_CONNECT_SWAPPED); g_signal_connect_object (config, "notify::transparency-custom-color2", G_CALLBACK (pika_item_stack_invalidate_previews), private->layers->container, G_CONNECT_SWAPPED); g_signal_connect_object (config, "notify::layer-previews", G_CALLBACK (pika_viewable_size_changed), image, G_CONNECT_SWAPPED); g_signal_connect_object (config, "notify::group-layer-previews", G_CALLBACK (pika_viewable_size_changed), image, G_CONNECT_SWAPPED); pika_container_add (image->pika->images, PIKA_OBJECT (image)); } static void pika_image_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { PikaImage *image = PIKA_IMAGE (object); PikaImagePrivate *private = PIKA_IMAGE_GET_PRIVATE (image); switch (property_id) { case PROP_PIKA: image->pika = 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); _pika_image_free_color_transforms (image); break; case PROP_PRECISION: private->precision = g_value_get_enum (value); _pika_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)) { PikaSymmetry *sym = iter->data; if (type == G_TYPE_FROM_INSTANCE (sym)) private->active_symmetry = iter->data; } if (! private->active_symmetry && g_type_is_a (type, PIKA_TYPE_SYMMETRY)) { PikaSymmetry *sym = pika_image_symmetry_new (image, type); pika_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 pika_image_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { PikaImage *image = PIKA_IMAGE (object); PikaImagePrivate *private = PIKA_IMAGE_GET_PRIVATE (image); switch (property_id) { case PROP_PIKA: g_value_set_object (value, image->pika); 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, pika_image_get_metadata (image)); break; case PROP_BUFFER: g_value_set_object (value, pika_image_get_buffer (PIKA_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 pika_image_dispose (GObject *object) { PikaImage *image = PIKA_IMAGE (object); PikaImagePrivate *private = PIKA_IMAGE_GET_PRIVATE (image); if (private->palette) pika_image_colormap_dispose (image); pika_image_undo_free (image); g_list_free_full (private->stored_layer_sets, g_object_unref); g_list_free_full (private->stored_channel_sets, g_object_unref); g_list_free_full (private->stored_vectors_sets, g_object_unref); g_signal_handlers_disconnect_by_func (private->layers->container, pika_image_invalidate, image); pika_container_remove_handler (private->layers->container, private->layer_offset_x_handler); pika_container_remove_handler (private->layers->container, private->layer_offset_y_handler); pika_container_remove_handler (private->layers->container, private->layer_bounding_box_handler); pika_container_remove_handler (private->layers->container, private->layer_alpha_handler); g_signal_handlers_disconnect_by_func (private->layers->container, pika_image_layers_changed, image); g_signal_handlers_disconnect_by_func (private->channels->container, pika_image_invalidate, image); pika_container_remove_handler (private->channels->container, private->channel_name_changed_handler); pika_container_remove_handler (private->channels->container, private->channel_color_changed_handler); g_signal_handlers_disconnect_by_func (private->channels->container, pika_image_channel_add, image); g_signal_handlers_disconnect_by_func (private->channels->container, pika_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 pika_image_finalize (GObject *object) { PikaImage *image = PIKA_IMAGE (object); PikaImagePrivate *private = PIKA_IMAGE_GET_PRIVATE (image); g_clear_object (&private->projection); g_clear_object (&private->graph); private->visible_mask = NULL; if (private->palette) pika_image_colormap_free (image); _pika_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_full (private->layer_stack, (GDestroyNotify) g_list_free); private->layer_stack = NULL; } g_clear_object (&private->selection_mask); g_clear_object (&private->parasites); if (private->guides) { g_list_free_full (private->guides, (GDestroyNotify) g_object_unref); private->guides = NULL; } if (private->symmetries) { g_list_free_full (private->symmetries, g_object_unref); private->symmetries = NULL; } g_clear_object (&private->grid); if (private->sample_points) { g_list_free_full (private->sample_points, (GDestroyNotify) g_object_unref); private->sample_points = NULL; } g_clear_object (&private->undo_stack); g_clear_object (&private->redo_stack); if (image->pika && image->pika->image_table) { pika_id_table_remove (image->pika->image_table, private->ID); image->pika = NULL; } g_clear_pointer (&private->display_name, g_free); g_clear_pointer (&private->display_path, g_free); G_OBJECT_CLASS (parent_class)->finalize (object); /* Don't just free the list silently. It is supposed to be empty. * Instead just assert this fact to warn on improper management of * hidden items. */ if (private->hidden_items != NULL) { g_warning ("%s: the hidden items list should be empty (%d items remaining).", G_STRFUNC, g_list_length (private->hidden_items)); g_list_free (private->hidden_items); } } static void pika_image_name_changed (PikaObject *object) { PikaImage *image = PIKA_IMAGE (object); PikaImagePrivate *private = PIKA_IMAGE_GET_PRIVATE (image); const gchar *name; if (PIKA_OBJECT_CLASS (parent_class)->name_changed) PIKA_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 = pika_object_get_name (object); if (name && strlen (name) == 0) { pika_object_name_free (object); name = NULL; } g_clear_object (&private->file); if (name) private->file = g_file_new_for_uri (name); } static gint64 pika_image_get_memsize (PikaObject *object, gint64 *gui_size) { PikaImage *image = PIKA_IMAGE (object); PikaImagePrivate *private = PIKA_IMAGE_GET_PRIVATE (image); gint64 memsize = 0; memsize += pika_object_get_memsize (PIKA_OBJECT (private->palette), gui_size); memsize += pika_object_get_memsize (PIKA_OBJECT (private->projection), gui_size); memsize += pika_g_list_get_memsize (pika_image_get_guides (image), sizeof (PikaGuide)); memsize += pika_object_get_memsize (PIKA_OBJECT (private->grid), gui_size); memsize += pika_g_list_get_memsize (pika_image_get_sample_points (image), sizeof (PikaSamplePoint)); memsize += pika_object_get_memsize (PIKA_OBJECT (private->layers), gui_size); memsize += pika_object_get_memsize (PIKA_OBJECT (private->channels), gui_size); memsize += pika_object_get_memsize (PIKA_OBJECT (private->vectors), gui_size); memsize += pika_g_slist_get_memsize (private->layer_stack, 0); memsize += pika_object_get_memsize (PIKA_OBJECT (private->selection_mask), gui_size); memsize += pika_object_get_memsize (PIKA_OBJECT (private->parasites), gui_size); memsize += pika_object_get_memsize (PIKA_OBJECT (private->undo_stack), gui_size); memsize += pika_object_get_memsize (PIKA_OBJECT (private->redo_stack), gui_size); return memsize + PIKA_OBJECT_CLASS (parent_class)->get_memsize (object, gui_size); } static gboolean pika_image_get_size (PikaViewable *viewable, gint *width, gint *height) { PikaImage *image = PIKA_IMAGE (viewable); *width = pika_image_get_width (image); *height = pika_image_get_height (image); return TRUE; } static void pika_image_size_changed (PikaViewable *viewable) { PikaImage *image = PIKA_IMAGE (viewable); GList *all_items; GList *list; if (PIKA_VIEWABLE_CLASS (parent_class)->size_changed) PIKA_VIEWABLE_CLASS (parent_class)->size_changed (viewable); all_items = pika_image_get_layer_list (image); for (list = all_items; list; list = g_list_next (list)) { PikaLayerMask *mask = pika_layer_get_mask (PIKA_LAYER (list->data)); pika_viewable_size_changed (PIKA_VIEWABLE (list->data)); if (mask) pika_viewable_size_changed (PIKA_VIEWABLE (mask)); } g_list_free (all_items); all_items = pika_image_get_channel_list (image); g_list_free_full (all_items, (GDestroyNotify) pika_viewable_size_changed); all_items = pika_image_get_vectors_list (image); g_list_free_full (all_items, (GDestroyNotify) pika_viewable_size_changed); pika_viewable_size_changed (PIKA_VIEWABLE (pika_image_get_mask (image))); pika_image_metadata_update_pixel_size (image); g_clear_object (&PIKA_IMAGE_GET_PRIVATE (image)->pickable_buffer); pika_image_update_bounding_box (image); } static gchar * pika_image_get_description (PikaViewable *viewable, gchar **tooltip) { PikaImage *image = PIKA_IMAGE (viewable); if (tooltip) *tooltip = g_strdup (pika_image_get_display_path (image)); return g_strdup_printf ("%s-%d", pika_image_get_display_name (image), pika_image_get_id (image)); } static void pika_image_real_mode_changed (PikaImage *image) { pika_projectable_structure_changed (PIKA_PROJECTABLE (image)); } static void pika_image_real_precision_changed (PikaImage *image) { pika_image_metadata_update_bits_per_sample (image); pika_projectable_structure_changed (PIKA_PROJECTABLE (image)); } static void pika_image_real_resolution_changed (PikaImage *image) { pika_image_metadata_update_resolution (image); } static void pika_image_real_size_changed_detailed (PikaImage *image, gint previous_origin_x, gint previous_origin_y, gint previous_width, gint previous_height) { /* Whenever PikaImage::size-changed-detailed is emitted, so is * PikaViewable::size-changed. Clients choose what signal to listen * to depending on how much info they need. */ pika_viewable_size_changed (PIKA_VIEWABLE (image)); } static void pika_image_real_unit_changed (PikaImage *image) { pika_image_metadata_update_resolution (image); } static void pika_image_real_colormap_changed (PikaImage *image, gint color_index) { PikaImagePrivate *private = PIKA_IMAGE_GET_PRIVATE (image); pika_image_colormap_update_formats (image); if (pika_image_get_base_type (image) == PIKA_INDEXED) { /* A colormap alteration affects the whole image */ pika_image_invalidate_all (image); pika_item_stack_invalidate_previews (PIKA_ITEM_STACK (private->layers->container)); } } static const guint8 * pika_image_color_managed_get_icc_profile (PikaColorManaged *managed, gsize *len) { return pika_image_get_icc_profile (PIKA_IMAGE (managed), len); } static PikaColorProfile * pika_image_color_managed_get_color_profile (PikaColorManaged *managed) { PikaImage *image = PIKA_IMAGE (managed); PikaColorProfile *profile; profile = pika_image_get_color_profile (image); if (! profile) profile = pika_image_get_builtin_color_profile (image); return profile; } static void pika_image_color_managed_profile_changed (PikaColorManaged *managed) { PikaImage *image = PIKA_IMAGE (managed); PikaItemStack *layers = PIKA_ITEM_STACK (pika_image_get_layers (image)); pika_image_metadata_update_colorspace (image); pika_projectable_structure_changed (PIKA_PROJECTABLE (image)); pika_viewable_invalidate_preview (PIKA_VIEWABLE (image)); pika_item_stack_profile_changed (layers); } static PikaColorProfile * pika_image_color_managed_get_simulation_profile (PikaColorManaged *managed) { PikaImage *image = PIKA_IMAGE (managed); PikaColorProfile *profile; profile = pika_image_get_simulation_profile (image); return profile; } static void pika_image_color_managed_simulation_profile_changed (PikaColorManaged *managed) { PikaImage *image = PIKA_IMAGE (managed); pika_projectable_structure_changed (PIKA_PROJECTABLE (image)); pika_viewable_invalidate_preview (PIKA_VIEWABLE (image)); } static PikaColorRenderingIntent pika_image_color_managed_get_simulation_intent (PikaColorManaged *managed) { PikaImage *image = PIKA_IMAGE (managed); return pika_image_get_simulation_intent (image); } static void pika_image_color_managed_simulation_intent_changed (PikaColorManaged *managed) { PikaImage *image = PIKA_IMAGE (managed); pika_projectable_structure_changed (PIKA_PROJECTABLE (image)); pika_viewable_invalidate_preview (PIKA_VIEWABLE (image)); } static gboolean pika_image_color_managed_get_simulation_bpc (PikaColorManaged *managed) { PikaImage *image = PIKA_IMAGE (managed); return pika_image_get_simulation_bpc (image); } static void pika_image_color_managed_simulation_bpc_changed (PikaColorManaged *managed) { PikaImage *image = PIKA_IMAGE (managed); pika_projectable_structure_changed (PIKA_PROJECTABLE (image)); pika_viewable_invalidate_preview (PIKA_VIEWABLE (image)); } static void pika_image_projectable_flush (PikaProjectable *projectable, gboolean invalidate_preview) { PikaImage *image = PIKA_IMAGE (projectable); PikaImagePrivate *private = PIKA_IMAGE_GET_PRIVATE (image); if (private->flush_accum.alpha_changed) { pika_image_alpha_changed (image); private->flush_accum.alpha_changed = FALSE; } if (private->flush_accum.mask_changed) { pika_image_mask_changed (image); private->flush_accum.mask_changed = FALSE; } if (private->flush_accum.floating_selection_changed) { pika_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 PikaImage * pika_image_get_image (PikaProjectable *projectable) { return PIKA_IMAGE (projectable); } static const Babl * pika_image_get_proj_format (PikaProjectable *projectable) { PikaImage *image = PIKA_IMAGE (projectable); PikaImagePrivate *private = PIKA_IMAGE_GET_PRIVATE (image); switch (private->base_type) { case PIKA_RGB: case PIKA_INDEXED: return pika_image_get_format (image, PIKA_RGB, pika_image_get_precision (image), TRUE, pika_image_get_layer_space (image)); case PIKA_GRAY: return pika_image_get_format (image, PIKA_GRAY, pika_image_get_precision (image), TRUE, pika_image_get_layer_space (image)); } g_return_val_if_reached (NULL); } static void pika_image_pickable_flush (PikaPickable *pickable) { PikaImagePrivate *private = PIKA_IMAGE_GET_PRIVATE (pickable); return pika_pickable_flush (PIKA_PICKABLE (private->projection)); } static GeglBuffer * pika_image_get_buffer (PikaPickable *pickable) { PikaImage *image = PIKA_IMAGE (pickable); PikaImagePrivate *private = PIKA_IMAGE_GET_PRIVATE (image); if (! private->pickable_buffer) { GeglBuffer *buffer; buffer = pika_pickable_get_buffer (PIKA_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, pika_image_get_width (image), pika_image_get_height (image))); } } return private->pickable_buffer; } static gboolean pika_image_get_pixel_at (PikaPickable *pickable, gint x, gint y, const Babl *format, gpointer pixel) { PikaImage *image = PIKA_IMAGE (pickable); PikaImagePrivate *private = PIKA_IMAGE_GET_PRIVATE (image); if (x >= 0 && y >= 0 && x < pika_image_get_width (image) && y < pika_image_get_height (image)) { return pika_pickable_get_pixel_at (PIKA_PICKABLE (private->projection), x, y, format, pixel); } return FALSE; } static gdouble pika_image_get_opacity_at (PikaPickable *pickable, gint x, gint y) { PikaImage *image = PIKA_IMAGE (pickable); PikaImagePrivate *private = PIKA_IMAGE_GET_PRIVATE (image); if (x >= 0 && y >= 0 && x < pika_image_get_width (image) && y < pika_image_get_height (image)) { return pika_pickable_get_opacity_at (PIKA_PICKABLE (private->projection), x, y); } return FALSE; } static void pika_image_get_pixel_average (PikaPickable *pickable, const GeglRectangle *rect, const Babl *format, gpointer pixel) { GeglBuffer *buffer = pika_pickable_get_buffer (pickable); return pika_gegl_average_color (buffer, rect, TRUE, GEGL_ABYSS_NONE, format, pixel); } static void pika_image_pixel_to_rgb (PikaPickable *pickable, const Babl *format, gpointer pixel, PikaRGB *color) { pika_image_color_profile_pixel_to_rgb (PIKA_IMAGE (pickable), format, pixel, color); } static void pika_image_rgb_to_pixel (PikaPickable *pickable, const PikaRGB *color, const Babl *format, gpointer pixel) { pika_image_color_profile_rgb_to_pixel (PIKA_IMAGE (pickable), color, format, pixel); } static GeglRectangle pika_image_get_bounding_box (PikaProjectable *projectable) { PikaImage *image = PIKA_IMAGE (projectable); return PIKA_IMAGE_GET_PRIVATE (image)->bounding_box; } static GeglNode * pika_image_get_graph (PikaProjectable *projectable) { PikaImage *image = PIKA_IMAGE (projectable); PikaImagePrivate *private = PIKA_IMAGE_GET_PRIVATE (image); GeglNode *layers_node; GeglNode *channels_node; GeglNode *output; PikaComponentMask mask; if (private->graph) return private->graph; private->graph = gegl_node_new (); layers_node = pika_filter_stack_get_graph (PIKA_FILTER_STACK (private->layers->container)); gegl_node_add_child (private->graph, layers_node); mask = ~pika_image_get_visible_mask (image) & PIKA_COMPONENT_MASK_ALL; private->visible_mask = gegl_node_new_child (private->graph, "operation", "pika:mask-components", "mask", mask, "alpha", 1.0, NULL); gegl_node_link (layers_node, private->visible_mask); channels_node = pika_filter_stack_get_graph (PIKA_FILTER_STACK (private->channels->container)); gegl_node_add_child (private->graph, channels_node); gegl_node_link (private->visible_mask, channels_node); output = gegl_node_get_output_proxy (private->graph, "output"); gegl_node_link (channels_node, output); return private->graph; } static void pika_image_projection_buffer_notify (PikaProjection *projection, const GParamSpec *pspec, PikaImage *image) { g_clear_object (&PIKA_IMAGE_GET_PRIVATE (image)->pickable_buffer); } static void pika_image_mask_update (PikaDrawable *drawable, gint x, gint y, gint width, gint height, PikaImage *image) { PIKA_IMAGE_GET_PRIVATE (image)->flush_accum.mask_changed = TRUE; } static void pika_image_layers_changed (PikaContainer *container, PikaChannel *channel, PikaImage *image) { pika_image_update_bounding_box (image); } static void pika_image_layer_offset_changed (PikaDrawable *drawable, const GParamSpec *pspec, PikaImage *image) { pika_image_update_bounding_box (image); } static void pika_image_layer_bounding_box_changed (PikaDrawable *drawable, PikaImage *image) { pika_image_update_bounding_box (image); } static void pika_image_layer_alpha_changed (PikaDrawable *drawable, PikaImage *image) { PikaImagePrivate *private = PIKA_IMAGE_GET_PRIVATE (image); if (pika_container_get_n_children (private->layers->container) == 1) private->flush_accum.alpha_changed = TRUE; } static void pika_image_channel_add (PikaContainer *container, PikaChannel *channel, PikaImage *image) { if (! strcmp (PIKA_IMAGE_QUICK_MASK_NAME, pika_object_get_name (channel))) { pika_image_set_quick_mask_state (image, TRUE); } } static void pika_image_channel_remove (PikaContainer *container, PikaChannel *channel, PikaImage *image) { if (! strcmp (PIKA_IMAGE_QUICK_MASK_NAME, pika_object_get_name (channel))) { pika_image_set_quick_mask_state (image, FALSE); } } static void pika_image_channel_name_changed (PikaChannel *channel, PikaImage *image) { if (! strcmp (PIKA_IMAGE_QUICK_MASK_NAME, pika_object_get_name (channel))) { pika_image_set_quick_mask_state (image, TRUE); } else if (pika_image_get_quick_mask_state (image) && ! pika_image_get_quick_mask (image)) { pika_image_set_quick_mask_state (image, FALSE); } } static void pika_image_channel_color_changed (PikaChannel *channel, PikaImage *image) { if (! strcmp (PIKA_IMAGE_QUICK_MASK_NAME, pika_object_get_name (channel))) { PIKA_IMAGE_GET_PRIVATE (image)->quick_mask_color = channel->color; } } static void pika_image_selected_layers_notify (PikaItemTree *tree, const GParamSpec *pspec, PikaImage *image) { PikaImagePrivate *private = PIKA_IMAGE_GET_PRIVATE (image); GList *layers = pika_image_get_selected_layers (image); if (layers) { /* Configure the layer stack to reflect this change */ GSList *prev_layers; while ((prev_layers = g_slist_find_custom (private->layer_stack, layers, (GCompareFunc) pika_image_layer_stack_cmp))) { g_list_free (prev_layers->data); private->layer_stack = g_slist_delete_link (private->layer_stack, prev_layers); } private->layer_stack = g_slist_prepend (private->layer_stack, g_list_copy (layers)); } g_signal_emit (image, pika_image_signals[SELECTED_LAYERS_CHANGED], 0); if (layers && pika_image_get_selected_channels (image)) pika_image_set_selected_channels (image, NULL); } static void pika_image_selected_channels_notify (PikaItemTree *tree, const GParamSpec *pspec, PikaImage *image) { GList *channels = pika_image_get_selected_channels (image); g_signal_emit (image, pika_image_signals[SELECTED_CHANNELS_CHANGED], 0); if (channels && pika_image_get_selected_layers (image)) pika_image_set_selected_layers (image, NULL); } static void pika_image_selected_vectors_notify (PikaItemTree *tree, const GParamSpec *pspec, PikaImage *image) { g_signal_emit (image, pika_image_signals[SELECTED_VECTORS_CHANGED], 0); } static void pika_image_freeze_bounding_box (PikaImage *image) { PikaImagePrivate *private = PIKA_IMAGE_GET_PRIVATE (image); private->bounding_box_freeze_count++; } static void pika_image_thaw_bounding_box (PikaImage *image) { PikaImagePrivate *private = PIKA_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; pika_image_update_bounding_box (image); } } static void pika_image_update_bounding_box (PikaImage *image) { PikaImagePrivate *private = PIKA_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 = pika_image_get_width (image); bounding_box.height = pika_image_get_height (image); if (private->show_all) { GList *iter; for (iter = pika_image_get_layer_iter (image); iter; iter = g_list_next (iter)) { PikaLayer *layer = iter->data; GeglRectangle layer_bounding_box; gint offset_x; gint offset_y; pika_item_get_offset (PIKA_ITEM (layer), &offset_x, &offset_y); layer_bounding_box = pika_drawable_get_bounding_box ( PIKA_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; pika_projectable_bounds_changed (PIKA_PROJECTABLE (image), 0, 0); } } static gint pika_image_layer_stack_cmp (GList *layers1, GList *layers2) { if (g_list_length (layers1) != g_list_length (layers2)) { /* We don't really need to order lists of layers, and only care * about identity. */ return 1; } else { GList *iter; for (iter = layers1; iter; iter = iter->next) { if (! g_list_find (layers2, iter->data)) return 1; } return 0; } } /** * pika_image_rec_remove_layer_stack_dups: * @image: * * Recursively remove duplicates from the layer stack. You should not * call this directly, call pika_image_clean_layer_stack() instead. */ static void pika_image_rec_remove_layer_stack_dups (PikaImage *image, GSList *start) { PikaImagePrivate *private; GSList *dup_layers; g_return_if_fail (PIKA_IS_IMAGE (image)); private = PIKA_IMAGE_GET_PRIVATE (image); if (start == NULL || start->next == NULL) return; while ((dup_layers = g_slist_find_custom (start->next, start->data, (GCompareFunc) pika_image_layer_stack_cmp))) { g_list_free (dup_layers->data); /* We can safely remove the duplicate then search again because we * know that @start is never removed as we search after it. */ private->layer_stack = g_slist_delete_link (private->layer_stack, dup_layers); } pika_image_rec_remove_layer_stack_dups (image, start->next); } /** * pika_image_clean_layer_stack: * @image: * * Remove any duplicate and empty selections in the layer stack. */ static void pika_image_clean_layer_stack (PikaImage *image) { PikaImagePrivate *private; g_return_if_fail (PIKA_IS_IMAGE (image)); private = PIKA_IMAGE_GET_PRIVATE (image); /* First remove all empty layer lists. */ private->layer_stack = g_slist_remove_all (private->layer_stack, NULL); /* Then remove all duplicates. */ pika_image_rec_remove_layer_stack_dups (image, private->layer_stack); } static void pika_image_remove_from_layer_stack (PikaImage *image, PikaLayer *layer) { PikaImagePrivate *private; GSList *slist; g_return_if_fail (PIKA_IS_IMAGE (image)); g_return_if_fail (PIKA_IS_LAYER (layer)); private = PIKA_IMAGE_GET_PRIVATE (image); /* Remove layer itself from the MRU layer stack. */ for (slist = private->layer_stack; slist; slist = slist->next) slist->data = g_list_remove (slist->data, layer); /* Also remove all children of a group layer from the layer_stack */ if (pika_viewable_get_children (PIKA_VIEWABLE (layer))) { PikaContainer *stack = pika_viewable_get_children (PIKA_VIEWABLE (layer)); GList *children; GList *list; children = pika_item_stack_get_item_list (PIKA_ITEM_STACK (stack)); for (list = children; list; list = g_list_next (list)) { PikaLayer *child = list->data; for (slist = private->layer_stack; slist; slist = slist->next) slist->data = g_list_remove (slist->data, child); } g_list_free (children); } pika_image_clean_layer_stack (image); } static gint pika_image_selected_is_descendant (PikaViewable *selected, PikaViewable *viewable) { /* Used as a GCompareFunc to g_list_find_custom() in order to know if * one of the selected items is a descendant to @viewable. */ if (pika_viewable_is_ancestor (viewable, selected)) return 0; else return 1; } /* public functions */ PikaImage * pika_image_new (Pika *pika, gint width, gint height, PikaImageBaseType base_type, PikaPrecision precision) { g_return_val_if_fail (PIKA_IS_PIKA (pika), NULL); g_return_val_if_fail (pika_babl_is_valid (base_type, precision), NULL); return g_object_new (PIKA_TYPE_IMAGE, "pika", pika, "width", width, "height", height, "base-type", base_type, "precision", precision, NULL); } gint64 pika_image_estimate_memsize (PikaImage *image, PikaComponentType 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 (PIKA_IS_IMAGE (image), 0); current_width = pika_image_get_width (image); current_height = pika_image_get_height (image); current_size = pika_object_get_memsize (PIKA_OBJECT (image), NULL); /* the part of the image's memsize that scales linearly with the image */ drawables = pika_image_item_list_get_list (image, PIKA_ITEM_TYPE_LAYERS | PIKA_ITEM_TYPE_CHANNELS, PIKA_ITEM_SET_ALL); pika_image_item_list_filter (drawables); drawables = g_list_prepend (drawables, pika_image_get_mask (image)); for (list = drawables; list; list = g_list_next (list)) { PikaDrawable *drawable = list->data; gdouble drawable_width; gdouble drawable_height; drawable_width = pika_item_get_width (PIKA_ITEM (drawable)); drawable_height = pika_item_get_height (PIKA_ITEM (drawable)); scalable_size += pika_drawable_estimate_memsize (drawable, pika_drawable_get_component_type (drawable), drawable_width, drawable_height); scaled_size += pika_drawable_estimate_memsize (drawable, component_type, drawable_width * width / current_width, drawable_height * height / current_height); } g_list_free (drawables); scalable_size += pika_projection_estimate_memsize (pika_image_get_base_type (image), pika_image_get_component_type (image), pika_image_get_width (image), pika_image_get_height (image)); scaled_size += pika_projection_estimate_memsize (pika_image_get_base_type (image), component_type, width, height); PIKA_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; } PikaImageBaseType pika_image_get_base_type (PikaImage *image) { g_return_val_if_fail (PIKA_IS_IMAGE (image), -1); return PIKA_IMAGE_GET_PRIVATE (image)->base_type; } PikaComponentType pika_image_get_component_type (PikaImage *image) { g_return_val_if_fail (PIKA_IS_IMAGE (image), -1); return pika_babl_component_type (PIKA_IMAGE_GET_PRIVATE (image)->precision); } PikaPrecision pika_image_get_precision (PikaImage *image) { g_return_val_if_fail (PIKA_IS_IMAGE (image), -1); return PIKA_IMAGE_GET_PRIVATE (image)->precision; } const Babl * pika_image_get_format (PikaImage *image, PikaImageBaseType base_type, PikaPrecision precision, gboolean with_alpha, const Babl *space) { g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL); switch (base_type) { case PIKA_RGB: case PIKA_GRAY: return pika_babl_format (base_type, precision, with_alpha, space); case PIKA_INDEXED: if (precision == PIKA_PRECISION_U8_NON_LINEAR) { if (with_alpha) return pika_image_colormap_get_rgba_format (image); else return pika_image_colormap_get_rgb_format (image); } } g_return_val_if_reached (NULL); } const Babl * pika_image_get_layer_space (PikaImage *image) { g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL); return PIKA_IMAGE_GET_PRIVATE (image)->layer_space; } const Babl * pika_image_get_layer_format (PikaImage *image, gboolean with_alpha) { g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL); return pika_image_get_format (image, pika_image_get_base_type (image), pika_image_get_precision (image), with_alpha, pika_image_get_layer_space (image)); } const Babl * pika_image_get_channel_format (PikaImage *image) { PikaPrecision precision; g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL); precision = pika_image_get_precision (image); if (precision == PIKA_PRECISION_U8_NON_LINEAR) return pika_image_get_format (image, PIKA_GRAY, pika_image_get_precision (image), FALSE, NULL); return pika_babl_mask_format (precision); } const Babl * pika_image_get_mask_format (PikaImage *image) { g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL); return pika_babl_mask_format (pika_image_get_precision (image)); } PikaLayerMode pika_image_get_default_new_layer_mode (PikaImage *image) { PikaImagePrivate *private; g_return_val_if_fail (PIKA_IS_IMAGE (image), PIKA_LAYER_MODE_NORMAL); private = PIKA_IMAGE_GET_PRIVATE (image); if (private->new_layer_mode == -1) { GList *layers = pika_image_get_layer_list (image); if (layers) { GList *list; for (list = layers; list; list = g_list_next (list)) { PikaLayer *layer = list->data; PikaLayerMode mode = pika_layer_get_mode (layer); if (! pika_layer_mode_is_legacy (mode)) { /* any non-legacy layer switches the mode to non-legacy */ private->new_layer_mode = PIKA_LAYER_MODE_NORMAL; break; } } /* only if all layers are legacy, the mode is also legacy */ if (! list) private->new_layer_mode = PIKA_LAYER_MODE_NORMAL_LEGACY; g_list_free (layers); } else { /* empty images are never considered legacy */ private->new_layer_mode = PIKA_LAYER_MODE_NORMAL; } } return private->new_layer_mode; } void pika_image_unset_default_new_layer_mode (PikaImage *image) { g_return_if_fail (PIKA_IS_IMAGE (image)); PIKA_IMAGE_GET_PRIVATE (image)->new_layer_mode = -1; } gint pika_image_get_id (PikaImage *image) { g_return_val_if_fail (PIKA_IS_IMAGE (image), -1); return PIKA_IMAGE_GET_PRIVATE (image)->ID; } PikaImage * pika_image_get_by_id (Pika *pika, gint image_id) { g_return_val_if_fail (PIKA_IS_PIKA (pika), NULL); if (pika->image_table == NULL) return NULL; return (PikaImage *) pika_id_table_lookup (pika->image_table, image_id); } void pika_image_set_file (PikaImage *image, GFile *file) { PikaImagePrivate *private; g_return_if_fail (PIKA_IS_IMAGE (image)); g_return_if_fail (file == NULL || G_IS_FILE (file)); private = PIKA_IMAGE_GET_PRIVATE (image); if (private->file != file) { pika_object_take_name (PIKA_OBJECT (image), file ? g_file_get_uri (file) : NULL); } } /** * pika_image_get_untitled_file: * * Returns: A #GFile saying "Untitled" for newly created images. **/ GFile * pika_image_get_untitled_file (PikaImage *image) { PikaImagePrivate *private; g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL); private = PIKA_IMAGE_GET_PRIVATE (image); if (! private->untitled_file) private->untitled_file = g_file_new_for_uri (_("Untitled")); return private->untitled_file; } /** * pika_image_get_file_or_untitled: * @image: A #PikaImage. * * Get the file of the XCF image, or the "Untitled" file if there is no file. * * Returns: A #GFile. **/ GFile * pika_image_get_file_or_untitled (PikaImage *image) { GFile *file; g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL); file = pika_image_get_file (image); if (! file) file = pika_image_get_untitled_file (image); return file; } /** * pika_image_get_file: * @image: A #PikaImage. * * Get the file of the XCF image, or %NULL if there is no file. * * Returns: (nullable): The file, or %NULL. **/ GFile * pika_image_get_file (PikaImage *image) { g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL); return PIKA_IMAGE_GET_PRIVATE (image)->file; } /** * pika_image_get_imported_file: * @image: A #PikaImage. * * Returns: (nullable): The file of the imported image, or %NULL if the image * has been saved as XCF after it was imported. **/ GFile * pika_image_get_imported_file (PikaImage *image) { g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL); return PIKA_IMAGE_GET_PRIVATE (image)->imported_file; } /** * pika_image_get_exported_file: * @image: A #PikaImage. * * Returns: (nullable): The file of the image last exported from this XCF file, * or %NULL if the image has never been exported. **/ GFile * pika_image_get_exported_file (PikaImage *image) { g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL); return PIKA_IMAGE_GET_PRIVATE (image)->exported_file; } /** * pika_image_get_save_a_copy_file: * @image: A #PikaImage. * * Returns: The URI of the last copy that was saved of this XCF file. **/ GFile * pika_image_get_save_a_copy_file (PikaImage *image) { g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL); return PIKA_IMAGE_GET_PRIVATE (image)->save_a_copy_file; } /** * pika_image_get_any_file: * @image: A #PikaImage. * * Returns: The XCF file, the imported file, or the exported file, in * that order of precedence. **/ GFile * pika_image_get_any_file (PikaImage *image) { GFile *file; g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL); file = pika_image_get_file (image); if (! file) { file = pika_image_get_imported_file (image); if (! file) { file = pika_image_get_exported_file (image); } } return file; } /** * pika_image_set_imported_uri: * @image: A #PikaImage. * @file: * * Sets the URI this file was imported from. **/ void pika_image_set_imported_file (PikaImage *image, GFile *file) { PikaImagePrivate *private; g_return_if_fail (PIKA_IS_IMAGE (image)); g_return_if_fail (file == NULL || G_IS_FILE (file)); private = PIKA_IMAGE_GET_PRIVATE (image); if (g_set_object (&private->imported_file, file)) { pika_object_name_changed (PIKA_OBJECT (image)); } if (! private->resolution_set && file != NULL) { /* Unlike new files (which follow technological progress and will * use higher default resolution, or explicitly chosen templates), * imported files have a more backward-compatible value. * * 72 PPI is traditionally the default value when none other had * been explicitly set (for instance it is the default when no * resolution metadata was set in Exif version 2.32, and below, * standard). This historical value will only ever apply to loaded * images. New images will continue having more modern or * templated defaults. */ private->xresolution = 72.0; private->yresolution = 72.0; private->resolution_unit = PIKA_UNIT_INCH; } } /** * pika_image_set_exported_file: * @image: A #PikaImage. * @file: * * Sets the file this image was last exported to. Note that saving as * XCF is not "exporting". **/ void pika_image_set_exported_file (PikaImage *image, GFile *file) { PikaImagePrivate *private; g_return_if_fail (PIKA_IS_IMAGE (image)); g_return_if_fail (file == NULL || G_IS_FILE (file)); private = PIKA_IMAGE_GET_PRIVATE (image); if (g_set_object (&private->exported_file, file)) { pika_object_name_changed (PIKA_OBJECT (image)); } } /** * pika_image_set_save_a_copy_file: * @image: A #PikaImage. * @uri: * * Set the URI to the last copy this XCF file was saved to through the * "save a copy" action. **/ void pika_image_set_save_a_copy_file (PikaImage *image, GFile *file) { PikaImagePrivate *private; g_return_if_fail (PIKA_IS_IMAGE (image)); g_return_if_fail (file == NULL || G_IS_FILE (file)); private = PIKA_IMAGE_GET_PRIVATE (image); g_set_object (&private->save_a_copy_file, file); } static gchar * pika_image_format_display_uri (PikaImage *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 (PIKA_IS_IMAGE (image), NULL); file = pika_image_get_file (image); source = pika_image_get_imported_file (image); dest = pika_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 (! pika_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 = pika_file_with_new_extension (display_file, NULL); uri_format = "[%s]"; } if (! display_file) display_file = g_object_ref (pika_image_get_untitled_file (image)); if (basename) display_uri = g_path_get_basename (pika_file_get_utf8_name (display_file)); else display_uri = g_strdup (pika_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 * pika_image_get_display_name (PikaImage *image) { PikaImagePrivate *private; g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL); private = PIKA_IMAGE_GET_PRIVATE (image); if (! private->display_name) private->display_name = pika_image_format_display_uri (image, TRUE); return private->display_name; } const gchar * pika_image_get_display_path (PikaImage *image) { PikaImagePrivate *private; g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL); private = PIKA_IMAGE_GET_PRIVATE (image); if (! private->display_path) private->display_path = pika_image_format_display_uri (image, FALSE); return private->display_path; } void pika_image_set_load_proc (PikaImage *image, PikaPlugInProcedure *proc) { g_return_if_fail (PIKA_IS_IMAGE (image)); PIKA_IMAGE_GET_PRIVATE (image)->load_proc = proc; } PikaPlugInProcedure * pika_image_get_load_proc (PikaImage *image) { g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL); return PIKA_IMAGE_GET_PRIVATE (image)->load_proc; } void pika_image_set_save_proc (PikaImage *image, PikaPlugInProcedure *proc) { g_return_if_fail (PIKA_IS_IMAGE (image)); PIKA_IMAGE_GET_PRIVATE (image)->save_proc = proc; } PikaPlugInProcedure * pika_image_get_save_proc (PikaImage *image) { g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL); return PIKA_IMAGE_GET_PRIVATE (image)->save_proc; } void pika_image_set_export_proc (PikaImage *image, PikaPlugInProcedure *proc) { g_return_if_fail (PIKA_IS_IMAGE (image)); PIKA_IMAGE_GET_PRIVATE (image)->export_proc = proc; } PikaPlugInProcedure * pika_image_get_export_proc (PikaImage *image) { g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL); return PIKA_IMAGE_GET_PRIVATE (image)->export_proc; } gint pika_image_get_xcf_version (PikaImage *image, gboolean zlib_compression, gint *pika_version, const gchar **version_string, gchar **version_reason) { GList *items; GList *list; GList *reasons = NULL; gint version = 0; /* default to oldest */ const gchar *enum_desc; #define ADD_REASON(_reason) \ if (version_reason) { \ gchar *tmp = _reason; \ if (g_list_find_custom (reasons, tmp, (GCompareFunc) strcmp)) \ g_free (tmp); \ else \ reasons = g_list_prepend (reasons, tmp); } /* need version 1 for colormaps */ if (pika_image_get_colormap_palette (image)) version = 1; items = pika_image_get_layer_list (image); for (list = items; list; list = g_list_next (list)) { PikaLayer *layer = PIKA_LAYER (list->data); switch (pika_layer_get_mode (layer)) { /* Modes that exist since ancient times */ case PIKA_LAYER_MODE_NORMAL_LEGACY: case PIKA_LAYER_MODE_DISSOLVE: case PIKA_LAYER_MODE_BEHIND_LEGACY: case PIKA_LAYER_MODE_MULTIPLY_LEGACY: case PIKA_LAYER_MODE_SCREEN_LEGACY: case PIKA_LAYER_MODE_OVERLAY_LEGACY: case PIKA_LAYER_MODE_DIFFERENCE_LEGACY: case PIKA_LAYER_MODE_ADDITION_LEGACY: case PIKA_LAYER_MODE_SUBTRACT_LEGACY: case PIKA_LAYER_MODE_DARKEN_ONLY_LEGACY: case PIKA_LAYER_MODE_LIGHTEN_ONLY_LEGACY: case PIKA_LAYER_MODE_HSV_HUE_LEGACY: case PIKA_LAYER_MODE_HSV_SATURATION_LEGACY: case PIKA_LAYER_MODE_HSL_COLOR_LEGACY: case PIKA_LAYER_MODE_HSV_VALUE_LEGACY: case PIKA_LAYER_MODE_DIVIDE_LEGACY: case PIKA_LAYER_MODE_DODGE_LEGACY: case PIKA_LAYER_MODE_BURN_LEGACY: case PIKA_LAYER_MODE_HARDLIGHT_LEGACY: break; /* Since 2.6 */ case PIKA_LAYER_MODE_SOFTLIGHT_LEGACY: case PIKA_LAYER_MODE_GRAIN_EXTRACT_LEGACY: case PIKA_LAYER_MODE_GRAIN_MERGE_LEGACY: case PIKA_LAYER_MODE_COLOR_ERASE_LEGACY: pika_enum_get_value (PIKA_TYPE_LAYER_MODE, pika_layer_get_mode (layer), NULL, NULL, &enum_desc, NULL); ADD_REASON (g_strdup_printf (_("Layer mode '%s' was added in %s"), enum_desc, "PIKA 2.6")); version = MAX (2, version); break; /* Since 2.10 */ case PIKA_LAYER_MODE_OVERLAY: case PIKA_LAYER_MODE_LCH_HUE: case PIKA_LAYER_MODE_LCH_CHROMA: case PIKA_LAYER_MODE_LCH_COLOR: case PIKA_LAYER_MODE_LCH_LIGHTNESS: pika_enum_get_value (PIKA_TYPE_LAYER_MODE, pika_layer_get_mode (layer), NULL, NULL, &enum_desc, NULL); ADD_REASON (g_strdup_printf (_("Layer mode '%s' was added in %s"), enum_desc, "PIKA 2.10")); version = MAX (9, version); break; /* Since 2.10 */ case PIKA_LAYER_MODE_NORMAL: case PIKA_LAYER_MODE_BEHIND: case PIKA_LAYER_MODE_MULTIPLY: case PIKA_LAYER_MODE_SCREEN: case PIKA_LAYER_MODE_DIFFERENCE: case PIKA_LAYER_MODE_ADDITION: case PIKA_LAYER_MODE_SUBTRACT: case PIKA_LAYER_MODE_DARKEN_ONLY: case PIKA_LAYER_MODE_LIGHTEN_ONLY: case PIKA_LAYER_MODE_HSV_HUE: case PIKA_LAYER_MODE_HSV_SATURATION: case PIKA_LAYER_MODE_HSL_COLOR: case PIKA_LAYER_MODE_HSV_VALUE: case PIKA_LAYER_MODE_DIVIDE: case PIKA_LAYER_MODE_DODGE: case PIKA_LAYER_MODE_BURN: case PIKA_LAYER_MODE_HARDLIGHT: case PIKA_LAYER_MODE_SOFTLIGHT: case PIKA_LAYER_MODE_GRAIN_EXTRACT: case PIKA_LAYER_MODE_GRAIN_MERGE: case PIKA_LAYER_MODE_VIVID_LIGHT: case PIKA_LAYER_MODE_PIN_LIGHT: case PIKA_LAYER_MODE_LINEAR_LIGHT: case PIKA_LAYER_MODE_HARD_MIX: case PIKA_LAYER_MODE_EXCLUSION: case PIKA_LAYER_MODE_LINEAR_BURN: case PIKA_LAYER_MODE_LUMA_DARKEN_ONLY: case PIKA_LAYER_MODE_LUMA_LIGHTEN_ONLY: case PIKA_LAYER_MODE_LUMINANCE: case PIKA_LAYER_MODE_COLOR_ERASE: case PIKA_LAYER_MODE_ERASE: case PIKA_LAYER_MODE_MERGE: case PIKA_LAYER_MODE_SPLIT: case PIKA_LAYER_MODE_PASS_THROUGH: pika_enum_get_value (PIKA_TYPE_LAYER_MODE, pika_layer_get_mode (layer), NULL, NULL, &enum_desc, NULL); ADD_REASON (g_strdup_printf (_("Layer mode '%s' was added in %s"), enum_desc, "PIKA 2.10")); version = MAX (10, version); break; /* Just here instead of default so we get compiler warnings */ case PIKA_LAYER_MODE_REPLACE: case PIKA_LAYER_MODE_ANTI_ERASE: case PIKA_LAYER_MODE_SEPARATOR: break; } /* need version 3 for layer trees */ if (pika_viewable_get_children (PIKA_VIEWABLE (layer))) { ADD_REASON (g_strdup_printf (_("Layer groups were added in %s"), "PIKA 2.8")); version = MAX (3, version); /* need version 13 for group layers with masks */ if (pika_layer_get_mask (layer)) { ADD_REASON (g_strdup_printf (_("Masks on layer groups were " "added in %s"), "PIKA 2.10")); version = MAX (13, version); } if (pika_item_get_lock_position (PIKA_ITEM (layer))) { ADD_REASON (g_strdup_printf (_("Position locks on layer groups were added in %s"), "PIKA 3.0")); version = MAX (17, version); } if (pika_layer_get_lock_alpha (layer)) { ADD_REASON (g_strdup_printf (_("Alpha channel locks on layer groups were added in %s"), "PIKA 3.0")); version = MAX (17, version); } } if (pika_item_get_lock_visibility (PIKA_ITEM (layer))) { ADD_REASON (g_strdup_printf (_("Visibility locks were added in %s"), "PIKA 3.0")); version = MAX (17, version); } if (PIKA_IS_TEXT_LAYER (layer) && /* If we loaded an old XCF and didn't touch the text layers, then we * just resave the text layer as-is. No need to bump the XCF version. */ ! PIKA_TEXT_LAYER (layer)->text_parasite_is_old) { ADD_REASON (g_strdup_printf (_("Format of font information in text layer was changed in %s"), "PIKA 3.0")); version = MAX (19, version); } } g_list_free (items); items = pika_image_get_channel_list (image); for (list = items; list; list = g_list_next (list)) { PikaChannel *channel = PIKA_CHANNEL (list->data); if (pika_item_get_lock_visibility (PIKA_ITEM (channel))) { ADD_REASON (g_strdup_printf (_("Visibility locks were added in %s"), "PIKA 3.0")); version = MAX (17, version); } } g_list_free (items); if (g_list_length (pika_image_get_selected_vectors (image)) > 1) { ADD_REASON (g_strdup_printf (_("Multiple path selection was " "added in %s"), "PIKA 3.0.0")); version = MAX (18, version); } items = pika_image_get_vectors_list (image); for (list = items; list; list = g_list_next (list)) { PikaVectors *vectors = PIKA_VECTORS (list->data); if (pika_item_get_color_tag (PIKA_ITEM (vectors)) != PIKA_COLOR_TAG_NONE) { ADD_REASON (g_strdup_printf (_("Storing color tags in path was " "added in %s"), "PIKA 3.0.0")); version = MAX (18, version); } if (pika_item_get_lock_content (PIKA_ITEM (list->data)) || pika_item_get_lock_position (PIKA_ITEM (list->data))) { ADD_REASON (g_strdup_printf (_("Storing locks in path was " "added in %s"), "PIKA 3.0.0")); version = MAX (18, version); } } g_list_free (items); /* version 6 for new metadata has been dropped since they are * saved through parasites, which is compatible with older versions. */ /* need version 7 for != 8-bit gamma images */ if (pika_image_get_precision (image) != PIKA_PRECISION_U8_NON_LINEAR) { ADD_REASON (g_strdup_printf (_("High bit-depth images were added " "in %s"), "PIKA 2.10")); version = MAX (7, version); } /* need version 12 for > 8-bit images for proper endian swapping */ if (pika_image_get_component_type (image) > PIKA_COMPONENT_TYPE_U8) { ADD_REASON (g_strdup_printf (_("Encoding of high bit-depth images was " "fixed in %s"), "PIKA 2.10")); version = MAX (12, version); } /* need version 8 for zlib compression */ if (zlib_compression) { ADD_REASON (g_strdup_printf (_("Internal zlib compression was " "added in %s"), "PIKA 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 (pika_object_get_memsize (PIKA_OBJECT (image), NULL) >= ((gint64) 1 << 32)) { ADD_REASON (g_strdup_printf (_("Support for image files larger than " "4GB was added in %s"), "PIKA 2.10")); version = MAX (11, version); } if (g_list_length (pika_image_get_selected_layers (image)) > 1) { ADD_REASON (g_strdup_printf (_("Multiple layer selection was " "added in %s"), "PIKA 3.0.0")); version = MAX (14, version); } if ((list = pika_image_get_guides (image))) { for (; list; list = g_list_next (list)) { gint32 position = pika_guide_get_position (list->data); if (position < 0 || (pika_guide_get_orientation (list->data) == PIKA_ORIENTATION_HORIZONTAL && position > pika_image_get_height (image)) || (pika_guide_get_orientation (list->data) == PIKA_ORIENTATION_VERTICAL && position > pika_image_get_width (image))) { ADD_REASON (g_strdup_printf (_("Off-canvas guides " "added in %s"), "PIKA 3.0.0")); version = MAX (15, version); break; } } } if (pika_image_get_stored_item_sets (image, PIKA_TYPE_LAYER) || pika_image_get_stored_item_sets (image, PIKA_TYPE_CHANNEL)) { ADD_REASON (g_strdup_printf (_("Item set and pattern search in item's name were " "added in %s"), "PIKA 3.0.0")); version = MAX (16, version); } if (g_list_length (pika_image_get_selected_channels (image)) > 1) { ADD_REASON (g_strdup_printf (_("Multiple channel selection was " "added in %s"), "PIKA 3.0.0")); version = MAX (16, version); } #undef ADD_REASON switch (version) { case 0: case 1: case 2: if (pika_version) *pika_version = 206; if (version_string) *version_string = "PIKA 2.6"; break; case 3: if (pika_version) *pika_version = 208; if (version_string) *version_string = "PIKA 2.8"; break; case 4: case 5: case 6: case 7: case 8: case 9: case 10: case 11: case 12: case 13: if (pika_version) *pika_version = 210; if (version_string) *version_string = "PIKA 2.10"; break; case 14: case 15: case 16: case 17: case 18: case 19: if (pika_version) *pika_version = 300; if (version_string) *version_string = "PIKA 3.0"; 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 pika_image_set_xcf_compression (PikaImage *image, gboolean compression) { g_return_if_fail (PIKA_IS_IMAGE (image)); PIKA_IMAGE_GET_PRIVATE (image)->xcf_compression = compression; } gboolean pika_image_get_xcf_compression (PikaImage *image) { g_return_val_if_fail (PIKA_IS_IMAGE (image), FALSE); return PIKA_IMAGE_GET_PRIVATE (image)->xcf_compression; } void pika_image_set_resolution (PikaImage *image, gdouble xresolution, gdouble yresolution) { PikaImagePrivate *private; g_return_if_fail (PIKA_IS_IMAGE (image)); private = PIKA_IMAGE_GET_PRIVATE (image); /* don't allow to set the resolution out of bounds */ if (xresolution < PIKA_MIN_RESOLUTION || xresolution > PIKA_MAX_RESOLUTION || yresolution < PIKA_MIN_RESOLUTION || yresolution > PIKA_MAX_RESOLUTION) return; private->resolution_set = TRUE; if ((ABS (private->xresolution - xresolution) >= 1e-5) || (ABS (private->yresolution - yresolution) >= 1e-5)) { pika_image_undo_push_image_resolution (image, C_("undo-type", "Change Image Resolution")); private->xresolution = xresolution; private->yresolution = yresolution; pika_image_resolution_changed (image); pika_image_size_changed_detailed (image, 0, 0, pika_image_get_width (image), pika_image_get_height (image)); } } void pika_image_get_resolution (PikaImage *image, gdouble *xresolution, gdouble *yresolution) { PikaImagePrivate *private; g_return_if_fail (PIKA_IS_IMAGE (image)); g_return_if_fail (xresolution != NULL && yresolution != NULL); private = PIKA_IMAGE_GET_PRIVATE (image); *xresolution = private->xresolution; *yresolution = private->yresolution; } void pika_image_resolution_changed (PikaImage *image) { g_return_if_fail (PIKA_IS_IMAGE (image)); g_signal_emit (image, pika_image_signals[RESOLUTION_CHANGED], 0); } void pika_image_set_unit (PikaImage *image, PikaUnit unit) { PikaImagePrivate *private; g_return_if_fail (PIKA_IS_IMAGE (image)); g_return_if_fail (unit > PIKA_UNIT_PIXEL); private = PIKA_IMAGE_GET_PRIVATE (image); if (private->resolution_unit != unit) { pika_image_undo_push_image_resolution (image, C_("undo-type", "Change Image Unit")); private->resolution_unit = unit; pika_image_unit_changed (image); } } PikaUnit pika_image_get_unit (PikaImage *image) { g_return_val_if_fail (PIKA_IS_IMAGE (image), PIKA_UNIT_INCH); return PIKA_IMAGE_GET_PRIVATE (image)->resolution_unit; } void pika_image_unit_changed (PikaImage *image) { g_return_if_fail (PIKA_IS_IMAGE (image)); g_signal_emit (image, pika_image_signals[UNIT_CHANGED], 0); } gint pika_image_get_width (PikaImage *image) { g_return_val_if_fail (PIKA_IS_IMAGE (image), 0); return PIKA_IMAGE_GET_PRIVATE (image)->width; } gint pika_image_get_height (PikaImage *image) { g_return_val_if_fail (PIKA_IS_IMAGE (image), 0); return PIKA_IMAGE_GET_PRIVATE (image)->height; } gboolean pika_image_has_alpha (PikaImage *image) { PikaImagePrivate *private; PikaLayer *layer; g_return_val_if_fail (PIKA_IS_IMAGE (image), TRUE); private = PIKA_IMAGE_GET_PRIVATE (image); layer = PIKA_LAYER (pika_container_get_first_child (private->layers->container)); return ((pika_image_get_n_layers (image) > 1) || (layer && pika_drawable_has_alpha (PIKA_DRAWABLE (layer)))); } gboolean pika_image_is_empty (PikaImage *image) { g_return_val_if_fail (PIKA_IS_IMAGE (image), TRUE); return pika_container_is_empty (PIKA_IMAGE_GET_PRIVATE (image)->layers->container); } void pika_image_set_floating_selection (PikaImage *image, PikaLayer *floating_sel) { PikaImagePrivate *private; g_return_if_fail (PIKA_IS_IMAGE (image)); g_return_if_fail (floating_sel == NULL || PIKA_IS_LAYER (floating_sel)); private = PIKA_IMAGE_GET_PRIVATE (image); if (private->floating_sel != floating_sel) { private->floating_sel = floating_sel; private->flush_accum.floating_selection_changed = TRUE; } } PikaLayer * pika_image_get_floating_selection (PikaImage *image) { g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL); return PIKA_IMAGE_GET_PRIVATE (image)->floating_sel; } void pika_image_floating_selection_changed (PikaImage *image) { g_return_if_fail (PIKA_IS_IMAGE (image)); g_signal_emit (image, pika_image_signals[FLOATING_SELECTION_CHANGED], 0); } PikaChannel * pika_image_get_mask (PikaImage *image) { g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL); return PIKA_IMAGE_GET_PRIVATE (image)->selection_mask; } void pika_image_mask_changed (PikaImage *image) { g_return_if_fail (PIKA_IS_IMAGE (image)); g_signal_emit (image, pika_image_signals[MASK_CHANGED], 0); } /** * pika_item_mask_intersect: * @image: the #PikaImage * @items: a list of #PikaItem * @x: (out) (optional): return location for x * @y: (out) (optional): return location for y * @width: (out) (optional): return location for the width * @height: (out) (optional): return location for the height * * Intersect the area of the @items and its image's selection mask. * The computed area is the bounding box of the selection intersection * within the image. These values are only returned if the function * returns %TRUE. * * Note that even though the items bounding box may not be empty, it is * possible to get a return value of %FALSE. Imagine disjoint items, one * on the left, one on the right, and a selection in the middle not * intersecting with any of the items. * * Returns: %TRUE if the selection intersects with any of the @items. */ gboolean pika_image_mask_intersect (PikaImage *image, GList *items, gint *x, gint *y, gint *width, gint *height) { PikaChannel *selection; GList *iter; gint sel_x, sel_y, sel_width, sel_height; gint x1 = G_MAXINT; gint y1 = G_MAXINT; gint x2 = G_MININT; gint y2 = G_MININT; gboolean intersect = FALSE; g_return_val_if_fail (PIKA_IS_IMAGE (image), FALSE); for (iter = items; iter; iter = iter->next) { g_return_val_if_fail (PIKA_IS_ITEM (iter->data), FALSE); g_return_val_if_fail (pika_item_is_attached (iter->data), FALSE); g_return_val_if_fail (pika_item_get_image (iter->data) == image, FALSE); } selection = pika_image_get_mask (image); if (selection) pika_item_bounds (PIKA_ITEM (selection), &sel_x, &sel_y, &sel_width, &sel_height); for (iter = items; iter; iter = iter->next) { PikaItem *item = iter->data; gboolean item_intersect; gint tmp_x, tmp_y; gint tmp_width, tmp_height; pika_item_get_offset (item, &tmp_x, &tmp_y); /* check for is_empty() before intersecting so we ignore the * selection if it is suspended (like when stroking) */ if (PIKA_ITEM (selection) != item && ! pika_channel_is_empty (selection)) { item_intersect = pika_rectangle_intersect (sel_x, sel_y, sel_width, sel_height, tmp_x, tmp_y, pika_item_get_width (item), pika_item_get_height (item), &tmp_x, &tmp_y, &tmp_width, &tmp_height); } else { tmp_width = pika_item_get_width (item); tmp_height = pika_item_get_height (item); item_intersect = TRUE; } if (item_intersect) { x1 = MIN (x1, tmp_x); y1 = MIN (y1, tmp_y); x2 = MAX (x2, tmp_x + tmp_width); y2 = MAX (y2, tmp_y + tmp_height); intersect = TRUE; } } if (intersect) { if (x) *x = x1; if (y) *y = y1; if (width) *width = x2 - x1; if (height) *height = y2 - y1; } return intersect; } void pika_image_take_mask (PikaImage *image, PikaChannel *mask) { PikaImagePrivate *private; g_return_if_fail (PIKA_IS_IMAGE (image)); g_return_if_fail (PIKA_IS_SELECTION (mask)); private = PIKA_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 (pika_image_mask_update), image); } /* image components */ const Babl * pika_image_get_component_format (PikaImage *image, PikaChannelType channel) { g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL); switch (channel) { case PIKA_CHANNEL_RED: return pika_babl_component_format (PIKA_RGB, pika_image_get_precision (image), RED); case PIKA_CHANNEL_GREEN: return pika_babl_component_format (PIKA_RGB, pika_image_get_precision (image), GREEN); case PIKA_CHANNEL_BLUE: return pika_babl_component_format (PIKA_RGB, pika_image_get_precision (image), BLUE); case PIKA_CHANNEL_ALPHA: return pika_babl_component_format (PIKA_RGB, pika_image_get_precision (image), ALPHA); case PIKA_CHANNEL_GRAY: return pika_babl_component_format (PIKA_GRAY, pika_image_get_precision (image), GRAY); case PIKA_CHANNEL_INDEXED: return babl_format ("Y u8"); /* will extract grayscale, the best * we can do here */ } return NULL; } gint pika_image_get_component_index (PikaImage *image, PikaChannelType channel) { g_return_val_if_fail (PIKA_IS_IMAGE (image), -1); switch (channel) { case PIKA_CHANNEL_RED: return RED; case PIKA_CHANNEL_GREEN: return GREEN; case PIKA_CHANNEL_BLUE: return BLUE; case PIKA_CHANNEL_GRAY: return GRAY; case PIKA_CHANNEL_INDEXED: return INDEXED; case PIKA_CHANNEL_ALPHA: switch (pika_image_get_base_type (image)) { case PIKA_RGB: return ALPHA; case PIKA_GRAY: return ALPHA_G; case PIKA_INDEXED: return ALPHA_I; } } return -1; } void pika_image_set_component_active (PikaImage *image, PikaChannelType channel, gboolean active) { PikaImagePrivate *private; gint index = -1; g_return_if_fail (PIKA_IS_IMAGE (image)); private = PIKA_IMAGE_GET_PRIVATE (image); index = pika_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... */ pika_image_unset_selected_channels (image); g_signal_emit (image, pika_image_signals[COMPONENT_ACTIVE_CHANGED], 0, channel); } } gboolean pika_image_get_component_active (PikaImage *image, PikaChannelType channel) { gint index = -1; g_return_val_if_fail (PIKA_IS_IMAGE (image), FALSE); index = pika_image_get_component_index (image, channel); if (index != -1) return PIKA_IMAGE_GET_PRIVATE (image)->active[index]; return FALSE; } void pika_image_get_active_array (PikaImage *image, gboolean *components) { PikaImagePrivate *private; gint i; g_return_if_fail (PIKA_IS_IMAGE (image)); g_return_if_fail (components != NULL); private = PIKA_IMAGE_GET_PRIVATE (image); for (i = 0; i < MAX_CHANNELS; i++) components[i] = private->active[i]; } PikaComponentMask pika_image_get_active_mask (PikaImage *image) { PikaImagePrivate *private; PikaComponentMask mask = 0; g_return_val_if_fail (PIKA_IS_IMAGE (image), 0); private = PIKA_IMAGE_GET_PRIVATE (image); switch (pika_image_get_base_type (image)) { case PIKA_RGB: mask |= (private->active[RED]) ? PIKA_COMPONENT_MASK_RED : 0; mask |= (private->active[GREEN]) ? PIKA_COMPONENT_MASK_GREEN : 0; mask |= (private->active[BLUE]) ? PIKA_COMPONENT_MASK_BLUE : 0; mask |= (private->active[ALPHA]) ? PIKA_COMPONENT_MASK_ALPHA : 0; break; case PIKA_GRAY: case PIKA_INDEXED: mask |= (private->active[GRAY]) ? PIKA_COMPONENT_MASK_RED : 0; mask |= (private->active[GRAY]) ? PIKA_COMPONENT_MASK_GREEN : 0; mask |= (private->active[GRAY]) ? PIKA_COMPONENT_MASK_BLUE : 0; mask |= (private->active[ALPHA_G]) ? PIKA_COMPONENT_MASK_ALPHA : 0; break; } return mask; } void pika_image_set_component_visible (PikaImage *image, PikaChannelType channel, gboolean visible) { PikaImagePrivate *private; gint index = -1; g_return_if_fail (PIKA_IS_IMAGE (image)); private = PIKA_IMAGE_GET_PRIVATE (image); index = pika_image_get_component_index (image, channel); if (index != -1 && visible != private->visible[index]) { private->visible[index] = visible ? TRUE : FALSE; if (private->visible_mask) { PikaComponentMask mask; mask = ~pika_image_get_visible_mask (image) & PIKA_COMPONENT_MASK_ALL; gegl_node_set (private->visible_mask, "mask", mask, NULL); } g_signal_emit (image, pika_image_signals[COMPONENT_VISIBILITY_CHANGED], 0, channel); pika_image_invalidate_all (image); } } gboolean pika_image_get_component_visible (PikaImage *image, PikaChannelType channel) { gint index = -1; g_return_val_if_fail (PIKA_IS_IMAGE (image), FALSE); index = pika_image_get_component_index (image, channel); if (index != -1) return PIKA_IMAGE_GET_PRIVATE (image)->visible[index]; return FALSE; } void pika_image_get_visible_array (PikaImage *image, gboolean *components) { PikaImagePrivate *private; gint i; g_return_if_fail (PIKA_IS_IMAGE (image)); g_return_if_fail (components != NULL); private = PIKA_IMAGE_GET_PRIVATE (image); for (i = 0; i < MAX_CHANNELS; i++) components[i] = private->visible[i]; } PikaComponentMask pika_image_get_visible_mask (PikaImage *image) { PikaImagePrivate *private; PikaComponentMask mask = 0; g_return_val_if_fail (PIKA_IS_IMAGE (image), 0); private = PIKA_IMAGE_GET_PRIVATE (image); switch (pika_image_get_base_type (image)) { case PIKA_RGB: mask |= (private->visible[RED]) ? PIKA_COMPONENT_MASK_RED : 0; mask |= (private->visible[GREEN]) ? PIKA_COMPONENT_MASK_GREEN : 0; mask |= (private->visible[BLUE]) ? PIKA_COMPONENT_MASK_BLUE : 0; mask |= (private->visible[ALPHA]) ? PIKA_COMPONENT_MASK_ALPHA : 0; break; case PIKA_GRAY: case PIKA_INDEXED: mask |= (private->visible[GRAY]) ? PIKA_COMPONENT_MASK_RED : 0; mask |= (private->visible[GRAY]) ? PIKA_COMPONENT_MASK_GREEN : 0; mask |= (private->visible[GRAY]) ? PIKA_COMPONENT_MASK_BLUE : 0; mask |= (private->visible[ALPHA]) ? PIKA_COMPONENT_MASK_ALPHA : 0; break; } return mask; } /* emitting image signals */ void pika_image_mode_changed (PikaImage *image) { g_return_if_fail (PIKA_IS_IMAGE (image)); g_signal_emit (image, pika_image_signals[MODE_CHANGED], 0); } void pika_image_precision_changed (PikaImage *image) { g_return_if_fail (PIKA_IS_IMAGE (image)); g_signal_emit (image, pika_image_signals[PRECISION_CHANGED], 0); } void pika_image_alpha_changed (PikaImage *image) { g_return_if_fail (PIKA_IS_IMAGE (image)); g_signal_emit (image, pika_image_signals[ALPHA_CHANGED], 0); } void pika_image_invalidate (PikaImage *image, gint x, gint y, gint width, gint height) { g_return_if_fail (PIKA_IS_IMAGE (image)); pika_projectable_invalidate (PIKA_PROJECTABLE (image), x, y, width, height); PIKA_IMAGE_GET_PRIVATE (image)->flush_accum.preview_invalidated = TRUE; } void pika_image_invalidate_all (PikaImage *image) { const GeglRectangle *bounding_box; g_return_if_fail (PIKA_IS_IMAGE (image)); bounding_box = &PIKA_IMAGE_GET_PRIVATE (image)->bounding_box; pika_image_invalidate (image, bounding_box->x, bounding_box->y, bounding_box->width, bounding_box->height); } void pika_image_guide_added (PikaImage *image, PikaGuide *guide) { g_return_if_fail (PIKA_IS_IMAGE (image)); g_return_if_fail (PIKA_IS_GUIDE (guide)); g_signal_emit (image, pika_image_signals[GUIDE_ADDED], 0, guide); } void pika_image_guide_removed (PikaImage *image, PikaGuide *guide) { g_return_if_fail (PIKA_IS_IMAGE (image)); g_return_if_fail (PIKA_IS_GUIDE (guide)); g_signal_emit (image, pika_image_signals[GUIDE_REMOVED], 0, guide); } void pika_image_guide_moved (PikaImage *image, PikaGuide *guide) { g_return_if_fail (PIKA_IS_IMAGE (image)); g_return_if_fail (PIKA_IS_GUIDE (guide)); g_signal_emit (image, pika_image_signals[GUIDE_MOVED], 0, guide); } void pika_image_sample_point_added (PikaImage *image, PikaSamplePoint *sample_point) { g_return_if_fail (PIKA_IS_IMAGE (image)); g_return_if_fail (PIKA_IS_SAMPLE_POINT (sample_point)); g_signal_emit (image, pika_image_signals[SAMPLE_POINT_ADDED], 0, sample_point); } void pika_image_sample_point_removed (PikaImage *image, PikaSamplePoint *sample_point) { g_return_if_fail (PIKA_IS_IMAGE (image)); g_return_if_fail (PIKA_IS_SAMPLE_POINT (sample_point)); g_signal_emit (image, pika_image_signals[SAMPLE_POINT_REMOVED], 0, sample_point); } void pika_image_sample_point_moved (PikaImage *image, PikaSamplePoint *sample_point) { g_return_if_fail (PIKA_IS_IMAGE (image)); g_return_if_fail (PIKA_IS_SAMPLE_POINT (sample_point)); g_signal_emit (image, pika_image_signals[SAMPLE_POINT_MOVED], 0, sample_point); } /** * pika_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 PikaViewable::size-changed is also emitted. **/ void pika_image_size_changed_detailed (PikaImage *image, gint previous_origin_x, gint previous_origin_y, gint previous_width, gint previous_height) { g_return_if_fail (PIKA_IS_IMAGE (image)); g_signal_emit (image, pika_image_signals[SIZE_CHANGED_DETAILED], 0, previous_origin_x, previous_origin_y, previous_width, previous_height); } void pika_image_colormap_changed (PikaImage *image, gint color_index) { PikaPalette *palette; gint n_colors; g_return_if_fail (PIKA_IS_IMAGE (image)); palette = PIKA_IMAGE_GET_PRIVATE (image)->palette; n_colors = palette ? pika_palette_get_n_colors (palette) : 0; g_return_if_fail (color_index >= -1 && color_index < n_colors); g_signal_emit (image, pika_image_signals[COLORMAP_CHANGED], 0, color_index); } void pika_image_selection_invalidate (PikaImage *image) { g_return_if_fail (PIKA_IS_IMAGE (image)); g_signal_emit (image, pika_image_signals[SELECTION_INVALIDATE], 0); } void pika_image_quick_mask_changed (PikaImage *image) { g_return_if_fail (PIKA_IS_IMAGE (image)); g_signal_emit (image, pika_image_signals[QUICK_MASK_CHANGED], 0); } void pika_image_undo_event (PikaImage *image, PikaUndoEvent event, PikaUndo *undo) { g_return_if_fail (PIKA_IS_IMAGE (image)); g_return_if_fail (((event == PIKA_UNDO_EVENT_UNDO_FREE || event == PIKA_UNDO_EVENT_UNDO_FREEZE || event == PIKA_UNDO_EVENT_UNDO_THAW) && undo == NULL) || PIKA_IS_UNDO (undo)); g_signal_emit (image, pika_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 pikaimage-undo.c for the gory details. */ /* * NEVER CALL pika_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 pika_image_dirty (PikaImage *image, PikaDirtyMask dirty_mask) { PikaImagePrivate *private; g_return_val_if_fail (PIKA_IS_IMAGE (image), FALSE); private = PIKA_IMAGE_GET_PRIVATE (image); private->dirty++; private->export_dirty++; if (! private->dirty_time) private->dirty_time = time (NULL); g_signal_emit (image, pika_image_signals[DIRTY], 0, dirty_mask); TRC (("dirty %d -> %d\n", private->dirty - 1, private->dirty)); return private->dirty; } gint pika_image_clean (PikaImage *image, PikaDirtyMask dirty_mask) { PikaImagePrivate *private; g_return_val_if_fail (PIKA_IS_IMAGE (image), FALSE); private = PIKA_IMAGE_GET_PRIVATE (image); private->dirty--; private->export_dirty--; g_signal_emit (image, pika_image_signals[CLEAN], 0, dirty_mask); TRC (("clean %d -> %d\n", private->dirty + 1, private->dirty)); return private->dirty; } void pika_image_clean_all (PikaImage *image) { PikaImagePrivate *private; g_return_if_fail (PIKA_IS_IMAGE (image)); private = PIKA_IMAGE_GET_PRIVATE (image); private->dirty = 0; private->dirty_time = 0; g_signal_emit (image, pika_image_signals[CLEAN], 0, PIKA_DIRTY_ALL); pika_object_name_changed (PIKA_OBJECT (image)); } void pika_image_export_clean_all (PikaImage *image) { PikaImagePrivate *private; g_return_if_fail (PIKA_IS_IMAGE (image)); private = PIKA_IMAGE_GET_PRIVATE (image); private->export_dirty = 0; g_signal_emit (image, pika_image_signals[CLEAN], 0, PIKA_DIRTY_ALL); pika_object_name_changed (PIKA_OBJECT (image)); } /** * pika_image_is_dirty: * @image: * * Returns: True if the image is dirty, false otherwise. **/ gint pika_image_is_dirty (PikaImage *image) { g_return_val_if_fail (PIKA_IS_IMAGE (image), FALSE); return PIKA_IMAGE_GET_PRIVATE (image)->dirty != 0; } /** * pika_image_is_export_dirty: * @image: * * Returns: True if the image export is dirty, false otherwise. **/ gboolean pika_image_is_export_dirty (PikaImage *image) { g_return_val_if_fail (PIKA_IS_IMAGE (image), FALSE); return PIKA_IMAGE_GET_PRIVATE (image)->export_dirty != 0; } gint64 pika_image_get_dirty_time (PikaImage *image) { g_return_val_if_fail (PIKA_IS_IMAGE (image), 0); return PIKA_IMAGE_GET_PRIVATE (image)->dirty_time; } /** * pika_image_saving: * @image: * * Emits the "saving" signal, indicating that @image is about to be saved, * or exported. */ void pika_image_saving (PikaImage *image) { g_return_if_fail (PIKA_IS_IMAGE (image)); g_signal_emit (image, pika_image_signals[SAVING], 0); } /** * pika_image_saved: * @image: * @file: * * Emits the "saved" signal, indicating that @image was saved to the * location specified by @file. */ void pika_image_saved (PikaImage *image, GFile *file) { g_return_if_fail (PIKA_IS_IMAGE (image)); g_return_if_fail (G_IS_FILE (file)); g_signal_emit (image, pika_image_signals[SAVED], 0, file); } /** * pika_image_exported: * @image: * @file: * * Emits the "exported" signal, indicating that @image was exported to the * location specified by @file. */ void pika_image_exported (PikaImage *image, GFile *file) { g_return_if_fail (PIKA_IS_IMAGE (image)); g_return_if_fail (G_IS_FILE (file)); g_signal_emit (image, pika_image_signals[EXPORTED], 0, file); } /* flush this image's displays */ void pika_image_flush (PikaImage *image) { g_return_if_fail (PIKA_IS_IMAGE (image)); pika_projectable_flush (PIKA_PROJECTABLE (image), PIKA_IMAGE_GET_PRIVATE (image)->flush_accum.preview_invalidated); } /* display / instance counters */ gint pika_image_get_display_count (PikaImage *image) { g_return_val_if_fail (PIKA_IS_IMAGE (image), 0); return PIKA_IMAGE_GET_PRIVATE (image)->disp_count; } void pika_image_inc_display_count (PikaImage *image) { g_return_if_fail (PIKA_IS_IMAGE (image)); PIKA_IMAGE_GET_PRIVATE (image)->disp_count++; } void pika_image_dec_display_count (PikaImage *image) { g_return_if_fail (PIKA_IS_IMAGE (image)); PIKA_IMAGE_GET_PRIVATE (image)->disp_count--; } gint pika_image_get_instance_count (PikaImage *image) { g_return_val_if_fail (PIKA_IS_IMAGE (image), 0); return PIKA_IMAGE_GET_PRIVATE (image)->instance_count; } void pika_image_inc_instance_count (PikaImage *image) { g_return_if_fail (PIKA_IS_IMAGE (image)); PIKA_IMAGE_GET_PRIVATE (image)->instance_count++; } void pika_image_inc_show_all_count (PikaImage *image) { g_return_if_fail (PIKA_IS_IMAGE (image)); PIKA_IMAGE_GET_PRIVATE (image)->show_all++; if (PIKA_IMAGE_GET_PRIVATE (image)->show_all == 1) { g_clear_object (&PIKA_IMAGE_GET_PRIVATE (image)->pickable_buffer); pika_image_update_bounding_box (image); } } void pika_image_dec_show_all_count (PikaImage *image) { g_return_if_fail (PIKA_IS_IMAGE (image)); PIKA_IMAGE_GET_PRIVATE (image)->show_all--; if (PIKA_IMAGE_GET_PRIVATE (image)->show_all == 0) { g_clear_object (&PIKA_IMAGE_GET_PRIVATE (image)->pickable_buffer); pika_image_update_bounding_box (image); } } /* parasites */ const PikaParasite * pika_image_parasite_find (PikaImage *image, const gchar *name) { g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL); return pika_parasite_list_find (PIKA_IMAGE_GET_PRIVATE (image)->parasites, name); } static void list_func (gchar *key, PikaParasite *p, gchar ***cur) { *(*cur)++ = (gchar *) g_strdup (key); } gchar ** pika_image_parasite_list (PikaImage *image) { PikaImagePrivate *private; gint count; gchar **list; gchar **cur; g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL); private = PIKA_IMAGE_GET_PRIVATE (image); count = pika_parasite_list_length (private->parasites); cur = list = g_new0 (gchar *, count + 1); pika_parasite_list_foreach (private->parasites, (GHFunc) list_func, &cur); return list; } gboolean pika_image_parasite_validate (PikaImage *image, const PikaParasite *parasite, GError **error) { const gchar *name; g_return_val_if_fail (PIKA_IS_IMAGE (image), FALSE); g_return_val_if_fail (parasite != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); name = pika_parasite_get_name (parasite); if (strcmp (name, PIKA_ICC_PROFILE_PARASITE_NAME) == 0 || strcmp (name, PIKA_SIMULATION_ICC_PROFILE_PARASITE_NAME) == 0) { return pika_image_validate_icc_parasite (image, parasite, name, NULL, error); } else if (strcmp (name, "pika-comment") == 0) { const gchar *data; guint32 length; gboolean valid = FALSE; data = pika_parasite_get_data (parasite, &length); if (length > 0) { if (data[length - 1] == '\0') valid = g_utf8_validate (data, -1, NULL); else valid = g_utf8_validate (data, length, NULL); } if (! valid) { g_set_error (error, PIKA_ERROR, PIKA_FAILED, _("'pika-comment' parasite validation failed: " "comment contains invalid UTF-8")); return FALSE; } } return TRUE; } void pika_image_parasite_attach (PikaImage *image, const PikaParasite *parasite, gboolean push_undo) { PikaImagePrivate *private; PikaParasite copy; const gchar *name; g_return_if_fail (PIKA_IS_IMAGE (image)); g_return_if_fail (parasite != NULL); private = PIKA_IMAGE_GET_PRIVATE (image); name = pika_parasite_get_name (parasite); /* this is so ugly and is only for the PDB */ if (strcmp (name, PIKA_ICC_PROFILE_PARASITE_NAME) == 0 || strcmp (name, PIKA_SIMULATION_ICC_PROFILE_PARASITE_NAME) == 0) { PikaColorProfile *profile; PikaColorProfile *builtin; const guint8 *parasite_data; guint32 parasite_length; parasite_data = pika_parasite_get_data (parasite, ¶site_length); profile = pika_color_profile_new_from_icc_profile (parasite_data, parasite_length, NULL); builtin = pika_image_get_builtin_color_profile (image); if (pika_color_profile_is_equal (profile, builtin)) { /* setting the builtin profile is equal to removing the profile */ pika_image_parasite_detach (image, PIKA_ICC_PROFILE_PARASITE_NAME, push_undo); g_object_unref (profile); return; } g_object_unref (profile); } /* make a temporary copy of the PikaParasite struct because * pika_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 && pika_parasite_is_undoable (©)) pika_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 */ pika_parasite_list_add (private->parasites, ©); if (push_undo && pika_parasite_has_flag (©, PIKA_PARASITE_ATTACH_PARENT)) { pika_parasite_shift_parent (©); pika_parasite_attach (image->pika, ©); } if (strcmp (name, PIKA_ICC_PROFILE_PARASITE_NAME) == 0) _pika_image_update_color_profile (image, parasite); if (strcmp (name, PIKA_SIMULATION_ICC_PROFILE_PARASITE_NAME) == 0) _pika_image_update_simulation_profile (image, parasite); g_signal_emit (image, pika_image_signals[PARASITE_ATTACHED], 0, name); } void pika_image_parasite_detach (PikaImage *image, const gchar *name, gboolean push_undo) { PikaImagePrivate *private; const PikaParasite *parasite; g_return_if_fail (PIKA_IS_IMAGE (image)); g_return_if_fail (name != NULL); private = PIKA_IMAGE_GET_PRIVATE (image); if (! (parasite = pika_parasite_list_find (private->parasites, name))) return; if (push_undo && pika_parasite_is_undoable (parasite)) pika_image_undo_push_image_parasite_remove (image, C_("undo-type", "Remove Parasite from Image"), name); pika_parasite_list_remove (private->parasites, name); if (strcmp (name, PIKA_ICC_PROFILE_PARASITE_NAME) == 0) _pika_image_update_color_profile (image, NULL); if (strcmp (name, PIKA_SIMULATION_ICC_PROFILE_PARASITE_NAME) == 0) _pika_image_update_simulation_profile (image, NULL); g_signal_emit (image, pika_image_signals[PARASITE_DETACHED], 0, name); } /* tattoos */ PikaTattoo pika_image_get_new_tattoo (PikaImage *image) { PikaImagePrivate *private; g_return_val_if_fail (PIKA_IS_IMAGE (image), 0); private = PIKA_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; } PikaTattoo pika_image_get_tattoo_state (PikaImage *image) { g_return_val_if_fail (PIKA_IS_IMAGE (image), 0); return PIKA_IMAGE_GET_PRIVATE (image)->tattoo_state; } gboolean pika_image_set_tattoo_state (PikaImage *image, PikaTattoo val) { GList *all_items; GList *list; gboolean retval = TRUE; PikaTattoo maxval = 0; g_return_val_if_fail (PIKA_IS_IMAGE (image), FALSE); /* Check that the layer tattoos don't overlap with channel or vector ones */ all_items = pika_image_get_layer_list (image); for (list = all_items; list; list = g_list_next (list)) { PikaTattoo ltattoo; ltattoo = pika_item_get_tattoo (PIKA_ITEM (list->data)); if (ltattoo > maxval) maxval = ltattoo; if (pika_image_get_channel_by_tattoo (image, ltattoo)) retval = FALSE; /* Oopps duplicated tattoo in channel */ if (pika_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 = pika_image_get_channel_list (image); for (list = all_items; list; list = g_list_next (list)) { PikaTattoo ctattoo; ctattoo = pika_item_get_tattoo (PIKA_ITEM (list->data)); if (ctattoo > maxval) maxval = ctattoo; if (pika_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 = pika_image_get_vectors_list (image); for (list = all_items; list; list = g_list_next (list)) { PikaTattoo vtattoo; vtattoo = pika_item_get_tattoo (PIKA_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) PIKA_IMAGE_GET_PRIVATE (image)->tattoo_state = val; return retval; } /* projection */ PikaProjection * pika_image_get_projection (PikaImage *image) { g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL); return PIKA_IMAGE_GET_PRIVATE (image)->projection; } /* layers / channels / vectors */ PikaItemTree * pika_image_get_layer_tree (PikaImage *image) { g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL); return PIKA_IMAGE_GET_PRIVATE (image)->layers; } PikaItemTree * pika_image_get_channel_tree (PikaImage *image) { g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL); return PIKA_IMAGE_GET_PRIVATE (image)->channels; } PikaItemTree * pika_image_get_vectors_tree (PikaImage *image) { g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL); return PIKA_IMAGE_GET_PRIVATE (image)->vectors; } PikaContainer * pika_image_get_layers (PikaImage *image) { g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL); return PIKA_IMAGE_GET_PRIVATE (image)->layers->container; } PikaContainer * pika_image_get_channels (PikaImage *image) { g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL); return PIKA_IMAGE_GET_PRIVATE (image)->channels->container; } PikaContainer * pika_image_get_vectors (PikaImage *image) { g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL); return PIKA_IMAGE_GET_PRIVATE (image)->vectors->container; } gint pika_image_get_n_layers (PikaImage *image) { PikaItemStack *stack; g_return_val_if_fail (PIKA_IS_IMAGE (image), 0); stack = PIKA_ITEM_STACK (pika_image_get_layers (image)); return pika_item_stack_get_n_items (stack); } gint pika_image_get_n_channels (PikaImage *image) { PikaItemStack *stack; g_return_val_if_fail (PIKA_IS_IMAGE (image), 0); stack = PIKA_ITEM_STACK (pika_image_get_channels (image)); return pika_item_stack_get_n_items (stack); } gint pika_image_get_n_vectors (PikaImage *image) { PikaItemStack *stack; g_return_val_if_fail (PIKA_IS_IMAGE (image), 0); stack = PIKA_ITEM_STACK (pika_image_get_vectors (image)); return pika_item_stack_get_n_items (stack); } GList * pika_image_get_layer_iter (PikaImage *image) { PikaItemStack *stack; g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL); stack = PIKA_ITEM_STACK (pika_image_get_layers (image)); return pika_item_stack_get_item_iter (stack); } GList * pika_image_get_channel_iter (PikaImage *image) { PikaItemStack *stack; g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL); stack = PIKA_ITEM_STACK (pika_image_get_channels (image)); return pika_item_stack_get_item_iter (stack); } GList * pika_image_get_vectors_iter (PikaImage *image) { PikaItemStack *stack; g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL); stack = PIKA_ITEM_STACK (pika_image_get_vectors (image)); return pika_item_stack_get_item_iter (stack); } GList * pika_image_get_layer_list (PikaImage *image) { PikaItemStack *stack; g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL); stack = PIKA_ITEM_STACK (pika_image_get_layers (image)); return pika_item_stack_get_item_list (stack); } GList * pika_image_get_channel_list (PikaImage *image) { PikaItemStack *stack; g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL); stack = PIKA_ITEM_STACK (pika_image_get_channels (image)); return pika_item_stack_get_item_list (stack); } GList * pika_image_get_vectors_list (PikaImage *image) { PikaItemStack *stack; g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL); stack = PIKA_ITEM_STACK (pika_image_get_vectors (image)); return pika_item_stack_get_item_list (stack); } /* active drawable, layer, channel, vectors */ void pika_image_unset_selected_channels (PikaImage *image) { PikaImagePrivate *private; GList *channels; g_return_if_fail (PIKA_IS_IMAGE (image)); private = PIKA_IMAGE_GET_PRIVATE (image); channels = pika_image_get_selected_channels (image); if (channels) { pika_image_set_selected_channels (image, NULL); if (private->layer_stack) pika_image_set_selected_layers (image, private->layer_stack->data); } } /** * pika_image_is_selected_drawable: * @image: * @drawable: * * Checks if @drawable belong to the list of currently selected * drawables. It doesn't mean this is the only selected drawable (if * this is what you want to check, use * pika_image_equal_selected_drawables() with a list containing only * this drawable). * * Returns: %TRUE is @drawable is one of the selected drawables. */ gboolean pika_image_is_selected_drawable (PikaImage *image, PikaDrawable *drawable) { GList *selected_drawables; gboolean found; selected_drawables = pika_image_get_selected_drawables (image); found = (g_list_find (selected_drawables, drawable) != NULL); g_list_free (selected_drawables); return found; } /** * pika_image_equal_selected_drawables: * @image: * @drawables: * * Compare the list of @drawables with the selected drawables in @image * (i.e. the result of pika_image_equal_selected_drawables()). * The order of the @drawables does not matter, only if the size and * contents of the list is the same. */ gboolean pika_image_equal_selected_drawables (PikaImage *image, GList *drawables) { GList *selected_drawables; GList *iter; gboolean equal = FALSE; selected_drawables = pika_image_get_selected_drawables (image); if (g_list_length (drawables) == g_list_length (selected_drawables)) { equal = TRUE; for (iter = drawables; iter; iter = iter->next) if (! g_list_find (selected_drawables, iter->data)) { equal = FALSE; break; } } g_list_free (selected_drawables); return equal; } GList * pika_image_get_selected_drawables (PikaImage *image) { PikaImagePrivate *private; GList *selected_channels; GList *selected_layers; g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL); private = PIKA_IMAGE_GET_PRIVATE (image); selected_channels = pika_item_tree_get_selected_items (private->channels); selected_layers = pika_item_tree_get_selected_items (private->layers); /* If there is an active channel (a saved selection, etc.), * we ignore the active layer */ if (selected_channels) { return g_list_copy (selected_channels); } else if (selected_layers) { selected_layers = g_list_copy (selected_layers); if (g_list_length (selected_layers) == 1) { /* As a special case, if only one layer is selected and mask * edit is in progress, we return the mask as selected * drawable instead of the layer. */ PikaLayer *layer = PIKA_LAYER (selected_layers->data); PikaLayerMask *mask = pika_layer_get_mask (layer); if (mask && pika_layer_get_edit_mask (layer)) selected_layers->data = mask; } return selected_layers; } return NULL; } GList * pika_image_get_selected_layers (PikaImage *image) { PikaImagePrivate *private; g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL); private = PIKA_IMAGE_GET_PRIVATE (image); return pika_item_tree_get_selected_items (private->layers); } GList * pika_image_get_selected_channels (PikaImage *image) { PikaImagePrivate *private; g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL); private = PIKA_IMAGE_GET_PRIVATE (image); return pika_item_tree_get_selected_items (private->channels); } GList * pika_image_get_selected_vectors (PikaImage *image) { PikaImagePrivate *private; g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL); private = PIKA_IMAGE_GET_PRIVATE (image); return pika_item_tree_get_selected_items (private->vectors); } void pika_image_set_selected_layers (PikaImage *image, GList *layers) { PikaImagePrivate *private; PikaLayer *floating_sel; GList *selected_layers; GList *layers2; GList *iter; gboolean selection_changed = TRUE; g_return_if_fail (PIKA_IS_IMAGE (image)); layers2 = g_list_copy (layers); for (iter = layers; iter; iter = iter->next) { g_return_if_fail (PIKA_IS_LAYER (iter->data)); g_return_if_fail (pika_item_get_image (PIKA_ITEM (iter->data)) == image); /* Silently remove non-attached layers from selection. Do not * error out on it as it may happen for instance when selection * changes while in the process of removing a layer group. */ if (! pika_item_is_attached (PIKA_ITEM (iter->data))) layers2 = g_list_remove (layers2, iter->data); } private = PIKA_IMAGE_GET_PRIVATE (image); floating_sel = pika_image_get_floating_selection (image); /* Make sure the floating_sel always is the active layer */ if (floating_sel && (g_list_length (layers2) != 1 || layers2->data != floating_sel)) return; selected_layers = pika_image_get_selected_layers (image); if (g_list_length (layers2) == g_list_length (selected_layers)) { selection_changed = FALSE; for (iter = layers2; iter; iter = iter->next) { if (g_list_find (selected_layers, iter->data) == NULL) { selection_changed = TRUE; break; } } } if (selection_changed) { /* Don't cache selection info for the previous active layer */ if (selected_layers) pika_drawable_invalidate_boundary (PIKA_DRAWABLE (selected_layers->data)); pika_item_tree_set_selected_items (private->layers, layers2); /* We cannot edit masks with multiple selected layers. */ if (g_list_length (layers2) > 1) { for (iter = layers2; iter; iter = iter->next) { if (pika_layer_get_mask (iter->data)) pika_layer_set_edit_mask (iter->data, FALSE); } } } else { g_list_free (layers2); } } void pika_image_set_selected_channels (PikaImage *image, GList *channels) { PikaImagePrivate *private; GList *iter; g_return_if_fail (PIKA_IS_IMAGE (image)); for (iter = channels; iter; iter = iter->next) { g_return_if_fail (PIKA_IS_CHANNEL (iter->data)); g_return_if_fail (pika_item_is_attached (PIKA_ITEM (iter->data)) && pika_item_get_image (PIKA_ITEM (iter->data)) == image); } private = PIKA_IMAGE_GET_PRIVATE (image); /* Not if there is a floating selection */ if (g_list_length (channels) > 0 && pika_image_get_floating_selection (image)) return; pika_item_tree_set_selected_items (private->channels, g_list_copy (channels)); } void pika_image_set_selected_vectors (PikaImage *image, GList *vectors) { PikaImagePrivate *private; GList *iter; g_return_if_fail (PIKA_IS_IMAGE (image)); for (iter = vectors; iter; iter = iter->next) { g_return_if_fail (PIKA_IS_VECTORS (iter->data)); g_return_if_fail (pika_item_is_attached (PIKA_ITEM (iter->data)) && pika_item_get_image (PIKA_ITEM (iter->data)) == image); } private = PIKA_IMAGE_GET_PRIVATE (image); pika_item_tree_set_selected_items (private->vectors, g_list_copy (vectors)); } /* layer, channel, vectors by tattoo */ PikaLayer * pika_image_get_layer_by_tattoo (PikaImage *image, PikaTattoo tattoo) { PikaItemStack *stack; g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL); stack = PIKA_ITEM_STACK (pika_image_get_layers (image)); return PIKA_LAYER (pika_item_stack_get_item_by_tattoo (stack, tattoo)); } PikaChannel * pika_image_get_channel_by_tattoo (PikaImage *image, PikaTattoo tattoo) { PikaItemStack *stack; g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL); stack = PIKA_ITEM_STACK (pika_image_get_channels (image)); return PIKA_CHANNEL (pika_item_stack_get_item_by_tattoo (stack, tattoo)); } PikaVectors * pika_image_get_vectors_by_tattoo (PikaImage *image, PikaTattoo tattoo) { PikaItemStack *stack; g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL); stack = PIKA_ITEM_STACK (pika_image_get_vectors (image)); return PIKA_VECTORS (pika_item_stack_get_item_by_tattoo (stack, tattoo)); } /* layer, channel, vectors by name */ PikaLayer * pika_image_get_layer_by_name (PikaImage *image, const gchar *name) { PikaItemTree *tree; g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL); g_return_val_if_fail (name != NULL, NULL); tree = pika_image_get_layer_tree (image); return PIKA_LAYER (pika_item_tree_get_item_by_name (tree, name)); } PikaChannel * pika_image_get_channel_by_name (PikaImage *image, const gchar *name) { PikaItemTree *tree; g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL); g_return_val_if_fail (name != NULL, NULL); tree = pika_image_get_channel_tree (image); return PIKA_CHANNEL (pika_item_tree_get_item_by_name (tree, name)); } PikaVectors * pika_image_get_vectors_by_name (PikaImage *image, const gchar *name) { PikaItemTree *tree; g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL); g_return_val_if_fail (name != NULL, NULL); tree = pika_image_get_vectors_tree (image); return PIKA_VECTORS (pika_item_tree_get_item_by_name (tree, name)); } /* items */ gboolean pika_image_reorder_item (PikaImage *image, PikaItem *item, PikaItem *new_parent, gint new_index, gboolean push_undo, const gchar *undo_desc) { PikaItemTree *tree; gboolean result; g_return_val_if_fail (PIKA_IS_IMAGE (image), FALSE); g_return_val_if_fail (PIKA_IS_ITEM (item), FALSE); g_return_val_if_fail (pika_item_get_image (item) == image, FALSE); tree = pika_item_get_tree (item); g_return_val_if_fail (tree != NULL, FALSE); if (push_undo) { if (! undo_desc) undo_desc = PIKA_ITEM_GET_CLASS (item)->reorder_desc; pika_image_undo_group_start (image, PIKA_UNDO_GROUP_IMAGE_ITEM_REORDER, undo_desc); } pika_image_freeze_bounding_box (image); pika_item_start_move (item, push_undo); /* item and new_parent are type-checked in PikaItemTree */ result = pika_item_tree_reorder_item (tree, item, new_parent, new_index, push_undo, undo_desc); pika_item_end_move (item, push_undo); pika_image_thaw_bounding_box (image); if (push_undo) pika_image_undo_group_end (image); return result; } gboolean pika_image_raise_item (PikaImage *image, PikaItem *item, GError **error) { gint index; g_return_val_if_fail (PIKA_IS_IMAGE (image), FALSE); g_return_val_if_fail (PIKA_IS_ITEM (item), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); index = pika_item_get_index (item); g_return_val_if_fail (index != -1, FALSE); if (index == 0) { g_set_error_literal (error, PIKA_ERROR, PIKA_FAILED, PIKA_ITEM_GET_CLASS (item)->raise_failed); return FALSE; } return pika_image_reorder_item (image, item, pika_item_get_parent (item), index - 1, TRUE, PIKA_ITEM_GET_CLASS (item)->raise_desc); } gboolean pika_image_raise_item_to_top (PikaImage *image, PikaItem *item) { g_return_val_if_fail (PIKA_IS_IMAGE (image), FALSE); g_return_val_if_fail (PIKA_IS_ITEM (item), FALSE); return pika_image_reorder_item (image, item, pika_item_get_parent (item), 0, TRUE, PIKA_ITEM_GET_CLASS (item)->raise_to_top_desc); } gboolean pika_image_lower_item (PikaImage *image, PikaItem *item, GError **error) { PikaContainer *container; gint index; g_return_val_if_fail (PIKA_IS_IMAGE (image), FALSE); g_return_val_if_fail (PIKA_IS_ITEM (item), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); container = pika_item_get_container (item); g_return_val_if_fail (container != NULL, FALSE); index = pika_item_get_index (item); if (index == pika_container_get_n_children (container) - 1) { g_set_error_literal (error, PIKA_ERROR, PIKA_FAILED, PIKA_ITEM_GET_CLASS (item)->lower_failed); return FALSE; } return pika_image_reorder_item (image, item, pika_item_get_parent (item), index + 1, TRUE, PIKA_ITEM_GET_CLASS (item)->lower_desc); } gboolean pika_image_lower_item_to_bottom (PikaImage *image, PikaItem *item) { PikaContainer *container; gint length; g_return_val_if_fail (PIKA_IS_IMAGE (image), FALSE); g_return_val_if_fail (PIKA_IS_ITEM (item), FALSE); container = pika_item_get_container (item); g_return_val_if_fail (container != NULL, FALSE); length = pika_container_get_n_children (container); return pika_image_reorder_item (image, item, pika_item_get_parent (item), length - 1, TRUE, PIKA_ITEM_GET_CLASS (item)->lower_to_bottom_desc); } /* layers */ gboolean pika_image_add_layer (PikaImage *image, PikaLayer *layer, PikaLayer *parent, gint position, gboolean push_undo) { PikaImagePrivate *private; GList *selected_layers; gboolean old_has_alpha; g_return_val_if_fail (PIKA_IS_IMAGE (image), FALSE); private = PIKA_IMAGE_GET_PRIVATE (image); /* item and parent are type-checked in PikaItemTree */ if (! pika_item_tree_get_insert_pos (private->layers, (PikaItem *) layer, (PikaItem **) &parent, &position)) return FALSE; pika_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 && pika_image_get_floating_selection (image)) position = 1; old_has_alpha = pika_image_has_alpha (image); if (push_undo) pika_image_undo_push_layer_add (image, C_("undo-type", "Add Layer"), layer, pika_image_get_selected_layers (image)); pika_item_tree_add_item (private->layers, PIKA_ITEM (layer), PIKA_ITEM (parent), position); selected_layers = g_list_prepend (NULL, layer); pika_image_set_selected_layers (image, selected_layers); g_list_free (selected_layers); /* If the layer is a floating selection, attach it to the drawable */ if (pika_layer_is_floating_sel (layer)) pika_drawable_attach_floating_sel (pika_layer_get_floating_sel_drawable (layer), layer); if (old_has_alpha != pika_image_has_alpha (image)) private->flush_accum.alpha_changed = TRUE; return TRUE; } void pika_image_remove_layer (PikaImage *image, PikaLayer *layer, gboolean push_undo, GList *new_selected) { PikaImagePrivate *private; GList *selected_layers; gboolean old_has_alpha; const gchar *undo_desc; g_return_if_fail (PIKA_IS_IMAGE (image)); g_return_if_fail (PIKA_IS_LAYER (layer)); g_return_if_fail (pika_item_is_attached (PIKA_ITEM (layer))); g_return_if_fail (pika_item_get_image (PIKA_ITEM (layer)) == image); private = PIKA_IMAGE_GET_PRIVATE (image); pika_image_unset_default_new_layer_mode (image); if (push_undo) pika_image_undo_group_start (image, PIKA_UNDO_GROUP_IMAGE_ITEM_REMOVE, C_("undo-type", "Remove Layer")); pika_item_start_move (PIKA_ITEM (layer), push_undo); if (pika_drawable_get_floating_sel (PIKA_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://heckin.technology/AlderconeStudio/PIKApp/bugs/", G_STRFUNC); return; } pika_image_remove_layer (image, pika_drawable_get_floating_sel (PIKA_DRAWABLE (layer)), TRUE, NULL); } selected_layers = g_list_copy (pika_image_get_selected_layers (image)); old_has_alpha = pika_image_has_alpha (image); if (pika_layer_is_floating_sel (layer)) { undo_desc = C_("undo-type", "Remove Floating Selection"); pika_drawable_detach_floating_sel (pika_layer_get_floating_sel_drawable (layer)); } else { undo_desc = C_("undo-type", "Remove Layer"); } if (push_undo) pika_image_undo_push_layer_remove (image, undo_desc, layer, pika_layer_get_parent (layer), pika_item_get_index (PIKA_ITEM (layer)), selected_layers); g_object_ref (layer); /* Make sure we're not caching any old selection info */ if (g_list_find (selected_layers, layer)) pika_drawable_invalidate_boundary (PIKA_DRAWABLE (layer)); /* Remove layer and its children from the MRU layer stack. */ pika_image_remove_from_layer_stack (image, layer); new_selected = pika_item_tree_remove_item (private->layers, PIKA_ITEM (layer), new_selected); if (pika_layer_is_floating_sel (layer)) { /* If this was the floating selection, activate the underlying drawable */ floating_sel_activate_drawable (layer); } else if (selected_layers && (g_list_find (selected_layers, layer) || g_list_find_custom (selected_layers, layer, (GCompareFunc) pika_image_selected_is_descendant))) { pika_image_set_selected_layers (image, new_selected); } pika_item_end_move (PIKA_ITEM (layer), push_undo); g_object_unref (layer); g_list_free (selected_layers); if (new_selected) g_list_free (new_selected); if (old_has_alpha != pika_image_has_alpha (image)) private->flush_accum.alpha_changed = TRUE; if (push_undo) pika_image_undo_group_end (image); } void pika_image_add_layers (PikaImage *image, GList *layers, PikaLayer *parent, gint position, gint x, gint y, gint width, gint height, const gchar *undo_desc) { PikaImagePrivate *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 (PIKA_IS_IMAGE (image)); g_return_if_fail (layers != NULL); private = PIKA_IMAGE_GET_PRIVATE (image); /* item and parent are type-checked in PikaItemTree */ if (! pika_item_tree_get_insert_pos (private->layers, (PikaItem *) layers->data, (PikaItem **) &parent, &position)) return; for (list = layers; list; list = g_list_next (list)) { PikaItem *item = PIKA_ITEM (list->data); gint off_x, off_y; pika_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 + pika_item_get_width (item) - layers_x); layers_height = MAX (layers_height, off_y + pika_item_get_height (item) - layers_y); } offset_x = x + (width - layers_width) / 2 - layers_x; offset_y = y + (height - layers_height) / 2 - layers_y; pika_image_undo_group_start (image, PIKA_UNDO_GROUP_LAYER_ADD, undo_desc); for (list = layers; list; list = g_list_next (list)) { PikaItem *new_item = PIKA_ITEM (list->data); pika_item_translate (new_item, offset_x, offset_y, FALSE); pika_image_add_layer (image, PIKA_LAYER (new_item), parent, position, TRUE); position++; } if (layers) pika_image_set_selected_layers (image, layers); pika_image_undo_group_end (image); } /* * pika_image_store_item_set: * @image: * @set: (transfer full): a set of items which @images takes ownership * of. * * Store a new set of @layers. * If a set with the same name and type existed, this call will silently * replace it with the new set of layers. */ void pika_image_store_item_set (PikaImage *image, PikaItemList *set) { PikaImagePrivate *private; GList **stored_sets; GList *iter; guint signal; g_return_if_fail (PIKA_IS_IMAGE (image)); g_return_if_fail (PIKA_IS_ITEM_LIST (set)); private = PIKA_IMAGE_GET_PRIVATE (image); if (pika_item_list_get_item_type (set) == PIKA_TYPE_LAYER) { stored_sets = &private->stored_layer_sets; signal = pika_image_signals[LAYER_SETS_CHANGED]; } else if (pika_item_list_get_item_type (set) == PIKA_TYPE_CHANNEL) { stored_sets = &private->stored_channel_sets; signal = pika_image_signals[CHANNEL_SETS_CHANGED]; } else if (pika_item_list_get_item_type (set) == PIKA_TYPE_VECTORS) { stored_sets = &private->stored_vectors_sets; signal = pika_image_signals[VECTORS_SETS_CHANGED]; } else { g_return_if_reached (); } for (iter = *stored_sets; iter; iter = iter->next) { gboolean is_pattern; gboolean is_pattern2; PikaSelectMethod pattern_syntax; PikaSelectMethod pattern_syntax2; is_pattern = pika_item_list_is_pattern (iter->data, &pattern_syntax); is_pattern2 = pika_item_list_is_pattern (set, &pattern_syntax2); /* Remove a previous item set of same type and name. */ if (is_pattern == is_pattern2 && (! is_pattern || pattern_syntax == pattern_syntax2) && g_strcmp0 (pika_object_get_name (iter->data), pika_object_get_name (set)) == 0) break; } if (iter) { g_object_unref (iter->data); *stored_sets = g_list_delete_link (*stored_sets, iter); } *stored_sets = g_list_prepend (*stored_sets, set); g_signal_emit (image, signal, 0); } /* * @pika_image_unlink_item_set: * @image: * @link_name: * * Remove the set of layers named @link_name. * * Returns: %TRUE if the set was removed, %FALSE if no sets with this * name existed. */ gboolean pika_image_unlink_item_set (PikaImage *image, PikaItemList *set) { PikaImagePrivate *private; GList *found; GList **stored_sets; guint signal; gboolean success; g_return_val_if_fail (PIKA_IS_IMAGE (image), FALSE); private = PIKA_IMAGE_GET_PRIVATE (image); if (pika_item_list_get_item_type (set) == PIKA_TYPE_LAYER) { stored_sets = &private->stored_layer_sets; signal = pika_image_signals[LAYER_SETS_CHANGED]; } else if (pika_item_list_get_item_type (set) == PIKA_TYPE_CHANNEL) { stored_sets = &private->stored_channel_sets; signal = pika_image_signals[CHANNEL_SETS_CHANGED]; } else if (pika_item_list_get_item_type (set) == PIKA_TYPE_VECTORS) { stored_sets = &private->stored_vectors_sets; signal = pika_image_signals[VECTORS_SETS_CHANGED]; } else { g_return_val_if_reached (FALSE); } found = g_list_find (*stored_sets, set); success = (found != NULL); if (success) { *stored_sets = g_list_delete_link (*stored_sets, found); g_object_unref (set); g_signal_emit (image, signal, 0); } return success; } /* * @pika_image_get_stored_item_sets: * @image: * @item_type: * * Returns: (transfer none): the list of all the layer sets (which you * should not modify). Order of items is relevant. */ GList * pika_image_get_stored_item_sets (PikaImage *image, GType item_type) { PikaImagePrivate *private; g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL); private = PIKA_IMAGE_GET_PRIVATE (image); if (item_type == PIKA_TYPE_LAYER) return private->stored_layer_sets; else if (item_type == PIKA_TYPE_CHANNEL) return private->stored_channel_sets; else if (item_type == PIKA_TYPE_VECTORS) return private->stored_vectors_sets; g_return_val_if_reached (FALSE); } /* * @pika_image_select_item_set: * @image: * @set: * * Replace currently selected layers in @image with the layers belonging * to @set. */ void pika_image_select_item_set (PikaImage *image, PikaItemList *set) { GList *items; GError *error = NULL; g_return_if_fail (PIKA_IS_IMAGE (image)); g_return_if_fail (PIKA_IS_ITEM_LIST (set)); items = pika_item_list_get_items (set, &error); if (! error) { GType item_type = pika_item_list_get_item_type (set); if (item_type == PIKA_TYPE_LAYER) pika_image_set_selected_layers (image, items); else if (item_type == PIKA_TYPE_CHANNEL) pika_image_set_selected_channels (image, items); else if (item_type == PIKA_TYPE_VECTORS) pika_image_set_selected_vectors (image, items); else g_return_if_reached (); } g_list_free (items); g_clear_error (&error); } /* * @pika_image_add_item_set: * @image: * @set: * * Add the layers belonging to @set to the items currently selected in * @image. */ void pika_image_add_item_set (PikaImage *image, PikaItemList *set) { GList *items; GError *error = NULL; g_return_if_fail (PIKA_IS_IMAGE (image)); g_return_if_fail (PIKA_IS_ITEM_LIST (set)); items = pika_item_list_get_items (set, &error); if (! error) { GList *selected; GList *iter; GType item_type = pika_item_list_get_item_type (set); if (item_type == PIKA_TYPE_LAYER) selected = pika_image_get_selected_layers (image); else if (item_type == PIKA_TYPE_CHANNEL) selected = pika_image_get_selected_channels (image); else if (item_type == PIKA_TYPE_VECTORS) selected = pika_image_get_selected_vectors (image); else g_return_if_reached (); selected = g_list_copy (selected); for (iter = items; iter; iter = iter->next) { if (! g_list_find (selected, iter->data)) selected = g_list_prepend (selected, iter->data); } if (item_type == PIKA_TYPE_LAYER) pika_image_set_selected_layers (image, selected); else if (item_type == PIKA_TYPE_CHANNEL) pika_image_set_selected_channels (image, selected); else if (item_type == PIKA_TYPE_VECTORS) pika_image_set_selected_vectors (image, items); g_list_free (selected); } g_clear_error (&error); } /* * @pika_image_remove_item_set: * @image: * @set: * * Remove the layers belonging to the set named @link_name (which must * exist) from the layers currently selected in @image. * * Returns: %TRUE if the selection change is done (even if it turned out * selected layers stay the same), %FALSE if no sets with this * name existed. */ void pika_image_remove_item_set (PikaImage *image, PikaItemList *set) { GList *items; GError *error = NULL; g_return_if_fail (PIKA_IS_IMAGE (image)); g_return_if_fail (PIKA_IS_ITEM_LIST (set)); items = pika_item_list_get_items (set, &error); if (! error) { GList *selected; GList *iter; GType item_type = pika_item_list_get_item_type (set); if (item_type == PIKA_TYPE_LAYER) selected = pika_image_get_selected_layers (image); else if (item_type == PIKA_TYPE_CHANNEL) selected = pika_image_get_selected_channels (image); else if (item_type == PIKA_TYPE_VECTORS) selected = pika_image_get_selected_vectors (image); else g_return_if_reached (); selected = g_list_copy (selected); for (iter = items; iter; iter = iter->next) { GList *remove; if ((remove = g_list_find (selected, iter->data))) selected = g_list_delete_link (selected, remove); } if (item_type == PIKA_TYPE_LAYER) pika_image_set_selected_layers (image, selected); else if (item_type == PIKA_TYPE_CHANNEL) pika_image_set_selected_channels (image, selected); else if (item_type == PIKA_TYPE_VECTORS) pika_image_set_selected_vectors (image, items); g_list_free (selected); } g_clear_error (&error); } /* * @pika_image_intersect_item_set: * @image: * @set: * * Remove any layers from the layers currently selected in @image if * they don't also belong to the set named @link_name (which must * exist). * * Returns: %TRUE if the selection change is done (even if it turned out * selected layers stay the same), %FALSE if no sets with this * name existed. */ void pika_image_intersect_item_set (PikaImage *image, PikaItemList *set) { GList *items; GError *error = NULL; g_return_if_fail (PIKA_IS_IMAGE (image)); g_return_if_fail (PIKA_IS_ITEM_LIST (set)); items = pika_item_list_get_items (set, &error); if (! error) { GList *selected; GList *remove = NULL; GList *iter; GType item_type = pika_item_list_get_item_type (set); if (item_type == PIKA_TYPE_LAYER) selected = pika_image_get_selected_layers (image); else if (item_type == PIKA_TYPE_CHANNEL) selected = pika_image_get_selected_channels (image); else if (item_type == PIKA_TYPE_VECTORS) selected = pika_image_get_selected_vectors (image); else g_return_if_reached (); selected = g_list_copy (selected); /* Remove items in selected but not in items. */ for (iter = selected; iter; iter = iter->next) { if (! g_list_find (items, iter->data)) remove = g_list_prepend (remove, iter); } for (iter = remove; iter; iter = iter->next) selected = g_list_delete_link (selected, iter->data); g_list_free (remove); /* Finally select the intersection. */ if (item_type == PIKA_TYPE_LAYER) pika_image_set_selected_layers (image, selected); else if (item_type == PIKA_TYPE_CHANNEL) pika_image_set_selected_channels (image, selected); else if (item_type == PIKA_TYPE_VECTORS) pika_image_set_selected_vectors (image, items); g_list_free (selected); } g_clear_error (&error); } /* channels */ gboolean pika_image_add_channel (PikaImage *image, PikaChannel *channel, PikaChannel *parent, gint position, gboolean push_undo) { PikaImagePrivate *private; GList *channels; g_return_val_if_fail (PIKA_IS_IMAGE (image), FALSE); private = PIKA_IMAGE_GET_PRIVATE (image); /* item and parent are type-checked in PikaItemTree */ if (! pika_item_tree_get_insert_pos (private->channels, (PikaItem *) channel, (PikaItem **) &parent, &position)) return FALSE; if (push_undo) pika_image_undo_push_channel_add (image, C_("undo-type", "Add Channel"), channel, pika_image_get_selected_channels (image)); pika_item_tree_add_item (private->channels, PIKA_ITEM (channel), PIKA_ITEM (parent), position); channels = g_list_prepend (NULL, channel); pika_image_set_selected_channels (image, channels); g_list_free (channels); return TRUE; } void pika_image_remove_channel (PikaImage *image, PikaChannel *channel, gboolean push_undo, GList *new_selected) { PikaImagePrivate *private; GList *selected_channels; g_return_if_fail (PIKA_IS_IMAGE (image)); g_return_if_fail (PIKA_IS_CHANNEL (channel)); g_return_if_fail (pika_item_is_attached (PIKA_ITEM (channel))); g_return_if_fail (pika_item_get_image (PIKA_ITEM (channel)) == image); if (push_undo) pika_image_undo_group_start (image, PIKA_UNDO_GROUP_IMAGE_ITEM_REMOVE, C_("undo-type", "Remove Channel")); pika_item_start_move (PIKA_ITEM (channel), push_undo); if (pika_drawable_get_floating_sel (PIKA_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://heckin.technology/AlderconeStudio/PIKApp/bugs/", G_STRFUNC); return; } pika_image_remove_layer (image, pika_drawable_get_floating_sel (PIKA_DRAWABLE (channel)), TRUE, NULL); } private = PIKA_IMAGE_GET_PRIVATE (image); selected_channels = pika_image_get_selected_channels (image); selected_channels = g_list_copy (selected_channels); if (push_undo) pika_image_undo_push_channel_remove (image, C_("undo-type", "Remove Channel"), channel, pika_channel_get_parent (channel), pika_item_get_index (PIKA_ITEM (channel)), selected_channels); g_object_ref (channel); new_selected = pika_item_tree_remove_item (private->channels, PIKA_ITEM (channel), new_selected); if (selected_channels && (g_list_find (selected_channels, channel) || g_list_find_custom (selected_channels, channel, (GCompareFunc) pika_image_selected_is_descendant))) { if (new_selected) pika_image_set_selected_channels (image, new_selected); else pika_image_unset_selected_channels (image); } g_list_free (selected_channels); pika_item_end_move (PIKA_ITEM (channel), push_undo); g_object_unref (channel); if (new_selected) g_list_free (new_selected); if (push_undo) pika_image_undo_group_end (image); } /* vectors */ gboolean pika_image_add_vectors (PikaImage *image, PikaVectors *vectors, PikaVectors *parent, gint position, gboolean push_undo) { PikaImagePrivate *private; GList *list = NULL; g_return_val_if_fail (PIKA_IS_IMAGE (image), FALSE); private = PIKA_IMAGE_GET_PRIVATE (image); /* item and parent are type-checked in PikaItemTree */ if (! pika_item_tree_get_insert_pos (private->vectors, (PikaItem *) vectors, (PikaItem **) &parent, &position)) return FALSE; if (push_undo) pika_image_undo_push_vectors_add (image, C_("undo-type", "Add Path"), vectors, pika_image_get_selected_vectors (image)); pika_item_tree_add_item (private->vectors, PIKA_ITEM (vectors), PIKA_ITEM (parent), position); if (vectors != NULL) list = g_list_prepend (NULL, vectors); pika_image_set_selected_vectors (image, list); g_list_free (list); return TRUE; } void pika_image_remove_vectors (PikaImage *image, PikaVectors *vectors, gboolean push_undo, GList *new_selected) { PikaImagePrivate *private; GList *selected_vectors; g_return_if_fail (PIKA_IS_IMAGE (image)); g_return_if_fail (PIKA_IS_VECTORS (vectors)); g_return_if_fail (pika_item_is_attached (PIKA_ITEM (vectors))); g_return_if_fail (pika_item_get_image (PIKA_ITEM (vectors)) == image); private = PIKA_IMAGE_GET_PRIVATE (image); if (push_undo) pika_image_undo_group_start (image, PIKA_UNDO_GROUP_IMAGE_ITEM_REMOVE, C_("undo-type", "Remove Path")); pika_item_start_move (PIKA_ITEM (vectors), push_undo); selected_vectors = pika_image_get_selected_vectors (image); selected_vectors = g_list_copy (selected_vectors); if (push_undo) pika_image_undo_push_vectors_remove (image, C_("undo-type", "Remove Path"), vectors, pika_vectors_get_parent (vectors), pika_item_get_index (PIKA_ITEM (vectors)), selected_vectors); g_object_ref (vectors); new_selected = pika_item_tree_remove_item (private->vectors, PIKA_ITEM (vectors), new_selected); if (selected_vectors && (g_list_find (selected_vectors, vectors) || g_list_find_custom (selected_vectors, vectors, (GCompareFunc) pika_image_selected_is_descendant))) { pika_image_set_selected_vectors (image, new_selected); } g_list_free (selected_vectors); pika_item_end_move (PIKA_ITEM (vectors), push_undo); g_object_unref (vectors); if (new_selected) g_list_free (new_selected); if (push_undo) pika_image_undo_group_end (image); } /* hidden items */ /* Sometimes you want to create a channel or other types of drawables to * work on them without adding them to the public trees and to be * visible in the GUI. This is when you create hidden items. No undo * steps will ever be created either for any processing on these items. * * Note that you are expected to manage the hidden items properly. In * particular, once you are done with them, remove them with * pika_image_remove_hidden_item() and free them. * @image is not assuming ownership of @item. */ gboolean pika_image_add_hidden_item (PikaImage *image, PikaItem *item) { PikaImagePrivate *private; g_return_val_if_fail (PIKA_IS_IMAGE (image), FALSE); g_return_val_if_fail (PIKA_IS_ITEM (item), FALSE); g_return_val_if_fail (! pika_item_is_attached (item), FALSE); g_return_val_if_fail (pika_item_get_image (item) == image, FALSE); private = PIKA_IMAGE_GET_PRIVATE (image); private->hidden_items = g_list_prepend (private->hidden_items, item); return TRUE; } void pika_image_remove_hidden_item (PikaImage *image, PikaItem *item) { PikaImagePrivate *private; g_return_if_fail (PIKA_IS_IMAGE (image)); g_return_if_fail (PIKA_IS_ITEM (item)); g_return_if_fail (pika_item_get_image (item) == image); private = PIKA_IMAGE_GET_PRIVATE (image); g_return_if_fail (g_list_find (private->hidden_items, item) != NULL); private->hidden_items = g_list_remove (private->hidden_items, item); } gboolean pika_image_is_hidden_item (PikaImage *image, PikaItem *item) { PikaImagePrivate *private; g_return_val_if_fail (PIKA_IS_IMAGE (image), FALSE); g_return_val_if_fail (PIKA_IS_ITEM (item), FALSE); g_return_val_if_fail (pika_item_get_image (item) == image, FALSE); private = PIKA_IMAGE_GET_PRIVATE (image); return (g_list_find (private->hidden_items, item) != NULL); } gboolean pika_image_coords_in_active_pickable (PikaImage *image, const PikaCoords *coords, gboolean show_all, gboolean sample_merged, gboolean selected_only) { gint x, y; gboolean in_pickable = FALSE; g_return_val_if_fail (PIKA_IS_IMAGE (image), FALSE); x = floor (coords->x); y = floor (coords->y); if (sample_merged) { if (show_all || (x >= 0 && x < pika_image_get_width (image) && y >= 0 && y < pika_image_get_height (image))) { in_pickable = TRUE; } } else { GList *drawables = pika_image_get_selected_drawables (image); GList *iter; for (iter = drawables; iter; iter = iter->next) { PikaItem *item = iter->data; gint off_x, off_y; gint d_x, d_y; pika_item_get_offset (item, &off_x, &off_y); d_x = x - off_x; d_y = y - off_y; if (d_x >= 0 && d_x < pika_item_get_width (item) && d_y >= 0 && d_y < pika_item_get_height (item)) { in_pickable = TRUE; break; } } g_list_free (drawables); } if (in_pickable && selected_only) { PikaChannel *selection = pika_image_get_mask (image); if (! pika_channel_is_empty (selection) && ! pika_pickable_get_opacity_at (PIKA_PICKABLE (selection), x, y)) { in_pickable = FALSE; } } return in_pickable; } void pika_image_invalidate_previews (PikaImage *image) { PikaItemStack *layers; PikaItemStack *channels; g_return_if_fail (PIKA_IS_IMAGE (image)); layers = PIKA_ITEM_STACK (pika_image_get_layers (image)); channels = PIKA_ITEM_STACK (pika_image_get_channels (image)); pika_item_stack_invalidate_previews (layers); pika_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 * pika_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 pika_image_set_converting (PikaImage *image, gboolean converting) { g_return_if_fail (PIKA_IS_IMAGE (image)); g_object_set (image, "converting", converting, NULL); } gboolean pika_image_get_converting (PikaImage *image) { PikaImagePrivate *private; g_return_val_if_fail (PIKA_IS_IMAGE (image), FALSE); private = PIKA_IMAGE_GET_PRIVATE (image); return private->converting; }