From 1ca6faab0d1100c58da0d15198ee12ea80d77cc3 Mon Sep 17 00:00:00 2001 From: Jehan Date: Thu, 9 Oct 2025 19:45:10 +0200 Subject: [PATCH] app, menus: new actions layers-rasterize and layers-retrieve. These 2 new actions are meant to be usable on all 3 types of non-raster (and non-group) layers, i.e. link, text and vector layers, to respectively rasterize and un-rasterize them. This will also work with multiple selected layers, and is not specific to one type of layers. I also change how gimp_text_layer_discard() used to work, by marking the text layer as modified instead of actually discarding all text information. The main consequence of this was that a layer rasterized this way was forever lost. Now it can actually be revived as a text layer, not only through the new layers-retrieve action, but also by trying to edit it with the Text tool, which will trigger the same dialog as when a text layer had been rasterized by editing it with a paint tool. Whereas the label of "layers-rasterize" seem to be quite straightforward (simply "Rasterize" per discussions in gimp-ux#252), I am really unsure of the label for "layers-retrieve". Further UX discussions should help on this front. --- app/actions/layers-actions.c | 93 +++++++++++++++++++++-------------- app/actions/layers-commands.c | 75 ++++++++++------------------ app/actions/layers-commands.h | 12 ++--- app/core/gimpitem.c | 25 ++++++++++ app/core/gimpitem.h | 3 ++ app/text/gimptextlayer.c | 43 +++++++++++++--- app/text/gimptextlayer.h | 1 + menus/image-menu.ui.in.in | 20 ++++---- menus/layers-menu.ui | 12 ++--- 9 files changed, 164 insertions(+), 120 deletions(-) diff --git a/app/actions/layers-actions.c b/app/actions/layers-actions.c index 982e6c72dc..a99c08b4b4 100644 --- a/app/actions/layers-actions.c +++ b/app/actions/layers-actions.c @@ -185,19 +185,31 @@ static const GimpActionEntry layers_actions[] = { "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, + layers_rasterize_cmd_callback, + GIMP_HELP_LAYER_TEXT_DISCARD }, + + { "layers-rasterize", GIMP_ICON_TOOL_TEXT, + NC_("layers-action", "_Rasterize"), NULL, { NULL }, + NC_("layers-action", "Turn selected text, link or vector layers into raster layers"), + layers_rasterize_cmd_callback, + GIMP_HELP_LAYER_TEXT_DISCARD }, + + { "layers-retrieve", GIMP_ICON_TOOL_TEXT, + NC_("layers-action", "_Retrieve Layers Information"), NULL, { NULL }, + NC_("layers-action", "Turn rasterized layers back into a text, link or vector layers"), + layers_retrieve_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, + layers_retrieve_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"), - layers_text_discard_cmd_callback, + layers_rasterize_cmd_callback, GIMP_HELP_LAYER_TEXT_DISCARD }, { "layers-text-to-path", GIMP_ICON_TOOL_TEXT, @@ -221,7 +233,7 @@ static const GimpActionEntry layers_actions[] = { "layers-vector-discard", NULL, NC_("layers-action", "Discard Vector Information"), NULL, { NULL }, NC_("layers-action", "Turn this vector layer into a normal layer"), - layers_vector_discard_cmd_callback, + layers_rasterize_cmd_callback, GIMP_HELP_LAYER_VECTOR_DISCARD }, { "layers-resize", GIMP_ICON_OBJECT_RESIZE, @@ -779,39 +791,41 @@ void layers_actions_update (GimpActionGroup *group, gpointer data) { - GimpImage *image = action_data_get_image (data); - GList *layers = NULL; - GList *iter = NULL; - GimpLayer *layer = NULL; - gboolean fs = FALSE; /* floating sel */ - gboolean ac = FALSE; /* Has selected channels */ - gboolean sel = FALSE; - gboolean indexed = FALSE; /* is indexed */ - gboolean lock_alpha = TRUE; - gboolean can_lock_alpha = FALSE; - gboolean text_layer = FALSE; - gboolean vector_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. */ - gboolean next_mode = TRUE; - gboolean prev_mode = TRUE; - gboolean last_mode = FALSE; - gboolean first_mode = FALSE; + GimpImage *image = action_data_get_image (data); + GList *layers = NULL; + GList *iter = NULL; + GimpLayer *layer = NULL; + gboolean fs = FALSE; /* floating sel */ + gboolean ac = FALSE; /* Has selected channels */ + gboolean sel = FALSE; + gboolean indexed = FALSE; /* is indexed */ + gboolean lock_alpha = TRUE; + gboolean can_lock_alpha = FALSE; + gboolean has_rasterizable = FALSE; + gboolean has_rasterized = FALSE; + gboolean text_layer = FALSE; + gboolean vector_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. */ + gboolean next_mode = TRUE; + gboolean prev_mode = TRUE; + gboolean last_mode = FALSE; + gboolean first_mode = FALSE; - gboolean first_selected = FALSE; /* First layer is selected */ - gboolean last_selected = FALSE; /* Last layer is selected */ + gboolean first_selected = FALSE; /* First layer is selected */ + gboolean last_selected = FALSE; /* Last layer is selected */ - gboolean have_masks = FALSE; /* At least 1 selected layer has a mask. */ - gboolean have_no_masks = FALSE; /* At least 1 selected layer has no mask. */ - gboolean have_groups = FALSE; /* At least 1 selected layer is a group. */ - gboolean have_no_groups = FALSE; /* At least 1 selected layer is not a group. */ - gboolean have_writable = FALSE; /* At least 1 selected layer has no contents lock. */ - gboolean have_prev = FALSE; /* At least 1 selected layer has a previous sibling. */ - gboolean have_next = FALSE; /* At least 1 selected layer has a next sibling. */ - gboolean have_alpha = FALSE; /* At least 1 selected layer has an alpha channel. */ - gboolean have_no_alpha = FALSE; /* At least 1 selected layer has no alpha channel. */ + gboolean have_masks = FALSE; /* At least 1 selected layer has a mask. */ + gboolean have_no_masks = FALSE; /* At least 1 selected layer has no mask. */ + gboolean have_groups = FALSE; /* At least 1 selected layer is a group. */ + gboolean have_no_groups = FALSE; /* At least 1 selected layer is not a group. */ + gboolean have_writable = FALSE; /* At least 1 selected layer has no contents lock. */ + gboolean have_prev = FALSE; /* At least 1 selected layer has a previous sibling. */ + gboolean have_next = FALSE; /* At least 1 selected layer has a next sibling. */ + gboolean have_alpha = FALSE; /* At least 1 selected layer has an alpha channel. */ + gboolean have_no_alpha = FALSE; /* At least 1 selected layer has no alpha channel. */ gboolean all_visible = TRUE; gboolean all_next_visible = TRUE; @@ -954,6 +968,9 @@ layers_actions_update (GimpActionGroup *group, if (GIMP_IS_TEXT_LAYER (iter->data)) n_text_layers++; + + has_rasterizable = has_rasterizable || gimp_item_is_rasterizable (iter->data); + has_rasterized = has_rasterized || gimp_item_is_rasterized (iter->data); } if (n_selected_layers == 1) @@ -1014,8 +1031,7 @@ layers_actions_update (GimpActionGroup *group, text_layer = gimp_item_is_text_layer (GIMP_ITEM (layer)); vector_layer = gimp_item_is_vector_layer (GIMP_ITEM (layer)); - if (GIMP_IS_LINK_LAYER (layer)) - link_layer = gimp_link_layer_is_monitored (GIMP_LINK_LAYER (layer)); + link_layer = gimp_item_is_link_layer (GIMP_ITEM (layer)); } } @@ -1077,6 +1093,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-rasterize", has_rasterizable); + SET_VISIBLE ("layers-retrieve", has_rasterized); + SET_VISIBLE ("layers-text-discard", n_text_layers > 0 && !ac); SET_VISIBLE ("layers-text-to-path", n_text_layers > 0 && !ac); SET_VISIBLE ("layers-text-along-path", text_layer && !ac); diff --git a/app/actions/layers-commands.c b/app/actions/layers-commands.c index ad78ff059b..d5d833b54e 100644 --- a/app/actions/layers-commands.c +++ b/app/actions/layers-commands.c @@ -1106,56 +1106,54 @@ layers_delete_cmd_callback (GimpAction *action, } void -layers_link_discard_cmd_callback (GimpAction *action, - GVariant *value, - gpointer data) +layers_rasterize_cmd_callback (GimpAction *action, + GVariant *value, + gpointer data) { GimpImage *image; GList *layers; GList *iter; + return_if_no_layers (image, layers, data); gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_ITEM_PROPERTIES, - _("Discard Links")); + _("Rasterize Layers")); + for (iter = layers; iter; iter = iter->next) - if (GIMP_IS_LINK_LAYER (iter->data)) - gimp_link_layer_discard (GIMP_LINK_LAYER (iter->data)); + { + if (gimp_item_is_link_layer (iter->data)) + gimp_link_layer_discard (GIMP_LINK_LAYER (iter->data)); + else if (gimp_item_is_text_layer (iter->data)) + gimp_text_layer_discard (GIMP_TEXT_LAYER (iter->data)); + else if (gimp_item_is_vector_layer (iter->data)) + gimp_vector_layer_discard (GIMP_VECTOR_LAYER (iter->data)); + } + gimp_image_undo_group_end (image); } void -layers_link_monitor_cmd_callback (GimpAction *action, - GVariant *value, - gpointer data) +layers_retrieve_cmd_callback (GimpAction *action, + GVariant *value, + gpointer data) { GimpImage *image; GList *layers; GList *iter; + return_if_no_layers (image, layers, data); 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); -} + _("Retrieve Layers Information")); -void -layers_text_discard_cmd_callback (GimpAction *action, - GVariant *value, - gpointer data) -{ - GimpImage *image; - GList *layers; - GList *iter; - return_if_no_layers (image, layers, data); - - gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_TEXT, - _("Discard Text Information")); for (iter = layers; iter; iter = iter->next) - if (GIMP_IS_TEXT_LAYER (iter->data)) - gimp_text_layer_discard (GIMP_TEXT_LAYER (iter->data)); + { + if (GIMP_IS_LINK_LAYER (iter->data) && ! gimp_item_is_link_layer (iter->data)) + gimp_link_layer_monitor (GIMP_LINK_LAYER (iter->data)); + else if (GIMP_IS_TEXT_LAYER (iter->data) && ! gimp_item_is_text_layer (iter->data)) + gimp_text_layer_retrieve (GIMP_TEXT_LAYER (iter->data)); + } + gimp_image_undo_group_end (image); } @@ -2731,25 +2729,6 @@ layers_vector_fill_stroke_cmd_callback (GimpAction *action, } } -void -layers_vector_discard_cmd_callback (GimpAction *action, - GVariant *value, - gpointer data) -{ - GimpImage *image; - GimpLayer *layer; - GList *layers; - return_if_no_layers (image, layers, data); - - if (g_list_length (layers) != 1) - return; - - layer = layers->data; - - if (GIMP_IS_VECTOR_LAYER (layer)) - gimp_vector_layer_discard (GIMP_VECTOR_LAYER (layer)); -} - static void layers_resize_callback (GtkWidget *dialog, GimpViewable *viewable, diff --git a/app/actions/layers-commands.h b/app/actions/layers-commands.h index 0a2621525f..bd2ff6777d 100644 --- a/app/actions/layers-commands.h +++ b/app/actions/layers-commands.h @@ -77,15 +77,14 @@ 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, + +void layers_rasterize_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, +void layers_retrieve_cmd_callback (GimpAction *action, GVariant *value, gpointer data); + void layers_text_to_path_cmd_callback (GimpAction *action, GVariant *value, gpointer data); @@ -95,9 +94,6 @@ void layers_text_along_path_cmd_callback (GimpAction *action, void layers_vector_fill_stroke_cmd_callback (GimpAction *action, GVariant *value, gpointer data); -void layers_vector_discard_cmd_callback (GimpAction *action, - GVariant *value, - gpointer data); void layers_resize_cmd_callback (GimpAction *action, GVariant *value, diff --git a/app/core/gimpitem.c b/app/core/gimpitem.c index 29f684005d..d6262d61be 100644 --- a/app/core/gimpitem.c +++ b/app/core/gimpitem.c @@ -28,6 +28,10 @@ #include "core-types.h" +#include "path/gimpvectorlayer.h" + +#include "text/gimptextlayer.h" + #include "gimp.h" #include "gimp-parasites.h" #include "gimpchannel.h" @@ -39,6 +43,7 @@ #include "gimpitem.h" #include "gimpitem-preview.h" #include "gimpitemtree.h" +#include "gimplinklayer.h" #include "gimplist.h" #include "gimpparasitelist.h" #include "gimpprogress.h" @@ -2784,3 +2789,23 @@ gimp_item_is_in_set (GimpItem *item, return FALSE; } + +gboolean +gimp_item_is_rasterizable (GimpItem *item) +{ + g_return_val_if_fail (GIMP_IS_ITEM (item), FALSE); + + return (gimp_item_is_text_layer (item) || + gimp_item_is_link_layer (item) || + gimp_item_is_vector_layer (item)); +} + +gboolean +gimp_item_is_rasterized (GimpItem *item) +{ + g_return_val_if_fail (GIMP_IS_ITEM (item), FALSE); + + return ((GIMP_IS_TEXT_LAYER (item) && ! gimp_item_is_text_layer (item)) || + (GIMP_IS_LINK_LAYER (item) && ! gimp_item_is_link_layer (item)) || + (GIMP_IS_VECTOR_LAYER (item) && ! gimp_item_is_vector_layer (item))); +} diff --git a/app/core/gimpitem.h b/app/core/gimpitem.h index b35637b662..467c555246 100644 --- a/app/core/gimpitem.h +++ b/app/core/gimpitem.h @@ -399,3 +399,6 @@ gboolean gimp_item_mask_intersect (GimpItem *item, gboolean gimp_item_is_in_set (GimpItem *item, GimpItemSet set); + +gboolean gimp_item_is_rasterizable (GimpItem *item); +gboolean gimp_item_is_rasterized (GimpItem *item); diff --git a/app/text/gimptextlayer.c b/app/text/gimptextlayer.c index cd2017e92f..ad33c9b00b 100644 --- a/app/text/gimptextlayer.c +++ b/app/text/gimptextlayer.c @@ -590,25 +590,52 @@ gimp_text_layer_set (GimpTextLayer *layer, void gimp_text_layer_discard (GimpTextLayer *layer) { + GimpImage *image; + g_return_if_fail (GIMP_IS_TEXT_LAYER (layer)); g_return_if_fail (gimp_item_is_attached (GIMP_ITEM (layer))); - if (! layer->text) + if (layer->modified) return; - gimp_image_undo_push_text_layer (gimp_item_get_image (GIMP_ITEM (layer)), - _("Discard Text Information"), - layer, NULL); + image = gimp_item_get_image (GIMP_ITEM (layer)); - gimp_text_layer_set_text (layer, NULL); + gimp_image_undo_push_text_layer_modified (image, NULL, layer); + g_object_set (layer, "modified", TRUE, NULL); + + gimp_viewable_invalidate_preview (GIMP_VIEWABLE (layer)); + /* Though technically selected layers are not changed, it will trigger + * actions update, so that visibility of any action depending on text + * layers being rasterized or not will be updated. + */ + g_signal_emit_by_name (image, "selected-layers-changed"); +} + +void +gimp_text_layer_retrieve (GimpTextLayer *layer) +{ + GimpImage *image; + + g_return_if_fail (GIMP_IS_TEXT_LAYER (layer)); + g_return_if_fail (gimp_item_is_attached (GIMP_ITEM (layer))); + + if (! layer->modified) + return; + + image = gimp_item_get_image (GIMP_ITEM (layer)); + + gimp_image_undo_push_text_layer_modified (image, NULL, layer); + gimp_image_undo_push_drawable_mod (image, NULL, GIMP_DRAWABLE (layer), TRUE); + g_object_set (layer, "modified", FALSE, NULL); + + gimp_text_layer_render (layer); + gimp_image_flush (image); } gboolean gimp_item_is_text_layer (GimpItem *item) { - return (GIMP_IS_TEXT_LAYER (item) && - GIMP_TEXT_LAYER (item)->text && - GIMP_TEXT_LAYER (item)->modified == FALSE); + return (GIMP_IS_TEXT_LAYER (item) && ! GIMP_TEXT_LAYER (item)->modified); } diff --git a/app/text/gimptextlayer.h b/app/text/gimptextlayer.h index b119f8b686..73529de511 100644 --- a/app/text/gimptextlayer.h +++ b/app/text/gimptextlayer.h @@ -66,6 +66,7 @@ GimpText * gimp_text_layer_get_text (GimpTextLayer *layer); void gimp_text_layer_set_text (GimpTextLayer *layer, GimpText *text); void gimp_text_layer_discard (GimpTextLayer *layer); +void gimp_text_layer_retrieve (GimpTextLayer *layer); void gimp_text_layer_set (GimpTextLayer *layer, const gchar *undo_desc, const gchar *first_property_name, diff --git a/menus/image-menu.ui.in.in b/menus/image-menu.ui.in.in index 08d2b32ba0..879ca50938 100644 --- a/menus/image-menu.ui.in.in +++ b/menus/image-menu.ui.in.in @@ -429,20 +429,18 @@ app.layers-delete
- Text - app.layers-text-discard + Non-Raster + + app.layers-text-to-path app.layers-text-along-path -
-
- Vector + + app.layers-vector-fill-stroke - app.layers-vector-discard -
-
- Link - app.layers-link-discard - app.layers-link-monitor + + + app.layers-rasterize + app.layers-retrieve
diff --git a/menus/layers-menu.ui b/menus/layers-menu.ui index d2bdee836d..dbe93984d2 100644 --- a/menus/layers-menu.ui +++ b/menus/layers-menu.ui @@ -60,17 +60,13 @@ app.layers-delete
- app.layers-link-discard - app.layers-link-monitor -
-
- app.layers-text-discard app.layers-text-to-path app.layers-text-along-path -
-
+ app.layers-vector-fill-stroke - app.layers-vector-discard + + app.layers-rasterize + app.layers-retrieve
app.layers-resize