/* PIKA - Photo and Image Kooker Application * a rebranding of The GNU Image Manipulation Program (created with heckimp) * A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio * * Original copyright, applying to most contents (license remains unchanged): * 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 "libpikabase/pikabase.h" #include "libpikacolor/pikacolor.h" #include "core/core-types.h" #include "config/pikacoreconfig.h" #include "gegl/pika-babl.h" #include "gegl/pika-gegl-tile-compat.h" #include "core/pika.h" #include "core/pikacontainer.h" #include "core/pikadrawable-private.h" /* eek */ #include "core/pikagrid.h" #include "core/pikagrouplayer.h" #include "core/pikaimage.h" #include "core/pikaimage-color-profile.h" #include "core/pikaimage-colormap.h" #include "core/pikaimage-grid.h" #include "core/pikaimage-guides.h" #include "core/pikaimage-metadata.h" #include "core/pikaimage-private.h" #include "core/pikaimage-sample-points.h" #include "core/pikaimage-symmetry.h" #include "core/pikaimage-undo.h" #include "core/pikaitemlist.h" #include "core/pikaitemstack.h" #include "core/pikalayer-floating-selection.h" #include "core/pikalayer-new.h" #include "core/pikalayermask.h" #include "core/pikaparasitelist.h" #include "core/pikaprogress.h" #include "core/pikaselection.h" #include "core/pikasymmetry.h" #include "core/pikatemplate.h" #include "operations/layer-modes/pika-layer-modes.h" #include "text/pikatextlayer.h" #include "text/pikatextlayer-xcf.h" #include "vectors/pikaanchor.h" #include "vectors/pikastroke.h" #include "vectors/pikabezierstroke.h" #include "vectors/pikavectors.h" #include "vectors/pikavectors-compat.h" #include "xcf-private.h" #include "xcf-load.h" #include "xcf-read.h" #include "xcf-seek.h" #include "xcf-utils.h" #include "pika-log.h" #include "pika-intl.h" #define MAX_XCF_PARASITE_DATA_LEN (256L * 1024 * 1024) /* #define PIKA_XCF_PATH_DEBUG */ static void xcf_load_add_masks (PikaImage *image); static gboolean xcf_load_image_props (XcfInfo *info, PikaImage *image); static gboolean xcf_load_layer_props (XcfInfo *info, PikaImage *image, PikaLayer **layer, GList **item_path, gboolean *apply_mask, gboolean *edit_mask, gboolean *show_mask, guint32 *text_layer_flags, guint32 *group_layer_flags); static gboolean xcf_check_layer_props (XcfInfo *info, GList **item_path, gboolean *is_group_layer, gboolean *is_text_layer); static gboolean xcf_load_channel_props (XcfInfo *info, PikaImage *image, PikaChannel **channel); static gboolean xcf_load_vectors_props (XcfInfo *info, PikaImage *image, PikaVectors **vectors); static gboolean xcf_load_prop (XcfInfo *info, PropType *prop_type, guint32 *prop_size); static PikaLayer * xcf_load_layer (XcfInfo *info, PikaImage *image, GList **item_path); static PikaChannel * xcf_load_channel (XcfInfo *info, PikaImage *image); static PikaVectors * xcf_load_vectors (XcfInfo *info, PikaImage *image); static PikaLayerMask * xcf_load_layer_mask (XcfInfo *info, PikaImage *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 PikaParasite * xcf_load_parasite (XcfInfo *info); static gboolean xcf_load_old_paths (XcfInfo *info, PikaImage *image); static gboolean xcf_load_old_path (XcfInfo *info, PikaImage *image); static gboolean xcf_load_old_vectors (XcfInfo *info, PikaImage *image); static gboolean xcf_load_old_vector (XcfInfo *info, PikaImage *image); static gboolean xcf_skip_unknown_prop (XcfInfo *info, gsize size); static gboolean xcf_item_path_is_parent (GList *path, GList *parent_path); static void xcf_fix_item_path (PikaLayer *layer, GList **path, GList *broken_paths); #define xcf_progress_update(info) G_STMT_START \ { \ if (info->progress) \ pika_progress_pulse (info->progress); \ } G_STMT_END PikaImage * xcf_load_image (Pika *pika, XcfInfo *info, GError **error) { PikaImage *image = NULL; const PikaParasite *parasite; gboolean has_metadata = FALSE; goffset saved_pos; goffset offset; gint width; gint height; gint image_type; PikaPrecision precision = PIKA_PRECISION_U8_NON_LINEAR; gint num_successful_elements = 0; gint n_broken_layers = 0; gint n_broken_channels = 0; gint n_broken_vectors = 0; GList *broken_paths = NULL; GList *group_layers = NULL; GList *syms; GList *iter; /* read in the image width, height and type */ xcf_read_int32 (info, (guint32 *) &width, 1); xcf_read_int32 (info, (guint32 *) &height, 1); xcf_read_int32 (info, (guint32 *) &image_type, 1); if (image_type < PIKA_RGB || image_type > PIKA_INDEXED) goto hard_error; /* Be lenient with corrupt image dimensions. * Hopefully layer dimensions will be valid. */ if (width <= 0 || height <= 0 || width > PIKA_MAX_IMAGE_SIZE || height > PIKA_MAX_IMAGE_SIZE) { PIKA_LOG (XCF, "Invalid image size %d x %d, setting to 1x1.", width, height); width = 1; height = 1; } if (info->file_version >= 4) { gint p; xcf_read_int32 (info, (guint32 *) &p, 1); if (info->file_version == 4) { switch (p) { case 0: precision = PIKA_PRECISION_U8_NON_LINEAR; break; case 1: precision = PIKA_PRECISION_U16_NON_LINEAR; break; case 2: precision = PIKA_PRECISION_U32_LINEAR; break; case 3: precision = PIKA_PRECISION_HALF_LINEAR; break; case 4: precision = PIKA_PRECISION_FLOAT_LINEAR; break; default: goto hard_error; } } else if (info->file_version == 5 || info->file_version == 6) { switch (p) { case 100: precision = PIKA_PRECISION_U8_LINEAR; break; case 150: precision = PIKA_PRECISION_U8_NON_LINEAR; break; case 200: precision = PIKA_PRECISION_U16_LINEAR; break; case 250: precision = PIKA_PRECISION_U16_NON_LINEAR; break; case 300: precision = PIKA_PRECISION_U32_LINEAR; break; case 350: precision = PIKA_PRECISION_U32_NON_LINEAR; break; case 400: precision = PIKA_PRECISION_HALF_LINEAR; break; case 450: precision = PIKA_PRECISION_HALF_NON_LINEAR; break; case 500: precision = PIKA_PRECISION_FLOAT_LINEAR; break; case 550: precision = PIKA_PRECISION_FLOAT_NON_LINEAR; break; default: goto hard_error; } } else { precision = p; } } PIKA_LOG (XCF, "version=%d, width=%d, height=%d, image_type=%d, precision=%d", info->file_version, width, height, image_type, precision); if (! pika_babl_is_valid (image_type, precision)) { pika_message_literal (pika, G_OBJECT (info->progress), PIKA_MESSAGE_ERROR, _("Invalid image mode and precision combination.")); goto hard_error; } image = pika_create_image (pika, width, height, image_type, precision, FALSE); pika_image_undo_disable (image); xcf_progress_update (info); /* read the image properties */ if (! xcf_load_image_props (info, image)) goto hard_error; PIKA_LOG (XCF, "image props loaded"); /* Order matters for item sets. */ info->layer_sets = g_list_reverse (info->layer_sets); info->channel_sets = g_list_reverse (info->channel_sets); /* check for simulation intent parasite */ parasite = pika_image_parasite_find (PIKA_IMAGE (image), "image-simulation-intent"); if (parasite) { guint32 parasite_size; const guint8 *intent; PikaImagePrivate *private = PIKA_IMAGE_GET_PRIVATE (image); intent = (const guint8 *) pika_parasite_get_data (parasite, ¶site_size); if (parasite_size == 1) { if (*intent != PIKA_COLOR_RENDERING_INTENT_PERCEPTUAL && *intent != PIKA_COLOR_RENDERING_INTENT_RELATIVE_COLORIMETRIC && *intent != PIKA_COLOR_RENDERING_INTENT_SATURATION && *intent != PIKA_COLOR_RENDERING_INTENT_ABSOLUTE_COLORIMETRIC) { pika_message (info->pika, G_OBJECT (info->progress), PIKA_MESSAGE_ERROR, "Unknown simulation rendering intent: %d", *intent); } else { pika_image_set_simulation_intent (image, (PikaColorRenderingIntent) *intent); } } else { pika_message (info->pika, G_OBJECT (info->progress), PIKA_MESSAGE_ERROR, "Invalid simulation intent data"); } pika_parasite_list_remove (private->parasites, pika_parasite_get_name (parasite)); } /* check for simulation bpc parasite */ parasite = pika_image_parasite_find (PIKA_IMAGE (image), "image-simulation-bpc"); if (parasite) { guint32 parasite_size; const guint8 *bpc; gboolean status = FALSE; PikaImagePrivate *private = PIKA_IMAGE_GET_PRIVATE (image); bpc = (const guint8 *) pika_parasite_get_data (parasite, ¶site_size); if (parasite_size == 1) { if (*bpc) status = TRUE; pika_image_set_simulation_bpc (image, status); } else { pika_message (info->pika, G_OBJECT (info->progress), PIKA_MESSAGE_ERROR, "Invalid simulation bpc data"); } pika_parasite_list_remove (private->parasites, pika_parasite_get_name (parasite)); } /* check for a PikaGrid parasite */ parasite = pika_image_parasite_find (PIKA_IMAGE (image), pika_grid_parasite_name ()); if (parasite) { PikaGrid *grid = pika_grid_from_parasite (parasite); if (grid) { PikaImagePrivate *private = PIKA_IMAGE_GET_PRIVATE (image); pika_parasite_list_remove (private->parasites, pika_parasite_get_name (parasite)); pika_image_set_grid (PIKA_IMAGE (image), grid, FALSE); g_object_unref (grid); } } /* check for a metadata parasite */ parasite = pika_image_parasite_find (PIKA_IMAGE (image), "pika-image-metadata"); if (parasite) { PikaImagePrivate *private = PIKA_IMAGE_GET_PRIVATE (image); PikaMetadata *metadata = NULL; gchar *meta_string; guint32 parasite_data_size; meta_string = (gchar *) pika_parasite_get_data (parasite, ¶site_data_size); if (meta_string) { meta_string = g_strndup (meta_string, parasite_data_size); metadata = pika_metadata_deserialize (meta_string); g_free (meta_string); } if (metadata) { has_metadata = TRUE; pika_image_set_metadata (image, metadata, FALSE); g_object_unref (metadata); } pika_parasite_list_remove (private->parasites, pika_parasite_get_name (parasite)); } /* check for symmetry parasites */ syms = pika_image_symmetry_list (); for (iter = syms; iter; iter = g_list_next (iter)) { GType type = (GType) iter->data; gchar *parasite_name = pika_symmetry_parasite_name (type); parasite = pika_image_parasite_find (image, parasite_name); g_free (parasite_name); if (parasite) { PikaSymmetry *sym = pika_symmetry_from_parasite (parasite, image, type); if (sym) { PikaImagePrivate *private = PIKA_IMAGE_GET_PRIVATE (image); pika_parasite_list_remove (private->parasites, pika_parasite_get_name (parasite)); pika_image_symmetry_add (image, sym); g_signal_emit_by_name (sym, "active-changed", NULL); if (sym->active) pika_image_set_active_symmetry (image, type); } } } g_list_free (syms); /* migrate the old "exif-data" parasite */ parasite = pika_image_parasite_find (PIKA_IMAGE (image), "exif-data"); if (parasite) { PikaImagePrivate *private = PIKA_IMAGE_GET_PRIVATE (image); if (has_metadata) { g_printerr ("xcf-load: inconsistent metadata discovered: XCF file " "has both 'pika-image-metadata' and 'exif-data' " "parasites, dropping old 'exif-data'\n"); } else { PikaMetadata *metadata = pika_image_get_metadata (image); GError *my_error = NULL; const guchar *parasite_data; guint32 parasite_data_size; parasite_data = (const guchar *) pika_parasite_get_data (parasite, ¶site_data_size); if (metadata) g_object_ref (metadata); else metadata = pika_metadata_new (); if (! pika_metadata_set_from_exif (metadata, parasite_data, parasite_data_size, &my_error)) { pika_message (pika, G_OBJECT (info->progress), PIKA_MESSAGE_WARNING, _("Corrupt 'exif-data' parasite discovered.\n" "Exif data could not be migrated: %s"), my_error->message); g_clear_error (&my_error); } else { pika_image_set_metadata (image, metadata, FALSE); } g_object_unref (metadata); } pika_parasite_list_remove (private->parasites, pika_parasite_get_name (parasite)); } /* migrate the old "pika-metadata" parasite */ parasite = pika_image_parasite_find (PIKA_IMAGE (image), "pika-metadata"); if (parasite) { PikaImagePrivate *private = PIKA_IMAGE_GET_PRIVATE (image); const gchar *xmp_data; guint32 xmp_length; xmp_data = (gchar *) pika_parasite_get_data (parasite, &xmp_length); if (has_metadata) { g_printerr ("xcf-load: inconsistent metadata discovered: XCF file " "has both 'pika-image-metadata' and 'pika-metadata' " "parasites, dropping old 'pika-metadata'\n"); } else if (xmp_length < 14 || strncmp (xmp_data, "PIKA_XMP_1", 10) != 0) { pika_message (pika, G_OBJECT (info->progress), PIKA_MESSAGE_WARNING, _("Corrupt 'pika-metadata' parasite discovered.\n" "XMP data could not be migrated.")); } else { PikaMetadata *metadata = pika_image_get_metadata (image); GError *my_error = NULL; if (metadata) g_object_ref (metadata); else metadata = pika_metadata_new (); if (! pika_metadata_set_from_xmp (metadata, (const guint8 *) xmp_data + 10, xmp_length - 10, &my_error)) { /* XMP metadata from 2.8.x or earlier can be really messed up. * Let's make the message more user friendly so they will * understand that we can't do anything about it. * See issue #987. */ pika_message (pika, G_OBJECT (info->progress), PIKA_MESSAGE_WARNING, _("Corrupt XMP metadata saved by an older version of " "PIKA could not be converted and will be ignored.\n" "If you don't know what XMP is, you most likely don't " "need it. Reported error: %s."), my_error->message); g_clear_error (&my_error); } else { pika_image_set_metadata (image, metadata, FALSE); } g_object_unref (metadata); } pika_parasite_list_remove (private->parasites, pika_parasite_get_name (parasite)); } /* check for a pika-xcf-compatibility-mode parasite */ parasite = pika_image_parasite_find (PIKA_IMAGE (image), "pika-xcf-compatibility-mode"); if (parasite) { PikaImagePrivate *private = PIKA_IMAGE_GET_PRIVATE (image); /* just ditch it, it's unused but shouldn't be re-saved */ pika_parasite_list_remove (private->parasites, pika_parasite_get_name (parasite)); } xcf_progress_update (info); while (TRUE) { PikaLayer *layer; GList *item_path = NULL; /* read in the offset of the next layer */ xcf_read_offset (info, &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; if (offset < saved_pos) { PIKA_LOG (XCF, "Invalid layer offset: %" G_GOFFSET_FORMAT " at offset: %" G_GOFFSET_FORMAT, offset, saved_pos); goto error; } /* 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) { n_broken_layers++; if (! xcf_seek_pos (info, saved_pos, NULL)) { if (item_path) g_list_free (item_path); goto error; } /* Don't just stop at the first broken layer. Load as much as * possible. */ if (! item_path) { PikaContainer *layers = pika_image_get_layers (image); item_path = g_list_prepend (NULL, GUINT_TO_POINTER (pika_container_get_n_children (layers))); broken_paths = g_list_prepend (broken_paths, item_path); } continue; } if (broken_paths && item_path) { /* Item paths may be a problem when layers are missing. */ xcf_fix_item_path (layer, &item_path, broken_paths); } num_successful_elements++; xcf_progress_update (info); /* suspend layer-group size updates */ if (PIKA_IS_GROUP_LAYER (layer)) { PikaGroupLayer *group = PIKA_GROUP_LAYER (layer); group_layers = g_list_prepend (group_layers, group); pika_group_layer_suspend_resize (group, FALSE); } /* add the layer to the image if its not the floating selection */ if (layer != info->floating_sel) { PikaContainer *layers = pika_image_get_layers (image); PikaContainer *container; PikaLayer *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 = PIKA_LAYER (pika_item_stack_get_parent_by_path (PIKA_ITEM_STACK (layers), item_path, NULL)); container = pika_viewable_get_children (PIKA_VIEWABLE (parent)); g_list_free (item_path); } else { parent = NULL; container = layers; } pika_image_add_layer (image, layer, parent, pika_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; } /* resume layer-group size updates, in reverse order */ for (iter = group_layers; iter; iter = g_list_next (iter)) { PikaGroupLayer *group = iter->data; pika_group_layer_resume_resize (group, FALSE); } g_clear_pointer (&group_layers, g_list_free); if (broken_paths) { g_list_free_full (broken_paths, (GDestroyNotify) g_list_free); broken_paths = NULL; } while (TRUE) { PikaChannel *channel; /* read in the offset of the next channel */ xcf_read_offset (info, &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; if (offset < saved_pos) { PIKA_LOG (XCF, "Invalid channel offset: %" G_GOFFSET_FORMAT " at offset: % "G_GOFFSET_FORMAT, offset, saved_pos); goto error; } /* 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) { n_broken_channels++; PIKA_LOG (XCF, "Failed to load channel."); if (! xcf_seek_pos (info, saved_pos, NULL)) goto error; continue; } num_successful_elements++; xcf_progress_update (info); /* add the channel to the image if its not the selection */ if (channel != pika_image_get_mask (image)) pika_image_add_channel (image, channel, NULL, /* FIXME tree */ pika_container_get_n_children (pika_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; } if (n_broken_layers == 0 && n_broken_channels == 0) xcf_load_add_masks (image); if (info->floating_sel && info->floating_sel_drawable) { /* we didn't fix the loaded floating selection's format before * because we didn't know if it needed the layer space */ if (PIKA_IS_LAYER (info->floating_sel_drawable) && pika_drawable_is_gray (PIKA_DRAWABLE (info->floating_sel))) pika_layer_fix_format_space (info->floating_sel, TRUE, FALSE); floating_sel_attach (info->floating_sel, info->floating_sel_drawable); } if (info->file_version >= 18) { while (TRUE) { PikaVectors *vectors; /* read in the offset of the next path */ xcf_read_offset (info, &offset, 1); /* if the offset is 0 then we are at the end * of the path list. */ if (offset == 0) break; /* save the current position as it is where the * next channel offset is stored. */ saved_pos = info->cp; if (offset < saved_pos) { PIKA_LOG (XCF, "Invalid path offset: %" G_GOFFSET_FORMAT " at offset: % "G_GOFFSET_FORMAT, offset, saved_pos); goto error; } /* seek to the path offset */ if (! xcf_seek_pos (info, offset, NULL)) goto error; /* read in the path */ vectors = xcf_load_vectors (info, image); if (! vectors) { n_broken_vectors++; PIKA_LOG (XCF, "Failed to load path."); if (! xcf_seek_pos (info, saved_pos, NULL)) goto error; continue; } num_successful_elements++; xcf_progress_update (info); pika_image_add_vectors (image, vectors, NULL, /* can't be a tree */ pika_container_get_n_children (pika_image_get_vectors (image)), FALSE); /* restore the saved position so we'll be ready to * read the next offset. */ if (! xcf_seek_pos (info, saved_pos, NULL)) goto error; } } if (info->selected_layers) { pika_image_set_selected_layers (image, info->selected_layers); g_clear_pointer (&info->selected_layers, g_list_free); } if (info->selected_channels) pika_image_set_selected_channels (image, info->selected_channels); if (info->selected_vectors) pika_image_set_selected_vectors (image, info->selected_vectors); /* We don't have linked items concept anymore. We transform formerly * linked items into stored sets of named items instead. */ if (info->linked_layers) { PikaItemList *set; set = pika_item_list_named_new (image, PIKA_TYPE_LAYER, _("Linked Layers"), info->linked_layers); pika_image_store_item_set (image, set); g_clear_pointer (&info->linked_layers, g_list_free); } if (info->linked_channels) { PikaItemList *set; set = pika_item_list_named_new (image, PIKA_TYPE_CHANNEL, _("Linked Channels"), info->linked_channels); pika_image_store_item_set (image, set); g_clear_pointer (&info->linked_channels, g_list_free); } if (info->linked_paths) { /* It is kind of ugly but vectors are really implemented as * exception in our XCF spec and building over it seems like a * mistake. Since I'm seriously not sure this would be much of an * issue, I'll let it as it for now. * Note that it's still possible to multi-select paths. It's only * not possible to store these selections. * * Only warn for more than 1 linked path. Less is kind of * pointless and doesn't deserve worrying people for no reason. */ if (g_list_length (info->linked_paths) > 1) g_printerr ("xcf: some paths were linked. " "PIKA does not support linked paths since version 3.0.\n"); #if 0 PikaItemList *set; set = pika_item_list_named_new (image, PIKA_TYPE_VECTORS, _("Linked Paths"), info->linked_paths); pika_image_store_item_set (image, set); #endif g_clear_pointer (&info->linked_paths, g_list_free); } for (iter = g_list_last (info->layer_sets); iter; iter = iter->prev) { if (iter->data) pika_image_store_item_set (image, iter->data); } g_list_free (info->layer_sets); for (iter = g_list_last (info->channel_sets); iter; iter = iter->prev) { if (iter->data) pika_image_store_item_set (image, iter->data); } g_list_free (info->channel_sets); if (info->file) pika_image_set_file (image, info->file); if (info->tattoo_state > 0) pika_image_set_tattoo_state (image, info->tattoo_state); if (n_broken_layers > 0 || n_broken_channels > 0 || n_broken_vectors > 0) goto error; pika_image_undo_enable (image); return image; error: if (num_successful_elements == 0) goto hard_error; g_clear_pointer (&group_layers, g_list_free); if (broken_paths) { g_list_free_full (broken_paths, (GDestroyNotify) g_list_free); broken_paths = NULL; } pika_message_literal (pika, G_OBJECT (info->progress), PIKA_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); pika_image_undo_enable (image); return image; hard_error: g_clear_pointer (&group_layers, g_list_free); if (broken_paths) { g_list_free_full (broken_paths, (GDestroyNotify) g_list_free); broken_paths = NULL; } 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.")); g_clear_object (&image); return NULL; } static void xcf_load_add_masks (PikaImage *image) { GList *layers; GList *list; layers = pika_image_get_layer_list (image); for (list = layers; list; list = g_list_next (list)) { PikaLayer *layer = list->data; PikaLayerMask *mask; mask = g_object_get_data (G_OBJECT (layer), "pika-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), "pika-layer-mask-apply")); edit_mask = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (layer), "pika-layer-mask-edit")); show_mask = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (layer), "pika-layer-mask-show")); pika_layer_add_mask (layer, mask, FALSE, NULL); pika_layer_set_apply_mask (layer, apply_mask, FALSE); pika_layer_set_edit_mask (layer, edit_mask); pika_layer_set_show_mask (layer, show_mask, FALSE); g_object_set_data (G_OBJECT (layer), "pika-layer-mask", NULL); g_object_set_data (G_OBJECT (layer), "pika-layer-mask-apply", NULL); g_object_set_data (G_OBJECT (layer), "pika-layer-mask-edit", NULL); g_object_set_data (G_OBJECT (layer), "pika-layer-mask-show", NULL); } } g_list_free (layers); } static gboolean xcf_load_image_props (XcfInfo *info, PikaImage *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[PIKA_IMAGE_COLORMAP_SIZE]; xcf_read_int32 (info, &n_colors, 1); if (n_colors > (PIKA_IMAGE_COLORMAP_SIZE / 3)) { pika_message (info->pika, G_OBJECT (info->progress), PIKA_MESSAGE_ERROR, "Maximum colormap size (%d) exceeded", PIKA_IMAGE_COLORMAP_SIZE); return FALSE; } if (info->file_version == 0) { gint i; pika_message_literal (info->pika, G_OBJECT (info->progress), PIKA_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 { xcf_read_int8 (info, cmap, n_colors * 3); } /* only set color map if image is indexed, this is just * sanity checking to make sure pika doesn't end up with * an image state that is impossible. */ if (pika_image_get_base_type (image) == PIKA_INDEXED) pika_image_set_colormap (image, cmap, n_colors, FALSE); PIKA_LOG (XCF, "prop colormap n_colors=%d", n_colors); } break; case PROP_COMPRESSION: { guint8 compression; xcf_read_int8 (info, (guint8 *) &compression, 1); if ((compression != COMPRESS_NONE) && (compression != COMPRESS_RLE) && (compression != COMPRESS_ZLIB) && (compression != COMPRESS_FRACTAL)) { pika_message (info->pika, G_OBJECT (info->progress), PIKA_MESSAGE_ERROR, "Unknown compression type: %d", (gint) compression); return FALSE; } info->compression = compression; pika_image_set_xcf_compression (image, compression >= COMPRESS_ZLIB); PIKA_LOG (XCF, "prop compression=%d", compression); } break; case PROP_GUIDES: { PikaImagePrivate *private = PIKA_IMAGE_GET_PRIVATE (image); gint32 position; gint8 orientation; gint i, nguides; nguides = prop_size / (4 + 1); for (i = 0; i < nguides; i++) { xcf_read_int32 (info, (guint32 *) &position, 1); xcf_read_int8 (info, (guint8 *) &orientation, 1); /* Some very old XCF had -1 guides which have been * skipped since 2003 (commit 909a28ced2). * Then XCF up to version 14 only had positive guide * positions. * Since XCF 15 (PIKA 3.0), off-canvas guides became a * thing. */ if (info->file_version < 15 && position < 0) continue; PIKA_LOG (XCF, "prop guide orientation=%d position=%d", orientation, position); switch (orientation) { case XCF_ORIENTATION_HORIZONTAL: if (info->file_version < 15 && position > pika_image_get_height (image)) pika_message (info->pika, G_OBJECT (info->progress), PIKA_MESSAGE_WARNING, "Ignoring off-canvas horizontal guide (position %d) in XCF %d file", position, info->file_version); else pika_image_add_hguide (image, position, FALSE); break; case XCF_ORIENTATION_VERTICAL: if (info->file_version < 15 && position > pika_image_get_width (image)) pika_message (info->pika, G_OBJECT (info->progress), PIKA_MESSAGE_WARNING, "Ignoring off-canvas vertical guide (position %d) in XCF %d file", position, info->file_version); else pika_image_add_vguide (image, position, FALSE); break; default: pika_message_literal (info->pika, G_OBJECT (info->progress), PIKA_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: { gint n_sample_points, i; n_sample_points = prop_size / (5 * 4); for (i = 0; i < n_sample_points; i++) { PikaSamplePoint *sample_point; gint32 x, y; PikaColorPickMode pick_mode; guint32 padding[2] = { 0, }; xcf_read_int32 (info, (guint32 *) &x, 1); xcf_read_int32 (info, (guint32 *) &y, 1); xcf_read_int32 (info, (guint32 *) &pick_mode, 1); xcf_read_int32 (info, (guint32 *) padding, 2); PIKA_LOG (XCF, "prop sample point x=%d y=%d mode=%d", x, y, pick_mode); if (pick_mode > PIKA_COLOR_PICK_MODE_LAST) pick_mode = PIKA_COLOR_PICK_MODE_PIXEL; sample_point = pika_image_add_sample_point_at_pos (image, x, y, FALSE); pika_image_set_sample_point_pick_mode (image, sample_point, pick_mode, FALSE); } } break; case PROP_OLD_SAMPLE_POINTS: { gint32 x, y; gint i, n_sample_points; /* if there are already sample points, we loaded the new * prop before */ if (pika_image_get_sample_points (image)) { if (! xcf_skip_unknown_prop (info, prop_size)) return FALSE; break; } n_sample_points = prop_size / (4 + 4); for (i = 0; i < n_sample_points; i++) { xcf_read_int32 (info, (guint32 *) &x, 1); xcf_read_int32 (info, (guint32 *) &y, 1); PIKA_LOG (XCF, "prop old sample point x=%d y=%d", x, y); pika_image_add_sample_point_at_pos (image, x, y, FALSE); } } break; case PROP_RESOLUTION: { gfloat xres, yres; xcf_read_float (info, &xres, 1); xcf_read_float (info, &yres, 1); PIKA_LOG (XCF, "prop resolution x=%f y=%f", xres, yres); if (xres < PIKA_MIN_RESOLUTION || xres > PIKA_MAX_RESOLUTION || yres < PIKA_MIN_RESOLUTION || yres > PIKA_MAX_RESOLUTION) { PikaTemplate *template = image->pika->config->default_image; pika_message_literal (info->pika, G_OBJECT (info->progress), PIKA_MESSAGE_WARNING, "Warning, resolution out of range in XCF file"); xres = pika_template_get_resolution_x (template); yres = pika_template_get_resolution_y (template); } pika_image_set_resolution (image, xres, yres); } break; case PROP_TATTOO: { xcf_read_int32 (info, &info->tattoo_state, 1); PIKA_LOG (XCF, "prop tattoo state=%d", info->tattoo_state); } break; case PROP_PARASITES: { goffset base = info->cp; while (info->cp - base < prop_size) { PikaParasite *p = xcf_load_parasite (info); GError *error = NULL; if (! p) { pika_message (info->pika, G_OBJECT (info->progress), PIKA_MESSAGE_WARNING, "Invalid image parasite found. " "Possibly corrupt XCF file."); xcf_seek_pos (info, base + prop_size, NULL); continue; } if (! pika_image_parasite_validate (image, p, &error)) { pika_message (info->pika, G_OBJECT (info->progress), PIKA_MESSAGE_WARNING, "Warning, invalid image parasite in XCF file: %s", error->message); g_clear_error (&error); } else { pika_image_parasite_attach (image, p, FALSE); } pika_parasite_free (p); } if (info->cp - base != prop_size) pika_message_literal (info->pika, G_OBJECT (info->progress), PIKA_MESSAGE_WARNING, "Error while loading an image's parasites"); } break; case PROP_UNIT: { guint32 unit; xcf_read_int32 (info, &unit, 1); PIKA_LOG (XCF, "prop unit=%d", unit); if ((unit <= PIKA_UNIT_PIXEL) || (unit >= pika_unit_get_number_of_built_in_units ())) { pika_message_literal (info->pika, G_OBJECT (info->progress), PIKA_MESSAGE_WARNING, "Warning, unit out of range in XCF file, " "falling back to inches"); unit = PIKA_UNIT_INCH; } pika_image_set_unit (image, unit); } break; case PROP_PATHS: { goffset base = info->cp; if (info->file_version >= 18) pika_message (info->pika, G_OBJECT (info->progress), PIKA_MESSAGE_WARNING, "XCF %d file should not contain PROP_PATHS image properties", info->file_version); if (! xcf_load_old_paths (info, image)) xcf_seek_pos (info, base + prop_size, NULL); } break; case PROP_USER_UNIT: { gchar *unit_strings[5]; float factor; guint32 digits; PikaUnit unit; gint num_units; gint i; xcf_read_float (info, &factor, 1); xcf_read_int32 (info, &digits, 1); xcf_read_string (info, unit_strings, 5); for (i = 0; i < 5; i++) if (unit_strings[i] == NULL) unit_strings[i] = g_strdup (""); num_units = pika_unit_get_number_of_units (); for (unit = pika_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 (pika_unit_get_factor (unit) - factor) < 1e-5) && (strcmp (unit_strings[0], pika_unit_get_identifier (unit)) == 0)) { break; } } /* no match */ if (unit == num_units) unit = pika_unit_new (unit_strings[0], factor, digits, unit_strings[1], unit_strings[2], unit_strings[3], unit_strings[4]); pika_image_set_unit (image, unit); for (i = 0; i < 5; i++) g_free (unit_strings[i]); } break; case PROP_VECTORS: { goffset base = info->cp; if (info->file_version >= 18) pika_message (info->pika, G_OBJECT (info->progress), PIKA_MESSAGE_WARNING, "XCF %d file should not contain PROP_VECTORS image properties", info->file_version); if (xcf_load_old_vectors (info, image)) { if (base + prop_size != info->cp) { g_printerr ("Mismatch in PROP_VECTORS size: " "skipping %" G_GOFFSET_FORMAT " 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_old_vectors already explained what was wrong */ xcf_seek_pos (info, base + prop_size, NULL); } } break; case PROP_ITEM_SET: { PikaItemList *set = NULL; gchar *label; GType item_type = 0; guint32 itype; guint32 method; xcf_read_int32 (info, &itype, 1); xcf_read_int32 (info, &method, 1); xcf_read_string (info, &label, 1); if (itype == 0) item_type = PIKA_TYPE_LAYER; else item_type = PIKA_TYPE_CHANNEL; if (itype > 1) { g_printerr ("xcf: unsupported item set '%s' type: %d (skipping)\n", label ? label : "unnamed", itype); /* Only case where we break because we wouldn't even * know where to categorize the item set anyway. */ break; } else if (label == NULL) { g_printerr ("xcf: item set without a name or pattern (skipping)\n"); } else if (method != G_MAXUINT32 && method > PIKA_SELECT_GLOB_PATTERN) { g_printerr ("xcf: unsupported item set '%s' selection method attribute: 0x%x (skipping)\n", label, method); } else { if (method == G_MAXUINT32) { /* Don't use pika_item_list_named_new() because it * doesn't allow NULL items (it would try to get the * selected items instead). */ set = g_object_new (PIKA_TYPE_ITEM_LIST, "image", image, "name", label, "is-pattern", FALSE, "item-type", item_type, "items", NULL, NULL); } else { set = pika_item_list_pattern_new (image, item_type, method, label); } } /* Note: we are still adding invalid item sets as NULL on * purpose, in order not to break order-base association * between PROP_ITEM_SET and PROP_ITEM_SET_ITEM. */ if (item_type == PIKA_TYPE_LAYER) info->layer_sets = g_list_prepend (info->layer_sets, set); else info->channel_sets = g_list_prepend (info->channel_sets, set); } break; default: #ifdef PIKA_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, PikaImage *image, PikaLayer **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->selected_layers = g_list_prepend (info->selected_layers, *layer); break; case PROP_FLOATING_SELECTION: info->floating_sel = *layer; xcf_read_offset (info, &info->floating_sel_offset, 1); break; case PROP_OPACITY: { guint32 opacity; xcf_read_int32 (info, &opacity, 1); pika_layer_set_opacity (*layer, (gdouble) opacity / 255.0, FALSE); } break; case PROP_FLOAT_OPACITY: { gfloat opacity; xcf_read_float (info, &opacity, 1); pika_layer_set_opacity (*layer, opacity, FALSE); } break; case PROP_VISIBLE: { gboolean visible; xcf_read_int32 (info, (guint32 *) &visible, 1); pika_item_set_visible (PIKA_ITEM (*layer), visible, FALSE); } break; case PROP_LINKED: { gboolean linked; xcf_read_int32 (info, (guint32 *) &linked, 1); if (linked) info->linked_layers = g_list_prepend (info->linked_layers, *layer); } break; case PROP_COLOR_TAG: { PikaColorTag color_tag; xcf_read_int32 (info, (guint32 *) &color_tag, 1); pika_item_set_color_tag (PIKA_ITEM (*layer), color_tag, FALSE); } break; case PROP_LOCK_CONTENT: { gboolean lock_content; xcf_read_int32 (info, (guint32 *) &lock_content, 1); if (pika_item_can_lock_content (PIKA_ITEM (*layer))) pika_item_set_lock_content (PIKA_ITEM (*layer), lock_content, FALSE); } break; case PROP_LOCK_ALPHA: { gboolean lock_alpha; xcf_read_int32 (info, (guint32 *) &lock_alpha, 1); if (pika_layer_can_lock_alpha (*layer)) pika_layer_set_lock_alpha (*layer, lock_alpha, FALSE); } break; case PROP_LOCK_POSITION: { gboolean lock_position; xcf_read_int32 (info, (guint32 *) &lock_position, 1); if (pika_item_can_lock_position (PIKA_ITEM (*layer))) pika_item_set_lock_position (PIKA_ITEM (*layer), lock_position, FALSE); } break; case PROP_LOCK_VISIBILITY: { gboolean lock_visibility; xcf_read_int32 (info, (guint32 *) &lock_visibility, 1); if (pika_item_can_lock_visibility (PIKA_ITEM (*layer))) pika_item_set_lock_visibility (PIKA_ITEM (*layer), lock_visibility, FALSE); } break; case PROP_APPLY_MASK: xcf_read_int32 (info, (guint32 *) apply_mask, 1); break; case PROP_EDIT_MASK: xcf_read_int32 (info, (guint32 *) edit_mask, 1); break; case PROP_SHOW_MASK: xcf_read_int32 (info, (guint32 *) show_mask, 1); break; case PROP_OFFSETS: { gint32 offset_x; gint32 offset_y; xcf_read_int32 (info, (guint32 *) &offset_x, 1); xcf_read_int32 (info, (guint32 *) &offset_y, 1); if (offset_x < -PIKA_MAX_IMAGE_SIZE || offset_x > PIKA_MAX_IMAGE_SIZE) { g_printerr ("unexpected item offset_x (%d) in XCF, " "setting to 0\n", offset_x); offset_x = 0; } if (offset_y < -PIKA_MAX_IMAGE_SIZE || offset_y > PIKA_MAX_IMAGE_SIZE) { g_printerr ("unexpected item offset_y (%d) in XCF, " "setting to 0\n", offset_y); offset_y = 0; } pika_item_set_offset (PIKA_ITEM (*layer), offset_x, offset_y); } break; case PROP_MODE: { PikaLayerMode mode; xcf_read_int32 (info, (guint32 *) &mode, 1); if (mode == PIKA_LAYER_MODE_OVERLAY_LEGACY) mode = PIKA_LAYER_MODE_SOFTLIGHT_LEGACY; pika_layer_set_mode (*layer, mode, FALSE); } break; case PROP_BLEND_SPACE: { gint32 blend_space; xcf_read_int32 (info, (guint32 *) &blend_space, 1); /* if blend_space < 0 it was originally AUTO, and its negative is * the actual value AUTO used to map to at the time the file was * saved. if AUTO still maps to the same value, keep using AUTO * for the property; otherwise, use the concrete value. */ if (blend_space < 0) { PikaLayerMode mode = pika_layer_get_mode (*layer); blend_space = -blend_space; if (blend_space == pika_layer_mode_get_blend_space (mode)) blend_space = PIKA_LAYER_COLOR_SPACE_AUTO; else PIKA_LOG (XCF, "BLEND_SPACE: AUTO => %d", blend_space); } pika_layer_set_blend_space (*layer, blend_space, FALSE); } break; case PROP_COMPOSITE_SPACE: { gint32 composite_space; xcf_read_int32 (info, (guint32 *) &composite_space, 1); /* if composite_space < 0 it was originally AUTO, and its negative * is the actual value AUTO used to map to at the time the file was * saved. if AUTO still maps to the same value, keep using AUTO * for the property; otherwise, use the concrete value. */ if (composite_space < 0) { PikaLayerMode mode = pika_layer_get_mode (*layer); composite_space = -composite_space; if (composite_space == pika_layer_mode_get_composite_space (mode)) composite_space = PIKA_LAYER_COLOR_SPACE_AUTO; else PIKA_LOG (XCF, "COMPOSITE_SPACE: AUTO => %d", composite_space); } pika_layer_set_composite_space (*layer, composite_space, FALSE); } break; case PROP_COMPOSITE_MODE: { gint32 composite_mode; xcf_read_int32 (info, (guint32 *) &composite_mode, 1); /* if composite_mode < 0 it was originally AUTO, and its negative * is the actual value AUTO used to map to at the time the file was * saved. if AUTO still maps to the same value, keep using AUTO * for the property; otherwise, use the concrete value. */ if (composite_mode < 0) { PikaLayerMode mode = pika_layer_get_mode (*layer); composite_mode = -composite_mode; if (composite_mode == pika_layer_mode_get_composite_mode (mode)) composite_mode = PIKA_LAYER_COMPOSITE_AUTO; else PIKA_LOG (XCF, "COMPOSITE_MODE: AUTO => %d", composite_mode); } pika_layer_set_composite_mode (*layer, composite_mode, FALSE); } break; case PROP_TATTOO: { PikaTattoo tattoo; xcf_read_int32 (info, (guint32 *) &tattoo, 1); pika_item_set_tattoo (PIKA_ITEM (*layer), tattoo); } break; case PROP_PARASITES: { goffset base = info->cp; while (info->cp - base < prop_size) { PikaParasite *p = xcf_load_parasite (info); GError *error = NULL; if (! p) return FALSE; if (! pika_item_parasite_validate (PIKA_ITEM (*layer), p, &error)) { pika_message (info->pika, G_OBJECT (info->progress), PIKA_MESSAGE_WARNING, "Warning, invalid layer parasite in XCF file: %s", error->message); g_clear_error (&error); } else { pika_item_parasite_attach (PIKA_ITEM (*layer), p, FALSE); } pika_parasite_free (p); } if (info->cp - base != prop_size) pika_message_literal (info->pika, G_OBJECT (info->progress), PIKA_MESSAGE_WARNING, "Error while loading a layer's parasites"); } break; case PROP_TEXT_LAYER_FLAGS: xcf_read_int32 (info, text_layer_flags, 1); break; case PROP_GROUP_ITEM: { PikaLayer *group; gboolean is_selected_layer; /* We're going to delete *layer, Don't leave its pointers * in @info. After that, we'll restore them back with the * new pointer. See bug #767873. */ is_selected_layer = (g_list_find (info->selected_layers, *layer ) != NULL); if (is_selected_layer) info->selected_layers = g_list_remove (info->selected_layers, *layer); if (*layer == info->floating_sel) info->floating_sel = NULL; group = pika_group_layer_new (image); pika_object_set_name (PIKA_OBJECT (group), pika_object_get_name (*layer)); g_object_ref_sink (*layer); g_object_unref (*layer); *layer = group; if (is_selected_layer) info->selected_layers = g_list_prepend (info->selected_layers, *layer); /* Don't restore info->floating_sel because group layers * can't be floating selections */ } break; case PROP_ITEM_PATH: { goffset base = info->cp; GList *path = NULL; while (info->cp - base < prop_size) { guint32 index; if (xcf_read_int32 (info, &index, 1) != 4) { g_list_free (path); return FALSE; } path = g_list_append (path, GUINT_TO_POINTER (index)); } *item_path = path; } break; case PROP_GROUP_ITEM_FLAGS: xcf_read_int32 (info, group_layer_flags, 1); break; case PROP_ITEM_SET_ITEM: { PikaItemList *set; guint32 n; xcf_read_int32 (info, &n, 1); set = g_list_nth_data (info->layer_sets, n); if (set == NULL) g_printerr ("xcf: layer '%s' cannot be added to unknown layer set at index %d (skipping)\n", pika_object_get_name (*layer), n); else if (! g_type_is_a (G_TYPE_FROM_INSTANCE (*layer), pika_item_list_get_item_type (set))) g_printerr ("xcf: layer '%s' cannot be added to item set '%s' with item type %s (skipping)\n", pika_object_get_name (*layer), pika_object_get_name (set), g_type_name (pika_item_list_get_item_type (set))); else if (pika_item_list_is_pattern (set, NULL)) g_printerr ("xcf: layer '%s' cannot be added to pattern item set '%s' (skipping)\n", pika_object_get_name (*layer), pika_object_get_name (set)); else pika_item_list_add (set, PIKA_ITEM (*layer)); } break; default: #ifdef PIKA_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_check_layer_props (XcfInfo *info, GList **item_path, gboolean *is_group_layer, gboolean *is_text_layer) { PropType prop_type; guint32 prop_size; g_return_val_if_fail (*is_group_layer == FALSE, FALSE); g_return_val_if_fail (*is_text_layer == FALSE, FALSE); while (TRUE) { if (! xcf_load_prop (info, &prop_type, &prop_size)) return FALSE; switch (prop_type) { case PROP_END: return TRUE; case PROP_TEXT_LAYER_FLAGS: *is_text_layer = TRUE; if (! xcf_skip_unknown_prop (info, prop_size)) return FALSE; break; case PROP_GROUP_ITEM: case PROP_GROUP_ITEM_FLAGS: *is_group_layer = TRUE; if (! xcf_skip_unknown_prop (info, prop_size)) return FALSE; break; case PROP_ITEM_PATH: { goffset base = info->cp; GList *path = NULL; while (info->cp - base < prop_size) { guint32 index; if (xcf_read_int32 (info, &index, 1) != 4) { g_list_free (path); return FALSE; } path = g_list_append (path, GUINT_TO_POINTER (index)); } *item_path = path; } break; case PROP_ACTIVE_LAYER: case PROP_FLOATING_SELECTION: case PROP_OPACITY: case PROP_FLOAT_OPACITY: case PROP_VISIBLE: case PROP_LINKED: case PROP_COLOR_TAG: case PROP_LOCK_CONTENT: case PROP_LOCK_ALPHA: case PROP_LOCK_POSITION: case PROP_LOCK_VISIBILITY: case PROP_APPLY_MASK: case PROP_EDIT_MASK: case PROP_SHOW_MASK: case PROP_OFFSETS: case PROP_MODE: case PROP_BLEND_SPACE: case PROP_COMPOSITE_SPACE: case PROP_COMPOSITE_MODE: case PROP_TATTOO: case PROP_PARASITES: case PROP_ITEM_SET_ITEM: if (! xcf_skip_unknown_prop (info, prop_size)) return FALSE; /* Just ignore for now. */ break; default: #ifdef PIKA_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, PikaImage *image, PikaChannel **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->selected_channels = g_list_prepend (info->selected_channels, *channel); break; case PROP_SELECTION: { PikaChannel *mask; GList *iter; /* We're going to delete *channel, Don't leave its pointer * in @info. See bug #767873. */ for (iter = info->selected_channels; iter; iter = iter->next) if (*channel == iter->data) { info->selected_channels = g_list_delete_link (info->selected_channels, iter); break; } mask = pika_selection_new (image, pika_item_get_width (PIKA_ITEM (*channel)), pika_item_get_height (PIKA_ITEM (*channel))); pika_image_take_mask (image, mask); pika_drawable_steal_buffer (PIKA_DRAWABLE (mask), PIKA_DRAWABLE (*channel)); g_object_unref (*channel); *channel = mask; /* Don't restore info->selected_channels because the * selection can't be the active channel */ } break; case PROP_OPACITY: { guint32 opacity; xcf_read_int32 (info, &opacity, 1); pika_channel_set_opacity (*channel, opacity / 255.0, FALSE); } break; case PROP_FLOAT_OPACITY: { gfloat opacity; xcf_read_float (info, &opacity, 1); pika_channel_set_opacity (*channel, opacity, FALSE); } break; case PROP_VISIBLE: { gboolean visible; xcf_read_int32 (info, (guint32 *) &visible, 1); pika_item_set_visible (PIKA_ITEM (*channel), visible, FALSE); } break; case PROP_COLOR_TAG: { PikaColorTag color_tag; xcf_read_int32 (info, (guint32 *) &color_tag, 1); pika_item_set_color_tag (PIKA_ITEM (*channel), color_tag, FALSE); } break; case PROP_LINKED: { gboolean linked; xcf_read_int32 (info, (guint32 *) &linked, 1); if (linked) info->linked_channels = g_list_prepend (info->linked_channels, *channel); } break; case PROP_LOCK_CONTENT: { gboolean lock_content; xcf_read_int32 (info, (guint32 *) &lock_content, 1); if (pika_item_can_lock_content (PIKA_ITEM (*channel))) pika_item_set_lock_content (PIKA_ITEM (*channel), lock_content, FALSE); } break; case PROP_LOCK_POSITION: { gboolean lock_position; xcf_read_int32 (info, (guint32 *) &lock_position, 1); if (pika_item_can_lock_position (PIKA_ITEM (*channel))) pika_item_set_lock_position (PIKA_ITEM (*channel), lock_position, FALSE); } break; case PROP_LOCK_VISIBILITY: { gboolean lock_visibility; xcf_read_int32 (info, (guint32 *) &lock_visibility, 1); if (pika_item_can_lock_visibility (PIKA_ITEM (*channel))) pika_item_set_lock_visibility (PIKA_ITEM (*channel), lock_visibility, FALSE); } break; case PROP_SHOW_MASKED: { gboolean show_masked; xcf_read_int32 (info, (guint32 *) &show_masked, 1); pika_channel_set_show_masked (*channel, show_masked); } break; case PROP_COLOR: { guchar col[3]; xcf_read_int8 (info, (guint8 *) col, 3); pika_rgb_set_uchar (&(*channel)->color, col[0], col[1], col[2]); } break; case PROP_FLOAT_COLOR: { gfloat col[3]; xcf_read_float (info, col, 3); pika_rgb_set (&(*channel)->color, col[0], col[1], col[2]); } break; case PROP_TATTOO: { PikaTattoo tattoo; xcf_read_int32 (info, (guint32 *) &tattoo, 1); pika_item_set_tattoo (PIKA_ITEM (*channel), tattoo); } break; case PROP_PARASITES: { goffset base = info->cp; while ((info->cp - base) < prop_size) { PikaParasite *p = xcf_load_parasite (info); GError *error = NULL; if (! p) return FALSE; if (! pika_item_parasite_validate (PIKA_ITEM (*channel), p, &error)) { pika_message (info->pika, G_OBJECT (info->progress), PIKA_MESSAGE_WARNING, "Warning, invalid channel parasite in XCF file: %s", error->message); g_clear_error (&error); } else { pika_item_parasite_attach (PIKA_ITEM (*channel), p, FALSE); } pika_parasite_free (p); } if (info->cp - base != prop_size) pika_message_literal (info->pika, G_OBJECT (info->progress), PIKA_MESSAGE_WARNING, "Error while loading a channel's parasites"); } break; case PROP_ITEM_SET_ITEM: { PikaItemList *set; guint32 n; xcf_read_int32 (info, &n, 1); set = g_list_nth_data (info->channel_sets, n); if (set == NULL) g_printerr ("xcf: unknown channel set: %d (skipping)\n", n); else if (! g_type_is_a (G_TYPE_FROM_INSTANCE (*channel), pika_item_list_get_item_type (set))) g_printerr ("xcf: channel '%s' cannot be added to item set '%s' with item type %s (skipping)\n", pika_object_get_name (*channel), pika_object_get_name (set), g_type_name (pika_item_list_get_item_type (set))); else pika_item_list_add (set, PIKA_ITEM (*channel)); } break; default: #ifdef PIKA_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_vectors_props (XcfInfo *info, PikaImage *image, PikaVectors **vectors) { PropType prop_type; guint32 prop_size; while (TRUE) { if (! xcf_load_prop (info, &prop_type, &prop_size)) return FALSE; switch (prop_type) { case PROP_END: return TRUE; case PROP_SELECTED_PATH: info->selected_vectors = g_list_prepend (info->selected_vectors, *vectors); break; case PROP_VISIBLE: { gboolean visible; xcf_read_int32 (info, (guint32 *) &visible, 1); pika_item_set_visible (PIKA_ITEM (*vectors), visible, FALSE); } break; case PROP_COLOR_TAG: { PikaColorTag color_tag; xcf_read_int32 (info, (guint32 *) &color_tag, 1); pika_item_set_color_tag (PIKA_ITEM (*vectors), color_tag, FALSE); } break; case PROP_LOCK_CONTENT: { gboolean lock_content; xcf_read_int32 (info, (guint32 *) &lock_content, 1); if (pika_item_can_lock_content (PIKA_ITEM (*vectors))) pika_item_set_lock_content (PIKA_ITEM (*vectors), lock_content, FALSE); } break; case PROP_LOCK_POSITION: { gboolean lock_position; xcf_read_int32 (info, (guint32 *) &lock_position, 1); if (pika_item_can_lock_position (PIKA_ITEM (*vectors))) pika_item_set_lock_position (PIKA_ITEM (*vectors), lock_position, FALSE); } break; case PROP_LOCK_VISIBILITY: { gboolean lock_visibility; xcf_read_int32 (info, (guint32 *) &lock_visibility, 1); if (pika_item_can_lock_visibility (PIKA_ITEM (*vectors))) pika_item_set_lock_visibility (PIKA_ITEM (*vectors), lock_visibility, FALSE); } break; case PROP_TATTOO: { PikaTattoo tattoo; xcf_read_int32 (info, (guint32 *) &tattoo, 1); pika_item_set_tattoo (PIKA_ITEM (*vectors), tattoo); } break; case PROP_PARASITES: { goffset base = info->cp; while ((info->cp - base) < prop_size) { PikaParasite *p = xcf_load_parasite (info); GError *error = NULL; if (! p) return FALSE; if (! pika_item_parasite_validate (PIKA_ITEM (*vectors), p, &error)) { pika_message (info->pika, G_OBJECT (info->progress), PIKA_MESSAGE_WARNING, "Warning, invalid path parasite in XCF file: %s", error->message); g_clear_error (&error); } else { pika_item_parasite_attach (PIKA_ITEM (*vectors), p, FALSE); } pika_parasite_free (p); } if (info->cp - base != prop_size) pika_message_literal (info->pika, G_OBJECT (info->progress), PIKA_MESSAGE_WARNING, "Error while loading a path's parasites"); } break; #if 0 case PROP_ITEM_SET_ITEM: { PikaItemList *set; guint32 n; xcf_read_int32 (info, &n, 1); set = g_list_nth_data (info->vectors_sets, n); if (set == NULL) g_printerr ("xcf: unknown path set: %d (skipping)\n", n); else if (! g_type_is_a (G_TYPE_FROM_INSTANCE (*vectors), pika_item_list_get_item_type (set))) g_printerr ("xcf: path '%s' cannot be added to item set '%s' with item type %s (skipping)\n", pika_object_get_name (*vectors), pika_object_get_name (set), g_type_name (pika_item_list_get_item_type (set))); else pika_item_list_add (set, PIKA_ITEM (*vectors)); } break; #endif default: #ifdef PIKA_UNSTABLE g_printerr ("unexpected/unknown path property: %d (skipping)\n", prop_type); #endif if (! xcf_skip_unknown_prop (info, prop_size)) return FALSE; break; } } return FALSE; } static gboolean xcf_load_prop (XcfInfo *info, PropType *prop_type, guint32 *prop_size) { if (G_UNLIKELY (xcf_read_int32 (info, (guint32 *) prop_type, 1) != 4)) return FALSE; if (G_UNLIKELY (xcf_read_int32 (info, (guint32 *) prop_size, 1) != 4)) return FALSE; PIKA_LOG (XCF, "prop type=%d size=%u", *prop_type, *prop_size); return TRUE; } static PikaLayer * xcf_load_layer (XcfInfo *info, PikaImage *image, GList **item_path) { PikaLayer *layer; PikaLayerMask *layer_mask; goffset hierarchy_offset; goffset layer_mask_offset; gboolean apply_mask = TRUE; gboolean edit_mask = FALSE; gboolean show_mask = FALSE; GList *selected; GList *linked; gboolean floating; guint32 group_layer_flags = 0; guint32 text_layer_flags = 0; gint width; gint height; gint type; PikaImageBaseType base_type; gboolean has_alpha; const Babl *format; gboolean is_fs_drawable; gchar *name; goffset cur_offset; /* 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 */ xcf_read_int32 (info, (guint32 *) &width, 1); xcf_read_int32 (info, (guint32 *) &height, 1); xcf_read_int32 (info, (guint32 *) &type, 1); xcf_read_string (info, &name, 1); PIKA_LOG (XCF, "width=%d, height=%d, type=%d, name='%s'", width, height, type, name); switch (type) { case PIKA_RGB_IMAGE: base_type = PIKA_RGB; has_alpha = FALSE; break; case PIKA_RGBA_IMAGE: base_type = PIKA_RGB; has_alpha = TRUE; break; case PIKA_GRAY_IMAGE: base_type = PIKA_GRAY; has_alpha = FALSE; break; case PIKA_GRAYA_IMAGE: base_type = PIKA_GRAY; has_alpha = TRUE; break; case PIKA_INDEXED_IMAGE: base_type = PIKA_INDEXED; has_alpha = FALSE; break; case PIKA_INDEXEDA_IMAGE: base_type = PIKA_INDEXED; has_alpha = TRUE; break; default: g_free (name); return NULL; } if (width <= 0 || height <= 0 || width > PIKA_MAX_IMAGE_SIZE || height > PIKA_MAX_IMAGE_SIZE) { gboolean is_group_layer = FALSE; gboolean is_text_layer = FALSE; goffset saved_pos; saved_pos = info->cp; /* Load item path and check if this is a group or text layer. */ xcf_check_layer_props (info, item_path, &is_group_layer, &is_text_layer); if ((is_text_layer || is_group_layer) && xcf_seek_pos (info, saved_pos, NULL)) { /* Something is wrong, but leave a chance to the layer because * anyway group and text layer depends on their contents. */ width = height = 1; g_clear_pointer (item_path, g_list_free); } else { g_free (name); return NULL; } } if (base_type == PIKA_GRAY) { /* do not use pika_image_get_layer_format() because it might * be the floating selection of a channel or mask */ format = pika_image_get_format (image, base_type, pika_image_get_precision (image), has_alpha, NULL /* we will fix the space later */); } else { format = pika_image_get_layer_format (image, has_alpha); } /* create a new layer */ layer = pika_layer_new (image, width, height, format, name, PIKA_OPACITY_OPAQUE, PIKA_LAYER_MODE_NORMAL); 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; PIKA_LOG (XCF, "layer props loaded"); xcf_progress_update (info); /* call the evil text layer hack that might change our layer pointer */ selected = g_list_find (info->selected_layers, layer); linked = g_list_find (info->linked_layers, layer); floating = (info->floating_sel == layer); if (pika_text_layer_xcf_load_hack (&layer)) { pika_text_layer_set_xcf_flags (PIKA_TEXT_LAYER (layer), text_layer_flags); if (selected) { info->selected_layers = g_list_delete_link (info->selected_layers, selected); info->selected_layers = g_list_prepend (info->selected_layers, layer); } if (linked) { info->linked_layers = g_list_delete_link (info->linked_layers, linked); info->linked_layers = g_list_prepend (info->linked_layers, layer); } if (floating) info->floating_sel = layer; } /* if this is not the floating selection, we can fix the layer's * space already now, the function will do nothing if we already * created the layer with the right format */ if (! floating && base_type == PIKA_GRAY) pika_layer_fix_format_space (layer, FALSE, FALSE); /* read the hierarchy and layer mask offsets */ cur_offset = info->cp; xcf_read_offset (info, &hierarchy_offset, 1); xcf_read_offset (info, &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 (! pika_viewable_get_children (PIKA_VIEWABLE (layer))) { if (hierarchy_offset < cur_offset) { PIKA_LOG (XCF, "Invalid layer hierarchy offset!"); goto error; } if (! xcf_seek_pos (info, hierarchy_offset, NULL)) goto error; PIKA_LOG (XCF, "loading buffer"); if (! xcf_load_buffer (info, pika_drawable_get_buffer (PIKA_DRAWABLE (layer)))) goto error; PIKA_LOG (XCF, "buffer loaded"); xcf_progress_update (info); } else { gboolean expanded = group_layer_flags & XCF_GROUP_ITEM_EXPANDED; pika_viewable_set_expanded (PIKA_VIEWABLE (layer), expanded); } /* read in the layer mask */ if (layer_mask_offset != 0) { if (layer_mask_offset < cur_offset) { PIKA_LOG (XCF, "Invalid layer mask offset!"); goto error; } 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), "pika-layer-mask", g_object_ref_sink (layer_mask), (GDestroyNotify) g_object_unref); g_object_set_data (G_OBJECT (layer), "pika-layer-mask-apply", GINT_TO_POINTER (apply_mask)); g_object_set_data (G_OBJECT (layer), "pika-layer-mask-edit", GINT_TO_POINTER (edit_mask)); g_object_set_data (G_OBJECT (layer), "pika-layer-mask-show", GINT_TO_POINTER (show_mask)); } /* attach the floating selection... */ if (is_fs_drawable) info->floating_sel_drawable = PIKA_DRAWABLE (layer); return layer; error: info->selected_layers = g_list_remove (info->selected_layers, layer); if (info->floating_sel == layer) info->floating_sel = NULL; if (info->floating_sel_drawable == PIKA_DRAWABLE (layer)) info->floating_sel_drawable = NULL; g_object_unref (layer); return NULL; } static PikaChannel * xcf_load_channel (XcfInfo *info, PikaImage *image) { PikaChannel *channel; goffset hierarchy_offset; gint width; gint height; gboolean is_fs_drawable; gchar *name; PikaRGB color = { 0.0, 0.0, 0.0, PIKA_OPACITY_OPAQUE }; goffset cur_offset; /* 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 */ xcf_read_int32 (info, (guint32 *) &width, 1); xcf_read_int32 (info, (guint32 *) &height, 1); if (width <= 0 || height <= 0 || width > PIKA_MAX_IMAGE_SIZE || height > PIKA_MAX_IMAGE_SIZE) { PIKA_LOG (XCF, "Invalid channel size %d x %d.", width, height); return NULL; } xcf_read_string (info, &name, 1); PIKA_LOG (XCF, "Channel width=%d, height=%d, name='%s'", width, height, name); /* create a new channel */ channel = pika_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 offset */ cur_offset = info->cp; xcf_read_offset (info, &hierarchy_offset, 1); if (hierarchy_offset < cur_offset) { PIKA_LOG (XCF, "Invalid hierarchy offset!"); goto error; } /* read in the hierarchy */ if (! xcf_seek_pos (info, hierarchy_offset, NULL)) goto error; if (! xcf_load_buffer (info, pika_drawable_get_buffer (PIKA_DRAWABLE (channel)))) goto error; xcf_progress_update (info); if (is_fs_drawable) info->floating_sel_drawable = PIKA_DRAWABLE (channel); return channel; error: /* don't unref the selection of a partially loaded XCF */ if (channel != pika_image_get_mask (image)) { GList *iter; for (iter = info->selected_channels; iter; iter = iter->next) if (channel == iter->data) { info->selected_channels = g_list_delete_link (info->selected_channels, iter); break; } if (info->floating_sel_drawable == PIKA_DRAWABLE (channel)) info->floating_sel_drawable = NULL; g_object_unref (channel); } return NULL; } /* The new path structure since XCF 18. */ static PikaVectors * xcf_load_vectors (XcfInfo *info, PikaImage *image) { PikaVectors *vectors = NULL; gchar *name; guint32 version; guint32 plength; guint32 num_strokes; goffset base; gint i; /* read in the path name. */ xcf_read_string (info, &name, 1); PIKA_LOG (XCF, "Path name='%s'", name); /* create a new path */ vectors = pika_vectors_new (image, name); g_free (name); if (! vectors) return NULL; /* Read the path's payload size. */ xcf_read_int32 (info, (guint32 *) &plength, 1); base = info->cp; /* read in the path properties */ if (! xcf_load_vectors_props (info, image, &vectors)) goto error; PIKA_LOG (XCF, "path props loaded"); xcf_progress_update (info); xcf_read_int32 (info, (guint32 *) &version, 1); if (version != 1) { pika_message (info->pika, G_OBJECT (info->progress), PIKA_MESSAGE_WARNING, "Unknown vectors version: %d (skipping)", version); goto error; } /* Read the number of strokes. */ xcf_read_int32 (info, &num_strokes, 1); for (i = 0; i < num_strokes; i++) { guint32 stroke_type_id; guint32 closed; guint32 num_axes; guint32 num_control_points; guint32 type; gfloat coords[13] = PIKA_COORDS_DEFAULT_VALUES; PikaStroke *stroke; gint j; PikaValueArray *control_points; GValue value = G_VALUE_INIT; PikaAnchor anchor = { { 0, } }; GType stroke_type; g_value_init (&value, PIKA_TYPE_ANCHOR); xcf_read_int32 (info, &stroke_type_id, 1); xcf_read_int32 (info, &closed, 1); xcf_read_int32 (info, &num_axes, 1); xcf_read_int32 (info, &num_control_points, 1); #ifdef PIKA_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 = PIKA_TYPE_BEZIER_STROKE; break; default: g_printerr ("skipping unknown stroke type\n"); xcf_seek_pos (info, info->cp + 4 * num_axes * num_control_points, NULL); continue; } if (num_axes < 2 || num_axes > 6) { g_printerr ("bad number of axes in stroke description\n"); goto error; } control_points = pika_value_array_new (num_control_points); anchor.selected = FALSE; for (j = 0; j < num_control_points; j++) { xcf_read_int32 (info, &type, 1); xcf_read_float (info, coords, num_axes); anchor.type = type; anchor.position.x = coords[0]; anchor.position.y = coords[1]; anchor.position.pressure = coords[2]; anchor.position.xtilt = coords[3]; anchor.position.ytilt = coords[4]; anchor.position.wheel = coords[5]; g_value_set_boxed (&value, &anchor); pika_value_array_append (control_points, &value); #ifdef PIKA_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); pika_vectors_stroke_add (vectors, stroke); g_object_unref (stroke); pika_value_array_unref (control_points); } if (plength != info->cp - base) { pika_message (info->pika, G_OBJECT (info->progress), PIKA_MESSAGE_WARNING, "Path payload size does not match stored size (skipping)"); goto error; } return vectors; error: xcf_seek_pos (info, base + plength, NULL); g_clear_object (&vectors); return NULL; } static PikaLayerMask * xcf_load_layer_mask (XcfInfo *info, PikaImage *image) { PikaLayerMask *layer_mask; PikaChannel *channel; GList *iter; goffset hierarchy_offset; gint width; gint height; gboolean is_fs_drawable; gchar *name; PikaRGB color = { 0.0, 0.0, 0.0, PIKA_OPACITY_OPAQUE }; goffset cur_offset; /* 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 */ xcf_read_int32 (info, (guint32 *) &width, 1); xcf_read_int32 (info, (guint32 *) &height, 1); if (width <= 0 || height <= 0 || width > PIKA_MAX_IMAGE_SIZE || height > PIKA_MAX_IMAGE_SIZE) { PIKA_LOG (XCF, "Invalid layer mask size %d x %d.", width, height); return NULL; } xcf_read_string (info, &name, 1); PIKA_LOG (XCF, "Layer mask width=%d, height=%d, name='%s'", width, height, name); /* create a new layer mask */ layer_mask = pika_layer_mask_new (image, width, height, name, &color); g_free (name); if (! layer_mask) return NULL; /* read in the layer_mask properties */ channel = PIKA_CHANNEL (layer_mask); if (! xcf_load_channel_props (info, image, &channel)) goto error; xcf_progress_update (info); /* read the hierarchy offset */ cur_offset = info->cp; xcf_read_offset (info, &hierarchy_offset, 1); if (hierarchy_offset < cur_offset) { PIKA_LOG (XCF, "Invalid hierarchy offset!"); goto error; } /* read in the hierarchy */ if (! xcf_seek_pos (info, hierarchy_offset, NULL)) goto error; if (! xcf_load_buffer (info, pika_drawable_get_buffer (PIKA_DRAWABLE (layer_mask)))) goto error; xcf_progress_update (info); /* attach the floating selection... */ if (is_fs_drawable) info->floating_sel_drawable = PIKA_DRAWABLE (layer_mask); return layer_mask; error: for (iter = info->selected_channels; iter; iter = iter->next) if (layer_mask == iter->data) { info->selected_channels = g_list_delete_link (info->selected_channels, iter); break; } if (info->floating_sel_drawable == PIKA_DRAWABLE (layer_mask)) info->floating_sel_drawable = NULL; g_object_unref (layer_mask); return NULL; } static gboolean xcf_load_buffer (XcfInfo *info, GeglBuffer *buffer) { const Babl *format; goffset offset; gint width; gint height; gint bpp; goffset cur_offset; format = gegl_buffer_get_format (buffer); xcf_read_int32 (info, (guint32 *) &width, 1); xcf_read_int32 (info, (guint32 *) &height, 1); xcf_read_int32 (info, (guint32 *) &bpp, 1); /* make sure the values in the file correspond to the values * calculated when the GeglBuffer 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; cur_offset = info->cp; xcf_read_offset (info, &offset, 1); /* top level */ if (offset < cur_offset) { PIKA_LOG (XCF, "Invalid buffer offset!"); return FALSE; } /* 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; goffset saved_pos; goffset offset; goffset offset2; goffset max_data_length; 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); xcf_read_int32 (info, (guint32 *) &width, 1); xcf_read_int32 (info, (guint32 *) &height, 1); if (width != gegl_buffer_get_width (buffer) || height != gegl_buffer_get_height (buffer)) return FALSE; /* 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. */ max_data_length = XCF_TILE_WIDTH * XCF_TILE_HEIGHT * bpp * XCF_TILE_MAX_DATA_LENGTH_FACTOR /* = 1.5, currently */; /* read in the first tile offset. * if it is '0', then this tile level is empty * and we can simply return. */ xcf_read_offset (info, &offset, 1); if (offset == 0) return TRUE; n_tile_rows = pika_gegl_buffer_get_n_tile_rows (buffer, XCF_TILE_HEIGHT); n_tile_cols = pika_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) { pika_message_literal (info->pika, G_OBJECT (info->progress), PIKA_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 */ xcf_read_offset (info, &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 + max_data_length; /* seek to the tile offset */ if (! xcf_seek_pos (info, offset, NULL)) return FALSE; if (offset2 < offset || offset2 - offset > max_data_length) { pika_message (info->pika, G_OBJECT (info->progress), PIKA_MESSAGE_ERROR, "invalid tile data length: %" G_GOFFSET_FORMAT, offset2 - offset); return FALSE; } /* get buffer rectangle to write to */ pika_gegl_buffer_get_tile_rect (buffer, XCF_TILE_WIDTH, XCF_TILE_HEIGHT, i, &rect); PIKA_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; PIKA_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 */ xcf_read_offset (info, &offset, 1); } if (offset != 0) { pika_message (info->pika, G_OBJECT (info->progress), PIKA_MESSAGE_ERROR, "encountered garbage after reading level: %" G_GOFFSET_FORMAT, 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); if (info->file_version <= 11) { xcf_read_int8 (info, tile_data, tile_size); } else { gint n_components = babl_format_get_n_components (format); xcf_read_component (info, bpp / n_components, tile_data, tile_size / bpp * n_components); } if (! xcf_data_is_zero (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); guchar nonzero = FALSE; gsize bytes_read; gint i; 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, &bytes_read, NULL, NULL); info->cp += bytes_read; if (bytes_read == 0) return TRUE; xcfdatalimit = &xcfodata[bytes_read - 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++; nonzero |= *data; 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++; nonzero |= val; for (j = 0; j < length; j++) { *data = val; data += bpp; } } } } if (nonzero) { if (info->file_version >= 12) { gint n_components = babl_format_get_n_components (format); xcf_read_from_be (bpp / n_components, tile_data, tile_size / bpp * n_components); } 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); info->cp += bytes_read; if (bytes_read == 0) return TRUE; 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; } } if (! xcf_data_is_zero (tile_data, tile_size)) { if (info->file_version >= 12) { gint n_components = babl_format_get_n_components (format); xcf_read_from_be (bpp / n_components, tile_data, tile_size / bpp * n_components); } gegl_buffer_set (buffer, tile_rect, 0, format, tile_data, GEGL_AUTO_ROWSTRIDE); } inflateEnd (&strm); return TRUE; } static PikaParasite * xcf_load_parasite (XcfInfo *info) { PikaParasite *parasite = NULL; gchar *name; guint32 flags; guint32 size, size_read; gpointer data; xcf_read_string (info, &name, 1); xcf_read_int32 (info, &flags, 1); xcf_read_int32 (info, &size, 1); PIKA_LOG (XCF, "Parasite name: %s, flags: %d, size: %d", name, flags, size); 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; } if (!name) { g_printerr ("Parasite has no name! Possibly corrupt XCF file.\n"); return NULL; } data = g_new (gchar, size); size_read = xcf_read_int8 (info, data, size); if (size_read != size) { g_printerr ("Incorrect parasite data size: read %u bytes instead of %u. " "Possibly corrupt XCF file.\n", size_read, size); } else { parasite = pika_parasite_new (name, flags, size, data); } g_free (name); g_free (data); return parasite; } /* Old paths are the PROP_PATHS property, even older than PROP_VECTORS. */ static gboolean xcf_load_old_paths (XcfInfo *info, PikaImage *image) { guint32 num_paths; guint32 last_selected_row; PikaVectors *active_vectors; xcf_read_int32 (info, &last_selected_row, 1); xcf_read_int32 (info, &num_paths, 1); PIKA_LOG (XCF, "Number of old paths: %u", num_paths); while (num_paths-- > 0) if (! xcf_load_old_path (info, image)) return FALSE; active_vectors = PIKA_VECTORS (pika_container_get_child_by_index (pika_image_get_vectors (image), last_selected_row)); if (active_vectors) { GList *list = g_list_prepend (NULL, active_vectors); pika_image_set_selected_vectors (image, list); g_list_free (list); } return TRUE; } static gboolean xcf_load_old_path (XcfInfo *info, PikaImage *image) { gchar *name; guint32 locked; guint8 state; guint32 closed; guint32 num_points; guint32 version; /* changed from num_paths */ PikaTattoo tattoo = 0; PikaVectors *vectors; PikaVectorsCompatPoint *points; gint i; xcf_read_string (info, &name, 1); xcf_read_int32 (info, &locked, 1); xcf_read_int8 (info, &state, 1); xcf_read_int32 (info, &closed, 1); xcf_read_int32 (info, &num_points, 1); xcf_read_int32 (info, &version, 1); if (version == 2) { guint32 dummy; /* Had extra type field and points are stored as doubles */ xcf_read_int32 (info, (guint32 *) &dummy, 1); } else if (version == 3) { guint32 dummy; /* Has extra tattoo field */ xcf_read_int32 (info, (guint32 *) &dummy, 1); xcf_read_int32 (info, (guint32 *) &tattoo, 1); } else if (version != 1) { g_printerr ("Unknown path type (version: %u). Possibly corrupt XCF file.\n", version); g_free (name); return FALSE; } /* skip empty compatibility paths */ if (num_points == 0) { g_free (name); return FALSE; } points = g_new0 (PikaVectorsCompatPoint, num_points); for (i = 0; i < num_points; i++) { if (version == 1) { gint32 x; gint32 y; xcf_read_int32 (info, &points[i].type, 1); xcf_read_int32 (info, (guint32 *) &x, 1); xcf_read_int32 (info, (guint32 *) &y, 1); points[i].x = x; points[i].y = y; } else { gfloat x; gfloat y; xcf_read_int32 (info, &points[i].type, 1); xcf_read_float (info, &x, 1); xcf_read_float (info, &y, 1); points[i].x = x; points[i].y = y; } } vectors = pika_vectors_compat_new (image, name, points, num_points, closed); g_free (name); g_free (points); if (locked) info->linked_paths = g_list_prepend (info->linked_paths, vectors); if (tattoo) pika_item_set_tattoo (PIKA_ITEM (vectors), tattoo); pika_image_add_vectors (image, vectors, NULL, /* can't be a tree */ pika_container_get_n_children (pika_image_get_vectors (image)), FALSE); return TRUE; } /* Old vectors are the PROP_VECTORS property up to all PIKA 2.10 versions. */ static gboolean xcf_load_old_vectors (XcfInfo *info, PikaImage *image) { guint32 version; guint32 active_index; guint32 num_paths; PikaVectors *active_vectors; #ifdef PIKA_XCF_PATH_DEBUG g_printerr ("xcf_load_old_vectors\n"); #endif xcf_read_int32 (info, &version, 1); if (version != 1) { pika_message (info->pika, G_OBJECT (info->progress), PIKA_MESSAGE_WARNING, "Unknown vectors version: %d (skipping)", version); return FALSE; } xcf_read_int32 (info, &active_index, 1); xcf_read_int32 (info, &num_paths, 1); #ifdef PIKA_XCF_PATH_DEBUG g_printerr ("%d paths (active: %d)\n", num_paths, active_index); #endif while (num_paths-- > 0) if (! xcf_load_old_vector (info, image)) return FALSE; /* FIXME tree */ active_vectors = PIKA_VECTORS (pika_container_get_child_by_index (pika_image_get_vectors (image), active_index)); if (active_vectors) { GList *list = g_list_prepend (NULL, active_vectors); pika_image_set_selected_vectors (image, list); g_list_free (list); } #ifdef PIKA_XCF_PATH_DEBUG g_printerr ("xcf_load_old_vectors: loaded %d bytes\n", info->cp - base); #endif return TRUE; } static gboolean xcf_load_old_vector (XcfInfo *info, PikaImage *image) { gchar *name; PikaTattoo tattoo = 0; guint32 visible; guint32 linked; guint32 num_parasites; guint32 num_strokes; PikaVectors *vectors; gint i; #ifdef PIKA_XCF_PATH_DEBUG g_printerr ("xcf_load_old_vector\n"); #endif xcf_read_string (info, &name, 1); xcf_read_int32 (info, &tattoo, 1); xcf_read_int32 (info, &visible, 1); xcf_read_int32 (info, &linked, 1); xcf_read_int32 (info, &num_parasites, 1); xcf_read_int32 (info, &num_strokes, 1); #ifdef PIKA_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 = pika_vectors_new (image, name); g_free (name); pika_item_set_visible (PIKA_ITEM (vectors), visible, FALSE); if (linked) info->linked_paths = g_list_prepend (info->linked_paths, vectors); if (tattoo) pika_item_set_tattoo (PIKA_ITEM (vectors), tattoo); for (i = 0; i < num_parasites; i++) { PikaParasite *parasite = xcf_load_parasite (info); GError *error = NULL; if (! parasite) return FALSE; if (! pika_item_parasite_validate (PIKA_ITEM (vectors), parasite, &error)) { pika_message (info->pika, G_OBJECT (info->progress), PIKA_MESSAGE_WARNING, "Warning, invalid vectors parasite in XCF file: %s", error->message); g_clear_error (&error); } else { pika_item_parasite_attach (PIKA_ITEM (vectors), parasite, FALSE); } pika_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[13] = PIKA_COORDS_DEFAULT_VALUES; PikaStroke *stroke; gint j; PikaValueArray *control_points; GValue value = G_VALUE_INIT; PikaAnchor anchor = { { 0, } }; GType stroke_type; g_value_init (&value, PIKA_TYPE_ANCHOR); xcf_read_int32 (info, &stroke_type_id, 1); xcf_read_int32 (info, &closed, 1); xcf_read_int32 (info, &num_axes, 1); xcf_read_int32 (info, &num_control_points, 1); #ifdef PIKA_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 = PIKA_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 = pika_value_array_new (num_control_points); anchor.selected = FALSE; for (j = 0; j < num_control_points; j++) { xcf_read_int32 (info, &type, 1); xcf_read_float (info, coords, num_axes); anchor.type = type; anchor.position.x = coords[0]; anchor.position.y = coords[1]; anchor.position.pressure = coords[2]; anchor.position.xtilt = coords[3]; anchor.position.ytilt = coords[4]; anchor.position.wheel = coords[5]; g_value_set_boxed (&value, &anchor); pika_value_array_append (control_points, &value); #ifdef PIKA_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); pika_vectors_stroke_add (vectors, stroke); g_object_unref (stroke); pika_value_array_unref (control_points); } pika_image_add_vectors (image, vectors, NULL, /* FIXME tree */ pika_container_get_n_children (pika_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, buf, amount); if (amount == 0) return FALSE; size -= amount; } return TRUE; } static gboolean xcf_item_path_is_parent (GList *path, GList *parent_path) { GList *iter = path; GList *parent_iter = parent_path; if (g_list_length (parent_path) >= g_list_length (path)) return FALSE; while (iter && parent_iter) { if (iter->data != parent_iter->data) return FALSE; iter = iter->next; parent_iter = parent_iter->next; } return TRUE; } static void xcf_fix_item_path (PikaLayer *layer, GList **path, GList *broken_paths) { GList *iter; for (iter = broken_paths; iter; iter = iter->next) { if (xcf_item_path_is_parent (*path, iter->data)) { /* Not much to do when the absent path is a parent. */ g_printerr ("%s: layer '%s' moved to layer tree root because of missing parent.", G_STRFUNC, pika_object_get_name (layer)); g_clear_pointer (path, g_list_free); return; } } /* Check if a parent of path, or path itself is on the same * tree level as any broken path; and if so, and if the broken path is * in a lower position in the item group, decrement it. */ for (iter = broken_paths; iter; iter = iter->next) { GList *broken_path = iter->data; GList *iter1 = *path; GList *iter2 = broken_path; if (g_list_length (broken_path) > g_list_length (*path)) continue; while (iter1 && iter2) { if (iter2->next && iter1->data != iter2->data) /* Paths diverged before reaching iter2 leaf. */ break; if (iter2->next) { iter1 = iter1->next; iter2 = iter2->next; continue; } if (GPOINTER_TO_UINT (iter2->data) < GPOINTER_TO_UINT (iter1->data)) iter1->data = GUINT_TO_POINTER (GPOINTER_TO_UINT (iter1->data) - 1); break; } } }