/* 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 . */ #include "config.h" #include #include #include #include #include #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. * Do not use g_alloca since it may cause Stack Overflow on * large images, see issue #6138. */ offset_table = g_malloc0 ((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"); g_free (offset_table); 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); g_free (offset_table); 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)); g_free (offset_table); 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; }