From 5753ac75b4bce4f043d22da338658a9b04d82618 Mon Sep 17 00:00:00 2001 From: Alx Sa Date: Thu, 8 Jan 2026 21:14:39 +0000 Subject: [PATCH] libgimpconfig: Save legacy XCF grid colors... ...in GimpRGB format. Resolves #14754 GimpGrid properties are saved and loaded in XCFs as GimpParasites. When we converted from GimpRGB to GeglColor for the fgcolor and bgcolor properties, we caused those properties to be lost when saving a 2.10 and below compatible XCF in GIMP 3.0+. This patch adds new gimp_config_get_xcf_version () and gimp_config_set_xcf_version () API to libgimpconfig's interface. This allows us to pass the intended XCF version to the parasite serialization process, and save as either GeglColor or GimpRGB compatible formats based on that value. --- app/xcf/xcf-save.c | 3 + libgimpconfig/gimpconfig-iface.c | 124 +++++++++++++++++++++++---- libgimpconfig/gimpconfig-iface.h | 4 + libgimpconfig/gimpconfig-serialize.c | 104 +++++++++++++--------- libgimpconfig/gimpconfig.def | 2 + 5 files changed, 178 insertions(+), 59 deletions(-) diff --git a/app/xcf/xcf-save.c b/app/xcf/xcf-save.c index 160f8c4e37..d9e4b254e0 100644 --- a/app/xcf/xcf-save.c +++ b/app/xcf/xcf-save.c @@ -556,6 +556,9 @@ xcf_save_image_props (XcfInfo *info, { GimpGrid *grid = gimp_image_get_grid (image); + /* Set the XCF version so that the grid colors are written as GimpRGB + * values when saving in legacy (2.10 and below) XCF formats */ + gimp_config_set_xcf_version (GIMP_CONFIG (grid), info->file_version); grid_parasite = gimp_grid_to_parasite (grid); gimp_parasite_list_add (private->parasites, grid_parasite); } diff --git a/libgimpconfig/gimpconfig-iface.c b/libgimpconfig/gimpconfig-iface.c index 131693e487..84907c7c47 100644 --- a/libgimpconfig/gimpconfig-iface.c +++ b/libgimpconfig/gimpconfig-iface.c @@ -40,6 +40,16 @@ #include "libgimp/libgimp-intl.h" +typedef struct _GimpConfigInterfacePrivate GimpConfigInterfacePrivate; + +struct _GimpConfigInterfacePrivate +{ + guint xcf_version; +}; + +#define GIMP_CONFIG_INTERFACE_GET_PRIVATE(obj) (gimp_config_iface_get_private ((GimpConfigInterface *) (obj))) + + /* * GimpConfigIface: * @@ -49,23 +59,26 @@ /* local function prototypes */ -static void gimp_config_iface_default_init (GimpConfigInterface *iface); -static void gimp_config_iface_base_init (GimpConfigInterface *iface); +static GimpConfigInterfacePrivate * gimp_config_iface_get_private (GimpConfigInterface *iface); +static void gimp_config_iface_private_finalize (GimpConfigInterfacePrivate *private); -static gboolean gimp_config_iface_serialize (GimpConfig *config, - GimpConfigWriter *writer, - gpointer data); -static gboolean gimp_config_iface_deserialize (GimpConfig *config, - GScanner *scanner, - gint nest_level, - gpointer data); -static GimpConfig * gimp_config_iface_duplicate (GimpConfig *config); -static gboolean gimp_config_iface_equal (GimpConfig *a, - GimpConfig *b); -static void gimp_config_iface_reset (GimpConfig *config); -static gboolean gimp_config_iface_copy (GimpConfig *src, - GimpConfig *dest, - GParamFlags flags); +static void gimp_config_iface_default_init (GimpConfigInterface *iface); +static void gimp_config_iface_base_init (GimpConfigInterface *iface); + +static gboolean gimp_config_iface_serialize (GimpConfig *config, + GimpConfigWriter *writer, + gpointer data); +static gboolean gimp_config_iface_deserialize (GimpConfig *config, + GScanner *scanner, + gint nest_level, + gpointer data); +static GimpConfig * gimp_config_iface_duplicate (GimpConfig *config); +static gboolean gimp_config_iface_equal (GimpConfig *a, + GimpConfig *b); +static void gimp_config_iface_reset (GimpConfig *config); +static gboolean gimp_config_iface_copy (GimpConfig *src, + GimpConfig *dest, + GParamFlags flags); /* private functions */ @@ -266,6 +279,37 @@ gimp_config_iface_copy (GimpConfig *src, return gimp_config_sync (G_OBJECT (src), G_OBJECT (dest), flags); } +static GimpConfigInterfacePrivate * +gimp_config_iface_get_private (GimpConfigInterface *iface) +{ + GimpConfigInterfacePrivate *private; + + static GQuark private_key = 0; + + if (! private_key) + private_key = g_quark_from_static_string ("gimp-config-iface-private"); + + private = g_object_get_qdata ((GObject *) iface, private_key); + + if (! private) + { + private = g_slice_new0 (GimpConfigInterfacePrivate); + + private->xcf_version = G_MAXUINT32; + + g_object_set_qdata_full ((GObject *) iface, private_key, private, + (GDestroyNotify) gimp_config_iface_private_finalize); + } + + return private; +} + +static void +gimp_config_iface_private_finalize (GimpConfigInterfacePrivate *private) +{ + g_slice_free (GimpConfigInterfacePrivate, private); +} + /* public functions */ @@ -402,7 +446,7 @@ gimp_config_serialize_to_string (GimpConfig *config, g_return_val_if_fail (GIMP_IS_CONFIG (config), NULL); - str = g_string_new (NULL); + str = g_string_new (NULL); writer = gimp_config_writer_new_from_string (str); GIMP_CONFIG_GET_IFACE (config)->serialize (config, writer, data); @@ -837,3 +881,49 @@ gimp_config_copy (GimpConfig *src, return changed; } + +/** + * gimp_config_get_xcf_version: + * @config: a #GObject that implements the #GimpConfigInterface. + * + * Returns the current XCF version of the @config. + * + * Returns: the XCF version associated with the @config. + * + * Since: 3.2 + **/ +guint +gimp_config_get_xcf_version (GimpConfig *config) +{ + GimpConfigInterfacePrivate *private; + + g_return_val_if_fail (GIMP_IS_CONFIG (config), G_MAXUINT32); + + private = GIMP_CONFIG_INTERFACE_GET_PRIVATE (config); + + return private->xcf_version; +} + +/** + * gimp_config_set_xcf_version: + * @config: a #GObject that implements the #GimpConfigInterface. + * @xcf_version: a mask of GParamFlags + * + * Sets the current XCF version of the @config. This information can be used + * to adjust how properties are serialized depending on the version of the XCF + * that it is being saved to. + * + * Since: 3.2 + **/ +void +gimp_config_set_xcf_version (GimpConfig *config, + guint xcf_version) +{ + GimpConfigInterfacePrivate *private; + + g_return_if_fail (GIMP_IS_CONFIG (config)); + + private = GIMP_CONFIG_INTERFACE_GET_PRIVATE (config); + + private->xcf_version = xcf_version; +} diff --git a/libgimpconfig/gimpconfig-iface.h b/libgimpconfig/gimpconfig-iface.h index c142ee6c52..fef3def071 100644 --- a/libgimpconfig/gimpconfig-iface.h +++ b/libgimpconfig/gimpconfig-iface.h @@ -129,6 +129,10 @@ gboolean gimp_config_copy (GimpConfig *src, GimpConfig *dest, GParamFlags flags); +guint gimp_config_get_xcf_version (GimpConfig *config); +void gimp_config_set_xcf_version (GimpConfig *config, + guint xcf_version); + G_END_DECLS diff --git a/libgimpconfig/gimpconfig-serialize.c b/libgimpconfig/gimpconfig-serialize.c index ce5617bb49..930d71f93f 100644 --- a/libgimpconfig/gimpconfig-serialize.c +++ b/libgimpconfig/gimpconfig-serialize.c @@ -309,61 +309,81 @@ gimp_config_serialize_property (GimpConfig *config, if (color) { - const gchar *encoding; - const Babl *format = gegl_color_get_format (color); - const Babl *space; - GBytes *bytes; - gconstpointer data; - gsize data_length; - int profile_length = 0; + guint xcf_version = gimp_config_get_xcf_version (config); - gimp_config_writer_open (writer, "color"); - - if (babl_format_is_palette (format)) + /* If XCF version is before GIMP 3.0, then colors should be saved + * in legacy GimpRGB format for backwards compatibility */ + if (xcf_version >= 14) { - guint8 pixel[40]; + const gchar *encoding; + const Babl *format = gegl_color_get_format (color); + const Babl *space; + GBytes *bytes; + gconstpointer data; + gsize data_length; + int profile_length = 0; - /* As a special case, we don't want to serialize - * palette colors, because they are just too much - * dependent on external data and cannot be - * deserialized back safely. So we convert them first. - */ - free_color = TRUE; - color = gegl_color_duplicate (color); + gimp_config_writer_open (writer, "color"); - format = babl_format_with_space ("R'G'B'A u8", format); - gegl_color_get_pixel (color, format, pixel); - gegl_color_set_pixel (color, format, pixel); - } + if (babl_format_is_palette (format)) + { + guint8 pixel[40]; - encoding = babl_format_get_encoding (format); - gimp_config_writer_string (writer, encoding); + /* As a special case, we don't want to serialize + * palette colors, because they are just too much + * dependent on external data and cannot be + * deserialized back safely. So we convert them first. + */ + free_color = TRUE; + color = gegl_color_duplicate (color); - bytes = gegl_color_get_bytes (color, format); - data = g_bytes_get_data (bytes, &data_length); + format = babl_format_with_space ("R'G'B'A u8", format); + gegl_color_get_pixel (color, format, pixel); + gegl_color_set_pixel (color, format, pixel); + } - gimp_config_writer_printf (writer, "%" G_GSIZE_FORMAT, data_length); - gimp_config_writer_data (writer, data_length, data); + encoding = babl_format_get_encoding (format); + gimp_config_writer_string (writer, encoding); - space = babl_format_get_space (format); - if (space != babl_space ("sRGB")) - { - guint8 *profile_data; + bytes = gegl_color_get_bytes (color, format); + data = g_bytes_get_data (bytes, &data_length); - profile_data = (guint8 *) babl_space_get_icc (babl_format_get_space (format), - &profile_length); - gimp_config_writer_printf (writer, "%u", profile_length); - if (profile_data) - gimp_config_writer_data (writer, profile_length, profile_data); + gimp_config_writer_printf (writer, "%" G_GSIZE_FORMAT, + data_length); + gimp_config_writer_data (writer, data_length, data); + + space = babl_format_get_space (format); + if (space != babl_space ("sRGB")) + { + guint8 *profile_data; + + profile_data = + (guint8 *) babl_space_get_icc (babl_format_get_space (format), + &profile_length); + gimp_config_writer_printf (writer, "%u", profile_length); + if (profile_data) + gimp_config_writer_data (writer, profile_length, + profile_data); + } + else + { + gimp_config_writer_printf (writer, "%u", profile_length); + } + + g_bytes_unref (bytes); + gimp_config_writer_close (writer); } else { - gimp_config_writer_printf (writer, "%u", profile_length); + gdouble rgba[4]; + + gegl_color_get_pixel (color, babl_format ("R'G'B'A double"), rgba); + + gimp_config_writer_printf (writer, + "(color-rgba %f %f %f %f)", + rgba[0], rgba[1], rgba[2], + rgba[3]); } - - g_bytes_unref (bytes); - - gimp_config_writer_close (writer); } else { diff --git a/libgimpconfig/gimpconfig.def b/libgimpconfig/gimpconfig.def index a23ddf04a6..2d7068482b 100644 --- a/libgimpconfig/gimpconfig.def +++ b/libgimpconfig/gimpconfig.def @@ -34,6 +34,7 @@ EXPORTS gimp_config_duplicate gimp_config_error_quark gimp_config_get_type + gimp_config_get_xcf_version gimp_config_is_equal_to gimp_config_param_spec_duplicate gimp_config_path_expand @@ -54,6 +55,7 @@ EXPORTS gimp_config_serialize_to_stream gimp_config_serialize_to_string gimp_config_serialize_value + gimp_config_set_xcf_version gimp_config_string_append_escaped gimp_config_sync gimp_config_type_register