/* 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 "config/gimpcoreconfig.h" #include "gegl/gimp-gegl-tile-compat.h" #include "core/gimp.h" #include "core/gimpcontainer.h" #include "core/gimpdrawable-private.h" /* eek */ #include "core/gimpgrid.h" #include "core/gimpgrouplayer.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-undo.h" #include "core/gimpitemstack.h" #include "core/gimplayer-floating-sel.h" #include "core/gimplayer-new.h" #include "core/gimplayermask.h" #include "core/gimpparasitelist.h" #include "core/gimpprogress.h" #include "core/gimpselection.h" #include "core/gimptemplate.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-load.h" #include "xcf-read.h" #include "xcf-seek.h" #include "gimp-log.h" #include "gimp-intl.h" #define MAX_XCF_PARASITE_DATA_LEN (256L * 1024 * 1024) /* #define GIMP_XCF_PATH_DEBUG */ static void xcf_load_add_masks (GimpImage *image); static gboolean xcf_load_image_props (XcfInfo *info, GimpImage *image); static gboolean xcf_load_layer_props (XcfInfo *info, GimpImage *image, GimpLayer **layer, GList **item_path, gboolean *apply_mask, gboolean *edit_mask, gboolean *show_mask, guint32 *text_layer_flags, guint32 *group_layer_flags); static gboolean xcf_load_channel_props (XcfInfo *info, GimpImage *image, GimpChannel **channel); static gboolean xcf_load_prop (XcfInfo *info, PropType *prop_type, guint32 *prop_size); static GimpLayer * xcf_load_layer (XcfInfo *info, GimpImage *image, GList **item_path); static GimpChannel * xcf_load_channel (XcfInfo *info, GimpImage *image); static GimpLayerMask * xcf_load_layer_mask (XcfInfo *info, GimpImage *image); static gboolean xcf_load_buffer (XcfInfo *info, GeglBuffer *buffer); static gboolean xcf_load_level (XcfInfo *info, GeglBuffer *buffer); static gboolean xcf_load_tile (XcfInfo *info, GeglBuffer *buffer, GeglRectangle *tile_rect, const Babl *format); static gboolean xcf_load_tile_rle (XcfInfo *info, GeglBuffer *buffer, GeglRectangle *tile_rect, const Babl *format, gint data_length); static gboolean xcf_load_tile_zlib (XcfInfo *info, GeglBuffer *buffer, GeglRectangle *tile_rect, const Babl *format, gint data_length); static GimpParasite * xcf_load_parasite (XcfInfo *info); static gboolean xcf_load_old_paths (XcfInfo *info, GimpImage *image); static gboolean xcf_load_old_path (XcfInfo *info, GimpImage *image); static gboolean xcf_load_vectors (XcfInfo *info, GimpImage *image); static gboolean xcf_load_vector (XcfInfo *info, GimpImage *image); static gboolean xcf_skip_unknown_prop (XcfInfo *info, gsize size); #define xcf_progress_update(info) G_STMT_START \ { \ if (info->progress) \ gimp_progress_pulse (info->progress); \ } G_STMT_END GimpImage * xcf_load_image (Gimp *gimp, XcfInfo *info, GError **error) { GimpImage *image = NULL; const GimpParasite *parasite; gboolean has_metadata = FALSE; guint32 saved_pos; guint32 offset; gint width; gint height; gint image_type; GimpPrecision precision = GIMP_PRECISION_U8_GAMMA; gint num_successful_elements = 0; /* read in the image width, height and type */ info->cp += xcf_read_int32 (info->input, (guint32 *) &width, 1); info->cp += xcf_read_int32 (info->input, (guint32 *) &height, 1); info->cp += xcf_read_int32 (info->input, (guint32 *) &image_type, 1); if (image_type < GIMP_RGB || image_type > GIMP_INDEXED || width <= 0 || height <= 0) goto hard_error; if (info->file_version >= 4) { gint p; info->cp += xcf_read_int32 (info->input, (guint32 *) &p, 1); if (info->file_version == 4) { switch (p) { case 0: precision = GIMP_PRECISION_U8_GAMMA; break; case 1: precision = GIMP_PRECISION_U16_GAMMA; break; case 2: precision = GIMP_PRECISION_U32_LINEAR; break; case 3: precision = GIMP_PRECISION_HALF_LINEAR; break; case 4: precision = GIMP_PRECISION_FLOAT_LINEAR; break; default: goto hard_error; } } else if (info->file_version == 5 || info->file_version == 6) { switch (p) { case 100: precision = GIMP_PRECISION_U8_LINEAR; break; case 150: precision = GIMP_PRECISION_U8_GAMMA; break; case 200: precision = GIMP_PRECISION_U16_LINEAR; break; case 250: precision = GIMP_PRECISION_U16_GAMMA; break; case 300: precision = GIMP_PRECISION_U32_LINEAR; break; case 350: precision = GIMP_PRECISION_U32_GAMMA; break; case 400: precision = GIMP_PRECISION_HALF_LINEAR; break; case 450: precision = GIMP_PRECISION_HALF_GAMMA; break; case 500: precision = GIMP_PRECISION_FLOAT_LINEAR; break; case 550: precision = GIMP_PRECISION_FLOAT_GAMMA; break; default: goto hard_error; } } else { precision = p; } } GIMP_LOG (XCF, "version=%d, width=%d, height=%d, image_type=%d, precision=%d", info->file_version, width, height, image_type, precision); image = gimp_create_image (gimp, width, height, image_type, precision, FALSE); gimp_image_undo_disable (image); xcf_progress_update (info); /* read the image properties */ if (! xcf_load_image_props (info, image)) goto hard_error; GIMP_LOG (XCF, "image props loaded"); /* check for a GimpGrid parasite */ parasite = gimp_image_parasite_find (GIMP_IMAGE (image), gimp_grid_parasite_name ()); if (parasite) { GimpGrid *grid = gimp_grid_from_parasite (parasite); if (grid) { GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image); gimp_parasite_list_remove (private->parasites, gimp_parasite_name (parasite)); gimp_image_set_grid (GIMP_IMAGE (image), grid, FALSE); g_object_unref (grid); } } /* check for a metadata parasite */ parasite = gimp_image_parasite_find (GIMP_IMAGE (image), "gimp-image-metadata"); if (parasite) { GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image); GimpMetadata *metadata; const gchar *meta_string; meta_string = (gchar *) gimp_parasite_data (parasite); metadata = gimp_metadata_deserialize (meta_string); if (metadata) { has_metadata = TRUE; gimp_image_set_metadata (image, metadata, FALSE); g_object_unref (metadata); } gimp_parasite_list_remove (private->parasites, gimp_parasite_name (parasite)); } /* migrate the old "exif-data" parasite */ parasite = gimp_image_parasite_find (GIMP_IMAGE (image), "exif-data"); if (parasite) { GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image); if (has_metadata) { g_printerr ("xcf-load: inconsistent metadata discovered: XCF file " "has both 'gimp-image-metadata' and 'exif-data' " "parasites, dropping old 'exif-data'\n"); } else { GimpMetadata *metadata = gimp_image_get_metadata (image); GError *my_error = NULL; if (metadata) g_object_ref (metadata); else metadata = gimp_metadata_new (); if (! gimp_metadata_set_from_exif (metadata, gimp_parasite_data (parasite), gimp_parasite_data_size (parasite), &my_error)) { gimp_message (gimp, G_OBJECT (info->progress), GIMP_MESSAGE_WARNING, _("Corrupt 'exif-data' parasite discovered.\n" "Exif data could not be migrated: %s"), my_error->message); g_clear_error (&my_error); } else { gimp_image_set_metadata (image, metadata, FALSE); } g_object_unref (metadata); } gimp_parasite_list_remove (private->parasites, gimp_parasite_name (parasite)); } /* migrate the old "gimp-metadata" parasite */ parasite = gimp_image_parasite_find (GIMP_IMAGE (image), "gimp-metadata"); if (parasite) { GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image); if (has_metadata) { g_printerr ("xcf-load: inconsistent metadata discovered: XCF file " "has both 'gimp-image-metadata' and 'gimp-metadata' " "parasites, dropping old 'gimp-metadata'\n"); } else { GimpMetadata *metadata = gimp_image_get_metadata (image); GError *my_error = NULL; if (metadata) g_object_ref (metadata); else metadata = gimp_metadata_new (); if (! gimp_metadata_set_from_xmp (metadata, gimp_parasite_data (parasite), gimp_parasite_data_size (parasite), &my_error)) { gimp_message (gimp, G_OBJECT (info->progress), GIMP_MESSAGE_WARNING, _("Corrupt 'gimp-metadata' parasite discovered.\n" "XMP data could not be migrated: %s"), my_error->message); g_clear_error (&my_error); } else { gimp_image_set_metadata (image, metadata, FALSE); } g_object_unref (metadata); } gimp_parasite_list_remove (private->parasites, gimp_parasite_name (parasite)); } /* check for a gimp-xcf-compatibility-mode parasite */ parasite = gimp_image_parasite_find (GIMP_IMAGE (image), "gimp-xcf-compatibility-mode"); if (parasite) { GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image); gimp_image_set_xcf_compat_mode (image, TRUE); gimp_parasite_list_remove (private->parasites, gimp_parasite_name (parasite)); } xcf_progress_update (info); while (TRUE) { GimpLayer *layer; GList *item_path = NULL; /* read in the offset of the next layer */ info->cp += xcf_read_int32 (info->input, &offset, 1); /* if the offset is 0 then we are at the end * of the layer list. */ if (offset == 0) break; /* save the current position as it is where the * next layer offset is stored. */ saved_pos = info->cp; /* seek to the layer offset */ if (! xcf_seek_pos (info, offset, NULL)) goto error; /* read in the layer */ layer = xcf_load_layer (info, image, &item_path); if (!layer) goto error; num_successful_elements++; xcf_progress_update (info); /* add the layer to the image if its not the floating selection */ if (layer != info->floating_sel) { GimpContainer *layers = gimp_image_get_layers (image); GimpContainer *container; GimpLayer *parent; if (item_path) { if (info->floating_sel) { /* there is a floating selection, but it will get * added after all layers are loaded, so toplevel * layer indices are off-by-one. Adjust item paths * accordingly: */ gint toplevel_index; toplevel_index = GPOINTER_TO_UINT (item_path->data); toplevel_index--; item_path->data = GUINT_TO_POINTER (toplevel_index); } parent = GIMP_LAYER (gimp_item_stack_get_parent_by_path (GIMP_ITEM_STACK (layers), item_path, NULL)); container = gimp_viewable_get_children (GIMP_VIEWABLE (parent)); g_list_free (item_path); } else { parent = NULL; container = layers; } gimp_image_add_layer (image, layer, parent, gimp_container_get_n_children (container), FALSE); } /* restore the saved position so we'll be ready to * read the next offset. */ if (! xcf_seek_pos (info, saved_pos, NULL)) goto error; } while (TRUE) { GimpChannel *channel; /* read in the offset of the next channel */ info->cp += xcf_read_int32 (info->input, &offset, 1); /* if the offset is 0 then we are at the end * of the channel list. */ if (offset == 0) break; /* save the current position as it is where the * next channel offset is stored. */ saved_pos = info->cp; /* seek to the channel offset */ if (! xcf_seek_pos (info, offset, NULL)) goto error; /* read in the channel */ channel = xcf_load_channel (info, image); if (!channel) goto error; num_successful_elements++; xcf_progress_update (info); /* add the channel to the image if its not the selection */ if (channel != gimp_image_get_mask (image)) gimp_image_add_channel (image, channel, NULL, /* FIXME tree */ gimp_container_get_n_children (gimp_image_get_channels (image)), FALSE); /* restore the saved position so we'll be ready to * read the next offset. */ if (! xcf_seek_pos (info, saved_pos, NULL)) goto error; } xcf_load_add_masks (image); if (info->floating_sel && info->floating_sel_drawable) floating_sel_attach (info->floating_sel, info->floating_sel_drawable); if (info->active_layer) gimp_image_set_active_layer (image, info->active_layer); if (info->active_channel) gimp_image_set_active_channel (image, info->active_channel); gimp_image_set_file (image, info->file); if (info->tattoo_state > 0) gimp_image_set_tattoo_state (image, info->tattoo_state); gimp_image_undo_enable (image); return image; error: if (num_successful_elements == 0) goto hard_error; gimp_message_literal (gimp, G_OBJECT (info->progress), GIMP_MESSAGE_WARNING, _("This XCF file is corrupt! I have loaded as much " "of it as I can, but it is incomplete.")); xcf_load_add_masks (image); gimp_image_undo_enable (image); return image; hard_error: g_set_error_literal (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, _("This XCF file is corrupt! I could not even " "salvage any partial image data from it.")); if (image) g_object_unref (image); return NULL; } static void xcf_load_add_masks (GimpImage *image) { GList *layers; GList *list; layers = gimp_image_get_layer_list (image); for (list = layers; list; list = g_list_next (list)) { GimpLayer *layer = list->data; GimpLayerMask *mask; mask = g_object_get_data (G_OBJECT (layer), "gimp-layer-mask"); if (mask) { gboolean apply_mask; gboolean edit_mask; gboolean show_mask; apply_mask = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (layer), "gimp-layer-mask-apply")); edit_mask = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (layer), "gimp-layer-mask-edit")); show_mask = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (layer), "gimp-layer-mask-show")); gimp_layer_add_mask (layer, mask, FALSE, NULL); gimp_layer_set_apply_mask (layer, apply_mask, FALSE); gimp_layer_set_edit_mask (layer, edit_mask); gimp_layer_set_show_mask (layer, show_mask, FALSE); g_object_set_data (G_OBJECT (layer), "gimp-layer-mask", NULL); g_object_set_data (G_OBJECT (layer), "gimp-layer-mask-apply", NULL); g_object_set_data (G_OBJECT (layer), "gimp-layer-mask-edit", NULL); g_object_set_data (G_OBJECT (layer), "gimp-layer-mask-show", NULL); } } g_list_free (layers); } static gboolean xcf_load_image_props (XcfInfo *info, GimpImage *image) { PropType prop_type; guint32 prop_size; while (TRUE) { if (! xcf_load_prop (info, &prop_type, &prop_size)) return FALSE; switch (prop_type) { case PROP_END: return TRUE; case PROP_COLORMAP: { guint32 n_colors; guchar cmap[GIMP_IMAGE_COLORMAP_SIZE]; info->cp += xcf_read_int32 (info->input, &n_colors, 1); if (n_colors > (GIMP_IMAGE_COLORMAP_SIZE / 3)) { gimp_message (info->gimp, G_OBJECT (info->progress), GIMP_MESSAGE_ERROR, "Maximum colormap size (%d) exceeded", GIMP_IMAGE_COLORMAP_SIZE); return FALSE; } if (info->file_version == 0) { gint i; gimp_message_literal (info->gimp, G_OBJECT (info->progress), GIMP_MESSAGE_WARNING, _("XCF warning: version 0 of XCF file format\n" "did not save indexed colormaps correctly.\n" "Substituting grayscale map.")); if (! xcf_seek_pos (info, info->cp + n_colors, NULL)) return FALSE; for (i = 0; i < n_colors; i++) { cmap[i * 3 + 0] = i; cmap[i * 3 + 1] = i; cmap[i * 3 + 2] = i; } } else { info->cp += xcf_read_int8 (info->input, cmap, n_colors * 3); } /* only set color map if image is indexed, this is just * sanity checking to make sure gimp doesn't end up with * an image state that is impossible. */ if (gimp_image_get_base_type (image) == GIMP_INDEXED) gimp_image_set_colormap (image, cmap, n_colors, FALSE); GIMP_LOG (XCF, "prop colormap n_colors=%d", n_colors); } break; case PROP_COMPRESSION: { guint8 compression; info->cp += xcf_read_int8 (info->input, (guint8 *) &compression, 1); if ((compression != COMPRESS_NONE) && (compression != COMPRESS_RLE) && (compression != COMPRESS_ZLIB) && (compression != COMPRESS_FRACTAL)) { gimp_message (info->gimp, G_OBJECT (info->progress), GIMP_MESSAGE_ERROR, "Unknown compression type: %d", (gint) compression); return FALSE; } info->compression = compression; GIMP_LOG (XCF, "prop compression=%d", compression); } break; case PROP_GUIDES: { GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image); gint32 position; gint8 orientation; gint i, nguides; nguides = prop_size / (4 + 1); for (i = 0; i < nguides; i++) { info->cp += xcf_read_int32 (info->input, (guint32 *) &position, 1); info->cp += xcf_read_int8 (info->input, (guint8 *) &orientation, 1); /* skip -1 guides from old XCFs */ if (position < 0) continue; GIMP_LOG (XCF, "prop guide orientation=%d position=%d", orientation, position); switch (orientation) { case XCF_ORIENTATION_HORIZONTAL: gimp_image_add_hguide (image, position, FALSE); break; case XCF_ORIENTATION_VERTICAL: gimp_image_add_vguide (image, position, FALSE); break; default: gimp_message_literal (info->gimp, G_OBJECT (info->progress), GIMP_MESSAGE_WARNING, "Guide orientation out of range in XCF file"); continue; } } /* this is silly as the order of guides doesn't really matter, * but it restores the list to its original order, which * cannot be wrong --Mitch */ private->guides = g_list_reverse (private->guides); } break; case PROP_SAMPLE_POINTS: { gint32 x, y; gint i, n_sample_points; n_sample_points = prop_size / (4 + 4); for (i = 0; i < n_sample_points; i++) { info->cp += xcf_read_int32 (info->input, (guint32 *) &x, 1); info->cp += xcf_read_int32 (info->input, (guint32 *) &y, 1); GIMP_LOG (XCF, "prop sample point x=%d y=%d", x, y); gimp_image_add_sample_point_at_pos (image, x, y, FALSE); } } break; case PROP_RESOLUTION: { gfloat xres, yres; info->cp += xcf_read_float (info->input, &xres, 1); info->cp += xcf_read_float (info->input, &yres, 1); GIMP_LOG (XCF, "prop resolution x=%f y=%f", xres, yres); if (xres < GIMP_MIN_RESOLUTION || xres > GIMP_MAX_RESOLUTION || yres < GIMP_MIN_RESOLUTION || yres > GIMP_MAX_RESOLUTION) { GimpTemplate *template = image->gimp->config->default_image; gimp_message_literal (info->gimp, G_OBJECT (info->progress), GIMP_MESSAGE_WARNING, "Warning, resolution out of range in XCF file"); xres = gimp_template_get_resolution_x (template); yres = gimp_template_get_resolution_y (template); } gimp_image_set_resolution (image, xres, yres); } break; case PROP_TATTOO: { info->cp += xcf_read_int32 (info->input, &info->tattoo_state, 1); GIMP_LOG (XCF, "prop tattoo state=%d", info->tattoo_state); } break; case PROP_PARASITES: { glong base = info->cp; while (info->cp - base < prop_size) { GimpParasite *p = xcf_load_parasite (info); GError *error = NULL; if (! p) return FALSE; if (! gimp_image_parasite_validate (image, p, &error)) { gimp_message (info->gimp, G_OBJECT (info->progress), GIMP_MESSAGE_WARNING, "Warning, invalid image parasite in XCF file: %s", error->message); g_clear_error (&error); } else { gimp_image_parasite_attach (image, p); } gimp_parasite_free (p); } if (info->cp - base != prop_size) gimp_message_literal (info->gimp, G_OBJECT (info->progress), GIMP_MESSAGE_WARNING, "Error while loading an image's parasites"); } break; case PROP_UNIT: { guint32 unit; info->cp += xcf_read_int32 (info->input, &unit, 1); GIMP_LOG (XCF, "prop unit=%d", unit); if ((unit <= GIMP_UNIT_PIXEL) || (unit >= gimp_unit_get_number_of_built_in_units ())) { gimp_message_literal (info->gimp, G_OBJECT (info->progress), GIMP_MESSAGE_WARNING, "Warning, unit out of range in XCF file, " "falling back to inches"); unit = GIMP_UNIT_INCH; } gimp_image_set_unit (image, unit); } break; case PROP_PATHS: xcf_load_old_paths (info, image); break; case PROP_USER_UNIT: { gchar *unit_strings[5]; float factor; guint32 digits; GimpUnit unit; gint num_units; gint i; info->cp += xcf_read_float (info->input, &factor, 1); info->cp += xcf_read_int32 (info->input, &digits, 1); info->cp += xcf_read_string (info->input, unit_strings, 5); for (i = 0; i < 5; i++) if (unit_strings[i] == NULL) unit_strings[i] = g_strdup (""); num_units = gimp_unit_get_number_of_units (); for (unit = gimp_unit_get_number_of_built_in_units (); unit < num_units; unit++) { /* if the factor and the identifier match some unit * in unitrc, use the unitrc unit */ if ((ABS (gimp_unit_get_factor (unit) - factor) < 1e-5) && (strcmp (unit_strings[0], gimp_unit_get_identifier (unit)) == 0)) { break; } } /* no match */ if (unit == num_units) unit = gimp_unit_new (unit_strings[0], factor, digits, unit_strings[1], unit_strings[2], unit_strings[3], unit_strings[4]); gimp_image_set_unit (image, unit); for (i = 0; i < 5; i++) g_free (unit_strings[i]); } break; case PROP_VECTORS: { guint32 base = info->cp; if (xcf_load_vectors (info, image)) { if (base + prop_size != info->cp) { g_printerr ("Mismatch in PROP_VECTORS size: " "skipping %d bytes.\n", base + prop_size - info->cp); xcf_seek_pos (info, base + prop_size, NULL); } } else { /* skip silently since we don't understand the format and * xcf_load_vectors already explained what was wrong */ xcf_seek_pos (info, base + prop_size, NULL); } } break; default: #ifdef GIMP_UNSTABLE g_printerr ("unexpected/unknown image property: %d (skipping)\n", prop_type); #endif if (! xcf_skip_unknown_prop (info, prop_size)) return FALSE; break; } } return FALSE; } static gboolean xcf_load_layer_props (XcfInfo *info, GimpImage *image, GimpLayer **layer, GList **item_path, gboolean *apply_mask, gboolean *edit_mask, gboolean *show_mask, guint32 *text_layer_flags, guint32 *group_layer_flags) { PropType prop_type; guint32 prop_size; while (TRUE) { if (! xcf_load_prop (info, &prop_type, &prop_size)) return FALSE; switch (prop_type) { case PROP_END: return TRUE; case PROP_ACTIVE_LAYER: info->active_layer = *layer; break; case PROP_FLOATING_SELECTION: info->floating_sel = *layer; info->cp += xcf_read_int32 (info->input, (guint32 *) &info->floating_sel_offset, 1); break; case PROP_OPACITY: { guint32 opacity; info->cp += xcf_read_int32 (info->input, &opacity, 1); gimp_layer_set_opacity (*layer, (gdouble) opacity / 255.0, FALSE); } break; case PROP_FLOAT_OPACITY: { gfloat opacity; info->cp += xcf_read_float (info->input, &opacity, 1); gimp_layer_set_opacity (*layer, opacity, FALSE); } break; case PROP_VISIBLE: { gboolean visible; info->cp += xcf_read_int32 (info->input, (guint32 *) &visible, 1); gimp_item_set_visible (GIMP_ITEM (*layer), visible, FALSE); } break; case PROP_LINKED: { gboolean linked; info->cp += xcf_read_int32 (info->input, (guint32 *) &linked, 1); gimp_item_set_linked (GIMP_ITEM (*layer), linked, FALSE); } break; case PROP_LOCK_CONTENT: { gboolean lock_content; info->cp += xcf_read_int32 (info->input, (guint32 *) &lock_content, 1); if (gimp_item_can_lock_content (GIMP_ITEM (*layer))) gimp_item_set_lock_content (GIMP_ITEM (*layer), lock_content, FALSE); } break; case PROP_LOCK_ALPHA: { gboolean lock_alpha; info->cp += xcf_read_int32 (info->input, (guint32 *) &lock_alpha, 1); if (gimp_layer_can_lock_alpha (*layer)) gimp_layer_set_lock_alpha (*layer, lock_alpha, FALSE); } break; case PROP_LOCK_POSITION: { gboolean lock_position; info->cp += xcf_read_int32 (info->input, (guint32 *) &lock_position, 1); if (gimp_item_can_lock_position (GIMP_ITEM (*layer))) gimp_item_set_lock_position (GIMP_ITEM (*layer), lock_position, FALSE); } break; case PROP_APPLY_MASK: info->cp += xcf_read_int32 (info->input, (guint32 *) apply_mask, 1); break; case PROP_EDIT_MASK: info->cp += xcf_read_int32 (info->input, (guint32 *) edit_mask, 1); break; case PROP_SHOW_MASK: info->cp += xcf_read_int32 (info->input, (guint32 *) show_mask, 1); break; case PROP_OFFSETS: { guint32 offset_x; guint32 offset_y; info->cp += xcf_read_int32 (info->input, &offset_x, 1); info->cp += xcf_read_int32 (info->input, &offset_y, 1); gimp_item_set_offset (GIMP_ITEM (*layer), offset_x, offset_y); } break; case PROP_MODE: { guint32 mode; info->cp += xcf_read_int32 (info->input, &mode, 1); if (mode == GIMP_OVERLAY_MODE) mode = GIMP_SOFTLIGHT_MODE; gimp_layer_set_mode (*layer, (GimpLayerModeEffects) mode, FALSE); } break; case PROP_TATTOO: { GimpTattoo tattoo; info->cp += xcf_read_int32 (info->input, (guint32 *) &tattoo, 1); gimp_item_set_tattoo (GIMP_ITEM (*layer), tattoo); } break; case PROP_PARASITES: { glong base = info->cp; while (info->cp - base < prop_size) { GimpParasite *p = xcf_load_parasite (info); GError *error = NULL; if (! p) return FALSE; if (! gimp_item_parasite_validate (GIMP_ITEM (*layer), p, &error)) { gimp_message (info->gimp, G_OBJECT (info->progress), GIMP_MESSAGE_WARNING, "Warning, invalid layer parasite in XCF file: %s", error->message); g_clear_error (&error); } else { gimp_item_parasite_attach (GIMP_ITEM (*layer), p, FALSE); } gimp_parasite_free (p); } if (info->cp - base != prop_size) gimp_message_literal (info->gimp, G_OBJECT (info->progress), GIMP_MESSAGE_WARNING, "Error while loading a layer's parasites"); } break; case PROP_TEXT_LAYER_FLAGS: info->cp += xcf_read_int32 (info->input, text_layer_flags, 1); break; case PROP_GROUP_ITEM: { GimpLayer *group; group = gimp_group_layer_new (image); gimp_object_set_name (GIMP_OBJECT (group), gimp_object_get_name (*layer)); g_object_ref_sink (*layer); g_object_unref (*layer); *layer = group; } break; case PROP_ITEM_PATH: { glong base = info->cp; GList *path = NULL; while (info->cp - base < prop_size) { guint32 index; if (xcf_read_int32 (info->input, &index, 1) != 4) { g_list_free (path); return FALSE; } info->cp += 4; path = g_list_append (path, GUINT_TO_POINTER (index)); } *item_path = path; } break; case PROP_GROUP_ITEM_FLAGS: info->cp += xcf_read_int32 (info->input, group_layer_flags, 1); break; default: #ifdef GIMP_UNSTABLE g_printerr ("unexpected/unknown layer property: %d (skipping)\n", prop_type); #endif if (! xcf_skip_unknown_prop (info, prop_size)) return FALSE; break; } } return FALSE; } static gboolean xcf_load_channel_props (XcfInfo *info, GimpImage *image, GimpChannel **channel) { PropType prop_type; guint32 prop_size; while (TRUE) { if (! xcf_load_prop (info, &prop_type, &prop_size)) return FALSE; switch (prop_type) { case PROP_END: return TRUE; case PROP_ACTIVE_CHANNEL: info->active_channel = *channel; break; case PROP_SELECTION: { GimpChannel *mask; mask = gimp_selection_new (image, gimp_item_get_width (GIMP_ITEM (*channel)), gimp_item_get_height (GIMP_ITEM (*channel))); gimp_image_take_mask (image, mask); g_object_unref (GIMP_DRAWABLE (mask)->private->buffer); GIMP_DRAWABLE (mask)->private->buffer = GIMP_DRAWABLE (*channel)->private->buffer; GIMP_DRAWABLE (*channel)->private->buffer = NULL; g_object_unref (*channel); *channel = mask; (*channel)->boundary_known = FALSE; (*channel)->bounds_known = FALSE; } break; case PROP_OPACITY: { guint32 opacity; info->cp += xcf_read_int32 (info->input, &opacity, 1); gimp_channel_set_opacity (*channel, opacity / 255.0, FALSE); } break; case PROP_FLOAT_OPACITY: { gfloat opacity; info->cp += xcf_read_float (info->input, &opacity, 1); gimp_channel_set_opacity (*channel, opacity, FALSE); } break; case PROP_VISIBLE: { gboolean visible; info->cp += xcf_read_int32 (info->input, (guint32 *) &visible, 1); gimp_item_set_visible (GIMP_ITEM (*channel), visible ? TRUE : FALSE, FALSE); } break; case PROP_LINKED: { gboolean linked; info->cp += xcf_read_int32 (info->input, (guint32 *) &linked, 1); gimp_item_set_linked (GIMP_ITEM (*channel), linked ? TRUE : FALSE, FALSE); } break; case PROP_LOCK_CONTENT: { gboolean lock_content; info->cp += xcf_read_int32 (info->input, (guint32 *) &lock_content, 1); gimp_item_set_lock_content (GIMP_ITEM (*channel), lock_content ? TRUE : FALSE, FALSE); } break; case PROP_LOCK_POSITION: { gboolean lock_position; info->cp += xcf_read_int32 (info->input, (guint32 *) &lock_position, 1); gimp_item_set_lock_position (GIMP_ITEM (*channel), lock_position ? TRUE : FALSE, FALSE); } break; case PROP_SHOW_MASKED: { gboolean show_masked; info->cp += xcf_read_int32 (info->input, (guint32 *) &show_masked, 1); gimp_channel_set_show_masked (*channel, show_masked); } break; case PROP_COLOR: { guchar col[3]; info->cp += xcf_read_int8 (info->input, (guint8 *) col, 3); gimp_rgb_set_uchar (&(*channel)->color, col[0], col[1], col[2]); } break; case PROP_TATTOO: { GimpTattoo tattoo; info->cp += xcf_read_int32 (info->input, (guint32 *) &tattoo, 1); gimp_item_set_tattoo (GIMP_ITEM (*channel), tattoo); } break; case PROP_PARASITES: { glong base = info->cp; while ((info->cp - base) < prop_size) { GimpParasite *p = xcf_load_parasite (info); GError *error = NULL; if (! p) return FALSE; if (! gimp_item_parasite_validate (GIMP_ITEM (*channel), p, &error)) { gimp_message (info->gimp, G_OBJECT (info->progress), GIMP_MESSAGE_WARNING, "Warning, invalid channel parasite in XCF file: %s", error->message); g_clear_error (&error); } else { gimp_item_parasite_attach (GIMP_ITEM (*channel), p, FALSE); } gimp_parasite_free (p); } if (info->cp - base != prop_size) gimp_message_literal (info->gimp, G_OBJECT (info->progress), GIMP_MESSAGE_WARNING, "Error while loading a channel's parasites"); } break; default: #ifdef GIMP_UNSTABLE g_printerr ("unexpected/unknown channel property: %d (skipping)\n", prop_type); #endif if (! xcf_skip_unknown_prop (info, prop_size)) return FALSE; break; } } return FALSE; } static gboolean xcf_load_prop (XcfInfo *info, PropType *prop_type, guint32 *prop_size) { if (G_UNLIKELY (xcf_read_int32 (info->input, (guint32 *) prop_type, 1) != 4)) return FALSE; info->cp += 4; if (G_UNLIKELY (xcf_read_int32 (info->input, (guint32 *) prop_size, 1) != 4)) return FALSE; info->cp += 4; return TRUE; } static GimpLayer * xcf_load_layer (XcfInfo *info, GimpImage *image, GList **item_path) { GimpLayer *layer; GimpLayerMask *layer_mask; guint32 hierarchy_offset; guint32 layer_mask_offset; gboolean apply_mask = TRUE; gboolean edit_mask = FALSE; gboolean show_mask = FALSE; gboolean active; gboolean floating; guint32 group_layer_flags = 0; guint32 text_layer_flags = 0; gint width; gint height; gint type; GimpImageBaseType base_type; gboolean has_alpha; const Babl *format; gboolean is_fs_drawable; gchar *name; /* check and see if this is the drawable the floating selection * is attached to. if it is then we'll do the attachment in our caller. */ is_fs_drawable = (info->cp == info->floating_sel_offset); /* read in the layer width, height, type and name */ info->cp += xcf_read_int32 (info->input, (guint32 *) &width, 1); info->cp += xcf_read_int32 (info->input, (guint32 *) &height, 1); info->cp += xcf_read_int32 (info->input, (guint32 *) &type, 1); info->cp += xcf_read_string (info->input, &name, 1); GIMP_LOG (XCF, "width=%d, height=%d, type=%d, name='%s'", width, height, type, name); switch (type) { case GIMP_RGB_IMAGE: base_type = GIMP_RGB; has_alpha = FALSE; break; case GIMP_RGBA_IMAGE: base_type = GIMP_RGB; has_alpha = TRUE; break; case GIMP_GRAY_IMAGE: base_type = GIMP_GRAY; has_alpha = FALSE; break; case GIMP_GRAYA_IMAGE: base_type = GIMP_GRAY; has_alpha = TRUE; break; case GIMP_INDEXED_IMAGE: base_type = GIMP_INDEXED; has_alpha = FALSE; break; case GIMP_INDEXEDA_IMAGE: base_type = GIMP_INDEXED; has_alpha = TRUE; break; default: return NULL; } if (width <= 0 || height <= 0) return NULL; /* do not use gimp_image_get_layer_format() because it might * be the floating selection of a channel or mask */ format = gimp_image_get_format (image, base_type, gimp_image_get_precision (image), has_alpha); /* create a new layer */ layer = gimp_layer_new (image, width, height, format, name, 255, GIMP_NORMAL_MODE); g_free (name); if (! layer) return NULL; /* read in the layer properties */ if (! xcf_load_layer_props (info, image, &layer, item_path, &apply_mask, &edit_mask, &show_mask, &text_layer_flags, &group_layer_flags)) goto error; GIMP_LOG (XCF, "layer props loaded"); xcf_progress_update (info); /* call the evil text layer hack that might change our layer pointer */ active = (info->active_layer == layer); floating = (info->floating_sel == layer); if (gimp_text_layer_xcf_load_hack (&layer)) { gimp_text_layer_set_xcf_flags (GIMP_TEXT_LAYER (layer), text_layer_flags); if (active) info->active_layer = layer; if (floating) info->floating_sel = layer; } /* read the hierarchy and layer mask offsets */ info->cp += xcf_read_int32 (info->input, &hierarchy_offset, 1); info->cp += xcf_read_int32 (info->input, &layer_mask_offset, 1); /* read in the hierarchy (ignore it for group layers, both as an * optimization and because the hierarchy's extents don't match * the group layer's tiles) */ if (! gimp_viewable_get_children (GIMP_VIEWABLE (layer))) { if (! xcf_seek_pos (info, hierarchy_offset, NULL)) goto error; GIMP_LOG (XCF, "loading buffer"); if (! xcf_load_buffer (info, gimp_drawable_get_buffer (GIMP_DRAWABLE (layer)))) goto error; GIMP_LOG (XCF, "buffer loaded"); xcf_progress_update (info); } else { gboolean expanded = group_layer_flags & XCF_GROUP_ITEM_EXPANDED; gimp_viewable_set_expanded (GIMP_VIEWABLE (layer), expanded); } /* read in the layer mask */ if (layer_mask_offset != 0) { if (! xcf_seek_pos (info, layer_mask_offset, NULL)) goto error; layer_mask = xcf_load_layer_mask (info, image); if (! layer_mask) goto error; xcf_progress_update (info); /* don't add the layer mask yet, that won't work for group * layers which update their size automatically; instead * attach it so it can be added when all layers are loaded */ g_object_set_data_full (G_OBJECT (layer), "gimp-layer-mask", g_object_ref_sink (layer_mask), (GDestroyNotify) g_object_unref); g_object_set_data (G_OBJECT (layer), "gimp-layer-mask-apply", GINT_TO_POINTER (apply_mask)); g_object_set_data (G_OBJECT (layer), "gimp-layer-mask-edit", GINT_TO_POINTER (edit_mask)); g_object_set_data (G_OBJECT (layer), "gimp-layer-mask-show", GINT_TO_POINTER (show_mask)); } /* attach the floating selection... */ if (is_fs_drawable) info->floating_sel_drawable = GIMP_DRAWABLE (layer); return layer; error: g_object_unref (layer); return NULL; } static GimpChannel * xcf_load_channel (XcfInfo *info, GimpImage *image) { GimpChannel *channel; guint32 hierarchy_offset; gint width; gint height; gboolean is_fs_drawable; gchar *name; GimpRGB color = { 0.0, 0.0, 0.0, GIMP_OPACITY_OPAQUE }; /* check and see if this is the drawable the floating selection * is attached to. if it is then we'll do the attachment in our caller. */ is_fs_drawable = (info->cp == info->floating_sel_offset); /* read in the layer width, height and name */ info->cp += xcf_read_int32 (info->input, (guint32 *) &width, 1); info->cp += xcf_read_int32 (info->input, (guint32 *) &height, 1); if (width <= 0 || height <= 0) return NULL; info->cp += xcf_read_string (info->input, &name, 1); /* create a new channel */ channel = gimp_channel_new (image, width, height, name, &color); g_free (name); if (!channel) return NULL; /* read in the channel properties */ if (!xcf_load_channel_props (info, image, &channel)) goto error; xcf_progress_update (info); /* read the hierarchy and layer mask offsets */ info->cp += xcf_read_int32 (info->input, &hierarchy_offset, 1); /* read in the hierarchy */ if (!xcf_seek_pos (info, hierarchy_offset, NULL)) goto error; if (!xcf_load_buffer (info, gimp_drawable_get_buffer (GIMP_DRAWABLE (channel)))) goto error; xcf_progress_update (info); if (is_fs_drawable) info->floating_sel_drawable = GIMP_DRAWABLE (channel); return channel; error: g_object_unref (channel); return NULL; } static GimpLayerMask * xcf_load_layer_mask (XcfInfo *info, GimpImage *image) { GimpLayerMask *layer_mask; GimpChannel *channel; guint32 hierarchy_offset; gint width; gint height; gboolean is_fs_drawable; gchar *name; GimpRGB color = { 0.0, 0.0, 0.0, GIMP_OPACITY_OPAQUE }; /* check and see if this is the drawable the floating selection * is attached to. if it is then we'll do the attachment in our caller. */ is_fs_drawable = (info->cp == info->floating_sel_offset); /* read in the layer width, height and name */ info->cp += xcf_read_int32 (info->input, (guint32 *) &width, 1); info->cp += xcf_read_int32 (info->input, (guint32 *) &height, 1); if (width <= 0 || height <= 0) return NULL; info->cp += xcf_read_string (info->input, &name, 1); /* create a new layer mask */ layer_mask = gimp_layer_mask_new (image, width, height, name, &color); g_free (name); if (!layer_mask) return NULL; /* read in the layer_mask properties */ channel = GIMP_CHANNEL (layer_mask); if (!xcf_load_channel_props (info, image, &channel)) goto error; xcf_progress_update (info); /* read the hierarchy and layer mask offsets */ info->cp += xcf_read_int32 (info->input, &hierarchy_offset, 1); /* read in the hierarchy */ if (! xcf_seek_pos (info, hierarchy_offset, NULL)) goto error; if (!xcf_load_buffer (info, gimp_drawable_get_buffer (GIMP_DRAWABLE (layer_mask)))) goto error; xcf_progress_update (info); /* attach the floating selection... */ if (is_fs_drawable) info->floating_sel_drawable = GIMP_DRAWABLE (layer_mask); return layer_mask; error: g_object_unref (layer_mask); return NULL; } static gboolean xcf_load_buffer (XcfInfo *info, GeglBuffer *buffer) { const Babl *format; guint32 offset; gint width; gint height; gint bpp; format = gegl_buffer_get_format (buffer); info->cp += xcf_read_int32 (info->input, (guint32 *) &width, 1); info->cp += xcf_read_int32 (info->input, (guint32 *) &height, 1); info->cp += xcf_read_int32 (info->input, (guint32 *) &bpp, 1); /* make sure the values in the file correspond to the values * calculated when the TileManager was created. */ if (width != gegl_buffer_get_width (buffer) || height != gegl_buffer_get_height (buffer) || bpp != babl_format_get_bytes_per_pixel (format)) return FALSE; info->cp += xcf_read_int32 (info->input, &offset, 1); /* top level */ /* seek to the level offset */ if (!xcf_seek_pos (info, offset, NULL)) return FALSE; /* read in the level */ if (!xcf_load_level (info, buffer)) return FALSE; /* discard levels below first. */ return TRUE; } static gboolean xcf_load_level (XcfInfo *info, GeglBuffer *buffer) { const Babl *format; gint bpp; guint32 saved_pos; guint32 offset, offset2; gint n_tile_rows; gint n_tile_cols; guint ntiles; gint width; gint height; gint i; gint fail; format = gegl_buffer_get_format (buffer); bpp = babl_format_get_bytes_per_pixel (format); info->cp += xcf_read_int32 (info->input, (guint32 *) &width, 1); info->cp += xcf_read_int32 (info->input, (guint32 *) &height, 1); if (width != gegl_buffer_get_width (buffer) || height != gegl_buffer_get_height (buffer)) return FALSE; /* read in the first tile offset. * if it is '0', then this tile level is empty * and we can simply return. */ info->cp += xcf_read_int32 (info->input, &offset, 1); if (offset == 0) return TRUE; 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; for (i = 0; i < ntiles; i++) { GeglRectangle rect; fail = FALSE; if (offset == 0) { gimp_message_literal (info->gimp, G_OBJECT (info->progress), GIMP_MESSAGE_ERROR, "not enough tiles found in level"); return FALSE; } /* save the current position as it is where the * next tile offset is stored. */ saved_pos = info->cp; /* read in the offset of the next tile so we can calculate the amount of data needed for this tile*/ info->cp += xcf_read_int32 (info->input, &offset2, 1); /* if the offset is 0 then we need to read in the maximum possible allowing for negative compression */ if (offset2 == 0) offset2 = offset + XCF_TILE_WIDTH * XCF_TILE_WIDTH * bpp * 1.5; /* 1.5 is probably more than we need to allow */ /* seek to the tile offset */ if (! xcf_seek_pos (info, offset, NULL)) return FALSE; /* get the tile from the tile manager */ gimp_gegl_buffer_get_tile_rect (buffer, XCF_TILE_WIDTH, XCF_TILE_HEIGHT, i, &rect); GIMP_LOG (XCF, "loading tile %d/%d", i + 1, ntiles); /* read in the tile */ switch (info->compression) { case COMPRESS_NONE: if (!xcf_load_tile (info, buffer, &rect, format)) fail = TRUE; break; case COMPRESS_RLE: if (!xcf_load_tile_rle (info, buffer, &rect, format, offset2 - offset)) fail = TRUE; break; case COMPRESS_ZLIB: if (!xcf_load_tile_zlib (info, buffer, &rect, format, offset2 - offset)) fail = TRUE; break; case COMPRESS_FRACTAL: g_printerr ("xcf: fractal compression unimplemented. " "Possibly corrupt XCF file."); fail = TRUE; break; default: g_printerr ("xcf: unknown compression. " "Possibly corrupt XCF file."); fail = TRUE; break; } if (fail) return FALSE; GIMP_LOG (XCF, "loaded tile %d/%d", i + 1, ntiles); /* restore the saved position so we'll be ready to * read the next offset. */ if (!xcf_seek_pos (info, saved_pos, NULL)) return FALSE; /* read in the offset of the next tile */ info->cp += xcf_read_int32 (info->input, &offset, 1); } if (offset != 0) { gimp_message (info->gimp, G_OBJECT (info->progress), GIMP_MESSAGE_ERROR, "encountered garbage after reading level: %d", offset); return FALSE; } return TRUE; } static gboolean xcf_load_tile (XcfInfo *info, GeglBuffer *buffer, GeglRectangle *tile_rect, const Babl *format) { 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); info->cp += xcf_read_int8 (info->input, tile_data, tile_size); gegl_buffer_set (buffer, tile_rect, 0, format, tile_data, GEGL_AUTO_ROWSTRIDE); return TRUE; } static gboolean xcf_load_tile_rle (XcfInfo *info, GeglBuffer *buffer, GeglRectangle *tile_rect, const Babl *format, gint data_length) { 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 i; gsize nmemb_read_successfully; guchar *xcfdata; guchar *xcfodata; guchar *xcfdatalimit; /* Workaround for bug #357809: avoid crashing on g_malloc() and skip * this tile (return TRUE without storing data) as if it did not * contain any data. It is better than returning FALSE, which would * skip the whole hierarchy while there may still be some valid * tiles in the file. */ if (data_length <= 0) return TRUE; xcfdata = xcfodata = g_alloca (data_length); /* we have to read directly instead of xcf_read_* because we may be * reading past the end of the file here */ g_input_stream_read_all (info->input, xcfdata, data_length, &nmemb_read_successfully, NULL, NULL); if (nmemb_read_successfully == 0) return TRUE; info->cp += nmemb_read_successfully; xcfdatalimit = &xcfodata[nmemb_read_successfully - 1]; for (i = 0; i < bpp; i++) { guchar *data = tile_data + i; gint size = tile_rect->width * tile_rect->height; gint count = 0; guchar val; gint length; gint j; while (size > 0) { if (xcfdata > xcfdatalimit) { goto bogus_rle; } val = *xcfdata++; length = val; if (length >= 128) { length = 255 - (length - 1); if (length == 128) { if (xcfdata >= xcfdatalimit) { goto bogus_rle; } length = (*xcfdata << 8) + xcfdata[1]; xcfdata += 2; } count += length; size -= length; if (size < 0) { goto bogus_rle; } if (&xcfdata[length-1] > xcfdatalimit) { goto bogus_rle; } while (length-- > 0) { *data = *xcfdata++; data += bpp; } } else { length += 1; if (length == 128) { if (xcfdata >= xcfdatalimit) { goto bogus_rle; } length = (*xcfdata << 8) + xcfdata[1]; xcfdata += 2; } count += length; size -= length; if (size < 0) { goto bogus_rle; } if (xcfdata > xcfdatalimit) { goto bogus_rle; } val = *xcfdata++; for (j = 0; j < length; j++) { *data = val; data += bpp; } } } } gegl_buffer_set (buffer, tile_rect, 0, format, tile_data, GEGL_AUTO_ROWSTRIDE); return TRUE; bogus_rle: return FALSE; } static gboolean xcf_load_tile_zlib (XcfInfo *info, GeglBuffer *buffer, GeglRectangle *tile_rect, const Babl *format, gint data_length) { z_stream strm; int action; int status; 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); gsize bytes_read; guchar *xcfdata; /* Workaround for bug #357809: avoid crashing on g_malloc() and skip * this tile (return TRUE without storing data) as if it did not * contain any data. It is better than returning FALSE, which would * skip the whole hierarchy while there may still be some valid * tiles in the file. */ if (data_length <= 0) return TRUE; xcfdata = g_alloca (data_length); /* we have to read directly instead of xcf_read_* because we may be * reading past the end of the file here */ g_input_stream_read_all (info->input, xcfdata, data_length, &bytes_read, NULL, NULL); if (bytes_read == 0) return TRUE; info->cp += bytes_read; strm.next_out = tile_data; strm.avail_out = tile_size; strm.zalloc = Z_NULL; strm.zfree = Z_NULL; strm.opaque = Z_NULL; strm.next_in = xcfdata; strm.avail_in = bytes_read; /* Initialize the stream decompression. */ status = inflateInit (&strm); if (status != Z_OK) return FALSE; action = Z_NO_FLUSH; while (status == Z_OK) { if (strm.avail_in == 0) { action = Z_FINISH; } status = inflate (&strm, action); if (status == Z_STREAM_END) { /* All the data was successfully decoded. */ break; } else if (status == Z_BUF_ERROR) { g_printerr ("xcf: decompressed tile bigger than the expected size."); inflateEnd (&strm); return FALSE; } else if (status != Z_OK) { g_printerr ("xcf: tile decompression failed: %s", zError (status)); inflateEnd (&strm); return FALSE; } } gegl_buffer_set (buffer, tile_rect, 0, format, tile_data, GEGL_AUTO_ROWSTRIDE); inflateEnd (&strm); return TRUE; } static GimpParasite * xcf_load_parasite (XcfInfo *info) { GimpParasite *parasite; gchar *name; guint32 flags; guint32 size; gpointer data; info->cp += xcf_read_string (info->input, &name, 1); info->cp += xcf_read_int32 (info->input, &flags, 1); info->cp += xcf_read_int32 (info->input, &size, 1); if (size > MAX_XCF_PARASITE_DATA_LEN) { g_printerr ("Maximum parasite data length (%ld bytes) exceeded. " "Possibly corrupt XCF file.", MAX_XCF_PARASITE_DATA_LEN); g_free (name); return NULL; } data = g_new (gchar, size); info->cp += xcf_read_int8 (info->input, data, size); parasite = gimp_parasite_new (name, flags, size, data); g_free (name); g_free (data); return parasite; } static gboolean xcf_load_old_paths (XcfInfo *info, GimpImage *image) { guint32 num_paths; guint32 last_selected_row; GimpVectors *active_vectors; info->cp += xcf_read_int32 (info->input, &last_selected_row, 1); info->cp += xcf_read_int32 (info->input, &num_paths, 1); while (num_paths-- > 0) xcf_load_old_path (info, image); active_vectors = GIMP_VECTORS (gimp_container_get_child_by_index (gimp_image_get_vectors (image), last_selected_row)); if (active_vectors) gimp_image_set_active_vectors (image, active_vectors); return TRUE; } static gboolean xcf_load_old_path (XcfInfo *info, GimpImage *image) { gchar *name; guint32 locked; guint8 state; guint32 closed; guint32 num_points; guint32 version; /* changed from num_paths */ GimpTattoo tattoo = 0; GimpVectors *vectors; GimpVectorsCompatPoint *points; gint i; info->cp += xcf_read_string (info->input, &name, 1); info->cp += xcf_read_int32 (info->input, &locked, 1); info->cp += xcf_read_int8 (info->input, &state, 1); info->cp += xcf_read_int32 (info->input, &closed, 1); info->cp += xcf_read_int32 (info->input, &num_points, 1); info->cp += xcf_read_int32 (info->input, &version, 1); if (version == 2) { guint32 dummy; /* Had extra type field and points are stored as doubles */ info->cp += xcf_read_int32 (info->input, (guint32 *) &dummy, 1); } else if (version == 3) { guint32 dummy; /* Has extra tatto field */ info->cp += xcf_read_int32 (info->input, (guint32 *) &dummy, 1); info->cp += xcf_read_int32 (info->input, (guint32 *) &tattoo, 1); } else if (version != 1) { g_printerr ("Unknown path type. Possibly corrupt XCF file"); return FALSE; } /* skip empty compatibility paths */ if (num_points == 0) { g_free (name); return FALSE; } points = g_new0 (GimpVectorsCompatPoint, num_points); for (i = 0; i < num_points; i++) { if (version == 1) { gint32 x; gint32 y; info->cp += xcf_read_int32 (info->input, &points[i].type, 1); info->cp += xcf_read_int32 (info->input, (guint32 *) &x, 1); info->cp += xcf_read_int32 (info->input, (guint32 *) &y, 1); points[i].x = x; points[i].y = y; } else { gfloat x; gfloat y; info->cp += xcf_read_int32 (info->input, &points[i].type, 1); info->cp += xcf_read_float (info->input, &x, 1); info->cp += xcf_read_float (info->input, &y, 1); points[i].x = x; points[i].y = y; } } vectors = gimp_vectors_compat_new (image, name, points, num_points, closed); g_free (name); g_free (points); gimp_item_set_linked (GIMP_ITEM (vectors), locked, FALSE); if (tattoo) gimp_item_set_tattoo (GIMP_ITEM (vectors), tattoo); gimp_image_add_vectors (image, vectors, NULL, /* can't be a tree */ gimp_container_get_n_children (gimp_image_get_vectors (image)), FALSE); return TRUE; } static gboolean xcf_load_vectors (XcfInfo *info, GimpImage *image) { guint32 version; guint32 active_index; guint32 num_paths; GimpVectors *active_vectors; #ifdef GIMP_XCF_PATH_DEBUG g_printerr ("xcf_load_vectors\n"); #endif info->cp += xcf_read_int32 (info->input, &version, 1); if (version != 1) { gimp_message (info->gimp, G_OBJECT (info->progress), GIMP_MESSAGE_WARNING, "Unknown vectors version: %d (skipping)", version); return FALSE; } info->cp += xcf_read_int32 (info->input, &active_index, 1); info->cp += xcf_read_int32 (info->input, &num_paths, 1); #ifdef GIMP_XCF_PATH_DEBUG g_printerr ("%d paths (active: %d)\n", num_paths, active_index); #endif while (num_paths-- > 0) if (! xcf_load_vector (info, image)) return FALSE; /* FIXME tree */ active_vectors = GIMP_VECTORS (gimp_container_get_child_by_index (gimp_image_get_vectors (image), active_index)); if (active_vectors) gimp_image_set_active_vectors (image, active_vectors); #ifdef GIMP_XCF_PATH_DEBUG g_printerr ("xcf_load_vectors: loaded %d bytes\n", info->cp - base); #endif return TRUE; } static gboolean xcf_load_vector (XcfInfo *info, GimpImage *image) { gchar *name; GimpTattoo tattoo = 0; guint32 visible; guint32 linked; guint32 num_parasites; guint32 num_strokes; GimpVectors *vectors; gint i; #ifdef GIMP_XCF_PATH_DEBUG g_printerr ("xcf_load_vector\n"); #endif info->cp += xcf_read_string (info->input, &name, 1); info->cp += xcf_read_int32 (info->input, &tattoo, 1); info->cp += xcf_read_int32 (info->input, &visible, 1); info->cp += xcf_read_int32 (info->input, &linked, 1); info->cp += xcf_read_int32 (info->input, &num_parasites, 1); info->cp += xcf_read_int32 (info->input, &num_strokes, 1); #ifdef GIMP_XCF_PATH_DEBUG g_printerr ("name: %s, tattoo: %d, visible: %d, linked: %d, " "num_parasites %d, num_strokes %d\n", name, tattoo, visible, linked, num_parasites, num_strokes); #endif vectors = gimp_vectors_new (image, name); g_free (name); gimp_item_set_visible (GIMP_ITEM (vectors), visible, FALSE); gimp_item_set_linked (GIMP_ITEM (vectors), linked, FALSE); if (tattoo) gimp_item_set_tattoo (GIMP_ITEM (vectors), tattoo); for (i = 0; i < num_parasites; i++) { GimpParasite *parasite = xcf_load_parasite (info); GError *error = NULL; if (! parasite) return FALSE; if (! gimp_item_parasite_validate (GIMP_ITEM (vectors), parasite, &error)) { gimp_message (info->gimp, G_OBJECT (info->progress), GIMP_MESSAGE_WARNING, "Warning, invalid vectors parasite in XCF file: %s", error->message); g_clear_error (&error); } else { gimp_item_parasite_attach (GIMP_ITEM (vectors), parasite, FALSE); } gimp_parasite_free (parasite); } for (i = 0; i < num_strokes; i++) { guint32 stroke_type_id; guint32 closed; guint32 num_axes; guint32 num_control_points; guint32 type; gfloat coords[10] = GIMP_COORDS_DEFAULT_VALUES; GimpStroke *stroke; gint j; GimpValueArray *control_points; GValue value = G_VALUE_INIT; GimpAnchor anchor = { { 0, } }; GType stroke_type; g_value_init (&value, GIMP_TYPE_ANCHOR); info->cp += xcf_read_int32 (info->input, &stroke_type_id, 1); info->cp += xcf_read_int32 (info->input, &closed, 1); info->cp += xcf_read_int32 (info->input, &num_axes, 1); info->cp += xcf_read_int32 (info->input, &num_control_points, 1); #ifdef GIMP_XCF_PATH_DEBUG g_printerr ("stroke_type: %d, closed: %d, num_axes %d, len %d\n", stroke_type_id, closed, num_axes, num_control_points); #endif switch (stroke_type_id) { case XCF_STROKETYPE_BEZIER_STROKE: stroke_type = GIMP_TYPE_BEZIER_STROKE; break; default: g_printerr ("skipping unknown stroke type\n"); xcf_seek_pos (info, info->cp + 4 * num_axes * num_control_points, NULL); continue; } if (num_axes < 2 || num_axes > 6) { g_printerr ("bad number of axes in stroke description\n"); return FALSE; } control_points = gimp_value_array_new (num_control_points); anchor.selected = FALSE; for (j = 0; j < num_control_points; j++) { info->cp += xcf_read_int32 (info->input, &type, 1); info->cp += xcf_read_float (info->input, coords, num_axes); anchor.type = type; anchor.position.x = coords[0]; anchor.position.y = coords[1]; anchor.position.pressure = coords[2]; anchor.position.xtilt = coords[3]; anchor.position.ytilt = coords[4]; anchor.position.wheel = coords[5]; g_value_set_boxed (&value, &anchor); gimp_value_array_append (control_points, &value); #ifdef GIMP_XCF_PATH_DEBUG g_printerr ("Anchor: %d, (%f, %f, %f, %f, %f, %f)\n", type, coords[0], coords[1], coords[2], coords[3], coords[4], coords[5]); #endif } g_value_unset (&value); stroke = g_object_new (stroke_type, "closed", closed, "control-points", control_points, NULL); gimp_vectors_stroke_add (vectors, stroke); g_object_unref (stroke); gimp_value_array_unref (control_points); } gimp_image_add_vectors (image, vectors, NULL, /* FIXME tree */ gimp_container_get_n_children (gimp_image_get_vectors (image)), FALSE); return TRUE; } static gboolean xcf_skip_unknown_prop (XcfInfo *info, gsize size) { guint8 buf[16]; guint amount; while (size > 0) { if (g_input_stream_is_closed (info->input)) return FALSE; amount = MIN (16, size); amount = xcf_read_int8 (info->input, buf, amount); if (amount == 0) return FALSE; info->cp += amount; size -= amount; } return TRUE; }