6212 lines
188 KiB
C
6212 lines
188 KiB
C
|
/* 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 <https://www.gnu.org/licenses/>.
|
||
|
*/
|
||
|
|
||
|
#include "config.h"
|
||
|
|
||
|
#include <string.h>
|
||
|
#include <time.h>
|
||
|
|
||
|
#include <cairo.h>
|
||
|
#include <gdk-pixbuf/gdk-pixbuf.h>
|
||
|
#include <gegl.h>
|
||
|
#include <gexiv2/gexiv2.h>
|
||
|
|
||
|
#include "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;
|
||
|
}
|