app, devel-docs: add a new "Vectors Structure" in the XCF format.

Instead of storing vectors as properties, they have their own structure, which
make them able to store and load all the usual and common properties of other
items. In other words, it makes XCF now able to store locks, color tags and
several selected paths.
This commit is contained in:
Jehan 2022-10-20 00:11:34 +02:00
parent 32cf103939
commit 81969a0651
6 changed files with 791 additions and 38 deletions

View File

@ -2966,6 +2966,34 @@ gimp_image_get_xcf_version (GimpImage *image,
}
g_list_free (items);
if (g_list_length (gimp_image_get_selected_vectors (image)) > 1)
{
ADD_REASON (g_strdup_printf (_("Multiple path selection was "
"added in %s"), "GIMP 3.0.0"));
version = MAX (18, version);
}
items = gimp_image_get_vectors_list (image);
for (list = items; list; list = g_list_next (list))
{
GimpVectors *vectors = GIMP_VECTORS (list->data);
if (gimp_item_get_color_tag (GIMP_ITEM (vectors)) != GIMP_COLOR_TAG_NONE)
{
ADD_REASON (g_strdup_printf (_("Storing color tags in path was "
"added in %s"), "GIMP 3.0.0"));
version = MAX (18, version);
}
if (gimp_item_get_lock_content (GIMP_ITEM (list->data)) ||
gimp_item_get_lock_position (GIMP_ITEM (list->data)))
{
ADD_REASON (g_strdup_printf (_("Storing locks in path was "
"added in %s"), "GIMP 3.0.0"));
version = MAX (18, version);
}
}
g_list_free (items);
/* version 6 for new metadata has been dropped since they are
* saved through parasites, which is compatible with older versions.
*/
@ -3086,6 +3114,7 @@ gimp_image_get_xcf_version (GimpImage *image,
case 15:
case 16:
case 17:
case 18:
if (gimp_version) *gimp_version = 300;
if (version_string) *version_string = "GIMP 3.0";
break;

View File

@ -105,6 +105,9 @@ static gboolean xcf_check_layer_props (XcfInfo *info,
static gboolean xcf_load_channel_props (XcfInfo *info,
GimpImage *image,
GimpChannel **channel);
static gboolean xcf_load_vectors_props (XcfInfo *info,
GimpImage *image,
GimpVectors **vectors);
static gboolean xcf_load_prop (XcfInfo *info,
PropType *prop_type,
guint32 *prop_size);
@ -113,6 +116,8 @@ static GimpLayer * xcf_load_layer (XcfInfo *info,
GList **item_path);
static GimpChannel * xcf_load_channel (XcfInfo *info,
GimpImage *image);
static GimpVectors * xcf_load_vectors (XcfInfo *info,
GimpImage *image);
static GimpLayerMask * xcf_load_layer_mask (XcfInfo *info,
GimpImage *image);
static gboolean xcf_load_buffer (XcfInfo *info,
@ -138,9 +143,9 @@ static gboolean xcf_load_old_paths (XcfInfo *info,
GimpImage *image);
static gboolean xcf_load_old_path (XcfInfo *info,
GimpImage *image);
static gboolean xcf_load_vectors (XcfInfo *info,
static gboolean xcf_load_old_vectors (XcfInfo *info,
GimpImage *image);
static gboolean xcf_load_vector (XcfInfo *info,
static gboolean xcf_load_old_vector (XcfInfo *info,
GimpImage *image);
static gboolean xcf_skip_unknown_prop (XcfInfo *info,
@ -176,6 +181,7 @@ xcf_load_image (Gimp *gimp,
gint num_successful_elements = 0;
gint n_broken_layers = 0;
gint n_broken_channels = 0;
gint n_broken_vectors = 0;
GList *broken_paths = NULL;
GList *group_layers = NULL;
GList *syms;
@ -775,6 +781,67 @@ xcf_load_image (Gimp *gimp,
floating_sel_attach (info->floating_sel, info->floating_sel_drawable);
}
if (info->file_version >= 18)
{
while (TRUE)
{
GimpVectors *vectors;
/* read in the offset of the next path */
xcf_read_offset (info, &offset, 1);
/* if the offset is 0 then we are at the end
* of the path list.
*/
if (offset == 0)
break;
/* save the current position as it is where the
* next channel offset is stored.
*/
saved_pos = info->cp;
if (offset < saved_pos)
{
GIMP_LOG (XCF, "Invalid path offset: %" G_GOFFSET_FORMAT
" at offset: % "G_GOFFSET_FORMAT, offset, saved_pos);
goto error;
}
/* seek to the path offset */
if (! xcf_seek_pos (info, offset, NULL))
goto error;
/* read in the path */
vectors = xcf_load_vectors (info, image);
if (! vectors)
{
n_broken_vectors++;
GIMP_LOG (XCF, "Failed to load path.");
if (! xcf_seek_pos (info, saved_pos, NULL))
goto error;
continue;
}
num_successful_elements++;
xcf_progress_update (info);
gimp_image_add_vectors (image, vectors,
NULL, /* can't be a tree */
gimp_container_get_n_children (gimp_image_get_vectors (image)),
FALSE);
/* restore the saved position so we'll be ready to
* read the next offset.
*/
if (! xcf_seek_pos (info, saved_pos, NULL))
goto error;
}
}
if (info->selected_layers)
{
gimp_image_set_selected_layers (image, info->selected_layers);
@ -784,6 +851,9 @@ xcf_load_image (Gimp *gimp,
if (info->selected_channels)
gimp_image_set_selected_channels (image, info->selected_channels);
if (info->selected_vectors)
gimp_image_set_selected_vectors (image, info->selected_vectors);
/* We don't have linked items concept anymore. We transform formerly
* linked items into stored sets of named items instead.
*/
@ -854,7 +924,7 @@ xcf_load_image (Gimp *gimp,
if (info->tattoo_state > 0)
gimp_image_set_tattoo_state (image, info->tattoo_state);
if (n_broken_layers > 0 || n_broken_channels > 0)
if (n_broken_layers > 0 || n_broken_channels > 0 || n_broken_vectors > 0)
goto error;
gimp_image_undo_enable (image);
@ -1267,6 +1337,12 @@ xcf_load_image_props (XcfInfo *info,
{
goffset base = info->cp;
if (info->file_version >= 18)
gimp_message (info->gimp, G_OBJECT (info->progress),
GIMP_MESSAGE_WARNING,
"XCF %d file should not contain PROP_PATHS image properties",
info->file_version);
if (! xcf_load_old_paths (info, image))
xcf_seek_pos (info, base + prop_size, NULL);
}
@ -1326,7 +1402,13 @@ xcf_load_image_props (XcfInfo *info,
{
goffset base = info->cp;
if (xcf_load_vectors (info, image))
if (info->file_version >= 18)
gimp_message (info->gimp, G_OBJECT (info->progress),
GIMP_MESSAGE_WARNING,
"XCF %d file should not contain PROP_VECTORS image properties",
info->file_version);
if (xcf_load_old_vectors (info, image))
{
if (base + prop_size != info->cp)
{
@ -1339,7 +1421,7 @@ xcf_load_image_props (XcfInfo *info,
else
{
/* skip silently since we don't understand the format and
* xcf_load_vectors already explained what was wrong
* xcf_load_old_vectors already explained what was wrong
*/
xcf_seek_pos (info, base + prop_size, NULL);
}
@ -2189,6 +2271,165 @@ xcf_load_channel_props (XcfInfo *info,
return FALSE;
}
static gboolean
xcf_load_vectors_props (XcfInfo *info,
GimpImage *image,
GimpVectors **vectors)
{
PropType prop_type;
guint32 prop_size;
while (TRUE)
{
if (! xcf_load_prop (info, &prop_type, &prop_size))
return FALSE;
switch (prop_type)
{
case PROP_END:
return TRUE;
case PROP_SELECTED_PATH:
info->selected_vectors = g_list_prepend (info->selected_vectors, *vectors);
break;
case PROP_VISIBLE:
{
gboolean visible;
xcf_read_int32 (info, (guint32 *) &visible, 1);
gimp_item_set_visible (GIMP_ITEM (*vectors), visible, FALSE);
}
break;
case PROP_COLOR_TAG:
{
GimpColorTag color_tag;
xcf_read_int32 (info, (guint32 *) &color_tag, 1);
gimp_item_set_color_tag (GIMP_ITEM (*vectors), color_tag, FALSE);
}
break;
case PROP_LOCK_CONTENT:
{
gboolean lock_content;
xcf_read_int32 (info, (guint32 *) &lock_content, 1);
if (gimp_item_can_lock_content (GIMP_ITEM (*vectors)))
gimp_item_set_lock_content (GIMP_ITEM (*vectors),
lock_content, FALSE);
}
break;
case PROP_LOCK_POSITION:
{
gboolean lock_position;
xcf_read_int32 (info, (guint32 *) &lock_position, 1);
if (gimp_item_can_lock_position (GIMP_ITEM (*vectors)))
gimp_item_set_lock_position (GIMP_ITEM (*vectors),
lock_position, FALSE);
}
break;
case PROP_LOCK_VISIBILITY:
{
gboolean lock_visibility;
xcf_read_int32 (info, (guint32 *) &lock_visibility, 1);
if (gimp_item_can_lock_visibility (GIMP_ITEM (*vectors)))
gimp_item_set_lock_visibility (GIMP_ITEM (*vectors),
lock_visibility, FALSE);
}
break;
case PROP_TATTOO:
{
GimpTattoo tattoo;
xcf_read_int32 (info, (guint32 *) &tattoo, 1);
gimp_item_set_tattoo (GIMP_ITEM (*vectors), tattoo);
}
break;
case PROP_PARASITES:
{
goffset base = info->cp;
while ((info->cp - base) < prop_size)
{
GimpParasite *p = xcf_load_parasite (info);
GError *error = NULL;
if (! p)
return FALSE;
if (! gimp_item_parasite_validate (GIMP_ITEM (*vectors), p,
&error))
{
gimp_message (info->gimp, G_OBJECT (info->progress),
GIMP_MESSAGE_WARNING,
"Warning, invalid path parasite in XCF file: %s",
error->message);
g_clear_error (&error);
}
else
{
gimp_item_parasite_attach (GIMP_ITEM (*vectors), p, FALSE);
}
gimp_parasite_free (p);
}
if (info->cp - base != prop_size)
gimp_message_literal (info->gimp, G_OBJECT (info->progress),
GIMP_MESSAGE_WARNING,
"Error while loading a path's parasites");
}
break;
#if 0
case PROP_ITEM_SET_ITEM:
{
GimpItemList *set;
guint32 n;
xcf_read_int32 (info, &n, 1);
set = g_list_nth_data (info->vectors_sets, n);
if (set == NULL)
g_printerr ("xcf: unknown path set: %d (skipping)\n", n);
else if (! g_type_is_a (G_TYPE_FROM_INSTANCE (*vectors),
gimp_item_list_get_item_type (set)))
g_printerr ("xcf: path '%s' cannot be added to item set '%s' with item type %s (skipping)\n",
gimp_object_get_name (*vectors), gimp_object_get_name (set),
g_type_name (gimp_item_list_get_item_type (set)));
else
gimp_item_list_add (set, GIMP_ITEM (*vectors));
}
break;
#endif
default:
#ifdef GIMP_UNSTABLE
g_printerr ("unexpected/unknown path property: %d (skipping)\n",
prop_type);
#endif
if (! xcf_skip_unknown_prop (info, prop_size))
return FALSE;
break;
}
}
return FALSE;
}
static gboolean
xcf_load_prop (XcfInfo *info,
PropType *prop_type,
@ -2552,6 +2793,162 @@ xcf_load_channel (XcfInfo *info,
return NULL;
}
/* The new path structure since XCF 18. */
static GimpVectors *
xcf_load_vectors (XcfInfo *info,
GimpImage *image)
{
GimpVectors *vectors = NULL;
gchar *name;
guint32 version;
guint32 plength;
guint32 num_strokes;
goffset base;
gint i;
/* read in the path name. */
xcf_read_string (info, &name, 1);
GIMP_LOG (XCF, "Path name='%s'", name);
/* create a new path */
vectors = gimp_vectors_new (image, name);
g_free (name);
if (! vectors)
return NULL;
/* Read the path's payload size. */
xcf_read_int32 (info, (guint32 *) &plength, 1);
base = info->cp;
/* read in the path properties */
if (! xcf_load_vectors_props (info, image, &vectors))
goto error;
GIMP_LOG (XCF, "path props loaded");
xcf_progress_update (info);
xcf_read_int32 (info, (guint32 *) &version, 1);
if (version != 1)
{
gimp_message (info->gimp, G_OBJECT (info->progress),
GIMP_MESSAGE_WARNING,
"Unknown vectors version: %d (skipping)", version);
goto error;
}
/* Read the number of strokes. */
xcf_read_int32 (info, &num_strokes, 1);
for (i = 0; i < num_strokes; i++)
{
guint32 stroke_type_id;
guint32 closed;
guint32 num_axes;
guint32 num_control_points;
guint32 type;
gfloat coords[13] = GIMP_COORDS_DEFAULT_VALUES;
GimpStroke *stroke;
gint j;
GimpValueArray *control_points;
GValue value = G_VALUE_INIT;
GimpAnchor anchor = { { 0, } };
GType stroke_type;
g_value_init (&value, GIMP_TYPE_ANCHOR);
xcf_read_int32 (info, &stroke_type_id, 1);
xcf_read_int32 (info, &closed, 1);
xcf_read_int32 (info, &num_axes, 1);
xcf_read_int32 (info, &num_control_points, 1);
#ifdef GIMP_XCF_PATH_DEBUG
g_printerr ("stroke_type: %d, closed: %d, num_axes %d, len %d\n",
stroke_type_id, closed, num_axes, num_control_points);
#endif
switch (stroke_type_id)
{
case XCF_STROKETYPE_BEZIER_STROKE:
stroke_type = GIMP_TYPE_BEZIER_STROKE;
break;
default:
g_printerr ("skipping unknown stroke type\n");
xcf_seek_pos (info,
info->cp + 4 * num_axes * num_control_points,
NULL);
continue;
}
if (num_axes < 2 || num_axes > 6)
{
g_printerr ("bad number of axes in stroke description\n");
goto error;
}
control_points = gimp_value_array_new (num_control_points);
anchor.selected = FALSE;
for (j = 0; j < num_control_points; j++)
{
xcf_read_int32 (info, &type, 1);
xcf_read_float (info, coords, num_axes);
anchor.type = type;
anchor.position.x = coords[0];
anchor.position.y = coords[1];
anchor.position.pressure = coords[2];
anchor.position.xtilt = coords[3];
anchor.position.ytilt = coords[4];
anchor.position.wheel = coords[5];
g_value_set_boxed (&value, &anchor);
gimp_value_array_append (control_points, &value);
#ifdef GIMP_XCF_PATH_DEBUG
g_printerr ("Anchor: %d, (%f, %f, %f, %f, %f, %f)\n", type,
coords[0], coords[1], coords[2], coords[3],
coords[4], coords[5]);
#endif
}
g_value_unset (&value);
stroke = g_object_new (stroke_type,
"closed", closed,
"control-points", control_points,
NULL);
gimp_vectors_stroke_add (vectors, stroke);
g_object_unref (stroke);
gimp_value_array_unref (control_points);
}
if (plength != info->cp - base)
{
gimp_message (info->gimp, G_OBJECT (info->progress),
GIMP_MESSAGE_WARNING,
"Path payload size does not match stored size (skipping)");
goto error;
}
return vectors;
error:
xcf_seek_pos (info, base + plength, NULL);
g_clear_object (&vectors);
return NULL;
}
static GimpLayerMask *
xcf_load_layer_mask (XcfInfo *info,
GimpImage *image)
@ -3174,6 +3571,7 @@ xcf_load_parasite (XcfInfo *info)
return parasite;
}
/* Old paths are the PROP_PATHS property, even older than PROP_VECTORS. */
static gboolean
xcf_load_old_paths (XcfInfo *info,
GimpImage *image)
@ -3302,9 +3700,10 @@ xcf_load_old_path (XcfInfo *info,
return TRUE;
}
/* Old vectors are the PROP_VECTORS property up to all GIMP 2.10 versions. */
static gboolean
xcf_load_vectors (XcfInfo *info,
GimpImage *image)
xcf_load_old_vectors (XcfInfo *info,
GimpImage *image)
{
guint32 version;
guint32 active_index;
@ -3312,7 +3711,7 @@ xcf_load_vectors (XcfInfo *info,
GimpVectors *active_vectors;
#ifdef GIMP_XCF_PATH_DEBUG
g_printerr ("xcf_load_vectors\n");
g_printerr ("xcf_load_old_vectors\n");
#endif
xcf_read_int32 (info, &version, 1);
@ -3333,7 +3732,7 @@ xcf_load_vectors (XcfInfo *info,
#endif
while (num_paths-- > 0)
if (! xcf_load_vector (info, image))
if (! xcf_load_old_vector (info, image))
return FALSE;
/* FIXME tree */
@ -3345,14 +3744,14 @@ xcf_load_vectors (XcfInfo *info,
gimp_image_set_active_vectors (image, active_vectors);
#ifdef GIMP_XCF_PATH_DEBUG
g_printerr ("xcf_load_vectors: loaded %d bytes\n", info->cp - base);
g_printerr ("xcf_load_old_vectors: loaded %d bytes\n", info->cp - base);
#endif
return TRUE;
}
static gboolean
xcf_load_vector (XcfInfo *info,
GimpImage *image)
xcf_load_old_vector (XcfInfo *info,
GimpImage *image)
{
gchar *name;
GimpTattoo tattoo = 0;
@ -3364,7 +3763,7 @@ xcf_load_vector (XcfInfo *info,
gint i;
#ifdef GIMP_XCF_PATH_DEBUG
g_printerr ("xcf_load_vector\n");
g_printerr ("xcf_load_old_vector\n");
#endif
xcf_read_string (info, &name, 1);

View File

@ -68,6 +68,7 @@ typedef enum
PROP_ITEM_SET = 40,
PROP_ITEM_SET_ITEM = 41,
PROP_LOCK_VISIBILITY = 42,
PROP_SELECTED_PATH = 43,
} PropType;
typedef enum
@ -110,6 +111,7 @@ struct _XcfInfo
GimpTattoo tattoo_state;
GList *selected_layers;
GList *selected_channels;
GList *selected_vectors;
/* Old deprecated "linked" concept which we keep in the XcfInfo
* probably forever to transform these tags into named stored item

View File

@ -85,6 +85,10 @@ static gboolean xcf_save_channel_props (XcfInfo *info,
GimpImage *image,
GimpChannel *channel,
GError **error);
static gboolean xcf_save_path_props (XcfInfo *info,
GimpImage *image,
GimpVectors *vectors,
GError **error);
static gboolean xcf_save_prop (XcfInfo *info,
GimpImage *image,
PropType prop_type,
@ -98,6 +102,10 @@ static gboolean xcf_save_channel (XcfInfo *info,
GimpImage *image,
GimpChannel *channel,
GError **error);
static gboolean xcf_save_path (XcfInfo *info,
GimpImage *image,
GimpVectors *vectors,
GError **error);
static gboolean xcf_save_buffer (XcfInfo *info,
GeglBuffer *buffer,
GError **error);
@ -129,7 +137,7 @@ static gboolean xcf_save_parasite_list (XcfInfo *info,
static gboolean xcf_save_old_paths (XcfInfo *info,
GimpImage *image,
GError **error);
static gboolean xcf_save_vectors (XcfInfo *info,
static gboolean xcf_save_old_vectors (XcfInfo *info,
GimpImage *image,
GError **error);
@ -224,16 +232,19 @@ xcf_save_image (XcfInfo *info,
{
GList *all_layers;
GList *all_channels;
GList *all_paths = NULL;
GList *list;
goffset saved_pos;
goffset offset;
guint32 value;
guint n_layers;
guint n_channels;
guint n_paths = 0;
guint progress = 0;
guint max_progress;
gchar version_tag[16];
GError *tmp_error = NULL;
gboolean write_paths = FALSE;
GError *tmp_error = NULL;
/* write out the tag information for the image */
if (info->file_version > 0)
@ -264,6 +275,9 @@ xcf_save_image (XcfInfo *info,
xcf_write_int32_check_error (info, &value, 1);
}
if (info->file_version >= 18)
write_paths = TRUE;
/* determine the number of layers and channels in the image */
all_layers = gimp_image_get_layer_list (image);
all_channels = gimp_image_get_channel_list (image);
@ -277,7 +291,13 @@ xcf_save_image (XcfInfo *info,
n_layers = (guint) g_list_length (all_layers);
n_channels = (guint) g_list_length (all_channels);
max_progress = 1 + n_layers + n_channels;
if (write_paths)
{
all_paths = gimp_image_get_vectors_list (image);
n_paths = (guint) g_list_length (all_paths);
}
max_progress = 1 + n_layers + n_channels + n_paths;
/* write the property information for the image */
xcf_check_error (xcf_save_image_props (info, image, error));
@ -288,7 +308,9 @@ xcf_save_image (XcfInfo *info,
saved_pos = info->cp;
/* write an empty offset table */
xcf_write_zero_offset_check_error (info, n_layers + n_channels + 2);
xcf_write_zero_offset_check_error (info,
n_layers + n_channels + 2 +
(write_paths ? n_paths + 1 : 0));
/* 'offset' is where we will write the next layer or channel */
offset = info->cp;
@ -344,12 +366,44 @@ xcf_save_image (XcfInfo *info,
xcf_progress_update (info);
}
if (write_paths)
{
/* skip a '0' in the offset table to indicate the end of the channel
* offsets
*/
saved_pos += info->bytes_per_offset;
for (list = all_paths; list; list = g_list_next (list))
{
GimpVectors *vectors = list->data;
/* seek back to the next slot in the offset table and write the
* offset of the channel
*/
xcf_check_error (xcf_seek_pos (info, saved_pos, error));
xcf_write_offset_check_error (info, &offset, 1);
/* remember the next slot in the offset table */
saved_pos = info->cp;
/* seek to the channel offset and save the channel */
xcf_check_error (xcf_seek_pos (info, offset, error));
xcf_check_error (xcf_save_path (info, image, vectors, error));
/* the next channels's offset is after the channel we just wrote */
offset = info->cp;
xcf_progress_update (info);
}
}
/* there is already a '0' at the end of the offset table to indicate
* the end of the channel offsets
*/
g_list_free (all_layers);
g_list_free (all_channels);
g_list_free (all_paths);
return ! g_output_stream_is_closed (info->output);
}
@ -408,7 +462,8 @@ xcf_save_image_props (XcfInfo *info,
if (unit < gimp_unit_get_number_of_built_in_units ())
xcf_check_error (xcf_save_prop (info, image, PROP_UNIT, error, unit));
if (gimp_container_get_n_children (gimp_image_get_vectors (image)) > 0)
if (gimp_container_get_n_children (gimp_image_get_vectors (image)) > 0 &&
info->file_version < 18)
{
if (gimp_vectors_compat_is_compatible (image))
xcf_check_error (xcf_save_prop (info, image, PROP_PATHS, error));
@ -714,6 +769,60 @@ xcf_save_channel_props (XcfInfo *info,
return TRUE;
}
static gboolean
xcf_save_path_props (XcfInfo *info,
GimpImage *image,
GimpVectors *vectors,
GError **error)
{
GimpParasiteList *parasites;
if (g_list_find (gimp_image_get_selected_vectors (image), vectors))
xcf_check_error (xcf_save_prop (info, image, PROP_SELECTED_PATH, error));
xcf_check_error (xcf_save_prop (info, image, PROP_VISIBLE, error,
gimp_item_get_visible (GIMP_ITEM (vectors))));
xcf_check_error (xcf_save_prop (info, image, PROP_COLOR_TAG, error,
gimp_item_get_color_tag (GIMP_ITEM (vectors))));
xcf_check_error (xcf_save_prop (info, image, PROP_LOCK_CONTENT, error,
gimp_item_get_lock_content (GIMP_ITEM (vectors))));
xcf_check_error (xcf_save_prop (info, image, PROP_LOCK_POSITION, error,
gimp_item_get_lock_position (GIMP_ITEM (vectors))));
xcf_check_error (xcf_save_prop (info, image, PROP_TATTOO, error,
gimp_item_get_tattoo (GIMP_ITEM (vectors))));
parasites = gimp_item_get_parasites (GIMP_ITEM (vectors));
if (gimp_parasite_list_length (parasites) > 0)
{
xcf_check_error (xcf_save_prop (info, image, PROP_PARASITES, error,
parasites));
}
#if 0
for (iter = info->vectors_sets; iter; iter = iter->next)
{
GimpItemList *set = iter->data;
if (! gimp_item_list_is_pattern (set, NULL))
{
GList *items = gimp_item_list_get_items (set, NULL);
if (g_list_find (items, GIMP_ITEM (vectors)))
xcf_check_error (xcf_save_prop (info, image, PROP_ITEM_SET_ITEM, error,
g_list_position (info->layer_sets, iter)));
g_list_free (items);
}
}
#endif
xcf_check_error (xcf_save_prop (info, image, PROP_END, error));
return TRUE;
}
static gboolean
xcf_save_prop (XcfInfo *info,
GimpImage *image,
@ -753,6 +862,7 @@ xcf_save_prop (XcfInfo *info,
case PROP_ACTIVE_LAYER:
case PROP_ACTIVE_CHANNEL:
case PROP_SELECTED_PATH:
case PROP_SELECTION:
case PROP_GROUP_ITEM:
size = 0;
@ -1352,7 +1462,7 @@ xcf_save_prop (XcfInfo *info,
base = info->cp;
xcf_check_error (xcf_save_vectors (info, image, error));
xcf_check_error (xcf_save_old_vectors (info, image, error));
size = info->cp - base;
@ -2124,12 +2234,13 @@ xcf_save_parasite_list (XcfInfo *info,
return TRUE;
}
/* This is the oldest way to save paths. */
static gboolean
xcf_save_old_paths (XcfInfo *info,
GimpImage *image,
GError **error)
{
GimpVectors *active_vectors;
GimpVectors *active_vectors = NULL;
guint32 num_paths;
guint32 active_index = 0;
GList *list;
@ -2145,7 +2256,16 @@ xcf_save_old_paths (XcfInfo *info,
num_paths = gimp_container_get_n_children (gimp_image_get_vectors (image));
active_vectors = gimp_image_get_active_vectors (image);
if (gimp_image_get_selected_vectors (image))
{
active_vectors = gimp_image_get_selected_vectors (image)->data;
/* Having more than 1 selected vectors should not have happened in this
* code path but let's not break saving, only produce a critical.
*/
if (g_list_length (gimp_image_get_selected_vectors (image)) > 1)
g_critical ("%s: this code path should not happen with multiple paths selected",
G_STRFUNC);
}
if (active_vectors)
active_index = gimp_container_get_child_index (gimp_image_get_vectors (image),
@ -2233,14 +2353,18 @@ xcf_save_old_paths (XcfInfo *info,
return TRUE;
}
/* This is an older way to save paths, though more recent than
* xcf_save_old_paths(). It used to be the normal path storing format until all
* 2.10 versions. It changed with GIMP 3.0.
*/
static gboolean
xcf_save_vectors (XcfInfo *info,
GimpImage *image,
GError **error)
xcf_save_old_vectors (XcfInfo *info,
GimpImage *image,
GError **error)
{
GimpVectors *active_vectors;
guint32 version = 1;
guint32 active_index = 0;
GimpVectors *active_vectors = NULL;
guint32 version = 1;
guint32 active_index = 0;
guint32 num_paths;
GList *list;
GList *stroke_list;
@ -2255,7 +2379,16 @@ xcf_save_vectors (XcfInfo *info,
* then each path:-
*/
active_vectors = gimp_image_get_active_vectors (image);
if (gimp_image_get_selected_vectors (image))
{
active_vectors = gimp_image_get_selected_vectors (image)->data;
/* Having more than 1 selected vectors should not have happened in this
* code path but let's not break saving, only produce a critical.
*/
if (g_list_length (gimp_image_get_selected_vectors (image)) > 1)
g_critical ("%s: this code path should not happen with multiple paths selected",
G_STRFUNC);
}
if (active_vectors)
active_index = gimp_container_get_child_index (gimp_image_get_vectors (image),
@ -2388,3 +2521,128 @@ xcf_save_vectors (XcfInfo *info,
return TRUE;
}
static gboolean
xcf_save_path (XcfInfo *info,
GimpImage *image,
GimpVectors *vectors,
GError **error)
{
const gchar *string;
GList *stroke_list;
GError *tmp_error = NULL;
/* Version of the path format is always 1 for now. */
guint32 version = 1;
guint32 num_strokes;
guint32 size;
goffset base;
goffset pos;
/* write out the path name */
string = gimp_object_get_name (vectors);
xcf_write_string_check_error (info, (gchar **) &string, 1);
/* Payload size */
size = 0;
pos = info->cp;
xcf_write_int32_check_error (info, &size, 1);
base = info->cp;
/* write out the path properties */
xcf_save_path_props (info, image, vectors, error);
/* Path version */
xcf_write_int32_check_error (info, &version, 1);
/* Write out the number of strokes. */
num_strokes = g_queue_get_length (vectors->strokes);
xcf_write_int32_check_error (info, &num_strokes, 1);
for (stroke_list = g_list_first (vectors->strokes->head);
stroke_list;
stroke_list = g_list_next (stroke_list))
{
GimpStroke *stroke = stroke_list->data;
guint32 stroke_type;
guint32 closed;
guint32 num_axes;
GArray *control_points;
gint i;
guint32 type;
gfloat coords[6];
/*
* stroke_type (gint)
* closed (gint)
* num_axes (gint)
* num_control_points (gint)
*
* then each control point.
*/
if (GIMP_IS_BEZIER_STROKE (stroke))
{
stroke_type = XCF_STROKETYPE_BEZIER_STROKE;
num_axes = 2; /* hardcoded, might be increased later */
}
else
{
g_printerr ("Skipping unknown stroke type!\n");
continue;
}
control_points = gimp_stroke_control_points_get (stroke,
(gint32 *) &closed);
/* Stroke type. */
xcf_write_int32_check_error (info, &stroke_type, 1);
/* close path or not? */
xcf_write_int32_check_error (info, &closed, 1);
/* Number of floats given for each point. */
xcf_write_int32_check_error (info, &num_axes, 1);
/* Number of control points. */
xcf_write_int32_check_error (info, &control_points->len, 1);
for (i = 0; i < control_points->len; i++)
{
GimpAnchor *anchor;
anchor = & (g_array_index (control_points, GimpAnchor, i));
type = anchor->type;
coords[0] = anchor->position.x;
coords[1] = anchor->position.y;
coords[2] = anchor->position.pressure;
coords[3] = anchor->position.xtilt;
coords[4] = anchor->position.ytilt;
coords[5] = anchor->position.wheel;
/*
* type (gint)
*
* the first num_axis elements of:
* [0] x (gfloat)
* [1] y (gfloat)
* [2] pressure (gfloat)
* [3] xtilt (gfloat)
* [4] ytilt (gfloat)
* [5] wheel (gfloat)
*/
xcf_write_int32_check_error (info, &type, 1);
xcf_write_float_check_error (info, coords, num_axes);
}
g_array_free (control_points, TRUE);
}
/* go back to the saved position and write the length */
size = info->cp - base;
xcf_check_error (xcf_seek_pos (info, pos, error));
xcf_write_int32_check_error (info, &size, 1);
xcf_check_error (xcf_seek_pos (info, base + size, error));
return TRUE;
}

View File

@ -86,6 +86,7 @@ static GimpXcfLoaderFunc * const xcf_loaders[] =
xcf_load_image, /* version 15 */
xcf_load_image, /* version 16 */
xcf_load_image, /* version 17 */
xcf_load_image, /* version 18 */
};

View File

@ -199,6 +199,11 @@ Since GIMP 3.0.0, released on TODO.
- New PROP_LOCK_VISIBILITY on layers and channels.
- PROP_LOCK_POSITION and PROP_LOCK_ALPHA can now be set on layer groups.
Vection 18:
Since GIMP 3.0.0, released on TODO.
- New Vectors Structure for storing paths with properties.
1. BASIC CONCEPTS
=================
@ -506,8 +511,9 @@ names which will be ignored by other plug-ins.
A list of known parasites and their data formats can be found in the
file devel-doc/parasites.txt of the GIMP source tree.
The PROP_PARASITE property stores the parasites of the image, layers
and channels and the PROP_VECTORS property those of the paths.
The PROP_PARASITE property stores the parasites of the image, layers, channels
and vectors structures. The PROP_VECTORS stores those of paths when using this
image property instead of proper vectors structure.
The number of parasites there is not directly encoded; the list ends when
the total length of the parasite data read equals the property payload length.
@ -602,6 +608,11 @@ If all paths are continuous sequences of Bezier strokes, then GIMP uses
the PROP_PATHS property, otherwise PROP_VECTORS. PROP_PATHS is for old
files from GIMP up to version 1.2.
If more than 2 paths are selected or if a path have a color tag, a content lock
or a position lock, GIMP will use vectors structures, which bumps the XCF to at
least version 18. If the XCF is already 18 or later, GIMP will always use
vectors structure.
2. GENERAL PROPERTIES
=====================
@ -728,8 +739,9 @@ PROP_TATTOO (internal GIMP state)
uint32 4 Four bytes of payload
uint32 tattoo Nonzero unsigned integer identifier
PROP_TATTOO is an unique identifier for the denoted image, channel or layer.
It appears in the property list of layers, channels, and the image.
PROP_TATTOO is an unique identifier for the denoted image, channel, layer or
path.
It appears in the property list of layers, channels, vectors and the image.
PROP_VISIBLE (essential)
uint32 8 Type identification
@ -737,8 +749,10 @@ PROP_VISIBLE (essential)
uint32 visible 1 if the layer/channel is visible; 0 if not
PROP_VISIBLE specifies the visibility of a layer or channel.
It appears in the property list for layers and channels.
For the visibility of a path see the PROP_VECTORS property.
It appears in the property list for layers, channels and paths.
For the visibility of a path stored with the older PROP_VECTORS property, see
this property's description.
When reading old XCF files that lack this property, assume that
layers are visible and channels are not.
@ -827,6 +841,10 @@ The image structure always starts at offset 0 in the XCF file.
| pointer cptr Pointer to the channel structure.
`--
pointer 0 Zero marks the end of the array of channel pointers.
,------------------ Repeat once for each path, in no particular order:
| pointer cptr Pointer to the vectors structure.
`--
pointer 0 Zero marks the end of the array of vectors pointers.
The last 4 characters of the initial 13-character identification string are
a version indicator. The version will be higher than 3 if the correct
@ -960,7 +978,7 @@ PROP_PATHS
This format is used to save path data if all paths in the image are
continuous sequences of Bezier strokes. Otherwise GIMP stores the paths in
PROP_VECTORS.
PROP_VECTORS or in Vectors Structure.
Note: the attribute 'linked' was formerly erroneously called 'locked'
(but meant 'linked' anyway).
@ -1526,8 +1544,54 @@ PROP_TEXT_LAYER_FLAGS
The actual text (and other parameters such as font and color) is a
parasite rather than a property.
6. THE VECTORS STRUCTURE
========================
6. THE HIERARCHY STRUCTURE
Vectors structures are pointed to from the master image structure.
string name Name of the path
uint32 plength Total length of the following payload in bytes
property-list Vectors properties
uint32 1 Version tag; so far always 1
uint32 k Number of strokes in the path
,-------------------- Repeat k times:
| uint32 1 The stroke is a Bezier stroke
| uint32 closed 1 if path is closed; 0 otherwise
| uint32 nf Number of floats given for each point;
| must be >= 2 and <= 6.
| uint32 np Number of control points for this stroke
| ,------------------ Repeat np times:
| | uint32 type Type of the first point; one of
| | 0: Anchor
| | 1: Bezier control point
| | float x X coordinate
| | float y Y coordinate
| | float pressure Only if nf >= 3; otherwise defaults to 1.0
| | float xtilt Only if nf >= 4; otherwise defaults to 0.5
| | float ytilt Only if nf >= 5; otherwise defaults to 0.5
| | float wheel Only if nf == 6; otherwise defaults to 0.5
| `--
`--
Vectors properties
------------------
The following properties are found only in the property list of
vectors structures. Additionally the list can also contain the
properties: PROP_COLOR_TAG, PROP_END, PROP_LOCK_CONTENT, PROP_LOCK_POSITION,
PROP_PARASITES, PROP_TATTOO and PROP_VISIBLE, defined in chapter 2.
PROP_SELECTED_PATH (editing state)
uint32 43 Type identification
uint32 0 PROP_SELECTED_PATH has no payload
The presence of PROP_SELECTED_PATH indicates that the path is currently
selected.
It appears in the property list of any currently selected path.
Any number of paths (or none) can have this property at any time.
7. THE HIERARCHY STRUCTURE
==========================
A hierarchy contains data for a rectangular array of pixels.
@ -1581,7 +1645,7 @@ hierarchy structure (except for the dummy levels).
Ceil(x) is the smallest integer not smaller than x.
7. TILE DATA ORGANIZATION
8. TILE DATA ORGANIZATION
=========================
The format of the data blocks pointed to by the tile pointers in the
@ -1683,7 +1747,7 @@ TODO: If each tile has a maximum of 64 pixels (resulting in a maximum of 64
bytes for each color in this tile), do values>64 and long runs apply at all?
8. MISCELLANEOUS
9. MISCELLANEOUS
================