From 97bf7e1bfa942835865f1679e499d957fc9ef6a2 Mon Sep 17 00:00:00 2001 From: Jehan Date: Tue, 26 Aug 2025 22:51:36 +0200 Subject: [PATCH] app: rework load/save of link layers. - We now bump to XCF version 25 since vector layers were implemented in XCF 24. - Fix some now broken code in saving/loading, also rename PROP_LINK_LAYER_DATA to simply PROP_LINK_LAYER and use the generic gimp_layer_from_layer() which I created in commit afb8867bce and is also used for text and vector layers. - Add a PROP_TRANSFORM layer property. It is separate from PROP_LINK_LAYER because I will likely reuse this for other (all even) types of layers as well. --- app/core/gimplinklayer.c | 89 ++++---------------- app/core/gimplinklayer.h | 3 +- app/xcf/xcf-load.c | 173 +++++++++++++++++++++++++++++++-------- app/xcf/xcf-private.h | 3 +- app/xcf/xcf-save.c | 50 +++++++++-- app/xcf/xcf.c | 1 + 6 files changed, 201 insertions(+), 118 deletions(-) diff --git a/app/core/gimplinklayer.c b/app/core/gimplinklayer.c index 866823d783..825db62bcd 100644 --- a/app/core/gimplinklayer.c +++ b/app/core/gimplinklayer.c @@ -151,8 +151,6 @@ static void gimp_link_layer_convert_type (GimpLayer *layer, static void gimp_link_layer_render_full (GimpLinkLayer *layer); static gboolean gimp_link_layer_render_link (GimpLinkLayer *layer); -static void gimp_link_layer_set_xcf_flags (GimpLinkLayer *layer, - guint32 flags); static gboolean gimp_link_layer_is_scaling_matrix (GimpLinkLayer *layer, const GimpMatrix3 *matrix, @@ -927,6 +925,20 @@ gimp_link_layer_set_transform (GimpLinkLayer *layer, return rendered; } +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, + NULL); + + if ((flags & LINK_LAYER_XCF_MODIFIED) != 0) + gimp_link_freeze (layer->p->link); +} + guint32 gimp_link_layer_get_xcf_flags (GimpLinkLayer *link_layer) { @@ -943,63 +955,6 @@ gimp_link_layer_get_xcf_flags (GimpLinkLayer *link_layer) 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 */ @@ -1130,22 +1085,6 @@ gimp_link_layer_render_link (GimpLinkLayer *layer) return (width > 0 && height > 0); } -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, - NULL); - - if ((flags & LINK_LAYER_XCF_MODIFIED) != 0) - gimp_link_freeze (layer->p->link); - else - gimp_link_thaw (layer->p->link); -} - static gboolean gimp_link_layer_is_scaling_matrix (GimpLinkLayer *layer, const GimpMatrix3 *matrix, diff --git a/app/core/gimplinklayer.h b/app/core/gimplinklayer.h index 886cd9f1c0..834c13c75a 100644 --- a/app/core/gimplinklayer.h +++ b/app/core/gimplinklayer.h @@ -81,6 +81,5 @@ gboolean gimp_link_layer_set_transform (GimpLinkLayer *layer, /* 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, +void gimp_link_layer_set_xcf_flags (GimpLinkLayer *layer, guint32 flags); diff --git a/app/xcf/xcf-load.c b/app/xcf/xcf-load.c index 59e46e99e2..d8763354f7 100644 --- a/app/xcf/xcf-load.c +++ b/app/xcf/xcf-load.c @@ -148,6 +148,14 @@ typedef struct gdouble *stroke_dashes; } VectorLayerData; +typedef struct +{ + gint offset_x; + gint offset_y; + GimpInterpolationType interpolation; + GimpMatrix3 matrix; +} LayerTransformData; + static void xcf_load_add_masks (GimpImage *image); static void xcf_load_add_effects (XcfInfo *info, GimpImage *image); @@ -946,19 +954,20 @@ xcf_load_image (Gimp *gimp, } } - /* Once all items are loaded, we transform any vector layer in - * waiting. We could not create vector layers directly because we - * needed the paths to be loaded first. - */ layers = gimp_image_get_layer_list (image); for (iter = layers; iter; iter = g_list_next (iter)) { - GimpLayer *layer = iter->data; - VectorLayerData *data; + GimpLayer *layer = iter->data; + VectorLayerData *vdata; + LayerTransformData *tdata; - data = g_object_get_data (G_OBJECT (layer), "gimp-vector-layer-data"); + /* Once all items are loaded, we transform any vector layer in + * waiting. We could not create vector layers directly because we + * needed the paths to be loaded first. + */ + vdata = g_object_get_data (G_OBJECT (layer), "gimp-vector-layer-data"); - if (data != NULL) + if (vdata != NULL) { GimpLayer *vlayer; GimpVectorLayerOptions *options; @@ -972,7 +981,7 @@ xcf_load_image (Gimp *gimp, linked = g_list_find (info->linked_layers, layer); floating = (info->floating_sel == layer); - path = gimp_image_get_path_by_tattoo (image, data->path_tattoo); + path = gimp_image_get_path_by_tattoo (image, vdata->path_tattoo); if (path == NULL) { GIMP_LOG (XCF, @@ -984,27 +993,27 @@ xcf_load_image (Gimp *gimp, } options = gimp_vector_layer_options_new (image, path, gimp_get_user_context (info->gimp)); - options->enable_fill = data->enable_fill; - options->enable_stroke = data->enable_stroke; + options->enable_fill = vdata->enable_fill; + options->enable_stroke = vdata->enable_stroke; - gimp_fill_options_set_custom_style (options->fill_options, data->fill_style); - gimp_fill_options_set_antialias (options->fill_options, data->fill_antialias); - gimp_context_set_foreground (GIMP_CONTEXT (options->fill_options), data->fill_color); - gimp_context_set_pattern (GIMP_CONTEXT (options->fill_options), data->fill_pattern); + gimp_fill_options_set_custom_style (options->fill_options, vdata->fill_style); + gimp_fill_options_set_antialias (options->fill_options, vdata->fill_antialias); + gimp_context_set_foreground (GIMP_CONTEXT (options->fill_options), vdata->fill_color); + gimp_context_set_pattern (GIMP_CONTEXT (options->fill_options), vdata->fill_pattern); - gimp_fill_options_set_custom_style (GIMP_FILL_OPTIONS (options->stroke_options), data->stroke_style); - gimp_fill_options_set_antialias (GIMP_FILL_OPTIONS (options->stroke_options), data->stroke_antialias); - gimp_context_set_foreground (GIMP_CONTEXT (options->stroke_options), data->stroke_color); - gimp_context_set_pattern (GIMP_CONTEXT (options->stroke_options), data->stroke_pattern); + gimp_fill_options_set_custom_style (GIMP_FILL_OPTIONS (options->stroke_options), vdata->stroke_style); + gimp_fill_options_set_antialias (GIMP_FILL_OPTIONS (options->stroke_options), vdata->stroke_antialias); + gimp_context_set_foreground (GIMP_CONTEXT (options->stroke_options), vdata->stroke_color); + gimp_context_set_pattern (GIMP_CONTEXT (options->stroke_options), vdata->stroke_pattern); - dash_pattern = gimp_dash_pattern_from_double_array (data->n_stroke_dashes, data->stroke_dashes); + dash_pattern = gimp_dash_pattern_from_double_array (vdata->n_stroke_dashes, vdata->stroke_dashes); gimp_stroke_options_take_dash_pattern (options->stroke_options, GIMP_DASH_CUSTOM, dash_pattern); g_object_set (G_OBJECT (options->stroke_options), - "width", data->stroke_width, - "cap-style", data->stroke_cap_style, - "join-style", data->stroke_join_style, - "miter-limit", data->stroke_miter_limit, + "width", vdata->stroke_width, + "cap-style", vdata->stroke_cap_style, + "join-style", vdata->stroke_join_style, + "miter-limit", vdata->stroke_miter_limit, NULL); vlayer = gimp_layer_from_layer (layer, GIMP_TYPE_VECTOR_LAYER, @@ -1012,7 +1021,7 @@ xcf_load_image (Gimp *gimp, "vector-layer-options", options, NULL); g_object_unref (options); - GIMP_VECTOR_LAYER (vlayer)->modified = data->modified; + GIMP_VECTOR_LAYER (vlayer)->modified = vdata->modified; if (selected) { @@ -1026,6 +1035,43 @@ xcf_load_image (Gimp *gimp, } if (floating) info->floating_sel = vlayer; + + layer = vlayer; + } + + /* If any layer has a transformation matrix, we apply it. + * + * XXX Right now, this can only be applied to link layers, but + * eventually we should be able to port this to any type of layer + * as a last-minute transformation in-one concept which would work + * as a lesser-destruction edit when applying several + * transformations. + */ + tdata = g_object_get_data (G_OBJECT (layer), "gimp-layer-transform-data"); + if (tdata != NULL) + { + if (! GIMP_IS_LINK_LAYER (layer)) + { + GIMP_LOG (XCF, + "PROP_TRANSFORM property can only be applied on link layers. " + "The transformation on layer \"%s\" was dropped.", + gimp_object_get_name (layer)); + g_object_set_data (G_OBJECT (layer), "gimp-layer-transform-data", NULL); + continue; + } + else if (! gimp_link_layer_is_monitored (GIMP_LINK_LAYER (layer)) || + gimp_link_is_broken (gimp_link_layer_get_link (GIMP_LINK_LAYER (layer)))) + { + /* The loaded buffer from XCF will already be transformed. + * It's not an error. + */ + g_object_set_data (G_OBJECT (layer), "gimp-layer-transform-data", NULL); + continue; + } + + gimp_item_set_offset (GIMP_ITEM (layer), tdata->offset_x, tdata->offset_y); + gimp_link_layer_set_transform (GIMP_LINK_LAYER (layer), &tdata->matrix, tdata->interpolation, FALSE); + g_object_set_data (G_OBJECT (layer), "gimp-layer-transform-data", NULL); } } g_list_free (layers); @@ -2176,13 +2222,19 @@ xcf_load_layer_props (XcfInfo *info, (GDestroyNotify) xcf_load_free_vector_data); } - case PROP_LINK_LAYER_DATA: + case PROP_LINK_LAYER: { GimpLink *link; GFile *folder; gchar *path; guint32 flags; - gboolean is_selected_layer; + GList *selected; + GList *linked; + gboolean floating; + + selected = g_list_find (info->selected_layers, *layer); + linked = g_list_find (info->linked_layers, *layer); + floating = (info->floating_sel == *layer); xcf_read_int32 (info, &flags, 1); xcf_read_string (info, &path, 1); @@ -2190,17 +2242,62 @@ xcf_load_layer_props (XcfInfo *info, folder = g_file_get_parent (info->file); link = gimp_link_new (info->gimp, g_file_resolve_relative_path (folder, path), NULL, NULL); + *layer = gimp_layer_from_layer (*layer, GIMP_TYPE_LINK_LAYER, + "image", image, + NULL); + + gimp_link_layer_set_link (GIMP_LINK_LAYER (*layer), link, FALSE); + gimp_link_layer_set_xcf_flags (GIMP_LINK_LAYER (*layer), flags); + + if (selected) + { + info->selected_layers = g_list_delete_link (info->selected_layers, selected); + info->selected_layers = g_list_prepend (info->selected_layers, *layer); + } + if (linked) + { + info->linked_layers = g_list_delete_link (info->linked_layers, linked); + info->linked_layers = g_list_prepend (info->linked_layers, *layer); + } + if (floating) + info->floating_sel = *layer; + + g_object_unref (link); g_object_unref (folder); g_free (path); + } + break; - 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); + case PROP_TRANSFORM: + { + LayerTransformData *data; + gint32 int_val[2]; + guint32 uint_val; + gfloat mfloat[9]; - gimp_link_layer_from_layer (layer, link, flags); + data = g_new0 (LayerTransformData, 1); - if (is_selected_layer) - info->selected_layers = g_list_prepend (info->selected_layers, *layer); + xcf_read_int32 (info, (guint32 *) int_val, 2); + data->offset_x = (gint) int_val[0]; + data->offset_y = (gint) int_val[1]; + + xcf_read_int32 (info, &uint_val, 1); + data->interpolation = (GimpInterpolationType) uint_val; + + xcf_read_float (info, mfloat, 9); + data->matrix.coeff[0][0] = (gdouble) mfloat[0]; + data->matrix.coeff[0][1] = (gdouble) mfloat[1]; + data->matrix.coeff[0][2] = (gdouble) mfloat[2]; + data->matrix.coeff[1][0] = (gdouble) mfloat[3]; + data->matrix.coeff[1][1] = (gdouble) mfloat[4]; + data->matrix.coeff[1][2] = (gdouble) mfloat[5]; + data->matrix.coeff[2][0] = (gdouble) mfloat[6]; + data->matrix.coeff[2][1] = (gdouble) mfloat[7]; + data->matrix.coeff[2][2] = (gdouble) mfloat[8]; + + g_object_set_data_full (G_OBJECT (*layer), + "gimp-layer-transform-data", data, + (GDestroyNotify) g_free); } break; @@ -2332,7 +2429,7 @@ xcf_check_layer_props (XcfInfo *info, return FALSE; break; - case PROP_LINK_LAYER_DATA: + case PROP_LINK_LAYER: *is_link_layer = TRUE; if (! xcf_skip_unknown_prop (info, prop_size)) @@ -3379,7 +3476,13 @@ xcf_load_layer (XcfInfo *info, * optimization and because the hierarchy's extents don't match * the group layer's tiles) */ - if (! gimp_viewable_get_children (GIMP_VIEWABLE (layer))) + if (! gimp_viewable_get_children (GIMP_VIEWABLE (layer)) && + /* Link layers are loaded from XCF only if they are not monitored + * or if the link is broken. + */ + (! GIMP_IS_LINK_LAYER (layer) || + ! gimp_link_layer_is_monitored (GIMP_LINK_LAYER (layer)) || + gimp_link_is_broken (gimp_link_layer_get_link (GIMP_LINK_LAYER (layer))))) { if (hierarchy_offset < cur_offset) { diff --git a/app/xcf/xcf-private.h b/app/xcf/xcf-private.h index 18e151d7d5..8f21f7e6db 100644 --- a/app/xcf/xcf-private.h +++ b/app/xcf/xcf-private.h @@ -73,7 +73,8 @@ typedef enum PROP_FILTER_ARGUMENT = 45, PROP_FILTER_CLIP = 46, PROP_VECTOR_LAYER = 47, - PROP_LINK_LAYER_DATA = 48, + PROP_LINK_LAYER = 48, + PROP_TRANSFORM = 49, } PropType; typedef enum diff --git a/app/xcf/xcf-save.c b/app/xcf/xcf-save.c index d7047509f5..e2f1009de9 100644 --- a/app/xcf/xcf-save.c +++ b/app/xcf/xcf-save.c @@ -741,9 +741,11 @@ xcf_save_layer_props (XcfInfo *info, { xcf_check_error (xcf_save_prop (info, image, PROP_VECTOR_LAYER, error, layer), ;); } - - if (GIMP_IS_LINK_LAYER (layer)) - xcf_check_error (xcf_save_prop (info, image, PROP_LINK_LAYER_DATA, error, layer), ;); + else if (GIMP_IS_LINK_LAYER (layer)) + { + xcf_check_error (xcf_save_prop (info, image, PROP_LINK_LAYER, error, layer), ;); + xcf_check_error (xcf_save_prop (info, image, PROP_TRANSFORM, error, layer), ;); + } if (gimp_viewable_get_children (GIMP_VIEWABLE (layer))) { @@ -1729,7 +1731,7 @@ xcf_save_prop (XcfInfo *info, } break; - case PROP_LINK_LAYER_DATA: + case PROP_LINK_LAYER: { GimpLinkLayer *layer = va_arg (args, GimpLinkLayer *); gchar *path = NULL; @@ -1738,7 +1740,7 @@ xcf_save_prop (XcfInfo *info, flags = gimp_link_layer_get_xcf_flags (layer); gimp_link_get_file (gimp_link_layer_get_link (layer), info->file, &path); - size = 4 + strlen (path) ? strlen (path) + 5 : 4; + size = 4 + (strlen (path) ? strlen (path) + 1 : 4); xcf_write_prop_type_check_error (info, prop_type, va_end (args)); xcf_write_int32_check_error (info, &size, 1, va_end (args)); @@ -1750,6 +1752,44 @@ xcf_save_prop (XcfInfo *info, } break; + case PROP_TRANSFORM: + { + GimpLinkLayer *layer = va_arg (args, GimpLinkLayer *); + GimpMatrix3 matrix; + gint offset_x; + gint offset_y; + GimpInterpolationType interpolation; + gint32 int_val; + guint32 uint_val; + gfloat mfloat[9]; + + gimp_link_layer_get_transform (layer, &matrix, &offset_x, &offset_y, &interpolation); + + size = 4 * 12; + + xcf_write_prop_type_check_error (info, prop_type, va_end (args)); + xcf_write_int32_check_error (info, &size, 1, va_end (args)); + + int_val = (gint32) offset_x; + xcf_write_int32_check_error (info, (guint32 *) &int_val, 1, va_end (args)); + int_val = (gint32) offset_y; + xcf_write_int32_check_error (info, (guint32 *) &int_val, 1, va_end (args)); + uint_val = (guint32) interpolation; + xcf_write_int32_check_error (info, &uint_val, 1, va_end (args)); + + mfloat[0] = matrix.coeff[0][0]; + mfloat[1] = matrix.coeff[0][1]; + mfloat[2] = matrix.coeff[0][2]; + mfloat[3] = matrix.coeff[1][0]; + mfloat[4] = matrix.coeff[1][1]; + mfloat[5] = matrix.coeff[1][2]; + mfloat[6] = matrix.coeff[2][0]; + mfloat[7] = matrix.coeff[2][1]; + mfloat[8] = matrix.coeff[2][2]; + xcf_write_float_check_error (info, mfloat, 9, va_end (args)); + } + break; + case PROP_ITEM_PATH: { GList *path = va_arg (args, GList *); diff --git a/app/xcf/xcf.c b/app/xcf/xcf.c index fccf6642cf..ffd9241b73 100644 --- a/app/xcf/xcf.c +++ b/app/xcf/xcf.c @@ -93,6 +93,7 @@ static GimpXcfLoaderFunc * const xcf_loaders[] = xcf_load_image, /* version 22 */ xcf_load_image, /* version 23 */ xcf_load_image, /* version 24 */ + xcf_load_image, /* version 25 */ };