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.
This commit is contained in:
Alx Sa 2026-01-08 21:14:39 +00:00
parent 4a63666c8e
commit 5753ac75b4
5 changed files with 178 additions and 59 deletions

View file

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

View file

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

View file

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

View file

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

View file

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