diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 16:23:22 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 16:23:22 +0000 |
commit | e42129241681dde7adae7d20697e7b421682fbb4 (patch) | |
tree | af1fe815a5e639e68e59fabd8395ec69458b3e5e /app/xcf/xcf-save.c | |
parent | Initial commit. (diff) | |
download | gimp-e42129241681dde7adae7d20697e7b421682fbb4.tar.xz gimp-e42129241681dde7adae7d20697e7b421682fbb4.zip |
Adding upstream version 2.10.22.upstream/2.10.22upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'app/xcf/xcf-save.c')
-rw-r--r-- | app/xcf/xcf-save.c | 2266 |
1 files changed, 2266 insertions, 0 deletions
diff --git a/app/xcf/xcf-save.c b/app/xcf/xcf-save.c new file mode 100644 index 0000000..ee5cdcf --- /dev/null +++ b/app/xcf/xcf-save.c @@ -0,0 +1,2266 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (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 + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <string.h> +#include <zlib.h> + +#include <cairo.h> +#include <gegl.h> +#include <gdk-pixbuf/gdk-pixbuf.h> + +#include "libgimpbase/gimpbase.h" +#include "libgimpcolor/gimpcolor.h" + +#include "core/core-types.h" + +#include "gegl/gimp-babl-compat.h" +#include "gegl/gimp-gegl-tile-compat.h" + +#include "core/gimp.h" +#include "core/gimpcontainer.h" +#include "core/gimpchannel.h" +#include "core/gimpdrawable.h" +#include "core/gimpgrid.h" +#include "core/gimpguide.h" +#include "core/gimpimage.h" +#include "core/gimpimage-colormap.h" +#include "core/gimpimage-grid.h" +#include "core/gimpimage-guides.h" +#include "core/gimpimage-metadata.h" +#include "core/gimpimage-private.h" +#include "core/gimpimage-sample-points.h" +#include "core/gimpimage-symmetry.h" +#include "core/gimplayer.h" +#include "core/gimplayermask.h" +#include "core/gimpparasitelist.h" +#include "core/gimpprogress.h" +#include "core/gimpsamplepoint.h" +#include "core/gimpsymmetry.h" + +#include "operations/layer-modes/gimp-layer-modes.h" + +#include "text/gimptextlayer.h" +#include "text/gimptextlayer-xcf.h" + +#include "vectors/gimpanchor.h" +#include "vectors/gimpstroke.h" +#include "vectors/gimpbezierstroke.h" +#include "vectors/gimpvectors.h" +#include "vectors/gimpvectors-compat.h" + +#include "xcf-private.h" +#include "xcf-read.h" +#include "xcf-save.h" +#include "xcf-seek.h" +#include "xcf-write.h" + +#include "gimp-intl.h" + + +static gboolean xcf_save_image_props (XcfInfo *info, + GimpImage *image, + GError **error); +static gboolean xcf_save_layer_props (XcfInfo *info, + GimpImage *image, + GimpLayer *layer, + GError **error); +static gboolean xcf_save_channel_props (XcfInfo *info, + GimpImage *image, + GimpChannel *channel, + GError **error); +static gboolean xcf_save_prop (XcfInfo *info, + GimpImage *image, + PropType prop_type, + GError **error, + ...); +static gboolean xcf_save_layer (XcfInfo *info, + GimpImage *image, + GimpLayer *layer, + GError **error); +static gboolean xcf_save_channel (XcfInfo *info, + GimpImage *image, + GimpChannel *channel, + GError **error); +static gboolean xcf_save_buffer (XcfInfo *info, + GeglBuffer *buffer, + GError **error); +static gboolean xcf_save_level (XcfInfo *info, + GeglBuffer *buffer, + GError **error); +static gboolean xcf_save_tile (XcfInfo *info, + GeglBuffer *buffer, + GeglRectangle *tile_rect, + const Babl *format, + GError **error); +static gboolean xcf_save_tile_rle (XcfInfo *info, + GeglBuffer *buffer, + GeglRectangle *tile_rect, + const Babl *format, + guchar *rlebuf, + GError **error); +static gboolean xcf_save_tile_zlib (XcfInfo *info, + GeglBuffer *buffer, + GeglRectangle *tile_rect, + const Babl *format, + GError **error); +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, + GimpImage *image, + GError **error); +static gboolean xcf_save_vectors (XcfInfo *info, + GimpImage *image, + GError **error); + + +/* private convenience macros */ +#define xcf_write_int32_check_error(info, data, count) G_STMT_START { \ + xcf_write_int32 (info, data, count, &tmp_error); \ + if (tmp_error) \ + { \ + g_propagate_error (error, tmp_error); \ + return FALSE; \ + } \ + } G_STMT_END + +#define xcf_write_offset_check_error(info, data, count) G_STMT_START { \ + xcf_write_offset (info, data, count, &tmp_error); \ + if (tmp_error) \ + { \ + g_propagate_error (error, tmp_error); \ + return FALSE; \ + } \ + } G_STMT_END + +#define xcf_write_zero_offset_check_error(info, count) G_STMT_START { \ + xcf_write_zero_offset (info, count, &tmp_error); \ + if (tmp_error) \ + { \ + g_propagate_error (error, tmp_error); \ + return FALSE; \ + } \ + } G_STMT_END + +#define xcf_write_int8_check_error(info, data, count) G_STMT_START { \ + xcf_write_int8 (info, data, count, &tmp_error); \ + if (tmp_error) \ + { \ + g_propagate_error (error, tmp_error); \ + return FALSE; \ + } \ + } G_STMT_END + +#define xcf_write_float_check_error(info, data, count) G_STMT_START { \ + xcf_write_float (info, data, count, &tmp_error); \ + if (tmp_error) \ + { \ + g_propagate_error (error, tmp_error); \ + return FALSE; \ + } \ + } G_STMT_END + +#define xcf_write_string_check_error(info, data, count) G_STMT_START { \ + xcf_write_string (info, data, count, &tmp_error); \ + if (tmp_error) \ + { \ + g_propagate_error (error, tmp_error); \ + return FALSE; \ + } \ + } G_STMT_END + +#define xcf_write_component_check_error(info, bpc, data, count) G_STMT_START { \ + xcf_write_component (info, bpc, data, count, &tmp_error); \ + if (tmp_error) \ + { \ + g_propagate_error (error, tmp_error); \ + return FALSE; \ + } \ + } G_STMT_END + +#define xcf_write_prop_type_check_error(info, prop_type) G_STMT_START { \ + guint32 _prop_int32 = prop_type; \ + xcf_write_int32_check_error (info, &_prop_int32, 1); \ + } G_STMT_END + +#define xcf_check_error(x) G_STMT_START { \ + if (! (x)) \ + return FALSE; \ + } G_STMT_END + +#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 + + +gboolean +xcf_save_image (XcfInfo *info, + GimpImage *image, + GError **error) +{ + GList *all_layers; + GList *all_channels; + GList *list; + goffset saved_pos; + goffset offset; + guint32 value; + guint n_layers; + guint n_channels; + guint progress = 0; + guint max_progress; + gchar version_tag[16]; + GError *tmp_error = NULL; + + /* write out the tag information for the image */ + if (info->file_version > 0) + { + g_snprintf (version_tag, sizeof (version_tag), + "gimp xcf v%03d", info->file_version); + } + else + { + strcpy (version_tag, "gimp xcf file"); + } + + xcf_write_int8_check_error (info, (guint8 *) version_tag, 14); + + /* write out the width, height and image type information for the image */ + value = gimp_image_get_width (image); + xcf_write_int32_check_error (info, (guint32 *) &value, 1); + + value = gimp_image_get_height (image); + xcf_write_int32_check_error (info, (guint32 *) &value, 1); + + value = gimp_image_get_base_type (image); + xcf_write_int32_check_error (info, &value, 1); + + if (info->file_version >= 4) + { + value = gimp_image_get_precision (image); + xcf_write_int32_check_error (info, &value, 1); + } + + /* determine the number of layers and channels in the image */ + all_layers = gimp_image_get_layer_list (image); + all_channels = gimp_image_get_channel_list (image); + + /* check and see if we have to save out the selection */ + if (! gimp_channel_is_empty (gimp_image_get_mask (image))) + { + 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); + + max_progress = 1 + n_layers + n_channels; + + /* write the property information for the image */ + xcf_check_error (xcf_save_image_props (info, image, error)); + + xcf_progress_update (info); + + /* 'saved_pos' is the next slot in the offset table */ + saved_pos = info->cp; + + /* write an empty offset table */ + xcf_write_zero_offset_check_error (info, n_layers + n_channels + 2); + + /* 'offset' is where we will write the next layer or channel */ + offset = info->cp; + + for (list = all_layers; list; list = g_list_next (list)) + { + GimpLayer *layer = list->data; + + /* seek back to the next slot in the offset table and write the + * offset of the layer + */ + xcf_check_error (xcf_seek_pos (info, saved_pos, error)); + xcf_write_offset_check_error (info, &offset, 1); + + /* remember the next slot in the offset table */ + saved_pos = info->cp; + + /* seek to the layer offset and save the layer */ + xcf_check_error (xcf_seek_pos (info, offset, error)); + xcf_check_error (xcf_save_layer (info, image, layer, error)); + + /* the next layer's offset is after the layer we just wrote */ + offset = info->cp; + + xcf_progress_update (info); + } + + /* skip a '0' in the offset table to indicate the end of the layer + * offsets + */ + saved_pos += info->bytes_per_offset; + + for (list = all_channels; list; list = g_list_next (list)) + { + GimpChannel *channel = list->data; + + /* seek back to the next slot in the offset table and write the + * offset of the channel + */ + xcf_check_error (xcf_seek_pos (info, saved_pos, error)); + xcf_write_offset_check_error (info, &offset, 1); + + /* remember the next slot in the offset table */ + saved_pos = info->cp; + + /* seek to the channel offset and save the channel */ + xcf_check_error (xcf_seek_pos (info, offset, error)); + xcf_check_error (xcf_save_channel (info, image, channel, error)); + + /* the next channels's offset is after the channel we just wrote */ + offset = info->cp; + + xcf_progress_update (info); + } + + /* there is already a '0' at the end of the offset table to indicate + * the end of the channel offsets + */ + + g_list_free (all_layers); + g_list_free (all_channels); + + return ! g_output_stream_is_closed (info->output); +} + +static gboolean +xcf_save_image_props (XcfInfo *info, + GimpImage *image, + GError **error) +{ + GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image); + GimpParasite *grid_parasite = NULL; + GimpParasite *meta_parasite = NULL; + GList *symmetry_parasites = NULL; + GList *iter; + GimpUnit unit = gimp_image_get_unit (image); + gdouble xres; + gdouble yres; + + gimp_image_get_resolution (image, &xres, &yres); + + /* check and see if we should save the colormap property */ + if (gimp_image_get_colormap (image)) + xcf_check_error (xcf_save_prop (info, image, PROP_COLORMAP, error, + gimp_image_get_colormap_size (image), + gimp_image_get_colormap (image))); + + if (info->compression != COMPRESS_NONE) + xcf_check_error (xcf_save_prop (info, image, PROP_COMPRESSION, error, + info->compression)); + + if (gimp_image_get_guides (image)) + xcf_check_error (xcf_save_prop (info, image, PROP_GUIDES, error, + gimp_image_get_guides (image))); + + if (gimp_image_get_sample_points (image)) + { + /* 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, + gimp_image_get_sample_points (image))); + xcf_check_error (xcf_save_prop (info, image, PROP_OLD_SAMPLE_POINTS, error, + gimp_image_get_sample_points (image))); + } + + xcf_check_error (xcf_save_prop (info, image, PROP_RESOLUTION, error, + xres, yres)); + + xcf_check_error (xcf_save_prop (info, image, PROP_TATTOO, error, + gimp_image_get_tattoo_state (image))); + + if (unit < gimp_unit_get_number_of_built_in_units ()) + xcf_check_error (xcf_save_prop (info, image, PROP_UNIT, error, unit)); + + if (gimp_container_get_n_children (gimp_image_get_vectors (image)) > 0) + { + if (gimp_vectors_compat_is_compatible (image)) + xcf_check_error (xcf_save_prop (info, image, PROP_PATHS, error)); + else + xcf_check_error (xcf_save_prop (info, image, PROP_VECTORS, error)); + } + + if (unit >= gimp_unit_get_number_of_built_in_units ()) + xcf_check_error (xcf_save_prop (info, image, PROP_USER_UNIT, error, unit)); + + if (gimp_image_get_grid (image)) + { + GimpGrid *grid = gimp_image_get_grid (image); + + 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); + } + } + + 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); + } + } + + if (gimp_parasite_list_length (private->parasites) > 0) + { + xcf_check_error (xcf_save_prop (info, image, PROP_PARASITES, error, + private->parasites)); + } + + if (grid_parasite) + { + gimp_parasite_list_remove (private->parasites, + gimp_parasite_name (grid_parasite)); + gimp_parasite_free (grid_parasite); + } + + if (meta_parasite) + { + gimp_parasite_list_remove (private->parasites, + gimp_parasite_name (meta_parasite)); + gimp_parasite_free (meta_parasite); + } + + for (iter = symmetry_parasites; iter; iter = g_list_next (iter)) + { + GimpParasite *parasite = iter->data; + + gimp_parasite_list_remove (private->parasites, + gimp_parasite_name (parasite)); + } + g_list_free_full (symmetry_parasites, + (GDestroyNotify) gimp_parasite_free); + + xcf_check_error (xcf_save_prop (info, image, PROP_END, error)); + + return TRUE; +} + +static gboolean +xcf_save_layer_props (XcfInfo *info, + GimpImage *image, + GimpLayer *layer, + GError **error) +{ + GimpParasiteList *parasites; + gint offset_x; + gint offset_y; + + if (gimp_viewable_get_children (GIMP_VIEWABLE (layer))) + xcf_check_error (xcf_save_prop (info, image, PROP_GROUP_ITEM, error)); + + 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, + path)); + g_list_free (path); + } + + if (layer == gimp_image_get_active_layer (image)) + xcf_check_error (xcf_save_prop (info, image, PROP_ACTIVE_LAYER, error)); + + if (layer == gimp_image_get_floating_selection (image)) + { + info->floating_sel_drawable = gimp_layer_get_floating_sel_drawable (layer); + xcf_check_error (xcf_save_prop (info, image, PROP_FLOATING_SELECTION, + error)); + } + + xcf_check_error (xcf_save_prop (info, image, PROP_OPACITY, error, + gimp_layer_get_opacity (layer))); + xcf_check_error (xcf_save_prop (info, image, PROP_FLOAT_OPACITY, error, + gimp_layer_get_opacity (layer))); + xcf_check_error (xcf_save_prop (info, image, PROP_VISIBLE, error, + gimp_item_get_visible (GIMP_ITEM (layer)))); + xcf_check_error (xcf_save_prop (info, image, PROP_LINKED, error, + gimp_item_get_linked (GIMP_ITEM (layer)))); + xcf_check_error (xcf_save_prop (info, image, PROP_COLOR_TAG, error, + gimp_item_get_color_tag (GIMP_ITEM (layer)))); + xcf_check_error (xcf_save_prop (info, image, PROP_LOCK_CONTENT, error, + gimp_item_get_lock_content (GIMP_ITEM (layer)))); + xcf_check_error (xcf_save_prop (info, image, PROP_LOCK_ALPHA, error, + gimp_layer_get_lock_alpha (layer))); + xcf_check_error (xcf_save_prop (info, image, PROP_LOCK_POSITION, error, + gimp_item_get_lock_position (GIMP_ITEM (layer)))); + + if (gimp_layer_get_mask (layer)) + { + xcf_check_error (xcf_save_prop (info, image, PROP_APPLY_MASK, error, + gimp_layer_get_apply_mask (layer))); + xcf_check_error (xcf_save_prop (info, image, PROP_EDIT_MASK, error, + gimp_layer_get_edit_mask (layer))); + xcf_check_error (xcf_save_prop (info, image, PROP_SHOW_MASK, error, + gimp_layer_get_show_mask (layer))); + } + else + { + xcf_check_error (xcf_save_prop (info, image, PROP_APPLY_MASK, error, + FALSE)); + xcf_check_error (xcf_save_prop (info, image, PROP_EDIT_MASK, error, + FALSE)); + xcf_check_error (xcf_save_prop (info, image, PROP_SHOW_MASK, error, + FALSE)); + } + + gimp_item_get_offset (GIMP_ITEM (layer), &offset_x, &offset_y); + + xcf_check_error (xcf_save_prop (info, image, PROP_OFFSETS, error, + offset_x, offset_y)); + xcf_check_error (xcf_save_prop (info, image, PROP_MODE, error, + gimp_layer_get_mode (layer))); + xcf_check_error (xcf_save_prop (info, image, PROP_BLEND_SPACE, error, + gimp_layer_get_mode (layer), + gimp_layer_get_blend_space (layer))); + xcf_check_error (xcf_save_prop (info, image, PROP_COMPOSITE_SPACE, error, + gimp_layer_get_mode (layer), + gimp_layer_get_composite_space (layer))); + xcf_check_error (xcf_save_prop (info, image, PROP_COMPOSITE_MODE, error, + gimp_layer_get_mode (layer), + gimp_layer_get_composite_mode (layer))); + xcf_check_error (xcf_save_prop (info, image, PROP_TATTOO, error, + gimp_item_get_tattoo (GIMP_ITEM (layer)))); + + 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, + image, PROP_TEXT_LAYER_FLAGS, error, + flags)); + } + + if (gimp_viewable_get_children (GIMP_VIEWABLE (layer))) + { + gint32 flags = 0; + + if (gimp_viewable_get_expanded (GIMP_VIEWABLE (layer))) + flags |= XCF_GROUP_ITEM_EXPANDED; + + xcf_check_error (xcf_save_prop (info, + image, PROP_GROUP_ITEM_FLAGS, error, + flags)); + } + + parasites = gimp_item_get_parasites (GIMP_ITEM (layer)); + + if (gimp_parasite_list_length (parasites) > 0) + { + xcf_check_error (xcf_save_prop (info, image, PROP_PARASITES, error, + parasites)); + } + + xcf_check_error (xcf_save_prop (info, image, PROP_END, error)); + + return TRUE; +} + +static gboolean +xcf_save_channel_props (XcfInfo *info, + GimpImage *image, + GimpChannel *channel, + GError **error) +{ + GimpParasiteList *parasites; + + if (channel == gimp_image_get_active_channel (image)) + xcf_check_error (xcf_save_prop (info, image, PROP_ACTIVE_CHANNEL, error)); + + if (channel == gimp_image_get_mask (image)) + xcf_check_error (xcf_save_prop (info, image, PROP_SELECTION, error)); + + xcf_check_error (xcf_save_prop (info, image, PROP_OPACITY, error, + gimp_channel_get_opacity (channel))); + xcf_check_error (xcf_save_prop (info, image, PROP_FLOAT_OPACITY, error, + gimp_channel_get_opacity (channel))); + xcf_check_error (xcf_save_prop (info, image, PROP_VISIBLE, error, + gimp_item_get_visible (GIMP_ITEM (channel)))); + xcf_check_error (xcf_save_prop (info, image, PROP_LINKED, error, + gimp_item_get_linked (GIMP_ITEM (channel)))); + xcf_check_error (xcf_save_prop (info, image, PROP_COLOR_TAG, error, + gimp_item_get_color_tag (GIMP_ITEM (channel)))); + xcf_check_error (xcf_save_prop (info, image, PROP_LOCK_CONTENT, error, + gimp_item_get_lock_content (GIMP_ITEM (channel)))); + xcf_check_error (xcf_save_prop (info, image, PROP_LOCK_POSITION, error, + gimp_item_get_lock_position (GIMP_ITEM (channel)))); + xcf_check_error (xcf_save_prop (info, image, PROP_SHOW_MASKED, error, + gimp_channel_get_show_masked (channel))); + xcf_check_error (xcf_save_prop (info, image, PROP_COLOR, error, + &channel->color)); + xcf_check_error (xcf_save_prop (info, image, PROP_FLOAT_COLOR, error, + &channel->color)); + xcf_check_error (xcf_save_prop (info, image, PROP_TATTOO, error, + gimp_item_get_tattoo (GIMP_ITEM (channel)))); + + parasites = gimp_item_get_parasites (GIMP_ITEM (channel)); + + if (gimp_parasite_list_length (parasites) > 0) + { + xcf_check_error (xcf_save_prop (info, image, PROP_PARASITES, error, + parasites)); + } + + xcf_check_error (xcf_save_prop (info, image, PROP_END, error)); + + return TRUE; +} + +static gboolean +xcf_save_prop (XcfInfo *info, + GimpImage *image, + PropType prop_type, + GError **error, + ...) +{ + guint32 size; + va_list args; + GError *tmp_error = NULL; + + va_start (args, error); + + switch (prop_type) + { + case PROP_END: + size = 0; + + xcf_write_prop_type_check_error (info, prop_type); + xcf_write_int32_check_error (info, &size, 1); + break; + + case PROP_COLORMAP: + { + guint32 n_colors = va_arg (args, guint32); + guchar *colors = va_arg (args, guchar *); + + size = 4 + n_colors * 3; + + xcf_write_prop_type_check_error (info, prop_type); + xcf_write_int32_check_error (info, &size, 1); + + xcf_write_int32_check_error (info, &n_colors, 1); + xcf_write_int8_check_error (info, colors, n_colors * 3); + } + break; + + case PROP_ACTIVE_LAYER: + case PROP_ACTIVE_CHANNEL: + case PROP_SELECTION: + case PROP_GROUP_ITEM: + size = 0; + + xcf_write_prop_type_check_error (info, prop_type); + xcf_write_int32_check_error (info, &size, 1); + break; + + case PROP_FLOATING_SELECTION: + size = info->bytes_per_offset; + + xcf_write_prop_type_check_error (info, prop_type); + xcf_write_int32_check_error (info, &size, 1); + + info->floating_sel_offset = info->cp; + xcf_write_zero_offset_check_error (info, 1); + break; + + case PROP_OPACITY: + { + gdouble opacity = va_arg (args, gdouble); + guint32 uint_opacity = opacity * 255.999; + + size = 4; + + xcf_write_prop_type_check_error (info, prop_type); + xcf_write_int32_check_error (info, &size, 1); + + xcf_write_int32_check_error (info, &uint_opacity, 1); + } + break; + + case PROP_FLOAT_OPACITY: + { + gfloat opacity = va_arg (args, gdouble); + + size = 4; + + xcf_write_prop_type_check_error (info, prop_type); + xcf_write_int32_check_error (info, &size, 1); + + xcf_write_float_check_error (info, &opacity, 1); + } + break; + + case PROP_MODE: + { + gint32 mode = va_arg (args, gint32); + + size = 4; + + if (mode == GIMP_LAYER_MODE_OVERLAY_LEGACY) + mode = GIMP_LAYER_MODE_SOFTLIGHT_LEGACY; + + xcf_write_prop_type_check_error (info, prop_type); + xcf_write_int32_check_error (info, &size, 1); + + xcf_write_int32_check_error (info, (guint32 *) &mode, 1); + } + break; + + case PROP_BLEND_SPACE: + { + 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); + + size = 4; + + xcf_write_prop_type_check_error (info, prop_type); + xcf_write_int32_check_error (info, &size, 1); + + xcf_write_int32_check_error (info, (guint32 *) &blend_space, 1); + } + break; + + case PROP_COMPOSITE_SPACE: + { + 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); + + size = 4; + + xcf_write_prop_type_check_error (info, prop_type); + xcf_write_int32_check_error (info, &size, 1); + + xcf_write_int32_check_error (info, (guint32 *) &composite_space, 1); + } + break; + + case PROP_COMPOSITE_MODE: + { + 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); + + size = 4; + + xcf_write_prop_type_check_error (info, prop_type); + xcf_write_int32_check_error (info, &size, 1); + + xcf_write_int32_check_error (info, (guint32 *) &composite_mode, 1); + } + break; + + case PROP_VISIBLE: + { + guint32 visible = va_arg (args, guint32); + + size = 4; + + xcf_write_prop_type_check_error (info, prop_type); + xcf_write_int32_check_error (info, &size, 1); + + xcf_write_int32_check_error (info, &visible, 1); + } + break; + + case PROP_LINKED: + { + guint32 linked = va_arg (args, guint32); + + size = 4; + + xcf_write_prop_type_check_error (info, prop_type); + xcf_write_int32_check_error (info, &size, 1); + + xcf_write_int32_check_error (info, &linked, 1); + } + break; + + case PROP_COLOR_TAG: + { + guint32 color_tag = va_arg (args, gint32); + + size = 4; + + xcf_write_prop_type_check_error (info, prop_type); + xcf_write_int32_check_error (info, &size, 1); + + xcf_write_int32_check_error (info, &color_tag, 1); + } + break; + + case PROP_LOCK_CONTENT: + { + guint32 lock_content = va_arg (args, guint32); + + size = 4; + + xcf_write_prop_type_check_error (info, prop_type); + xcf_write_int32_check_error (info, &size, 1); + + xcf_write_int32_check_error (info, &lock_content, 1); + } + break; + + case PROP_LOCK_ALPHA: + { + guint32 lock_alpha = va_arg (args, guint32); + + size = 4; + + xcf_write_prop_type_check_error (info, prop_type); + xcf_write_int32_check_error (info, &size, 1); + + xcf_write_int32_check_error (info, &lock_alpha, 1); + } + break; + + case PROP_LOCK_POSITION: + { + guint32 lock_position = va_arg (args, guint32); + + size = 4; + + xcf_write_prop_type_check_error (info, prop_type); + xcf_write_int32_check_error (info, &size, 1); + + xcf_write_int32_check_error (info, &lock_position, 1); + } + break; + + case PROP_APPLY_MASK: + { + guint32 apply_mask = va_arg (args, guint32); + + size = 4; + + xcf_write_prop_type_check_error (info, prop_type); + xcf_write_int32_check_error (info, &size, 1); + + xcf_write_int32_check_error (info, &apply_mask, 1); + } + break; + + case PROP_EDIT_MASK: + { + guint32 edit_mask = va_arg (args, guint32); + + size = 4; + + xcf_write_prop_type_check_error (info, prop_type); + xcf_write_int32_check_error (info, &size, 1); + + xcf_write_int32_check_error (info, &edit_mask, 1); + } + break; + + case PROP_SHOW_MASK: + { + guint32 show_mask = va_arg (args, guint32); + + size = 4; + + xcf_write_prop_type_check_error (info, prop_type); + xcf_write_int32_check_error (info, &size, 1); + + xcf_write_int32_check_error (info, &show_mask, 1); + } + break; + + case PROP_SHOW_MASKED: + { + guint32 show_masked = va_arg (args, guint32); + + size = 4; + + xcf_write_prop_type_check_error (info, prop_type); + xcf_write_int32_check_error (info, &size, 1); + + xcf_write_int32_check_error (info, &show_masked, 1); + } + break; + + case PROP_OFFSETS: + { + gint32 offsets[2]; + + offsets[0] = va_arg (args, gint32); + offsets[1] = va_arg (args, gint32); + + size = 8; + + xcf_write_prop_type_check_error (info, prop_type); + xcf_write_int32_check_error (info, &size, 1); + + xcf_write_int32_check_error (info, (guint32 *) offsets, 2); + } + break; + + case PROP_COLOR: + { + GimpRGB *color = va_arg (args, GimpRGB *); + guchar col[3]; + + gimp_rgb_get_uchar (color, &col[0], &col[1], &col[2]); + + size = 3; + + xcf_write_prop_type_check_error (info, prop_type); + xcf_write_int32_check_error (info, &size, 1); + + xcf_write_int8_check_error (info, col, 3); + } + break; + + case PROP_FLOAT_COLOR: + { + GimpRGB *color = va_arg (args, GimpRGB *); + gfloat col[3]; + + col[0] = color->r; + col[1] = color->g; + col[2] = color->b; + + size = 3 * 4; + + xcf_write_prop_type_check_error (info, prop_type); + xcf_write_int32_check_error (info, &size, 1); + + xcf_write_float_check_error (info, col, 3); + } + break; + + case PROP_COMPRESSION: + { + guint8 compression = (guint8) va_arg (args, guint32); + + size = 1; + + xcf_write_prop_type_check_error (info, prop_type); + xcf_write_int32_check_error (info, &size, 1); + + xcf_write_int8_check_error (info, &compression, 1); + } + break; + + case PROP_GUIDES: + { + GList *guides = va_arg (args, GList *); + gint n_guides = g_list_length (guides); + GList *iter; + + 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--; + } + + if (n_guides > 0) + { + size = n_guides * (4 + 1); + + xcf_write_prop_type_check_error (info, prop_type); + xcf_write_int32_check_error (info, &size, 1); + + for (; guides; guides = g_list_next (guides)) + { + 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; + } + + xcf_write_int32_check_error (info, (guint32 *) &position, 1); + xcf_write_int8_check_error (info, (guint8 *) &orientation, 1); + } + } + } + break; + + case PROP_SAMPLE_POINTS: + { + GList *sample_points = va_arg (args, GList *); + gint n_sample_points = g_list_length (sample_points); + + size = n_sample_points * (5 * 4); + + xcf_write_prop_type_check_error (info, prop_type); + xcf_write_int32_check_error (info, &size, 1); + + 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); + + xcf_write_int32_check_error (info, (guint32 *) &x, 1); + xcf_write_int32_check_error (info, (guint32 *) &y, 1); + xcf_write_int32_check_error (info, (guint32 *) &pick_mode, 1); + xcf_write_int32_check_error (info, (guint32 *) padding, 2); + } + } + break; + + case PROP_OLD_SAMPLE_POINTS: + { + GList *sample_points = va_arg (args, GList *); + gint n_sample_points = g_list_length (sample_points); + + size = n_sample_points * (4 + 4); + + xcf_write_prop_type_check_error (info, prop_type); + xcf_write_int32_check_error (info, &size, 1); + + for (; sample_points; sample_points = g_list_next (sample_points)) + { + GimpSamplePoint *sample_point = sample_points->data; + gint32 x, y; + + gimp_sample_point_get_position (sample_point, &x, &y); + + xcf_write_int32_check_error (info, (guint32 *) &x, 1); + xcf_write_int32_check_error (info, (guint32 *) &y, 1); + } + } + break; + + case PROP_RESOLUTION: + { + gfloat resolution[2]; + + resolution[0] = va_arg (args, double); + resolution[1] = va_arg (args, double); + + size = 2 * 4; + + xcf_write_prop_type_check_error (info, prop_type); + xcf_write_int32_check_error (info, &size, 1); + + xcf_write_float_check_error (info, resolution, 2); + } + break; + + case PROP_TATTOO: + { + guint32 tattoo = va_arg (args, guint32); + + size = 4; + + xcf_write_prop_type_check_error (info, prop_type); + xcf_write_int32_check_error (info, &size, 1); + + xcf_write_int32_check_error (info, &tattoo, 1); + } + break; + + case PROP_PARASITES: + { + GimpParasiteList *list = va_arg (args, GimpParasiteList *); + + if (gimp_parasite_list_persistent_length (list) > 0) + { + goffset base; + goffset pos; + + size = 0; + + xcf_write_prop_type_check_error (info, prop_type); + + /* because we don't know how much room the parasite list + * will take we save the file position and write the + * length later + */ + pos = info->cp; + xcf_write_int32_check_error (info, &size, 1); + + base = info->cp; + + xcf_check_error (xcf_save_parasite_list (info, list, error)); + + size = info->cp - base; + + /* go back to the saved position and write the length */ + xcf_check_error (xcf_seek_pos (info, pos, error)); + xcf_write_int32_check_error (info, &size, 1); + + xcf_check_error (xcf_seek_pos (info, base + size, error)); + } + } + break; + + case PROP_UNIT: + { + guint32 unit = va_arg (args, guint32); + + size = 4; + + xcf_write_prop_type_check_error (info, prop_type); + xcf_write_int32_check_error (info, &size, 1); + + xcf_write_int32_check_error (info, &unit, 1); + } + break; + + case PROP_PATHS: + { + goffset base; + goffset pos; + + size = 0; + + xcf_write_prop_type_check_error (info, prop_type); + + /* because we don't know how much room the paths list will + * take we save the file position and write the length later + */ + pos = info->cp; + xcf_write_int32_check_error (info, &size, 1); + + base = info->cp; + + xcf_check_error (xcf_save_old_paths (info, image, error)); + + size = info->cp - base; + + /* go back to the saved position and write the length */ + xcf_check_error (xcf_seek_pos (info, pos, error)); + xcf_write_int32_check_error (info, &size, 1); + + xcf_check_error (xcf_seek_pos (info, base + size, error)); + } + break; + + case PROP_USER_UNIT: + { + GimpUnit unit = va_arg (args, guint32); + const gchar *unit_strings[5]; + gfloat factor; + guint32 digits; + + /* write the entire unit definition */ + unit_strings[0] = gimp_unit_get_identifier (unit); + 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); + unit_strings[3] = gimp_unit_get_singular (unit); + unit_strings[4] = gimp_unit_get_plural (unit); + + size = + 2 * 4 + + strlen (unit_strings[0]) ? strlen (unit_strings[0]) + 5 : 4 + + strlen (unit_strings[1]) ? strlen (unit_strings[1]) + 5 : 4 + + strlen (unit_strings[2]) ? strlen (unit_strings[2]) + 5 : 4 + + strlen (unit_strings[3]) ? strlen (unit_strings[3]) + 5 : 4 + + strlen (unit_strings[4]) ? strlen (unit_strings[4]) + 5 : 4; + + xcf_write_prop_type_check_error (info, prop_type); + xcf_write_int32_check_error (info, &size, 1); + + xcf_write_float_check_error (info, &factor, 1); + xcf_write_int32_check_error (info, &digits, 1); + xcf_write_string_check_error (info, (gchar **) unit_strings, 5); + } + break; + + case PROP_VECTORS: + { + goffset base; + goffset pos; + + size = 0; + + xcf_write_prop_type_check_error (info, prop_type); + + /* because we don't know how much room the paths list will + * take we save the file position and write the length later + */ + pos = info->cp; + xcf_write_int32_check_error (info, &size, 1); + + base = info->cp; + + xcf_check_error (xcf_save_vectors (info, image, error)); + + size = info->cp - base; + + /* go back to the saved position and write the length */ + xcf_check_error (xcf_seek_pos (info, pos, error)); + xcf_write_int32_check_error (info, &size, 1); + + xcf_check_error (xcf_seek_pos (info, base + size, error)); + } + break; + + case PROP_TEXT_LAYER_FLAGS: + { + guint32 flags = va_arg (args, guint32); + + size = 4; + + xcf_write_prop_type_check_error (info, prop_type); + xcf_write_int32_check_error (info, &size, 1); + + xcf_write_int32_check_error (info, &flags, 1); + } + break; + + case PROP_ITEM_PATH: + { + GList *path = va_arg (args, GList *); + + size = 4 * g_list_length (path); + + xcf_write_prop_type_check_error (info, prop_type); + xcf_write_int32_check_error (info, &size, 1); + + while (path) + { + guint32 index = GPOINTER_TO_UINT (path->data); + + xcf_write_int32_check_error (info, &index, 1); + + path = g_list_next (path); + } + } + break; + + case PROP_GROUP_ITEM_FLAGS: + { + guint32 flags = va_arg (args, guint32); + + size = 4; + + xcf_write_prop_type_check_error (info, prop_type); + xcf_write_int32_check_error (info, &size, 1); + xcf_write_int32_check_error (info, &flags, 1); + } + break; + } + + va_end (args); + + return TRUE; +} + +static gboolean +xcf_save_layer (XcfInfo *info, + GimpImage *image, + GimpLayer *layer, + GError **error) +{ + goffset saved_pos; + goffset offset; + guint32 value; + const gchar *string; + GError *tmp_error = NULL; + + /* check and see if this is the drawable that the floating + * selection is attached to. + */ + if (GIMP_DRAWABLE (layer) == info->floating_sel_drawable) + { + saved_pos = info->cp; + 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)); + } + + /* write out the width, height and image type information for the layer */ + value = gimp_item_get_width (GIMP_ITEM (layer)); + xcf_write_int32_check_error (info, &value, 1); + + value = gimp_item_get_height (GIMP_ITEM (layer)); + xcf_write_int32_check_error (info, &value, 1); + + value = gimp_babl_format_get_image_type (gimp_drawable_get_format (GIMP_DRAWABLE (layer))); + xcf_write_int32_check_error (info, &value, 1); + + /* write out the layers name */ + string = gimp_object_get_name (layer); + xcf_write_string_check_error (info, (gchar **) &string, 1); + + /* write out the layer properties */ + xcf_save_layer_props (info, image, layer, error); + + /* write out the layer tile hierarchy */ + offset = info->cp + 2 * info->bytes_per_offset; + xcf_write_offset_check_error (info, &offset, 1); + + saved_pos = info->cp; + + /* write a zero layer mask offset */ + xcf_write_zero_offset_check_error (info, 1); + + xcf_check_error (xcf_save_buffer (info, + gimp_drawable_get_buffer (GIMP_DRAWABLE (layer)), + error)); + + offset = info->cp; + + /* write out the layer mask */ + if (gimp_layer_get_mask (layer)) + { + GimpLayerMask *mask = gimp_layer_get_mask (layer); + + xcf_check_error (xcf_seek_pos (info, saved_pos, error)); + xcf_write_offset_check_error (info, &offset, 1); + + xcf_check_error (xcf_seek_pos (info, offset, error)); + xcf_check_error (xcf_save_channel (info, image, GIMP_CHANNEL (mask), + error)); + } + + return TRUE; +} + +static gboolean +xcf_save_channel (XcfInfo *info, + GimpImage *image, + GimpChannel *channel, + GError **error) +{ + goffset saved_pos; + goffset offset; + guint32 value; + const gchar *string; + GError *tmp_error = NULL; + + /* check and see if this is the drawable that the floating + * selection is attached to. + */ + if (GIMP_DRAWABLE (channel) == info->floating_sel_drawable) + { + saved_pos = info->cp; + 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)); + } + + /* write out the width and height information for the channel */ + value = gimp_item_get_width (GIMP_ITEM (channel)); + xcf_write_int32_check_error (info, &value, 1); + + value = gimp_item_get_height (GIMP_ITEM (channel)); + xcf_write_int32_check_error (info, &value, 1); + + /* write out the channels name */ + string = gimp_object_get_name (channel); + xcf_write_string_check_error (info, (gchar **) &string, 1); + + /* write out the channel properties */ + xcf_save_channel_props (info, image, channel, error); + + /* write out the channel tile hierarchy */ + offset = info->cp + info->bytes_per_offset; + xcf_write_offset_check_error (info, &offset, 1); + + xcf_check_error (xcf_save_buffer (info, + gimp_drawable_get_buffer (GIMP_DRAWABLE (channel)), + error)); + + return TRUE; +} + +static gint +xcf_calc_levels (gint size, + gint tile_size) +{ + gint levels; + + levels = 1; + while (size > tile_size) + { + size /= 2; + levels += 1; + } + + return levels; +} + + +static gboolean +xcf_save_buffer (XcfInfo *info, + GeglBuffer *buffer, + GError **error) +{ + const Babl *format; + goffset saved_pos; + goffset offset; + 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); + + 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); + + tmp1 = xcf_calc_levels (width, XCF_TILE_WIDTH); + tmp2 = xcf_calc_levels (height, XCF_TILE_HEIGHT); + nlevels = MAX (tmp1, tmp2); + + /* 'saved_pos' is the next slot in the offset table */ + saved_pos = info->cp; + + /* write an empty offset table */ + xcf_write_zero_offset_check_error (info, nlevels + 1); + + /* 'offset' is where we will write the next level */ + offset = info->cp; + + for (i = 0; i < nlevels; i++) + { + /* seek back to the next slot in the offset table and write the + * offset of the level + */ + xcf_check_error (xcf_seek_pos (info, saved_pos, error)); + xcf_write_offset_check_error (info, &offset, 1); + + /* remember the next slot in the offset table */ + saved_pos = info->cp; + + /* seek to the level offset and save the level */ + xcf_check_error (xcf_seek_pos (info, offset, error)); + + if (i == 0) + { + /* write out the level. */ + xcf_check_error (xcf_save_level (info, buffer, error)); + } + else + { + /* fake an empty level */ + tmp1 = 0; + width /= 2; + height /= 2; + xcf_write_int32_check_error (info, (guint32 *) &width, 1); + xcf_write_int32_check_error (info, (guint32 *) &height, 1); + + /* 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. + */ + xcf_write_int32_check_error (info, (guint32 *) &tmp1, 1); + } + + /* the next level's offset if after the level we just wrote */ + offset = info->cp; + } + + /* there is already a '0' at the end of the offset table to indicate + * the end of the level offsets + */ + + return TRUE; +} + +static gboolean +xcf_save_level (XcfInfo *info, + GeglBuffer *buffer, + GError **error) +{ + const Babl *format; + goffset *offset_table; + goffset *next_offset; + goffset saved_pos; + goffset offset; + goffset max_data_length; + guint32 width; + guint32 height; + gint bpp; + gint n_tile_rows; + gint n_tile_cols; + guint ntiles; + gint i; + guchar *rlebuf = NULL; + 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); + + xcf_write_int32_check_error (info, (guint32 *) &width, 1); + xcf_write_int32_check_error (info, (guint32 *) &height, 1); + + saved_pos = info->cp; + + /* 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 */; + + /* allocate a temporary buffer to store the rle data before it is + * written to disk + */ + if (info->compression == COMPRESS_RLE) + rlebuf = g_alloca (max_data_length); + + 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; + + /* 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. + */ + offset_table = g_alloca ((ntiles + 1) * sizeof (goffset)); + memset (offset_table, 0, (ntiles + 1) * sizeof (goffset)); + next_offset = offset_table; + + /* 'saved_pos' is the offset of the tile offset table */ + saved_pos = info->cp; + + /* write an empty offset table */ + xcf_write_zero_offset_check_error (info, ntiles + 1); + + /* 'offset' is where we will write the next tile */ + offset = info->cp; + + for (i = 0; i < ntiles; i++) + { + GeglRectangle rect; + + /* store the offset in the table and increment the next pointer */ + *next_offset++ = offset; + + gimp_gegl_buffer_get_tile_rect (buffer, + XCF_TILE_WIDTH, XCF_TILE_HEIGHT, + i, &rect); + + /* write out the tile. */ + switch (info->compression) + { + case COMPRESS_NONE: + xcf_check_error (xcf_save_tile (info, buffer, &rect, format, + error)); + break; + case COMPRESS_RLE: + xcf_check_error (xcf_save_tile_rle (info, buffer, &rect, format, + rlebuf, error)); + break; + case COMPRESS_ZLIB: + xcf_check_error (xcf_save_tile_zlib (info, buffer, &rect, format, + error)); + break; + case COMPRESS_FRACTAL: + g_warning ("xcf: fractal compression unimplemented"); + return FALSE; + } + + /* 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); + return FALSE; + } + + /* the next tile's offset is after the tile we just wrote */ + offset = info->cp; + } + + /* seek back to the offset table and write it */ + xcf_check_error (xcf_seek_pos (info, saved_pos, error)); + xcf_write_offset_check_error (info, offset_table, ntiles + 1); + + /* seek to the end of the file */ + xcf_check_error (xcf_seek_pos (info, offset, error)); + + return TRUE; +} + +static gboolean +xcf_save_tile (XcfInfo *info, + GeglBuffer *buffer, + GeglRectangle *tile_rect, + const Babl *format, + GError **error) +{ + 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; + + gegl_buffer_get (buffer, tile_rect, 1.0, format, tile_data, + GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); + + if (info->file_version <= 11) + { + xcf_write_int8_check_error (info, tile_data, tile_size); + } + else + { + gint n_components = babl_format_get_n_components (format); + + xcf_write_component_check_error (info, bpp / n_components, tile_data, + tile_size / bpp * n_components); + } + + return TRUE; +} + +static gboolean +xcf_save_tile_rle (XcfInfo *info, + GeglBuffer *buffer, + GeglRectangle *tile_rect, + const Babl *format, + guchar *rlebuf, + GError **error) +{ + 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); + gint len = 0; + gint i, j; + GError *tmp_error = NULL; + + gegl_buffer_get (buffer, tile_rect, 1.0, format, tile_data, + GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); + + if (info->file_version >= 12) + { + gint n_components = babl_format_get_n_components (format); + + xcf_write_to_be (bpp / n_components, tile_data, + tile_size / bpp * n_components); + } + + for (i = 0; i < bpp; i++) + { + 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; + + while (size > 0) + { + 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; + + if (length >= 128) + { + rlebuf[len++] = 127; + rlebuf[len++] = (length >> 8); + rlebuf[len++] = length & 0x00FF; + rlebuf[len++] = last; + } + else + { + rlebuf[len++] = length - 1; + rlebuf[len++] = last; + } + + size -= length; + length = 0; + } + else if ((length == 1) && (last != *data)) + { + state = 1; + } + 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]))) + { + const guchar *t; + + /* if we came here because of a new run, backup one */ + if (!((length == 32768) || ((size - length) == 0))) + { + length--; + data -= bpp; + } + + count += length; + state = 0; + + if (length >= 128) + { + rlebuf[len++] = 255 - 127; + rlebuf[len++] = (length >> 8); + rlebuf[len++] = length & 0x00FF; + } + else + { + rlebuf[len++] = 255 - (length - 1); + } + + t = data - length * bpp; + + for (j = 0; j < length; j++) + { + rlebuf[len++] = *t; + t += bpp; + } + + size -= length; + length = 0; + } + break; + } + + if (size > 0) + { + length += 1; + last = *data; + data += bpp; + } + } + + if (count != (tile_rect->width * tile_rect->height)) + g_message ("xcf: uh oh! xcf rle tile saving error: %d", count); + } + + xcf_write_int8_check_error (info, rlebuf, len); + + return TRUE; +} + +static gboolean +xcf_save_tile_zlib (XcfInfo *info, + GeglBuffer *buffer, + GeglRectangle *tile_rect, + const Babl *format, + GError **error) +{ + 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); + /* The buffer for compressed data. */ + guchar *buf = g_alloca (tile_size); + GError *tmp_error = NULL; + z_stream strm; + int action; + int status; + + gegl_buffer_get (buffer, tile_rect, 1.0, format, tile_data, + GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); + + if (info->file_version >= 12) + { + gint n_components = babl_format_get_n_components (format); + + xcf_write_to_be (bpp / n_components, tile_data, + tile_size / bpp * n_components); + } + + /* 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) + return FALSE; + + strm.next_in = tile_data; + strm.avail_in = tile_size; + strm.next_out = buf; + strm.avail_out = tile_size; + + 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) + { + size_t write_size = tile_size - strm.avail_out; + + xcf_write_int8_check_error (info, buf, write_size); + + /* Reset next_out and avail_out. */ + strm.next_out = buf; + strm.avail_out = tile_size; + } + else if (status != Z_OK) + { + g_printerr ("xcf: tile compression failed: %s", zError (status)); + deflateEnd (&strm); + return FALSE; + } + } + + deflateEnd (&strm); + return TRUE; +} + +static gboolean +xcf_save_parasite (XcfInfo *info, + GimpParasite *parasite, + GError **error) +{ + if (gimp_parasite_is_persistent (parasite)) + { + guint32 value; + const gchar *string; + GError *tmp_error = NULL; + + string = gimp_parasite_name (parasite); + xcf_write_string_check_error (info, (gchar **) &string, 1); + + value = gimp_parasite_flags (parasite); + xcf_write_int32_check_error (info, &value, 1); + + value = gimp_parasite_data_size (parasite); + xcf_write_int32_check_error (info, &value, 1); + + xcf_write_int8_check_error (info, + gimp_parasite_data (parasite), + gimp_parasite_data_size (parasite)); + } + + return TRUE; +} + +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); +} + +static gboolean +xcf_save_parasite_list (XcfInfo *info, + GimpParasiteList *list, + GError **error) +{ + XcfParasiteData data; + + data.info = info; + data.error = NULL; + + gimp_parasite_list_foreach (list, (GHFunc) xcf_save_parasite_func, &data); + + if (data.error) + { + g_propagate_error (error, data.error); + return FALSE; + } + + return TRUE; +} + +static gboolean +xcf_save_old_paths (XcfInfo *info, + GimpImage *image, + GError **error) +{ + GimpVectors *active_vectors; + guint32 num_paths; + guint32 active_index = 0; + GList *list; + GError *tmp_error = NULL; + + /* Write out the following:- + * + * last_selected_row (gint) + * number_of_paths (gint) + * + * then each path:- + */ + + num_paths = gimp_container_get_n_children (gimp_image_get_vectors (image)); + + active_vectors = gimp_image_get_active_vectors (image); + + if (active_vectors) + active_index = gimp_container_get_child_index (gimp_image_get_vectors (image), + GIMP_OBJECT (active_vectors)); + + xcf_write_int32_check_error (info, &active_index, 1); + xcf_write_int32_check_error (info, &num_paths, 1); + + for (list = gimp_image_get_vectors_iter (image); + list; + list = g_list_next (list)) + { + GimpVectors *vectors = list->data; + gchar *name; + guint32 locked; + guint8 state; + guint32 version; + guint32 pathtype; + guint32 tattoo; + GimpVectorsCompatPoint *points; + guint32 num_points; + guint32 closed; + gint i; + + /* + * name (string) + * locked (gint) + * state (gchar) + * closed (gint) + * number points (gint) + * version (gint) + * pathtype (gint) + * tattoo (gint) + * then each point. + */ + + points = gimp_vectors_compat_get_points (vectors, + (gint32 *) &num_points, + (gint32 *) &closed); + + /* if no points are generated because of a faulty path we should + * skip saving the path - this is unfortunately impossible, because + * we already saved the number of paths and I won't start seeking + * around to fix that cruft */ + + name = (gchar *) gimp_object_get_name (vectors); + locked = gimp_item_get_linked (GIMP_ITEM (vectors)); + state = closed ? 4 : 2; /* EDIT : ADD (editing state, 1.2 compat) */ + version = 3; + pathtype = 1; /* BEZIER (1.2 compat) */ + tattoo = gimp_item_get_tattoo (GIMP_ITEM (vectors)); + + 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); + + for (i = 0; i < num_points; i++) + { + gfloat x; + gfloat y; + + x = points[i].x; + y = points[i].y; + + /* + * type (gint) + * x (gfloat) + * y (gfloat) + */ + + 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); + } + + g_free (points); + } + + return TRUE; +} + +static gboolean +xcf_save_vectors (XcfInfo *info, + GimpImage *image, + GError **error) +{ + GimpVectors *active_vectors; + guint32 version = 1; + guint32 active_index = 0; + guint32 num_paths; + GList *list; + GList *stroke_list; + GError *tmp_error = NULL; + + /* Write out the following:- + * + * version (gint) + * active_index (gint) + * num_paths (gint) + * + * then each path:- + */ + + active_vectors = gimp_image_get_active_vectors (image); + + if (active_vectors) + active_index = gimp_container_get_child_index (gimp_image_get_vectors (image), + GIMP_OBJECT (active_vectors)); + + num_paths = gimp_container_get_n_children (gimp_image_get_vectors (image)); + + 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); + + for (list = gimp_image_get_vectors_iter (image); + list; + list = g_list_next (list)) + { + GimpVectors *vectors = list->data; + GimpParasiteList *parasites; + const gchar *name; + guint32 tattoo; + guint32 visible; + guint32 linked; + guint32 num_parasites; + guint32 num_strokes; + + /* + * name (string) + * tattoo (gint) + * visible (gint) + * linked (gint) + * num_parasites (gint) + * num_strokes (gint) + * + * then each parasite + * then each stroke + */ + + name = gimp_object_get_name (vectors); + visible = gimp_item_get_visible (GIMP_ITEM (vectors)); + linked = gimp_item_get_linked (GIMP_ITEM (vectors)); + tattoo = gimp_item_get_tattoo (GIMP_ITEM (vectors)); + parasites = gimp_item_get_parasites (GIMP_ITEM (vectors)); + num_parasites = gimp_parasite_list_persistent_length (parasites); + num_strokes = g_queue_get_length (vectors->strokes); + + 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); + + xcf_check_error (xcf_save_parasite_list (info, parasites, error)); + + for (stroke_list = g_list_first (vectors->strokes->head); + stroke_list; + stroke_list = g_list_next (stroke_list)) + { + GimpStroke *stroke = stroke_list->data; + guint32 stroke_type; + guint32 closed; + guint32 num_axes; + GArray *control_points; + gint i; + + guint32 type; + gfloat coords[6]; + + /* + * stroke_type (gint) + * closed (gint) + * num_axes (gint) + * num_control_points (gint) + * + * then each control point. + */ + + if (GIMP_IS_BEZIER_STROKE (stroke)) + { + stroke_type = XCF_STROKETYPE_BEZIER_STROKE; + num_axes = 2; /* hardcoded, might be increased later */ + } + else + { + g_printerr ("Skipping unknown stroke type!\n"); + continue; + } + + control_points = gimp_stroke_control_points_get (stroke, + (gint32 *) &closed); + + 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); + + for (i = 0; i < control_points->len; i++) + { + GimpAnchor *anchor; + + anchor = & (g_array_index (control_points, GimpAnchor, i)); + + type = anchor->type; + coords[0] = anchor->position.x; + coords[1] = anchor->position.y; + coords[2] = anchor->position.pressure; + coords[3] = anchor->position.xtilt; + coords[4] = anchor->position.ytilt; + coords[5] = anchor->position.wheel; + + /* + * type (gint) + * + * the first num_axis elements of: + * [0] x (gfloat) + * [1] y (gfloat) + * [2] pressure (gfloat) + * [3] xtilt (gfloat) + * [4] ytilt (gfloat) + * [5] wheel (gfloat) + */ + + xcf_write_int32_check_error (info, &type, 1); + xcf_write_float_check_error (info, coords, num_axes); + } + + g_array_free (control_points, TRUE); + } + } + + return TRUE; +} |