From 30fda00e24142755ad897f084551d06077e48bf7 Mon Sep 17 00:00:00 2001 From: Jehan Date: Wed, 3 Jul 2019 23:54:39 +0200 Subject: [PATCH 01/10] app: new GimpLink object. A GimpLink is just a class monitoring an image file and sending a "changed" signal when it changes. This will be used to implement link layers, whose contents is based on an external file. This is only a simple first version. Future versions may also allow to monitor specific layers in an image file, or a layer in the same XCF image which you'd want to link instead of duplicating it. --- app/core/core-types.h | 1 + app/core/gimplink.c | 340 ++++++++++++++++++++++++++++++++++++++++++ app/core/gimplink.h | 74 +++++++++ 3 files changed, 415 insertions(+) create mode 100644 app/core/gimplink.c create mode 100644 app/core/gimplink.h diff --git a/app/core/core-types.h b/app/core/core-types.h index 93cb8958c5..37a82b0a6d 100644 --- a/app/core/core-types.h +++ b/app/core/core-types.h @@ -92,6 +92,7 @@ typedef struct _GimpViewable GimpViewable; typedef struct _GimpFilter GimpFilter; typedef struct _GimpItem GimpItem; typedef struct _GimpAuxItem GimpAuxItem; +typedef struct _GimpLink GimpLink; typedef struct _Gimp Gimp; typedef struct _GimpImage GimpImage; diff --git a/app/core/gimplink.c b/app/core/gimplink.c new file mode 100644 index 0000000000..d1cff79a2a --- /dev/null +++ b/app/core/gimplink.c @@ -0,0 +1,340 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * GimpLink + * Copyright (C) 2019 Jehan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include +#include + +#include "libgimpbase/gimpbase.h" + +#include "core-types.h" + +#include "gimp.h" +#include "gimpimage.h" +#include "gimplink.h" +#include "gimpmarshal.h" +#include "gimppickable.h" +#include "gimpprojection.h" + +#include "file/file-open.h" + +#include "gimp-intl.h" + + +enum +{ + PROP_0, + PROP_GIMP, + PROP_FILE +}; + +enum +{ + CHANGED, + LAST_SIGNAL +}; + +struct _GimpLinkPrivate +{ + Gimp *gimp; + GFile *file; + GFileMonitor *monitor; + + gboolean broken; +}; + +static void gimp_link_finalize (GObject *object); +static void gimp_link_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); +static void gimp_link_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); + +static void gimp_link_file_changed (GFileMonitor *monitor, + GFile *file, + GFile *other_file, + GFileMonitorEvent event_type, + GimpLink *link); + +G_DEFINE_TYPE_WITH_PRIVATE (GimpLink, gimp_link, GIMP_TYPE_OBJECT) + +#define parent_class gimp_link_parent_class + +static guint link_signals[LAST_SIGNAL] = { 0 }; + +static void +gimp_link_class_init (GimpLinkClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + link_signals[CHANGED] = + g_signal_new ("changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpLinkClass, changed), + NULL, NULL, NULL, + G_TYPE_NONE, 0); + + object_class->finalize = gimp_link_finalize; + object_class->get_property = gimp_link_get_property; + object_class->set_property = gimp_link_set_property; + + g_object_class_install_property (object_class, PROP_GIMP, + g_param_spec_object ("gimp", NULL, NULL, + GIMP_TYPE_GIMP, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property (object_class, PROP_FILE, + g_param_spec_object ("file", NULL, NULL, + G_TYPE_FILE, + GIMP_PARAM_READWRITE)); +} + +static void +gimp_link_init (GimpLink *link) +{ + link->p = gimp_link_get_instance_private (link); + link->p->gimp = NULL; + link->p->file = NULL; + link->p->monitor = NULL; + link->p->broken = TRUE; +} + +static void +gimp_link_finalize (GObject *object) +{ + GimpLink *link = GIMP_LINK (object); + + g_clear_object (&link->p->file); + g_clear_object (&link->p->monitor); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_link_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpLink *link = GIMP_LINK (object); + + switch (property_id) + { + case PROP_GIMP: + g_value_set_object (value, link->p->gimp); + break; + case PROP_FILE: + g_value_set_object (value, link->p->file); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_link_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpLink *link = GIMP_LINK (object); + + switch (property_id) + { + case PROP_GIMP: + link->p->gimp = g_value_get_object (value); + break; + case PROP_FILE: + if (link->p->file) + g_object_unref (link->p->file); + link->p->file = g_value_dup_object (value); + if (link->p->monitor) + g_object_unref (link->p->monitor); + if (link->p->file) + { + gchar *basename = g_file_get_basename (link->p->file); + + link->p->monitor = g_file_monitor_file (link->p->file, G_FILE_MONITOR_NONE, NULL, NULL); + g_signal_connect (link->p->monitor, "changed", + G_CALLBACK (gimp_link_file_changed), + link); + gimp_object_set_name_safe (GIMP_OBJECT (object), basename); + g_free (basename); + } + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_link_file_changed (GFileMonitor *monitor, + GFile *file, + GFile *other_file, + GFileMonitorEvent event_type, + GimpLink *link) +{ + switch (event_type) + { + case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT: + case G_FILE_MONITOR_EVENT_CREATED: + g_signal_emit (link, link_signals[CHANGED], 0); + break; + case G_FILE_MONITOR_EVENT_DELETED: + link->p->broken = TRUE; + break; + default: + /* No need to signal for changes where nothing can be done anyway. + * In particular a file deletion, the link is broken, yet we don't + * want to re-render. + * Don't emit either on G_FILE_MONITOR_EVENT_CHANGED because too + * many such events may be emitted for a single file writing. + */ + break; + } + +} + +/* public functions */ + +/** + * gimp_link_new: + * @gimp: #Gimp object. + * @file: a #GFile object. + * + * Creates a new text layer. + * + * Return value: a new #GimpLink or %NULL in case of a problem + **/ +GimpLink * +gimp_link_new (Gimp *gimp, + GFile *file) +{ + GimpObject *link; + + g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL); + g_return_val_if_fail (G_IS_FILE (file), NULL); + + link = g_object_new (GIMP_TYPE_LINK, + "gimp", gimp, + "file", file, + NULL); + + return GIMP_LINK (link); +} + +GFile * +gimp_link_get_file (GimpLink *link) +{ + g_return_val_if_fail (GIMP_IS_LINK (link), NULL); + + return link->p->file; +} + +void +gimp_link_set_file (GimpLink *link, + GFile *file) +{ + g_return_if_fail (gimp_item_is_attached (GIMP_ITEM (link))); + g_return_if_fail (G_IS_FILE (file) || file == NULL); + + if (file && g_file_equal (file, link->p->file)) + return; + + g_object_set (link, + "file", file, + NULL); +} + +gboolean +gimp_link_is_broken (GimpLink *link, + gboolean recheck) +{ + GeglBuffer *buffer; + + if (recheck) + { + buffer = gimp_link_get_buffer (link, NULL, NULL); + g_clear_object (&buffer); + } + + return link->p->broken; +} + +GimpLink * +gimp_link_duplicate (GimpLink *link) +{ + g_return_val_if_fail (GIMP_IS_LINK (link), NULL); + + return gimp_link_new (link->p->gimp, link->p->file); +} + +GeglBuffer * +gimp_link_get_buffer (GimpLink *link, + GimpProgress *progress, + GError **error) +{ + GeglBuffer *buffer = NULL; + + if (link->p->file) + { + GimpImage *image; + GimpPDBStatusType status; + const gchar *mime_type = NULL; + + image = file_open_image (link->p->gimp, + gimp_get_user_context (link->p->gimp), + progress, + link->p->file, + FALSE, NULL, + /* XXX We might want interactive opening + * for a first opening (when done through + * GUI), but not for every re-render. + */ + GIMP_RUN_NONINTERACTIVE, + &status, &mime_type, error); + + if (image && status == GIMP_PDB_SUCCESS) + { + gimp_projection_finish_draw (gimp_image_get_projection (image)); + buffer = gimp_pickable_get_buffer (GIMP_PICKABLE (image)); + g_object_ref (buffer); + } + + /* Only keep the buffer, free the rest. */ + g_clear_object (&image); + } + + link->p->broken = (buffer == NULL); + + return buffer; +} diff --git a/app/core/gimplink.h b/app/core/gimplink.h new file mode 100644 index 0000000000..6b19a74e19 --- /dev/null +++ b/app/core/gimplink.h @@ -0,0 +1,74 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * GimpLink + * Copyright (C) 2019 Jehan + * + * 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 . + */ + +#ifndef __GIMP_LINK_H__ +#define __GIMP_LINK_H__ + + +#include "gimpitem.h" + + +#define GIMP_TYPE_LINK (gimp_link_get_type ()) +#define GIMP_LINK(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_LINK, GimpLink)) +#define GIMP_LINK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_LINK, GimpLinkClass)) +#define GIMP_IS_LINK(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_LINK)) +#define GIMP_IS_LINK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_LINK)) +#define GIMP_LINK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_LINK, GimpLinkClass)) + + +typedef struct _GimpLinkClass GimpLinkClass; +typedef struct _GimpLinkPrivate GimpLinkPrivate; + +struct _GimpLink +{ + GimpObject parent_instance; + + GimpLinkPrivate *p; +}; + +struct _GimpLinkClass +{ + GimpObjectClass parent_class; + + void (* changed) (GimpLink *link); +}; + + +GType gimp_link_get_type (void) G_GNUC_CONST; + +GimpLink * gimp_link_new (Gimp *gimp, + GFile *file); + +GFile * gimp_link_get_file (GimpLink *link); +void gimp_link_set_file (GimpLink *layer, + GFile *file); + +gboolean gimp_link_is_broken (GimpLink *link, + gboolean recheck); +GimpLink * gimp_link_duplicate (GimpLink *link); + +void gimp_link_get_size (GimpLink *link, + gint *width, + gint *height); +GeglBuffer * gimp_link_get_buffer (GimpLink *link, + GimpProgress *progress, + GError **error); + +#endif /* __GIMP_LINK_H__ */ From 610e48a17036ac5a8994c46a59c1465236df09f1 Mon Sep 17 00:00:00 2001 From: Jehan Date: Thu, 4 Jul 2019 23:49:20 +0200 Subject: [PATCH 02/10] app: new link layers. These are layers who content depends on another source (right now only an external image). This can be useful when for instance working at several people on a single artwork, hence being able to load new versions of an image without even touching anything in the XCF (for instance, say you draw an animated character while someone else is taking care of the background). Similarly to what we do for text layers, once you start modifying the contents, it turns into a "normal" layer. The link information is still available though, so it is possible to revert to the monitoring state with the menu item "Monitor Linked Image" which appear when a link layer became a normal layer. This is not finale as I'm still experimenting. In particular, I have not implemented XCF saving/loading yet for this new layer type. --- app/actions/layers-actions.c | 18 + app/actions/layers-commands.c | 138 +++++- app/actions/layers-commands.h | 6 + app/core/core-enums.c | 2 + app/core/core-enums.h | 1 + app/core/core-types.h | 1 + app/core/gimpimage-undo-push.c | 21 + app/core/gimpimage-undo-push.h | 5 + app/core/gimplinklayer.c | 651 ++++++++++++++++++++++++++++ app/core/gimplinklayer.h | 66 +++ app/core/gimplinklayerundo.c | 198 +++++++++ app/core/gimplinklayerundo.h | 56 +++ app/dialogs/item-options-dialog.c | 15 + app/dialogs/item-options-dialog.h | 1 + app/dialogs/layer-options-dialog.c | 102 ++++- app/dialogs/layer-options-dialog.h | 1 + app/widgets/gimpviewrendererlayer.c | 4 +- libgimpbase/gimpbaseenums.c | 2 + libgimpbase/gimpbaseenums.h | 4 +- menus/layers-menu.ui | 4 + pdb/enums.pl | 6 +- 21 files changed, 1289 insertions(+), 13 deletions(-) create mode 100644 app/core/gimplinklayer.c create mode 100644 app/core/gimplinklayer.h create mode 100644 app/core/gimplinklayerundo.c create mode 100644 app/core/gimplinklayerundo.h diff --git a/app/actions/layers-actions.c b/app/actions/layers-actions.c index 1322e3034d..b5dd4187ce 100644 --- a/app/actions/layers-actions.c +++ b/app/actions/layers-actions.c @@ -30,6 +30,7 @@ #include "core/gimpimage.h" #include "core/gimplayer.h" #include "core/gimplayer-floating-selection.h" +#include "core/gimplinklayer.h" #include "text/gimptextlayer.h" @@ -173,6 +174,18 @@ static const GimpActionEntry layers_actions[] = image_flatten_image_cmd_callback, GIMP_HELP_IMAGE_FLATTEN }, + { "layers-link-discard", GIMP_ICON_TOOL_TEXT, + NC_("layers-action", "_Discard Link Information"), NULL, { NULL }, + NC_("layers-action", "Turn this link layer into a normal layer"), + layers_link_discard_cmd_callback, + GIMP_HELP_LAYER_TEXT_DISCARD }, + + { "layers-link-monitor", GIMP_ICON_TOOL_TEXT, + NC_("layers-action", "_Monitor Linked Image"), NULL, { NULL }, + NC_("layers-action", "Discard any transformation and monitor the linked file again"), + layers_link_monitor_cmd_callback, + GIMP_HELP_LAYER_TEXT_DISCARD }, + { "layers-text-discard", GIMP_ICON_TOOL_TEXT, NC_("layers-action", "_Discard Text Information"), NULL, { NULL }, NC_("layers-action", "Turn these text layers into normal layers"), @@ -757,6 +770,7 @@ layers_actions_update (GimpActionGroup *group, gboolean lock_alpha = TRUE; gboolean can_lock_alpha = FALSE; gboolean text_layer = FALSE; + gboolean link_layer = FALSE; gboolean bs_mutable = FALSE; /* At least 1 selected layers' blend space is mutable. */ gboolean cs_mutable = FALSE; /* At least 1 selected layers' composite space is mutable. */ gboolean cm_mutable = FALSE; /* At least 1 selected layers' composite mode is mutable. */ @@ -977,6 +991,7 @@ layers_actions_update (GimpActionGroup *group, gimp_action_group_set_action_active (group, action, TRUE); text_layer = gimp_item_is_text_layer (GIMP_ITEM (layer)); + link_layer = gimp_item_is_link_layer (GIMP_ITEM (layer)); } } @@ -1037,6 +1052,9 @@ layers_actions_update (GimpActionGroup *group, SET_SENSITIVE ("layers-merge-layers", n_selected_layers > 0 && !fs && !ac); SET_SENSITIVE ("layers-flatten-image", !fs && !ac); + SET_VISIBLE ("layers-link-discard", link_layer && !ac); + SET_VISIBLE ("layers-link-monitor", GIMP_IS_LINK_LAYER (layer) && ! link_layer && !ac); + SET_VISIBLE ("layers-text-discard", n_text_layers > 0 && !ac); SET_VISIBLE ("layers-text-to-vectors", n_text_layers > 0 && !ac); SET_VISIBLE ("layers-text-along-vectors", text_layer && !ac); diff --git a/app/actions/layers-commands.c b/app/actions/layers-commands.c index 586cc9699d..f490d654ca 100644 --- a/app/actions/layers-commands.c +++ b/app/actions/layers-commands.c @@ -50,6 +50,8 @@ #include "core/gimplayerpropundo.h" #include "core/gimplayer-floating-selection.h" #include "core/gimplayer-new.h" +#include "core/gimplink.h" +#include "core/gimplinklayer.h" #include "core/gimplist.h" #include "core/gimppickable.h" #include "core/gimppickable-auto-shrink.h" @@ -68,6 +70,7 @@ #include "widgets/gimpaction.h" #include "widgets/gimpdock.h" #include "widgets/gimphelp-ids.h" +#include "widgets/gimpopendialog.h" #include "widgets/gimpprogressdialog.h" #include "display/gimpdisplay.h" @@ -103,6 +106,7 @@ static void layers_new_callback (GtkWidget *dialog, GimpLayerCompositeMode layer_composite_mode, gdouble layer_opacity, GimpFillType layer_fill_type, + GimpLink *link, gint layer_width, gint layer_height, gint layer_offset_x, @@ -126,6 +130,7 @@ static void layers_edit_attributes_callback (GtkWidget *dialog, GimpLayerCompositeMode layer_composite_mode, gdouble layer_opacity, GimpFillType layer_fill_type, + GimpLink *link, gint layer_width, gint layer_height, gint layer_offset_x, @@ -174,6 +179,9 @@ static gint layers_mode_index (GimpLayerMode layer_mode const GimpLayerMode *modes, gint n_modes); +static void layers_link_dialog_response (GtkDialog *dialog, + gint response_id, + GimpImage *image); /* private variables */ @@ -426,6 +434,22 @@ layers_new_last_vals_cmd_callback (GimpAction *action, return; } + if (config->layer_new_fill_type == GIMP_FILL_LINK) + { + GtkWidget *dialog; + + dialog = gimp_open_dialog_new (image->gimp); + gtk_window_set_title (GTK_WINDOW (dialog), _("Select Linked Image")); + gtk_file_chooser_set_select_multiple (GTK_FILE_CHOOSER (dialog), FALSE); + + g_signal_connect (dialog, "response", + G_CALLBACK (layers_link_dialog_response), + image); + gtk_widget_show (dialog); + + return; + } + layer_mode = config->layer_new_mode; if (layer_mode == GIMP_LAYER_MODE_NORMAL || @@ -1071,6 +1095,32 @@ layers_delete_cmd_callback (GimpAction *action, gimp_image_flush (image); } +void +layers_link_discard_cmd_callback (GimpAction *action, + GVariant *value, + gpointer data) +{ + GimpImage *image; + GimpLayer *layer; + return_if_no_layer (image, layer, data); + + if (GIMP_IS_LINK_LAYER (layer)) + gimp_link_layer_discard (GIMP_LINK_LAYER (layer)); +} + +void +layers_link_monitor_cmd_callback (GimpAction *action, + GVariant *value, + gpointer data) +{ + GimpImage *image; + GimpLayer *layer; + return_if_no_layer (image, layer, data); + + if (GIMP_IS_LINK_LAYER (layer)) + gimp_link_layer_monitor (GIMP_LINK_LAYER (layer)); +} + void layers_text_discard_cmd_callback (GimpAction *action, GVariant *value, @@ -2264,6 +2314,7 @@ layers_new_callback (GtkWidget *dialog, GimpLayerCompositeMode layer_composite_mode, gdouble layer_opacity, GimpFillType layer_fill_type, + GimpLink *link, gint layer_width, gint layer_height, gint layer_offset_x, @@ -2325,17 +2376,31 @@ layers_new_callback (GtkWidget *dialog, position = 0; } - layer = gimp_layer_new (image, layer_width, layer_height, - gimp_image_get_layer_format (image, TRUE), - config->layer_new_name, - config->layer_new_opacity, - config->layer_new_mode); + if (layer_fill_type == GIMP_FILL_LINK) + { + if (link) + { + layer = gimp_link_layer_new (image, link); + + if (layer_mode != gimp_layer_get_mode (layer)) + gimp_layer_set_mode (layer, layer_mode, TRUE); + } + } + else + { + layer = gimp_layer_new (image, layer_width, layer_height, + gimp_image_get_layer_format (image, TRUE), + config->layer_new_name, + config->layer_new_opacity, + config->layer_new_mode); + } if (layer) { gimp_item_set_offset (GIMP_ITEM (layer), layer_offset_x, layer_offset_y); - gimp_drawable_fill (GIMP_DRAWABLE (layer), context, - config->layer_new_fill_type); + if (layer_fill_type != GIMP_FILL_LINK) + gimp_drawable_fill (GIMP_DRAWABLE (layer), context, + config->layer_new_fill_type); gimp_item_set_visible (GIMP_ITEM (layer), layer_visible, FALSE); gimp_item_set_color_tag (GIMP_ITEM (layer), layer_color_tag, FALSE); gimp_item_set_lock_content (GIMP_ITEM (layer), layer_lock_pixels, @@ -2354,6 +2419,16 @@ layers_new_callback (GtkWidget *dialog, new_layers = g_list_prepend (new_layers, layer); } + else if (layer_fill_type == GIMP_FILL_LINK) + { + /* We special-case the link layers because chances of failure are + * higher and especially source of failure may be user choices. + * For such case, we don't want to generate WARNINGS (which are + * for code bugs), but an error dialog instead. + */ + gimp_message_literal (image->gimp, NULL, GIMP_MESSAGE_WARNING, + _("Invalid image file")); + } else { g_warning ("%s: could not allocate new layer", G_STRFUNC); @@ -2380,6 +2455,7 @@ layers_edit_attributes_callback (GtkWidget *dialog, GimpLayerCompositeMode layer_composite_mode, gdouble layer_opacity, GimpFillType unused1, + GimpLink *link, gint unused2, gint unused3, gint layer_offset_x, @@ -2408,7 +2484,8 @@ layers_edit_attributes_callback (GtkWidget *dialog, layer_lock_pixels != gimp_item_get_lock_content (item) || layer_lock_position != gimp_item_get_lock_position (item) || layer_lock_visibility != gimp_item_get_lock_visibility (item) || - layer_lock_alpha != gimp_layer_get_lock_alpha (layer)) + layer_lock_alpha != gimp_layer_get_lock_alpha (layer) || + link) { gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_ITEM_PROPERTIES, @@ -2469,6 +2546,9 @@ layers_edit_attributes_callback (GtkWidget *dialog, if (layer_lock_alpha != gimp_layer_get_lock_alpha (layer)) gimp_layer_set_lock_alpha (layer, layer_lock_alpha, TRUE); + if (GIMP_IS_LINK_LAYER (layer) && link) + gimp_link_layer_set_link (GIMP_LINK_LAYER (layer), link, TRUE); + gimp_image_undo_group_end (image); gimp_image_flush (image); @@ -2649,3 +2729,45 @@ layers_mode_index (GimpLayerMode layer_mode, return i; } + +static void +layers_link_dialog_response (GtkDialog *dialog, + gint response_id, + GimpImage *image) +{ + if (response_id == GTK_RESPONSE_OK) + { + GFile *file; + + file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog)); + if (file) + { + GimpLayer *layer; + GimpLink *link; + + link = gimp_link_new (image->gimp, file); + layer = gimp_link_layer_new (image, link); + + if (layer) + { + GimpDialogConfig *config; + + config = GIMP_DIALOG_CONFIG (image->gimp->config); + gimp_layer_set_blend_space (layer, + config->layer_new_blend_space, FALSE); + gimp_layer_set_composite_space (layer, + config->layer_new_composite_space, FALSE); + gimp_layer_set_composite_mode (layer, + config->layer_new_composite_mode, FALSE); + + gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_LAYER_ADD, + _("Add Link Layer")); + gimp_image_add_layer (image, layer, GIMP_IMAGE_ACTIVE_PARENT, -1, TRUE); + gimp_image_undo_group_end (image); + gimp_image_flush (image); + } + } + } + + gtk_widget_destroy (GTK_WIDGET (dialog)); +} diff --git a/app/actions/layers-commands.h b/app/actions/layers-commands.h index 6e28d641e5..283d186eda 100644 --- a/app/actions/layers-commands.h +++ b/app/actions/layers-commands.h @@ -75,6 +75,12 @@ void layers_merge_group_cmd_callback (GimpAction *action, void layers_delete_cmd_callback (GimpAction *action, GVariant *value, gpointer data); +void layers_link_discard_cmd_callback (GimpAction *action, + GVariant *value, + gpointer data); +void layers_link_monitor_cmd_callback (GimpAction *action, + GVariant *value, + gpointer data); void layers_text_discard_cmd_callback (GimpAction *action, GVariant *value, gpointer data); diff --git a/app/core/core-enums.c b/app/core/core-enums.c index e416283dcc..428f5b146b 100644 --- a/app/core/core-enums.c +++ b/app/core/core-enums.c @@ -1257,6 +1257,7 @@ gimp_undo_type_get_type (void) { GIMP_UNDO_LAYER_MODE, "GIMP_UNDO_LAYER_MODE", "layer-mode" }, { GIMP_UNDO_LAYER_OPACITY, "GIMP_UNDO_LAYER_OPACITY", "layer-opacity" }, { GIMP_UNDO_LAYER_LOCK_ALPHA, "GIMP_UNDO_LAYER_LOCK_ALPHA", "layer-lock-alpha" }, + { GIMP_UNDO_LINK_LAYER, "GIMP_UNDO_LINK_LAYER", "link-layer" }, { GIMP_UNDO_GROUP_LAYER_SUSPEND_RESIZE, "GIMP_UNDO_GROUP_LAYER_SUSPEND_RESIZE", "group-layer-suspend-resize" }, { GIMP_UNDO_GROUP_LAYER_RESUME_RESIZE, "GIMP_UNDO_GROUP_LAYER_RESUME_RESIZE", "group-layer-resume-resize" }, { GIMP_UNDO_GROUP_LAYER_SUSPEND_MASK, "GIMP_UNDO_GROUP_LAYER_SUSPEND_MASK", "group-layer-suspend-mask" }, @@ -1369,6 +1370,7 @@ gimp_undo_type_get_type (void) { GIMP_UNDO_LAYER_MODE, NC_("undo-type", "Set layer mode"), NULL }, { GIMP_UNDO_LAYER_OPACITY, NC_("undo-type", "Set layer opacity"), NULL }, { GIMP_UNDO_LAYER_LOCK_ALPHA, NC_("undo-type", "Lock/Unlock alpha channel"), NULL }, + { GIMP_UNDO_LINK_LAYER, NC_("undo-type", "Link layer"), NULL }, { GIMP_UNDO_GROUP_LAYER_SUSPEND_RESIZE, NC_("undo-type", "Suspend group layer resize"), NULL }, { GIMP_UNDO_GROUP_LAYER_RESUME_RESIZE, NC_("undo-type", "Resume group layer resize"), NULL }, { GIMP_UNDO_GROUP_LAYER_SUSPEND_MASK, NC_("undo-type", "Suspend group layer mask"), NULL }, diff --git a/app/core/core-enums.h b/app/core/core-enums.h index 3c33799540..9e30e3e33f 100644 --- a/app/core/core-enums.h +++ b/app/core/core-enums.h @@ -611,6 +611,7 @@ typedef enum /*< pdb-skip >*/ GIMP_UNDO_LAYER_MODE, /*< desc="Set layer mode" >*/ GIMP_UNDO_LAYER_OPACITY, /*< desc="Set layer opacity" >*/ GIMP_UNDO_LAYER_LOCK_ALPHA, /*< desc="Lock/Unlock alpha channel" >*/ + GIMP_UNDO_LINK_LAYER, /*< desc="Link layer" >*/ GIMP_UNDO_GROUP_LAYER_SUSPEND_RESIZE, /*< desc="Suspend group layer resize" >*/ GIMP_UNDO_GROUP_LAYER_RESUME_RESIZE, /*< desc="Resume group layer resize" >*/ GIMP_UNDO_GROUP_LAYER_SUSPEND_MASK, /*< desc="Suspend group layer mask" >*/ diff --git a/app/core/core-types.h b/app/core/core-types.h index 37a82b0a6d..adbf8dc56d 100644 --- a/app/core/core-types.h +++ b/app/core/core-types.h @@ -170,6 +170,7 @@ typedef struct _GimpLayerMask GimpLayerMask; typedef struct _GimpSelection GimpSelection; typedef struct _GimpLayer GimpLayer; typedef struct _GimpGroupLayer GimpGroupLayer; +typedef struct _GimpLinkLayer GimpLinkLayer; /* auxiliary image items */ diff --git a/app/core/gimpimage-undo-push.c b/app/core/gimpimage-undo-push.c index 426c2a4424..3058542938 100644 --- a/app/core/gimpimage-undo-push.c +++ b/app/core/gimpimage-undo-push.c @@ -48,6 +48,8 @@ #include "gimplayermaskundo.h" #include "gimplayerpropundo.h" #include "gimplayerundo.h" +#include "gimplinklayer.h" +#include "gimplinklayerundo.h" #include "gimpmaskundo.h" #include "gimpsamplepoint.h" #include "gimpsamplepointundo.h" @@ -851,6 +853,25 @@ gimp_image_undo_push_text_layer_convert (GimpImage *image, NULL); } +/**********************/ +/* Link Layer Undos */ +/**********************/ + +GimpUndo * +gimp_image_undo_push_link_layer (GimpImage *image, + const gchar *undo_desc, + GimpLinkLayer *layer) +{ + g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL); + g_return_val_if_fail (GIMP_IS_LINK_LAYER (layer), NULL); + g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (layer)), NULL); + + return gimp_image_undo_push (image, GIMP_TYPE_LINK_LAYER_UNDO, + GIMP_UNDO_LINK_LAYER, undo_desc, + GIMP_DIRTY_ITEM | GIMP_DIRTY_DRAWABLE, + "item", layer, + NULL); +} /**********************/ /* Layer Mask Undos */ diff --git a/app/core/gimpimage-undo-push.h b/app/core/gimpimage-undo-push.h index c08b5fa7e4..016dcb3db4 100644 --- a/app/core/gimpimage-undo-push.h +++ b/app/core/gimpimage-undo-push.h @@ -210,6 +210,11 @@ GimpUndo * gimp_image_undo_push_text_layer_convert (GimpImage *image, const gchar *undo_desc, GimpTextLayer *layer); +/* link layer undos */ + +GimpUndo * gimp_image_undo_push_link_layer (GimpImage *image, + const gchar *undo_desc, + GimpLinkLayer *layer); /* layer mask undos */ diff --git a/app/core/gimplinklayer.c b/app/core/gimplinklayer.c new file mode 100644 index 0000000000..793ae18b1c --- /dev/null +++ b/app/core/gimplinklayer.c @@ -0,0 +1,651 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * GimpLinkLayer + * Copyright (C) 2019 Jehan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include +#include + +#include "libgimpbase/gimpbase.h" +#include "libgimpcolor/gimpcolor.h" +#include "libgimpconfig/gimpconfig.h" + +#include "core-types.h" + +#include "gegl/gimp-gegl-loops.h" +#include "gegl/gimp-gegl-utils.h" + +#include "gimp.h" +#include "gimpcontext.h" +#include "gimpimage.h" +#include "gimpimage-color-profile.h" +#include "gimpimage-undo.h" +#include "gimpimage-undo-push.h" +#include "gimpitemtree.h" + +#include "gimplink.h" +#include "gimplinklayer.h" + +#include "gimp-intl.h" + + +enum +{ + PROP_0, + PROP_LINK, + PROP_AUTO_RENAME, + PROP_MODIFIED +}; + +struct _GimpLinkLayerPrivate +{ + GimpLink *link; + gboolean modified; + gboolean auto_rename; +}; + +static void gimp_link_layer_finalize (GObject *object); +static void gimp_link_layer_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); +static void gimp_link_layer_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); + +static gint64 gimp_link_layer_get_memsize (GimpObject *object, + gint64 *gui_size); + +static GimpItem * gimp_link_layer_duplicate (GimpItem *item, + GType new_type); +static gboolean gimp_link_layer_rename (GimpItem *item, + const gchar *new_name, + const gchar *undo_desc, + GError **error); + +static void gimp_link_layer_set_buffer (GimpDrawable *drawable, + gboolean push_undo, + const gchar *undo_desc, + GeglBuffer *buffer, + const GeglRectangle *bounds); +static void gimp_link_layer_push_undo (GimpDrawable *drawable, + const gchar *undo_desc, + GeglBuffer *buffer, + gint x, + gint y, + gint width, + gint height); + +static void gimp_link_layer_convert_type (GimpLayer *layer, + GimpImage *dest_image, + const Babl *new_format, + GimpColorProfile *src_profile, + GimpColorProfile *dest_profile, + GeglDitherMethod layer_dither_type, + GeglDitherMethod mask_dither_type, + gboolean push_undo, + GimpProgress *progress); + +static void gimp_link_layer_link_changed (GimpLinkLayer *layer); +static gboolean gimp_link_layer_render (GimpLinkLayer *layer); +static void gimp_link_layer_render_buffer (GimpLinkLayer *layer, + GeglBuffer *buffer); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpLinkLayer, gimp_link_layer, GIMP_TYPE_LAYER) + +#define parent_class gimp_link_layer_parent_class + + +static void +gimp_link_layer_class_init (GimpLinkLayerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpObjectClass *gimp_object_class = GIMP_OBJECT_CLASS (klass); + GimpViewableClass *viewable_class = GIMP_VIEWABLE_CLASS (klass); + GimpItemClass *item_class = GIMP_ITEM_CLASS (klass); + GimpDrawableClass *drawable_class = GIMP_DRAWABLE_CLASS (klass); + GimpLayerClass *layer_class = GIMP_LAYER_CLASS (klass); + + object_class->finalize = gimp_link_layer_finalize; + object_class->get_property = gimp_link_layer_get_property; + object_class->set_property = gimp_link_layer_set_property; + + gimp_object_class->get_memsize = gimp_link_layer_get_memsize; + + viewable_class->default_icon_name = "emblem-symbolic-link"; + + item_class->duplicate = gimp_link_layer_duplicate; + item_class->rename = gimp_link_layer_rename; + + item_class->default_name = _("Link Layer"); + item_class->rename_desc = _("Rename Link Layer"); + item_class->translate_desc = _("Move Link Layer"); + item_class->scale_desc = _("Scale Link Layer"); + item_class->resize_desc = _("Resize Link Layer"); + item_class->flip_desc = _("Flip Link Layer"); + item_class->rotate_desc = _("Rotate Link Layer"); + item_class->transform_desc = _("Transform Link Layer"); + + drawable_class->set_buffer = gimp_link_layer_set_buffer; + drawable_class->push_undo = gimp_link_layer_push_undo; + + layer_class->convert_type = gimp_link_layer_convert_type; + + g_object_class_install_property (object_class, PROP_LINK, + g_param_spec_object ("link", + NULL, NULL, + GIMP_TYPE_LINK, + GIMP_PARAM_READWRITE)); + + GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_AUTO_RENAME, + "auto-rename", + NULL, NULL, + TRUE, + GIMP_PARAM_STATIC_STRINGS); + + GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_MODIFIED, + "modified", + NULL, NULL, + FALSE, + GIMP_PARAM_STATIC_STRINGS); +} + +static void +gimp_link_layer_init (GimpLinkLayer *layer) +{ + layer->p = gimp_link_layer_get_instance_private (layer); + layer->p->link = NULL; +} + +static void +gimp_link_layer_finalize (GObject *object) +{ + GimpLinkLayer *layer = GIMP_LINK_LAYER (object); + + g_clear_object (&layer->p->link); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_link_layer_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpLinkLayer *layer = GIMP_LINK_LAYER (object); + + switch (property_id) + { + case PROP_LINK: + g_value_set_object (value, layer->p->link); + break; + case PROP_AUTO_RENAME: + g_value_set_boolean (value, layer->p->auto_rename); + break; + case PROP_MODIFIED: + g_value_set_boolean (value, layer->p->modified); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_link_layer_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpLinkLayer *layer = GIMP_LINK_LAYER (object); + + switch (property_id) + { + case PROP_LINK: + gimp_link_layer_set_link (layer, g_value_get_object (value), TRUE); + break; + case PROP_AUTO_RENAME: + layer->p->auto_rename = g_value_get_boolean (value); + break; + case PROP_MODIFIED: + layer->p->modified = g_value_get_boolean (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static gint64 +gimp_link_layer_get_memsize (GimpObject *object, + gint64 *gui_size) +{ + GimpLinkLayer *layer = GIMP_LINK_LAYER (object); + gint64 memsize = 0; + + memsize += gimp_object_get_memsize (GIMP_OBJECT (layer->p->link), + gui_size); + + return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object, + gui_size); +} + +static GimpItem * +gimp_link_layer_duplicate (GimpItem *item, + GType new_type) +{ + GimpItem *new_item; + + g_return_val_if_fail (g_type_is_a (new_type, GIMP_TYPE_DRAWABLE), NULL); + + new_item = GIMP_ITEM_CLASS (parent_class)->duplicate (item, new_type); + + if (GIMP_IS_LINK_LAYER (new_item)) + { + GimpLinkLayer *layer = GIMP_LINK_LAYER (item); + GimpLinkLayer *new_layer = GIMP_LINK_LAYER (new_item); + + gimp_config_sync (G_OBJECT (layer), G_OBJECT (new_layer), 0); + + if (layer->p->link) + { + GimpLink *link = gimp_link_duplicate (layer->p->link); + + gimp_link_layer_set_link (new_layer, link, FALSE); + } + } + + return new_item; +} + +static gboolean +gimp_link_layer_rename (GimpItem *item, + const gchar *new_name, + const gchar *undo_desc, + GError **error) +{ + if (GIMP_ITEM_CLASS (parent_class)->rename (item, new_name, undo_desc, error)) + { + g_object_set (item, "auto-rename", FALSE, NULL); + + return TRUE; + } + + return FALSE; +} + +static void +gimp_link_layer_set_buffer (GimpDrawable *drawable, + gboolean push_undo, + const gchar *undo_desc, + GeglBuffer *buffer, + const GeglRectangle *bounds) +{ + GimpLinkLayer *layer = GIMP_LINK_LAYER (drawable); + GimpImage *image = gimp_item_get_image (GIMP_ITEM (layer)); + + if (push_undo && ! layer->p->modified) + gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_DRAWABLE_MOD, + undo_desc); + + GIMP_DRAWABLE_CLASS (parent_class)->set_buffer (drawable, + push_undo, undo_desc, + buffer, + bounds); + + if (push_undo && ! layer->p->modified) + { + gimp_image_undo_push_link_layer (image, NULL, layer); + + g_object_set (drawable, "modified", TRUE, NULL); + + gimp_image_undo_group_end (image); + } +} + +static void +gimp_link_layer_push_undo (GimpDrawable *drawable, + const gchar *undo_desc, + GeglBuffer *buffer, + gint x, + gint y, + gint width, + gint height) +{ + GimpLinkLayer *layer = GIMP_LINK_LAYER (drawable); + GimpImage *image = gimp_item_get_image (GIMP_ITEM (layer)); + + if (! layer->p->modified) + gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_DRAWABLE, undo_desc); + + GIMP_DRAWABLE_CLASS (parent_class)->push_undo (drawable, undo_desc, + buffer, + x, y, width, height); + + if (! layer->p->modified) + { + gimp_image_undo_push_link_layer (image, NULL, layer); + + g_object_set (drawable, "modified", TRUE, NULL); + + gimp_image_undo_group_end (image); + } +} + +static void +gimp_link_layer_convert_type (GimpLayer *layer, + GimpImage *dest_image, + const Babl *new_format, + GimpColorProfile *src_profile, + GimpColorProfile *dest_profile, + GeglDitherMethod layer_dither_type, + GeglDitherMethod mask_dither_type, + gboolean push_undo, + GimpProgress *progress) +{ + GimpLinkLayer *link_layer = GIMP_LINK_LAYER (layer); + GimpImage *image = gimp_item_get_image (GIMP_ITEM (link_layer)); + + if (! link_layer->p->link || + link_layer->p->modified || + layer_dither_type != GEGL_DITHER_NONE) + { + GIMP_LAYER_CLASS (parent_class)->convert_type (layer, dest_image, + new_format, + src_profile, + dest_profile, + layer_dither_type, + mask_dither_type, + push_undo, + progress); + } + else + { + if (push_undo) + gimp_image_undo_push_link_layer (image, NULL, link_layer); + + gimp_link_layer_render (link_layer); + } +} + + +/* public functions */ + +/** + * gimp_link_layer_new: + * @image: the #GimpImage the layer should belong to + * @text: a #GimpText object + * + * Creates a new link layer. + * + * Return value: a new #GimpLinkLayer or %NULL in case of a problem + **/ +GimpLayer * +gimp_link_layer_new (GimpImage *image, + GimpLink *link) +{ + GimpLinkLayer *layer; + + g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL); + + layer = + GIMP_LINK_LAYER (gimp_drawable_new (GIMP_TYPE_LINK_LAYER, + image, NULL, + 0, 0, 1, 1, + gimp_image_get_layer_format (image, + TRUE))); + gimp_layer_set_mode (GIMP_LAYER (layer), + gimp_image_get_default_new_layer_mode (image), + FALSE); + if (! gimp_link_layer_set_link (layer, link, FALSE)) + { + g_object_unref (layer); + return NULL; + } + + return GIMP_LAYER (layer); +} + +GimpLink * +gimp_link_layer_get_link (GimpLinkLayer *layer) +{ + g_return_val_if_fail (GIMP_IS_LINK_LAYER (layer), NULL); + + return layer->p->link; +} + +gboolean +gimp_link_layer_set_link (GimpLinkLayer *layer, + GimpLink *link, + gboolean push_undo) +{ + gboolean rendered = FALSE; + + g_return_val_if_fail (GIMP_IS_LINK_LAYER (layer), FALSE); + g_return_val_if_fail (GIMP_IS_LINK (link), FALSE); + + /* TODO: look deeper into the link paths. */ + if (layer->p->link == link) + return ! gimp_link_is_broken (link, FALSE); + + if (gimp_item_is_attached (GIMP_ITEM (layer)) && push_undo) + gimp_image_undo_push_link_layer (gimp_item_get_image (GIMP_ITEM (layer)), + _("Set layer link"), layer); + + if (layer->p->link) + { + g_signal_handlers_disconnect_by_func (layer->p->link, + G_CALLBACK (gimp_link_layer_link_changed), + layer); + + g_clear_object (&layer->p->link); + } + + if (link) + { + layer->p->link = g_object_ref (link); + + g_signal_connect_object (link, "changed", + G_CALLBACK (gimp_link_layer_link_changed), + layer, G_CONNECT_SWAPPED); + + g_object_set (layer, "modified", FALSE, NULL); + rendered = gimp_link_layer_render (layer); + } + + g_object_notify (G_OBJECT (layer), "link"); + gimp_viewable_invalidate_preview (GIMP_VIEWABLE (layer)); + + return rendered; +} + +/** + * gimp_link_layer_discard: + * @layer: a #GimpLinkLayer + * + * Discards the link. This makes @layer behave like a + * normal layer. + */ +void +gimp_link_layer_discard (GimpLinkLayer *layer) +{ + g_return_if_fail (GIMP_IS_LINK_LAYER (layer)); + g_return_if_fail (gimp_item_is_attached (GIMP_ITEM (layer))); + + gimp_image_undo_push_link_layer (gimp_item_get_image (GIMP_ITEM (layer)), + _("Discard Link"), layer); + + layer->p->modified = TRUE; +} + +void +gimp_link_layer_monitor (GimpLinkLayer *layer) +{ + g_return_if_fail (GIMP_IS_LINK_LAYER (layer)); + g_return_if_fail (gimp_item_is_attached (GIMP_ITEM (layer))); + + gimp_image_undo_push_link_layer (gimp_item_get_image (GIMP_ITEM (layer)), + _("Monitor Link"), layer); + + layer->p->modified = FALSE; + gimp_link_layer_render (layer); +} + +gboolean +gimp_item_is_link_layer (GimpItem *item) +{ + return (GIMP_IS_LINK_LAYER (item) && + ! GIMP_LINK_LAYER (item)->p->modified); +} + +/* private functions */ + +static void +gimp_link_layer_link_changed (GimpLinkLayer *layer) +{ + if (! layer->p->modified) + gimp_link_layer_render (layer); +} + +static gboolean +gimp_link_layer_render (GimpLinkLayer *layer) +{ + GimpDrawable *drawable; + GimpItem *item; + GimpImage *image; + GeglBuffer *buffer; + gdouble xres; + gdouble yres; + gint width; + gint height; + + if (! layer->p->link) + return FALSE; + + drawable = GIMP_DRAWABLE (layer); + item = GIMP_ITEM (layer); + image = gimp_item_get_image (item); + + gimp_image_get_resolution (image, &xres, &yres); + /* TODO: I could imagine a GimpBusyBox (to be made as GimpProgress) in + * the later list showing layer update. + */ + buffer = gimp_link_get_buffer (layer->p->link, NULL, NULL); + + if (! buffer) + return FALSE; + + width = gegl_buffer_get_width (buffer); + height = gegl_buffer_get_height (buffer); + + g_object_freeze_notify (G_OBJECT (drawable)); + + if ((width != gimp_item_get_width (item) || + height != gimp_item_get_height (item))) + { + GeglBuffer *new_buffer; + + new_buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0, width, height), + gimp_drawable_get_format (drawable)); + gimp_drawable_set_buffer (drawable, FALSE, NULL, new_buffer); + g_object_unref (new_buffer); + + if (gimp_layer_get_mask (GIMP_LAYER (layer))) + { + GimpLayerMask *mask = gimp_layer_get_mask (GIMP_LAYER (layer)); + + static GimpContext *unused_eek = NULL; + + if (! unused_eek) + unused_eek = gimp_context_new (image->gimp, "eek", NULL); + + gimp_item_resize (GIMP_ITEM (mask), + unused_eek, GIMP_FILL_TRANSPARENT, + width, height, 0, 0); + } + } + + if (layer->p->auto_rename) + { + GimpItem *item = GIMP_ITEM (layer); + gchar *name = NULL; + + if (layer->p->link) + { + name = g_strdup (gimp_object_get_name (layer->p->link)); + } + + if (! name || ! name[0]) + { + g_free (name); + name = g_strdup (_("Link Layer")); + } + + if (gimp_item_is_attached (item)) + { + gimp_item_tree_rename_item (gimp_item_get_tree (item), item, + name, FALSE, NULL); + g_free (name); + } + else + { + gimp_object_take_name (GIMP_OBJECT (layer), name); + } + } + + gimp_link_layer_render_buffer (layer, buffer); + g_object_unref (buffer); + + g_object_thaw_notify (G_OBJECT (drawable)); + + gimp_drawable_update (drawable, 0, 0, width, height); + gimp_image_flush (image); + + return (width > 0 && height > 0); +} + +static void +gimp_link_layer_render_buffer (GimpLinkLayer *layer, + GeglBuffer *buffer) +{ + GimpDrawable *drawable = GIMP_DRAWABLE (layer); + GimpItem *item = GIMP_ITEM (layer); + GimpImage *image = gimp_item_get_image (item); + GimpColorTransform *transform; + + transform = gimp_image_get_color_transform_from_srgb_u8 (image); + if (transform) + gimp_color_transform_process_buffer (transform, + buffer, + NULL, + gimp_drawable_get_buffer (drawable), + NULL); + else + gimp_gegl_buffer_copy (buffer, NULL, GEGL_ABYSS_NONE, + gimp_drawable_get_buffer (drawable), NULL); +} diff --git a/app/core/gimplinklayer.h b/app/core/gimplinklayer.h new file mode 100644 index 0000000000..d13a48e360 --- /dev/null +++ b/app/core/gimplinklayer.h @@ -0,0 +1,66 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * GimpLinkLayer + * Copyright (C) 2019 Jehan + * + * 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 . + */ + +#ifndef __GIMP_LINK_LAYER_H__ +#define __GIMP_LINK_LAYER_H__ + + +#include "gimplayer.h" + + +#define GIMP_TYPE_LINK_LAYER (gimp_link_layer_get_type ()) +#define GIMP_LINK_LAYER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_LINK_LAYER, GimpLinkLayer)) +#define GIMP_LINK_LAYER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_LINK_LAYER, GimpLinkLayerClass)) +#define GIMP_IS_LINK_LAYER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_LINK_LAYER)) +#define GIMP_IS_LINK_LAYER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_LINK_LAYER)) +#define GIMP_LINK_LAYER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_LINK_LAYER, GimpLinkLayerClass)) + + +typedef struct _GimpLinkLayerClass GimpLinkLayerClass; +typedef struct _GimpLinkLayerPrivate GimpLinkLayerPrivate; + +struct _GimpLinkLayer +{ + GimpLayer layer; + + GimpLinkLayerPrivate *p; +}; + +struct _GimpLinkLayerClass +{ + GimpLayerClass parent_class; +}; + + +GType gimp_link_layer_get_type (void) G_GNUC_CONST; + +GimpLayer * gimp_link_layer_new (GimpImage *image, + GimpLink *link); + +GimpLink * gimp_link_layer_get_link (GimpLinkLayer *layer); +gboolean gimp_link_layer_set_link (GimpLinkLayer *layer, + GimpLink *link, + gboolean push_undo); + +void gimp_link_layer_discard (GimpLinkLayer *layer); +void gimp_link_layer_monitor (GimpLinkLayer *layer); +gboolean gimp_item_is_link_layer (GimpItem *item); + +#endif /* __GIMP_LINK_LAYER_H__ */ diff --git a/app/core/gimplinklayerundo.c b/app/core/gimplinklayerundo.c new file mode 100644 index 0000000000..52e828ddbd --- /dev/null +++ b/app/core/gimplinklayerundo.c @@ -0,0 +1,198 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * Copyright (C) 2019 Jehan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpbase/gimpbase.h" + +#include "core-types.h" + +#include "gimpimage.h" +#include "gimplayer.h" +#include "gimplink.h" +#include "gimplinklayer.h" +#include "gimplinklayerundo.h" + + +enum +{ + PROP_0, + PROP_PREV_MODIFIED, + PROP_PREV_LINK +}; + + +static void gimp_link_layer_undo_constructed (GObject *object); +static void gimp_link_layer_undo_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_link_layer_undo_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static gint64 gimp_link_layer_undo_get_memsize (GimpObject *object, + gint64 *gui_size); + +static void gimp_link_layer_undo_pop (GimpUndo *undo, + GimpUndoMode undo_mode, + GimpUndoAccumulator *accum); + + +G_DEFINE_TYPE (GimpLinkLayerUndo, gimp_link_layer_undo, GIMP_TYPE_ITEM_UNDO) + +#define parent_class gimp_link_layer_undo_parent_class + + +static void +gimp_link_layer_undo_class_init (GimpLinkLayerUndoClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpObjectClass *gimp_object_class = GIMP_OBJECT_CLASS (klass); + GimpUndoClass *undo_class = GIMP_UNDO_CLASS (klass); + + object_class->constructed = gimp_link_layer_undo_constructed; + object_class->set_property = gimp_link_layer_undo_set_property; + object_class->get_property = gimp_link_layer_undo_get_property; + + gimp_object_class->get_memsize = gimp_link_layer_undo_get_memsize; + + undo_class->pop = gimp_link_layer_undo_pop; + + g_object_class_install_property (object_class, PROP_PREV_MODIFIED, + g_param_spec_boolean ("prev-modified", NULL, NULL, + TRUE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_PREV_LINK, + g_param_spec_object ("prev-link", + NULL, NULL, + GIMP_TYPE_LINK, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); +} + +static void +gimp_link_layer_undo_init (GimpLinkLayerUndo *undo) +{ +} + +static void +gimp_link_layer_undo_constructed (GObject *object) +{ + GimpLinkLayerUndo *undo = GIMP_LINK_LAYER_UNDO (object); + GimpLinkLayer *layer; + + G_OBJECT_CLASS (parent_class)->constructed (object); + + gimp_assert (GIMP_IS_LINK_LAYER (GIMP_ITEM_UNDO (object)->item)); + + layer = GIMP_LINK_LAYER (GIMP_ITEM_UNDO (undo)->item); + + undo->link = gimp_link_layer_get_link (layer); + undo->modified = ! gimp_item_is_link_layer (GIMP_ITEM (layer)); +} + +static void +gimp_link_layer_undo_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpLinkLayerUndo *undo = GIMP_LINK_LAYER_UNDO (object); + + switch (property_id) + { + case PROP_PREV_MODIFIED: + undo->modified = g_value_get_boolean (value); + break; + case PROP_PREV_LINK: + undo->link = g_value_get_object (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_link_layer_undo_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpLinkLayerUndo *undo = GIMP_LINK_LAYER_UNDO (object); + + switch (property_id) + { + case PROP_PREV_MODIFIED: + g_value_set_boolean (value, undo->modified); + break; + case PROP_PREV_LINK: + g_value_set_object (value, undo->link); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static gint64 +gimp_link_layer_undo_get_memsize (GimpObject *object, + gint64 *gui_size) +{ + GimpItemUndo *item_undo = GIMP_ITEM_UNDO (object); + gint64 memsize = 0; + + if (! gimp_item_is_attached (item_undo->item)) + memsize += gimp_object_get_memsize (GIMP_OBJECT (item_undo->item), + gui_size); + + return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object, + gui_size); +} + +static void +gimp_link_layer_undo_pop (GimpUndo *undo, + GimpUndoMode undo_mode, + GimpUndoAccumulator *accum) +{ + GimpLinkLayerUndo *layer_undo = GIMP_LINK_LAYER_UNDO (undo); + GimpLinkLayer *layer = GIMP_LINK_LAYER (GIMP_ITEM_UNDO (undo)->item); + GimpLink *link; + gboolean modified; + + GIMP_UNDO_CLASS (parent_class)->pop (undo, undo_mode, accum); + + modified = ! gimp_item_is_link_layer (GIMP_ITEM (layer)); + link = gimp_link_layer_get_link (layer); + + gimp_link_layer_set_link (layer, layer_undo->link, FALSE); + g_object_set (layer, "modified", layer_undo->modified, NULL); + + layer_undo->modified = modified; + layer_undo->link = link; +} diff --git a/app/core/gimplinklayerundo.h b/app/core/gimplinklayerundo.h new file mode 100644 index 0000000000..7428bce3bf --- /dev/null +++ b/app/core/gimplinklayerundo.h @@ -0,0 +1,56 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * Copyright (C) 2019 Jehan + * + * 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 . + */ + +#ifndef __GIMP_LINK_LAYER_UNDO_H__ +#define __GIMP_LINK_LAYER_UNDO_H__ + + +#include "gimpitemundo.h" + + +#define GIMP_TYPE_LINK_LAYER_UNDO (gimp_link_layer_undo_get_type ()) +#define GIMP_LINK_LAYER_UNDO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_LINK_LAYER_UNDO, GimpLinkLayerUndo)) +#define GIMP_LINK_LAYER_UNDO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_LINK_LAYER_UNDO, GimpLinkLayerUndoClass)) +#define GIMP_IS_LINK_LAYER_UNDO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_LINK_LAYER_UNDO)) +#define GIMP_IS_LINK_LAYER_UNDO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_LINK_LAYER_UNDO)) +#define GIMP_LINK_LAYER_UNDO_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_LINK_LAYER_UNDO, GimpLinkLayerUndoClass)) + + +typedef struct _GimpLinkLayerUndo GimpLinkLayerUndo; +typedef struct _GimpLinkLayerUndoClass GimpLinkLayerUndoClass; + +struct _GimpLinkLayerUndo +{ + GimpItemUndo parent_instance; + + gboolean modified; + GimpLink *link; +}; + +struct _GimpLinkLayerUndoClass +{ + GimpItemUndoClass parent_class; +}; + + +GType gimp_link_layer_undo_get_type (void) G_GNUC_CONST; + + +#endif /* __GIMP_LINK_LAYER_UNDO_H__ */ + diff --git a/app/dialogs/item-options-dialog.c b/app/dialogs/item-options-dialog.c index 3413d7920d..f6d7f05b04 100644 --- a/app/dialogs/item-options-dialog.c +++ b/app/dialogs/item-options-dialog.c @@ -322,6 +322,21 @@ item_options_dialog_get_vbox (GtkWidget *dialog) return private->left_vbox; } +GtkWidget * +item_options_dialog_get_right_vbox (GtkWidget *dialog) +{ + ItemOptionsDialog *private; + + g_return_val_if_fail (GIMP_IS_VIEWABLE_DIALOG (dialog), NULL); + + private = g_object_get_data (G_OBJECT (dialog), + "item-options-dialog-private"); + + g_return_val_if_fail (private != NULL, NULL); + + return private->right_vbox; +} + GtkWidget * item_options_dialog_get_grid (GtkWidget *dialog, gint *next_row) diff --git a/app/dialogs/item-options-dialog.h b/app/dialogs/item-options-dialog.h index 15d7b2e205..1b56aee329 100644 --- a/app/dialogs/item-options-dialog.h +++ b/app/dialogs/item-options-dialog.h @@ -56,6 +56,7 @@ GtkWidget * item_options_dialog_new (GimpImage *image, gpointer user_data); GtkWidget * item_options_dialog_get_vbox (GtkWidget *dialog); +GtkWidget * item_options_dialog_get_right_vbox (GtkWidget *dialog); GtkWidget * item_options_dialog_get_grid (GtkWidget *dialog, gint *next_row); GtkWidget * item_options_dialog_get_name_entry (GtkWidget *dialog); diff --git a/app/dialogs/layer-options-dialog.c b/app/dialogs/layer-options-dialog.c index afcd354537..caf524baf1 100644 --- a/app/dialogs/layer-options-dialog.c +++ b/app/dialogs/layer-options-dialog.c @@ -32,12 +32,15 @@ #include "core/gimpdrawable-filters.h" #include "core/gimpimage.h" #include "core/gimplayer.h" +#include "core/gimplink.h" +#include "core/gimplinklayer.h" #include "text/gimptext.h" #include "text/gimptextlayer.h" #include "widgets/gimpcontainertreeview.h" #include "widgets/gimplayermodebox.h" +#include "widgets/gimpopendialog.h" #include "widgets/gimpviewabledialog.h" #include "item-options-dialog.h" @@ -50,6 +53,7 @@ typedef struct _LayerOptionsDialog LayerOptionsDialog; struct _LayerOptionsDialog { + Gimp *gimp; GimpLayer *layer; GimpLayerMode mode; GimpLayerColorSpace blend_space; @@ -68,6 +72,8 @@ struct _LayerOptionsDialog GtkWidget *composite_mode_combo; GtkWidget *size_se; GtkWidget *offset_se; + + GimpLink *link; }; @@ -94,6 +100,11 @@ static void layer_options_dialog_rename_toggled (GtkWidget *widget, LayerOptionsDialog *private); +static void layer_options_fill_changed (GtkWidget *combo, + GtkWidget *file_select); +static void layer_options_file_set (GtkFileChooserButton *widget, + LayerOptionsDialog *private); + /* public functions */ GtkWidget * @@ -127,6 +138,7 @@ layer_options_dialog_new (GimpImage *image, GtkWidget *grid; GtkListStore *space_model; GtkWidget *combo; + GtkWidget *file_select; GtkWidget *scale; GtkWidget *label; GtkAdjustment *adjustment; @@ -144,6 +156,7 @@ layer_options_dialog_new (GimpImage *image, private = g_slice_new0 (LayerOptionsDialog); + private->gimp = image->gimp; private->layer = layer; private->mode = layer_mode; private->blend_space = layer_blend_space; @@ -374,15 +387,35 @@ layer_options_dialog_new (GimpImage *image, if (! layer) { + GtkWidget *right_vbox = item_options_dialog_get_right_vbox (dialog); + GtkWidget *open_dialog; + /* The fill type */ combo = gimp_enum_combo_box_new (GIMP_TYPE_FILL_TYPE); - gimp_grid_attach_aligned (GTK_GRID (grid), 0, row++, + gimp_grid_attach_aligned (GTK_GRID (grid), 0, row, _("_Fill with:"), 0.0, 0.5, combo, 1); + + /* File chooser dialog. */ + open_dialog = gimp_open_dialog_new (private->gimp); + gtk_window_set_title (GTK_WINDOW (open_dialog), + _("Select Linked Image")); + + /* File chooser button. */ + file_select = gtk_file_chooser_button_new_with_dialog (open_dialog); + gtk_box_pack_end (GTK_BOX (right_vbox), file_select, FALSE, FALSE, 1); + gimp_int_combo_box_connect (GIMP_INT_COMBO_BOX (combo), private->fill_type, G_CALLBACK (gimp_int_combo_box_get_active), &private->fill_type, NULL); + g_signal_connect (combo, "changed", + G_CALLBACK (layer_options_fill_changed), + file_select); + g_signal_connect (file_select, "file-set", + G_CALLBACK (layer_options_file_set), + private); + layer_options_fill_changed (combo, file_select); } if (layer) @@ -402,6 +435,31 @@ layer_options_dialog_new (GimpImage *image, GIMP_VIEW_SIZE_SMALL, 0); gtk_container_add (GTK_CONTAINER (frame), view); gtk_widget_show (view); + + if (GIMP_IS_LINK_LAYER (layer)) + { + GtkWidget *open_dialog; + GimpLink *link; + + /* File chooser dialog. */ + open_dialog = gimp_open_dialog_new (private->gimp); + gtk_window_set_title (GTK_WINDOW (open_dialog), + _("Select Linked Image")); + + /* File chooser button. */ + file_select = gtk_file_chooser_button_new_with_dialog (open_dialog); + link = gimp_link_layer_get_link (GIMP_LINK_LAYER (layer)); + gtk_file_chooser_set_file (GTK_FILE_CHOOSER (file_select), + gimp_link_get_file (link), NULL); + gtk_widget_show (file_select); + gimp_grid_attach_aligned (GTK_GRID (grid), 0, row++, + _("_Linked image:"), 0.0, 0.5, + file_select, 1); + + g_signal_connect (file_select, "file-set", + G_CALLBACK (layer_options_file_set), + private); + } } button = item_options_dialog_get_lock_position (dialog); @@ -502,6 +560,7 @@ layer_options_dialog_callback (GtkWidget *dialog, private->composite_mode, private->opacity / 100.0, private->fill_type, + private->link, width, height, offset_x, @@ -574,3 +633,44 @@ layer_options_dialog_rename_toggled (GtkWidget *widget, } } } + +static void +layer_options_fill_changed (GtkWidget *combo, + GtkWidget *file_select) +{ + gint value = GIMP_FILL_FOREGROUND; + + g_return_if_fail (GIMP_IS_ENUM_COMBO_BOX (combo)); + g_return_if_fail (GTK_IS_WIDGET (file_select)); + + gimp_int_combo_box_get_active (GIMP_INT_COMBO_BOX (combo), &value); + gtk_widget_set_visible (file_select, (value == GIMP_FILL_LINK)); +} + +static void +layer_options_file_set (GtkFileChooserButton *widget, + LayerOptionsDialog *private) +{ + GimpLink *link = NULL; + GFile *file; + + file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (widget)); + if (file) + { + link = gimp_link_new (private->gimp, file); + if (gimp_link_is_broken (link, TRUE)) + { + g_clear_object (&link); + g_signal_handlers_block_by_func (widget, + G_CALLBACK (layer_options_file_set), + private); + gtk_file_chooser_unselect_file (GTK_FILE_CHOOSER (widget), file); + g_signal_handlers_unblock_by_func (widget, + G_CALLBACK (layer_options_file_set), + private); + } + } + g_clear_object (&file); + + private->link = link; +} diff --git a/app/dialogs/layer-options-dialog.h b/app/dialogs/layer-options-dialog.h index 0cac5e8013..5fbf9a2b84 100644 --- a/app/dialogs/layer-options-dialog.h +++ b/app/dialogs/layer-options-dialog.h @@ -30,6 +30,7 @@ typedef void (* GimpLayerOptionsCallback) (GtkWidget *dialog, GimpLayerCompositeMode layer_composite_mode, gdouble layer_opacity, GimpFillType layer_fill_type, + GimpLink *link, gint layer_width, gint layer_height, gint layer_offset_x, diff --git a/app/widgets/gimpviewrendererlayer.c b/app/widgets/gimpviewrendererlayer.c index 6c4c6cb5ee..f5266bf0fe 100644 --- a/app/widgets/gimpviewrendererlayer.c +++ b/app/widgets/gimpviewrendererlayer.c @@ -32,6 +32,7 @@ #include "core/gimp.h" #include "core/gimpcontainer.h" #include "core/gimpimage.h" +#include "core/gimplinklayer.h" #include "text/gimptextlayer.h" @@ -71,7 +72,8 @@ gimp_view_renderer_layer_render (GimpViewRenderer *renderer, { icon_name = GIMP_ICON_LAYER_FLOATING_SELECTION; } - else if (gimp_item_is_text_layer (GIMP_ITEM (renderer->viewable))) + else if (gimp_item_is_text_layer (GIMP_ITEM (renderer->viewable)) || + gimp_item_is_link_layer (GIMP_ITEM (renderer->viewable))) { icon_name = gimp_viewable_get_icon_name (renderer->viewable); } diff --git a/libgimpbase/gimpbaseenums.c b/libgimpbase/gimpbaseenums.c index eee17911a9..b7e5c0ef86 100644 --- a/libgimpbase/gimpbaseenums.c +++ b/libgimpbase/gimpbaseenums.c @@ -511,6 +511,7 @@ gimp_fill_type_get_type (void) { GIMP_FILL_WHITE, "GIMP_FILL_WHITE", "white" }, { GIMP_FILL_TRANSPARENT, "GIMP_FILL_TRANSPARENT", "transparent" }, { GIMP_FILL_PATTERN, "GIMP_FILL_PATTERN", "pattern" }, + { GIMP_FILL_LINK, "GIMP_FILL_LINK", "link" }, { 0, NULL, NULL } }; @@ -522,6 +523,7 @@ gimp_fill_type_get_type (void) { GIMP_FILL_WHITE, NC_("fill-type", "White"), NULL }, { GIMP_FILL_TRANSPARENT, NC_("fill-type", "Transparency"), NULL }, { GIMP_FILL_PATTERN, NC_("fill-type", "Pattern"), NULL }, + { GIMP_FILL_LINK, NC_("fill-type", "Image link"), NULL }, { 0, NULL, NULL } }; diff --git a/libgimpbase/gimpbaseenums.h b/libgimpbase/gimpbaseenums.h index 5960533e9f..72cc5529ae 100644 --- a/libgimpbase/gimpbaseenums.h +++ b/libgimpbase/gimpbaseenums.h @@ -368,6 +368,7 @@ typedef enum * @GIMP_FILL_WHITE: White * @GIMP_FILL_TRANSPARENT: Transparency * @GIMP_FILL_PATTERN: Pattern + * @GIMP_FILL_LINK: Image link * * Types of filling. **/ @@ -382,7 +383,8 @@ typedef enum GIMP_FILL_CIELAB_MIDDLE_GRAY, /*< desc="Middle Gray (CIELAB)" >*/ GIMP_FILL_WHITE, /*< desc="White" >*/ GIMP_FILL_TRANSPARENT, /*< desc="Transparency" >*/ - GIMP_FILL_PATTERN /*< desc="Pattern" >*/ + GIMP_FILL_PATTERN, /*< desc="Pattern" >*/ + GIMP_FILL_LINK /*< desc="Image link" >*/ } GimpFillType; diff --git a/menus/layers-menu.ui b/menus/layers-menu.ui index e780d64387..8027f58002 100644 --- a/menus/layers-menu.ui +++ b/menus/layers-menu.ui @@ -58,6 +58,10 @@ app.layers-merge-group app.layers-delete +
+ app.layers-link-discard + app.layers-link-monitor +
app.layers-text-discard app.layers-text-to-vectors diff --git a/pdb/enums.pl b/pdb/enums.pl index 8c4259522e..dab252f6f4 100644 --- a/pdb/enums.pl +++ b/pdb/enums.pl @@ -168,13 +168,15 @@ package Gimp::CodeGen::enums; header => 'libgimpbase/gimpbaseenums.h', symbols => [ qw(GIMP_FILL_FOREGROUND GIMP_FILL_BACKGROUND GIMP_FILL_CIELAB_MIDDLE_GRAY GIMP_FILL_WHITE - GIMP_FILL_TRANSPARENT GIMP_FILL_PATTERN) ], + GIMP_FILL_TRANSPARENT GIMP_FILL_PATTERN + GIMP_FILL_LINK) ], mapping => { GIMP_FILL_FOREGROUND => '0', GIMP_FILL_BACKGROUND => '1', GIMP_FILL_CIELAB_MIDDLE_GRAY => '2', GIMP_FILL_WHITE => '3', GIMP_FILL_TRANSPARENT => '4', - GIMP_FILL_PATTERN => '5' } + GIMP_FILL_PATTERN => '5', + GIMP_FILL_LINK => '6' } }, GimpForegroundExtractMode => { contig => 1, From 57609583b50e1cdc259cb5b50e14329cdfb067b6 Mon Sep 17 00:00:00 2001 From: Jehan Date: Mon, 8 Jul 2019 16:48:56 +0200 Subject: [PATCH 03/10] app: add saving/loading of link layers. --- app/core/gimpimage.c | 9 ++++ app/core/gimplinklayer.c | 95 ++++++++++++++++++++++++++++++++++++++++ app/core/gimplinklayer.h | 8 ++++ app/xcf/xcf-load.c | 29 +++++++++++- app/xcf/xcf-private.h | 3 +- app/xcf/xcf-save.c | 26 +++++++++++ 6 files changed, 168 insertions(+), 2 deletions(-) diff --git a/app/core/gimpimage.c b/app/core/gimpimage.c index 202fac626a..e83e9a98e2 100644 --- a/app/core/gimpimage.c +++ b/app/core/gimpimage.c @@ -71,6 +71,7 @@ #include "gimplayer-floating-selection.h" #include "gimplayermask.h" #include "gimplayerstack.h" +#include "gimplinklayer.h" #include "gimpmarshal.h" #include "gimppalette.h" #include "gimpparasitelist.h" @@ -3012,6 +3013,14 @@ gimp_image_get_xcf_version (GimpImage *image, */ version = MAX (23, version); } + + /* Need version 24 TODO for link layers. */ + if (GIMP_IS_LINK_LAYER (layer)) + { + ADD_REASON (g_strdup_printf (_("Link layers were added in %s"), + "GIMP TODO")); + version = MAX (14, version); + } } g_list_free (items); diff --git a/app/core/gimplinklayer.c b/app/core/gimplinklayer.c index 793ae18b1c..1858c51d72 100644 --- a/app/core/gimplinklayer.c +++ b/app/core/gimplinklayer.c @@ -49,6 +49,13 @@ #include "gimp-intl.h" +enum +{ + LINK_LAYER_XCF_NONE = 0, + LINK_LAYER_XCF_DONT_AUTO_RENAME = 1 << 0, + LINK_LAYER_XCF_MODIFIED = 1 << 1 +}; + enum { PROP_0, @@ -112,6 +119,8 @@ static gboolean gimp_link_layer_render (GimpLinkLayer *layer); static void gimp_link_layer_render_buffer (GimpLinkLayer *layer, GeglBuffer *buffer); +static void gimp_link_layer_set_xcf_flags (GimpLinkLayer *layer, + guint32 flags); G_DEFINE_TYPE_WITH_PRIVATE (GimpLinkLayer, gimp_link_layer, GIMP_TYPE_LAYER) @@ -523,6 +532,80 @@ gimp_item_is_link_layer (GimpItem *item) ! GIMP_LINK_LAYER (item)->p->modified); } +guint32 +gimp_link_layer_get_xcf_flags (GimpLinkLayer *link_layer) +{ + guint flags = 0; + + g_return_val_if_fail (GIMP_IS_LINK_LAYER (link_layer), 0); + + if (! link_layer->p->auto_rename) + flags |= LINK_LAYER_XCF_DONT_AUTO_RENAME; + + if (link_layer->p->modified) + flags |= LINK_LAYER_XCF_MODIFIED; + + return flags; +} + +/** + * gimp_link_layer_from_layer: + * @layer: a #GimpLayer object + * @link: a #GimpLink object + * @flags: flags as retrieved from the XCF file. + * + * Converts a standard #GimpLayer into a #GimpLinkLayer. + * The new link layer takes ownership of the @link. + * The old @layer object is freed and replaced in-place by the new + * #GimpLinkLayer. + * + * This is a hack similar to the one used to load text layers from XCF, + * since at first they are loaded as normal layers, and only later + * promoted to link layers when the corresponding property is read from + * the file. + **/ +void +gimp_link_layer_from_layer (GimpLayer **layer, + GimpLink *link, + guint32 flags) +{ + GimpLinkLayer *link_layer; + GimpDrawable *drawable; + + g_return_if_fail (GIMP_IS_LAYER (*layer)); + g_return_if_fail (GIMP_IS_LINK (link)); + + link_layer = g_object_new (GIMP_TYPE_LINK_LAYER, + "image", gimp_item_get_image (GIMP_ITEM (*layer)), + NULL); + + gimp_item_replace_item (GIMP_ITEM (link_layer), GIMP_ITEM (*layer)); + + drawable = GIMP_DRAWABLE (link_layer); + gimp_drawable_steal_buffer (drawable, GIMP_DRAWABLE (*layer)); + + gimp_layer_set_opacity (GIMP_LAYER (link_layer), + gimp_layer_get_opacity (*layer), FALSE); + gimp_layer_set_mode (GIMP_LAYER (link_layer), + gimp_layer_get_mode (*layer), FALSE); + gimp_layer_set_blend_space (GIMP_LAYER (link_layer), + gimp_layer_get_blend_space (*layer), FALSE); + gimp_layer_set_composite_space (GIMP_LAYER (link_layer), + gimp_layer_get_composite_space (*layer), FALSE); + gimp_layer_set_composite_mode (GIMP_LAYER (link_layer), + gimp_layer_get_composite_mode (*layer), FALSE); + gimp_layer_set_lock_alpha (GIMP_LAYER (link_layer), + gimp_layer_get_lock_alpha (*layer), FALSE); + + gimp_link_layer_set_link (link_layer, link, FALSE); + gimp_link_layer_set_xcf_flags (link_layer, flags); + + g_object_unref (link); + g_object_unref (*layer); + + *layer = GIMP_LAYER (link_layer); +} + /* private functions */ static void @@ -649,3 +732,15 @@ gimp_link_layer_render_buffer (GimpLinkLayer *layer, gimp_gegl_buffer_copy (buffer, NULL, GEGL_ABYSS_NONE, gimp_drawable_get_buffer (drawable), NULL); } + +static void +gimp_link_layer_set_xcf_flags (GimpLinkLayer *layer, + guint32 flags) +{ + g_return_if_fail (GIMP_IS_LINK_LAYER (layer)); + + g_object_set (layer, + "auto-rename", (flags & LINK_LAYER_XCF_DONT_AUTO_RENAME) == 0, + "modified", (flags & LINK_LAYER_XCF_MODIFIED) != 0, + NULL); +} diff --git a/app/core/gimplinklayer.h b/app/core/gimplinklayer.h index d13a48e360..379d7959c0 100644 --- a/app/core/gimplinklayer.h +++ b/app/core/gimplinklayer.h @@ -63,4 +63,12 @@ void gimp_link_layer_discard (GimpLinkLayer *layer); void gimp_link_layer_monitor (GimpLinkLayer *layer); gboolean gimp_item_is_link_layer (GimpItem *item); + +/* Only to be used for XCF loading/saving. */ + +guint32 gimp_link_layer_get_xcf_flags (GimpLinkLayer *layer); +void gimp_link_layer_from_layer (GimpLayer **layer, + GimpLink *link, + guint32 flags); + #endif /* __GIMP_LINK_LAYER_H__ */ diff --git a/app/xcf/xcf-load.c b/app/xcf/xcf-load.c index a530db281c..20d2e58217 100644 --- a/app/xcf/xcf-load.c +++ b/app/xcf/xcf-load.c @@ -59,6 +59,8 @@ #include "core/gimplayer-floating-selection.h" #include "core/gimplayer-new.h" #include "core/gimplayermask.h" +#include "core/gimplink.h" +#include "core/gimplinklayer.h" #include "core/gimpparasitelist.h" #include "core/gimpprogress.h" #include "core/gimpselection.h" @@ -131,7 +133,8 @@ static gboolean xcf_load_layer_props (XcfInfo *info, static gboolean xcf_check_layer_props (XcfInfo *info, GList **item_path, gboolean *is_group_layer, - gboolean *is_text_layer); + gboolean *is_text_layer, + gboolean *is_link_layer); static gboolean xcf_load_channel_props (XcfInfo *info, GimpImage *image, GimpChannel **channel, @@ -1961,6 +1964,30 @@ xcf_load_layer_props (XcfInfo *info, xcf_read_int32 (info, text_layer_flags, 1); break; + case PROP_LINK_LAYER_DATA: + { + GimpLink *link; + gchar *path; + guint32 flags; + gboolean is_selected_layer; + + xcf_read_int32 (info, &flags, 1); + xcf_read_string (info, &path, 1); + + link = gimp_link_new (info->gimp, g_file_new_for_path (path)); + g_free (path); + + is_selected_layer = (g_list_find (info->selected_layers, *layer ) != NULL); + if (is_selected_layer) + info->selected_layers = g_list_remove (info->selected_layers, *layer); + + gimp_link_layer_from_layer (layer, link, flags); + + if (is_selected_layer) + info->selected_layers = g_list_prepend (info->selected_layers, *layer); + } + break; + case PROP_GROUP_ITEM: { GimpLayer *group; diff --git a/app/xcf/xcf-private.h b/app/xcf/xcf-private.h index 944b0388c9..a65435207e 100644 --- a/app/xcf/xcf-private.h +++ b/app/xcf/xcf-private.h @@ -72,7 +72,8 @@ typedef enum PROP_SELECTED_PATH = 43, PROP_FILTER_REGION = 44, PROP_FILTER_ARGUMENT = 45, - PROP_FILTER_CLIP = 46 + PROP_FILTER_CLIP = 46, + PROP_LINK_LAYER_DATA = 47, } PropType; typedef enum diff --git a/app/xcf/xcf-save.c b/app/xcf/xcf-save.c index 9b42a44174..4c2143a068 100644 --- a/app/xcf/xcf-save.c +++ b/app/xcf/xcf-save.c @@ -54,6 +54,8 @@ #include "core/gimpitemlist.h" #include "core/gimplayer.h" #include "core/gimplayermask.h" +#include "core/gimplink.h" +#include "core/gimplinklayer.h" #include "core/gimplist.h" #include "core/gimpparasitelist.h" #include "core/gimpprogress.h" @@ -721,6 +723,9 @@ xcf_save_layer_props (XcfInfo *info, flags), ;); } + if (GIMP_IS_LINK_LAYER (layer)) + xcf_check_error (xcf_save_prop (info, image, PROP_LINK_LAYER_DATA, error, layer)); + if (gimp_viewable_get_children (GIMP_VIEWABLE (layer))) { gint32 flags = 0; @@ -1664,6 +1669,27 @@ xcf_save_prop (XcfInfo *info, } break; + case PROP_LINK_LAYER_DATA: + { + GimpLinkLayer *layer = va_arg (args, GimpLinkLayer *); + GFile *file; + const gchar *path; + guint32 flags; + + flags = gimp_link_layer_get_xcf_flags (layer); + file = gimp_link_get_file (gimp_link_layer_get_link (layer)); + path = g_file_peek_path (file); + + size = 4 + strlen (path) ? strlen (path) + 5 : 4; + + xcf_write_prop_type_check_error (info, prop_type); + xcf_write_int32_check_error (info, &size, 1); + + xcf_write_int32_check_error (info, &flags, 1); + xcf_write_string_check_error (info, (gchar **) &path, 1); + } + break; + case PROP_ITEM_PATH: { GList *path = va_arg (args, GList *); From 13b32ee87c4fedb9e2ead87967b259de01a2fce8 Mon Sep 17 00:00:00 2001 From: Jehan Date: Fri, 12 Jul 2019 14:33:48 +0200 Subject: [PATCH 04/10] app: salvage link layers of dimension 0. Similar to commit 06be074650ca5a6a4d5885ea87750cc57731cc02, but now for link layers. Their dimension are also determined by their contents so anyway let's be more resistant from some forms of file corruption. --- app/xcf/xcf-load.c | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/app/xcf/xcf-load.c b/app/xcf/xcf-load.c index 20d2e58217..f880459358 100644 --- a/app/xcf/xcf-load.c +++ b/app/xcf/xcf-load.c @@ -2089,13 +2089,15 @@ static gboolean xcf_check_layer_props (XcfInfo *info, GList **item_path, gboolean *is_group_layer, - gboolean *is_text_layer) + gboolean *is_text_layer, + gboolean *is_link_layer) { PropType prop_type; guint32 prop_size; g_return_val_if_fail (*is_group_layer == FALSE, FALSE); g_return_val_if_fail (*is_text_layer == FALSE, FALSE); + g_return_val_if_fail (*is_link_layer == TRUE, FALSE); while (TRUE) { @@ -2114,6 +2116,13 @@ xcf_check_layer_props (XcfInfo *info, return FALSE; break; + case PROP_LINK_LAYER_DATA: + *is_link_layer = TRUE; + + if (! xcf_skip_unknown_prop (info, prop_size)) + return FALSE; + break; + case PROP_GROUP_ITEM: case PROP_GROUP_ITEM_FLAGS: *is_group_layer = TRUE; @@ -3122,16 +3131,18 @@ xcf_load_layer (XcfInfo *info, { gboolean is_group_layer = FALSE; gboolean is_text_layer = FALSE; + gboolean is_link_layer = FALSE; goffset saved_pos; saved_pos = info->cp; /* Load item path and check if this is a group or text layer. */ - xcf_check_layer_props (info, item_path, &is_group_layer, &is_text_layer); - if ((is_text_layer || is_group_layer) && + xcf_check_layer_props (info, item_path, &is_group_layer, + &is_text_layer, &is_link_layer); + if ((is_text_layer || is_group_layer || is_link_layer) && xcf_seek_pos (info, saved_pos, NULL)) { /* Something is wrong, but leave a chance to the layer because - * anyway group and text layer depends on their contents. + * anyway group, text and link layer depends on their contents. */ width = height = 1; g_clear_pointer (item_path, g_list_free); From 861686b76d28df5af3c6197853cee2f0a29c9793 Mon Sep 17 00:00:00 2001 From: Jehan Date: Wed, 2 Oct 2019 21:45:45 +0200 Subject: [PATCH 05/10] app: port branch to meson. --- app/core/meson.build | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/core/meson.build b/app/core/meson.build index 36fead6b32..b20cac8db7 100644 --- a/app/core/meson.build +++ b/app/core/meson.build @@ -193,6 +193,9 @@ libappcore_sources = [ 'gimplayerstack.c', 'gimplayerundo.c', 'gimplineart.c', + 'gimplink.c', + 'gimplinklayer.c', + 'gimplinklayerundo.c', 'gimplist.c', 'gimpmaskundo.c', 'gimpmybrush-load.c', From 0717a896b4761165923c6d7bb627f5d6a0a76f3c Mon Sep 17 00:00:00 2001 From: Jehan Date: Mon, 20 Jul 2020 17:13:39 +0200 Subject: [PATCH 06/10] app: GimpLink should emit "changed" signal idly. Even though G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT should normally happen when we are *probably* at the last change of a set of changes, the keyword is clearly *probably* as I had 5 or 6 of these events when saving a single image. There is no need for our link layer to reload a same image that many times in under a second. Instead, just emit our "changed" signal in an idly source to avoid uselessly duplicating events. --- app/core/gimplink.c | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/app/core/gimplink.c b/app/core/gimplink.c index d1cff79a2a..7aa8495330 100644 --- a/app/core/gimplink.c +++ b/app/core/gimplink.c @@ -62,6 +62,7 @@ struct _GimpLinkPrivate GFileMonitor *monitor; gboolean broken; + guint idle_changed_source; }; static void gimp_link_finalize (GObject *object); @@ -79,6 +80,7 @@ static void gimp_link_file_changed (GFileMonitor *monitor, GFile *other_file, GFileMonitorEvent event_type, GimpLink *link); +static gboolean gimp_link_emit_changed (gpointer data); G_DEFINE_TYPE_WITH_PRIVATE (GimpLink, gimp_link, GIMP_TYPE_OBJECT) @@ -122,6 +124,8 @@ gimp_link_init (GimpLink *link) link->p->file = NULL; link->p->monitor = NULL; link->p->broken = TRUE; + + link->p->idle_changed_source = 0; } static void @@ -206,12 +210,18 @@ gimp_link_file_changed (GFileMonitor *monitor, switch (event_type) { case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT: + if (link->p->idle_changed_source == 0) + link->p->idle_changed_source = g_idle_add_full (G_PRIORITY_LOW, + gimp_link_emit_changed, + link, NULL); + break; case G_FILE_MONITOR_EVENT_CREATED: g_signal_emit (link, link_signals[CHANGED], 0); break; case G_FILE_MONITOR_EVENT_DELETED: link->p->broken = TRUE; break; + default: /* No need to signal for changes where nothing can be done anyway. * In particular a file deletion, the link is broken, yet we don't @@ -224,6 +234,17 @@ gimp_link_file_changed (GFileMonitor *monitor, } +static gboolean +gimp_link_emit_changed (gpointer data) +{ + GimpLink *link = GIMP_LINK (data); + + g_signal_emit (link, link_signals[CHANGED], 0); + link->p->idle_changed_source = 0; + + return G_SOURCE_REMOVE; +} + /* public functions */ /** From 5932d2c135045e6e86a6299c414b1e59bf56fd42 Mon Sep 17 00:00:00 2001 From: Jehan Date: Tue, 24 Jan 2023 21:38:12 +0100 Subject: [PATCH 07/10] app: fix branch after rebasing. return_if_no_layer() doesn't exist anymore. The 2 layers actions are now multi-layer aware. --- app/actions/layers-commands.c | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/app/actions/layers-commands.c b/app/actions/layers-commands.c index f490d654ca..0895adfa72 100644 --- a/app/actions/layers-commands.c +++ b/app/actions/layers-commands.c @@ -1101,11 +1101,16 @@ layers_link_discard_cmd_callback (GimpAction *action, gpointer data) { GimpImage *image; - GimpLayer *layer; - return_if_no_layer (image, layer, data); + GList *layers; + GList *iter; + return_if_no_layers (image, layers, data); - if (GIMP_IS_LINK_LAYER (layer)) - gimp_link_layer_discard (GIMP_LINK_LAYER (layer)); + gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_ITEM_PROPERTIES, + _("Discard Links")); + for (iter = layers; iter; iter = iter->next) + if (GIMP_IS_LINK_LAYER (iter->data)) + gimp_link_layer_discard (GIMP_LINK_LAYER (iter->data)); + gimp_image_undo_group_end (image); } void @@ -1114,11 +1119,16 @@ layers_link_monitor_cmd_callback (GimpAction *action, gpointer data) { GimpImage *image; - GimpLayer *layer; - return_if_no_layer (image, layer, data); + GList *layers; + GList *iter; + return_if_no_layers (image, layers, data); - if (GIMP_IS_LINK_LAYER (layer)) - gimp_link_layer_monitor (GIMP_LINK_LAYER (layer)); + gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_ITEM_PROPERTIES, + _("Monitor Links")); + for (iter = layers; iter; iter = iter->next) + if (GIMP_IS_LINK_LAYER (iter->data)) + gimp_link_layer_monitor (GIMP_LINK_LAYER (iter->data)); + gimp_image_undo_group_end (image); } void From 12dec68379d09867fbb96cfc4373256bf9a627a8 Mon Sep 17 00:00:00 2001 From: Jehan Date: Mon, 31 Mar 2025 18:41:09 +0200 Subject: [PATCH 08/10] app: more rebasing after GIMP 3.0 release. --- app/core/gimplink.c | 9 ++++++++- app/core/gimplinklayer.c | 26 ++------------------------ app/xcf/xcf-save.c | 10 +++++----- 3 files changed, 15 insertions(+), 30 deletions(-) diff --git a/app/core/gimplink.c b/app/core/gimplink.c index 7aa8495330..b952ba06e2 100644 --- a/app/core/gimplink.c +++ b/app/core/gimplink.c @@ -336,6 +336,7 @@ gimp_link_get_buffer (GimpLink *link, gimp_get_user_context (link->p->gimp), progress, link->p->file, + link->p->width, link->p->height, FALSE, NULL, /* XXX We might want interactive opening * for a first opening (when done through @@ -346,7 +347,13 @@ gimp_link_get_buffer (GimpLink *link, if (image && status == GIMP_PDB_SUCCESS) { - gimp_projection_finish_draw (gimp_image_get_projection (image)); + /* If we don't flush the projection first, the buffer may be empty. + * I do wonder if the flushing and updating of the link could + * not be multi-threaded with gimp_projection_flush() instead, + * then notifying the update through signals. For very heavy + * images, would it be a better UX? XXX + */ + gimp_projection_flush_now (gimp_image_get_projection (image), TRUE); buffer = gimp_pickable_get_buffer (GIMP_PICKABLE (image)); g_object_ref (buffer); } diff --git a/app/core/gimplinklayer.c b/app/core/gimplinklayer.c index 1858c51d72..2126dff489 100644 --- a/app/core/gimplinklayer.c +++ b/app/core/gimplinklayer.c @@ -116,8 +116,6 @@ static void gimp_link_layer_convert_type (GimpLayer *layer, static void gimp_link_layer_link_changed (GimpLinkLayer *layer); static gboolean gimp_link_layer_render (GimpLinkLayer *layer); -static void gimp_link_layer_render_buffer (GimpLinkLayer *layer, - GeglBuffer *buffer); static void gimp_link_layer_set_xcf_flags (GimpLinkLayer *layer, guint32 flags); @@ -701,7 +699,8 @@ gimp_link_layer_render (GimpLinkLayer *layer) } } - gimp_link_layer_render_buffer (layer, buffer); + gimp_gegl_buffer_copy (buffer, NULL, GEGL_ABYSS_NONE, + gimp_drawable_get_buffer (drawable), NULL); g_object_unref (buffer); g_object_thaw_notify (G_OBJECT (drawable)); @@ -712,27 +711,6 @@ gimp_link_layer_render (GimpLinkLayer *layer) return (width > 0 && height > 0); } -static void -gimp_link_layer_render_buffer (GimpLinkLayer *layer, - GeglBuffer *buffer) -{ - GimpDrawable *drawable = GIMP_DRAWABLE (layer); - GimpItem *item = GIMP_ITEM (layer); - GimpImage *image = gimp_item_get_image (item); - GimpColorTransform *transform; - - transform = gimp_image_get_color_transform_from_srgb_u8 (image); - if (transform) - gimp_color_transform_process_buffer (transform, - buffer, - NULL, - gimp_drawable_get_buffer (drawable), - NULL); - else - gimp_gegl_buffer_copy (buffer, NULL, GEGL_ABYSS_NONE, - gimp_drawable_get_buffer (drawable), NULL); -} - static void gimp_link_layer_set_xcf_flags (GimpLinkLayer *layer, guint32 flags) diff --git a/app/xcf/xcf-save.c b/app/xcf/xcf-save.c index 4c2143a068..390bc0de59 100644 --- a/app/xcf/xcf-save.c +++ b/app/xcf/xcf-save.c @@ -724,7 +724,7 @@ xcf_save_layer_props (XcfInfo *info, } if (GIMP_IS_LINK_LAYER (layer)) - xcf_check_error (xcf_save_prop (info, image, PROP_LINK_LAYER_DATA, error, layer)); + xcf_check_error (xcf_save_prop (info, image, PROP_LINK_LAYER_DATA, error, layer), ;); if (gimp_viewable_get_children (GIMP_VIEWABLE (layer))) { @@ -1682,11 +1682,11 @@ xcf_save_prop (XcfInfo *info, size = 4 + strlen (path) ? strlen (path) + 5 : 4; - xcf_write_prop_type_check_error (info, prop_type); - xcf_write_int32_check_error (info, &size, 1); + xcf_write_prop_type_check_error (info, prop_type, va_end (args)); + xcf_write_int32_check_error (info, &size, 1, va_end (args)); - xcf_write_int32_check_error (info, &flags, 1); - xcf_write_string_check_error (info, (gchar **) &path, 1); + xcf_write_int32_check_error (info, &flags, 1, va_end (args)); + xcf_write_string_check_error (info, (gchar **) &path, 1, va_end (args)); } break; From c6bc26914fa4686dd7a9c28cca05f5b192515a57 Mon Sep 17 00:00:00 2001 From: Jehan Date: Wed, 29 Jul 2020 19:17:04 +0200 Subject: [PATCH 09/10] app: properly handle vector images as link layers. This commit was edited after GIMP 3.0, now that we have dedicated support for loading vector images with GimpVectorLoadProcedure. - By default, when loaded as GimpLinkLayer, vector images are loaded at a size so that they are exactly contained in the image. - When scaled with Scale Layer dialog, link layers of type vector are re-loaded from the source file to always stay perfectly crisp. --- app/actions/file-commands.c | 2 +- app/actions/layers-commands.c | 1 + app/core/gimpimagefile.c | 2 +- app/core/gimplink.c | 39 ++++++++++++++++- app/core/gimplink.h | 5 +++ app/core/gimplinklayer.c | 72 ++++++++++++++++++++++++++++++++ app/file/file-open.c | 7 +++- app/file/file-open.h | 1 + app/tests/test-save-and-export.c | 4 ++ app/tests/test-xcf.c | 1 + 10 files changed, 129 insertions(+), 5 deletions(-) diff --git a/app/actions/file-commands.c b/app/actions/file-commands.c index c5b939f397..8236dc8a95 100644 --- a/app/actions/file-commands.c +++ b/app/actions/file-commands.c @@ -843,7 +843,7 @@ file_revert_confirm_response (GtkWidget *dialog, GIMP_PROGRESS (display), file, 0, 0, FALSE, NULL, GIMP_RUN_INTERACTIVE, - &status, NULL, &error); + NULL, &status, NULL, &error); if (new_image) { diff --git a/app/actions/layers-commands.c b/app/actions/layers-commands.c index 0895adfa72..3e8181296f 100644 --- a/app/actions/layers-commands.c +++ b/app/actions/layers-commands.c @@ -2390,6 +2390,7 @@ layers_new_callback (GtkWidget *dialog, { if (link) { + gimp_link_set_size (link, layer_width, layer_height); layer = gimp_link_layer_new (image, link); if (layer_mode != gimp_layer_get_mode (layer)) diff --git a/app/core/gimpimagefile.c b/app/core/gimpimagefile.c index 528b112cd6..488afd976f 100644 --- a/app/core/gimpimagefile.c +++ b/app/core/gimpimagefile.c @@ -516,7 +516,7 @@ gimp_imagefile_create_thumbnail (GimpImagefile *imagefile, private->file, size, size, FALSE, NULL, GIMP_RUN_NONINTERACTIVE, - &status, &mime_type, error); + NULL, &status, &mime_type, error); if (image) gimp_thumbnail_set_info_from_image (private->thumbnail, diff --git a/app/core/gimplink.c b/app/core/gimplink.c index b952ba06e2..0a079c4839 100644 --- a/app/core/gimplink.c +++ b/app/core/gimplink.c @@ -63,6 +63,10 @@ struct _GimpLinkPrivate gboolean broken; guint idle_changed_source; + + gboolean is_vector; + gint width; + gint height; }; static void gimp_link_finalize (GObject *object); @@ -124,6 +128,8 @@ gimp_link_init (GimpLink *link) link->p->file = NULL; link->p->monitor = NULL; link->p->broken = TRUE; + link->p->width = 0; + link->p->height = 0; link->p->idle_changed_source = 0; } @@ -178,13 +184,17 @@ gimp_link_set_property (GObject *object, case PROP_FILE: if (link->p->file) g_object_unref (link->p->file); - link->p->file = g_value_dup_object (value); if (link->p->monitor) g_object_unref (link->p->monitor); + + link->p->is_vector = FALSE; + link->p->file = g_value_dup_object (value); + if (link->p->file) { - gchar *basename = g_file_get_basename (link->p->file); + gchar *basename; + basename = g_file_get_basename (link->p->file); link->p->monitor = g_file_monitor_file (link->p->file, G_FILE_MONITOR_NONE, NULL, NULL); g_signal_connect (link->p->monitor, "changed", G_CALLBACK (gimp_link_file_changed), @@ -319,6 +329,30 @@ gimp_link_duplicate (GimpLink *link) return gimp_link_new (link->p->gimp, link->p->file); } +void +gimp_link_set_size (GimpLink *link, + gint width, + gint height) +{ + link->p->width = width; + link->p->height = height; +} + +void +gimp_link_get_size (GimpLink *link, + gint *width, + gint *height) +{ + *width = link->p->width; + *height = link->p->height; +} + +gboolean +gimp_link_is_vector (GimpLink *link) +{ + return link->p->is_vector; +} + GeglBuffer * gimp_link_get_buffer (GimpLink *link, GimpProgress *progress, @@ -343,6 +377,7 @@ gimp_link_get_buffer (GimpLink *link, * GUI), but not for every re-render. */ GIMP_RUN_NONINTERACTIVE, + &link->p->is_vector, &status, &mime_type, error); if (image && status == GIMP_PDB_SUCCESS) diff --git a/app/core/gimplink.h b/app/core/gimplink.h index 6b19a74e19..c62d3c557c 100644 --- a/app/core/gimplink.h +++ b/app/core/gimplink.h @@ -64,9 +64,14 @@ gboolean gimp_link_is_broken (GimpLink *link, gboolean recheck); GimpLink * gimp_link_duplicate (GimpLink *link); +void gimp_link_set_size (GimpLink *link, + gint width, + gint height); void gimp_link_get_size (GimpLink *link, gint *width, gint *height); +gboolean gimp_link_is_vector (GimpLink *link); + GeglBuffer * gimp_link_get_buffer (GimpLink *link, GimpProgress *progress, GError **error); diff --git a/app/core/gimplinklayer.c b/app/core/gimplinklayer.c index 2126dff489..1c818ce833 100644 --- a/app/core/gimplinklayer.c +++ b/app/core/gimplinklayer.c @@ -42,6 +42,8 @@ #include "gimpimage-undo.h" #include "gimpimage-undo-push.h" #include "gimpitemtree.h" +#include "gimpobjectqueue.h" +#include "gimpprogress.h" #include "gimplink.h" #include "gimplinklayer.h" @@ -90,6 +92,13 @@ static gboolean gimp_link_layer_rename (GimpItem *item, const gchar *new_name, const gchar *undo_desc, GError **error); +static void gimp_link_layer_scale (GimpItem *item, + gint new_width, + gint new_height, + gint new_offset_x, + gint new_offset_y, + GimpInterpolationType interpolation_type, + GimpProgress *progress); static void gimp_link_layer_set_buffer (GimpDrawable *drawable, gboolean push_undo, @@ -145,6 +154,7 @@ gimp_link_layer_class_init (GimpLinkLayerClass *klass) item_class->duplicate = gimp_link_layer_duplicate; item_class->rename = gimp_link_layer_rename; + item_class->scale = gimp_link_layer_scale; item_class->default_name = _("Link Layer"); item_class->rename_desc = _("Rename Link Layer"); @@ -306,6 +316,68 @@ gimp_link_layer_rename (GimpItem *item, return FALSE; } +static void +gimp_link_layer_scale (GimpItem *item, + gint new_width, + gint new_height, + gint new_offset_x, + gint new_offset_y, + GimpInterpolationType interpolation_type, + GimpProgress *progress) +{ + GimpLinkLayer *link_layer = GIMP_LINK_LAYER (item); + GimpLayer *layer = GIMP_LAYER (item); + GimpObjectQueue *queue = NULL; + + if (progress && layer->mask) + { + GimpLayerMask *mask; + + queue = gimp_object_queue_new (progress); + progress = GIMP_PROGRESS (queue); + + /* temporarily set layer->mask to NULL, so that its size won't be counted + * when pushing the layer to the queue. + */ + mask = layer->mask; + layer->mask = NULL; + + gimp_object_queue_push (queue, layer); + gimp_object_queue_push (queue, mask); + + layer->mask = mask; + } + + if (queue) + gimp_object_queue_pop (queue); + + if (link_layer->p->modified || ! gimp_link_is_vector (link_layer->p->link)) + { + GIMP_ITEM_CLASS (parent_class)->scale (GIMP_ITEM (layer), + new_width, new_height, + new_offset_x, new_offset_y, + interpolation_type, progress); + } + else + { + gimp_link_set_size (link_layer->p->link, new_width, new_height); + gimp_link_layer_render (link_layer); + } + + if (layer->mask) + { + if (queue) + gimp_object_queue_pop (queue); + + gimp_item_scale (GIMP_ITEM (layer->mask), + new_width, new_height, + new_offset_x, new_offset_y, + interpolation_type, progress); + } + + g_clear_object (&queue); +} + static void gimp_link_layer_set_buffer (GimpDrawable *drawable, gboolean push_undo, diff --git a/app/file/file-open.c b/app/file/file-open.c index 75337234b5..cc9f8202f1 100644 --- a/app/file/file-open.c +++ b/app/file/file-open.c @@ -78,6 +78,7 @@ file_open_image (Gimp *gimp, gboolean as_new, GimpPlugInProcedure *file_proc, GimpRunMode run_mode, + gboolean *file_proc_handles_vector, GimpPDBStatusType *status, const gchar **mime_type, GError **error) @@ -212,6 +213,9 @@ file_open_image (Gimp *gimp, return NULL; } + if (file_proc_handles_vector) + *file_proc_handles_vector = file_proc->handles_vector; + if (progress) g_object_add_weak_pointer (G_OBJECT (progress), (gpointer) &progress); @@ -536,6 +540,7 @@ file_open_with_proc_and_display (Gimp *gimp, as_new, file_proc, run_mode, + NULL, status, &mime_type, error); @@ -638,7 +643,7 @@ file_open_layers (Gimp *gimp, FALSE, file_proc, run_mode, - status, &mime_type, error); + NULL, status, &mime_type, error); if (new_image) { diff --git a/app/file/file-open.h b/app/file/file-open.h index 600af8b58a..2c50571fa6 100644 --- a/app/file/file-open.h +++ b/app/file/file-open.h @@ -30,6 +30,7 @@ GimpImage * file_open_image (Gimp *gimp, gboolean as_new, GimpPlugInProcedure *file_proc, GimpRunMode run_mode, + gboolean *file_proc_handles_vector, GimpPDBStatusType *status, const gchar **mime_type, GError **error); diff --git a/app/tests/test-save-and-export.c b/app/tests/test-save-and-export.c index 73e1d36193..ffaf325252 100644 --- a/app/tests/test-save-and-export.c +++ b/app/tests/test-save-and-export.c @@ -122,6 +122,7 @@ opened_xcf_file_files (gconstpointer data) FALSE /*as_new*/, NULL /*file_proc*/, GIMP_RUN_NONINTERACTIVE, + NULL, /* file_proc_handles_vector */ &status, NULL /*mime_type*/, NULL /*error*/); @@ -163,6 +164,7 @@ imported_file_files (gconstpointer data) FALSE /*as_new*/, NULL /*file_proc*/, GIMP_RUN_NONINTERACTIVE, + NULL, /* file_proc_handles_vector */ &status, NULL /*mime_type*/, NULL /*error*/); @@ -212,6 +214,7 @@ saved_imported_file_files (gconstpointer data) FALSE /*as_new*/, NULL /*file_proc*/, GIMP_RUN_NONINTERACTIVE, + NULL, /* file_proc_handles_vector */ &status, NULL /*mime_type*/, NULL /*error*/); @@ -320,6 +323,7 @@ clear_import_file_after_export (gconstpointer data) FALSE /*as_new*/, NULL /*file_proc*/, GIMP_RUN_NONINTERACTIVE, + NULL, /* file_proc_handles_vector */ &status, NULL /*mime_type*/, NULL /*error*/); diff --git a/app/tests/test-xcf.c b/app/tests/test-xcf.c index c4138bf583..161bd6caa3 100644 --- a/app/tests/test-xcf.c +++ b/app/tests/test-xcf.c @@ -282,6 +282,7 @@ gimp_test_load_image (Gimp *gimp, FALSE /*as_new*/, proc, GIMP_RUN_NONINTERACTIVE, + NULL, /* file_proc_handles_vector */ &unused /*status*/, NULL /*mime_type*/, NULL /*error*/); From 74ee2fd4f32509f309944860f783ef5cf97339c1 Mon Sep 17 00:00:00 2001 From: Jehan Date: Mon, 31 Mar 2025 22:36:34 +0200 Subject: [PATCH 10/10] app: scale linked raster images back from source file. Unless other types of edits have been done on a link layer (e.g. painting), we avoid quality loss by multiple scaling of raster link layers by always re-scaling from the source file. --- app/core/gimplinklayer.c | 48 ++++++++++++++++++++++++++++++++++------ 1 file changed, 41 insertions(+), 7 deletions(-) diff --git a/app/core/gimplinklayer.c b/app/core/gimplinklayer.c index 1c818ce833..e38653c013 100644 --- a/app/core/gimplinklayer.c +++ b/app/core/gimplinklayer.c @@ -63,13 +63,15 @@ enum PROP_0, PROP_LINK, PROP_AUTO_RENAME, - PROP_MODIFIED + PROP_MODIFIED, + PROP_SCALED_ONLY }; struct _GimpLinkLayerPrivate { GimpLink *link; gboolean modified; + gboolean scaled_only; gboolean auto_rename; }; @@ -187,6 +189,12 @@ gimp_link_layer_class_init (GimpLinkLayerClass *klass) NULL, NULL, FALSE, GIMP_PARAM_STATIC_STRINGS); + + GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_SCALED_ONLY, + "scaled-only", + NULL, NULL, + FALSE, + GIMP_PARAM_STATIC_STRINGS); } static void @@ -225,6 +233,9 @@ gimp_link_layer_get_property (GObject *object, case PROP_MODIFIED: g_value_set_boolean (value, layer->p->modified); break; + case PROP_SCALED_ONLY: + g_value_set_boolean (value, layer->p->scaled_only); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); @@ -251,6 +262,9 @@ gimp_link_layer_set_property (GObject *object, case PROP_MODIFIED: layer->p->modified = g_value_get_boolean (value); break; + case PROP_SCALED_ONLY: + layer->p->scaled_only = g_value_get_boolean (value); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); @@ -351,17 +365,36 @@ gimp_link_layer_scale (GimpItem *item, if (queue) gimp_object_queue_pop (queue); - if (link_layer->p->modified || ! gimp_link_is_vector (link_layer->p->link)) + if (gimp_link_is_vector (link_layer->p->link) && ! link_layer->p->modified) { + /* Non-modified vector images are always recomputed from the + * source file and therefore are always sharp. + */ + gimp_link_set_size (link_layer->p->link, new_width, new_height); + gimp_link_layer_render (link_layer); + } + else + { + gboolean scaled_only = FALSE; + + if (! link_layer->p->modified || link_layer->p->scaled_only) + { + /* Raster images whose only modification are previous scaling + * are scaled back from the source file. Though they are still + * considered "modified" after a scaling (unlike vector + * images) and therefore are demoted to work like normal + * layers, scaling is still special-cased for better image + * quality. + */ + gimp_link_layer_render (link_layer); + scaled_only = TRUE; + } + GIMP_ITEM_CLASS (parent_class)->scale (GIMP_ITEM (layer), new_width, new_height, new_offset_x, new_offset_y, interpolation_type, progress); - } - else - { - gimp_link_set_size (link_layer->p->link, new_width, new_height); - gimp_link_layer_render (link_layer); + g_object_set (layer, "scaled-only", scaled_only, NULL); } if (layer->mask) @@ -402,6 +435,7 @@ gimp_link_layer_set_buffer (GimpDrawable *drawable, gimp_image_undo_push_link_layer (image, NULL, layer); g_object_set (drawable, "modified", TRUE, NULL); + g_object_set (drawable, "scaled-only", FALSE, NULL); gimp_image_undo_group_end (image); }