2006-12-09 13:33:38 -08:00
|
|
|
/* GIMP - The GNU Image Manipulation Program
|
2001-07-03 11:38:56 -07:00
|
|
|
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
|
|
|
|
*
|
2009-01-17 14:28:01 -08:00
|
|
|
* This program is free software: you can redistribute it and/or modify
|
2001-07-03 11:38:56 -07:00
|
|
|
* it under the terms of the GNU General Public License as published by
|
2009-01-17 14:28:01 -08:00
|
|
|
* the Free Software Foundation; either version 3 of the License, or
|
2001-07-03 11:38:56 -07:00
|
|
|
* (at your option) any later version.
|
|
|
|
|
*
|
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
|
*
|
|
|
|
|
* You should have received a copy of the GNU General Public License
|
2018-07-11 14:27:07 -07:00
|
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
2001-07-03 11:38:56 -07:00
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include "config.h"
|
|
|
|
|
|
2008-10-09 13:24:04 -07:00
|
|
|
#include <string.h>
|
2014-09-15 06:33:22 -07:00
|
|
|
#include <zlib.h>
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2011-04-28 06:50:39 -07:00
|
|
|
#include <cairo.h>
|
2008-10-09 13:24:04 -07:00
|
|
|
#include <gegl.h>
|
2012-05-02 18:36:22 -07:00
|
|
|
#include <gdk-pixbuf/gdk-pixbuf.h>
|
2001-07-03 11:38:56 -07:00
|
|
|
|
|
|
|
|
#include "libgimpbase/gimpbase.h"
|
|
|
|
|
#include "libgimpcolor/gimpcolor.h"
|
2024-01-22 08:49:10 -08:00
|
|
|
#include "libgimpconfig/gimpconfig.h"
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2021-06-19 04:41:54 -07:00
|
|
|
#include "config/gimpgeglconfig.h"
|
|
|
|
|
|
2001-07-03 11:38:56 -07:00
|
|
|
#include "core/core-types.h"
|
|
|
|
|
|
2012-04-25 04:02:20 -07:00
|
|
|
#include "gegl/gimp-babl-compat.h"
|
2012-04-02 15:00:15 -07:00
|
|
|
#include "gegl/gimp-gegl-tile-compat.h"
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2006-08-08 14:06:36 -07:00
|
|
|
#include "core/gimp.h"
|
2008-11-02 12:46:57 -08:00
|
|
|
#include "core/gimpcontainer.h"
|
2001-07-03 11:38:56 -07:00
|
|
|
#include "core/gimpchannel.h"
|
2025-08-16 14:43:39 -07:00
|
|
|
#include "core/gimpdashpattern.h"
|
|
|
|
|
#include "core/gimpdata.h"
|
2001-07-03 11:38:56 -07:00
|
|
|
#include "core/gimpdrawable.h"
|
2023-06-19 07:54:21 -07:00
|
|
|
#include "core/gimpdrawable-filters.h"
|
|
|
|
|
#include "core/gimpdrawablefilter.h"
|
2025-08-16 14:43:39 -07:00
|
|
|
#include "core/gimpfilloptions.h"
|
2003-07-04 12:55:58 -07:00
|
|
|
#include "core/gimpgrid.h"
|
2006-06-06 15:48:57 -07:00
|
|
|
#include "core/gimpguide.h"
|
2001-07-03 11:38:56 -07:00
|
|
|
#include "core/gimpimage.h"
|
2007-12-21 08:37:01 -08:00
|
|
|
#include "core/gimpimage-colormap.h"
|
2003-07-04 12:55:58 -07:00
|
|
|
#include "core/gimpimage-grid.h"
|
2003-10-06 05:17:11 -07:00
|
|
|
#include "core/gimpimage-guides.h"
|
2013-10-19 09:38:01 -07:00
|
|
|
#include "core/gimpimage-metadata.h"
|
2010-02-03 14:42:32 -08:00
|
|
|
#include "core/gimpimage-private.h"
|
2006-08-10 10:10:12 -07:00
|
|
|
#include "core/gimpimage-sample-points.h"
|
2016-01-27 10:18:53 -08:00
|
|
|
#include "core/gimpimage-symmetry.h"
|
2021-12-22 13:53:15 -08:00
|
|
|
#include "core/gimpitemlist.h"
|
2001-07-03 11:38:56 -07:00
|
|
|
#include "core/gimplayer.h"
|
|
|
|
|
#include "core/gimplayermask.h"
|
2019-07-08 07:48:56 -07:00
|
|
|
#include "core/gimplink.h"
|
|
|
|
|
#include "core/gimplinklayer.h"
|
2023-06-19 07:54:21 -07:00
|
|
|
#include "core/gimplist.h"
|
2001-07-09 12:48:30 -07:00
|
|
|
#include "core/gimpparasitelist.h"
|
2006-07-11 13:21:18 -07:00
|
|
|
#include "core/gimpprogress.h"
|
2025-10-12 16:09:50 -07:00
|
|
|
#include "core/gimprasterizable.h"
|
2007-01-30 02:34:59 -08:00
|
|
|
#include "core/gimpsamplepoint.h"
|
2025-08-16 14:43:39 -07:00
|
|
|
#include "core/gimpstrokeoptions.h"
|
2016-01-27 10:18:53 -08:00
|
|
|
#include "core/gimpsymmetry.h"
|
2001-07-03 11:38:56 -07:00
|
|
|
|
app: future-proof XCF layer blend/composite props
The layer blend space, composite space, and composite mode
properties have a special AUTO value, which may map to different
concrete values based on the layer mode. Make sure we can change
this mapping in the future, without affecting existing XCFs (saved
after this commit), by encoding these properties as follows:
When saving an XCF, if the property has a concrete (non-AUTO)
value, which is always positive, encode it as is. If the property
is AUTO, which is always 0, encode it as the negative of the value
it actually maps to at the time of saving (note that in some cases
AUTO may map to AUTO, in which case it's encoded as 0).
When loading an XCF, if the encoded property (stored in the file)
is nonnegative, use it as is. Otherwise, compare the negative of
the encoded property to the value AUTO maps to at the time of
loading. If the values are equal, set the property to AUTO;
otherwise, use the concrete value (i.e., the negative of the value
stored in the XCF).
Note that XCFs saved prior to this commit still load fine, it's
simply that if we change the AUTO mapping in the future, all their
AUTO properties will keep being loaded as AUTO, even if the
resulting concrete values will have changed.
2017-05-21 05:01:39 -07:00
|
|
|
#include "operations/layer-modes/gimp-layer-modes.h"
|
|
|
|
|
|
2025-07-07 06:43:07 -07:00
|
|
|
#include "path/gimpanchor.h"
|
|
|
|
|
#include "path/gimpbezierstroke.h"
|
|
|
|
|
#include "path/gimppath.h"
|
|
|
|
|
#include "path/gimpstroke.h"
|
|
|
|
|
#include "path/gimppath-compat.h"
|
2025-03-24 19:39:49 -07:00
|
|
|
#include "path/gimpvectorlayer.h"
|
2025-08-16 14:43:39 -07:00
|
|
|
#include "path/gimpvectorlayeroptions.h"
|
2025-07-07 06:43:07 -07:00
|
|
|
|
2003-06-24 06:59:36 -07:00
|
|
|
#include "text/gimptextlayer.h"
|
2003-10-27 13:50:41 -08:00
|
|
|
#include "text/gimptextlayer-xcf.h"
|
2003-06-24 06:59:36 -07:00
|
|
|
|
2001-07-03 11:38:56 -07:00
|
|
|
#include "xcf-private.h"
|
|
|
|
|
#include "xcf-read.h"
|
2006-10-30 02:13:06 -08:00
|
|
|
#include "xcf-save.h"
|
2001-07-03 11:38:56 -07:00
|
|
|
#include "xcf-seek.h"
|
|
|
|
|
#include "xcf-write.h"
|
|
|
|
|
|
2003-03-25 08:38:19 -08:00
|
|
|
#include "gimp-intl.h"
|
2002-12-19 22:26:34 -08:00
|
|
|
|
2022-10-24 14:39:06 -07:00
|
|
|
typedef void (* CompressTileFunc) (GeglRectangle *tile_rect,
|
|
|
|
|
guchar *tile_data,
|
|
|
|
|
const Babl *format,
|
|
|
|
|
guchar *out_data,
|
|
|
|
|
gint out_data_max_len,
|
|
|
|
|
gint *lenptr);
|
2003-05-21 10:38:14 -07:00
|
|
|
|
2022-10-24 08:04:34 -07:00
|
|
|
/* Per thread data for xcf_save_tile_rle */
|
|
|
|
|
typedef struct
|
|
|
|
|
{
|
|
|
|
|
/* Common to all jobs. */
|
2022-10-24 14:39:06 -07:00
|
|
|
GeglBuffer *buffer;
|
|
|
|
|
gint file_version;
|
|
|
|
|
gint max_out_data_len;
|
|
|
|
|
CompressTileFunc compress;
|
2022-10-24 08:04:34 -07:00
|
|
|
|
|
|
|
|
/* Job specific. */
|
2022-10-24 14:39:06 -07:00
|
|
|
gint tile;
|
|
|
|
|
gint batch_size;
|
2022-10-24 08:04:34 -07:00
|
|
|
|
|
|
|
|
/* Temp data to avoid too many allocations. */
|
2022-10-24 14:39:06 -07:00
|
|
|
guchar *tile_data;
|
2022-10-24 08:04:34 -07:00
|
|
|
|
|
|
|
|
/* Return data. */
|
2022-10-24 14:39:06 -07:00
|
|
|
guchar *out_data;
|
|
|
|
|
gint out_data_len[XCF_TILE_SAVE_BATCH_SIZE];
|
2022-10-24 08:04:34 -07:00
|
|
|
} XcfJobData;
|
|
|
|
|
|
2003-09-10 09:22:33 -07:00
|
|
|
static gboolean xcf_save_image_props (XcfInfo *info,
|
2006-03-28 09:08:36 -08:00
|
|
|
GimpImage *image,
|
2003-09-10 09:22:33 -07:00
|
|
|
GError **error);
|
|
|
|
|
static gboolean xcf_save_layer_props (XcfInfo *info,
|
2006-03-28 09:08:36 -08:00
|
|
|
GimpImage *image,
|
2003-09-10 09:22:33 -07:00
|
|
|
GimpLayer *layer,
|
|
|
|
|
GError **error);
|
|
|
|
|
static gboolean xcf_save_channel_props (XcfInfo *info,
|
2006-03-28 09:08:36 -08:00
|
|
|
GimpImage *image,
|
2003-09-10 09:22:33 -07:00
|
|
|
GimpChannel *channel,
|
|
|
|
|
GError **error);
|
2023-06-19 07:54:21 -07:00
|
|
|
static gboolean xcf_save_effect_props (XcfInfo *info,
|
|
|
|
|
GimpImage *image,
|
|
|
|
|
GimpFilter *filter,
|
|
|
|
|
GError **error);
|
2022-10-19 15:11:34 -07:00
|
|
|
static gboolean xcf_save_path_props (XcfInfo *info,
|
|
|
|
|
GimpImage *image,
|
2025-07-07 07:05:07 -07:00
|
|
|
GimpPath *path,
|
2022-10-19 15:11:34 -07:00
|
|
|
GError **error);
|
2003-09-10 09:22:33 -07:00
|
|
|
static gboolean xcf_save_prop (XcfInfo *info,
|
2006-03-28 09:08:36 -08:00
|
|
|
GimpImage *image,
|
2003-09-10 09:22:33 -07:00
|
|
|
PropType prop_type,
|
|
|
|
|
GError **error,
|
2003-05-15 05:47:42 -07:00
|
|
|
...);
|
2003-09-10 09:22:33 -07:00
|
|
|
static gboolean xcf_save_layer (XcfInfo *info,
|
2006-03-28 09:08:36 -08:00
|
|
|
GimpImage *image,
|
2003-09-10 09:22:33 -07:00
|
|
|
GimpLayer *layer,
|
|
|
|
|
GError **error);
|
|
|
|
|
static gboolean xcf_save_channel (XcfInfo *info,
|
2006-03-28 09:08:36 -08:00
|
|
|
GimpImage *image,
|
2003-09-10 09:22:33 -07:00
|
|
|
GimpChannel *channel,
|
|
|
|
|
GError **error);
|
2023-06-19 07:54:21 -07:00
|
|
|
static gboolean xcf_save_effect (XcfInfo *info,
|
|
|
|
|
GimpImage *image,
|
|
|
|
|
GimpFilter *filter,
|
|
|
|
|
GError **error);
|
2025-08-14 13:10:20 -07:00
|
|
|
static gboolean xcf_save_color (XcfInfo *info,
|
|
|
|
|
GeglColor *color,
|
|
|
|
|
GError **error);
|
2025-08-16 14:43:39 -07:00
|
|
|
static gboolean xcf_save_fill_options (XcfInfo *info,
|
|
|
|
|
GimpFillOptions *fill_options,
|
|
|
|
|
GError **error);
|
|
|
|
|
static gboolean xcf_save_stroke_options (XcfInfo *info,
|
|
|
|
|
GimpStrokeOptions *stroke_options,
|
|
|
|
|
GError **error);
|
2022-10-19 15:11:34 -07:00
|
|
|
static gboolean xcf_save_path (XcfInfo *info,
|
|
|
|
|
GimpImage *image,
|
2025-07-07 07:05:07 -07:00
|
|
|
GimpPath *path,
|
2022-10-19 15:11:34 -07:00
|
|
|
GError **error);
|
2012-04-02 15:00:15 -07:00
|
|
|
static gboolean xcf_save_buffer (XcfInfo *info,
|
2022-10-23 01:38:21 -07:00
|
|
|
GimpImage *image,
|
2012-04-02 15:00:15 -07:00
|
|
|
GeglBuffer *buffer,
|
2003-09-10 09:22:33 -07:00
|
|
|
GError **error);
|
|
|
|
|
static gboolean xcf_save_level (XcfInfo *info,
|
2022-10-23 01:38:21 -07:00
|
|
|
GimpImage *image,
|
2012-04-02 15:00:15 -07:00
|
|
|
GeglBuffer *buffer,
|
2003-09-10 09:22:33 -07:00
|
|
|
GError **error);
|
|
|
|
|
static gboolean xcf_save_tile (XcfInfo *info,
|
2012-04-02 15:00:15 -07:00
|
|
|
GeglBuffer *buffer,
|
|
|
|
|
GeglRectangle *tile_rect,
|
2012-04-20 14:45:22 -07:00
|
|
|
const Babl *format,
|
2003-09-10 09:22:33 -07:00
|
|
|
GError **error);
|
2022-10-24 08:04:34 -07:00
|
|
|
static void xcf_save_free_job_data (XcfJobData *data);
|
|
|
|
|
static gint xcf_save_sort_job_data (XcfJobData *data1,
|
|
|
|
|
XcfJobData *data2,
|
2021-06-19 04:41:54 -07:00
|
|
|
gpointer user_data);
|
2022-10-24 14:39:06 -07:00
|
|
|
static void xcf_save_tile_parallel (XcfJobData *job_data,
|
2022-10-24 08:04:34 -07:00
|
|
|
GAsyncQueue *queue);
|
2022-10-24 14:39:06 -07:00
|
|
|
static void xcf_save_tile_rle (GeglRectangle *tile_rect,
|
2022-10-24 08:04:34 -07:00
|
|
|
guchar *tile_data,
|
|
|
|
|
const Babl *format,
|
|
|
|
|
guchar *rlebuf,
|
2022-10-24 14:39:06 -07:00
|
|
|
gint rlebuf_max_len,
|
|
|
|
|
gint *lenptr);
|
|
|
|
|
static void xcf_save_tile_zlib (GeglRectangle *tile_rect,
|
|
|
|
|
guchar *tile_data,
|
2014-09-15 06:33:22 -07:00
|
|
|
const Babl *format,
|
2022-10-24 14:39:06 -07:00
|
|
|
guchar *zlib_data,
|
|
|
|
|
gint zlib_data_max_len,
|
|
|
|
|
gint *lenptr);
|
2003-09-10 09:22:33 -07:00
|
|
|
static gboolean xcf_save_parasite (XcfInfo *info,
|
|
|
|
|
GimpParasite *parasite,
|
|
|
|
|
GError **error);
|
|
|
|
|
static gboolean xcf_save_parasite_list (XcfInfo *info,
|
|
|
|
|
GimpParasiteList *parasite,
|
|
|
|
|
GError **error);
|
|
|
|
|
static gboolean xcf_save_old_paths (XcfInfo *info,
|
2006-03-28 09:08:36 -08:00
|
|
|
GimpImage *image,
|
2003-09-10 09:22:33 -07:00
|
|
|
GError **error);
|
2022-10-19 15:11:34 -07:00
|
|
|
static gboolean xcf_save_old_vectors (XcfInfo *info,
|
2006-03-28 09:08:36 -08:00
|
|
|
GimpImage *image,
|
2003-09-10 09:22:33 -07:00
|
|
|
GError **error);
|
2003-05-15 05:47:42 -07:00
|
|
|
|
2002-12-19 22:26:34 -08:00
|
|
|
|
2004-01-26 01:22:06 -08:00
|
|
|
/* private convenience macros */
|
2023-05-11 06:18:33 -07:00
|
|
|
#define xcf_write_int32_check_error(info, data, count, cleanup_code) G_STMT_START { \
|
2017-03-23 04:42:38 -07:00
|
|
|
xcf_write_int32 (info, data, count, &tmp_error); \
|
|
|
|
|
if (tmp_error) \
|
|
|
|
|
{ \
|
|
|
|
|
g_propagate_error (error, tmp_error); \
|
2023-05-11 06:18:33 -07:00
|
|
|
cleanup_code; \
|
2017-03-23 04:42:38 -07:00
|
|
|
return FALSE; \
|
|
|
|
|
} \
|
|
|
|
|
} G_STMT_END
|
|
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
#define xcf_write_offset_check_error(info, data, count, cleanup_code) G_STMT_START { \
|
2017-03-23 04:42:38 -07:00
|
|
|
xcf_write_offset (info, data, count, &tmp_error); \
|
2013-10-08 12:22:14 -07:00
|
|
|
if (tmp_error) \
|
|
|
|
|
{ \
|
|
|
|
|
g_propagate_error (error, tmp_error); \
|
2023-05-11 06:18:33 -07:00
|
|
|
cleanup_code; \
|
2013-10-08 12:22:14 -07:00
|
|
|
return FALSE; \
|
|
|
|
|
} \
|
2003-04-10 07:15:24 -07:00
|
|
|
} G_STMT_END
|
|
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
#define xcf_write_zero_offset_check_error(info, count, cleanup_code) G_STMT_START { \
|
2017-03-23 04:42:38 -07:00
|
|
|
xcf_write_zero_offset (info, count, &tmp_error); \
|
|
|
|
|
if (tmp_error) \
|
|
|
|
|
{ \
|
|
|
|
|
g_propagate_error (error, tmp_error); \
|
2023-05-11 06:18:33 -07:00
|
|
|
cleanup_code; \
|
2017-03-23 04:42:38 -07:00
|
|
|
return FALSE; \
|
|
|
|
|
} \
|
2017-03-23 03:44:41 -07:00
|
|
|
} G_STMT_END
|
|
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
#define xcf_write_int8_check_error(info, data, count, cleanup_code) G_STMT_START { \
|
2017-03-23 04:42:38 -07:00
|
|
|
xcf_write_int8 (info, data, count, &tmp_error); \
|
|
|
|
|
if (tmp_error) \
|
|
|
|
|
{ \
|
|
|
|
|
g_propagate_error (error, tmp_error); \
|
2023-05-11 06:18:33 -07:00
|
|
|
cleanup_code; \
|
2017-03-23 04:42:38 -07:00
|
|
|
return FALSE; \
|
|
|
|
|
} \
|
2014-10-17 10:12:05 -07:00
|
|
|
} G_STMT_END
|
|
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
#define xcf_write_float_check_error(info, data, count, cleanup_code) G_STMT_START { \
|
2017-03-23 04:42:38 -07:00
|
|
|
xcf_write_float (info, data, count, &tmp_error); \
|
2013-10-08 12:22:14 -07:00
|
|
|
if (tmp_error) \
|
|
|
|
|
{ \
|
|
|
|
|
g_propagate_error (error, tmp_error); \
|
2023-05-11 06:18:33 -07:00
|
|
|
cleanup_code; \
|
2013-10-08 12:22:14 -07:00
|
|
|
return FALSE; \
|
|
|
|
|
} \
|
2003-04-10 07:15:24 -07:00
|
|
|
} G_STMT_END
|
|
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
#define xcf_write_string_check_error(info, data, count, cleanup_code) G_STMT_START { \
|
2017-03-23 04:42:38 -07:00
|
|
|
xcf_write_string (info, data, count, &tmp_error); \
|
2013-10-08 12:22:14 -07:00
|
|
|
if (tmp_error) \
|
|
|
|
|
{ \
|
|
|
|
|
g_propagate_error (error, tmp_error); \
|
2023-05-11 06:18:33 -07:00
|
|
|
cleanup_code; \
|
2013-10-08 12:22:14 -07:00
|
|
|
return FALSE; \
|
|
|
|
|
} \
|
2003-04-10 07:15:24 -07:00
|
|
|
} G_STMT_END
|
|
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
#define xcf_write_component_check_error(info, bpc, data, count, cleanup_code) G_STMT_START { \
|
2017-09-16 10:03:20 -07:00
|
|
|
xcf_write_component (info, bpc, data, count, &tmp_error); \
|
|
|
|
|
if (tmp_error) \
|
|
|
|
|
{ \
|
|
|
|
|
g_propagate_error (error, tmp_error); \
|
2023-05-11 06:18:33 -07:00
|
|
|
cleanup_code; \
|
2017-09-16 10:03:20 -07:00
|
|
|
return FALSE; \
|
|
|
|
|
} \
|
|
|
|
|
} G_STMT_END
|
|
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
#define xcf_write_prop_type_check_error(info, prop_type, cleanup_code) G_STMT_START { \
|
|
|
|
|
guint32 _prop_int32 = prop_type; \
|
|
|
|
|
xcf_write_int32_check_error (info, &_prop_int32, 1, cleanup_code); \
|
2004-01-26 01:22:06 -08:00
|
|
|
} G_STMT_END
|
|
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
#define xcf_check_error(x, cleanup_code) G_STMT_START { \
|
2017-03-23 04:42:38 -07:00
|
|
|
if (! (x)) \
|
2023-05-11 06:18:33 -07:00
|
|
|
{ \
|
|
|
|
|
cleanup_code; \
|
|
|
|
|
return FALSE; \
|
|
|
|
|
} \
|
2003-04-10 07:15:24 -07:00
|
|
|
} G_STMT_END
|
2003-09-10 03:40:57 -07:00
|
|
|
|
2006-07-11 13:21:18 -07:00
|
|
|
#define xcf_progress_update(info) G_STMT_START \
|
|
|
|
|
{ \
|
|
|
|
|
progress++; \
|
|
|
|
|
if (info->progress) \
|
|
|
|
|
gimp_progress_set_value (info->progress, \
|
|
|
|
|
(gdouble) progress / (gdouble) max_progress); \
|
|
|
|
|
} G_STMT_END
|
|
|
|
|
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2014-09-27 11:38:43 -07:00
|
|
|
gboolean
|
2007-12-02 23:44:49 -08:00
|
|
|
xcf_save_image (XcfInfo *info,
|
|
|
|
|
GimpImage *image,
|
|
|
|
|
GError **error)
|
2001-07-03 11:38:56 -07:00
|
|
|
{
|
2009-08-30 12:19:42 -07:00
|
|
|
GList *all_layers;
|
|
|
|
|
GList *all_channels;
|
2022-10-19 15:11:34 -07:00
|
|
|
GList *all_paths = NULL;
|
2009-08-30 12:19:42 -07:00
|
|
|
GList *list;
|
2017-03-23 03:44:41 -07:00
|
|
|
goffset saved_pos;
|
|
|
|
|
goffset offset;
|
2009-08-30 12:19:42 -07:00
|
|
|
guint32 value;
|
|
|
|
|
guint n_layers;
|
|
|
|
|
guint n_channels;
|
2022-10-19 15:11:34 -07:00
|
|
|
guint n_paths = 0;
|
2009-08-30 12:19:42 -07:00
|
|
|
guint progress = 0;
|
|
|
|
|
guint max_progress;
|
|
|
|
|
gchar version_tag[16];
|
2022-10-19 15:11:34 -07:00
|
|
|
gboolean write_paths = FALSE;
|
|
|
|
|
GError *tmp_error = NULL;
|
2001-07-03 11:38:56 -07:00
|
|
|
|
|
|
|
|
/* write out the tag information for the image */
|
2003-09-10 03:40:57 -07:00
|
|
|
if (info->file_version > 0)
|
2001-07-03 11:38:56 -07:00
|
|
|
{
|
2018-08-21 03:19:55 -07:00
|
|
|
g_snprintf (version_tag, sizeof (version_tag),
|
|
|
|
|
"gimp xcf v%03d", info->file_version);
|
2003-09-10 03:40:57 -07:00
|
|
|
}
|
|
|
|
|
else
|
2001-07-03 11:38:56 -07:00
|
|
|
{
|
2026-03-30 16:47:09 -07:00
|
|
|
#ifndef _UCRT
|
2001-07-03 11:38:56 -07:00
|
|
|
strcpy (version_tag, "gimp xcf file");
|
2026-03-30 16:47:09 -07:00
|
|
|
#else
|
|
|
|
|
strcpy_s (version_tag, sizeof (version_tag), "gimp xcf file");
|
|
|
|
|
#endif
|
2001-07-03 11:38:56 -07:00
|
|
|
}
|
2006-07-11 13:21:18 -07:00
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_int8_check_error (info, (guint8 *) version_tag, 14, ;);
|
2001-07-03 11:38:56 -07:00
|
|
|
|
|
|
|
|
/* write out the width, height and image type information for the image */
|
2007-12-25 08:21:40 -08:00
|
|
|
value = gimp_image_get_width (image);
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_int32_check_error (info, (guint32 *) &value, 1, ;);
|
2007-12-25 08:21:40 -08:00
|
|
|
|
|
|
|
|
value = gimp_image_get_height (image);
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_int32_check_error (info, (guint32 *) &value, 1, ;);
|
2007-04-12 07:48:04 -07:00
|
|
|
|
2012-05-07 12:57:33 -07:00
|
|
|
value = gimp_image_get_base_type (image);
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_int32_check_error (info, &value, 1, ;);
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2012-04-27 07:42:19 -07:00
|
|
|
if (info->file_version >= 4)
|
|
|
|
|
{
|
|
|
|
|
value = gimp_image_get_precision (image);
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_int32_check_error (info, &value, 1, ;);
|
2012-04-27 07:42:19 -07:00
|
|
|
}
|
|
|
|
|
|
2022-10-19 15:11:34 -07:00
|
|
|
if (info->file_version >= 18)
|
|
|
|
|
write_paths = TRUE;
|
|
|
|
|
|
2001-07-03 11:38:56 -07:00
|
|
|
/* determine the number of layers and channels in the image */
|
2009-08-30 12:19:42 -07:00
|
|
|
all_layers = gimp_image_get_layer_list (image);
|
|
|
|
|
all_channels = gimp_image_get_channel_list (image);
|
2006-07-11 13:21:18 -07:00
|
|
|
|
2001-07-03 11:38:56 -07:00
|
|
|
/* check and see if we have to save out the selection */
|
2015-06-30 12:59:26 -07:00
|
|
|
if (! gimp_channel_is_empty (gimp_image_get_mask (image)))
|
2009-08-30 12:19:42 -07:00
|
|
|
{
|
|
|
|
|
all_channels = g_list_append (all_channels, gimp_image_get_mask (image));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
n_layers = (guint) g_list_length (all_layers);
|
|
|
|
|
n_channels = (guint) g_list_length (all_channels);
|
|
|
|
|
|
2022-10-19 15:11:34 -07:00
|
|
|
if (write_paths)
|
|
|
|
|
{
|
2024-07-10 17:07:44 -07:00
|
|
|
all_paths = gimp_image_get_path_list (image);
|
2022-10-19 15:11:34 -07:00
|
|
|
n_paths = (guint) g_list_length (all_paths);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
max_progress = 1 + n_layers + n_channels + n_paths;
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2017-03-23 03:44:41 -07:00
|
|
|
/* write the property information for the image */
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_check_error (xcf_save_image_props (info, image, error), ;);
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2006-07-11 13:21:18 -07:00
|
|
|
xcf_progress_update (info);
|
|
|
|
|
|
2014-10-17 10:12:05 -07:00
|
|
|
/* 'saved_pos' is the next slot in the offset table */
|
|
|
|
|
saved_pos = info->cp;
|
|
|
|
|
|
|
|
|
|
/* write an empty offset table */
|
2022-10-19 15:11:34 -07:00
|
|
|
xcf_write_zero_offset_check_error (info,
|
|
|
|
|
n_layers + n_channels + 2 +
|
2023-05-11 06:18:33 -07:00
|
|
|
(write_paths ? n_paths + 1 : 0), ;);
|
2014-10-17 10:12:05 -07:00
|
|
|
|
|
|
|
|
/* 'offset' is where we will write the next layer or channel */
|
|
|
|
|
offset = info->cp;
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2009-08-30 12:19:42 -07:00
|
|
|
for (list = all_layers; list; list = g_list_next (list))
|
2001-07-03 11:38:56 -07:00
|
|
|
{
|
2009-08-30 12:07:22 -07:00
|
|
|
GimpLayer *layer = list->data;
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2014-10-17 10:12:05 -07:00
|
|
|
/* seek back to the next slot in the offset table and write the
|
|
|
|
|
* offset of the layer
|
|
|
|
|
*/
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_check_error (xcf_seek_pos (info, saved_pos, error), ;);
|
|
|
|
|
xcf_write_offset_check_error (info, &offset, 1, ;);
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2014-10-17 10:12:05 -07:00
|
|
|
/* remember the next slot in the offset table */
|
2014-10-15 14:15:18 -07:00
|
|
|
saved_pos = info->cp;
|
|
|
|
|
|
|
|
|
|
/* seek to the layer offset and save the layer */
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_check_error (xcf_seek_pos (info, offset, error), ;);
|
|
|
|
|
xcf_check_error (xcf_save_layer (info, image, layer, error), ;);
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2014-10-15 14:15:18 -07:00
|
|
|
/* the next layer's offset is after the layer we just wrote */
|
|
|
|
|
offset = info->cp;
|
2006-07-11 13:21:18 -07:00
|
|
|
|
2014-10-15 14:15:18 -07:00
|
|
|
xcf_progress_update (info);
|
2001-07-03 11:38:56 -07:00
|
|
|
}
|
|
|
|
|
|
2014-10-17 10:12:05 -07:00
|
|
|
/* skip a '0' in the offset table to indicate the end of the layer
|
|
|
|
|
* offsets
|
|
|
|
|
*/
|
2017-03-23 03:44:41 -07:00
|
|
|
saved_pos += info->bytes_per_offset;
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2009-08-30 12:19:42 -07:00
|
|
|
for (list = all_channels; list; list = g_list_next (list))
|
2001-07-03 11:38:56 -07:00
|
|
|
{
|
2009-08-30 12:19:42 -07:00
|
|
|
GimpChannel *channel = list->data;
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2014-10-17 10:12:05 -07:00
|
|
|
/* seek back to the next slot in the offset table and write the
|
|
|
|
|
* offset of the channel
|
|
|
|
|
*/
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_check_error (xcf_seek_pos (info, saved_pos, error), ;);
|
|
|
|
|
xcf_write_offset_check_error (info, &offset, 1, ;);
|
2014-10-15 14:15:18 -07:00
|
|
|
|
2014-10-17 10:12:05 -07:00
|
|
|
/* remember the next slot in the offset table */
|
2014-10-15 14:15:18 -07:00
|
|
|
saved_pos = info->cp;
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2014-10-15 14:15:18 -07:00
|
|
|
/* seek to the channel offset and save the channel */
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_check_error (xcf_seek_pos (info, offset, error), ;);
|
|
|
|
|
xcf_check_error (xcf_save_channel (info, image, channel, error), ;);
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2014-10-17 10:12:05 -07:00
|
|
|
/* the next channels's offset is after the channel we just wrote */
|
2014-10-15 14:15:18 -07:00
|
|
|
offset = info->cp;
|
2006-07-11 13:21:18 -07:00
|
|
|
|
2014-10-15 14:15:18 -07:00
|
|
|
xcf_progress_update (info);
|
2001-07-03 11:38:56 -07:00
|
|
|
}
|
|
|
|
|
|
2022-10-19 15:11:34 -07:00
|
|
|
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))
|
|
|
|
|
{
|
2025-07-07 07:05:07 -07:00
|
|
|
GimpPath *path = list->data;
|
2022-10-19 15:11:34 -07:00
|
|
|
|
|
|
|
|
/* seek back to the next slot in the offset table and write the
|
|
|
|
|
* offset of the channel
|
|
|
|
|
*/
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_check_error (xcf_seek_pos (info, saved_pos, error), ;);
|
|
|
|
|
xcf_write_offset_check_error (info, &offset, 1, ;);
|
2022-10-19 15:11:34 -07:00
|
|
|
|
|
|
|
|
/* remember the next slot in the offset table */
|
|
|
|
|
saved_pos = info->cp;
|
|
|
|
|
|
|
|
|
|
/* seek to the channel offset and save the channel */
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_check_error (xcf_seek_pos (info, offset, error), ;);
|
2025-07-07 07:05:07 -07:00
|
|
|
xcf_check_error (xcf_save_path (info, image, path, error), ;);
|
2022-10-19 15:11:34 -07:00
|
|
|
|
|
|
|
|
/* the next channels's offset is after the channel we just wrote */
|
|
|
|
|
offset = info->cp;
|
|
|
|
|
|
|
|
|
|
xcf_progress_update (info);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-10-17 10:12:05 -07:00
|
|
|
/* there is already a '0' at the end of the offset table to indicate
|
|
|
|
|
* the end of the channel offsets
|
|
|
|
|
*/
|
2014-10-15 14:15:18 -07:00
|
|
|
|
2009-08-30 12:19:42 -07:00
|
|
|
g_list_free (all_layers);
|
|
|
|
|
g_list_free (all_channels);
|
2022-10-19 15:11:34 -07:00
|
|
|
g_list_free (all_paths);
|
2009-08-30 12:19:42 -07:00
|
|
|
|
2013-10-08 12:22:14 -07:00
|
|
|
return ! g_output_stream_is_closed (info->output);
|
2001-07-03 11:38:56 -07:00
|
|
|
}
|
|
|
|
|
|
2002-12-19 22:26:34 -08:00
|
|
|
static gboolean
|
2006-11-06 00:14:46 -08:00
|
|
|
xcf_save_image_props (XcfInfo *info,
|
|
|
|
|
GimpImage *image,
|
|
|
|
|
GError **error)
|
2001-07-03 11:38:56 -07:00
|
|
|
{
|
2017-03-23 10:03:27 -07:00
|
|
|
GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image);
|
|
|
|
|
GimpParasite *grid_parasite = NULL;
|
|
|
|
|
GimpParasite *meta_parasite = NULL;
|
2016-01-27 10:18:53 -08:00
|
|
|
GList *symmetry_parasites = NULL;
|
|
|
|
|
GList *iter;
|
Issue #8900 and #9923: reimplementing GimpUnit as a proper class.
This fixes all our GObject Introspection issues with GimpUnit which was
both an enum and an int-derived type of user-defined units *completing*
the enum values. GIR clearly didn't like this!
Now GimpUnit is a proper class and units are unique objects, allowing to
compare them with an identity test (i.e. `unit == gimp_unit_pixel ()`
tells us if unit is the pixel unit or not), which makes it easy to use,
just like with int, yet adding also methods, making for nicer
introspected API.
As an aside, this also fixes #10738, by having all the built-in units
retrievable even if libgimpbase had not been properly initialized with
gimp_base_init().
I haven't checked in details how GIR works to introspect, but it looks
like it loads the library to inspect and runs functions, hence
triggering some CRITICALS because virtual methods (supposed to be
initialized with gimp_base_init() run by libgimp) are not set. This new
code won't trigger any critical because the vtable method are now not
necessary, at least for all built-in units.
Note that GimpUnit is still in libgimpbase. It could have been moved to
libgimp in order to avoid any virtual method table (since we need to
keep core and libgimp side's units in sync, PDB is required), but too
many libgimpwidgets widgets were already using GimpUnit. And technically
most of GimpUnit logic doesn't require PDB (only the creation/sync
part). This is one of the reasons why user-created GimpUnit list is
handled and stored differently from other types of objects.
Globally this simplifies the code a lot too and we don't need separate
implementations of various utils for core and libgimp, which means less
prone to errors.
2024-07-25 11:55:21 -07:00
|
|
|
GimpUnit *unit = gimp_image_get_unit (image);
|
2010-02-03 14:42:32 -08:00
|
|
|
gdouble xres;
|
|
|
|
|
gdouble yres;
|
2007-12-26 09:33:41 -08:00
|
|
|
|
|
|
|
|
gimp_image_get_resolution (image, &xres, &yres);
|
2003-07-04 12:55:58 -07:00
|
|
|
|
2001-07-03 11:38:56 -07:00
|
|
|
/* check and see if we should save the colormap property */
|
2019-11-08 01:50:32 -08:00
|
|
|
if (gimp_image_get_colormap_palette (image))
|
|
|
|
|
{
|
2023-12-08 23:53:44 -08:00
|
|
|
gint n_colors;
|
|
|
|
|
guint8 *colormap = _gimp_image_get_colormap (image, &n_colors);
|
2019-11-08 01:50:32 -08:00
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_COLORMAP, error,
|
2023-12-08 23:53:44 -08:00
|
|
|
n_colors, colormap), ;);
|
2019-11-08 01:50:32 -08:00
|
|
|
g_free (colormap);
|
|
|
|
|
}
|
2001-07-03 11:38:56 -07:00
|
|
|
|
|
|
|
|
if (info->compression != COMPRESS_NONE)
|
2007-12-25 09:09:04 -08:00
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_COMPRESSION, error,
|
2023-05-11 06:18:33 -07:00
|
|
|
info->compression), ;);
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2007-12-25 09:09:04 -08:00
|
|
|
if (gimp_image_get_guides (image))
|
|
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_GUIDES, error,
|
2023-05-11 06:18:33 -07:00
|
|
|
gimp_image_get_guides (image)), ;);
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2007-12-25 09:09:04 -08:00
|
|
|
if (gimp_image_get_sample_points (image))
|
2018-07-15 16:42:19 -07:00
|
|
|
{
|
|
|
|
|
/* save the new property before the old one, so loading can skip
|
|
|
|
|
* the latter
|
|
|
|
|
*/
|
|
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_SAMPLE_POINTS, error,
|
2023-05-11 06:18:33 -07:00
|
|
|
gimp_image_get_sample_points (image)), ;);
|
2018-07-15 16:42:19 -07:00
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_OLD_SAMPLE_POINTS, error,
|
2023-05-11 06:18:33 -07:00
|
|
|
gimp_image_get_sample_points (image)), ;);
|
2018-07-15 16:42:19 -07:00
|
|
|
}
|
2006-08-10 10:10:12 -07:00
|
|
|
|
2006-03-28 09:08:36 -08:00
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_RESOLUTION, error,
|
2023-05-11 06:18:33 -07:00
|
|
|
xres, yres), ;);
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2006-03-28 09:08:36 -08:00
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_TATTOO, error,
|
2023-05-11 06:18:33 -07:00
|
|
|
gimp_image_get_tattoo_state (image)), ;);
|
2001-07-03 11:38:56 -07:00
|
|
|
|
Issue #8900 and #9923: reimplementing GimpUnit as a proper class.
This fixes all our GObject Introspection issues with GimpUnit which was
both an enum and an int-derived type of user-defined units *completing*
the enum values. GIR clearly didn't like this!
Now GimpUnit is a proper class and units are unique objects, allowing to
compare them with an identity test (i.e. `unit == gimp_unit_pixel ()`
tells us if unit is the pixel unit or not), which makes it easy to use,
just like with int, yet adding also methods, making for nicer
introspected API.
As an aside, this also fixes #10738, by having all the built-in units
retrievable even if libgimpbase had not been properly initialized with
gimp_base_init().
I haven't checked in details how GIR works to introspect, but it looks
like it loads the library to inspect and runs functions, hence
triggering some CRITICALS because virtual methods (supposed to be
initialized with gimp_base_init() run by libgimp) are not set. This new
code won't trigger any critical because the vtable method are now not
necessary, at least for all built-in units.
Note that GimpUnit is still in libgimpbase. It could have been moved to
libgimp in order to avoid any virtual method table (since we need to
keep core and libgimp side's units in sync, PDB is required), but too
many libgimpwidgets widgets were already using GimpUnit. And technically
most of GimpUnit logic doesn't require PDB (only the creation/sync
part). This is one of the reasons why user-created GimpUnit list is
handled and stored differently from other types of objects.
Globally this simplifies the code a lot too and we don't need separate
implementations of various utils for core and libgimp, which means less
prone to errors.
2024-07-25 11:55:21 -07:00
|
|
|
if (gimp_unit_is_built_in (unit))
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_UNIT, error, unit), ;);
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2024-07-10 17:07:44 -07:00
|
|
|
if (gimp_container_get_n_children (gimp_image_get_paths (image)) > 0 &&
|
2022-10-19 15:11:34 -07:00
|
|
|
info->file_version < 18)
|
2003-09-09 08:46:59 -07:00
|
|
|
{
|
2024-07-12 22:07:57 -07:00
|
|
|
if (gimp_path_compat_is_compatible (image))
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_PATHS, error), ;);
|
2003-09-09 08:46:59 -07:00
|
|
|
else
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_VECTORS, error), ;);
|
2003-09-09 08:46:59 -07:00
|
|
|
}
|
2001-07-03 11:38:56 -07:00
|
|
|
|
Issue #8900 and #9923: reimplementing GimpUnit as a proper class.
This fixes all our GObject Introspection issues with GimpUnit which was
both an enum and an int-derived type of user-defined units *completing*
the enum values. GIR clearly didn't like this!
Now GimpUnit is a proper class and units are unique objects, allowing to
compare them with an identity test (i.e. `unit == gimp_unit_pixel ()`
tells us if unit is the pixel unit or not), which makes it easy to use,
just like with int, yet adding also methods, making for nicer
introspected API.
As an aside, this also fixes #10738, by having all the built-in units
retrievable even if libgimpbase had not been properly initialized with
gimp_base_init().
I haven't checked in details how GIR works to introspect, but it looks
like it loads the library to inspect and runs functions, hence
triggering some CRITICALS because virtual methods (supposed to be
initialized with gimp_base_init() run by libgimp) are not set. This new
code won't trigger any critical because the vtable method are now not
necessary, at least for all built-in units.
Note that GimpUnit is still in libgimpbase. It could have been moved to
libgimp in order to avoid any virtual method table (since we need to
keep core and libgimp side's units in sync, PDB is required), but too
many libgimpwidgets widgets were already using GimpUnit. And technically
most of GimpUnit logic doesn't require PDB (only the creation/sync
part). This is one of the reasons why user-created GimpUnit list is
handled and stored differently from other types of objects.
Globally this simplifies the code a lot too and we don't need separate
implementations of various utils for core and libgimp, which means less
prone to errors.
2024-07-25 11:55:21 -07:00
|
|
|
if (! gimp_unit_is_built_in (unit))
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_USER_UNIT, error, unit), ;);
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2010-02-03 13:16:02 -08:00
|
|
|
if (gimp_image_get_grid (image))
|
2003-07-04 12:55:58 -07:00
|
|
|
{
|
2006-03-28 09:08:36 -08:00
|
|
|
GimpGrid *grid = gimp_image_get_grid (image);
|
2003-07-04 12:55:58 -07:00
|
|
|
|
2026-01-08 13:14:39 -08:00
|
|
|
/* 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);
|
2013-10-19 09:38:01 -07:00
|
|
|
grid_parasite = gimp_grid_to_parasite (grid);
|
|
|
|
|
gimp_parasite_list_add (private->parasites, grid_parasite);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (gimp_image_get_metadata (image))
|
|
|
|
|
{
|
|
|
|
|
GimpMetadata *metadata = gimp_image_get_metadata (image);
|
|
|
|
|
gchar *meta_string;
|
|
|
|
|
|
|
|
|
|
meta_string = gimp_metadata_serialize (metadata);
|
|
|
|
|
|
|
|
|
|
if (meta_string)
|
|
|
|
|
{
|
|
|
|
|
meta_parasite = gimp_parasite_new ("gimp-image-metadata",
|
|
|
|
|
GIMP_PARASITE_PERSISTENT,
|
|
|
|
|
strlen (meta_string) + 1,
|
|
|
|
|
meta_string);
|
|
|
|
|
gimp_parasite_list_add (private->parasites, meta_parasite);
|
|
|
|
|
g_free (meta_string);
|
|
|
|
|
}
|
2003-07-04 12:55:58 -07:00
|
|
|
}
|
|
|
|
|
|
2016-01-27 10:18:53 -08:00
|
|
|
if (g_list_length (gimp_image_symmetry_get (image)))
|
|
|
|
|
{
|
|
|
|
|
GimpParasite *parasite = NULL;
|
|
|
|
|
GimpSymmetry *symmetry;
|
|
|
|
|
|
|
|
|
|
for (iter = gimp_image_symmetry_get (image); iter; iter = g_list_next (iter))
|
|
|
|
|
{
|
|
|
|
|
symmetry = GIMP_SYMMETRY (iter->data);
|
|
|
|
|
if (G_TYPE_FROM_INSTANCE (symmetry) == GIMP_TYPE_SYMMETRY)
|
|
|
|
|
/* Do not save the identity symmetry. */
|
|
|
|
|
continue;
|
|
|
|
|
parasite = gimp_symmetry_to_parasite (GIMP_SYMMETRY (iter->data));
|
|
|
|
|
gimp_parasite_list_add (private->parasites, parasite);
|
|
|
|
|
symmetry_parasites = g_list_prepend (symmetry_parasites, parasite);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-21 16:26:18 -07:00
|
|
|
if (gimp_parasite_list_length (private->parasites) > 0)
|
2003-07-04 12:55:58 -07:00
|
|
|
{
|
2006-03-28 09:08:36 -08:00
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_PARASITES, error,
|
2023-05-11 06:18:33 -07:00
|
|
|
private->parasites), ;);
|
2003-07-04 12:55:58 -07:00
|
|
|
}
|
|
|
|
|
|
2013-10-19 09:38:01 -07:00
|
|
|
if (grid_parasite)
|
|
|
|
|
{
|
|
|
|
|
gimp_parasite_list_remove (private->parasites,
|
2021-04-05 09:36:44 -07:00
|
|
|
gimp_parasite_get_name (grid_parasite));
|
2013-10-19 09:38:01 -07:00
|
|
|
gimp_parasite_free (grid_parasite);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (meta_parasite)
|
2003-07-04 12:55:58 -07:00
|
|
|
{
|
2010-02-03 14:42:32 -08:00
|
|
|
gimp_parasite_list_remove (private->parasites,
|
2021-04-05 09:36:44 -07:00
|
|
|
gimp_parasite_get_name (meta_parasite));
|
2013-10-19 09:38:01 -07:00
|
|
|
gimp_parasite_free (meta_parasite);
|
2003-07-04 12:55:58 -07:00
|
|
|
}
|
|
|
|
|
|
2016-01-27 10:18:53 -08:00
|
|
|
for (iter = symmetry_parasites; iter; iter = g_list_next (iter))
|
|
|
|
|
{
|
|
|
|
|
GimpParasite *parasite = iter->data;
|
|
|
|
|
|
|
|
|
|
gimp_parasite_list_remove (private->parasites,
|
2021-04-05 09:36:44 -07:00
|
|
|
gimp_parasite_get_name (parasite));
|
2016-01-27 10:18:53 -08:00
|
|
|
}
|
|
|
|
|
g_list_free_full (symmetry_parasites,
|
|
|
|
|
(GDestroyNotify) gimp_parasite_free);
|
|
|
|
|
|
2021-12-22 13:53:15 -08:00
|
|
|
info->layer_sets = gimp_image_get_stored_item_sets (image, GIMP_TYPE_LAYER);
|
|
|
|
|
info->channel_sets = gimp_image_get_stored_item_sets (image, GIMP_TYPE_CHANNEL);
|
|
|
|
|
|
|
|
|
|
for (iter = info->layer_sets; iter; iter = iter->next)
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_ITEM_SET, error, iter->data), ;);
|
2021-12-22 13:53:15 -08:00
|
|
|
for (iter = info->channel_sets; iter; iter = iter->next)
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_ITEM_SET, error, iter->data), ;);
|
2021-12-22 13:53:15 -08:00
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_END, error), ;);
|
2002-12-19 22:26:34 -08:00
|
|
|
|
|
|
|
|
return TRUE;
|
2001-07-03 11:38:56 -07:00
|
|
|
}
|
|
|
|
|
|
2003-09-10 03:40:57 -07:00
|
|
|
static gboolean
|
2006-11-06 00:14:46 -08:00
|
|
|
xcf_save_layer_props (XcfInfo *info,
|
|
|
|
|
GimpImage *image,
|
|
|
|
|
GimpLayer *layer,
|
|
|
|
|
GError **error)
|
2001-07-03 11:38:56 -07:00
|
|
|
{
|
2011-01-31 14:51:18 -08:00
|
|
|
GimpParasiteList *parasites;
|
2021-12-22 13:53:15 -08:00
|
|
|
GList *iter;
|
2011-01-31 14:51:18 -08:00
|
|
|
gint offset_x;
|
|
|
|
|
gint offset_y;
|
2003-06-24 06:59:36 -07:00
|
|
|
|
2009-08-30 12:28:59 -07:00
|
|
|
if (gimp_viewable_get_children (GIMP_VIEWABLE (layer)))
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_GROUP_ITEM, error), ;);
|
2009-08-30 12:28:59 -07:00
|
|
|
|
|
|
|
|
if (gimp_viewable_get_parent (GIMP_VIEWABLE (layer)))
|
|
|
|
|
{
|
|
|
|
|
GList *path;
|
|
|
|
|
|
|
|
|
|
path = gimp_item_get_path (GIMP_ITEM (layer));
|
|
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_ITEM_PATH, error,
|
2023-05-11 06:18:33 -07:00
|
|
|
path), ;);
|
2009-08-30 12:28:59 -07:00
|
|
|
g_list_free (path);
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-14 12:36:58 -07:00
|
|
|
if (g_list_find (gimp_image_get_selected_layers (image), layer))
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_ACTIVE_LAYER, error), ;);
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2008-11-14 07:01:44 -08:00
|
|
|
if (layer == gimp_image_get_floating_selection (image))
|
2001-07-03 11:38:56 -07:00
|
|
|
{
|
2009-01-26 14:47:16 -08:00
|
|
|
info->floating_sel_drawable = gimp_layer_get_floating_sel_drawable (layer);
|
2006-03-28 09:08:36 -08:00
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_FLOATING_SELECTION,
|
2023-05-11 06:18:33 -07:00
|
|
|
error), ;);
|
2001-07-03 11:38:56 -07:00
|
|
|
}
|
|
|
|
|
|
2006-03-28 09:08:36 -08:00
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_OPACITY, error,
|
2023-05-11 06:18:33 -07:00
|
|
|
gimp_layer_get_opacity (layer)), ;);
|
2015-10-16 12:59:11 -07:00
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_FLOAT_OPACITY, error,
|
2023-05-11 06:18:33 -07:00
|
|
|
gimp_layer_get_opacity (layer)), ;);
|
2006-03-28 09:08:36 -08:00
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_VISIBLE, error,
|
2023-05-11 06:18:33 -07:00
|
|
|
gimp_item_get_visible (GIMP_ITEM (layer))), ;);
|
2016-11-14 14:02:43 -08:00
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_COLOR_TAG, error,
|
2023-05-11 06:18:33 -07:00
|
|
|
gimp_item_get_color_tag (GIMP_ITEM (layer))), ;);
|
2009-08-30 03:44:35 -07:00
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_LOCK_CONTENT, error,
|
2023-05-11 06:18:33 -07:00
|
|
|
gimp_item_get_lock_content (GIMP_ITEM (layer))), ;);
|
2007-12-21 08:37:01 -08:00
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_LOCK_ALPHA, error,
|
2023-05-11 06:18:33 -07:00
|
|
|
gimp_layer_get_lock_alpha (layer)), ;);
|
2012-11-09 02:17:25 -08:00
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_LOCK_POSITION, error,
|
2023-05-11 06:18:33 -07:00
|
|
|
gimp_item_get_lock_position (GIMP_ITEM (layer))), ;);
|
2022-02-15 05:44:11 -08:00
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_LOCK_VISIBILITY, error,
|
2023-05-11 06:18:33 -07:00
|
|
|
gimp_item_get_lock_visibility (GIMP_ITEM (layer))), ;);
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2007-12-21 08:37:01 -08:00
|
|
|
if (gimp_layer_get_mask (layer))
|
2001-07-03 11:38:56 -07:00
|
|
|
{
|
2007-12-21 08:37:01 -08:00
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_APPLY_MASK, error,
|
2023-05-11 06:18:33 -07:00
|
|
|
gimp_layer_get_apply_mask (layer)), ;);
|
2007-12-21 08:37:01 -08:00
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_EDIT_MASK, error,
|
2023-05-11 06:18:33 -07:00
|
|
|
gimp_layer_get_edit_mask (layer)), ;);
|
2007-12-21 08:37:01 -08:00
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_SHOW_MASK, error,
|
2023-05-11 06:18:33 -07:00
|
|
|
gimp_layer_get_show_mask (layer)), ;);
|
2001-07-03 11:38:56 -07:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2007-12-21 08:37:01 -08:00
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_APPLY_MASK, error,
|
2023-05-11 06:18:33 -07:00
|
|
|
FALSE), ;);
|
2007-12-21 08:37:01 -08:00
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_EDIT_MASK, error,
|
2023-05-11 06:18:33 -07:00
|
|
|
FALSE), ;);
|
2007-12-21 08:37:01 -08:00
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_SHOW_MASK, error,
|
2023-05-11 06:18:33 -07:00
|
|
|
FALSE), ;);
|
2001-07-03 11:38:56 -07:00
|
|
|
}
|
|
|
|
|
|
2008-11-03 13:17:50 -08:00
|
|
|
gimp_item_get_offset (GIMP_ITEM (layer), &offset_x, &offset_y);
|
|
|
|
|
|
2006-03-28 09:08:36 -08:00
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_OFFSETS, error,
|
2023-05-11 06:18:33 -07:00
|
|
|
offset_x, offset_y), ;);
|
2006-03-28 09:08:36 -08:00
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_MODE, error,
|
2023-05-11 06:18:33 -07:00
|
|
|
gimp_layer_get_mode (layer)), ;);
|
2017-02-12 14:49:26 -08:00
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_BLEND_SPACE, error,
|
app: future-proof XCF layer blend/composite props
The layer blend space, composite space, and composite mode
properties have a special AUTO value, which may map to different
concrete values based on the layer mode. Make sure we can change
this mapping in the future, without affecting existing XCFs (saved
after this commit), by encoding these properties as follows:
When saving an XCF, if the property has a concrete (non-AUTO)
value, which is always positive, encode it as is. If the property
is AUTO, which is always 0, encode it as the negative of the value
it actually maps to at the time of saving (note that in some cases
AUTO may map to AUTO, in which case it's encoded as 0).
When loading an XCF, if the encoded property (stored in the file)
is nonnegative, use it as is. Otherwise, compare the negative of
the encoded property to the value AUTO maps to at the time of
loading. If the values are equal, set the property to AUTO;
otherwise, use the concrete value (i.e., the negative of the value
stored in the XCF).
Note that XCFs saved prior to this commit still load fine, it's
simply that if we change the AUTO mapping in the future, all their
AUTO properties will keep being loaded as AUTO, even if the
resulting concrete values will have changed.
2017-05-21 05:01:39 -07:00
|
|
|
gimp_layer_get_mode (layer),
|
2023-05-11 06:18:33 -07:00
|
|
|
gimp_layer_get_blend_space (layer)), ;);
|
2017-02-12 14:49:26 -08:00
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_COMPOSITE_SPACE, error,
|
app: future-proof XCF layer blend/composite props
The layer blend space, composite space, and composite mode
properties have a special AUTO value, which may map to different
concrete values based on the layer mode. Make sure we can change
this mapping in the future, without affecting existing XCFs (saved
after this commit), by encoding these properties as follows:
When saving an XCF, if the property has a concrete (non-AUTO)
value, which is always positive, encode it as is. If the property
is AUTO, which is always 0, encode it as the negative of the value
it actually maps to at the time of saving (note that in some cases
AUTO may map to AUTO, in which case it's encoded as 0).
When loading an XCF, if the encoded property (stored in the file)
is nonnegative, use it as is. Otherwise, compare the negative of
the encoded property to the value AUTO maps to at the time of
loading. If the values are equal, set the property to AUTO;
otherwise, use the concrete value (i.e., the negative of the value
stored in the XCF).
Note that XCFs saved prior to this commit still load fine, it's
simply that if we change the AUTO mapping in the future, all their
AUTO properties will keep being loaded as AUTO, even if the
resulting concrete values will have changed.
2017-05-21 05:01:39 -07:00
|
|
|
gimp_layer_get_mode (layer),
|
2023-05-11 06:18:33 -07:00
|
|
|
gimp_layer_get_composite_space (layer)), ;);
|
2017-02-12 14:49:26 -08:00
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_COMPOSITE_MODE, error,
|
app: future-proof XCF layer blend/composite props
The layer blend space, composite space, and composite mode
properties have a special AUTO value, which may map to different
concrete values based on the layer mode. Make sure we can change
this mapping in the future, without affecting existing XCFs (saved
after this commit), by encoding these properties as follows:
When saving an XCF, if the property has a concrete (non-AUTO)
value, which is always positive, encode it as is. If the property
is AUTO, which is always 0, encode it as the negative of the value
it actually maps to at the time of saving (note that in some cases
AUTO may map to AUTO, in which case it's encoded as 0).
When loading an XCF, if the encoded property (stored in the file)
is nonnegative, use it as is. Otherwise, compare the negative of
the encoded property to the value AUTO maps to at the time of
loading. If the values are equal, set the property to AUTO;
otherwise, use the concrete value (i.e., the negative of the value
stored in the XCF).
Note that XCFs saved prior to this commit still load fine, it's
simply that if we change the AUTO mapping in the future, all their
AUTO properties will keep being loaded as AUTO, even if the
resulting concrete values will have changed.
2017-05-21 05:01:39 -07:00
|
|
|
gimp_layer_get_mode (layer),
|
2023-05-11 06:18:33 -07:00
|
|
|
gimp_layer_get_composite_mode (layer)), ;);
|
2006-03-28 09:08:36 -08:00
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_TATTOO, error,
|
2023-05-11 06:18:33 -07:00
|
|
|
gimp_item_get_tattoo (GIMP_ITEM (layer))), ;);
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2004-03-18 07:27:23 -08:00
|
|
|
if (GIMP_IS_TEXT_LAYER (layer) && GIMP_TEXT_LAYER (layer)->text)
|
|
|
|
|
{
|
|
|
|
|
GimpTextLayer *text_layer = GIMP_TEXT_LAYER (layer);
|
|
|
|
|
guint32 flags = gimp_text_layer_get_xcf_flags (text_layer);
|
|
|
|
|
|
|
|
|
|
gimp_text_layer_xcf_save_prepare (text_layer);
|
|
|
|
|
|
|
|
|
|
if (flags)
|
|
|
|
|
xcf_check_error (xcf_save_prop (info,
|
2006-03-28 09:08:36 -08:00
|
|
|
image, PROP_TEXT_LAYER_FLAGS, error,
|
2023-05-11 06:18:33 -07:00
|
|
|
flags), ;);
|
2004-03-18 07:27:23 -08:00
|
|
|
}
|
2025-08-16 14:43:39 -07:00
|
|
|
else if (GIMP_IS_VECTOR_LAYER (layer))
|
2025-03-24 19:39:49 -07:00
|
|
|
{
|
2025-08-16 14:43:39 -07:00
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_VECTOR_LAYER, error, layer), ;);
|
2025-03-24 19:39:49 -07:00
|
|
|
}
|
2025-08-26 13:51:36 -07:00
|
|
|
else if (GIMP_IS_LINK_LAYER (layer))
|
|
|
|
|
{
|
|
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_LINK_LAYER, error, layer), ;);
|
2025-11-08 03:37:45 -08:00
|
|
|
if (gimp_link_layer_get_transform (GIMP_LINK_LAYER (layer), NULL, NULL, NULL, NULL))
|
|
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_TRANSFORM, error, layer), ;);
|
2025-08-26 13:51:36 -07:00
|
|
|
}
|
2019-07-08 07:48:56 -07:00
|
|
|
|
2011-09-25 12:57:20 -07:00
|
|
|
if (gimp_viewable_get_children (GIMP_VIEWABLE (layer)))
|
|
|
|
|
{
|
2011-10-07 11:05:52 -07:00
|
|
|
gint32 flags = 0;
|
|
|
|
|
|
|
|
|
|
if (gimp_viewable_get_expanded (GIMP_VIEWABLE (layer)))
|
|
|
|
|
flags |= XCF_GROUP_ITEM_EXPANDED;
|
2011-09-25 12:57:20 -07:00
|
|
|
|
|
|
|
|
xcf_check_error (xcf_save_prop (info,
|
|
|
|
|
image, PROP_GROUP_ITEM_FLAGS, error,
|
2023-05-11 06:18:33 -07:00
|
|
|
flags), ;);
|
2011-09-25 12:57:20 -07:00
|
|
|
}
|
|
|
|
|
|
2011-01-31 14:51:18 -08:00
|
|
|
parasites = gimp_item_get_parasites (GIMP_ITEM (layer));
|
|
|
|
|
|
2023-05-21 16:26:18 -07:00
|
|
|
if (gimp_parasite_list_length (parasites) > 0)
|
2003-06-24 06:59:36 -07:00
|
|
|
{
|
2006-03-28 09:08:36 -08:00
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_PARASITES, error,
|
2023-05-11 06:18:33 -07:00
|
|
|
parasites), ;);
|
2003-06-24 06:59:36 -07:00
|
|
|
}
|
|
|
|
|
|
2021-12-22 13:53:15 -08:00
|
|
|
for (iter = info->layer_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 (layer)))
|
|
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_ITEM_SET_ITEM, error,
|
2023-05-11 06:18:33 -07:00
|
|
|
g_list_position (info->layer_sets, iter)), ;);
|
2021-12-22 13:53:15 -08:00
|
|
|
|
|
|
|
|
g_list_free (items);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_END, error), ;);
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2002-12-19 22:26:34 -08:00
|
|
|
return TRUE;
|
2001-07-03 11:38:56 -07:00
|
|
|
}
|
|
|
|
|
|
2002-12-19 22:26:34 -08:00
|
|
|
static gboolean
|
2006-11-06 00:14:46 -08:00
|
|
|
xcf_save_channel_props (XcfInfo *info,
|
|
|
|
|
GimpImage *image,
|
|
|
|
|
GimpChannel *channel,
|
|
|
|
|
GError **error)
|
2001-07-03 11:38:56 -07:00
|
|
|
{
|
2011-01-31 14:51:18 -08:00
|
|
|
GimpParasiteList *parasites;
|
2021-12-22 13:53:15 -08:00
|
|
|
GList *iter;
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2021-06-19 15:00:53 -07:00
|
|
|
if (g_list_find (gimp_image_get_selected_channels (image), channel))
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_ACTIVE_CHANNEL, error), ;);
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2007-12-25 08:21:40 -08:00
|
|
|
if (channel == gimp_image_get_mask (image))
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_SELECTION, error), ;);
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2006-03-28 09:08:36 -08:00
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_OPACITY, error,
|
2023-05-11 06:18:33 -07:00
|
|
|
gimp_channel_get_opacity (channel)), ;);
|
2015-10-16 12:59:11 -07:00
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_FLOAT_OPACITY, error,
|
2023-05-11 06:18:33 -07:00
|
|
|
gimp_channel_get_opacity (channel)), ;);
|
2006-03-28 09:08:36 -08:00
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_VISIBLE, error,
|
2023-05-11 06:18:33 -07:00
|
|
|
gimp_item_get_visible (GIMP_ITEM (channel))), ;);
|
2016-11-14 14:02:43 -08:00
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_COLOR_TAG, error,
|
2023-05-11 06:18:33 -07:00
|
|
|
gimp_item_get_color_tag (GIMP_ITEM (channel))), ;);
|
2009-08-30 03:44:35 -07:00
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_LOCK_CONTENT, error,
|
2023-05-11 06:18:33 -07:00
|
|
|
gimp_item_get_lock_content (GIMP_ITEM (channel))), ;);
|
2012-11-09 02:17:25 -08:00
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_LOCK_POSITION, error,
|
2023-05-11 06:18:33 -07:00
|
|
|
gimp_item_get_lock_position (GIMP_ITEM (channel))), ;);
|
2022-02-15 05:44:11 -08:00
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_LOCK_VISIBILITY, error,
|
2023-05-11 06:18:33 -07:00
|
|
|
gimp_item_get_lock_visibility (GIMP_ITEM (channel))), ;);
|
2006-03-28 09:08:36 -08:00
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_SHOW_MASKED, error,
|
2023-05-11 06:18:33 -07:00
|
|
|
gimp_channel_get_show_masked (channel)), ;);
|
2017-03-24 14:35:57 -07:00
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_COLOR, error,
|
2023-11-19 15:20:03 -08:00
|
|
|
channel->color), ;);
|
2017-03-24 14:35:57 -07:00
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_FLOAT_COLOR, error,
|
2023-11-19 15:20:03 -08:00
|
|
|
channel->color), ;);
|
2006-03-28 09:08:36 -08:00
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_TATTOO, error,
|
2023-05-11 06:18:33 -07:00
|
|
|
gimp_item_get_tattoo (GIMP_ITEM (channel))), ;);
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2011-01-31 14:51:18 -08:00
|
|
|
parasites = gimp_item_get_parasites (GIMP_ITEM (channel));
|
|
|
|
|
|
2023-05-21 16:26:18 -07:00
|
|
|
if (gimp_parasite_list_length (parasites) > 0)
|
2011-01-31 14:51:18 -08:00
|
|
|
{
|
|
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_PARASITES, error,
|
2023-05-11 06:18:33 -07:00
|
|
|
parasites), ;);
|
2011-01-31 14:51:18 -08:00
|
|
|
}
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2021-12-22 13:53:15 -08:00
|
|
|
for (iter = info->channel_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 (channel)))
|
|
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_ITEM_SET_ITEM, error,
|
2023-05-11 06:18:33 -07:00
|
|
|
g_list_position (info->channel_sets, iter)), ;);
|
2021-12-22 13:53:15 -08:00
|
|
|
|
|
|
|
|
g_list_free (items);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_END, error), ;);
|
2002-12-19 22:26:34 -08:00
|
|
|
|
|
|
|
|
return TRUE;
|
2001-07-03 11:38:56 -07:00
|
|
|
}
|
|
|
|
|
|
2023-06-19 07:54:21 -07:00
|
|
|
static gboolean
|
|
|
|
|
xcf_save_effect_props (XcfInfo *info,
|
|
|
|
|
GimpImage *image,
|
|
|
|
|
GimpFilter *filter,
|
|
|
|
|
GError **error)
|
|
|
|
|
{
|
2024-01-03 10:57:08 -08:00
|
|
|
GParamSpec **pspecs;
|
|
|
|
|
guint n_pspecs;
|
|
|
|
|
GeglNode *node;
|
2024-01-22 08:49:10 -08:00
|
|
|
gchar *operation;
|
2024-01-03 10:57:08 -08:00
|
|
|
|
2023-06-19 07:54:21 -07:00
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_VISIBLE, error,
|
|
|
|
|
gimp_filter_get_active (filter)), ;);
|
2024-01-22 08:49:10 -08:00
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_FLOAT_OPACITY, error,
|
2023-06-19 07:54:21 -07:00
|
|
|
gimp_drawable_filter_get_opacity (GIMP_DRAWABLE_FILTER (filter))), ;);
|
|
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_MODE, error,
|
|
|
|
|
gimp_drawable_filter_get_paint_mode (GIMP_DRAWABLE_FILTER (filter))), ;);
|
|
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_BLEND_SPACE, error,
|
|
|
|
|
gimp_drawable_filter_get_blend_space (GIMP_DRAWABLE_FILTER (filter))), ;);
|
|
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_COMPOSITE_SPACE, error,
|
|
|
|
|
gimp_drawable_filter_get_composite_space (GIMP_DRAWABLE_FILTER (filter))), ;);
|
|
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_COMPOSITE_MODE, error,
|
|
|
|
|
gimp_drawable_filter_get_composite_mode (GIMP_DRAWABLE_FILTER (filter))), ;);
|
2024-03-31 10:30:00 -07:00
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_FILTER_CLIP, error,
|
|
|
|
|
gimp_drawable_filter_get_clip (GIMP_DRAWABLE_FILTER (filter))), ;);
|
2023-06-19 07:54:21 -07:00
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_FILTER_REGION, error,
|
|
|
|
|
gimp_drawable_filter_get_region (GIMP_DRAWABLE_FILTER (filter))), ;);
|
|
|
|
|
|
2024-01-03 10:57:08 -08:00
|
|
|
/* Save each GEGL property individually */
|
|
|
|
|
node = gimp_drawable_filter_get_operation (GIMP_DRAWABLE_FILTER (filter));
|
|
|
|
|
|
|
|
|
|
gegl_node_get (node,
|
|
|
|
|
"operation", &operation,
|
|
|
|
|
NULL);
|
|
|
|
|
|
|
|
|
|
pspecs = gegl_operation_list_properties (operation, &n_pspecs);
|
|
|
|
|
|
|
|
|
|
for (gint i = 0; i < n_pspecs; i++)
|
|
|
|
|
{
|
|
|
|
|
GParamSpec *pspec = pspecs[i];
|
|
|
|
|
GValue value = G_VALUE_INIT;
|
|
|
|
|
FilterPropType filter_type = FILTER_PROP_UNKNOWN;
|
|
|
|
|
|
|
|
|
|
g_value_init (&value, pspec->value_type);
|
|
|
|
|
gegl_node_get_property (node, pspec->name,
|
|
|
|
|
&value);
|
|
|
|
|
|
|
|
|
|
switch (G_VALUE_TYPE (&value))
|
|
|
|
|
{
|
|
|
|
|
case G_TYPE_INT:
|
|
|
|
|
filter_type = FILTER_PROP_INT;
|
|
|
|
|
break;
|
|
|
|
|
|
2024-02-13 11:25:18 -08:00
|
|
|
case G_TYPE_UINT:
|
|
|
|
|
filter_type = FILTER_PROP_UINT;
|
|
|
|
|
break;
|
|
|
|
|
|
2024-01-03 10:57:08 -08:00
|
|
|
case G_TYPE_BOOLEAN:
|
|
|
|
|
filter_type = FILTER_PROP_BOOL;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case G_TYPE_FLOAT:
|
|
|
|
|
case G_TYPE_DOUBLE:
|
|
|
|
|
filter_type = FILTER_PROP_FLOAT;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case G_TYPE_STRING:
|
|
|
|
|
filter_type = FILTER_PROP_STRING;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
2024-01-22 08:49:10 -08:00
|
|
|
if (g_type_is_a (G_VALUE_TYPE (&value), GIMP_TYPE_CONFIG))
|
|
|
|
|
{
|
|
|
|
|
filter_type = FILTER_PROP_CONFIG;
|
|
|
|
|
}
|
|
|
|
|
else if (g_type_is_a (G_VALUE_TYPE (&value), G_TYPE_ENUM))
|
|
|
|
|
{
|
|
|
|
|
filter_type = FILTER_PROP_ENUM;
|
|
|
|
|
}
|
2024-02-13 11:25:18 -08:00
|
|
|
else if (g_type_is_a (G_VALUE_TYPE (&value), GEGL_TYPE_COLOR))
|
2024-02-08 07:38:05 -08:00
|
|
|
{
|
2024-02-13 11:25:18 -08:00
|
|
|
filter_type = FILTER_PROP_COLOR;
|
2024-02-08 07:38:05 -08:00
|
|
|
}
|
2024-01-22 08:49:10 -08:00
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
gimp_message (info->gimp, G_OBJECT (info->progress),
|
|
|
|
|
GIMP_MESSAGE_WARNING,
|
|
|
|
|
"XCF Warning: argument \"%s\" of filter %s has "
|
|
|
|
|
"unsupported type %s. It was discarded.",
|
|
|
|
|
pspec->name, operation,
|
|
|
|
|
g_type_name (G_VALUE_TYPE (&value)));
|
|
|
|
|
}
|
2024-01-03 10:57:08 -08:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (filter_type != FILTER_PROP_UNKNOWN)
|
2024-01-22 08:49:10 -08:00
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_FILTER_ARGUMENT, error,
|
2024-01-03 10:57:08 -08:00
|
|
|
pspec->name, filter_type, value), ;);
|
|
|
|
|
|
|
|
|
|
g_value_unset (&value);
|
|
|
|
|
}
|
2024-01-22 08:49:10 -08:00
|
|
|
g_free (operation);
|
2024-01-03 10:57:08 -08:00
|
|
|
g_free (pspecs);
|
|
|
|
|
|
2023-06-19 07:54:21 -07:00
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_END, error), ;);
|
|
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-19 15:11:34 -07:00
|
|
|
static gboolean
|
|
|
|
|
xcf_save_path_props (XcfInfo *info,
|
|
|
|
|
GimpImage *image,
|
2025-07-07 07:05:07 -07:00
|
|
|
GimpPath *path,
|
2022-10-19 15:11:34 -07:00
|
|
|
GError **error)
|
|
|
|
|
{
|
|
|
|
|
GimpParasiteList *parasites;
|
|
|
|
|
|
2025-07-07 07:05:07 -07:00
|
|
|
if (g_list_find (gimp_image_get_selected_paths (image), path))
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_SELECTED_PATH, error), ;);
|
2022-10-19 15:11:34 -07:00
|
|
|
|
|
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_VISIBLE, error,
|
2025-07-07 07:05:07 -07:00
|
|
|
gimp_item_get_visible (GIMP_ITEM (path))), ;);
|
2022-10-19 15:11:34 -07:00
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_COLOR_TAG, error,
|
2025-07-07 07:05:07 -07:00
|
|
|
gimp_item_get_color_tag (GIMP_ITEM (path))), ;);
|
2022-10-19 15:11:34 -07:00
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_LOCK_CONTENT, error,
|
2025-07-07 07:05:07 -07:00
|
|
|
gimp_item_get_lock_content (GIMP_ITEM (path))), ;);
|
2022-10-19 15:11:34 -07:00
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_LOCK_POSITION, error,
|
2025-07-07 07:05:07 -07:00
|
|
|
gimp_item_get_lock_position (GIMP_ITEM (path))), ;);
|
2022-10-19 15:11:34 -07:00
|
|
|
|
|
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_TATTOO, error,
|
2025-07-07 07:05:07 -07:00
|
|
|
gimp_item_get_tattoo (GIMP_ITEM (path))), ;);
|
2022-10-19 15:11:34 -07:00
|
|
|
|
2025-07-07 07:05:07 -07:00
|
|
|
parasites = gimp_item_get_parasites (GIMP_ITEM (path));
|
2022-10-19 15:11:34 -07:00
|
|
|
|
2023-05-21 16:26:18 -07:00
|
|
|
if (gimp_parasite_list_length (parasites) > 0)
|
2022-10-19 15:11:34 -07:00
|
|
|
{
|
|
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_PARASITES, error,
|
2023-05-11 06:18:33 -07:00
|
|
|
parasites), ;);
|
2022-10-19 15:11:34 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#if 0
|
2025-07-07 07:05:07 -07:00
|
|
|
for (iter = info->path_sets; iter; iter = iter->next)
|
2022-10-19 15:11:34 -07:00
|
|
|
{
|
|
|
|
|
GimpItemList *set = iter->data;
|
|
|
|
|
|
|
|
|
|
if (! gimp_item_list_is_pattern (set, NULL))
|
|
|
|
|
{
|
|
|
|
|
GList *items = gimp_item_list_get_items (set, NULL);
|
|
|
|
|
|
2025-07-07 07:05:07 -07:00
|
|
|
if (g_list_find (items, GIMP_ITEM (path)))
|
2022-10-19 15:11:34 -07:00
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_ITEM_SET_ITEM, error,
|
2023-05-11 06:18:33 -07:00
|
|
|
g_list_position (info->layer_sets, iter)), ;);
|
2022-10-19 15:11:34 -07:00
|
|
|
|
|
|
|
|
g_list_free (items);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_check_error (xcf_save_prop (info, image, PROP_END, error), ;);
|
2022-10-19 15:11:34 -07:00
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
|
|
|
|
|
2002-12-19 22:26:34 -08:00
|
|
|
static gboolean
|
2006-11-06 00:14:46 -08:00
|
|
|
xcf_save_prop (XcfInfo *info,
|
|
|
|
|
GimpImage *image,
|
|
|
|
|
PropType prop_type,
|
|
|
|
|
GError **error,
|
2006-04-12 05:49:29 -07:00
|
|
|
...)
|
2001-07-03 11:38:56 -07:00
|
|
|
{
|
2017-03-23 05:02:36 -07:00
|
|
|
guint32 size;
|
|
|
|
|
va_list args;
|
|
|
|
|
GError *tmp_error = NULL;
|
2002-12-19 22:26:34 -08:00
|
|
|
|
|
|
|
|
va_start (args, error);
|
2001-07-03 11:38:56 -07:00
|
|
|
|
|
|
|
|
switch (prop_type)
|
|
|
|
|
{
|
|
|
|
|
case PROP_END:
|
|
|
|
|
size = 0;
|
|
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_prop_type_check_error (info, prop_type, va_end (args));
|
|
|
|
|
xcf_write_int32_check_error (info, &size, 1, va_end (args));
|
2001-07-03 11:38:56 -07:00
|
|
|
break;
|
2004-03-18 07:27:23 -08:00
|
|
|
|
2001-07-03 11:38:56 -07:00
|
|
|
case PROP_COLORMAP:
|
|
|
|
|
{
|
2017-03-23 05:02:36 -07:00
|
|
|
guint32 n_colors = va_arg (args, guint32);
|
|
|
|
|
guchar *colors = va_arg (args, guchar *);
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2007-12-21 08:37:01 -08:00
|
|
|
size = 4 + n_colors * 3;
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_prop_type_check_error (info, prop_type, va_end (args));
|
|
|
|
|
xcf_write_int32_check_error (info, &size, 1, va_end (args));
|
2017-03-23 05:02:36 -07:00
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_int32_check_error (info, &n_colors, 1, va_end (args));
|
|
|
|
|
xcf_write_int8_check_error (info, colors, n_colors * 3, va_end (args));
|
2001-07-03 11:38:56 -07:00
|
|
|
}
|
|
|
|
|
break;
|
2004-03-18 07:27:23 -08:00
|
|
|
|
2001-07-03 11:38:56 -07:00
|
|
|
case PROP_ACTIVE_LAYER:
|
|
|
|
|
case PROP_ACTIVE_CHANNEL:
|
2022-10-19 15:11:34 -07:00
|
|
|
case PROP_SELECTED_PATH:
|
2001-07-03 11:38:56 -07:00
|
|
|
case PROP_SELECTION:
|
2009-08-30 12:28:59 -07:00
|
|
|
case PROP_GROUP_ITEM:
|
2001-07-03 11:38:56 -07:00
|
|
|
size = 0;
|
|
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_prop_type_check_error (info, prop_type, va_end (args));
|
|
|
|
|
xcf_write_int32_check_error (info, &size, 1, va_end (args));
|
2001-07-03 11:38:56 -07:00
|
|
|
break;
|
2004-03-18 07:27:23 -08:00
|
|
|
|
2001-07-03 11:38:56 -07:00
|
|
|
case PROP_FLOATING_SELECTION:
|
2017-03-23 05:02:36 -07:00
|
|
|
size = info->bytes_per_offset;
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_prop_type_check_error (info, prop_type, va_end (args));
|
|
|
|
|
xcf_write_int32_check_error (info, &size, 1, va_end (args));
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2017-03-23 05:02:36 -07:00
|
|
|
info->floating_sel_offset = info->cp;
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_zero_offset_check_error (info, 1, va_end (args));
|
2001-07-03 11:38:56 -07:00
|
|
|
break;
|
2004-03-18 07:27:23 -08:00
|
|
|
|
2001-07-03 11:38:56 -07:00
|
|
|
case PROP_OPACITY:
|
|
|
|
|
{
|
2017-03-23 05:02:36 -07:00
|
|
|
gdouble opacity = va_arg (args, gdouble);
|
|
|
|
|
guint32 uint_opacity = opacity * 255.999;
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2006-04-12 05:49:29 -07:00
|
|
|
size = 4;
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_prop_type_check_error (info, prop_type, va_end (args));
|
|
|
|
|
xcf_write_int32_check_error (info, &size, 1, va_end (args));
|
2017-03-23 05:02:36 -07:00
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_int32_check_error (info, &uint_opacity, 1, va_end (args));
|
2001-07-03 11:38:56 -07:00
|
|
|
}
|
|
|
|
|
break;
|
2004-03-18 07:27:23 -08:00
|
|
|
|
2015-10-16 12:59:11 -07:00
|
|
|
case PROP_FLOAT_OPACITY:
|
|
|
|
|
{
|
2017-03-23 05:02:36 -07:00
|
|
|
gfloat opacity = va_arg (args, gdouble);
|
2015-10-16 12:59:11 -07:00
|
|
|
|
|
|
|
|
size = 4;
|
|
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_prop_type_check_error (info, prop_type, va_end (args));
|
|
|
|
|
xcf_write_int32_check_error (info, &size, 1, va_end (args));
|
2017-03-23 05:02:36 -07:00
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_float_check_error (info, &opacity, 1, va_end (args));
|
2015-10-16 12:59:11 -07:00
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
2001-07-03 11:38:56 -07:00
|
|
|
case PROP_MODE:
|
|
|
|
|
{
|
2017-03-23 05:02:36 -07:00
|
|
|
gint32 mode = va_arg (args, gint32);
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2006-04-12 05:49:29 -07:00
|
|
|
size = 4;
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2017-01-08 16:27:20 -08:00
|
|
|
if (mode == GIMP_LAYER_MODE_OVERLAY_LEGACY)
|
|
|
|
|
mode = GIMP_LAYER_MODE_SOFTLIGHT_LEGACY;
|
2015-04-27 14:48:00 -07:00
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_prop_type_check_error (info, prop_type, va_end (args));
|
|
|
|
|
xcf_write_int32_check_error (info, &size, 1, va_end (args));
|
2017-03-23 05:02:36 -07:00
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_int32_check_error (info, (guint32 *) &mode, 1, va_end (args));
|
2001-07-03 11:38:56 -07:00
|
|
|
}
|
|
|
|
|
break;
|
2004-03-18 07:27:23 -08:00
|
|
|
|
2017-02-12 14:49:26 -08:00
|
|
|
case PROP_BLEND_SPACE:
|
2017-02-01 15:38:25 -08:00
|
|
|
{
|
app: future-proof XCF layer blend/composite props
The layer blend space, composite space, and composite mode
properties have a special AUTO value, which may map to different
concrete values based on the layer mode. Make sure we can change
this mapping in the future, without affecting existing XCFs (saved
after this commit), by encoding these properties as follows:
When saving an XCF, if the property has a concrete (non-AUTO)
value, which is always positive, encode it as is. If the property
is AUTO, which is always 0, encode it as the negative of the value
it actually maps to at the time of saving (note that in some cases
AUTO may map to AUTO, in which case it's encoded as 0).
When loading an XCF, if the encoded property (stored in the file)
is nonnegative, use it as is. Otherwise, compare the negative of
the encoded property to the value AUTO maps to at the time of
loading. If the values are equal, set the property to AUTO;
otherwise, use the concrete value (i.e., the negative of the value
stored in the XCF).
Note that XCFs saved prior to this commit still load fine, it's
simply that if we change the AUTO mapping in the future, all their
AUTO properties will keep being loaded as AUTO, even if the
resulting concrete values will have changed.
2017-05-21 05:01:39 -07:00
|
|
|
GimpLayerMode mode = va_arg (args, GimpLayerMode);
|
|
|
|
|
gint32 blend_space = va_arg (args, gint32);
|
|
|
|
|
|
|
|
|
|
G_STATIC_ASSERT (GIMP_LAYER_COLOR_SPACE_AUTO == 0);
|
|
|
|
|
|
|
|
|
|
/* if blend_space is AUTO, save the negative of the actual value AUTO
|
|
|
|
|
* maps to for the given mode, so that we can correctly load it even if
|
|
|
|
|
* the mapping changes in the future.
|
|
|
|
|
*/
|
|
|
|
|
if (blend_space == GIMP_LAYER_COLOR_SPACE_AUTO)
|
|
|
|
|
blend_space = -gimp_layer_mode_get_blend_space (mode);
|
2017-02-01 15:38:25 -08:00
|
|
|
|
|
|
|
|
size = 4;
|
|
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_prop_type_check_error (info, prop_type, va_end (args));
|
|
|
|
|
xcf_write_int32_check_error (info, &size, 1, va_end (args));
|
2017-03-23 05:02:36 -07:00
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_int32_check_error (info, (guint32 *) &blend_space, 1, va_end (args));
|
2017-02-12 14:49:26 -08:00
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case PROP_COMPOSITE_SPACE:
|
|
|
|
|
{
|
app: future-proof XCF layer blend/composite props
The layer blend space, composite space, and composite mode
properties have a special AUTO value, which may map to different
concrete values based on the layer mode. Make sure we can change
this mapping in the future, without affecting existing XCFs (saved
after this commit), by encoding these properties as follows:
When saving an XCF, if the property has a concrete (non-AUTO)
value, which is always positive, encode it as is. If the property
is AUTO, which is always 0, encode it as the negative of the value
it actually maps to at the time of saving (note that in some cases
AUTO may map to AUTO, in which case it's encoded as 0).
When loading an XCF, if the encoded property (stored in the file)
is nonnegative, use it as is. Otherwise, compare the negative of
the encoded property to the value AUTO maps to at the time of
loading. If the values are equal, set the property to AUTO;
otherwise, use the concrete value (i.e., the negative of the value
stored in the XCF).
Note that XCFs saved prior to this commit still load fine, it's
simply that if we change the AUTO mapping in the future, all their
AUTO properties will keep being loaded as AUTO, even if the
resulting concrete values will have changed.
2017-05-21 05:01:39 -07:00
|
|
|
GimpLayerMode mode = va_arg (args, GimpLayerMode);
|
|
|
|
|
gint32 composite_space = va_arg (args, gint32);
|
|
|
|
|
|
|
|
|
|
G_STATIC_ASSERT (GIMP_LAYER_COLOR_SPACE_AUTO == 0);
|
|
|
|
|
|
|
|
|
|
/* if composite_space is AUTO, save the negative of the actual value
|
|
|
|
|
* AUTO maps to for the given mode, so that we can correctly load it
|
|
|
|
|
* even if the mapping changes in the future.
|
|
|
|
|
*/
|
|
|
|
|
if (composite_space == GIMP_LAYER_COLOR_SPACE_AUTO)
|
|
|
|
|
composite_space = -gimp_layer_mode_get_composite_space (mode);
|
2017-02-12 14:49:26 -08:00
|
|
|
|
|
|
|
|
size = 4;
|
|
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_prop_type_check_error (info, prop_type, va_end (args));
|
|
|
|
|
xcf_write_int32_check_error (info, &size, 1, va_end (args));
|
2017-03-23 05:02:36 -07:00
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_int32_check_error (info, (guint32 *) &composite_space, 1, va_end (args));
|
2017-02-12 14:49:26 -08:00
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case PROP_COMPOSITE_MODE:
|
|
|
|
|
{
|
app: future-proof XCF layer blend/composite props
The layer blend space, composite space, and composite mode
properties have a special AUTO value, which may map to different
concrete values based on the layer mode. Make sure we can change
this mapping in the future, without affecting existing XCFs (saved
after this commit), by encoding these properties as follows:
When saving an XCF, if the property has a concrete (non-AUTO)
value, which is always positive, encode it as is. If the property
is AUTO, which is always 0, encode it as the negative of the value
it actually maps to at the time of saving (note that in some cases
AUTO may map to AUTO, in which case it's encoded as 0).
When loading an XCF, if the encoded property (stored in the file)
is nonnegative, use it as is. Otherwise, compare the negative of
the encoded property to the value AUTO maps to at the time of
loading. If the values are equal, set the property to AUTO;
otherwise, use the concrete value (i.e., the negative of the value
stored in the XCF).
Note that XCFs saved prior to this commit still load fine, it's
simply that if we change the AUTO mapping in the future, all their
AUTO properties will keep being loaded as AUTO, even if the
resulting concrete values will have changed.
2017-05-21 05:01:39 -07:00
|
|
|
GimpLayerMode mode = va_arg (args, GimpLayerMode);
|
|
|
|
|
gint32 composite_mode = va_arg (args, gint32);
|
|
|
|
|
|
|
|
|
|
G_STATIC_ASSERT (GIMP_LAYER_COMPOSITE_AUTO == 0);
|
|
|
|
|
|
|
|
|
|
/* if composite_mode is AUTO, save the negative of the actual value
|
|
|
|
|
* AUTO maps to for the given mode, so that we can correctly load it
|
|
|
|
|
* even if the mapping changes in the future.
|
|
|
|
|
*/
|
|
|
|
|
if (composite_mode == GIMP_LAYER_COMPOSITE_AUTO)
|
|
|
|
|
composite_mode = -gimp_layer_mode_get_composite_mode (mode);
|
2017-02-12 14:49:26 -08:00
|
|
|
|
|
|
|
|
size = 4;
|
|
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_prop_type_check_error (info, prop_type, va_end (args));
|
|
|
|
|
xcf_write_int32_check_error (info, &size, 1, va_end (args));
|
2017-03-23 05:02:36 -07:00
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_int32_check_error (info, (guint32 *) &composite_mode, 1, va_end (args));
|
2017-02-01 15:38:25 -08:00
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
2001-07-03 11:38:56 -07:00
|
|
|
case PROP_VISIBLE:
|
|
|
|
|
{
|
2017-03-23 05:02:36 -07:00
|
|
|
guint32 visible = va_arg (args, guint32);
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2006-04-12 05:49:29 -07:00
|
|
|
size = 4;
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_prop_type_check_error (info, prop_type, va_end (args));
|
|
|
|
|
xcf_write_int32_check_error (info, &size, 1, va_end (args));
|
2017-03-23 05:02:36 -07:00
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_int32_check_error (info, &visible, 1, va_end (args));
|
2001-07-03 11:38:56 -07:00
|
|
|
}
|
|
|
|
|
break;
|
2004-03-18 07:27:23 -08:00
|
|
|
|
2001-07-03 11:38:56 -07:00
|
|
|
case PROP_LINKED:
|
2021-12-22 13:53:15 -08:00
|
|
|
/* This code should not be called any longer. */
|
|
|
|
|
g_return_val_if_reached (FALSE);
|
|
|
|
|
#if 0
|
2001-07-03 11:38:56 -07:00
|
|
|
{
|
2017-03-23 05:02:36 -07:00
|
|
|
guint32 linked = va_arg (args, guint32);
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2006-04-12 05:49:29 -07:00
|
|
|
size = 4;
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_prop_type_check_error (info, prop_type, va_end (args));
|
|
|
|
|
xcf_write_int32_check_error (info, &size, 1, va_end (args));
|
2017-03-23 05:02:36 -07:00
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_int32_check_error (info, &linked, 1, va_end (args));
|
2001-07-03 11:38:56 -07:00
|
|
|
}
|
2021-12-22 13:53:15 -08:00
|
|
|
#endif
|
2001-07-03 11:38:56 -07:00
|
|
|
break;
|
2004-03-18 07:27:23 -08:00
|
|
|
|
2016-11-14 14:02:43 -08:00
|
|
|
case PROP_COLOR_TAG:
|
|
|
|
|
{
|
2017-03-23 05:02:36 -07:00
|
|
|
guint32 color_tag = va_arg (args, gint32);
|
2016-11-14 14:02:43 -08:00
|
|
|
|
|
|
|
|
size = 4;
|
|
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_prop_type_check_error (info, prop_type, va_end (args));
|
|
|
|
|
xcf_write_int32_check_error (info, &size, 1, va_end (args));
|
2017-03-23 05:02:36 -07:00
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_int32_check_error (info, &color_tag, 1, va_end (args));
|
2016-11-14 14:02:43 -08:00
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
2009-08-30 03:44:35 -07:00
|
|
|
case PROP_LOCK_CONTENT:
|
|
|
|
|
{
|
2017-03-23 05:02:36 -07:00
|
|
|
guint32 lock_content = va_arg (args, guint32);
|
2009-08-30 03:44:35 -07:00
|
|
|
|
|
|
|
|
size = 4;
|
|
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_prop_type_check_error (info, prop_type, va_end (args));
|
|
|
|
|
xcf_write_int32_check_error (info, &size, 1, va_end (args));
|
2017-03-23 05:02:36 -07:00
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_int32_check_error (info, &lock_content, 1, va_end (args));
|
2009-08-30 03:44:35 -07:00
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
2005-07-10 14:17:22 -07:00
|
|
|
case PROP_LOCK_ALPHA:
|
2001-07-03 11:38:56 -07:00
|
|
|
{
|
2017-03-23 05:02:36 -07:00
|
|
|
guint32 lock_alpha = va_arg (args, guint32);
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2006-04-12 05:49:29 -07:00
|
|
|
size = 4;
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_prop_type_check_error (info, prop_type, va_end (args));
|
|
|
|
|
xcf_write_int32_check_error (info, &size, 1, va_end (args));
|
2017-03-23 05:02:36 -07:00
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_int32_check_error (info, &lock_alpha, 1, va_end (args));
|
2001-07-03 11:38:56 -07:00
|
|
|
}
|
|
|
|
|
break;
|
2004-03-18 07:27:23 -08:00
|
|
|
|
2012-11-09 02:17:25 -08:00
|
|
|
case PROP_LOCK_POSITION:
|
|
|
|
|
{
|
2017-03-23 05:02:36 -07:00
|
|
|
guint32 lock_position = va_arg (args, guint32);
|
2012-11-09 02:17:25 -08:00
|
|
|
|
|
|
|
|
size = 4;
|
|
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_prop_type_check_error (info, prop_type, va_end (args));
|
|
|
|
|
xcf_write_int32_check_error (info, &size, 1, va_end (args));
|
2017-03-23 05:02:36 -07:00
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_int32_check_error (info, &lock_position, 1, va_end (args));
|
2012-11-09 02:17:25 -08:00
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
2022-02-15 05:44:11 -08:00
|
|
|
case PROP_LOCK_VISIBILITY:
|
|
|
|
|
{
|
|
|
|
|
guint32 lock_visibility = va_arg (args, guint32);
|
|
|
|
|
|
|
|
|
|
size = 4;
|
|
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_prop_type_check_error (info, prop_type, va_end (args));
|
|
|
|
|
xcf_write_int32_check_error (info, &size, 1, va_end (args));
|
2022-02-15 05:44:11 -08:00
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_int32_check_error (info, &lock_visibility, 1, va_end (args));
|
2022-02-15 05:44:11 -08:00
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
2001-07-03 11:38:56 -07:00
|
|
|
case PROP_APPLY_MASK:
|
|
|
|
|
{
|
2017-03-23 05:02:36 -07:00
|
|
|
guint32 apply_mask = va_arg (args, guint32);
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2006-04-12 05:49:29 -07:00
|
|
|
size = 4;
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_prop_type_check_error (info, prop_type, va_end (args));
|
|
|
|
|
xcf_write_int32_check_error (info, &size, 1, va_end (args));
|
2017-03-23 05:02:36 -07:00
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_int32_check_error (info, &apply_mask, 1, va_end (args));
|
2001-07-03 11:38:56 -07:00
|
|
|
}
|
|
|
|
|
break;
|
2004-03-18 07:27:23 -08:00
|
|
|
|
2001-07-03 11:38:56 -07:00
|
|
|
case PROP_EDIT_MASK:
|
|
|
|
|
{
|
2017-03-23 05:02:36 -07:00
|
|
|
guint32 edit_mask = va_arg (args, guint32);
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2006-04-12 05:49:29 -07:00
|
|
|
size = 4;
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_prop_type_check_error (info, prop_type, va_end (args));
|
|
|
|
|
xcf_write_int32_check_error (info, &size, 1, va_end (args));
|
2017-03-23 05:02:36 -07:00
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_int32_check_error (info, &edit_mask, 1, va_end (args));
|
2001-07-03 11:38:56 -07:00
|
|
|
}
|
|
|
|
|
break;
|
2004-03-18 07:27:23 -08:00
|
|
|
|
2001-07-03 11:38:56 -07:00
|
|
|
case PROP_SHOW_MASK:
|
|
|
|
|
{
|
2017-03-23 05:02:36 -07:00
|
|
|
guint32 show_mask = va_arg (args, guint32);
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2006-04-12 05:49:29 -07:00
|
|
|
size = 4;
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_prop_type_check_error (info, prop_type, va_end (args));
|
|
|
|
|
xcf_write_int32_check_error (info, &size, 1, va_end (args));
|
2017-03-23 05:02:36 -07:00
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_int32_check_error (info, &show_mask, 1, va_end (args));
|
2001-07-03 11:38:56 -07:00
|
|
|
}
|
|
|
|
|
break;
|
2004-03-18 07:27:23 -08:00
|
|
|
|
2001-07-03 11:38:56 -07:00
|
|
|
case PROP_SHOW_MASKED:
|
|
|
|
|
{
|
2017-03-23 05:02:36 -07:00
|
|
|
guint32 show_masked = va_arg (args, guint32);
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2006-04-12 05:49:29 -07:00
|
|
|
size = 4;
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_prop_type_check_error (info, prop_type, va_end (args));
|
|
|
|
|
xcf_write_int32_check_error (info, &size, 1, va_end (args));
|
2017-03-23 05:02:36 -07:00
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_int32_check_error (info, &show_masked, 1, va_end (args));
|
2001-07-03 11:38:56 -07:00
|
|
|
}
|
|
|
|
|
break;
|
2004-03-18 07:27:23 -08:00
|
|
|
|
2001-07-03 11:38:56 -07:00
|
|
|
case PROP_OFFSETS:
|
|
|
|
|
{
|
2006-04-12 05:49:29 -07:00
|
|
|
gint32 offsets[2];
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2006-04-12 05:49:29 -07:00
|
|
|
offsets[0] = va_arg (args, gint32);
|
|
|
|
|
offsets[1] = va_arg (args, gint32);
|
2017-03-23 05:02:36 -07:00
|
|
|
|
2006-04-12 05:49:29 -07:00
|
|
|
size = 8;
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_prop_type_check_error (info, prop_type, va_end (args));
|
|
|
|
|
xcf_write_int32_check_error (info, &size, 1, va_end (args));
|
2017-03-23 05:02:36 -07:00
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_int32_check_error (info, (guint32 *) offsets, 2, va_end (args));
|
2001-07-03 11:38:56 -07:00
|
|
|
}
|
|
|
|
|
break;
|
2004-03-18 07:27:23 -08:00
|
|
|
|
2001-07-03 11:38:56 -07:00
|
|
|
case PROP_COLOR:
|
|
|
|
|
{
|
2023-11-19 15:20:03 -08:00
|
|
|
GeglColor *color = va_arg (args, GeglColor *);
|
|
|
|
|
guchar col[3];
|
2017-03-24 14:35:57 -07:00
|
|
|
|
2023-11-19 15:20:03 -08:00
|
|
|
gegl_color_get_pixel (color, babl_format ("R'G'B' u8"), col);
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2006-04-12 05:49:29 -07:00
|
|
|
size = 3;
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_prop_type_check_error (info, prop_type, va_end (args));
|
|
|
|
|
xcf_write_int32_check_error (info, &size, 1, va_end (args));
|
2017-03-23 05:02:36 -07:00
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_int8_check_error (info, col, 3, va_end (args));
|
2017-03-24 14:35:57 -07:00
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case PROP_FLOAT_COLOR:
|
|
|
|
|
{
|
2023-11-19 15:20:03 -08:00
|
|
|
GeglColor *color = va_arg (args, GeglColor *);
|
|
|
|
|
gfloat col[3];
|
2017-03-24 14:35:57 -07:00
|
|
|
|
2023-11-19 15:20:03 -08:00
|
|
|
gegl_color_get_pixel (color, babl_format ("R'G'B' float"), col);
|
2017-03-24 14:35:57 -07:00
|
|
|
|
|
|
|
|
size = 3 * 4;
|
|
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_prop_type_check_error (info, prop_type, va_end (args));
|
|
|
|
|
xcf_write_int32_check_error (info, &size, 1, va_end (args));
|
2017-03-24 14:35:57 -07:00
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_float_check_error (info, col, 3, va_end (args));
|
2001-07-03 11:38:56 -07:00
|
|
|
}
|
|
|
|
|
break;
|
2004-03-18 07:27:23 -08:00
|
|
|
|
2001-07-03 11:38:56 -07:00
|
|
|
case PROP_COMPRESSION:
|
|
|
|
|
{
|
2017-03-23 05:02:36 -07:00
|
|
|
guint8 compression = (guint8) va_arg (args, guint32);
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2006-04-12 05:49:29 -07:00
|
|
|
size = 1;
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_prop_type_check_error (info, prop_type, va_end (args));
|
|
|
|
|
xcf_write_int32_check_error (info, &size, 1, va_end (args));
|
2017-03-23 05:02:36 -07:00
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_int8_check_error (info, &compression, 1, va_end (args));
|
2001-07-03 11:38:56 -07:00
|
|
|
}
|
|
|
|
|
break;
|
2004-03-18 07:27:23 -08:00
|
|
|
|
2001-07-03 11:38:56 -07:00
|
|
|
case PROP_GUIDES:
|
|
|
|
|
{
|
2017-03-23 05:02:36 -07:00
|
|
|
GList *guides = va_arg (args, GList *);
|
|
|
|
|
gint n_guides = g_list_length (guides);
|
2015-12-14 17:54:04 -08:00
|
|
|
GList *iter;
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2015-12-14 17:54:04 -08:00
|
|
|
for (iter = guides; iter; iter = g_list_next (iter))
|
|
|
|
|
{
|
|
|
|
|
/* Do not save custom guides. */
|
|
|
|
|
if (gimp_guide_is_custom (GIMP_GUIDE (iter->data)))
|
|
|
|
|
n_guides--;
|
|
|
|
|
}
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2017-03-23 05:02:36 -07:00
|
|
|
if (n_guides > 0)
|
2006-04-12 05:49:29 -07:00
|
|
|
{
|
2017-03-23 05:02:36 -07:00
|
|
|
size = n_guides * (4 + 1);
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_prop_type_check_error (info, prop_type, va_end (args));
|
|
|
|
|
xcf_write_int32_check_error (info, &size, 1, va_end (args));
|
2015-12-14 17:54:04 -08:00
|
|
|
|
2017-03-23 05:02:36 -07:00
|
|
|
for (; guides; guides = g_list_next (guides))
|
2003-02-07 09:12:21 -08:00
|
|
|
{
|
2017-03-23 05:02:36 -07:00
|
|
|
GimpGuide *guide = guides->data;
|
|
|
|
|
gint32 position = gimp_guide_get_position (guide);
|
|
|
|
|
gint8 orientation;
|
|
|
|
|
|
|
|
|
|
if (gimp_guide_is_custom (guide))
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
switch (gimp_guide_get_orientation (guide))
|
|
|
|
|
{
|
|
|
|
|
case GIMP_ORIENTATION_HORIZONTAL:
|
|
|
|
|
orientation = XCF_ORIENTATION_HORIZONTAL;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case GIMP_ORIENTATION_VERTICAL:
|
|
|
|
|
orientation = XCF_ORIENTATION_VERTICAL;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
g_warning ("%s: skipping guide with bad orientation",
|
|
|
|
|
G_STRFUNC);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_int32_check_error (info, (guint32 *) &position, 1, va_end (args));
|
|
|
|
|
xcf_write_int8_check_error (info, (guint8 *) &orientation, 1, va_end (args));
|
2003-02-07 09:12:21 -08:00
|
|
|
}
|
2006-04-12 05:49:29 -07:00
|
|
|
}
|
2001-07-03 11:38:56 -07:00
|
|
|
}
|
2006-08-10 10:10:12 -07:00
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case PROP_SAMPLE_POINTS:
|
|
|
|
|
{
|
2018-07-15 16:42:19 -07:00
|
|
|
GList *sample_points = va_arg (args, GList *);
|
|
|
|
|
gint n_sample_points = g_list_length (sample_points);
|
|
|
|
|
|
|
|
|
|
size = n_sample_points * (5 * 4);
|
|
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_prop_type_check_error (info, prop_type, va_end (args));
|
|
|
|
|
xcf_write_int32_check_error (info, &size, 1, va_end (args));
|
2018-07-15 16:42:19 -07:00
|
|
|
|
|
|
|
|
for (; sample_points; sample_points = g_list_next (sample_points))
|
|
|
|
|
{
|
|
|
|
|
GimpSamplePoint *sample_point = sample_points->data;
|
|
|
|
|
gint32 x, y;
|
|
|
|
|
GimpColorPickMode pick_mode;
|
|
|
|
|
guint32 padding[2] = { 0, };
|
|
|
|
|
|
|
|
|
|
gimp_sample_point_get_position (sample_point, &x, &y);
|
|
|
|
|
pick_mode = gimp_sample_point_get_pick_mode (sample_point);
|
|
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_int32_check_error (info, (guint32 *) &x, 1, va_end (args));
|
|
|
|
|
xcf_write_int32_check_error (info, (guint32 *) &y, 1, va_end (args));
|
|
|
|
|
xcf_write_int32_check_error (info, (guint32 *) &pick_mode, 1, va_end (args));
|
|
|
|
|
xcf_write_int32_check_error (info, (guint32 *) padding, 2, va_end (args));
|
2018-07-15 16:42:19 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case PROP_OLD_SAMPLE_POINTS:
|
|
|
|
|
{
|
2017-03-23 05:02:36 -07:00
|
|
|
GList *sample_points = va_arg (args, GList *);
|
|
|
|
|
gint n_sample_points = g_list_length (sample_points);
|
2006-08-10 10:10:12 -07:00
|
|
|
|
|
|
|
|
size = n_sample_points * (4 + 4);
|
|
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_prop_type_check_error (info, prop_type, va_end (args));
|
|
|
|
|
xcf_write_int32_check_error (info, &size, 1, va_end (args));
|
2006-08-10 10:10:12 -07:00
|
|
|
|
|
|
|
|
for (; sample_points; sample_points = g_list_next (sample_points))
|
|
|
|
|
{
|
|
|
|
|
GimpSamplePoint *sample_point = sample_points->data;
|
|
|
|
|
gint32 x, y;
|
|
|
|
|
|
2016-01-04 13:06:27 -08:00
|
|
|
gimp_sample_point_get_position (sample_point, &x, &y);
|
2006-08-10 10:10:12 -07:00
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_int32_check_error (info, (guint32 *) &x, 1, va_end (args));
|
|
|
|
|
xcf_write_int32_check_error (info, (guint32 *) &y, 1, va_end (args));
|
2006-08-10 10:10:12 -07:00
|
|
|
}
|
|
|
|
|
}
|
2001-07-03 11:38:56 -07:00
|
|
|
break;
|
2004-03-18 07:27:23 -08:00
|
|
|
|
2001-07-03 11:38:56 -07:00
|
|
|
case PROP_RESOLUTION:
|
|
|
|
|
{
|
2017-03-23 05:02:36 -07:00
|
|
|
gfloat resolution[2];
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2017-03-23 05:02:36 -07:00
|
|
|
resolution[0] = va_arg (args, double);
|
|
|
|
|
resolution[1] = va_arg (args, double);
|
2003-09-10 03:40:57 -07:00
|
|
|
|
2017-03-23 05:02:36 -07:00
|
|
|
size = 2 * 4;
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_prop_type_check_error (info, prop_type, va_end (args));
|
|
|
|
|
xcf_write_int32_check_error (info, &size, 1, va_end (args));
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_float_check_error (info, resolution, 2, va_end (args));
|
2001-07-03 11:38:56 -07:00
|
|
|
}
|
|
|
|
|
break;
|
2004-03-18 07:27:23 -08:00
|
|
|
|
2001-07-03 11:38:56 -07:00
|
|
|
case PROP_TATTOO:
|
|
|
|
|
{
|
2017-03-23 05:02:36 -07:00
|
|
|
guint32 tattoo = va_arg (args, guint32);
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2006-04-12 05:49:29 -07:00
|
|
|
size = 4;
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_prop_type_check_error (info, prop_type, va_end (args));
|
|
|
|
|
xcf_write_int32_check_error (info, &size, 1, va_end (args));
|
2017-03-23 05:02:36 -07:00
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_int32_check_error (info, &tattoo, 1, va_end (args));
|
2001-07-03 11:38:56 -07:00
|
|
|
}
|
|
|
|
|
break;
|
2004-03-18 07:27:23 -08:00
|
|
|
|
2001-07-03 11:38:56 -07:00
|
|
|
case PROP_PARASITES:
|
|
|
|
|
{
|
2017-03-23 05:02:36 -07:00
|
|
|
GimpParasiteList *list = va_arg (args, GimpParasiteList *);
|
2001-07-04 15:59:25 -07:00
|
|
|
|
2006-04-12 05:49:29 -07:00
|
|
|
if (gimp_parasite_list_persistent_length (list) > 0)
|
|
|
|
|
{
|
2017-03-23 03:44:41 -07:00
|
|
|
goffset base;
|
|
|
|
|
goffset pos;
|
2017-03-23 05:02:36 -07:00
|
|
|
|
|
|
|
|
size = 0;
|
2012-08-03 18:20:36 -07:00
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_prop_type_check_error (info, prop_type, va_end (args));
|
2004-01-26 01:22:06 -08:00
|
|
|
|
2017-03-23 03:44:41 -07:00
|
|
|
/* because we don't know how much room the parasite list
|
|
|
|
|
* will take we save the file position and write the
|
|
|
|
|
* length later
|
2006-04-12 05:49:29 -07:00
|
|
|
*/
|
2001-07-19 08:34:19 -07:00
|
|
|
pos = info->cp;
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_int32_check_error (info, &size, 1, va_end (args));
|
2017-03-23 03:44:41 -07:00
|
|
|
|
2006-04-12 05:49:29 -07:00
|
|
|
base = info->cp;
|
2003-09-10 09:22:33 -07:00
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_check_error (xcf_save_parasite_list (info, list, error), va_end (args));
|
2003-09-10 09:22:33 -07:00
|
|
|
|
2017-03-23 05:02:36 -07:00
|
|
|
size = info->cp - base;
|
2014-10-15 14:36:06 -07:00
|
|
|
|
2006-04-12 05:49:29 -07:00
|
|
|
/* go back to the saved position and write the length */
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_check_error (xcf_seek_pos (info, pos, error), va_end (args));
|
|
|
|
|
xcf_write_int32_check_error (info, &size, 1, va_end (args));
|
2003-09-10 03:40:57 -07:00
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_check_error (xcf_seek_pos (info, base + size, error), va_end (args));
|
2006-04-12 05:49:29 -07:00
|
|
|
}
|
2001-07-03 11:38:56 -07:00
|
|
|
}
|
|
|
|
|
break;
|
2004-03-18 07:27:23 -08:00
|
|
|
|
2001-07-03 11:38:56 -07:00
|
|
|
case PROP_UNIT:
|
|
|
|
|
{
|
Issue #8900 and #9923: reimplementing GimpUnit as a proper class.
This fixes all our GObject Introspection issues with GimpUnit which was
both an enum and an int-derived type of user-defined units *completing*
the enum values. GIR clearly didn't like this!
Now GimpUnit is a proper class and units are unique objects, allowing to
compare them with an identity test (i.e. `unit == gimp_unit_pixel ()`
tells us if unit is the pixel unit or not), which makes it easy to use,
just like with int, yet adding also methods, making for nicer
introspected API.
As an aside, this also fixes #10738, by having all the built-in units
retrievable even if libgimpbase had not been properly initialized with
gimp_base_init().
I haven't checked in details how GIR works to introspect, but it looks
like it loads the library to inspect and runs functions, hence
triggering some CRITICALS because virtual methods (supposed to be
initialized with gimp_base_init() run by libgimp) are not set. This new
code won't trigger any critical because the vtable method are now not
necessary, at least for all built-in units.
Note that GimpUnit is still in libgimpbase. It could have been moved to
libgimp in order to avoid any virtual method table (since we need to
keep core and libgimp side's units in sync, PDB is required), but too
many libgimpwidgets widgets were already using GimpUnit. And technically
most of GimpUnit logic doesn't require PDB (only the creation/sync
part). This is one of the reasons why user-created GimpUnit list is
handled and stored differently from other types of objects.
Globally this simplifies the code a lot too and we don't need separate
implementations of various utils for core and libgimp, which means less
prone to errors.
2024-07-25 11:55:21 -07:00
|
|
|
GimpUnit *unit = va_arg (args, GimpUnit *);
|
|
|
|
|
guint32 unit_index = gimp_unit_get_id (unit);
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2006-04-12 05:49:29 -07:00
|
|
|
size = 4;
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_prop_type_check_error (info, prop_type, va_end (args));
|
|
|
|
|
xcf_write_int32_check_error (info, &size, 1, va_end (args));
|
2017-03-23 05:02:36 -07:00
|
|
|
|
Issue #8900 and #9923: reimplementing GimpUnit as a proper class.
This fixes all our GObject Introspection issues with GimpUnit which was
both an enum and an int-derived type of user-defined units *completing*
the enum values. GIR clearly didn't like this!
Now GimpUnit is a proper class and units are unique objects, allowing to
compare them with an identity test (i.e. `unit == gimp_unit_pixel ()`
tells us if unit is the pixel unit or not), which makes it easy to use,
just like with int, yet adding also methods, making for nicer
introspected API.
As an aside, this also fixes #10738, by having all the built-in units
retrievable even if libgimpbase had not been properly initialized with
gimp_base_init().
I haven't checked in details how GIR works to introspect, but it looks
like it loads the library to inspect and runs functions, hence
triggering some CRITICALS because virtual methods (supposed to be
initialized with gimp_base_init() run by libgimp) are not set. This new
code won't trigger any critical because the vtable method are now not
necessary, at least for all built-in units.
Note that GimpUnit is still in libgimpbase. It could have been moved to
libgimp in order to avoid any virtual method table (since we need to
keep core and libgimp side's units in sync, PDB is required), but too
many libgimpwidgets widgets were already using GimpUnit. And technically
most of GimpUnit logic doesn't require PDB (only the creation/sync
part). This is one of the reasons why user-created GimpUnit list is
handled and stored differently from other types of objects.
Globally this simplifies the code a lot too and we don't need separate
implementations of various utils for core and libgimp, which means less
prone to errors.
2024-07-25 11:55:21 -07:00
|
|
|
xcf_write_int32_check_error (info, &unit_index, 1, va_end (args));
|
2001-07-03 11:38:56 -07:00
|
|
|
}
|
|
|
|
|
break;
|
2004-03-18 07:27:23 -08:00
|
|
|
|
2001-07-03 11:38:56 -07:00
|
|
|
case PROP_PATHS:
|
|
|
|
|
{
|
2017-03-23 03:44:41 -07:00
|
|
|
goffset base;
|
|
|
|
|
goffset pos;
|
2017-03-23 05:02:36 -07:00
|
|
|
|
|
|
|
|
size = 0;
|
2003-05-21 10:38:14 -07:00
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_prop_type_check_error (info, prop_type, va_end (args));
|
2003-05-21 10:38:14 -07:00
|
|
|
|
2017-03-23 03:44:41 -07:00
|
|
|
/* because we don't know how much room the paths list will
|
|
|
|
|
* take we save the file position and write the length later
|
2004-01-26 01:22:06 -08:00
|
|
|
*/
|
2003-05-21 10:38:14 -07:00
|
|
|
pos = info->cp;
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_int32_check_error (info, &size, 1, va_end (args));
|
2001-07-19 08:34:19 -07:00
|
|
|
|
2003-05-21 10:38:14 -07:00
|
|
|
base = info->cp;
|
2001-07-19 08:34:19 -07:00
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_check_error (xcf_save_old_paths (info, image, error), va_end (args));
|
2003-05-21 10:38:14 -07:00
|
|
|
|
2017-03-23 05:02:36 -07:00
|
|
|
size = info->cp - base;
|
2003-05-21 10:38:14 -07:00
|
|
|
|
|
|
|
|
/* go back to the saved position and write the length */
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_check_error (xcf_seek_pos (info, pos, error), va_end (args));
|
|
|
|
|
xcf_write_int32_check_error (info, &size, 1, va_end (args));
|
2003-05-21 10:38:14 -07:00
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_check_error (xcf_seek_pos (info, base + size, error), va_end (args));
|
2001-07-03 11:38:56 -07:00
|
|
|
}
|
|
|
|
|
break;
|
2004-03-18 07:27:23 -08:00
|
|
|
|
2001-07-03 11:38:56 -07:00
|
|
|
case PROP_USER_UNIT:
|
|
|
|
|
{
|
Issue #8900 and #9923: reimplementing GimpUnit as a proper class.
This fixes all our GObject Introspection issues with GimpUnit which was
both an enum and an int-derived type of user-defined units *completing*
the enum values. GIR clearly didn't like this!
Now GimpUnit is a proper class and units are unique objects, allowing to
compare them with an identity test (i.e. `unit == gimp_unit_pixel ()`
tells us if unit is the pixel unit or not), which makes it easy to use,
just like with int, yet adding also methods, making for nicer
introspected API.
As an aside, this also fixes #10738, by having all the built-in units
retrievable even if libgimpbase had not been properly initialized with
gimp_base_init().
I haven't checked in details how GIR works to introspect, but it looks
like it loads the library to inspect and runs functions, hence
triggering some CRITICALS because virtual methods (supposed to be
initialized with gimp_base_init() run by libgimp) are not set. This new
code won't trigger any critical because the vtable method are now not
necessary, at least for all built-in units.
Note that GimpUnit is still in libgimpbase. It could have been moved to
libgimp in order to avoid any virtual method table (since we need to
keep core and libgimp side's units in sync, PDB is required), but too
many libgimpwidgets widgets were already using GimpUnit. And technically
most of GimpUnit logic doesn't require PDB (only the creation/sync
part). This is one of the reasons why user-created GimpUnit list is
handled and stored differently from other types of objects.
Globally this simplifies the code a lot too and we don't need separate
implementations of various utils for core and libgimp, which means less
prone to errors.
2024-07-25 11:55:21 -07:00
|
|
|
GimpUnit *unit = va_arg (args, GimpUnit *);
|
2006-04-12 05:49:29 -07:00
|
|
|
const gchar *unit_strings[5];
|
|
|
|
|
gfloat factor;
|
|
|
|
|
guint32 digits;
|
|
|
|
|
|
|
|
|
|
/* write the entire unit definition */
|
Issue #434: remove broken plural support for GimpUnit.
Rather than trying to implement full i18n plural support, we just remove
this failed attempt from the past. The fact is that to get proper
support, we'd basically need to reimplement a Gettext-like plural
definition syntax within our API, then ask people to write down this
plural definition for their language, then to write every plural form…
all this for custom units which only them will ever see!
Moreover code investigation shows that the singular form was simply
never used, and the plural form was always used (whatever the actual
unit value displayed).
As for the "identifier", this was a text which was never shown anywhere
(except in the unit editor) and for all built-in units, as well as
default unitrc units, it was equivalent to the English plural value.
So we now just have a unique name which is the "long label" to be used
everywhere in the GUI, and abbreviation will be basically the "short
label". That's it. No useless (or worse, not actually usable because it
was not generic internationalization) values anymore!
2024-08-05 07:02:47 -07:00
|
|
|
unit_strings[0] = gimp_unit_get_name (unit);
|
2010-02-21 07:46:39 -08:00
|
|
|
factor = gimp_unit_get_factor (unit);
|
|
|
|
|
digits = gimp_unit_get_digits (unit);
|
|
|
|
|
unit_strings[1] = gimp_unit_get_symbol (unit);
|
|
|
|
|
unit_strings[2] = gimp_unit_get_abbreviation (unit);
|
Issue #434: remove broken plural support for GimpUnit.
Rather than trying to implement full i18n plural support, we just remove
this failed attempt from the past. The fact is that to get proper
support, we'd basically need to reimplement a Gettext-like plural
definition syntax within our API, then ask people to write down this
plural definition for their language, then to write every plural form…
all this for custom units which only them will ever see!
Moreover code investigation shows that the singular form was simply
never used, and the plural form was always used (whatever the actual
unit value displayed).
As for the "identifier", this was a text which was never shown anywhere
(except in the unit editor) and for all built-in units, as well as
default unitrc units, it was equivalent to the English plural value.
So we now just have a unique name which is the "long label" to be used
everywhere in the GUI, and abbreviation will be basically the "short
label". That's it. No useless (or worse, not actually usable because it
was not generic internationalization) values anymore!
2024-08-05 07:02:47 -07:00
|
|
|
/* Singular and plural forms were deprecated in XCF 21. Just use
|
|
|
|
|
* the unit name as bogus (yet reasonable) replacements.
|
|
|
|
|
*/
|
|
|
|
|
unit_strings[3] = gimp_unit_get_name (unit);
|
|
|
|
|
unit_strings[4] = gimp_unit_get_name (unit);
|
2006-04-12 05:49:29 -07:00
|
|
|
|
|
|
|
|
size =
|
|
|
|
|
2 * 4 +
|
|
|
|
|
strlen (unit_strings[0]) ? strlen (unit_strings[0]) + 5 : 4 +
|
|
|
|
|
strlen (unit_strings[1]) ? strlen (unit_strings[1]) + 5 : 4 +
|
Issue #434: remove broken plural support for GimpUnit.
Rather than trying to implement full i18n plural support, we just remove
this failed attempt from the past. The fact is that to get proper
support, we'd basically need to reimplement a Gettext-like plural
definition syntax within our API, then ask people to write down this
plural definition for their language, then to write every plural form…
all this for custom units which only them will ever see!
Moreover code investigation shows that the singular form was simply
never used, and the plural form was always used (whatever the actual
unit value displayed).
As for the "identifier", this was a text which was never shown anywhere
(except in the unit editor) and for all built-in units, as well as
default unitrc units, it was equivalent to the English plural value.
So we now just have a unique name which is the "long label" to be used
everywhere in the GUI, and abbreviation will be basically the "short
label". That's it. No useless (or worse, not actually usable because it
was not generic internationalization) values anymore!
2024-08-05 07:02:47 -07:00
|
|
|
strlen (unit_strings[2]) ? strlen (unit_strings[2]) + 5 : 4;
|
|
|
|
|
|
|
|
|
|
if (info->file_version < 21)
|
|
|
|
|
size +=
|
|
|
|
|
strlen (unit_strings[3]) ? strlen (unit_strings[3]) + 5 : 4 +
|
|
|
|
|
strlen (unit_strings[4]) ? strlen (unit_strings[4]) + 5 : 4;
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_prop_type_check_error (info, prop_type, va_end (args));
|
|
|
|
|
xcf_write_int32_check_error (info, &size, 1, va_end (args));
|
2017-03-23 05:02:36 -07:00
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_float_check_error (info, &factor, 1, va_end (args));
|
|
|
|
|
xcf_write_int32_check_error (info, &digits, 1, va_end (args));
|
Issue #434: remove broken plural support for GimpUnit.
Rather than trying to implement full i18n plural support, we just remove
this failed attempt from the past. The fact is that to get proper
support, we'd basically need to reimplement a Gettext-like plural
definition syntax within our API, then ask people to write down this
plural definition for their language, then to write every plural form…
all this for custom units which only them will ever see!
Moreover code investigation shows that the singular form was simply
never used, and the plural form was always used (whatever the actual
unit value displayed).
As for the "identifier", this was a text which was never shown anywhere
(except in the unit editor) and for all built-in units, as well as
default unitrc units, it was equivalent to the English plural value.
So we now just have a unique name which is the "long label" to be used
everywhere in the GUI, and abbreviation will be basically the "short
label". That's it. No useless (or worse, not actually usable because it
was not generic internationalization) values anymore!
2024-08-05 07:02:47 -07:00
|
|
|
if (info->file_version < 21)
|
|
|
|
|
xcf_write_string_check_error (info, (gchar **) unit_strings, 5, va_end (args));
|
|
|
|
|
else
|
|
|
|
|
xcf_write_string_check_error (info, (gchar **) unit_strings, 3, va_end (args));
|
2001-07-03 11:38:56 -07:00
|
|
|
}
|
|
|
|
|
break;
|
2004-03-18 07:27:23 -08:00
|
|
|
|
2003-09-09 08:46:59 -07:00
|
|
|
case PROP_VECTORS:
|
|
|
|
|
{
|
2017-03-23 03:44:41 -07:00
|
|
|
goffset base;
|
|
|
|
|
goffset pos;
|
2017-03-23 05:02:36 -07:00
|
|
|
|
|
|
|
|
size = 0;
|
2003-09-09 08:46:59 -07:00
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_prop_type_check_error (info, prop_type, va_end (args));
|
2003-09-09 08:46:59 -07:00
|
|
|
|
2017-03-23 03:44:41 -07:00
|
|
|
/* because we don't know how much room the paths list will
|
|
|
|
|
* take we save the file position and write the length later
|
2004-01-26 01:22:06 -08:00
|
|
|
*/
|
2003-09-09 08:46:59 -07:00
|
|
|
pos = info->cp;
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_int32_check_error (info, &size, 1, va_end (args));
|
2003-09-09 08:46:59 -07:00
|
|
|
|
|
|
|
|
base = info->cp;
|
|
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_check_error (xcf_save_old_vectors (info, image, error), va_end (args));
|
2003-09-09 08:46:59 -07:00
|
|
|
|
2017-03-23 05:02:36 -07:00
|
|
|
size = info->cp - base;
|
2003-09-09 08:46:59 -07:00
|
|
|
|
|
|
|
|
/* go back to the saved position and write the length */
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_check_error (xcf_seek_pos (info, pos, error), va_end (args));
|
|
|
|
|
xcf_write_int32_check_error (info, &size, 1, va_end (args));
|
2003-09-09 08:46:59 -07:00
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_check_error (xcf_seek_pos (info, base + size, error), va_end (args));
|
2003-09-09 08:46:59 -07:00
|
|
|
}
|
|
|
|
|
break;
|
2004-03-18 07:27:23 -08:00
|
|
|
|
|
|
|
|
case PROP_TEXT_LAYER_FLAGS:
|
|
|
|
|
{
|
2017-03-23 05:02:36 -07:00
|
|
|
guint32 flags = va_arg (args, guint32);
|
2004-03-18 07:27:23 -08:00
|
|
|
|
2006-04-12 05:49:29 -07:00
|
|
|
size = 4;
|
2004-03-18 07:27:23 -08:00
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_prop_type_check_error (info, prop_type, va_end (args));
|
|
|
|
|
xcf_write_int32_check_error (info, &size, 1, va_end (args));
|
2017-03-23 05:02:36 -07:00
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_int32_check_error (info, &flags, 1, va_end (args));
|
2004-03-18 07:27:23 -08:00
|
|
|
}
|
|
|
|
|
break;
|
2009-08-30 12:28:59 -07:00
|
|
|
|
2025-08-16 14:43:39 -07:00
|
|
|
case PROP_VECTOR_LAYER:
|
|
|
|
|
{
|
|
|
|
|
GimpVectorLayer *vector_layer;
|
|
|
|
|
GimpVectorLayerOptions *options;
|
|
|
|
|
guint32 uint_val;
|
|
|
|
|
goffset base;
|
|
|
|
|
goffset pos;
|
|
|
|
|
|
|
|
|
|
vector_layer = va_arg (args, GimpVectorLayer *);
|
|
|
|
|
options = vector_layer->options;
|
|
|
|
|
|
|
|
|
|
xcf_write_prop_type_check_error (info, prop_type, va_end (args));
|
|
|
|
|
|
|
|
|
|
size = 0;
|
|
|
|
|
pos = info->cp;
|
|
|
|
|
xcf_write_int32_check_error (info, &size, 1, va_end (args));
|
|
|
|
|
base = info->cp;
|
|
|
|
|
|
2025-10-12 16:09:50 -07:00
|
|
|
uint_val = (guint32) gimp_rasterizable_is_rasterized (GIMP_RASTERIZABLE (vector_layer));
|
2025-08-16 14:43:39 -07:00
|
|
|
xcf_write_int32_check_error (info, (guint32 *) &uint_val, 1, va_end (args));
|
|
|
|
|
|
|
|
|
|
uint_val = gimp_item_get_tattoo (GIMP_ITEM (options->path));
|
|
|
|
|
xcf_write_int32_check_error (info, (guint32 *) &uint_val, 1, va_end (args));
|
|
|
|
|
|
|
|
|
|
uint_val = (guint32) options->enable_fill;
|
|
|
|
|
xcf_write_int32_check_error (info, (guint32 *) &uint_val, 1, va_end (args));
|
|
|
|
|
|
|
|
|
|
uint_val = (guint32) options->enable_stroke;
|
|
|
|
|
xcf_write_int32_check_error (info, (guint32 *) &uint_val, 1, va_end (args));
|
|
|
|
|
|
|
|
|
|
xcf_check_error (xcf_save_fill_options (info, options->fill_options, error), va_end (args));
|
|
|
|
|
xcf_check_error (xcf_save_stroke_options (info, options->stroke_options, error), va_end (args));
|
|
|
|
|
|
|
|
|
|
size = info->cp - base;
|
|
|
|
|
/* go back to the saved position and write the length */
|
|
|
|
|
xcf_check_error (xcf_seek_pos (info, pos, error), va_end (args));
|
|
|
|
|
xcf_write_int32_check_error (info, &size, 1, va_end (args));
|
|
|
|
|
xcf_check_error (xcf_seek_pos (info, base + size, error), va_end (args));
|
|
|
|
|
}
|
2019-07-08 07:48:56 -07:00
|
|
|
break;
|
|
|
|
|
|
2025-08-26 13:51:36 -07:00
|
|
|
case PROP_LINK_LAYER:
|
2019-07-08 07:48:56 -07:00
|
|
|
{
|
|
|
|
|
GimpLinkLayer *layer = va_arg (args, GimpLinkLayer *);
|
2025-08-04 11:22:45 -07:00
|
|
|
gchar *path = NULL;
|
2025-08-28 14:06:11 -07:00
|
|
|
gint width;
|
|
|
|
|
gint height;
|
|
|
|
|
guint32 dimensions[2];
|
2019-07-08 07:48:56 -07:00
|
|
|
guint32 flags;
|
|
|
|
|
|
|
|
|
|
flags = gimp_link_layer_get_xcf_flags (layer);
|
2025-08-04 11:22:45 -07:00
|
|
|
gimp_link_get_file (gimp_link_layer_get_link (layer), info->file, &path);
|
2019-07-08 07:48:56 -07:00
|
|
|
|
2025-08-28 14:06:11 -07:00
|
|
|
size = 3 * 4 + (strlen (path) ? strlen (path) + 1 : 4);
|
2019-07-08 07:48:56 -07:00
|
|
|
|
2025-03-31 09:41:09 -07:00
|
|
|
xcf_write_prop_type_check_error (info, prop_type, va_end (args));
|
|
|
|
|
xcf_write_int32_check_error (info, &size, 1, va_end (args));
|
2019-07-08 07:48:56 -07:00
|
|
|
|
2025-03-31 09:41:09 -07:00
|
|
|
xcf_write_int32_check_error (info, &flags, 1, va_end (args));
|
|
|
|
|
xcf_write_string_check_error (info, (gchar **) &path, 1, va_end (args));
|
2025-08-04 11:22:45 -07:00
|
|
|
|
2025-08-28 14:06:11 -07:00
|
|
|
gimp_link_get_size (gimp_link_layer_get_link (layer), &width, &height);
|
|
|
|
|
dimensions[0] = width;
|
|
|
|
|
dimensions[1] = height;
|
|
|
|
|
xcf_write_int32_check_error (info, dimensions, 2, va_end (args));
|
|
|
|
|
|
2025-08-04 11:22:45 -07:00
|
|
|
g_free (path);
|
2019-07-08 07:48:56 -07:00
|
|
|
}
|
2025-08-16 14:43:39 -07:00
|
|
|
break;
|
|
|
|
|
|
2025-08-26 13:51:36 -07:00
|
|
|
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;
|
|
|
|
|
|
2009-08-30 12:28:59 -07:00
|
|
|
case PROP_ITEM_PATH:
|
|
|
|
|
{
|
2017-03-23 05:02:36 -07:00
|
|
|
GList *path = va_arg (args, GList *);
|
2009-08-30 12:28:59 -07:00
|
|
|
|
|
|
|
|
size = 4 * g_list_length (path);
|
|
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_prop_type_check_error (info, prop_type, va_end (args));
|
|
|
|
|
xcf_write_int32_check_error (info, &size, 1, va_end (args));
|
2009-08-30 12:28:59 -07:00
|
|
|
|
|
|
|
|
while (path)
|
|
|
|
|
{
|
|
|
|
|
guint32 index = GPOINTER_TO_UINT (path->data);
|
|
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_int32_check_error (info, &index, 1, va_end (args));
|
2009-08-30 12:28:59 -07:00
|
|
|
|
|
|
|
|
path = g_list_next (path);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
2011-09-25 12:57:20 -07:00
|
|
|
|
|
|
|
|
case PROP_GROUP_ITEM_FLAGS:
|
|
|
|
|
{
|
2017-03-23 05:02:36 -07:00
|
|
|
guint32 flags = va_arg (args, guint32);
|
2011-09-25 12:57:20 -07:00
|
|
|
|
|
|
|
|
size = 4;
|
|
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_prop_type_check_error (info, prop_type, va_end (args));
|
|
|
|
|
xcf_write_int32_check_error (info, &size, 1, va_end (args));
|
|
|
|
|
xcf_write_int32_check_error (info, &flags, 1, va_end (args));
|
2011-09-25 12:57:20 -07:00
|
|
|
}
|
|
|
|
|
break;
|
2021-12-22 13:53:15 -08:00
|
|
|
|
|
|
|
|
case PROP_ITEM_SET:
|
|
|
|
|
{
|
|
|
|
|
GimpItemList *set = va_arg (args, GimpItemList *);
|
|
|
|
|
const gchar *string;
|
|
|
|
|
guint32 method;
|
|
|
|
|
guint32 item_type;
|
|
|
|
|
goffset base;
|
|
|
|
|
goffset pos;
|
|
|
|
|
|
|
|
|
|
size = 0;
|
|
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_prop_type_check_error (info, prop_type, va_end (args));
|
2021-12-22 13:53:15 -08:00
|
|
|
pos = info->cp;
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_int32_check_error (info, &size, 1, va_end (args));
|
2021-12-22 13:53:15 -08:00
|
|
|
base = info->cp;
|
|
|
|
|
|
|
|
|
|
if (gimp_item_list_get_item_type (set) == GIMP_TYPE_LAYER)
|
|
|
|
|
item_type = 0;
|
|
|
|
|
else if (gimp_item_list_get_item_type (set) == GIMP_TYPE_CHANNEL)
|
|
|
|
|
item_type = 1;
|
2024-07-11 23:16:25 -07:00
|
|
|
else if (gimp_item_list_get_item_type (set) == GIMP_TYPE_PATH)
|
2021-12-22 13:53:15 -08:00
|
|
|
item_type = 2;
|
|
|
|
|
else
|
|
|
|
|
g_return_val_if_reached (FALSE);
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_int32_check_error (info, &item_type, 1, va_end (args));
|
2021-12-22 13:53:15 -08:00
|
|
|
|
2026-03-31 10:13:58 -07:00
|
|
|
if (! gimp_item_list_is_pattern (set, (GimpSelectMethod *) &method))
|
2021-12-22 13:53:15 -08:00
|
|
|
method = G_MAXUINT32;
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_int32_check_error (info, &method, 1, va_end (args));
|
2021-12-22 13:53:15 -08:00
|
|
|
|
|
|
|
|
string = gimp_object_get_name (set);
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_string_check_error (info, (gchar **) &string, 1, va_end (args));
|
2021-12-22 13:53:15 -08:00
|
|
|
|
|
|
|
|
/* go back to the saved position and write the length */
|
|
|
|
|
size = info->cp - base;
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_check_error (xcf_seek_pos (info, pos, error), va_end (args));
|
|
|
|
|
xcf_write_int32_check_error (info, &size, 1, va_end (args));
|
2021-12-22 13:53:15 -08:00
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_check_error (xcf_seek_pos (info, base + size, error), va_end (args));
|
2021-12-22 13:53:15 -08:00
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case PROP_ITEM_SET_ITEM:
|
|
|
|
|
{
|
|
|
|
|
guint32 set_n = va_arg (args, guint32);
|
|
|
|
|
|
|
|
|
|
size = 4;
|
|
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_prop_type_check_error (info, prop_type, va_end (args));
|
|
|
|
|
xcf_write_int32_check_error (info, &size, 1, va_end (args));
|
|
|
|
|
xcf_write_int32_check_error (info, &set_n, 1, va_end (args));
|
2021-12-22 13:53:15 -08:00
|
|
|
}
|
|
|
|
|
break;
|
2023-06-19 07:54:21 -07:00
|
|
|
|
|
|
|
|
case PROP_FILTER_REGION:
|
|
|
|
|
{
|
|
|
|
|
guint32 filter_region = va_arg (args, guint32);
|
|
|
|
|
|
|
|
|
|
size = 4;
|
|
|
|
|
|
|
|
|
|
xcf_write_prop_type_check_error (info, prop_type, va_end (args));
|
|
|
|
|
xcf_write_int32_check_error (info, &size, 1, va_end (args));
|
|
|
|
|
|
|
|
|
|
xcf_write_int32_check_error (info, &filter_region, 1, va_end (args));
|
|
|
|
|
}
|
|
|
|
|
break;
|
2024-01-03 10:57:08 -08:00
|
|
|
|
2024-01-22 08:49:10 -08:00
|
|
|
case PROP_FILTER_ARGUMENT:
|
2024-01-03 10:57:08 -08:00
|
|
|
{
|
|
|
|
|
const gchar *string = va_arg (args, const gchar *);
|
|
|
|
|
guint32 filter_type = va_arg (args, guint32);
|
|
|
|
|
GValue filter_value;
|
|
|
|
|
goffset pos;
|
|
|
|
|
goffset base;
|
|
|
|
|
|
|
|
|
|
size = 0;
|
|
|
|
|
|
|
|
|
|
xcf_write_prop_type_check_error (info, prop_type, va_end (args));
|
|
|
|
|
pos = info->cp;
|
|
|
|
|
xcf_write_int32_check_error (info, &size, 1, va_end (args));
|
|
|
|
|
base = info->cp;
|
|
|
|
|
filter_value = va_arg (args, GValue);
|
|
|
|
|
|
|
|
|
|
xcf_write_string_check_error (info, (gchar **) &string, 1, va_end (args));
|
|
|
|
|
xcf_write_int32_check_error (info, &filter_type, 1, va_end (args));
|
|
|
|
|
|
|
|
|
|
switch (filter_type)
|
|
|
|
|
{
|
|
|
|
|
case FILTER_PROP_INT:
|
2024-02-08 07:38:05 -08:00
|
|
|
case FILTER_PROP_UINT:
|
2024-01-22 08:49:10 -08:00
|
|
|
case FILTER_PROP_ENUM:
|
2024-01-03 10:57:08 -08:00
|
|
|
{
|
2024-01-22 08:49:10 -08:00
|
|
|
guint32 value;
|
|
|
|
|
|
|
|
|
|
if (filter_type == FILTER_PROP_INT)
|
|
|
|
|
value = g_value_get_int (&filter_value);
|
2024-02-08 07:38:05 -08:00
|
|
|
else if (filter_type == FILTER_PROP_UINT)
|
|
|
|
|
value = g_value_get_uint (&filter_value);
|
2024-01-22 08:49:10 -08:00
|
|
|
else
|
|
|
|
|
value = g_value_get_enum (&filter_value);
|
2024-01-03 10:57:08 -08:00
|
|
|
|
|
|
|
|
xcf_write_int32_check_error (info, &value, 1, va_end (args));
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case FILTER_PROP_BOOL:
|
|
|
|
|
{
|
|
|
|
|
gboolean value = g_value_get_boolean (&filter_value);
|
|
|
|
|
|
|
|
|
|
xcf_write_int32_check_error (info, (guint32 *) &value, 1, va_end (args));
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case FILTER_PROP_FLOAT:
|
|
|
|
|
{
|
|
|
|
|
gfloat value = g_value_get_double (&filter_value);
|
|
|
|
|
|
|
|
|
|
xcf_write_float_check_error (info, &value, 1, va_end (args));
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case FILTER_PROP_STRING:
|
|
|
|
|
{
|
|
|
|
|
const gchar *value = g_value_get_string (&filter_value);
|
|
|
|
|
|
|
|
|
|
xcf_write_string_check_error (info, (gchar **) &value, 1, va_end (args));
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
2024-02-13 11:25:18 -08:00
|
|
|
case FILTER_PROP_COLOR:
|
|
|
|
|
{
|
|
|
|
|
GeglColor *color = g_value_get_object (&filter_value);
|
2025-08-14 13:10:20 -07:00
|
|
|
xcf_check_error (xcf_save_color (info, color, error), va_end (args));
|
2024-02-13 11:25:18 -08:00
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
2024-01-22 08:49:10 -08:00
|
|
|
case FILTER_PROP_CONFIG:
|
|
|
|
|
{
|
|
|
|
|
GimpConfig *config = g_value_get_object (&filter_value);
|
|
|
|
|
gchar *value = gimp_config_serialize_to_string (config, NULL);
|
|
|
|
|
|
|
|
|
|
xcf_write_string_check_error (info, (gchar **) &value, 1, va_end (args));
|
|
|
|
|
g_free (value);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
2024-01-03 10:57:08 -08:00
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* go back to the saved position and write the length */
|
|
|
|
|
size = info->cp - base;
|
|
|
|
|
xcf_check_error (xcf_seek_pos (info, pos, error), va_end (args));
|
|
|
|
|
xcf_write_int32_check_error (info, &size, 1, va_end (args));
|
|
|
|
|
|
|
|
|
|
xcf_check_error (xcf_seek_pos (info, base + size, error), va_end (args));
|
|
|
|
|
}
|
2024-03-31 10:30:00 -07:00
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case PROP_FILTER_CLIP:
|
|
|
|
|
{
|
|
|
|
|
guint32 visible = va_arg (args, guint32);
|
|
|
|
|
|
|
|
|
|
size = 4;
|
|
|
|
|
|
|
|
|
|
xcf_write_prop_type_check_error (info, prop_type, va_end (args));
|
|
|
|
|
xcf_write_int32_check_error (info, &size, 1, va_end (args));
|
|
|
|
|
|
|
|
|
|
xcf_write_int32_check_error (info, &visible, 1, va_end (args));
|
|
|
|
|
}
|
|
|
|
|
break;
|
2001-07-03 11:38:56 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
va_end (args);
|
2002-12-19 22:26:34 -08:00
|
|
|
|
|
|
|
|
return TRUE;
|
2001-07-03 11:38:56 -07:00
|
|
|
}
|
|
|
|
|
|
2002-12-19 22:26:34 -08:00
|
|
|
static gboolean
|
2006-11-06 00:14:46 -08:00
|
|
|
xcf_save_layer (XcfInfo *info,
|
|
|
|
|
GimpImage *image,
|
|
|
|
|
GimpLayer *layer,
|
|
|
|
|
GError **error)
|
2001-07-03 11:38:56 -07:00
|
|
|
{
|
2023-06-19 07:54:21 -07:00
|
|
|
goffset saved_pos;
|
|
|
|
|
goffset offset;
|
|
|
|
|
guint32 value;
|
|
|
|
|
const gchar *string;
|
|
|
|
|
GimpContainer *filters;
|
|
|
|
|
GList *filter_list;
|
|
|
|
|
guint32 num_effects = 0;
|
|
|
|
|
GList *list;
|
|
|
|
|
GError *tmp_error = NULL;
|
2002-12-19 22:26:34 -08:00
|
|
|
|
2001-07-03 11:38:56 -07:00
|
|
|
/* check and see if this is the drawable that the floating
|
|
|
|
|
* selection is attached to.
|
|
|
|
|
*/
|
2003-06-24 06:59:36 -07:00
|
|
|
if (GIMP_DRAWABLE (layer) == info->floating_sel_drawable)
|
2001-07-03 11:38:56 -07:00
|
|
|
{
|
|
|
|
|
saved_pos = info->cp;
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_check_error (xcf_seek_pos (info, info->floating_sel_offset, error), ;);
|
|
|
|
|
xcf_write_offset_check_error (info, &saved_pos, 1, ;);
|
|
|
|
|
xcf_check_error (xcf_seek_pos (info, saved_pos, error), ;);
|
2001-07-03 11:38:56 -07:00
|
|
|
}
|
|
|
|
|
|
2023-06-19 07:54:21 -07:00
|
|
|
/* Get filter information */
|
|
|
|
|
filters = gimp_drawable_get_filters (GIMP_DRAWABLE (layer));
|
|
|
|
|
/* Since floating selections are also stored in the filter stack,
|
|
|
|
|
* we need to verify what's in there to get the correct count */
|
|
|
|
|
for (filter_list = GIMP_LIST (filters)->queue->tail; filter_list;
|
|
|
|
|
filter_list = g_list_previous (filter_list))
|
|
|
|
|
{
|
2025-05-31 05:14:02 -07:00
|
|
|
if (GIMP_IS_DRAWABLE_FILTER (filter_list->data) &&
|
|
|
|
|
! gimp_drawable_filter_get_temporary (filter_list->data))
|
2024-05-13 21:05:06 -07:00
|
|
|
{
|
2024-12-26 06:34:40 -08:00
|
|
|
GimpDrawableFilter *filter = filter_list->data;
|
|
|
|
|
GimpDrawableFilterMask *mask = NULL;
|
|
|
|
|
GeglNode *op_node = NULL;
|
2024-05-13 21:05:06 -07:00
|
|
|
|
|
|
|
|
mask = gimp_drawable_filter_get_mask (filter);
|
|
|
|
|
op_node = gimp_drawable_filter_get_operation (filter);
|
|
|
|
|
|
|
|
|
|
/* For now, prevent tool-based filters from being saved */
|
|
|
|
|
if (mask != NULL &&
|
|
|
|
|
op_node != NULL &&
|
|
|
|
|
strcmp (gegl_node_get_operation (op_node), "GraphNode") != 0)
|
|
|
|
|
num_effects++;
|
|
|
|
|
}
|
2023-06-19 07:54:21 -07:00
|
|
|
}
|
|
|
|
|
|
2001-07-03 11:38:56 -07:00
|
|
|
/* write out the width, height and image type information for the layer */
|
2008-11-02 16:09:01 -08:00
|
|
|
value = gimp_item_get_width (GIMP_ITEM (layer));
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_int32_check_error (info, &value, 1, ;);
|
2007-12-21 08:37:01 -08:00
|
|
|
|
2008-11-02 16:09:01 -08:00
|
|
|
value = gimp_item_get_height (GIMP_ITEM (layer));
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_int32_check_error (info, &value, 1, ;);
|
2007-04-12 07:48:04 -07:00
|
|
|
|
2012-04-10 16:12:08 -07:00
|
|
|
value = gimp_babl_format_get_image_type (gimp_drawable_get_format (GIMP_DRAWABLE (layer)));
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_int32_check_error (info, &value, 1, ;);
|
2001-07-03 11:38:56 -07:00
|
|
|
|
|
|
|
|
/* write out the layers name */
|
2009-08-31 13:47:18 -07:00
|
|
|
string = gimp_object_get_name (layer);
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_string_check_error (info, (gchar **) &string, 1, ;);
|
2001-07-03 11:38:56 -07:00
|
|
|
|
|
|
|
|
/* write out the layer properties */
|
2006-03-28 09:08:36 -08:00
|
|
|
xcf_save_layer_props (info, image, layer, error);
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2023-06-19 07:54:21 -07:00
|
|
|
/* write out the layer tile hierarchy and effects */
|
app: a few fixups to MR !958 (initial non-destructive editing).
- Do not leak allocated return value of gegl_node_to_xml_full().
- When merging layer effects, use gimp_drawable_filter_commit(), making
sure we use the exact same code path as when applying layer effects
destructively from the start. This also ensures that filters are
properly removed from the filter stack (unlike
gimp_drawable_merge_filter()), which was the reason why the rendering
was wrong (hence getting the buffer without effects first, then
reapplying it after was only a workaround to an actual bug).
- When removing a filter, verify the object still exists before doing
anything with it. If this was the last reference, we don't want to
call functions on this object. In gimp_drawable_filter_commit(), we
set up a weak pointer. In gimp_drawable_filter_remove_filter() itself,
we save the pointer to the filter's drawable before actual removal (as
we don't want to dereference a freed object later on).
- export_merge_layer_effects() should merge filters recursively through
layer groups.
- clean up the XCF code:
* No need to wrap the effect pointers list into 2 zero offset. Only
have one zero offset to indicate the list end.
* Add the layer effect mask in the effect structure (not in the layer
structure), similar as for layer masks.
* Effect name and icon made as main data in the structure, not as
properties.
2024-01-02 08:12:35 -08:00
|
|
|
offset = info->cp + (2 + num_effects + 1) * info->bytes_per_offset;
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_offset_check_error (info, &offset, 1, ;);
|
2014-10-15 14:15:18 -07:00
|
|
|
|
|
|
|
|
saved_pos = info->cp;
|
2014-10-17 10:12:05 -07:00
|
|
|
|
|
|
|
|
/* write a zero layer mask offset */
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_zero_offset_check_error (info, 1, ;);
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2023-06-19 07:54:21 -07:00
|
|
|
/* write out zero effect and effect mask offset(s) */
|
app: a few fixups to MR !958 (initial non-destructive editing).
- Do not leak allocated return value of gegl_node_to_xml_full().
- When merging layer effects, use gimp_drawable_filter_commit(), making
sure we use the exact same code path as when applying layer effects
destructively from the start. This also ensures that filters are
properly removed from the filter stack (unlike
gimp_drawable_merge_filter()), which was the reason why the rendering
was wrong (hence getting the buffer without effects first, then
reapplying it after was only a workaround to an actual bug).
- When removing a filter, verify the object still exists before doing
anything with it. If this was the last reference, we don't want to
call functions on this object. In gimp_drawable_filter_commit(), we
set up a weak pointer. In gimp_drawable_filter_remove_filter() itself,
we save the pointer to the filter's drawable before actual removal (as
we don't want to dereference a freed object later on).
- export_merge_layer_effects() should merge filters recursively through
layer groups.
- clean up the XCF code:
* No need to wrap the effect pointers list into 2 zero offset. Only
have one zero offset to indicate the list end.
* Add the layer effect mask in the effect structure (not in the layer
structure), similar as for layer masks.
* Effect name and icon made as main data in the structure, not as
properties.
2024-01-02 08:12:35 -08:00
|
|
|
for (gint i = 0; i < num_effects + 1; i++)
|
2023-06-19 07:54:21 -07:00
|
|
|
xcf_write_zero_offset_check_error (info, 1, ;);
|
|
|
|
|
|
2022-10-23 01:38:21 -07:00
|
|
|
xcf_check_error (xcf_save_buffer (info, image,
|
2012-04-02 15:00:15 -07:00
|
|
|
gimp_drawable_get_buffer (GIMP_DRAWABLE (layer)),
|
2023-05-11 06:18:33 -07:00
|
|
|
error), ;);
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2014-10-15 14:15:18 -07:00
|
|
|
offset = info->cp;
|
2001-07-03 11:38:56 -07:00
|
|
|
|
|
|
|
|
/* write out the layer mask */
|
2007-12-21 08:37:01 -08:00
|
|
|
if (gimp_layer_get_mask (layer))
|
2001-07-03 11:38:56 -07:00
|
|
|
{
|
2007-12-21 08:37:01 -08:00
|
|
|
GimpLayerMask *mask = gimp_layer_get_mask (layer);
|
|
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_check_error (xcf_seek_pos (info, saved_pos, error), ;);
|
|
|
|
|
xcf_write_offset_check_error (info, &offset, 1, ;);
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_check_error (xcf_seek_pos (info, offset, error), ;);
|
2007-12-21 08:37:01 -08:00
|
|
|
xcf_check_error (xcf_save_channel (info, image, GIMP_CHANNEL (mask),
|
2023-05-11 06:18:33 -07:00
|
|
|
error), ;);
|
2001-07-03 11:38:56 -07:00
|
|
|
}
|
2002-12-19 22:26:34 -08:00
|
|
|
|
2023-06-19 07:54:21 -07:00
|
|
|
/* write out any layer effects and effect masks */
|
|
|
|
|
if (num_effects > 0)
|
|
|
|
|
{
|
|
|
|
|
saved_pos += info->bytes_per_offset;
|
|
|
|
|
|
|
|
|
|
for (list = GIMP_LIST (filters)->queue->head; list;
|
|
|
|
|
list = g_list_next (list))
|
|
|
|
|
{
|
2025-05-31 05:14:02 -07:00
|
|
|
if (GIMP_IS_DRAWABLE_FILTER (list->data) &&
|
|
|
|
|
! gimp_drawable_filter_get_temporary (list->data))
|
2023-06-19 07:54:21 -07:00
|
|
|
{
|
2024-12-26 06:34:40 -08:00
|
|
|
GimpDrawableFilter *filter = list->data;
|
|
|
|
|
GimpDrawableFilterMask *mask = NULL;
|
|
|
|
|
GeglNode *op_node = NULL;
|
2024-05-13 21:05:06 -07:00
|
|
|
|
|
|
|
|
mask = gimp_drawable_filter_get_mask (filter);
|
|
|
|
|
op_node = gimp_drawable_filter_get_operation (filter);
|
|
|
|
|
|
|
|
|
|
/* For now, prevent tool-based filters from being saved */
|
|
|
|
|
if (mask != NULL &&
|
|
|
|
|
op_node != NULL &&
|
|
|
|
|
strcmp (gegl_node_get_operation (op_node), "GraphNode") != 0)
|
|
|
|
|
{
|
|
|
|
|
offset = info->cp;
|
|
|
|
|
|
|
|
|
|
xcf_check_error (xcf_seek_pos (info, saved_pos, error), ;);
|
|
|
|
|
xcf_write_offset_check_error (info, &offset, 1, ;);
|
|
|
|
|
|
|
|
|
|
saved_pos = info->cp;
|
|
|
|
|
|
|
|
|
|
xcf_check_error (xcf_seek_pos (info, offset, error), ;);
|
|
|
|
|
xcf_check_error (xcf_save_effect (info, image,
|
|
|
|
|
GIMP_FILTER (filter),
|
|
|
|
|
error), ;);
|
|
|
|
|
}
|
2023-06-19 07:54:21 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
g_list_free (list);
|
|
|
|
|
}
|
|
|
|
|
|
2002-12-19 22:26:34 -08:00
|
|
|
return TRUE;
|
2001-07-03 11:38:56 -07:00
|
|
|
}
|
|
|
|
|
|
2002-12-19 22:26:34 -08:00
|
|
|
static gboolean
|
|
|
|
|
xcf_save_channel (XcfInfo *info,
|
2006-04-12 05:49:29 -07:00
|
|
|
GimpImage *image,
|
|
|
|
|
GimpChannel *channel,
|
|
|
|
|
GError **error)
|
2001-07-03 11:38:56 -07:00
|
|
|
{
|
2017-03-23 03:44:41 -07:00
|
|
|
goffset saved_pos;
|
|
|
|
|
goffset offset;
|
2007-12-21 08:37:01 -08:00
|
|
|
guint32 value;
|
|
|
|
|
const gchar *string;
|
|
|
|
|
GError *tmp_error = NULL;
|
2002-12-19 22:26:34 -08:00
|
|
|
|
2001-07-03 11:38:56 -07:00
|
|
|
/* check and see if this is the drawable that the floating
|
|
|
|
|
* selection is attached to.
|
|
|
|
|
*/
|
2001-07-19 08:34:19 -07:00
|
|
|
if (GIMP_DRAWABLE (channel) == info->floating_sel_drawable)
|
2001-07-03 11:38:56 -07:00
|
|
|
{
|
|
|
|
|
saved_pos = info->cp;
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_check_error (xcf_seek_pos (info, info->floating_sel_offset, error), ;);
|
|
|
|
|
xcf_write_offset_check_error (info, &saved_pos, 1, ;);
|
|
|
|
|
xcf_check_error (xcf_seek_pos (info, saved_pos, error), ;);
|
2001-07-03 11:38:56 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* write out the width and height information for the channel */
|
2008-11-02 16:09:01 -08:00
|
|
|
value = gimp_item_get_width (GIMP_ITEM (channel));
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_int32_check_error (info, &value, 1, ;);
|
2007-12-21 08:37:01 -08:00
|
|
|
|
2008-11-02 16:09:01 -08:00
|
|
|
value = gimp_item_get_height (GIMP_ITEM (channel));
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_int32_check_error (info, &value, 1, ;);
|
2001-07-03 11:38:56 -07:00
|
|
|
|
|
|
|
|
/* write out the channels name */
|
2009-08-31 13:47:18 -07:00
|
|
|
string = gimp_object_get_name (channel);
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_string_check_error (info, (gchar **) &string, 1, ;);
|
2001-07-03 11:38:56 -07:00
|
|
|
|
|
|
|
|
/* write out the channel properties */
|
2006-03-28 09:08:36 -08:00
|
|
|
xcf_save_channel_props (info, image, channel, error);
|
2001-07-03 11:38:56 -07:00
|
|
|
|
|
|
|
|
/* write out the channel tile hierarchy */
|
2017-03-23 03:44:41 -07:00
|
|
|
offset = info->cp + info->bytes_per_offset;
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_offset_check_error (info, &offset, 1, ;);
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2022-10-23 01:38:21 -07:00
|
|
|
xcf_check_error (xcf_save_buffer (info, image,
|
2012-04-02 15:00:15 -07:00
|
|
|
gimp_drawable_get_buffer (GIMP_DRAWABLE (channel)),
|
2023-05-11 06:18:33 -07:00
|
|
|
error), ;);
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2002-12-19 22:26:34 -08:00
|
|
|
return TRUE;
|
2001-07-03 11:38:56 -07:00
|
|
|
}
|
|
|
|
|
|
2023-06-19 07:54:21 -07:00
|
|
|
static gboolean
|
|
|
|
|
xcf_save_effect (XcfInfo *info,
|
|
|
|
|
GimpImage *image,
|
|
|
|
|
GimpFilter *filter,
|
|
|
|
|
GError **error)
|
|
|
|
|
{
|
app: a few fixups to MR !958 (initial non-destructive editing).
- Do not leak allocated return value of gegl_node_to_xml_full().
- When merging layer effects, use gimp_drawable_filter_commit(), making
sure we use the exact same code path as when applying layer effects
destructively from the start. This also ensures that filters are
properly removed from the filter stack (unlike
gimp_drawable_merge_filter()), which was the reason why the rendering
was wrong (hence getting the buffer without effects first, then
reapplying it after was only a workaround to an actual bug).
- When removing a filter, verify the object still exists before doing
anything with it. If this was the last reference, we don't want to
call functions on this object. In gimp_drawable_filter_commit(), we
set up a weak pointer. In gimp_drawable_filter_remove_filter() itself,
we save the pointer to the filter's drawable before actual removal (as
we don't want to dereference a freed object later on).
- export_merge_layer_effects() should merge filters recursively through
layer groups.
- clean up the XCF code:
* No need to wrap the effect pointers list into 2 zero offset. Only
have one zero offset to indicate the list end.
* Add the layer effect mask in the effect structure (not in the layer
structure), similar as for layer masks.
* Effect name and icon made as main data in the structure, not as
properties.
2024-01-02 08:12:35 -08:00
|
|
|
gchar *name;
|
|
|
|
|
gchar *icon;
|
2025-01-11 13:28:41 -08:00
|
|
|
gboolean has_custom_name;
|
2023-06-19 07:54:21 -07:00
|
|
|
GimpDrawableFilter *filter_drawable;
|
|
|
|
|
GeglNode *node;
|
2024-01-03 10:57:08 -08:00
|
|
|
gchar *operation;
|
2024-08-19 04:21:08 -07:00
|
|
|
const gchar *op_version;
|
app: a few fixups to MR !958 (initial non-destructive editing).
- Do not leak allocated return value of gegl_node_to_xml_full().
- When merging layer effects, use gimp_drawable_filter_commit(), making
sure we use the exact same code path as when applying layer effects
destructively from the start. This also ensures that filters are
properly removed from the filter stack (unlike
gimp_drawable_merge_filter()), which was the reason why the rendering
was wrong (hence getting the buffer without effects first, then
reapplying it after was only a workaround to an actual bug).
- When removing a filter, verify the object still exists before doing
anything with it. If this was the last reference, we don't want to
call functions on this object. In gimp_drawable_filter_commit(), we
set up a weak pointer. In gimp_drawable_filter_remove_filter() itself,
we save the pointer to the filter's drawable before actual removal (as
we don't want to dereference a freed object later on).
- export_merge_layer_effects() should merge filters recursively through
layer groups.
- clean up the XCF code:
* No need to wrap the effect pointers list into 2 zero offset. Only
have one zero offset to indicate the list end.
* Add the layer effect mask in the effect structure (not in the layer
structure), similar as for layer masks.
* Effect name and icon made as main data in the structure, not as
properties.
2024-01-02 08:12:35 -08:00
|
|
|
GimpChannel *effect_mask;
|
|
|
|
|
goffset offset;
|
2023-06-19 07:54:21 -07:00
|
|
|
GError *tmp_error = NULL;
|
|
|
|
|
|
|
|
|
|
filter_drawable = GIMP_DRAWABLE_FILTER (filter);
|
|
|
|
|
node = gimp_drawable_filter_get_operation (filter_drawable);
|
|
|
|
|
|
|
|
|
|
gegl_node_get (node,
|
|
|
|
|
"operation", &operation,
|
|
|
|
|
NULL);
|
|
|
|
|
|
app: a few fixups to MR !958 (initial non-destructive editing).
- Do not leak allocated return value of gegl_node_to_xml_full().
- When merging layer effects, use gimp_drawable_filter_commit(), making
sure we use the exact same code path as when applying layer effects
destructively from the start. This also ensures that filters are
properly removed from the filter stack (unlike
gimp_drawable_merge_filter()), which was the reason why the rendering
was wrong (hence getting the buffer without effects first, then
reapplying it after was only a workaround to an actual bug).
- When removing a filter, verify the object still exists before doing
anything with it. If this was the last reference, we don't want to
call functions on this object. In gimp_drawable_filter_commit(), we
set up a weak pointer. In gimp_drawable_filter_remove_filter() itself,
we save the pointer to the filter's drawable before actual removal (as
we don't want to dereference a freed object later on).
- export_merge_layer_effects() should merge filters recursively through
layer groups.
- clean up the XCF code:
* No need to wrap the effect pointers list into 2 zero offset. Only
have one zero offset to indicate the list end.
* Add the layer effect mask in the effect structure (not in the layer
structure), similar as for layer masks.
* Effect name and icon made as main data in the structure, not as
properties.
2024-01-02 08:12:35 -08:00
|
|
|
g_object_get (filter,
|
2025-01-11 13:28:41 -08:00
|
|
|
"name", &name,
|
|
|
|
|
"icon-name", &icon,
|
|
|
|
|
"custom-name", &has_custom_name,
|
app: a few fixups to MR !958 (initial non-destructive editing).
- Do not leak allocated return value of gegl_node_to_xml_full().
- When merging layer effects, use gimp_drawable_filter_commit(), making
sure we use the exact same code path as when applying layer effects
destructively from the start. This also ensures that filters are
properly removed from the filter stack (unlike
gimp_drawable_merge_filter()), which was the reason why the rendering
was wrong (hence getting the buffer without effects first, then
reapplying it after was only a workaround to an actual bug).
- When removing a filter, verify the object still exists before doing
anything with it. If this was the last reference, we don't want to
call functions on this object. In gimp_drawable_filter_commit(), we
set up a weak pointer. In gimp_drawable_filter_remove_filter() itself,
we save the pointer to the filter's drawable before actual removal (as
we don't want to dereference a freed object later on).
- export_merge_layer_effects() should merge filters recursively through
layer groups.
- clean up the XCF code:
* No need to wrap the effect pointers list into 2 zero offset. Only
have one zero offset to indicate the list end.
* Add the layer effect mask in the effect structure (not in the layer
structure), similar as for layer masks.
* Effect name and icon made as main data in the structure, not as
properties.
2024-01-02 08:12:35 -08:00
|
|
|
NULL);
|
|
|
|
|
|
|
|
|
|
/* Write out effect name */
|
2025-01-11 13:28:41 -08:00
|
|
|
if (has_custom_name)
|
|
|
|
|
{
|
|
|
|
|
xcf_write_string_check_error (info, (gchar **) &name, 1, ;);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
gchar *empty = NULL;
|
|
|
|
|
|
|
|
|
|
xcf_write_string_check_error (info, (gchar **) &empty, 1, ;);
|
|
|
|
|
}
|
app: a few fixups to MR !958 (initial non-destructive editing).
- Do not leak allocated return value of gegl_node_to_xml_full().
- When merging layer effects, use gimp_drawable_filter_commit(), making
sure we use the exact same code path as when applying layer effects
destructively from the start. This also ensures that filters are
properly removed from the filter stack (unlike
gimp_drawable_merge_filter()), which was the reason why the rendering
was wrong (hence getting the buffer without effects first, then
reapplying it after was only a workaround to an actual bug).
- When removing a filter, verify the object still exists before doing
anything with it. If this was the last reference, we don't want to
call functions on this object. In gimp_drawable_filter_commit(), we
set up a weak pointer. In gimp_drawable_filter_remove_filter() itself,
we save the pointer to the filter's drawable before actual removal (as
we don't want to dereference a freed object later on).
- export_merge_layer_effects() should merge filters recursively through
layer groups.
- clean up the XCF code:
* No need to wrap the effect pointers list into 2 zero offset. Only
have one zero offset to indicate the list end.
* Add the layer effect mask in the effect structure (not in the layer
structure), similar as for layer masks.
* Effect name and icon made as main data in the structure, not as
properties.
2024-01-02 08:12:35 -08:00
|
|
|
g_free (name);
|
|
|
|
|
|
|
|
|
|
/* Write out effect icon */
|
|
|
|
|
xcf_write_string_check_error (info, (gchar **) &icon, 1, ;);
|
|
|
|
|
g_free (icon);
|
|
|
|
|
|
2024-01-03 10:57:08 -08:00
|
|
|
/* Write out GEGL operation name */
|
|
|
|
|
xcf_write_string_check_error (info, (gchar **) &operation, 1, ;);
|
2023-06-19 07:54:21 -07:00
|
|
|
|
2024-08-19 04:21:08 -07:00
|
|
|
if (info->file_version >= 22)
|
|
|
|
|
{
|
|
|
|
|
/* Write out GEGL operation version */
|
|
|
|
|
op_version = gegl_operation_get_op_version (operation);
|
|
|
|
|
xcf_write_string_check_error (info, (gchar **) &op_version, 1, ;);
|
|
|
|
|
}
|
2024-09-25 13:09:08 -07:00
|
|
|
g_free (operation);
|
2024-08-19 04:21:08 -07:00
|
|
|
|
2023-06-19 07:54:21 -07:00
|
|
|
/* write out the effect properties */
|
|
|
|
|
xcf_save_effect_props (info, image, filter, error);
|
|
|
|
|
|
app: a few fixups to MR !958 (initial non-destructive editing).
- Do not leak allocated return value of gegl_node_to_xml_full().
- When merging layer effects, use gimp_drawable_filter_commit(), making
sure we use the exact same code path as when applying layer effects
destructively from the start. This also ensures that filters are
properly removed from the filter stack (unlike
gimp_drawable_merge_filter()), which was the reason why the rendering
was wrong (hence getting the buffer without effects first, then
reapplying it after was only a workaround to an actual bug).
- When removing a filter, verify the object still exists before doing
anything with it. If this was the last reference, we don't want to
call functions on this object. In gimp_drawable_filter_commit(), we
set up a weak pointer. In gimp_drawable_filter_remove_filter() itself,
we save the pointer to the filter's drawable before actual removal (as
we don't want to dereference a freed object later on).
- export_merge_layer_effects() should merge filters recursively through
layer groups.
- clean up the XCF code:
* No need to wrap the effect pointers list into 2 zero offset. Only
have one zero offset to indicate the list end.
* Add the layer effect mask in the effect structure (not in the layer
structure), similar as for layer masks.
* Effect name and icon made as main data in the structure, not as
properties.
2024-01-02 08:12:35 -08:00
|
|
|
/* write a zero effect mask offset */
|
|
|
|
|
offset = info->cp + info->bytes_per_offset;
|
|
|
|
|
xcf_write_offset_check_error (info, &offset, 1, ;);
|
|
|
|
|
|
2024-12-26 06:34:40 -08:00
|
|
|
effect_mask = GIMP_CHANNEL (gimp_drawable_filter_get_mask (filter_drawable));
|
app: a few fixups to MR !958 (initial non-destructive editing).
- Do not leak allocated return value of gegl_node_to_xml_full().
- When merging layer effects, use gimp_drawable_filter_commit(), making
sure we use the exact same code path as when applying layer effects
destructively from the start. This also ensures that filters are
properly removed from the filter stack (unlike
gimp_drawable_merge_filter()), which was the reason why the rendering
was wrong (hence getting the buffer without effects first, then
reapplying it after was only a workaround to an actual bug).
- When removing a filter, verify the object still exists before doing
anything with it. If this was the last reference, we don't want to
call functions on this object. In gimp_drawable_filter_commit(), we
set up a weak pointer. In gimp_drawable_filter_remove_filter() itself,
we save the pointer to the filter's drawable before actual removal (as
we don't want to dereference a freed object later on).
- export_merge_layer_effects() should merge filters recursively through
layer groups.
- clean up the XCF code:
* No need to wrap the effect pointers list into 2 zero offset. Only
have one zero offset to indicate the list end.
* Add the layer effect mask in the effect structure (not in the layer
structure), similar as for layer masks.
* Effect name and icon made as main data in the structure, not as
properties.
2024-01-02 08:12:35 -08:00
|
|
|
xcf_check_error (xcf_save_channel (info, image, effect_mask,
|
|
|
|
|
error), ;);
|
|
|
|
|
|
2023-06-19 07:54:21 -07:00
|
|
|
return TRUE;
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-14 13:10:20 -07:00
|
|
|
static gboolean
|
|
|
|
|
xcf_save_color (XcfInfo *info,
|
|
|
|
|
GeglColor *color,
|
|
|
|
|
GError **error)
|
|
|
|
|
{
|
|
|
|
|
GError *tmp_error = NULL;
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
if (babl_format_is_palette (format))
|
|
|
|
|
{
|
|
|
|
|
guint8 pixel[40];
|
|
|
|
|
GeglColor *palette_color;
|
|
|
|
|
|
|
|
|
|
/* 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.
|
|
|
|
|
*/
|
|
|
|
|
palette_color = gegl_color_duplicate (color);
|
|
|
|
|
|
|
|
|
|
format = babl_format_with_space ("R'G'B'A u8", format);
|
|
|
|
|
gegl_color_get_pixel (palette_color, format, pixel);
|
|
|
|
|
gegl_color_set_pixel (color, format, pixel);
|
|
|
|
|
|
|
|
|
|
g_object_unref (palette_color);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
encoding = babl_format_get_encoding (format);
|
|
|
|
|
xcf_write_string_check_error (info, (gchar **) &encoding, 1, ;);
|
|
|
|
|
|
|
|
|
|
bytes = gegl_color_get_bytes (color, format);
|
|
|
|
|
data = (guint8 *) g_bytes_get_data (bytes, &data_length);
|
|
|
|
|
|
|
|
|
|
xcf_write_int32_check_error (info, (guint32 *) &data_length, 1, ;);
|
|
|
|
|
xcf_write_int8_check_error (info, (const guint8 *) data, data_length, ;);
|
|
|
|
|
g_bytes_unref (bytes);
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
xcf_write_int32_check_error (info, (guint32 *) &profile_length, 1, ;);
|
|
|
|
|
|
|
|
|
|
if (profile_data)
|
|
|
|
|
xcf_write_int8_check_error (info, profile_data, profile_length, ;);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
xcf_write_int32_check_error (info, (guint32 *) &profile_length, 1, ;);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
guint32 uint_val = 0;
|
|
|
|
|
|
2025-08-16 14:43:39 -07:00
|
|
|
xcf_write_int32_check_error (info, &uint_val, 1, ;);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static gboolean
|
|
|
|
|
xcf_save_data_id (XcfInfo *info,
|
|
|
|
|
GimpData *data,
|
|
|
|
|
GError **error)
|
|
|
|
|
{
|
|
|
|
|
/* XXX Fonts may want to be special-cased because there may be more
|
|
|
|
|
* than just the standard GimpData ID which would make reasonable
|
|
|
|
|
* heuristic to retrieve a font. See gimp_font_serialize().
|
|
|
|
|
*
|
|
|
|
|
* TODO: in future updates of XCF, embedding the full data itself
|
|
|
|
|
* rather than just point to it may be a worthy option too.
|
|
|
|
|
*/
|
|
|
|
|
gchar *name;
|
|
|
|
|
gchar *collection_id;
|
|
|
|
|
gboolean is_internal;
|
|
|
|
|
guint32 uint_val;
|
|
|
|
|
GError *tmp_error = NULL;
|
|
|
|
|
|
|
|
|
|
if (data)
|
|
|
|
|
{
|
|
|
|
|
gimp_data_get_identifiers (data, &name, &collection_id, &is_internal);
|
|
|
|
|
|
|
|
|
|
xcf_write_string_check_error (info, (gchar **) &name, 1, ;);
|
|
|
|
|
xcf_write_string_check_error (info, (gchar **) &collection_id, 1, ;);
|
|
|
|
|
uint_val = is_internal;
|
|
|
|
|
xcf_write_int32_check_error (info, (guint32 *) &uint_val, 1, ;);
|
|
|
|
|
|
|
|
|
|
g_free (name);
|
|
|
|
|
g_free (collection_id);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
guint32 uint_val = 0;
|
|
|
|
|
|
|
|
|
|
xcf_write_int32_check_error (info, &uint_val, 1, ;);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static gboolean
|
|
|
|
|
xcf_save_fill_options (XcfInfo *info,
|
|
|
|
|
GimpFillOptions *options,
|
|
|
|
|
GError **error)
|
|
|
|
|
{
|
|
|
|
|
GeglColor *color;
|
|
|
|
|
GimpPattern *pattern;
|
|
|
|
|
guint32 uint_val;
|
|
|
|
|
GError *tmp_error = NULL;
|
|
|
|
|
|
|
|
|
|
uint_val = (guint32) gimp_fill_options_get_custom_style (options);
|
|
|
|
|
xcf_write_int32_check_error (info, (guint32 *) &uint_val, 1, ;);
|
|
|
|
|
|
|
|
|
|
uint_val = (guint32) gimp_fill_options_get_antialias (options);
|
|
|
|
|
xcf_write_int32_check_error (info, (guint32 *) &uint_val, 1, ;);
|
|
|
|
|
|
|
|
|
|
color = gimp_context_get_foreground (GIMP_CONTEXT (options));
|
|
|
|
|
xcf_check_error (xcf_save_color (info, color, error), ;);
|
|
|
|
|
|
|
|
|
|
pattern = gimp_context_get_pattern (GIMP_CONTEXT (options));
|
|
|
|
|
xcf_check_error (xcf_save_data_id (info, GIMP_DATA (pattern), error), ;);
|
|
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static gboolean
|
|
|
|
|
xcf_save_stroke_options (XcfInfo *info,
|
|
|
|
|
GimpStrokeOptions *options,
|
|
|
|
|
GError **error)
|
|
|
|
|
{
|
|
|
|
|
GeglColor *color;
|
|
|
|
|
GimpPattern *pattern;
|
|
|
|
|
guint32 uint_val;
|
|
|
|
|
gfloat float_val;
|
|
|
|
|
GError *tmp_error = NULL;
|
|
|
|
|
GArray *dash_info;
|
|
|
|
|
gdouble *dashes;
|
|
|
|
|
gsize n_dashes = 0;
|
|
|
|
|
|
|
|
|
|
uint_val = (guint32) gimp_fill_options_get_custom_style (GIMP_FILL_OPTIONS (options));
|
|
|
|
|
xcf_write_int32_check_error (info, (guint32 *) &uint_val, 1, ;);
|
|
|
|
|
|
|
|
|
|
uint_val = (guint32) gimp_fill_options_get_antialias (GIMP_FILL_OPTIONS (options));
|
|
|
|
|
xcf_write_int32_check_error (info, (guint32 *) &uint_val, 1, ;);
|
|
|
|
|
|
|
|
|
|
color = gimp_context_get_foreground (GIMP_CONTEXT (options));
|
|
|
|
|
xcf_check_error (xcf_save_color (info, color, error), ;);
|
|
|
|
|
|
|
|
|
|
pattern = gimp_context_get_pattern (GIMP_CONTEXT (options));
|
|
|
|
|
xcf_check_error (xcf_save_data_id (info, GIMP_DATA (pattern), error), ;);
|
|
|
|
|
|
|
|
|
|
float_val = (gfloat) gimp_stroke_options_get_width (options);
|
|
|
|
|
xcf_write_float_check_error (info, (gfloat *) &float_val, 1, ;);
|
|
|
|
|
|
|
|
|
|
uint_val = (guint32) gimp_stroke_options_get_cap_style (options);
|
|
|
|
|
xcf_write_int32_check_error (info, (guint32 *) &uint_val, 1, ;);
|
|
|
|
|
|
|
|
|
|
uint_val = (guint32) gimp_stroke_options_get_join_style (options);
|
|
|
|
|
xcf_write_int32_check_error (info, (guint32 *) &uint_val, 1, ;);
|
|
|
|
|
|
|
|
|
|
float_val = (gfloat) gimp_stroke_options_get_miter_limit (options);
|
|
|
|
|
xcf_write_float_check_error (info, (gfloat *) &float_val, 1, ;);
|
|
|
|
|
|
|
|
|
|
dash_info = gimp_stroke_options_get_dash_info (options);
|
|
|
|
|
dashes = gimp_dash_pattern_to_double_array (dash_info, &n_dashes);
|
|
|
|
|
uint_val = (guint32) n_dashes;
|
|
|
|
|
xcf_write_int32_check_error (info, (guint32 *) &uint_val, 1, ;);
|
|
|
|
|
|
|
|
|
|
for (gint i = 0; i < n_dashes; i++)
|
|
|
|
|
{
|
|
|
|
|
float_val = (gfloat) dashes[i];
|
|
|
|
|
xcf_write_float_check_error (info, (gfloat *) &float_val, 1, ;);
|
2025-08-14 13:10:20 -07:00
|
|
|
}
|
2025-08-16 14:43:39 -07:00
|
|
|
g_free (dashes);
|
2025-08-14 13:10:20 -07:00
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
|
|
|
|
|
2001-07-03 11:38:56 -07:00
|
|
|
static gint
|
|
|
|
|
xcf_calc_levels (gint size,
|
2006-04-12 05:49:29 -07:00
|
|
|
gint tile_size)
|
2001-07-03 11:38:56 -07:00
|
|
|
{
|
2017-03-23 03:44:41 -07:00
|
|
|
gint levels;
|
2001-07-03 11:38:56 -07:00
|
|
|
|
|
|
|
|
levels = 1;
|
|
|
|
|
while (size > tile_size)
|
|
|
|
|
{
|
|
|
|
|
size /= 2;
|
|
|
|
|
levels += 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return levels;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2002-12-19 22:26:34 -08:00
|
|
|
static gboolean
|
2012-04-02 15:00:15 -07:00
|
|
|
xcf_save_buffer (XcfInfo *info,
|
2022-10-23 01:38:21 -07:00
|
|
|
GimpImage *image,
|
2012-04-02 15:00:15 -07:00
|
|
|
GeglBuffer *buffer,
|
|
|
|
|
GError **error)
|
2001-07-03 11:38:56 -07:00
|
|
|
{
|
2012-04-02 15:00:15 -07:00
|
|
|
const Babl *format;
|
2017-03-23 03:44:41 -07:00
|
|
|
goffset saved_pos;
|
|
|
|
|
goffset offset;
|
2012-04-02 15:00:15 -07:00
|
|
|
guint32 width;
|
|
|
|
|
guint32 height;
|
|
|
|
|
guint32 bpp;
|
|
|
|
|
gint i;
|
|
|
|
|
gint nlevels;
|
|
|
|
|
gint tmp1, tmp2;
|
|
|
|
|
GError *tmp_error = NULL;
|
|
|
|
|
|
|
|
|
|
format = gegl_buffer_get_format (buffer);
|
|
|
|
|
|
|
|
|
|
width = gegl_buffer_get_width (buffer);
|
|
|
|
|
height = gegl_buffer_get_height (buffer);
|
|
|
|
|
bpp = babl_format_get_bytes_per_pixel (format);
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_int32_check_error (info, (guint32 *) &width, 1, ;);
|
|
|
|
|
xcf_write_int32_check_error (info, (guint32 *) &height, 1, ;);
|
|
|
|
|
xcf_write_int32_check_error (info, (guint32 *) &bpp, 1, ;);
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2012-04-02 15:00:15 -07:00
|
|
|
tmp1 = xcf_calc_levels (width, XCF_TILE_WIDTH);
|
|
|
|
|
tmp2 = xcf_calc_levels (height, XCF_TILE_HEIGHT);
|
2001-07-03 11:38:56 -07:00
|
|
|
nlevels = MAX (tmp1, tmp2);
|
|
|
|
|
|
2014-10-17 10:12:05 -07:00
|
|
|
/* 'saved_pos' is the next slot in the offset table */
|
|
|
|
|
saved_pos = info->cp;
|
|
|
|
|
|
|
|
|
|
/* write an empty offset table */
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_zero_offset_check_error (info, nlevels + 1, ;);
|
2014-10-17 10:12:05 -07:00
|
|
|
|
|
|
|
|
/* 'offset' is where we will write the next level */
|
|
|
|
|
offset = info->cp;
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2003-09-10 03:40:57 -07:00
|
|
|
for (i = 0; i < nlevels; i++)
|
2001-07-03 11:38:56 -07:00
|
|
|
{
|
2014-10-17 10:12:05 -07:00
|
|
|
/* seek back to the next slot in the offset table and write the
|
|
|
|
|
* offset of the level
|
|
|
|
|
*/
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_check_error (xcf_seek_pos (info, saved_pos, error), ;);
|
|
|
|
|
xcf_write_offset_check_error (info, &offset, 1, ;);
|
2014-10-15 14:15:18 -07:00
|
|
|
|
2014-10-17 10:12:05 -07:00
|
|
|
/* remember the next slot in the offset table */
|
2014-10-15 14:15:18 -07:00
|
|
|
saved_pos = info->cp;
|
|
|
|
|
|
|
|
|
|
/* seek to the level offset and save the level */
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_check_error (xcf_seek_pos (info, offset, error), ;);
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2003-09-10 03:40:57 -07:00
|
|
|
if (i == 0)
|
2006-04-12 05:49:29 -07:00
|
|
|
{
|
|
|
|
|
/* write out the level. */
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_check_error (xcf_save_level (info, image, buffer, error), ;);
|
2006-04-12 05:49:29 -07:00
|
|
|
}
|
2003-09-10 03:40:57 -07:00
|
|
|
else
|
2006-04-12 05:49:29 -07:00
|
|
|
{
|
|
|
|
|
/* fake an empty level */
|
|
|
|
|
tmp1 = 0;
|
|
|
|
|
width /= 2;
|
|
|
|
|
height /= 2;
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_int32_check_error (info, (guint32 *) &width, 1, ;);
|
|
|
|
|
xcf_write_int32_check_error (info, (guint32 *) &height, 1, ;);
|
2018-12-04 08:57:40 -08:00
|
|
|
|
|
|
|
|
/* NOTE: this should be an offset, not an int32! however...
|
|
|
|
|
* since there are already 64-bit-offsets XCFs out there in
|
|
|
|
|
* which this field is 32-bit, and since it's not actually
|
|
|
|
|
* being used, we're going to keep this field 32-bit for the
|
|
|
|
|
* dummy levels, to remain consistent. if we ever make use
|
|
|
|
|
* of levels above the first, we should turn this field into
|
|
|
|
|
* an offset, and bump the xcf version.
|
|
|
|
|
*/
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_int32_check_error (info, (guint32 *) &tmp1, 1, ;);
|
2006-04-12 05:49:29 -07:00
|
|
|
}
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2014-10-15 14:15:18 -07:00
|
|
|
/* the next level's offset if after the level we just wrote */
|
|
|
|
|
offset = info->cp;
|
2001-07-03 11:38:56 -07:00
|
|
|
}
|
|
|
|
|
|
2014-10-17 10:12:05 -07:00
|
|
|
/* there is already a '0' at the end of the offset table to indicate
|
|
|
|
|
* the end of the level offsets
|
|
|
|
|
*/
|
2002-12-19 22:26:34 -08:00
|
|
|
|
|
|
|
|
return TRUE;
|
2001-07-03 11:38:56 -07:00
|
|
|
}
|
|
|
|
|
|
2002-12-19 22:26:34 -08:00
|
|
|
static gboolean
|
2012-04-02 15:00:15 -07:00
|
|
|
xcf_save_level (XcfInfo *info,
|
2022-10-23 01:38:21 -07:00
|
|
|
GimpImage *image,
|
2012-04-02 15:00:15 -07:00
|
|
|
GeglBuffer *buffer,
|
|
|
|
|
GError **error)
|
2001-07-03 11:38:56 -07:00
|
|
|
{
|
2012-04-02 15:00:15 -07:00
|
|
|
const Babl *format;
|
2017-03-23 03:44:41 -07:00
|
|
|
goffset *offset_table;
|
|
|
|
|
goffset *next_offset;
|
|
|
|
|
goffset saved_pos;
|
|
|
|
|
goffset offset;
|
app: limit allowable tile data size in XCFs
When loading tiles from an XCF, reject tiles whose on-disk size is
greater than 1.5 times the size of an uncompressed tile -- a limit
that is already present for the last tile in the buffer. This
should allow for the possibility of negative compression, while
restricting placing a realistic limit.
Currently, no limit is placed on the on-disk tile data size. When
loading RLE- and zlib-compressed tiles, a buffer large enough to
hold the entire on-disk tile data, up to 2GB, is allocated on the
stack, and the data is read into it. If the file is smaller than
the reported tile data size, the area of the buffer past the end
of the file is not touched. This allows a malicious XCF to write
up to 2GB of arbitrary data, at an arbitrary offset, up to 2GB,
below the stack.
Note that a similar issue had existed for earlier versions of GIMP
(see commit d7a9e6079d3e65baa516f302eb285fb2bbd97c2f), however,
since prior to 2.9 the tile data buffer was allocated on the heap,
the potential risk is far smaller.
2017-07-09 14:20:30 -07:00
|
|
|
goffset max_data_length;
|
2012-04-02 15:00:15 -07:00
|
|
|
guint32 width;
|
|
|
|
|
guint32 height;
|
|
|
|
|
gint bpp;
|
|
|
|
|
gint n_tile_rows;
|
|
|
|
|
gint n_tile_cols;
|
|
|
|
|
guint ntiles;
|
2022-10-23 01:38:21 -07:00
|
|
|
gint num_processors;
|
2021-06-19 04:41:54 -07:00
|
|
|
gint i, j, k;
|
2012-04-02 15:00:15 -07:00
|
|
|
GError *tmp_error = NULL;
|
|
|
|
|
|
2022-10-23 01:38:21 -07:00
|
|
|
num_processors = GIMP_GEGL_CONFIG (image->gimp->config)->num_processors;
|
|
|
|
|
|
2012-04-02 15:00:15 -07:00
|
|
|
format = gegl_buffer_get_format (buffer);
|
|
|
|
|
|
|
|
|
|
width = gegl_buffer_get_width (buffer);
|
|
|
|
|
height = gegl_buffer_get_height (buffer);
|
|
|
|
|
bpp = babl_format_get_bytes_per_pixel (format);
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_int32_check_error (info, (guint32 *) &width, 1, ;);
|
|
|
|
|
xcf_write_int32_check_error (info, (guint32 *) &height, 1, ;);
|
2001-07-03 11:38:56 -07:00
|
|
|
|
|
|
|
|
saved_pos = info->cp;
|
|
|
|
|
|
app: limit allowable tile data size in XCFs
When loading tiles from an XCF, reject tiles whose on-disk size is
greater than 1.5 times the size of an uncompressed tile -- a limit
that is already present for the last tile in the buffer. This
should allow for the possibility of negative compression, while
restricting placing a realistic limit.
Currently, no limit is placed on the on-disk tile data size. When
loading RLE- and zlib-compressed tiles, a buffer large enough to
hold the entire on-disk tile data, up to 2GB, is allocated on the
stack, and the data is read into it. If the file is smaller than
the reported tile data size, the area of the buffer past the end
of the file is not touched. This allows a malicious XCF to write
up to 2GB of arbitrary data, at an arbitrary offset, up to 2GB,
below the stack.
Note that a similar issue had existed for earlier versions of GIMP
(see commit d7a9e6079d3e65baa516f302eb285fb2bbd97c2f), however,
since prior to 2.9 the tile data buffer was allocated on the heap,
the potential risk is far smaller.
2017-07-09 14:20:30 -07:00
|
|
|
/* maximal allowable size of on-disk tile data. make it somewhat bigger than
|
|
|
|
|
* the uncompressed tile size, to allow for the possibility of negative
|
|
|
|
|
* compression. xcf_load_level() enforces this limit.
|
|
|
|
|
*/
|
|
|
|
|
max_data_length = XCF_TILE_WIDTH * XCF_TILE_HEIGHT * bpp *
|
|
|
|
|
XCF_TILE_MAX_DATA_LENGTH_FACTOR /* = 1.5, currently */;
|
|
|
|
|
|
2012-04-02 15:00:15 -07:00
|
|
|
n_tile_rows = gimp_gegl_buffer_get_n_tile_rows (buffer, XCF_TILE_HEIGHT);
|
|
|
|
|
n_tile_cols = gimp_gegl_buffer_get_n_tile_cols (buffer, XCF_TILE_WIDTH);
|
|
|
|
|
|
|
|
|
|
ntiles = n_tile_rows * n_tile_cols;
|
2014-10-15 14:15:18 -07:00
|
|
|
|
2015-04-06 11:01:39 -07:00
|
|
|
/* allocate an offset table so we don't have to seek back after each
|
|
|
|
|
* tile, see bug #686862. allocate ntiles + 1 slots because a zero
|
|
|
|
|
* offset indicates the offset table's end.
|
2021-01-20 11:56:45 -08:00
|
|
|
* Do not use g_alloca since it may cause Stack Overflow on
|
|
|
|
|
* large images, see issue #6138.
|
2015-04-06 11:01:39 -07:00
|
|
|
*/
|
2021-01-20 11:56:45 -08:00
|
|
|
offset_table = g_malloc0 ((ntiles + 1) * sizeof (goffset));
|
2015-04-06 11:01:39 -07:00
|
|
|
next_offset = offset_table;
|
|
|
|
|
|
|
|
|
|
/* 'saved_pos' is the offset of the tile offset table */
|
2014-10-17 10:12:05 -07:00
|
|
|
saved_pos = info->cp;
|
|
|
|
|
|
|
|
|
|
/* write an empty offset table */
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_zero_offset_check_error (info, ntiles + 1, ;);
|
2014-10-17 10:12:05 -07:00
|
|
|
|
|
|
|
|
/* 'offset' is where we will write the next tile */
|
|
|
|
|
offset = info->cp;
|
2012-04-02 15:00:15 -07:00
|
|
|
|
2022-10-24 14:39:06 -07:00
|
|
|
if (info->compression == COMPRESS_RLE ||
|
|
|
|
|
info->compression == COMPRESS_ZLIB)
|
2001-07-03 11:38:56 -07:00
|
|
|
{
|
2021-06-19 04:41:54 -07:00
|
|
|
/* parallel implementation */
|
2022-10-24 08:04:34 -07:00
|
|
|
XcfJobData *job_data;
|
2022-10-24 14:39:06 -07:00
|
|
|
guchar *switch_out_data;
|
|
|
|
|
gint out_data_len[XCF_TILE_SAVE_BATCH_SIZE];
|
2022-10-24 08:04:34 -07:00
|
|
|
|
|
|
|
|
GThreadPool *pool;
|
|
|
|
|
GAsyncQueue *queue;
|
|
|
|
|
gint num_tasks = num_processors * 2;
|
|
|
|
|
gint tile_size = XCF_TILE_WIDTH * XCF_TILE_HEIGHT * bpp;
|
2022-10-24 14:39:06 -07:00
|
|
|
gint out_data_max_size;
|
2022-10-24 08:04:34 -07:00
|
|
|
gint next_tile = 0;
|
|
|
|
|
|
2022-10-24 14:39:06 -07:00
|
|
|
out_data_max_size = tile_size * XCF_TILE_MAX_DATA_LENGTH_FACTOR;
|
|
|
|
|
/* Prepare an additional out_data to quickly switch. */
|
|
|
|
|
switch_out_data = g_malloc (out_data_max_size * XCF_TILE_SAVE_BATCH_SIZE);
|
2022-10-24 08:04:34 -07:00
|
|
|
|
|
|
|
|
/* The free function passed to the queue and thread pool will likely never
|
|
|
|
|
* be used. It would mean the thread pool is unfinidhed or the result
|
|
|
|
|
* queue still has data which would mean we had to interrupt the save,
|
|
|
|
|
* i.e. there is a bug in our code.
|
|
|
|
|
*/
|
|
|
|
|
queue = g_async_queue_new_full ((GDestroyNotify) xcf_save_free_job_data);
|
2022-10-24 14:39:06 -07:00
|
|
|
pool = g_thread_pool_new_full ((GFunc) xcf_save_tile_parallel,
|
2022-10-24 08:04:34 -07:00
|
|
|
queue,
|
|
|
|
|
(GDestroyNotify) xcf_save_free_job_data,
|
|
|
|
|
num_processors, TRUE, NULL);
|
|
|
|
|
|
2021-06-19 04:41:54 -07:00
|
|
|
i = 0;
|
2022-10-24 08:04:34 -07:00
|
|
|
/* We push more tasks than there are threads, ensuring threads always have
|
|
|
|
|
* something to do!
|
|
|
|
|
*/
|
|
|
|
|
for (j = 0; j < num_tasks && i < ntiles; j++)
|
|
|
|
|
{
|
|
|
|
|
job_data = g_malloc (sizeof (XcfJobData ));
|
|
|
|
|
job_data->buffer = buffer;
|
|
|
|
|
job_data->file_version = info->file_version;
|
2022-10-24 14:39:06 -07:00
|
|
|
job_data->max_out_data_len = out_data_max_size;
|
|
|
|
|
job_data->compress = (info->compression == COMPRESS_RLE) ?
|
|
|
|
|
xcf_save_tile_rle : xcf_save_tile_zlib;
|
2022-10-24 08:04:34 -07:00
|
|
|
job_data->tile_data = g_malloc (tile_size);
|
2022-10-24 14:39:06 -07:00
|
|
|
job_data->out_data = g_malloc (out_data_max_size * XCF_TILE_SAVE_BATCH_SIZE);
|
2022-10-24 08:04:34 -07:00
|
|
|
|
|
|
|
|
job_data->tile = i;
|
|
|
|
|
job_data->batch_size = MIN (XCF_TILE_SAVE_BATCH_SIZE, ntiles - i);
|
|
|
|
|
i += job_data->batch_size;
|
|
|
|
|
|
|
|
|
|
g_thread_pool_push (pool, job_data, NULL);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Continue pushing tasks and writing tasks as long as we have tiles to
|
|
|
|
|
* process.
|
|
|
|
|
*/
|
2021-06-19 04:41:54 -07:00
|
|
|
while (i < ntiles)
|
2012-04-02 15:00:15 -07:00
|
|
|
{
|
2022-10-24 08:04:34 -07:00
|
|
|
while ((job_data = g_async_queue_pop (queue)))
|
2021-06-19 04:41:54 -07:00
|
|
|
{
|
2022-10-24 08:04:34 -07:00
|
|
|
if (next_tile == job_data->tile)
|
2021-06-19 04:41:54 -07:00
|
|
|
{
|
2022-10-24 14:39:06 -07:00
|
|
|
guchar *tmp_out_data;
|
2022-10-24 08:04:34 -07:00
|
|
|
gint batch_size;
|
|
|
|
|
|
2022-10-24 14:39:06 -07:00
|
|
|
tmp_out_data = job_data->out_data;
|
|
|
|
|
job_data->out_data = switch_out_data;
|
|
|
|
|
switch_out_data = tmp_out_data;
|
2022-10-24 08:04:34 -07:00
|
|
|
|
|
|
|
|
batch_size = job_data->batch_size;
|
|
|
|
|
|
|
|
|
|
for (k = 0; k < batch_size; k++)
|
2022-10-24 14:39:06 -07:00
|
|
|
out_data_len[k] = job_data->out_data_len[k];
|
2022-10-24 08:04:34 -07:00
|
|
|
|
|
|
|
|
/* First immediately push a new task for the thread pool,
|
|
|
|
|
* ensuring it always has work to do.
|
|
|
|
|
*/
|
|
|
|
|
job_data->tile = i;
|
|
|
|
|
job_data->batch_size = MIN (XCF_TILE_SAVE_BATCH_SIZE, ntiles - i);
|
|
|
|
|
i += job_data->batch_size;
|
|
|
|
|
|
|
|
|
|
g_thread_pool_push (pool, job_data, NULL);
|
|
|
|
|
|
|
|
|
|
/* Now write the data. */
|
|
|
|
|
for (k = 0; k < batch_size; k++)
|
|
|
|
|
{
|
|
|
|
|
*next_offset++ = offset;
|
|
|
|
|
xcf_write_int8_check_error (info,
|
2022-10-24 14:39:06 -07:00
|
|
|
switch_out_data + out_data_max_size * k,
|
2023-05-11 06:18:33 -07:00
|
|
|
out_data_len[k], ;);
|
2022-10-24 08:04:34 -07:00
|
|
|
if (info->cp < offset || info->cp - offset > max_data_length)
|
|
|
|
|
{
|
|
|
|
|
g_message ("xcf: invalid tile data length: %" G_GOFFSET_FORMAT,
|
|
|
|
|
info->cp - offset);
|
|
|
|
|
g_thread_pool_free (pool, TRUE, TRUE);
|
|
|
|
|
g_async_queue_unref (queue);
|
|
|
|
|
g_free (offset_table);
|
|
|
|
|
return FALSE;
|
|
|
|
|
}
|
|
|
|
|
offset = info->cp;
|
|
|
|
|
}
|
|
|
|
|
next_tile += batch_size;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
g_async_queue_push_sorted (queue, job_data,
|
|
|
|
|
(GCompareDataFunc) xcf_save_sort_job_data,
|
|
|
|
|
NULL);
|
2021-06-19 04:41:54 -07:00
|
|
|
}
|
|
|
|
|
}
|
2022-10-24 08:04:34 -07:00
|
|
|
}
|
2022-10-24 14:39:06 -07:00
|
|
|
g_free (switch_out_data);
|
2022-10-24 08:04:34 -07:00
|
|
|
|
|
|
|
|
/* Finally wait for all remaining tasks to write. */
|
|
|
|
|
while ((job_data = g_async_queue_pop (queue)))
|
|
|
|
|
{
|
|
|
|
|
if (next_tile == job_data->tile)
|
2021-06-19 04:41:54 -07:00
|
|
|
{
|
2022-10-24 08:04:34 -07:00
|
|
|
gboolean done = FALSE;
|
|
|
|
|
|
|
|
|
|
for (k = 0; k < job_data->batch_size; k++)
|
2021-06-19 04:41:54 -07:00
|
|
|
{
|
|
|
|
|
*next_offset++ = offset;
|
|
|
|
|
xcf_write_int8_check_error (info,
|
2022-10-24 14:39:06 -07:00
|
|
|
job_data->out_data + out_data_max_size * k,
|
2023-05-11 06:18:33 -07:00
|
|
|
job_data->out_data_len[k], ;);
|
2021-06-19 04:41:54 -07:00
|
|
|
if (info->cp < offset || info->cp - offset > max_data_length)
|
|
|
|
|
{
|
2022-10-23 01:38:21 -07:00
|
|
|
g_message ("xcf: invalid tile data length: %" G_GOFFSET_FORMAT,
|
|
|
|
|
info->cp - offset);
|
2022-10-24 08:04:34 -07:00
|
|
|
g_thread_pool_free (pool, TRUE, TRUE);
|
|
|
|
|
g_async_queue_unref (queue);
|
2022-10-23 01:38:21 -07:00
|
|
|
g_free (offset_table);
|
|
|
|
|
return FALSE;
|
2021-06-19 04:41:54 -07:00
|
|
|
}
|
|
|
|
|
offset = info->cp;
|
|
|
|
|
}
|
2022-10-24 08:04:34 -07:00
|
|
|
next_tile += job_data->batch_size;
|
|
|
|
|
|
|
|
|
|
if (job_data->tile + job_data->batch_size >= ntiles)
|
|
|
|
|
done = TRUE;
|
|
|
|
|
|
|
|
|
|
xcf_save_free_job_data (job_data);
|
|
|
|
|
|
|
|
|
|
if (done)
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
g_async_queue_push_sorted (queue, job_data,
|
|
|
|
|
(GCompareDataFunc) xcf_save_sort_job_data,
|
|
|
|
|
NULL);
|
2021-06-19 04:41:54 -07:00
|
|
|
}
|
2006-04-12 05:49:29 -07:00
|
|
|
}
|
2022-10-23 01:38:21 -07:00
|
|
|
|
2022-10-24 08:04:34 -07:00
|
|
|
g_thread_pool_free (pool, FALSE, TRUE);
|
|
|
|
|
g_async_queue_unref (queue);
|
2021-06-19 04:41:54 -07:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
/* non parallel implementation */
|
|
|
|
|
for (i = 0; i < ntiles; i++)
|
app: limit allowable tile data size in XCFs
When loading tiles from an XCF, reject tiles whose on-disk size is
greater than 1.5 times the size of an uncompressed tile -- a limit
that is already present for the last tile in the buffer. This
should allow for the possibility of negative compression, while
restricting placing a realistic limit.
Currently, no limit is placed on the on-disk tile data size. When
loading RLE- and zlib-compressed tiles, a buffer large enough to
hold the entire on-disk tile data, up to 2GB, is allocated on the
stack, and the data is read into it. If the file is smaller than
the reported tile data size, the area of the buffer past the end
of the file is not touched. This allows a malicious XCF to write
up to 2GB of arbitrary data, at an arbitrary offset, up to 2GB,
below the stack.
Note that a similar issue had existed for earlier versions of GIMP
(see commit d7a9e6079d3e65baa516f302eb285fb2bbd97c2f), however,
since prior to 2.9 the tile data buffer was allocated on the heap,
the potential risk is far smaller.
2017-07-09 14:20:30 -07:00
|
|
|
{
|
2021-06-19 04:41:54 -07:00
|
|
|
GeglRectangle rect;
|
2022-10-23 01:38:21 -07:00
|
|
|
|
2021-06-19 04:41:54 -07:00
|
|
|
/* store the offset in the table and increment the next pointer */
|
|
|
|
|
*next_offset++ = offset;
|
2022-10-23 01:38:21 -07:00
|
|
|
|
2021-06-19 04:41:54 -07:00
|
|
|
gimp_gegl_buffer_get_tile_rect (buffer,
|
|
|
|
|
XCF_TILE_WIDTH, XCF_TILE_HEIGHT,
|
|
|
|
|
i, &rect);
|
2022-10-23 01:38:21 -07:00
|
|
|
|
2021-06-19 04:41:54 -07:00
|
|
|
/* write out the tile. */
|
|
|
|
|
switch (info->compression)
|
|
|
|
|
{
|
|
|
|
|
case COMPRESS_NONE:
|
|
|
|
|
xcf_check_error (xcf_save_tile (info, buffer, &rect, format,
|
2023-05-11 06:18:33 -07:00
|
|
|
error), ;);
|
2021-06-19 04:41:54 -07:00
|
|
|
break;
|
|
|
|
|
case COMPRESS_FRACTAL:
|
|
|
|
|
g_warning ("xcf: fractal compression unimplemented");
|
|
|
|
|
g_free (offset_table);
|
|
|
|
|
return FALSE;
|
|
|
|
|
default:
|
|
|
|
|
g_warning ("xcf: unsupported compression algorithm");
|
|
|
|
|
g_free (offset_table);
|
|
|
|
|
return FALSE;
|
|
|
|
|
}
|
2022-10-23 01:38:21 -07:00
|
|
|
|
2021-06-19 04:41:54 -07:00
|
|
|
/* make sure the on-disk tile data didn't end up being too big.
|
|
|
|
|
* xcf_load_level() would refuse to load the file if it did.
|
|
|
|
|
*/
|
|
|
|
|
if (info->cp < offset || info->cp - offset > max_data_length)
|
|
|
|
|
{
|
|
|
|
|
g_message ("xcf: invalid tile data length: %" G_GOFFSET_FORMAT,
|
|
|
|
|
info->cp - offset);
|
|
|
|
|
g_free (offset_table);
|
|
|
|
|
return FALSE;
|
|
|
|
|
}
|
2022-10-23 01:38:21 -07:00
|
|
|
|
2021-06-19 04:41:54 -07:00
|
|
|
/* the next tile's offset is after the tile we just wrote */
|
|
|
|
|
offset = info->cp;
|
app: limit allowable tile data size in XCFs
When loading tiles from an XCF, reject tiles whose on-disk size is
greater than 1.5 times the size of an uncompressed tile -- a limit
that is already present for the last tile in the buffer. This
should allow for the possibility of negative compression, while
restricting placing a realistic limit.
Currently, no limit is placed on the on-disk tile data size. When
loading RLE- and zlib-compressed tiles, a buffer large enough to
hold the entire on-disk tile data, up to 2GB, is allocated on the
stack, and the data is read into it. If the file is smaller than
the reported tile data size, the area of the buffer past the end
of the file is not touched. This allows a malicious XCF to write
up to 2GB of arbitrary data, at an arbitrary offset, up to 2GB,
below the stack.
Note that a similar issue had existed for earlier versions of GIMP
(see commit d7a9e6079d3e65baa516f302eb285fb2bbd97c2f), however,
since prior to 2.9 the tile data buffer was allocated on the heap,
the potential risk is far smaller.
2017-07-09 14:20:30 -07:00
|
|
|
}
|
2012-04-02 15:00:15 -07:00
|
|
|
}
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2015-04-06 11:01:39 -07:00
|
|
|
/* seek back to the offset table and write it */
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_check_error (xcf_seek_pos (info, saved_pos, error), ;);
|
|
|
|
|
xcf_write_offset_check_error (info, offset_table, ntiles + 1, ;);
|
2015-04-06 11:01:39 -07:00
|
|
|
|
|
|
|
|
/* seek to the end of the file */
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_check_error (xcf_seek_pos (info, offset, error), ;);
|
2002-12-19 22:26:34 -08:00
|
|
|
|
2021-01-20 11:56:45 -08:00
|
|
|
g_free (offset_table);
|
|
|
|
|
|
2002-12-19 22:26:34 -08:00
|
|
|
return TRUE;
|
2001-07-03 11:38:56 -07:00
|
|
|
}
|
|
|
|
|
|
2002-12-19 22:26:34 -08:00
|
|
|
static gboolean
|
2012-04-02 15:00:15 -07:00
|
|
|
xcf_save_tile (XcfInfo *info,
|
|
|
|
|
GeglBuffer *buffer,
|
|
|
|
|
GeglRectangle *tile_rect,
|
2012-04-20 14:45:22 -07:00
|
|
|
const Babl *format,
|
2012-04-02 15:00:15 -07:00
|
|
|
GError **error)
|
2001-07-03 11:38:56 -07:00
|
|
|
{
|
2012-04-20 14:45:22 -07:00
|
|
|
gint bpp = babl_format_get_bytes_per_pixel (format);
|
|
|
|
|
gint tile_size = bpp * tile_rect->width * tile_rect->height;
|
|
|
|
|
guchar *tile_data = g_alloca (tile_size);
|
|
|
|
|
GError *tmp_error = NULL;
|
2012-04-02 15:00:15 -07:00
|
|
|
|
2012-04-20 14:45:22 -07:00
|
|
|
gegl_buffer_get (buffer, tile_rect, 1.0, format, tile_data,
|
2012-04-02 15:00:15 -07:00
|
|
|
GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
|
2002-12-19 22:26:34 -08:00
|
|
|
|
2017-09-16 10:03:20 -07:00
|
|
|
if (info->file_version <= 11)
|
|
|
|
|
{
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_int8_check_error (info, tile_data, tile_size, ;);
|
2017-09-16 10:03:20 -07:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
gint n_components = babl_format_get_n_components (format);
|
|
|
|
|
|
|
|
|
|
xcf_write_component_check_error (info, bpp / n_components, tile_data,
|
2023-05-11 06:18:33 -07:00
|
|
|
tile_size / bpp * n_components, ;);
|
2017-09-16 10:03:20 -07:00
|
|
|
}
|
2002-12-19 22:26:34 -08:00
|
|
|
|
|
|
|
|
return TRUE;
|
2001-07-03 11:38:56 -07:00
|
|
|
}
|
|
|
|
|
|
2021-06-19 04:41:54 -07:00
|
|
|
static void
|
2022-10-24 08:04:34 -07:00
|
|
|
xcf_save_free_job_data (XcfJobData *data)
|
|
|
|
|
{
|
2022-10-24 14:39:06 -07:00
|
|
|
g_free (data->out_data);
|
2022-10-24 08:04:34 -07:00
|
|
|
g_free (data->tile_data);
|
|
|
|
|
g_free (data);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static gint
|
|
|
|
|
xcf_save_sort_job_data (XcfJobData *data1,
|
|
|
|
|
XcfJobData *data2,
|
|
|
|
|
gpointer user_data)
|
2021-06-19 04:41:54 -07:00
|
|
|
{
|
2022-10-24 08:04:34 -07:00
|
|
|
if (data1->tile < data2->tile)
|
|
|
|
|
return -1;
|
|
|
|
|
else if (data1->tile > data2->tile)
|
|
|
|
|
return 1;
|
|
|
|
|
else
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2022-10-24 14:39:06 -07:00
|
|
|
xcf_save_tile_parallel (XcfJobData *job_data,
|
|
|
|
|
GAsyncQueue *queue)
|
2022-10-24 08:04:34 -07:00
|
|
|
{
|
2022-10-24 14:39:06 -07:00
|
|
|
const Babl *format;
|
|
|
|
|
GeglRectangle tile_rect;
|
|
|
|
|
gint bpp;
|
|
|
|
|
|
|
|
|
|
format = gegl_buffer_get_format (job_data->buffer);
|
|
|
|
|
bpp = babl_format_get_bytes_per_pixel (format);
|
2022-10-24 08:04:34 -07:00
|
|
|
|
|
|
|
|
for (gint i = 0; i < job_data->batch_size; ++i)
|
|
|
|
|
{
|
|
|
|
|
gimp_gegl_buffer_get_tile_rect (job_data->buffer,
|
|
|
|
|
XCF_TILE_WIDTH,
|
|
|
|
|
XCF_TILE_HEIGHT,
|
|
|
|
|
job_data->tile + i,
|
|
|
|
|
&tile_rect);
|
|
|
|
|
/* only single thread can create tile data when cache miss */
|
2022-10-24 14:39:06 -07:00
|
|
|
gegl_buffer_get (job_data->buffer, &tile_rect, 1.0, format,
|
2022-10-24 08:04:34 -07:00
|
|
|
job_data->tile_data,
|
|
|
|
|
GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
|
2022-10-24 14:39:06 -07:00
|
|
|
|
|
|
|
|
if (job_data->file_version >= 12)
|
|
|
|
|
{
|
|
|
|
|
gint n_components = babl_format_get_n_components (format);
|
|
|
|
|
gint tile_size = bpp * tile_rect.width * tile_rect.height;
|
|
|
|
|
|
|
|
|
|
xcf_write_to_be (bpp / n_components, job_data->tile_data,
|
|
|
|
|
tile_size / bpp * n_components);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
job_data->compress (&tile_rect, job_data->tile_data, format,
|
|
|
|
|
job_data->out_data + job_data->max_out_data_len * i,
|
|
|
|
|
job_data->max_out_data_len,
|
|
|
|
|
job_data->out_data_len + i);
|
2022-10-24 08:04:34 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
g_async_queue_push_sorted (queue, job_data,
|
|
|
|
|
(GCompareDataFunc) xcf_save_sort_job_data,
|
|
|
|
|
NULL);
|
2021-06-19 04:41:54 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
xcf_save_tile_rle (GeglRectangle *tile_rect,
|
|
|
|
|
guchar *tile_data,
|
2022-10-23 01:38:21 -07:00
|
|
|
const Babl *format,
|
2012-04-02 15:00:15 -07:00
|
|
|
guchar *rlebuf,
|
2022-10-24 14:39:06 -07:00
|
|
|
gint rlebuf_max_len,
|
|
|
|
|
gint *lenptr)
|
2001-07-03 11:38:56 -07:00
|
|
|
{
|
2022-10-24 14:39:06 -07:00
|
|
|
gint bpp = babl_format_get_bytes_per_pixel (format);
|
|
|
|
|
gint len = 0;
|
2022-10-23 01:38:21 -07:00
|
|
|
gint i, j;
|
2012-04-20 14:45:22 -07:00
|
|
|
|
2001-07-03 11:38:56 -07:00
|
|
|
for (i = 0; i < bpp; i++)
|
|
|
|
|
{
|
2012-04-02 15:00:15 -07:00
|
|
|
const guchar *data = tile_data + i;
|
|
|
|
|
gint state = 0;
|
|
|
|
|
gint length = 0;
|
|
|
|
|
gint count = 0;
|
|
|
|
|
gint size = tile_rect->width * tile_rect->height;
|
|
|
|
|
guint last = -1;
|
2001-07-03 11:38:56 -07:00
|
|
|
|
|
|
|
|
while (size > 0)
|
2006-04-12 05:49:29 -07:00
|
|
|
{
|
|
|
|
|
switch (state)
|
|
|
|
|
{
|
|
|
|
|
case 0:
|
|
|
|
|
/* in state 0 we try to find a long sequence of
|
|
|
|
|
* matching values.
|
|
|
|
|
*/
|
|
|
|
|
if ((length == 32768) ||
|
|
|
|
|
((size - length) <= 0) ||
|
|
|
|
|
((length > 1) && (last != *data)))
|
|
|
|
|
{
|
|
|
|
|
count += length;
|
2006-07-11 13:21:18 -07:00
|
|
|
|
2006-04-12 05:49:29 -07:00
|
|
|
if (length >= 128)
|
|
|
|
|
{
|
|
|
|
|
rlebuf[len++] = 127;
|
2001-07-03 11:38:56 -07:00
|
|
|
rlebuf[len++] = (length >> 8);
|
|
|
|
|
rlebuf[len++] = length & 0x00FF;
|
2006-04-12 05:49:29 -07:00
|
|
|
rlebuf[len++] = last;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
rlebuf[len++] = length - 1;
|
|
|
|
|
rlebuf[len++] = last;
|
|
|
|
|
}
|
2006-07-11 13:21:18 -07:00
|
|
|
|
2006-04-12 05:49:29 -07:00
|
|
|
size -= length;
|
|
|
|
|
length = 0;
|
|
|
|
|
}
|
|
|
|
|
else if ((length == 1) && (last != *data))
|
2006-07-11 13:21:18 -07:00
|
|
|
{
|
|
|
|
|
state = 1;
|
|
|
|
|
}
|
2006-04-12 05:49:29 -07:00
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 1:
|
|
|
|
|
/* in state 1 we try and find a long sequence of
|
|
|
|
|
* non-matching values.
|
|
|
|
|
*/
|
|
|
|
|
if ((length == 32768) ||
|
|
|
|
|
((size - length) == 0) ||
|
|
|
|
|
((length > 0) && (last == *data) &&
|
|
|
|
|
((size - length) == 1 || last == data[bpp])))
|
|
|
|
|
{
|
2006-07-11 13:21:18 -07:00
|
|
|
const guchar *t;
|
|
|
|
|
|
2015-11-25 00:35:57 -08:00
|
|
|
/* if we came here because of a new run, backup one */
|
|
|
|
|
if (!((length == 32768) || ((size - length) == 0)))
|
|
|
|
|
{
|
|
|
|
|
length--;
|
|
|
|
|
data -= bpp;
|
|
|
|
|
}
|
|
|
|
|
|
2006-04-12 05:49:29 -07:00
|
|
|
count += length;
|
|
|
|
|
state = 0;
|
|
|
|
|
|
|
|
|
|
if (length >= 128)
|
|
|
|
|
{
|
|
|
|
|
rlebuf[len++] = 255 - 127;
|
2001-07-03 11:38:56 -07:00
|
|
|
rlebuf[len++] = (length >> 8);
|
|
|
|
|
rlebuf[len++] = length & 0x00FF;
|
2006-04-12 05:49:29 -07:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
rlebuf[len++] = 255 - (length - 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
t = data - length * bpp;
|
2006-07-11 13:21:18 -07:00
|
|
|
|
2006-04-12 05:49:29 -07:00
|
|
|
for (j = 0; j < length; j++)
|
|
|
|
|
{
|
|
|
|
|
rlebuf[len++] = *t;
|
|
|
|
|
t += bpp;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
size -= length;
|
|
|
|
|
length = 0;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2006-07-11 13:21:18 -07:00
|
|
|
if (size > 0)
|
|
|
|
|
{
|
|
|
|
|
length += 1;
|
|
|
|
|
last = *data;
|
|
|
|
|
data += bpp;
|
|
|
|
|
}
|
2006-04-12 05:49:29 -07:00
|
|
|
}
|
2001-07-03 11:38:56 -07:00
|
|
|
|
2012-04-02 15:00:15 -07:00
|
|
|
if (count != (tile_rect->width * tile_rect->height))
|
2006-04-12 05:49:29 -07:00
|
|
|
g_message ("xcf: uh oh! xcf rle tile saving error: %d", count);
|
2001-07-03 11:38:56 -07:00
|
|
|
}
|
2006-07-11 13:21:18 -07:00
|
|
|
|
2021-06-19 04:41:54 -07:00
|
|
|
*lenptr = len;
|
2001-07-03 11:38:56 -07:00
|
|
|
}
|
2003-05-15 05:47:42 -07:00
|
|
|
|
2022-10-24 14:39:06 -07:00
|
|
|
static void
|
|
|
|
|
xcf_save_tile_zlib (GeglRectangle *tile_rect,
|
|
|
|
|
guchar *tile_data,
|
2014-09-15 06:33:22 -07:00
|
|
|
const Babl *format,
|
2022-10-24 14:39:06 -07:00
|
|
|
guchar *zlib_data,
|
|
|
|
|
gint zlib_data_max_len,
|
|
|
|
|
gint *lenptr)
|
2014-09-15 06:33:22 -07:00
|
|
|
{
|
|
|
|
|
gint bpp = babl_format_get_bytes_per_pixel (format);
|
|
|
|
|
gint tile_size = bpp * tile_rect->width * tile_rect->height;
|
|
|
|
|
/* The buffer for compressed data. */
|
|
|
|
|
z_stream strm;
|
|
|
|
|
int action;
|
|
|
|
|
int status;
|
|
|
|
|
|
2022-10-24 14:39:06 -07:00
|
|
|
*lenptr = 0;
|
2017-09-16 10:03:20 -07:00
|
|
|
|
2014-09-15 06:33:22 -07:00
|
|
|
/* allocate deflate state */
|
|
|
|
|
strm.zalloc = Z_NULL;
|
|
|
|
|
strm.zfree = Z_NULL;
|
|
|
|
|
strm.opaque = Z_NULL;
|
|
|
|
|
|
|
|
|
|
status = deflateInit (&strm, Z_DEFAULT_COMPRESSION);
|
|
|
|
|
if (status != Z_OK)
|
2022-10-24 14:39:06 -07:00
|
|
|
return;
|
2014-09-15 06:33:22 -07:00
|
|
|
|
|
|
|
|
strm.next_in = tile_data;
|
|
|
|
|
strm.avail_in = tile_size;
|
2022-10-24 14:39:06 -07:00
|
|
|
strm.next_out = zlib_data;
|
|
|
|
|
strm.avail_out = zlib_data_max_len;
|
2014-09-15 06:33:22 -07:00
|
|
|
|
|
|
|
|
action = Z_NO_FLUSH;
|
|
|
|
|
|
|
|
|
|
while (status == Z_OK || status == Z_BUF_ERROR)
|
|
|
|
|
{
|
|
|
|
|
if (strm.avail_in == 0)
|
|
|
|
|
{
|
|
|
|
|
/* Finish the encoding. */
|
|
|
|
|
action = Z_FINISH;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
status = deflate (&strm, action);
|
|
|
|
|
|
|
|
|
|
if (status == Z_STREAM_END || status == Z_BUF_ERROR)
|
|
|
|
|
{
|
2022-10-24 14:39:06 -07:00
|
|
|
size_t write_size = zlib_data_max_len - strm.avail_out;
|
2014-09-15 06:33:22 -07:00
|
|
|
|
2022-10-24 14:39:06 -07:00
|
|
|
*lenptr = write_size;
|
2014-09-15 06:33:22 -07:00
|
|
|
|
|
|
|
|
/* Reset next_out and avail_out. */
|
2022-10-24 14:39:06 -07:00
|
|
|
strm.next_out = zlib_data;
|
|
|
|
|
strm.avail_out = zlib_data_max_len;
|
2014-09-15 06:33:22 -07:00
|
|
|
}
|
|
|
|
|
else if (status != Z_OK)
|
|
|
|
|
{
|
|
|
|
|
g_printerr ("xcf: tile compression failed: %s", zError (status));
|
|
|
|
|
deflateEnd (&strm);
|
2022-10-24 14:39:06 -07:00
|
|
|
return;
|
2014-09-15 06:33:22 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
deflateEnd (&strm);
|
|
|
|
|
}
|
|
|
|
|
|
2003-09-10 09:22:33 -07:00
|
|
|
static gboolean
|
|
|
|
|
xcf_save_parasite (XcfInfo *info,
|
|
|
|
|
GimpParasite *parasite,
|
|
|
|
|
GError **error)
|
|
|
|
|
{
|
|
|
|
|
if (gimp_parasite_is_persistent (parasite))
|
|
|
|
|
{
|
2021-01-29 14:52:03 -08:00
|
|
|
guint32 value;
|
|
|
|
|
const gchar *string;
|
|
|
|
|
GError *tmp_error = NULL;
|
|
|
|
|
gconstpointer parasite_data;
|
2007-12-21 08:37:01 -08:00
|
|
|
|
2021-04-05 09:36:44 -07:00
|
|
|
string = gimp_parasite_get_name (parasite);
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_string_check_error (info, (gchar **) &string, 1, ;);
|
2003-09-10 09:22:33 -07:00
|
|
|
|
2021-04-05 09:36:44 -07:00
|
|
|
value = gimp_parasite_get_flags (parasite);
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_int32_check_error (info, &value, 1, ;);
|
2007-12-21 08:37:01 -08:00
|
|
|
|
2021-01-29 14:52:03 -08:00
|
|
|
parasite_data = gimp_parasite_get_data (parasite, &value);
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_int32_check_error (info, &value, 1, ;);
|
2007-12-21 08:37:01 -08:00
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_int8_check_error (info, parasite_data, value, ;);
|
2003-09-10 09:22:33 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-21 16:26:18 -07:00
|
|
|
typedef struct
|
|
|
|
|
{
|
|
|
|
|
XcfInfo *info;
|
|
|
|
|
GError *error;
|
|
|
|
|
} XcfParasiteData;
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
xcf_save_parasite_func (gchar *key,
|
|
|
|
|
GimpParasite *parasite,
|
|
|
|
|
XcfParasiteData *data)
|
|
|
|
|
{
|
|
|
|
|
if (! data->error)
|
|
|
|
|
xcf_save_parasite (data->info, parasite, &data->error);
|
|
|
|
|
}
|
|
|
|
|
|
2003-09-10 09:22:33 -07:00
|
|
|
static gboolean
|
|
|
|
|
xcf_save_parasite_list (XcfInfo *info,
|
|
|
|
|
GimpParasiteList *list,
|
|
|
|
|
GError **error)
|
|
|
|
|
{
|
2023-05-21 16:26:18 -07:00
|
|
|
XcfParasiteData data;
|
2003-09-10 09:22:33 -07:00
|
|
|
|
2023-05-21 16:26:18 -07:00
|
|
|
data.info = info;
|
|
|
|
|
data.error = NULL;
|
2003-05-15 05:47:42 -07:00
|
|
|
|
2023-05-21 16:26:18 -07:00
|
|
|
gimp_parasite_list_foreach (list, (GHFunc) xcf_save_parasite_func, &data);
|
2003-09-10 09:22:33 -07:00
|
|
|
|
2023-05-21 16:26:18 -07:00
|
|
|
if (data.error)
|
2003-09-10 09:22:33 -07:00
|
|
|
{
|
2023-05-21 16:26:18 -07:00
|
|
|
g_propagate_error (error, data.error);
|
2003-09-10 09:22:33 -07:00
|
|
|
return FALSE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return TRUE;
|
2003-05-15 05:47:42 -07:00
|
|
|
}
|
|
|
|
|
|
2022-10-19 15:11:34 -07:00
|
|
|
/* This is the oldest way to save paths. */
|
2003-05-15 05:47:42 -07:00
|
|
|
static gboolean
|
2003-05-21 10:38:14 -07:00
|
|
|
xcf_save_old_paths (XcfInfo *info,
|
2006-03-28 09:08:36 -08:00
|
|
|
GimpImage *image,
|
2003-05-21 10:38:14 -07:00
|
|
|
GError **error)
|
2003-05-15 05:47:42 -07:00
|
|
|
{
|
2024-07-11 23:16:25 -07:00
|
|
|
GimpPath *active_path = NULL;
|
|
|
|
|
guint32 num_paths;
|
|
|
|
|
guint32 active_index = 0;
|
|
|
|
|
GList *list;
|
|
|
|
|
GError *tmp_error = NULL;
|
2003-05-15 05:47:42 -07:00
|
|
|
|
|
|
|
|
/* Write out the following:-
|
|
|
|
|
*
|
|
|
|
|
* last_selected_row (gint)
|
|
|
|
|
* number_of_paths (gint)
|
|
|
|
|
*
|
|
|
|
|
* then each path:-
|
|
|
|
|
*/
|
|
|
|
|
|
2024-07-10 17:07:44 -07:00
|
|
|
num_paths = gimp_container_get_n_children (gimp_image_get_paths (image));
|
2003-05-21 10:38:14 -07:00
|
|
|
|
2024-07-10 17:07:44 -07:00
|
|
|
if (gimp_image_get_selected_paths (image))
|
2022-10-19 15:11:34 -07:00
|
|
|
{
|
2024-07-10 17:07:44 -07:00
|
|
|
active_path = gimp_image_get_selected_paths (image)->data;
|
2025-07-07 07:05:07 -07:00
|
|
|
/* Having more than 1 selected paths should not have happened in this
|
2022-10-19 15:11:34 -07:00
|
|
|
* code path but let's not break saving, only produce a critical.
|
|
|
|
|
*/
|
2024-07-10 17:07:44 -07:00
|
|
|
if (g_list_length (gimp_image_get_selected_paths (image)) > 1)
|
2022-10-19 15:11:34 -07:00
|
|
|
g_critical ("%s: this code path should not happen with multiple paths selected",
|
|
|
|
|
G_STRFUNC);
|
|
|
|
|
}
|
2003-05-15 05:47:42 -07:00
|
|
|
|
2024-07-10 17:07:44 -07:00
|
|
|
if (active_path)
|
|
|
|
|
active_index = gimp_container_get_child_index (gimp_image_get_paths (image),
|
|
|
|
|
GIMP_OBJECT (active_path));
|
2003-05-21 10:38:14 -07:00
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_int32_check_error (info, &active_index, 1, ;);
|
|
|
|
|
xcf_write_int32_check_error (info, &num_paths, 1, ;);
|
2003-05-15 05:47:42 -07:00
|
|
|
|
2024-07-10 17:07:44 -07:00
|
|
|
for (list = gimp_image_get_path_iter (image);
|
2003-05-21 10:38:14 -07:00
|
|
|
list;
|
|
|
|
|
list = g_list_next (list))
|
2003-05-15 05:47:42 -07:00
|
|
|
{
|
2025-07-07 07:05:07 -07:00
|
|
|
GimpPath *path = list->data;
|
2003-05-22 12:02:38 -07:00
|
|
|
gchar *name;
|
|
|
|
|
guint32 locked;
|
|
|
|
|
guint8 state;
|
|
|
|
|
guint32 version;
|
|
|
|
|
guint32 pathtype;
|
|
|
|
|
guint32 tattoo;
|
2024-07-12 22:07:57 -07:00
|
|
|
GimpPathCompatPoint *points;
|
2006-04-02 11:19:48 -07:00
|
|
|
guint32 num_points;
|
|
|
|
|
guint32 closed;
|
2003-05-22 12:02:38 -07:00
|
|
|
gint i;
|
2003-05-15 05:47:42 -07:00
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* name (string)
|
|
|
|
|
* locked (gint)
|
|
|
|
|
* state (gchar)
|
|
|
|
|
* closed (gint)
|
|
|
|
|
* number points (gint)
|
2003-05-21 10:38:14 -07:00
|
|
|
* version (gint)
|
|
|
|
|
* pathtype (gint)
|
|
|
|
|
* tattoo (gint)
|
2003-05-15 05:47:42 -07:00
|
|
|
* then each point.
|
|
|
|
|
*/
|
|
|
|
|
|
2025-07-07 07:05:07 -07:00
|
|
|
points = gimp_path_compat_get_points (path,
|
2024-07-12 22:07:57 -07:00
|
|
|
(gint32 *) &num_points,
|
|
|
|
|
(gint32 *) &closed);
|
2003-05-21 10:38:14 -07:00
|
|
|
|
2003-09-09 08:46:59 -07:00
|
|
|
/* if no points are generated because of a faulty path we should
|
|
|
|
|
* skip saving the path - this is unfortunately impossible, because
|
2018-03-24 13:49:01 -07:00
|
|
|
* we already saved the number of paths and I won't start seeking
|
2003-09-09 08:46:59 -07:00
|
|
|
* around to fix that cruft */
|
|
|
|
|
|
2025-07-07 07:05:07 -07:00
|
|
|
name = (gchar *) gimp_object_get_name (path);
|
2021-12-22 13:53:15 -08:00
|
|
|
/* The 'linked' concept does not exist anymore in GIMP 3.0 and over. */
|
|
|
|
|
locked = 0;
|
2003-05-22 12:02:38 -07:00
|
|
|
state = closed ? 4 : 2; /* EDIT : ADD (editing state, 1.2 compat) */
|
2003-05-21 10:38:14 -07:00
|
|
|
version = 3;
|
2003-05-22 12:02:38 -07:00
|
|
|
pathtype = 1; /* BEZIER (1.2 compat) */
|
2025-07-07 07:05:07 -07:00
|
|
|
tattoo = gimp_item_get_tattoo (GIMP_ITEM (path));
|
2003-09-10 03:40:57 -07:00
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_string_check_error (info, &name, 1, ;);
|
|
|
|
|
xcf_write_int32_check_error (info, &locked, 1, ;);
|
|
|
|
|
xcf_write_int8_check_error (info, &state, 1, ;);
|
|
|
|
|
xcf_write_int32_check_error (info, &closed, 1, ;);
|
|
|
|
|
xcf_write_int32_check_error (info, &num_points, 1, ;);
|
|
|
|
|
xcf_write_int32_check_error (info, &version, 1, ;);
|
|
|
|
|
xcf_write_int32_check_error (info, &pathtype, 1, ;);
|
|
|
|
|
xcf_write_int32_check_error (info, &tattoo, 1, ;);
|
2003-05-21 10:38:14 -07:00
|
|
|
|
2003-05-22 12:02:38 -07:00
|
|
|
for (i = 0; i < num_points; i++)
|
2003-05-15 05:47:42 -07:00
|
|
|
{
|
2003-05-22 12:02:38 -07:00
|
|
|
gfloat x;
|
|
|
|
|
gfloat y;
|
|
|
|
|
|
|
|
|
|
x = points[i].x;
|
|
|
|
|
y = points[i].y;
|
2003-05-21 10:38:14 -07:00
|
|
|
|
2003-05-22 12:02:38 -07:00
|
|
|
/*
|
|
|
|
|
* type (gint)
|
|
|
|
|
* x (gfloat)
|
|
|
|
|
* y (gfloat)
|
|
|
|
|
*/
|
|
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_int32_check_error (info, &points[i].type, 1, ;);
|
|
|
|
|
xcf_write_float_check_error (info, &x, 1, ;);
|
|
|
|
|
xcf_write_float_check_error (info, &y, 1, ;);
|
2003-05-15 05:47:42 -07:00
|
|
|
}
|
2003-05-22 12:02:38 -07:00
|
|
|
|
|
|
|
|
g_free (points);
|
2003-05-15 05:47:42 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
2003-09-09 08:46:59 -07:00
|
|
|
|
2022-10-19 15:11:34 -07:00
|
|
|
/* 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.
|
|
|
|
|
*/
|
2003-09-09 08:46:59 -07:00
|
|
|
static gboolean
|
2022-10-19 15:11:34 -07:00
|
|
|
xcf_save_old_vectors (XcfInfo *info,
|
|
|
|
|
GimpImage *image,
|
|
|
|
|
GError **error)
|
2003-09-09 08:46:59 -07:00
|
|
|
{
|
2024-07-11 23:16:25 -07:00
|
|
|
GimpPath *active_path = NULL;
|
|
|
|
|
guint32 version = 1;
|
|
|
|
|
guint32 active_index = 0;
|
|
|
|
|
guint32 num_paths;
|
|
|
|
|
GList *list;
|
|
|
|
|
GList *stroke_list;
|
|
|
|
|
GError *tmp_error = NULL;
|
2003-09-09 08:46:59 -07:00
|
|
|
|
|
|
|
|
/* Write out the following:-
|
|
|
|
|
*
|
2003-09-10 09:22:33 -07:00
|
|
|
* version (gint)
|
|
|
|
|
* active_index (gint)
|
|
|
|
|
* num_paths (gint)
|
2003-09-09 08:46:59 -07:00
|
|
|
*
|
|
|
|
|
* then each path:-
|
|
|
|
|
*/
|
|
|
|
|
|
2024-07-10 17:07:44 -07:00
|
|
|
if (gimp_image_get_selected_paths (image))
|
2022-10-19 15:11:34 -07:00
|
|
|
{
|
2024-07-10 17:07:44 -07:00
|
|
|
active_path = gimp_image_get_selected_paths (image)->data;
|
2025-07-07 07:05:07 -07:00
|
|
|
/* Having more than 1 selected paths should not have happened in this
|
2022-10-19 15:11:34 -07:00
|
|
|
* code path but let's not break saving, only produce a critical.
|
|
|
|
|
*/
|
2024-07-10 17:07:44 -07:00
|
|
|
if (g_list_length (gimp_image_get_selected_paths (image)) > 1)
|
2022-10-19 15:11:34 -07:00
|
|
|
g_critical ("%s: this code path should not happen with multiple paths selected",
|
|
|
|
|
G_STRFUNC);
|
|
|
|
|
}
|
2003-09-09 08:46:59 -07:00
|
|
|
|
2024-07-10 17:07:44 -07:00
|
|
|
if (active_path)
|
|
|
|
|
active_index = gimp_container_get_child_index (gimp_image_get_paths (image),
|
|
|
|
|
GIMP_OBJECT (active_path));
|
2003-09-09 08:46:59 -07:00
|
|
|
|
2024-07-10 17:07:44 -07:00
|
|
|
num_paths = gimp_container_get_n_children (gimp_image_get_paths (image));
|
2003-09-10 09:22:33 -07:00
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_int32_check_error (info, &version, 1, ;);
|
|
|
|
|
xcf_write_int32_check_error (info, &active_index, 1, ;);
|
|
|
|
|
xcf_write_int32_check_error (info, &num_paths, 1, ;);
|
2003-09-09 08:46:59 -07:00
|
|
|
|
2024-07-10 17:07:44 -07:00
|
|
|
for (list = gimp_image_get_path_iter (image);
|
2003-09-09 08:46:59 -07:00
|
|
|
list;
|
|
|
|
|
list = g_list_next (list))
|
|
|
|
|
{
|
2025-07-07 07:05:07 -07:00
|
|
|
GimpPath *path = list->data;
|
2003-09-10 09:22:33 -07:00
|
|
|
GimpParasiteList *parasites;
|
2007-12-21 08:37:01 -08:00
|
|
|
const gchar *name;
|
2003-09-10 09:22:33 -07:00
|
|
|
guint32 tattoo;
|
2003-09-11 12:52:29 -07:00
|
|
|
guint32 visible;
|
2003-09-10 09:22:33 -07:00
|
|
|
guint32 linked;
|
|
|
|
|
guint32 num_parasites;
|
|
|
|
|
guint32 num_strokes;
|
2003-09-09 08:46:59 -07:00
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* name (string)
|
|
|
|
|
* tattoo (gint)
|
2003-09-11 12:52:29 -07:00
|
|
|
* visible (gint)
|
2003-09-10 09:22:33 -07:00
|
|
|
* linked (gint)
|
|
|
|
|
* num_parasites (gint)
|
|
|
|
|
* num_strokes (gint)
|
2003-09-09 08:46:59 -07:00
|
|
|
*
|
2003-09-10 09:22:33 -07:00
|
|
|
* then each parasite
|
|
|
|
|
* then each stroke
|
2003-09-09 08:46:59 -07:00
|
|
|
*/
|
|
|
|
|
|
2025-07-07 07:05:07 -07:00
|
|
|
name = gimp_object_get_name (path);
|
|
|
|
|
visible = gimp_item_get_visible (GIMP_ITEM (path));
|
2021-12-22 13:53:15 -08:00
|
|
|
/* The 'linked' concept does not exist anymore in GIMP 3.0 and over. */
|
|
|
|
|
linked = 0;
|
2025-07-07 07:05:07 -07:00
|
|
|
tattoo = gimp_item_get_tattoo (GIMP_ITEM (path));
|
|
|
|
|
parasites = gimp_item_get_parasites (GIMP_ITEM (path));
|
2003-09-10 09:22:33 -07:00
|
|
|
num_parasites = gimp_parasite_list_persistent_length (parasites);
|
2025-07-07 07:05:07 -07:00
|
|
|
num_strokes = g_queue_get_length (path->strokes);
|
2003-09-10 03:40:57 -07:00
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_string_check_error (info, (gchar **) &name, 1, ;);
|
|
|
|
|
xcf_write_int32_check_error (info, &tattoo, 1, ;);
|
|
|
|
|
xcf_write_int32_check_error (info, &visible, 1, ;);
|
|
|
|
|
xcf_write_int32_check_error (info, &linked, 1, ;);
|
|
|
|
|
xcf_write_int32_check_error (info, &num_parasites, 1, ;);
|
|
|
|
|
xcf_write_int32_check_error (info, &num_strokes, 1, ;);
|
2003-09-09 08:46:59 -07:00
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_check_error (xcf_save_parasite_list (info, parasites, error), ;);
|
2003-09-09 08:46:59 -07:00
|
|
|
|
2025-07-07 07:05:07 -07:00
|
|
|
for (stroke_list = g_list_first (path->strokes->head);
|
2003-09-09 08:46:59 -07:00
|
|
|
stroke_list;
|
|
|
|
|
stroke_list = g_list_next (stroke_list))
|
|
|
|
|
{
|
2007-12-21 08:37:01 -08:00
|
|
|
GimpStroke *stroke = stroke_list->data;
|
2003-09-10 09:22:33 -07:00
|
|
|
guint32 stroke_type;
|
|
|
|
|
guint32 closed;
|
|
|
|
|
guint32 num_axes;
|
|
|
|
|
GArray *control_points;
|
|
|
|
|
gint i;
|
2003-09-09 08:46:59 -07:00
|
|
|
|
2003-09-10 09:22:33 -07:00
|
|
|
guint32 type;
|
|
|
|
|
gfloat coords[6];
|
2003-09-09 08:46:59 -07:00
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* 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;
|
|
|
|
|
}
|
|
|
|
|
|
2006-04-02 11:19:48 -07:00
|
|
|
control_points = gimp_stroke_control_points_get (stroke,
|
|
|
|
|
(gint32 *) &closed);
|
2003-09-09 08:46:59 -07:00
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_int32_check_error (info, &stroke_type, 1, ;);
|
|
|
|
|
xcf_write_int32_check_error (info, &closed, 1, ;);
|
|
|
|
|
xcf_write_int32_check_error (info, &num_axes, 1, ;);
|
|
|
|
|
xcf_write_int32_check_error (info, &control_points->len, 1, ;);
|
2003-09-09 08:46:59 -07:00
|
|
|
|
|
|
|
|
for (i = 0; i < control_points->len; i++)
|
|
|
|
|
{
|
|
|
|
|
GimpAnchor *anchor;
|
|
|
|
|
|
|
|
|
|
anchor = & (g_array_index (control_points, GimpAnchor, i));
|
|
|
|
|
|
2003-09-10 09:22:33 -07:00
|
|
|
type = anchor->type;
|
2003-09-09 08:46:59 -07:00
|
|
|
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)
|
2003-09-10 09:22:33 -07:00
|
|
|
*
|
|
|
|
|
* the first num_axis elements of:
|
|
|
|
|
* [0] x (gfloat)
|
|
|
|
|
* [1] y (gfloat)
|
|
|
|
|
* [2] pressure (gfloat)
|
|
|
|
|
* [3] xtilt (gfloat)
|
|
|
|
|
* [4] ytilt (gfloat)
|
|
|
|
|
* [5] wheel (gfloat)
|
2003-09-09 08:46:59 -07:00
|
|
|
*/
|
|
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_int32_check_error (info, &type, 1, ;);
|
|
|
|
|
xcf_write_float_check_error (info, coords, num_axes, ;);
|
2003-09-09 08:46:59 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
g_array_free (control_points, TRUE);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
2022-10-19 15:11:34 -07:00
|
|
|
|
|
|
|
|
static gboolean
|
|
|
|
|
xcf_save_path (XcfInfo *info,
|
|
|
|
|
GimpImage *image,
|
2025-07-07 07:05:07 -07:00
|
|
|
GimpPath *path,
|
2022-10-19 15:11:34 -07:00
|
|
|
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 */
|
2025-07-07 07:05:07 -07:00
|
|
|
string = gimp_object_get_name (path);
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_string_check_error (info, (gchar **) &string, 1, ;);
|
2022-10-19 15:11:34 -07:00
|
|
|
|
|
|
|
|
/* Payload size */
|
|
|
|
|
size = 0;
|
|
|
|
|
pos = info->cp;
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_int32_check_error (info, &size, 1, ;);
|
2022-10-19 15:11:34 -07:00
|
|
|
base = info->cp;
|
|
|
|
|
|
|
|
|
|
/* write out the path properties */
|
2025-07-07 07:05:07 -07:00
|
|
|
xcf_save_path_props (info, image, path, error);
|
2022-10-19 15:11:34 -07:00
|
|
|
|
|
|
|
|
/* Path version */
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_int32_check_error (info, &version, 1, ;);
|
2022-10-19 15:11:34 -07:00
|
|
|
|
|
|
|
|
/* Write out the number of strokes. */
|
2025-07-07 07:05:07 -07:00
|
|
|
num_strokes = g_queue_get_length (path->strokes);
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_int32_check_error (info, &num_strokes, 1, ;);
|
2022-10-19 15:11:34 -07:00
|
|
|
|
2025-07-07 07:05:07 -07:00
|
|
|
for (stroke_list = g_list_first (path->strokes->head);
|
2022-10-19 15:11:34 -07:00
|
|
|
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. */
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_int32_check_error (info, &stroke_type, 1, ;);
|
2022-10-19 15:11:34 -07:00
|
|
|
/* close path or not? */
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_int32_check_error (info, &closed, 1, ;);
|
2022-10-19 15:11:34 -07:00
|
|
|
/* Number of floats given for each point. */
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_int32_check_error (info, &num_axes, 1, ;);
|
2022-10-19 15:11:34 -07:00
|
|
|
/* Number of control points. */
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_int32_check_error (info, &control_points->len, 1, ;);
|
2022-10-19 15:11:34 -07:00
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
*/
|
|
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_write_int32_check_error (info, &type, 1, ;);
|
|
|
|
|
xcf_write_float_check_error (info, coords, num_axes, ;);
|
2022-10-19 15:11:34 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
g_array_free (control_points, TRUE);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* go back to the saved position and write the length */
|
|
|
|
|
size = info->cp - base;
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_check_error (xcf_seek_pos (info, pos, error), ;);
|
|
|
|
|
xcf_write_int32_check_error (info, &size, 1, ;);
|
2022-10-19 15:11:34 -07:00
|
|
|
|
2023-05-11 06:18:33 -07:00
|
|
|
xcf_check_error (xcf_seek_pos (info, base + size, error), ;);
|
2022-10-19 15:11:34 -07:00
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
|
}
|