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.
This commit is contained in:
Jehan 2025-08-26 22:51:36 +02:00
parent 95f1d768c3
commit 97bf7e1bfa
6 changed files with 201 additions and 118 deletions

View file

@ -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,

View file

@ -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);

View file

@ -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)
{

View file

@ -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

View file

@ -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 *);

View file

@ -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 */
};