/* GIMP - The GNU Image Manipulation Program
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

#include "config.h"

#include <string.h>
#include <zlib.h>

#include <cairo.h>
#include <gegl.h>
#include <gegl-plugin.h>
#include <gdk-pixbuf/gdk-pixbuf.h>

#include "libgimpbase/gimpbase.h"
#include "libgimpcolor/gimpcolor.h"
#include "libgimpconfig/gimpconfig.h"

#include "core/core-types.h"

#include "config/gimpcoreconfig.h"

#include "gegl/gimp-babl.h"
#include "gegl/gimp-gegl-tile-compat.h"
#include "gegl/gimp-gegl-utils.h"

#include "core/gimp.h"
#include "core/gimpcontainer.h"
#include "core/gimpdashpattern.h"
#include "core/gimpdatafactory.h"
#include "core/gimpdrawable-filters.h"
#include "core/gimpdrawable-private.h" /* eek */
#include "core/gimpdrawablefilter.h"
#include "core/gimpfilloptions.h"
#include "core/gimpfilterstack.h"
#include "core/gimpgrid.h"
#include "core/gimpgrouplayer.h"
#include "core/gimpimage.h"
#include "core/gimpimage-color-profile.h"
#include "core/gimpimage-colormap.h"
#include "core/gimpimage-grid.h"
#include "core/gimpimage-guides.h"
#include "core/gimpimage-metadata.h"
#include "core/gimpimage-private.h"
#include "core/gimpimage-sample-points.h"
#include "core/gimpimage-symmetry.h"
#include "core/gimpimage-undo.h"
#include "core/gimpitemlist.h"
#include "core/gimpitemstack.h"
#include "core/gimplayer-floating-selection.h"
#include "core/gimplayer-new.h"
#include "core/gimplayer-xcf.h"
#include "core/gimplayermask.h"
#include "core/gimplink.h"
#include "core/gimplinklayer.h"
#include "core/gimpparasitelist.h"
#include "core/gimppattern.h"
#include "core/gimpprogress.h"
#include "core/gimprasterizable.h"
#include "core/gimpselection.h"
#include "core/gimpstrokeoptions.h"
#include "core/gimpsymmetry.h"
#include "core/gimptemplate.h"
#include "core/gimpunit.h"

#include "operations/layer-modes/gimp-layer-modes.h"

#include "path/gimpanchor.h"
#include "path/gimpstroke.h"
#include "path/gimpbezierstroke.h"
#include "path/gimppath.h"
#include "path/gimppath-compat.h"
#include "path/gimpvectorlayer.h"
#include "path/gimpvectorlayeroptions.h"

#include "plug-in/gimppluginmanager-file.h"
#include "plug-in/gimppluginprocedure.h"

#include "text/gimptextlayer.h"
#include "text/gimptextlayer-xcf.h"

#include "xcf-private.h"
#include "xcf-load.h"
#include "xcf-read.h"
#include "xcf-seek.h"
#include "xcf-utils.h"

#include "gimp-log.h"
#include "gimp-intl.h"


#define MAX_XCF_PARASITE_DATA_LEN (256L * 1024 * 1024)

/* #define GIMP_XCF_PATH_DEBUG */

/* Filters can not be created until a layer is attached
 * to an image, so we use this struct to store relevant
 * information until then */
typedef struct
{
  GeglNode              *operation;
  gchar                 *name;
  gchar                 *icon_name;
  gchar                 *operation_name;
  gchar                 *op_version;
  GimpChannel           *mask;
  gboolean               is_visible;
  gdouble                opacity;
  GimpLayerMode          paint_mode;
  GimpLayerColorSpace    blend_space;
  GimpLayerColorSpace    composite_space;
  GimpLayerCompositeMode composite_mode;
  gboolean               clip;
  GimpFilterRegion       region;

  gboolean               unsupported_operation;
} FilterData;

typedef struct
{
  GimpTattoo       path_tattoo;
  gboolean         modified;
  gboolean         enable_fill;
  gboolean         enable_stroke;

  GimpCustomStyle  fill_style;
  gboolean         fill_antialias;
  GeglColor       *fill_color;
  GimpPattern     *fill_pattern;

  GimpCustomStyle  stroke_style;
  gboolean         stroke_antialias;
  GeglColor       *stroke_color;
  GimpPattern     *stroke_pattern;
  gdouble          stroke_width;
  GimpCapStyle     stroke_cap_style;
  GimpJoinStyle    stroke_join_style;
  gdouble          stroke_miter_limit;
  gsize            n_stroke_dashes;
  gdouble         *stroke_dashes;
} VectorLayerData;

typedef struct
{
  gint                  offset_x;
  gint                  offset_y;
  GimpInterpolationType interpolation;
  GimpMatrix3           matrix;
} LayerTransformData;

static void            xcf_load_add_masks     (GimpImage     *image);
static void            xcf_load_add_effects   (XcfInfo       *info,
                                               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         *loop_files,
                                               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,
                                               gboolean      *is_link_layer);
static gboolean        xcf_load_channel_props (XcfInfo       *info,
                                               GimpImage     *image,
                                               GimpChannel  **channel,
                                               gboolean       is_mask);
static gboolean        xcf_load_effect_props  (XcfInfo       *info,
                                               FilterData    *filter);
static gboolean        xcf_load_path_props    (XcfInfo       *info,
                                               GimpImage     *image,
                                               GimpPath     **paths);
static gboolean        xcf_load_prop          (XcfInfo       *info,
                                               PropType      *prop_type,
                                               guint32       *prop_size);
static GimpLayer     * xcf_load_layer         (XcfInfo       *info,
                                               GimpImage     *image,
                                               GList         *loop_files,
                                               GList        **item_path,
                                               gint          *n_broken_effects);
static GimpChannel   * xcf_load_channel       (XcfInfo       *info,
                                               GimpImage     *image);
static FilterData    * xcf_load_effect        (XcfInfo       *info,
                                               GimpImage     *image,
                                               GimpDrawable  *drawable);
static void            xcf_load_free_effect   (FilterData    *data);
static void            xcf_load_free_effects  (GList         *effects);
static GeglColor     * xcf_load_color         (XcfInfo       *info,
                                               goffset        next_prop,
                                               gboolean      *valid_prop_value,
                                               GError       **error);
static GimpData      * xcf_load_data          (XcfInfo       *info,
                                               GType          data_type,
                                               GError       **error);
static GimpPath      * xcf_load_path          (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_old_vectors   (XcfInfo       *info,
                                               GimpImage     *image);
static gboolean        xcf_load_old_vector    (XcfInfo       *info,
                                               GimpImage     *image);

static void            xcf_load_user_init     (XcfInfo       *info,
                                               GimpImage     *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       (GimpLayer    *layer,
                                                GList       **path,
                                                GList        *broken_paths);

static void            xcf_load_free_vector_data (VectorLayerData *data);


#define xcf_progress_update(info) G_STMT_START  \
  {                                             \
    if (info->progress)                         \
      gimp_progress_pulse (info->progress);     \
  } G_STMT_END


gboolean
xcf_load_magic_version (Gimp          *gimp,
                        GInputStream  *input,
                        GFile         *input_file,
                        GimpProgress  *progress,
                        XcfInfo       *info)
{
  gchar id[14];

  g_return_val_if_fail (GIMP_IS_GIMP (gimp), FALSE);
  g_return_val_if_fail (G_IS_INPUT_STREAM (input), FALSE);
  g_return_val_if_fail (input_file == NULL || G_IS_FILE (input_file), FALSE);
  g_return_val_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress), FALSE);

  info->gimp             = gimp;
  info->input            = input;
  info->seekable         = G_SEEKABLE (input);
  info->bytes_per_offset = 4;
  info->progress         = progress;
  info->file             = input_file;
  info->compression      = COMPRESS_NONE;

  xcf_read_int8 (info, (guint8 *) id, 14);

  if (! g_str_has_prefix (id, "gimp xcf "))
    {
      return FALSE;
    }
  else if (strcmp (id + 9, "file") == 0)
    {
      info->file_version = 0;
    }
  else if (id[9]  == 'v' &&
           id[13] == '\0')
    {
      info->file_version = atoi (id + 10);
    }
  else
    {
      return FALSE;
    }

  if (info->file_version >= 11)
    info->bytes_per_offset = 8;

  return TRUE;
}

/* This function will load the image header then do a quick pass through
 * link layers, returning all files to ignore in link layers as
 * @loop_files.
 *
 * Argument @loop_found should be NULL on the initial call. It is only
 * being used in recursive calls to stop earlier.
 */
gboolean
xcf_load_image_header (Gimp           *gimp,
                       XcfInfo        *info,
                       gint           *width,
                       gint           *height,
                       gint           *image_type,
                       GimpPrecision  *precision,
                       GList          *prev_files,
                       GList         **loop_files,
                       gboolean       *loop_found,
                       GError        **error)
{
  goffset reset_pos;

  /* 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 < GIMP_RGB || *image_type > GIMP_INDEXED)
    {
      if (error)
        g_set_error_literal (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
                             /* TODO: localize after freeze ends. */
                             "invalid image type.");
      return FALSE;
    }

  /* Be lenient with corrupt image dimensions.
   * Hopefully layer dimensions will be valid. */
  if (*width <= 0 || *height <= 0 ||
      *width > GIMP_MAX_IMAGE_SIZE || *height > GIMP_MAX_IMAGE_SIZE)
    {
      GIMP_LOG (XCF, "Invalid image size %d x %d, setting to 1x1.", *width, *height);
      *width  = 1;
      *height = 1;
    }

  *precision = GIMP_PRECISION_U8_NON_LINEAR;
  if (info->file_version >= 4)
    {
      gint p;

      xcf_read_int32 (info, (guint32 *) &p, 1);

      if (info->file_version == 4)
        {
          switch (p)
            {
            case 0:
              *precision = GIMP_PRECISION_U8_NON_LINEAR;
              break;
            case 1:
              *precision = GIMP_PRECISION_U16_NON_LINEAR;
              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:
              if (error)
                g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
                             /* TODO: localize after freeze ends. */
                             "Invalid image precision value %d for XCF version %d.",
                             p, info->file_version);
              return FALSE;
            }
        }
      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_NON_LINEAR;
              break;
            case 200:
              *precision = GIMP_PRECISION_U16_LINEAR;
              break;
            case 250:
              *precision = GIMP_PRECISION_U16_NON_LINEAR;
              break;
            case 300:
              *precision = GIMP_PRECISION_U32_LINEAR;
              break;
            case 350:
              *precision = GIMP_PRECISION_U32_NON_LINEAR;
              break;
            case 400:
              *precision = GIMP_PRECISION_HALF_LINEAR;
              break;
            case 450:
              *precision = GIMP_PRECISION_HALF_NON_LINEAR;
              break;
            case 500:
              *precision = GIMP_PRECISION_FLOAT_LINEAR;
              break;
            case 550:
              *precision = GIMP_PRECISION_FLOAT_NON_LINEAR;
              break;
            default:
              if (error)
                g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
                             /* TODO: localize after freeze ends. */
                             "Invalid image precision value %d for XCF version %d.",
                             p, info->file_version);
              return FALSE;
            }
        }
      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);

  if (! gimp_babl_is_valid (*image_type, *precision))
    {
      if (error)
        g_set_error_literal (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
                             _("Invalid image mode and precision combination."));
      return FALSE;
    }

  reset_pos = info->cp;
  /******** END OF HEADER DATA LOAD ********\
   * We will seek back to reset_pos as the position where the stream
   * will be if this function succeed.
   *
   * The rest of this function will quickly skip to layer structures and
   * try to identify any cycle in linked files.
  \******** START LINK SANITY CHECK ********/

  /* Skip image properties */
  while (TRUE)
    {
      PropType prop_type;
      guint32  prop_size;

      if (! xcf_load_prop (info, &prop_type, &prop_size))
        {
          if (error)
            g_set_error_literal (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
                                 /* TODO: localize after freeze ends. */
                                 "Failed reading image properties.");
          return FALSE;
        }

      if (prop_type == PROP_END)
        break;

      if (prop_type == PROP_USER_UNIT)
        {
          /* Special-casing because of buggy prop size, cf. #16129. */
          xcf_load_user_init (info, NULL);
        }
      else if (! xcf_skip_unknown_prop (info, prop_size))
        {
          if (error)
            g_set_error_literal (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
                                 /* TODO: localize after freeze ends. */
                                 "Failed skipping image properties.");
          return FALSE;
        }
    }

  /* Scan layers for link layers to search for loops (link layers in
   * succession ending up calling a parent XCF).
   * I do not error out on errors and will let xcf_load_image() do this,
   * or possibly salvage what can still be loaded.
   */
  while (TRUE)
    {
      goffset  offset;
      gint     lwidth;
      gint     lheight;
      gint     ltype;
      gchar   *lname;
      goffset  saved_pos;

      /* read in the offset of the next layer */
      if (xcf_read_offset (info, &offset, 1) < info->bytes_per_offset)
        break;

      if (offset == 0)
        break;

      saved_pos = info->cp;

      if (offset < saved_pos)
        break;

      /* seek to the layer offset */
      if (! xcf_seek_pos (info, offset, NULL))
        break;

      /* read in the layer */
      xcf_read_int32  (info, (guint32 *) &lwidth,  1);
      xcf_read_int32  (info, (guint32 *) &lheight, 1);
      xcf_read_int32  (info, (guint32 *) &ltype,   1);
      xcf_read_string (info,             &lname,   1);
      g_free (lname);

      while (TRUE)
        {
          PropType prop_type;
          guint32  prop_size;

          if (! xcf_load_prop (info, &prop_type, &prop_size))
            break;

          if (prop_type == PROP_END)
            {
              break;
            }
          else if (prop_type == PROP_LINK_LAYER)
            {
              gchar   *path = NULL;
              guint32  flags;
              guint32  dimensions[2];

              xcf_read_int32  (info, &flags,     1);
              xcf_read_string (info, &path,      1);
              xcf_read_int32  (info, dimensions, 2);

              if (path != NULL)
                {
                  GFile               *folder;
                  GFile               *link_file;
                  GimpPlugInProcedure *file_proc;
                  GList               *iter;

                  folder    = g_file_get_parent (info->file);
                  link_file = g_file_resolve_relative_path (folder, path);
                  g_clear_object (&folder);
                  g_free (path);

                  for (iter = *loop_files; iter; iter = iter->next)
                    if (xcf_load_file_equal (link_file, iter->data))
                      break;
                  if (iter != NULL)
                    {
                      g_clear_object (&link_file);
                      if (loop_found)
                        *loop_found = TRUE;
                      break;
                    }

                  for (iter = prev_files; iter; iter = iter->next)
                    if (xcf_load_file_equal (link_file, iter->data))
                      break;
                  if (iter != NULL)
                    {
                      *loop_files = g_list_prepend (*loop_files, link_file);
                      if (loop_found)
                        *loop_found = TRUE;
                      break;
                    }

                  file_proc = gimp_plug_in_manager_file_procedure_find (gimp->plug_in_manager,
                                                                        GIMP_FILE_PROCEDURE_GROUP_OPEN,
                                                                        link_file, error);
                  if (file_proc && gimp_plug_in_procedure_is_xcf_load (file_proc))
                    {
                      GInputStream  *input;
                      GList         *parent_files;
                      XcfInfo        info2  = { 0, };
                      gint           width2;
                      gint           height2;
                      gint           type2;
                      GimpPrecision  precision2;
                      gboolean       subloop_found = FALSE;

                      parent_files = g_list_copy (prev_files);
                      parent_files = g_list_prepend (parent_files, link_file);

                      input = G_INPUT_STREAM (g_file_read (link_file, NULL, NULL));
                      if (input && xcf_load_magic_version (gimp, input, link_file, NULL, &info2))
                        xcf_load_image_header (gimp, &info2, &width2, &height2, &type2, &precision2,
                                               parent_files, loop_files, &subloop_found, NULL);

                      g_clear_object (&input);
                      g_list_free (parent_files);

                      if (subloop_found)
                        {
                          *loop_files = g_list_prepend (*loop_files, link_file);
                          if (loop_found)
                            *loop_found = TRUE;
                          break;
                        }
                    }

                  g_clear_object (&link_file);
                }
            }
          else if (! xcf_skip_unknown_prop (info, prop_size))
            {
              break;
            }
        }

      if (loop_found && *loop_found)
        break;

      if (! xcf_seek_pos (info, saved_pos, NULL))
        break;
    }

  if (! xcf_seek_pos (info, reset_pos, NULL))
    {
      if (error)
        g_set_error_literal (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
                             /* TODO: localize after freeze ends. */
                             "Failed seeking back.");
      return FALSE;
    }

  return TRUE;
}

GimpImage *
xcf_load_image (Gimp     *gimp,
                XcfInfo  *info,
                GError  **error)
{
  GimpImage          *image = NULL;
  const GimpParasite *parasite;
  gboolean            has_metadata = FALSE;
  goffset             saved_pos;
  goffset             offset;
  gint                width;
  gint                height;
  gint                image_type;
  GimpPrecision       precision = GIMP_PRECISION_U8_NON_LINEAR;
  gint                num_successful_elements = 0;
  gint                n_broken_layers         = 0;
  gint                n_broken_channels       = 0;
  gint                n_broken_paths          = 0;
  gint                n_broken_effects        = 0;
  GList              *layers;
  GList              *broken_paths            = NULL;
  GList              *group_layers            = NULL;
  GList              *syms;
  GList              *iter;
  GList              *parent_files            = NULL;
  GList              *loop_files              = NULL;

  parent_files = g_list_prepend (parent_files, info->file);
  if (! xcf_load_image_header (gimp, info, &width, &height,
                               &image_type, &precision,
                               parent_files, &loop_files, NULL,
                               error))
    goto hard_error;

  g_clear_pointer (&parent_files, g_list_free);

  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");

  /* 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 = gimp_image_parasite_find (GIMP_IMAGE (image),
                                       "image-simulation-intent");
  if (parasite)
    {
      guint32                   parasite_size;
      GimpImagePrivate         *private = GIMP_IMAGE_GET_PRIVATE (image);
      const guint8             *data;
      GimpColorRenderingIntent  intent;

      data   = (const guint8 *) gimp_parasite_get_data (parasite, &parasite_size);
      intent = (GimpColorRenderingIntent) *data;

      gimp_parasite_list_remove (private->parasites,
                                 gimp_parasite_get_name (parasite));

      if (parasite_size == 1)
        {
          if (intent != GIMP_COLOR_RENDERING_INTENT_PERCEPTUAL            &&
              intent != GIMP_COLOR_RENDERING_INTENT_RELATIVE_COLORIMETRIC &&
              intent != GIMP_COLOR_RENDERING_INTENT_SATURATION            &&
              intent != GIMP_COLOR_RENDERING_INTENT_ABSOLUTE_COLORIMETRIC)
            {
              gimp_message (info->gimp, G_OBJECT (info->progress),
                            GIMP_MESSAGE_ERROR,
                            "Unknown simulation rendering intent: %d",
                            intent);
            }
          else
            {
              gimp_image_set_simulation_intent (image, intent);
            }
        }
      else
        {
          gimp_message (info->gimp, G_OBJECT (info->progress),
                        GIMP_MESSAGE_ERROR,
                        "Invalid simulation intent data");
        }
    }


  /* check for simulation bpc parasite */
  parasite = gimp_image_parasite_find (GIMP_IMAGE (image),
                                       "image-simulation-bpc");
  if (parasite)
    {
      guint32           parasite_size;
      GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image);
      const guint8     *data;
      gboolean          bpc;

      data = (const guint8 *) gimp_parasite_get_data (parasite, &parasite_size);
      bpc  = *data ? TRUE : FALSE;

      gimp_parasite_list_remove (private->parasites,
                                 gimp_parasite_get_name (parasite));

      if (parasite_size == 1)
        {
          gimp_image_set_simulation_bpc (image, bpc);
        }
      else
        {
          gimp_message (info->gimp, G_OBJECT (info->progress),
                        GIMP_MESSAGE_ERROR,
                        "Invalid simulation bpc data");
        }
    }

  /* 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_get_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 = NULL;
      gchar            *meta_string;
      guint32           parasite_data_size;

      meta_string = (gchar *) gimp_parasite_get_data (parasite, &parasite_data_size);
      if (meta_string)
        {
          meta_string = g_strndup (meta_string, parasite_data_size);
          metadata = gimp_metadata_deserialize (meta_string);
          g_free (meta_string);
        }

      gimp_parasite_list_remove (private->parasites,
                                 gimp_parasite_get_name (parasite));

      if (metadata)
        {
          has_metadata = TRUE;

          gimp_image_set_metadata (image, metadata, FALSE);
          g_object_unref (metadata);
        }
    }

  /* check for symmetry parasites */
  syms = gimp_image_symmetry_list ();
  for (iter = syms; iter; iter = g_list_next (iter))
    {
      GType  type = (GType) iter->data;
      gchar *parasite_name = gimp_symmetry_parasite_name (type);

      parasite = gimp_image_parasite_find (image,
                                           parasite_name);
      g_free (parasite_name);
      if (parasite)
        {
          GimpSymmetry *sym = gimp_symmetry_from_parasite (parasite,
                                                           image,
                                                           type);

          if (sym)
            {
              GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image);

              gimp_parasite_list_remove (private->parasites,
                                         gimp_parasite_get_name (parasite));

              gimp_image_symmetry_add (image, sym);

              g_signal_emit_by_name (sym, "active-changed", NULL);
              if (sym->active)
                gimp_image_set_active_symmetry (image, type);
            }
        }
    }
  g_list_free (syms);

  /* 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;
          const guchar *parasite_data;
          guint32       parasite_data_size;

          parasite_data = (const guchar *) gimp_parasite_get_data (parasite, &parasite_data_size);

          if (metadata)
            g_object_ref (metadata);
          else
            metadata = gimp_metadata_new ();

          if (! gimp_metadata_set_from_exif (metadata,
                                             parasite_data, parasite_data_size,
                                             &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_get_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);
      const gchar      *xmp_data;
      guint32           xmp_length;

      xmp_data = (gchar *) gimp_parasite_get_data (parasite, &xmp_length);

      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 if (xmp_length < 14 ||
               strncmp (xmp_data, "GIMP_XMP_1", 10) != 0)
        {
          gimp_message (gimp, G_OBJECT (info->progress),
                        GIMP_MESSAGE_WARNING,
                        _("Corrupt 'gimp-metadata' parasite discovered.\n"
                          "XMP data could not be migrated."));
        }
      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,
                                            (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. */
              gimp_message (gimp, G_OBJECT (info->progress),
                            GIMP_MESSAGE_WARNING,
                            _("Corrupt XMP metadata saved by an older version of "
                              "GIMP 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
            {
              gimp_image_set_metadata (image, metadata, FALSE);
            }

          g_object_unref (metadata);
        }

      gimp_parasite_list_remove (private->parasites,
                                 gimp_parasite_get_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);

      /* just ditch it, it's unused but shouldn't be re-saved */
      gimp_parasite_list_remove (private->parasites,
                                 gimp_parasite_get_name (parasite));
    }

  xcf_progress_update (info);

  while (TRUE)
    {
      GimpLayer *layer;
      GList     *item_path = NULL;

      /* read in the offset of the next layer */
      if (xcf_read_offset (info, &offset, 1) < info->bytes_per_offset)
        {
          GIMP_LOG (XCF, "Failed to read layer offset"
                    " at offset: %" G_GOFFSET_FORMAT, info->cp);
          break;
        }

      /* 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)
        {
          GIMP_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, loop_files, &item_path, &n_broken_effects);
      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)
            {
              GimpContainer *layers = gimp_image_get_layers (image);

              item_path = g_list_prepend (NULL,
                                          GUINT_TO_POINTER (gimp_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 (GIMP_IS_GROUP_LAYER (layer))
        {
          GimpGroupLayer *group = GIMP_GROUP_LAYER (layer);

          group_layers = g_list_prepend (group_layers, group);

          gimp_group_layer_suspend_resize (group, FALSE);
        }

      /* 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;
    }
  g_list_free_full (loop_files, g_object_unref);
  loop_files = NULL;

  /* resume layer-group size updates, in reverse order */
  for (iter = group_layers; iter; iter = g_list_next (iter))
    {
      GimpGroupLayer *group = iter->data;

      gimp_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)
    {
      GimpChannel *channel;

      /* read in the offset of the next channel */
      if (xcf_read_offset (info, &offset, 1) < info->bytes_per_offset)
        {
          GIMP_LOG (XCF, "Failed to read channel offset"
                    " at offset: %" G_GOFFSET_FORMAT, info->cp);
          break;
        }

      /* 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)
        {
          GIMP_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++;
          GIMP_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 != 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;
    }

  if (n_broken_layers == 0 && n_broken_channels == 0)
    {
      xcf_load_add_masks (image);
      xcf_load_add_effects (info, 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 (GIMP_IS_LAYER (info->floating_sel_drawable) &&
          gimp_drawable_is_gray (GIMP_DRAWABLE (info->floating_sel)))
        gimp_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)
        {
          GimpPath *path;

          /* read in the offset of the next path */
          if (xcf_read_offset (info, &offset, 1) < info->bytes_per_offset)
            {
              GIMP_LOG (XCF, "Failed to read path offset"
                        " at offset: %" G_GOFFSET_FORMAT, info->cp);
              break;
            }

          /* 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)
            {
              GIMP_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 */
          path = xcf_load_path (info, image);
          if (! path)
            {
              n_broken_paths++;
              GIMP_LOG (XCF, "Failed to load path.");

              if (! xcf_seek_pos (info, saved_pos, NULL))
                goto error;

              continue;
            }

          num_successful_elements++;

          xcf_progress_update (info);

          gimp_image_add_path (image, path,
                               NULL, /* can't be a tree */
                               gimp_container_get_n_children (gimp_image_get_paths (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;
        }
    }

  layers = gimp_image_get_layer_list (image);
  for (iter = layers; iter; iter = g_list_next (iter))
    {
      GimpLayer          *layer = iter->data;
      VectorLayerData    *vdata;
      LayerTransformData *tdata;

      /* Once all items are loaded, we transform any vector layer in
       * waiting. We could not create vector layers directly because we
       * needed the paths to be loaded first.
       */
      vdata = g_object_get_data (G_OBJECT (layer), "gimp-vector-layer-data");

      if (vdata != NULL)
        {
          GimpLayer              *vlayer;
          GimpLayer              *parent = NULL;
          GimpVectorLayerOptions *options;
          GimpPath               *path;
          GArray                 *dash_pattern;
          GList                  *selected;
          GList                  *linked;
          gboolean                floating;
          gchar                  *name;
          gint                    position = 0;

          selected = g_list_find (info->selected_layers, layer);
          linked   = g_list_find (info->linked_layers, layer);
          floating = (info->floating_sel == layer);
          name     = g_strdup (gimp_object_get_name (layer));

          path = gimp_image_get_path_by_tattoo (image, vdata->path_tattoo);
          if (path == NULL)
            {
              GIMP_LOG (XCF,
                        "Failed to load path associated with vector layer \"%s\". "
                        "The vector layer is downgraded to a raster layer.",
                        gimp_object_get_name (layer));
              g_object_set_data (G_OBJECT (layer), "gimp-vector-layer-data", NULL);
              continue;
            }
          options = gimp_vector_layer_options_new (image, path,
                                                   gimp_get_user_context (info->gimp));
          options->enable_fill   = vdata->enable_fill;
          options->enable_stroke = vdata->enable_stroke;

          gimp_fill_options_set_custom_style (options->fill_options, vdata->fill_style);
          gimp_fill_options_set_antialias (options->fill_options, vdata->fill_antialias);
          gimp_context_set_foreground (GIMP_CONTEXT (options->fill_options), vdata->fill_color);
          gimp_context_set_pattern (GIMP_CONTEXT (options->fill_options), vdata->fill_pattern);

          gimp_fill_options_set_custom_style (GIMP_FILL_OPTIONS (options->stroke_options), vdata->stroke_style);
          gimp_fill_options_set_antialias (GIMP_FILL_OPTIONS (options->stroke_options), vdata->stroke_antialias);
          gimp_context_set_foreground (GIMP_CONTEXT (options->stroke_options), vdata->stroke_color);
          gimp_context_set_pattern (GIMP_CONTEXT (options->stroke_options), vdata->stroke_pattern);

          dash_pattern = gimp_dash_pattern_from_double_array (vdata->n_stroke_dashes, vdata->stroke_dashes);
          gimp_stroke_options_take_dash_pattern (options->stroke_options, GIMP_DASH_CUSTOM, dash_pattern);

          g_object_set (G_OBJECT (options->stroke_options),
                        "width",       vdata->stroke_width,
                        "cap-style",   vdata->stroke_cap_style,
                        "join-style",  vdata->stroke_join_style,
                        "miter-limit", vdata->stroke_miter_limit,
                        NULL);

          /* Store current layer position so we can add it after the fact */
          parent   = gimp_layer_get_parent (layer);
          position = gimp_item_get_index (GIMP_ITEM (layer));

          vlayer = gimp_layer_from_layer (layer, FALSE, GIMP_TYPE_VECTOR_LAYER,
                                          "image",                image,
                                          "vector-layer-options", options,
                                          NULL);
          g_object_unref (options);

          if (vdata->modified)
            gimp_rasterizable_rasterize (GIMP_RASTERIZABLE (vlayer), FALSE);
          gimp_image_add_layer (image, vlayer, parent, position, FALSE);

          gimp_object_set_name (GIMP_OBJECT (vlayer), name);
          g_free (name);

          if (selected)
            {
              info->selected_layers = g_list_delete_link (info->selected_layers, selected);
              info->selected_layers = g_list_prepend (info->selected_layers, vlayer);
            }
          if (linked)
            {
              info->linked_layers = g_list_delete_link (info->linked_layers, linked);
              info->linked_layers = g_list_prepend (info->linked_layers, vlayer);
            }
          if (floating)
            info->floating_sel = vlayer;

          layer = vlayer;
        }

      /* If any layer has a transformation matrix, we apply it after
       * everything is loaded so that we are sure that order of props
       * doesn't matter and also we need items to be already attached.
       *
       * XXX Right now, this can only be applied to link layers, but
       * eventually we should be able to port this to any type of layer
       * as a last-minute transformation in-one concept which would work
       * as a lesser-destruction edit when applying several
       * transformations.
       */
      tdata = g_object_get_data (G_OBJECT (layer), "gimp-layer-transform-data");
      if (tdata != NULL)
        {
          if (! GIMP_IS_LINK_LAYER (layer))
            {
              GIMP_LOG (XCF,
                        "PROP_TRANSFORM property can only be applied on link layers. "
                        "The transformation on layer \"%s\" was dropped.",
                        gimp_object_get_name (layer));
              g_object_set_data (G_OBJECT (layer), "gimp-layer-transform-data", NULL);
              continue;
            }
          else if (! gimp_link_layer_is_monitored (GIMP_LINK_LAYER (layer)) ||
                   gimp_link_is_broken (gimp_link_layer_get_link (GIMP_LINK_LAYER (layer))))
            {
              /* The loaded buffer from XCF will already be transformed.
               * It's not an error.
               */
              g_object_set_data (G_OBJECT (layer), "gimp-layer-transform-data", NULL);
              continue;
            }

          gimp_item_set_offset (GIMP_ITEM (layer), tdata->offset_x, tdata->offset_y);
          gimp_link_layer_set_transform (GIMP_LINK_LAYER (layer), &tdata->matrix, tdata->interpolation, FALSE);
          g_object_set_data (G_OBJECT (layer), "gimp-layer-transform-data", NULL);
        }
    }
  g_list_free (layers);

  if (info->selected_layers)
    {
      gimp_image_set_selected_layers (image, info->selected_layers);
      g_clear_pointer (&info->selected_layers, g_list_free);
    }

  if (info->selected_channels)
    gimp_image_set_selected_channels (image, info->selected_channels);

  if (info->selected_paths)
    gimp_image_set_selected_paths (image, info->selected_paths);

  /* We don't have linked items concept anymore. We transform formerly
   * linked items into stored sets of named items instead.
   */
  if (info->linked_layers)
    {
      GimpItemList *set;

      set = gimp_item_list_named_new (image, GIMP_TYPE_LAYER,
                                      _("Linked Layers"),
                                      info->linked_layers);
      gimp_image_store_item_set (image, set);
      g_clear_pointer (&info->linked_layers, g_list_free);
    }
  if (info->linked_channels)
    {
      GimpItemList *set;

      set = gimp_item_list_named_new (image, GIMP_TYPE_CHANNEL,
                                      _("Linked Channels"),
                                      info->linked_channels);
      gimp_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 paths 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. "
                    "GIMP does not support linked paths since version 3.0.\n");

#if 0
      GimpItemList *set;

      set = gimp_item_list_named_new (image, GIMP_TYPE_PATH,
                                      _("Linked Paths"),
                                      info->linked_paths);
      gimp_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)
        gimp_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)
        gimp_image_store_item_set (image, iter->data);
    }
  g_list_free (info->channel_sets);

  if (info->file)
    gimp_image_set_file (image, info->file);

  if (info->tattoo_state > 0)
    gimp_image_set_tattoo_state (image, info->tattoo_state);

  if (n_broken_layers > 0 || n_broken_channels > 0 || n_broken_paths > 0 || n_broken_effects)
    goto error;

  gimp_image_undo_enable (image);

  return image;

error:
  if (num_successful_elements == 0)
    goto hard_error;

  g_clear_pointer (&group_layers, g_list_free);
  g_list_free_full (loop_files, g_object_unref);

  if (broken_paths)
    {
      g_list_free_full (broken_paths, (GDestroyNotify) g_list_free);
      broken_paths = NULL;
    }

  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);
  xcf_load_add_effects (info, image);

  gimp_image_undo_enable (image);

  return image;

hard_error:
  g_clear_pointer (&group_layers, g_list_free);
  g_list_free (parent_files);
  g_list_free_full (loop_files, g_object_unref);

  if (broken_paths)
    {
      g_list_free_full (broken_paths, (GDestroyNotify) g_list_free);
      broken_paths = NULL;
    }

  if (error && *error)
    g_prefix_error (error,
                    /* TODO: localize after string freeze ends. */
                    "This XCF file is corrupt: ");
  else
    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;
}

gboolean
xcf_load_file_equal (GFile *file1,
                     GFile *file2)
{
  GFileInfo   *info1;
  GFileInfo   *info2;
  const gchar *id1;
  const gchar *id2;
  gboolean     equal;

  if (g_file_equal (file1, file2))
    return TRUE;

  info1 = g_file_query_info (file1,
                             G_FILE_ATTRIBUTE_ID_FILE,
                             /* This will follow symlinks by default. */
                             G_FILE_QUERY_INFO_NONE,
                             NULL, NULL);
  info2 = g_file_query_info (file2,
                             G_FILE_ATTRIBUTE_ID_FILE,
                             G_FILE_QUERY_INFO_NONE,
                             NULL, NULL);
  id1 = g_file_info_get_attribute_string (info1, G_FILE_ATTRIBUTE_ID_FILE);
  id2 = g_file_info_get_attribute_string (info2, G_FILE_ATTRIBUTE_ID_FILE);

  /* If hard-linking is supported, this will verify 2 files are the same
   * inode. If we don't have the ID attribute, we just assume these are
   * different files.
   */
  equal = (id1 && id2 && g_strcmp0 (id1, id2) == 0);

  g_object_unref (info1);
  g_object_unref (info2);

  return equal;
}

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, edit_mask, FALSE, NULL);

          gimp_layer_set_apply_mask (layer, apply_mask, FALSE);
          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 void
xcf_load_add_effects (XcfInfo   *info,
                      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;
      GList     *effects_nodes;

      effects_nodes = g_object_get_data (G_OBJECT (layer), "gimp-layer-effects");

      if (effects_nodes)
        {
          GList *iter;

          for (iter = effects_nodes; iter; iter = iter->next)
            {
              FilterData *data = iter->data;

              if (! data->unsupported_operation)
                {
                  GimpDrawableFilter *filter = NULL;

                  if (! data->icon_name)
                    data->icon_name = g_strdup ("gimp-gegl");

                  filter = gimp_drawable_filter_new (GIMP_DRAWABLE (layer),
                                                     data->name, data->operation,
                                                     data->icon_name);

                  gimp_drawable_filter_set_opacity (filter, data->opacity);
                  gimp_drawable_filter_set_mode (filter, data->paint_mode,
                                                 data->blend_space,
                                                 data->composite_space,
                                                 data->composite_mode);
                  gimp_drawable_filter_set_clip (filter, data->clip);
                  gimp_drawable_filter_set_region (filter, data->region);

                  g_object_set (filter,
                                "mask", data->mask,
                                NULL);

                  gimp_drawable_filter_apply_with_mask (filter, data->mask,
                                                        NULL);

                  gimp_drawable_filter_commit (filter, TRUE, NULL, FALSE);

                  gimp_drawable_filter_layer_mask_freeze (filter);

                  gimp_filter_set_active (GIMP_FILTER (filter), data->is_visible);

                  g_object_unref (filter);
                }
            }

          g_object_set_data (G_OBJECT (layer), "gimp-layer-effects", 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];

              xcf_read_int32 (info, &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
                {
                  xcf_read_int8 (info, 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;

              xcf_read_int8 (info, (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_image_set_xcf_compression (image,
                                              compression >= COMPRESS_ZLIB);

              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++)
                {
                  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 (GIMP 3.0), off-canvas guides became a
                   * thing.
                   */
                  if (info->file_version < 15 && position < 0)
                    continue;

                  GIMP_LOG (XCF, "prop guide orientation=%d position=%d",
                            orientation, position);

                  switch (orientation)
                    {
                    case XCF_ORIENTATION_HORIZONTAL:
                      if (info->file_version < 15 && position > gimp_image_get_height (image))
                        gimp_message (info->gimp, G_OBJECT (info->progress),
                                      GIMP_MESSAGE_WARNING,
                                      "Ignoring off-canvas horizontal guide (position %d) in XCF %d file",
                                      position, info->file_version);
                      else
                        gimp_image_add_hguide (image, position, FALSE);
                      break;

                    case XCF_ORIENTATION_VERTICAL:
                      if (info->file_version < 15 && position > gimp_image_get_width (image))
                        gimp_message (info->gimp, G_OBJECT (info->progress),
                                      GIMP_MESSAGE_WARNING,
                                      "Ignoring off-canvas vertical guide (position %d) in XCF %d file",
                                      position, info->file_version);
                      else
                        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:
            {
              gint n_sample_points, i;

              n_sample_points = prop_size / (5 * 4);
              for (i = 0; i < n_sample_points; i++)
                {
                  GimpSamplePoint   *sample_point;
                  gint32             x, y;
                  GimpColorPickMode  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);

                  GIMP_LOG (XCF, "prop sample point x=%d y=%d mode=%d",
                            x, y, pick_mode);

                  if (pick_mode > GIMP_COLOR_PICK_MODE_LAST)
                    pick_mode = GIMP_COLOR_PICK_MODE_PIXEL;

                  sample_point = gimp_image_add_sample_point_at_pos (image,
                                                                     x, y, FALSE);
                  gimp_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 (gimp_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);

                  GIMP_LOG (XCF, "prop old 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;

              xcf_read_float (info, &xres, 1);
              xcf_read_float (info, &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:
            {
              xcf_read_int32 (info, &info->tattoo_state, 1);

              GIMP_LOG (XCF, "prop tattoo state=%d", info->tattoo_state);
            }
          break;

        case PROP_PARASITES:
            {
              goffset base = info->cp;

              while (info->cp - base < prop_size)
                {
                  GimpParasite *p     = xcf_load_parasite (info);
                  GError       *error = NULL;

                  if (! p)
                    {
                      gimp_message (info->gimp, G_OBJECT (info->progress),
                                    GIMP_MESSAGE_WARNING,
                                    "Invalid image parasite found. "
                                    "Possibly corrupt XCF file.");

                      xcf_seek_pos (info, base + prop_size, NULL);
                      continue;
                    }

                  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, 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 an image's parasites");
            }
          break;

        case PROP_UNIT:
            {
              guint32 unit_index;

              xcf_read_int32 (info, &unit_index, 1);

              GIMP_LOG (XCF, "prop unit=%d", unit_index);

              if (unit_index <= GIMP_UNIT_PIXEL || unit_index >= GIMP_UNIT_END)
                {
                  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_index = GIMP_UNIT_INCH;
                }

              gimp_image_set_unit (image, gimp_unit_get_by_id (unit_index));
            }
          break;

        case PROP_PATHS:
            {
              goffset base = info->cp;

              if (info->file_version >= 18)
                gimp_message (info->gimp, G_OBJECT (info->progress),
                              GIMP_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:
          xcf_load_user_init (info, image);
          break;

        case PROP_VECTORS:
            {
              goffset base = info->cp;

              if (info->file_version >= 18)
                gimp_message (info->gimp, G_OBJECT (info->progress),
                              GIMP_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:
            {
              GimpItemList *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 = GIMP_TYPE_LAYER;
              else
                item_type = GIMP_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 > GIMP_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 gimp_item_list_named_new() because it
                       * doesn't allow NULL items (it would try to get the
                       * selected items instead).
                       */
                      set = g_object_new (GIMP_TYPE_ITEM_LIST,
                                          "image",      image,
                                          "name",       label,
                                          "is-pattern", FALSE,
                                          "item-type",  item_type,
                                          "items",      NULL,
                                          NULL);
                    }
                  else
                    {
                      set = gimp_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 == GIMP_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 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      *loop_files,
                      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:
          {
            if (g_list_index (info->selected_layers, *layer) < 0)
              info->selected_layers = g_list_prepend (info->selected_layers, *layer);
            else
              gimp_message_literal (info->gimp, G_OBJECT (info->progress),
                                    GIMP_MESSAGE_WARNING,
                                    "Invalid duplicate selected layer detected");
          }
          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);

            gimp_layer_set_opacity (*layer, (gdouble) opacity / 255.0, FALSE);
          }
          break;

        case PROP_FLOAT_OPACITY:
          {
            gfloat opacity;

            xcf_read_float (info, &opacity, 1);

            gimp_layer_set_opacity (*layer, opacity, FALSE);
          }
          break;

        case PROP_VISIBLE:
          {
            gboolean visible;

            xcf_read_int32 (info, (guint32 *) &visible, 1);

            gimp_item_set_visible (GIMP_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:
          {
            GimpColorTag color_tag;

            xcf_read_int32 (info, (guint32 *) &color_tag, 1);

            gimp_item_set_color_tag (GIMP_ITEM (*layer), color_tag, FALSE);
          }
          break;

        case PROP_LOCK_CONTENT:
          {
            gboolean lock_content;

            xcf_read_int32 (info, (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;

            xcf_read_int32 (info, (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;

            xcf_read_int32 (info, (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_LOCK_VISIBILITY:
          {
            gboolean lock_visibility;

            xcf_read_int32 (info, (guint32 *) &lock_visibility, 1);

            if (gimp_item_can_lock_visibility (GIMP_ITEM (*layer)))
              gimp_item_set_lock_visibility (GIMP_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 < -GIMP_MAX_IMAGE_SIZE ||
                offset_x > GIMP_MAX_IMAGE_SIZE)
              {
                g_printerr ("unexpected item offset_x (%d) in XCF, "
                            "setting to 0\n", offset_x);
                offset_x = 0;
              }

            if (offset_y < -GIMP_MAX_IMAGE_SIZE ||
                offset_y > GIMP_MAX_IMAGE_SIZE)
              {
                g_printerr ("unexpected item offset_y (%d) in XCF, "
                            "setting to 0\n", offset_y);
                offset_y = 0;
              }

            gimp_item_set_offset (GIMP_ITEM (*layer), offset_x, offset_y);
          }
          break;

        case PROP_MODE:
          {
            GimpLayerMode mode;

            xcf_read_int32 (info, (guint32 *) &mode, 1);

            if (mode == GIMP_LAYER_MODE_OVERLAY_LEGACY)
              mode = GIMP_LAYER_MODE_SOFTLIGHT_LEGACY;

            gimp_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)
              {
                GimpLayerMode mode = gimp_layer_get_mode (*layer);

                blend_space = -blend_space;

                if (blend_space == gimp_layer_mode_get_blend_space (mode))
                  blend_space = GIMP_LAYER_COLOR_SPACE_AUTO;
                else
                  GIMP_LOG (XCF, "BLEND_SPACE: AUTO => %d", blend_space);
              }

            gimp_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)
              {
                GimpLayerMode mode = gimp_layer_get_mode (*layer);

                composite_space = -composite_space;

                if (composite_space == gimp_layer_mode_get_composite_space (mode))
                  composite_space = GIMP_LAYER_COLOR_SPACE_AUTO;
                else
                  GIMP_LOG (XCF, "COMPOSITE_SPACE: AUTO => %d", composite_space);
              }

            gimp_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)
              {
                GimpLayerMode mode = gimp_layer_get_mode (*layer);

                composite_mode = -composite_mode;

                if (composite_mode == gimp_layer_mode_get_composite_mode (mode))
                  composite_mode = GIMP_LAYER_COMPOSITE_AUTO;
                else
                  GIMP_LOG (XCF, "COMPOSITE_MODE: AUTO => %d", composite_mode);
              }

            gimp_layer_set_composite_mode (*layer, composite_mode, FALSE);
          }
          break;

        case PROP_TATTOO:
          {
            GimpTattoo tattoo;

            xcf_read_int32 (info, (guint32 *) &tattoo, 1);

            gimp_item_set_tattoo (GIMP_ITEM (*layer), tattoo);
          }
          break;

        case PROP_PARASITES:
          {
            goffset 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:
          xcf_read_int32 (info, text_layer_flags, 1);
          break;

        case PROP_VECTOR_LAYER:
          {
            VectorLayerData *data;
            guint32          uint_val;
            gfloat           float_val;
            goffset          next_prop;
            gboolean         valid_color = TRUE;
            GError          *error       = NULL;

            next_prop = info->cp + prop_size;

            data = g_new0 (VectorLayerData, 1);

            xcf_read_int32 (info, (guint32 *) &uint_val, 1);
            data->modified = (gboolean) uint_val;

            xcf_read_int32 (info, (guint32 *) &uint_val, 1);
            data->path_tattoo = (GimpTattoo) uint_val;
            xcf_read_int32 (info, (guint32 *) &uint_val, 1);
            data->enable_fill = (gboolean) uint_val;
            xcf_read_int32 (info, (guint32 *) &uint_val, 1);
            data->enable_stroke = (gboolean) uint_val;

            xcf_read_int32 (info, (guint32 *) &uint_val, 1);
            data->fill_style = (GimpCustomStyle) uint_val;
            xcf_read_int32 (info, (guint32 *) &uint_val, 1);
            data->fill_antialias = (gboolean) uint_val;

            data->fill_color = xcf_load_color (info, next_prop, &valid_color, &error);
            if (error)
              {
                gimp_message (info->gimp, G_OBJECT (info->progress),
                              GIMP_MESSAGE_WARNING,
                              "Warning, invalid color in XCF file: %s",
                              error->message);
                g_clear_error (&error);
                valid_color = TRUE;
              }
            data->fill_pattern = GIMP_PATTERN (xcf_load_data (info, GIMP_TYPE_PATTERN, &error));
            /* Just ignore errors here? */
            g_clear_error (&error);

            xcf_read_int32 (info, (guint32 *) &uint_val, 1);
            data->stroke_style = (GimpCustomStyle) uint_val;
            xcf_read_int32 (info, (guint32 *) &uint_val, 1);
            data->stroke_antialias = (gboolean) uint_val;

            data->stroke_color = xcf_load_color (info, next_prop, &valid_color, &error);
            if (error)
              {
                gimp_message (info->gimp, G_OBJECT (info->progress),
                              GIMP_MESSAGE_WARNING,
                              "Warning, invalid color in XCF file: %s",
                              error->message);
                g_clear_error (&error);
                valid_color = TRUE;
              }
            /* Just ignore errors here? */
            data->stroke_pattern = GIMP_PATTERN (xcf_load_data (info, GIMP_TYPE_PATTERN, &error));
            g_clear_error (&error);

            xcf_read_float (info, (gfloat *) &float_val, 1);
            data->stroke_width = (gfloat) float_val;
            xcf_read_int32 (info, (guint32 *) &uint_val, 1);
            data->stroke_cap_style = (GimpCapStyle) uint_val;
            xcf_read_int32 (info, (guint32 *) &uint_val, 1);
            data->stroke_join_style = (GimpJoinStyle) uint_val;
            xcf_read_float (info, (gfloat *) &float_val, 1);
            data->stroke_miter_limit = (gfloat) float_val;

            xcf_read_int32 (info, (guint32 *) &uint_val, 1);
            data->n_stroke_dashes = (gboolean) uint_val;

            data->stroke_dashes = g_new0 (gdouble, data->n_stroke_dashes);
            for (gint i = 0; i < data->n_stroke_dashes; i++)
              {
                xcf_read_float (info, (gfloat *) &float_val, 1);
                data->stroke_dashes[i] = (gdouble) float_val;
              }

            g_object_set_data_full (G_OBJECT (*layer),
                                    "gimp-vector-layer-data", data,
                                    (GDestroyNotify) xcf_load_free_vector_data);
          }
          break;

        case PROP_LINK_LAYER:
            {
              gchar   *path;
              guint32  flags;
              guint32  dimensions[2];

              xcf_read_int32  (info, &flags,     1);
              xcf_read_string (info, &path,      1);
              xcf_read_int32  (info, dimensions, 2);

              if (path == NULL)
                {
                  gimp_message (info->gimp, G_OBJECT (info->progress),
                                GIMP_MESSAGE_WARNING,
                                _("XCF Warning: invalid link in XCF file. "
                                  "The link layer \"%s\" is downgraded to a raster layer."),
                                gimp_object_get_name (*layer));
                }
              else
                {
                  GFile    *folder;
                  GFile    *link_file;
                  gboolean  ignore = FALSE;

                  folder    = g_file_get_parent (info->file);
                  link_file = g_file_resolve_relative_path (folder, path);
                  g_object_unref (folder);

                  for (GList *iter = loop_files; iter; iter = iter->next)
                    {
                      if (xcf_load_file_equal (iter->data, link_file))
                        {
                          gimp_message (info->gimp, G_OBJECT (info->progress),
                                        GIMP_MESSAGE_WARNING,
                                        _("XCF Warning: circular reference detected in XCF file. "
                                          "The link layer \"%s\" is downgraded to a raster layer."),
                                        gimp_object_get_name (*layer));
                          ignore = TRUE;
                          break;
                        }
                    }

                  if (! ignore)
                    {
                      GimpLink *link;
                      GList    *selected;
                      GList    *linked;
                      gboolean  floating;
                      gint      raster_width;
                      gint      raster_height;

                      raster_width  = gimp_item_get_width (GIMP_ITEM (*layer));
                      raster_height = gimp_item_get_height (GIMP_ITEM (*layer));

                      floating = (info->floating_sel == *layer);
                      selected = g_list_find (info->selected_layers, *layer);
                      linked   = g_list_find (info->linked_layers, *layer);

                      link   = gimp_link_new (info->gimp, link_file,
                                              (gint) dimensions[0], (gint) dimensions[1],
                                              FALSE, NULL, NULL);
                      *layer = gimp_layer_from_layer (*layer, TRUE,
                                                      GIMP_TYPE_LINK_LAYER,
                                                      "image", image,
                                                      NULL);

                      gimp_link_layer_set_link (GIMP_LINK_LAYER (*layer), link, FALSE);
                      gimp_link_layer_set_xcf_flags (GIMP_LINK_LAYER (*layer), flags);

                      if (! gimp_link_layer_is_monitored (GIMP_LINK_LAYER (*layer)) ||
                          gimp_link_is_broken (gimp_link_layer_get_link (GIMP_LINK_LAYER (*layer))))
                        {
                          GeglColor *color = gegl_color_new ("transparent");

                          /* Let's completely ignore the link size. The
                           * stored buffer will be used instead, so we
                           * should resize the item back to how it was.
                           * We don't care about proper scaling here,
                           * the buffer will be the real content.
                           */
                          gimp_item_resize (GIMP_ITEM (*layer),
                                            gimp_get_user_context (info->gimp),
                                            GIMP_FILL_WHITE,
                                            raster_width, raster_height,
                                            0, 0);
                          gegl_buffer_set_color (gimp_drawable_get_buffer (GIMP_DRAWABLE (*layer)),
                                                 NULL, color);
                          g_object_unref (color);
                        }

                      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;

                      g_object_unref (link);
                    }

                  g_object_unref (link_file);
                }
              g_free (path);
            }
          break;

        case PROP_TRANSFORM:
            {
              LayerTransformData *data;
              gint32              int_val[2];
              guint32             uint_val;
              gfloat              mfloat[9];

              data = g_new0 (LayerTransformData, 1);

              xcf_read_int32 (info, (guint32 *) int_val, 2);
              data->offset_x = (gint) int_val[0];
              data->offset_y = (gint) int_val[1];

              xcf_read_int32 (info, &uint_val, 1);
              data->interpolation = (GimpInterpolationType) uint_val;

              xcf_read_float (info, mfloat, 9);
              data->matrix.coeff[0][0] = (gdouble) mfloat[0];
              data->matrix.coeff[0][1] = (gdouble) mfloat[1];
              data->matrix.coeff[0][2] = (gdouble) mfloat[2];
              data->matrix.coeff[1][0] = (gdouble) mfloat[3];
              data->matrix.coeff[1][1] = (gdouble) mfloat[4];
              data->matrix.coeff[1][2] = (gdouble) mfloat[5];
              data->matrix.coeff[2][0] = (gdouble) mfloat[6];
              data->matrix.coeff[2][1] = (gdouble) mfloat[7];
              data->matrix.coeff[2][2] = (gdouble) mfloat[8];

              g_object_set_data_full (G_OBJECT (*layer),
                                      "gimp-layer-transform-data", data,
                                      (GDestroyNotify) g_free);
            }
          break;

        case PROP_GROUP_ITEM:
          {
            GimpLayer *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 = 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;

            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:
            {
              GimpItemList *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",
                            gimp_object_get_name (*layer), n);
              else if (! g_type_is_a (G_TYPE_FROM_INSTANCE (*layer),
                                      gimp_item_list_get_item_type (set)))
                g_printerr ("xcf: layer '%s' cannot be added to item set '%s' with item type %s (skipping)\n",
                            gimp_object_get_name (*layer), gimp_object_get_name (set),
                            g_type_name (gimp_item_list_get_item_type (set)));
              else if (gimp_item_list_is_pattern (set, NULL))
                g_printerr ("xcf: layer '%s' cannot be added to pattern item set '%s' (skipping)\n",
                            gimp_object_get_name (*layer), gimp_object_get_name (set));
              else
                gimp_item_list_add (set, GIMP_ITEM (*layer));
            }
          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_check_layer_props (XcfInfo    *info,
                       GList     **item_path,
                       gboolean   *is_group_layer,
                       gboolean   *is_text_layer,
                       gboolean   *is_link_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);
  g_return_val_if_fail (*is_link_layer  == TRUE, 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_LINK_LAYER:
          *is_link_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 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,
                        gboolean      is_mask)
{
  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:
          {
            GimpChannel *mask;
            GList       *iter;

            if (is_mask)
              {
                /* PROP_SELECTION is not valid for masks, and we have to avoid
                 * overwriting the channel.
                 */
                continue;
              }

            if (*channel == gimp_image_get_mask (image))
              {
                /* PROP_SELECTION was already seen once for this
                 * channel. Let's silently ignore the second identical
                 * property to avoid a double free.
                 */
                continue;
              }
            else if (gimp_image_get_mask (image) != NULL &&
                     ! gimp_channel_is_empty (gimp_image_get_mask (image)))
              {
                /* This would happen when PROP_SELECTION was already set
                 * on a previous channel. This is a minor case of data
                 * loss (we don't know which selection was the right one
                 * and we drop the non-first ones), and also means it's
                 * a broken XCF, though it's not a major bug either. So
                 * let's go with a stderr print.
                 */
                g_printerr ("PROP_SELECTION property was set on 2 channels (skipping)\n");
                continue;
              }

            /* 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 =
              gimp_selection_new (image,
                                  gimp_item_get_width  (GIMP_ITEM (*channel)),
                                  gimp_item_get_height (GIMP_ITEM (*channel)));
            gimp_image_take_mask (image, mask);

            gimp_drawable_steal_buffer (GIMP_DRAWABLE (mask),
                                        GIMP_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);

            gimp_channel_set_opacity (*channel, opacity / 255.0, FALSE);
          }
          break;

        case PROP_FLOAT_OPACITY:
          {
            gfloat opacity;

            xcf_read_float (info, &opacity, 1);

            gimp_channel_set_opacity (*channel, opacity, FALSE);
          }
          break;

        case PROP_VISIBLE:
          {
            gboolean visible;

            xcf_read_int32 (info, (guint32 *) &visible, 1);

            gimp_item_set_visible (GIMP_ITEM (*channel), visible, FALSE);
          }
          break;

        case PROP_COLOR_TAG:
          {
            GimpColorTag color_tag;

            xcf_read_int32 (info, (guint32 *) &color_tag, 1);

            gimp_item_set_color_tag (GIMP_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 (gimp_item_can_lock_content (GIMP_ITEM (*channel)))
              gimp_item_set_lock_content (GIMP_ITEM (*channel),
                                          lock_content, FALSE);
          }
          break;

        case PROP_LOCK_POSITION:
          {
            gboolean lock_position;

            xcf_read_int32 (info, (guint32 *) &lock_position, 1);

            if (gimp_item_can_lock_position (GIMP_ITEM (*channel)))
              gimp_item_set_lock_position (GIMP_ITEM (*channel),
                                           lock_position, FALSE);
          }
          break;

        case PROP_LOCK_VISIBILITY:
          {
            gboolean lock_visibility;

            xcf_read_int32 (info, (guint32 *) &lock_visibility, 1);

            if (gimp_item_can_lock_visibility (GIMP_ITEM (*channel)))
              gimp_item_set_lock_visibility (GIMP_ITEM (*channel),
                                             lock_visibility, FALSE);
          }
          break;

        case PROP_SHOW_MASKED:
          {
            gboolean show_masked;

            xcf_read_int32 (info, (guint32 *) &show_masked, 1);

            gimp_channel_set_show_masked (*channel, show_masked);
          }
          break;

        case PROP_COLOR:
          {
            guchar  col[3];
            gdouble opacity;

            /* Load existing opacity */
            opacity = gimp_channel_get_opacity (*channel);

            xcf_read_int8 (info, (guint8 *) col, 3);

            gegl_color_set_pixel ((*channel)->color, babl_format ("R'G'B' u8"), col);

            gimp_channel_set_opacity (*channel, opacity, FALSE);
          }
          break;

        case PROP_FLOAT_COLOR:
          {
            gfloat  col[3];
            gdouble opacity;

            /* Load existing opacity */
            opacity = gimp_channel_get_opacity (*channel);

            xcf_read_float (info, col, 3);

            /* TODO: is the channel color in sRGB or in the image's color space? */
            gegl_color_set_pixel ((*channel)->color, babl_format ("R'G'B' float"), col);

            gimp_channel_set_opacity (*channel, opacity, FALSE);
          }
          break;

        case PROP_TATTOO:
          {
            GimpTattoo tattoo;

            xcf_read_int32 (info, (guint32 *) &tattoo, 1);

            gimp_item_set_tattoo (GIMP_ITEM (*channel), tattoo);
          }
          break;

        case PROP_PARASITES:
          {
            goffset 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;

        case PROP_ITEM_SET_ITEM:
            {
              GimpItemList *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),
                                      gimp_item_list_get_item_type (set)))
                g_printerr ("xcf: channel '%s' cannot be added to item set '%s' with item type %s (skipping)\n",
                            gimp_object_get_name (*channel), gimp_object_get_name (set),
                            g_type_name (gimp_item_list_get_item_type (set)));
              else
                gimp_item_list_add (set, GIMP_ITEM (*channel));
            }
          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_effect_props (XcfInfo      *info,
                       FilterData   *filter)
{
  PropType prop_type;
  guint32  prop_size;

  while (TRUE)
    {
      goffset next_prop;

      if (! xcf_load_prop (info, &prop_type, &prop_size))
        return FALSE;

      next_prop = info->cp + prop_size;
      switch (prop_type)
        {
        case PROP_END:
          return TRUE;

        case PROP_VISIBLE:
          {
            gboolean visible;

            xcf_read_int32 (info, (guint32 *) &visible, 1);
            filter->is_visible = visible;
          }
          break;

        case PROP_FLOAT_OPACITY:
          {
            gfloat opacity;

            xcf_read_float (info, &opacity, 1);

            filter->opacity = (gdouble) opacity;
          }
          break;

        case PROP_MODE:
          {
            GimpLayerMode mode;

            xcf_read_int32 (info, (guint32 *) &mode, 1);

            if (mode == GIMP_LAYER_MODE_OVERLAY_LEGACY)
              mode = GIMP_LAYER_MODE_SOFTLIGHT_LEGACY;

            filter->paint_mode = mode;
          }
          break;

        case PROP_BLEND_SPACE:
          {
            gint32 blend_space;

            xcf_read_int32 (info, (guint32 *) &blend_space, 1);

            /* TODO: Revisit when blend space can be set
             * on filter effects */
            blend_space = GIMP_LAYER_COLOR_SPACE_AUTO;

            filter->blend_space = blend_space;
          }
          break;

        case PROP_COMPOSITE_SPACE:
          {
            gint32 composite_space;

            xcf_read_int32 (info, (guint32 *) &composite_space, 1);

            /* TODO: Revisit when composite space can be set
             * on filter effects */
            composite_space = GIMP_LAYER_COLOR_SPACE_AUTO;

            filter->composite_space = composite_space;
          }
          break;

        case PROP_COMPOSITE_MODE:
          {
            gint32 composite_mode;

            xcf_read_int32 (info, (guint32 *) &composite_mode, 1);

            /* TODO: Revisit when composite mode can be set
             * on filter effects */
            composite_mode = GIMP_LAYER_COMPOSITE_AUTO;

            filter->composite_mode = composite_mode;
          }
          break;

        case PROP_FILTER_REGION:
          {
            GimpFilterRegion region;

            xcf_read_int32 (info, (guint32 *) &region, 1);

            filter->region = region;
          }
          break;

        case PROP_FILTER_ARGUMENT:
          {
            GParamSpec *pspec;
            gchar      *filter_prop_name;
            guint32     filter_type       = FILTER_PROP_UNKNOWN;
            GValue      filter_prop_value = G_VALUE_INIT;
            gboolean    valid_prop_value  = TRUE;

            xcf_read_string (info, &filter_prop_name, 1);
            xcf_read_int32 (info, (guint32 *) &filter_type, 1);

            /* Check if valid property first */
            if (! (pspec = gegl_operation_find_property (filter->operation_name,
                                                         filter_prop_name)))
              {
                gimp_message (info->gimp, G_OBJECT (info->progress),
                              GIMP_MESSAGE_WARNING,
                              _("XCF Warning: version %s of filter \"%s\" does not "
                                "have the %s property. The property was ignored and "
                                "the filter may not render properly.\n"
                                "This should not happen. "
                                "You should report the issue to the filter's developers."),
                              filter->op_version ? filter->op_version : "0:0",
                              filter->operation_name, filter_prop_name);
                valid_prop_value = FALSE;
                goto set_or_seek_node_property;
              }

            switch (filter_type)
              {
                case FILTER_PROP_INT:
                case FILTER_PROP_UINT:
                case FILTER_PROP_ENUM:
                  {
                    guint32 value;

                    xcf_read_int32 (info, (guint32 *) &value, 1);
                    g_value_init (&filter_prop_value, pspec->value_type);
                    if (filter_type == FILTER_PROP_INT)
                      g_value_set_int (&filter_prop_value, value);
                    else if (filter_type == FILTER_PROP_UINT)
                      g_value_set_uint (&filter_prop_value, value);
                    else
                      g_value_set_enum (&filter_prop_value, value);
                  }
                  break;

                case FILTER_PROP_BOOL:
                  {
                    gboolean value;

                    xcf_read_int32 (info, (guint32 *) &value, 1);
                    g_value_init (&filter_prop_value, G_TYPE_BOOLEAN);
                    g_value_set_boolean (&filter_prop_value, value);
                  }
                  break;

                case FILTER_PROP_FLOAT:
                  {
                    gfloat value;

                    xcf_read_float (info, &value, 1);
                    g_value_init (&filter_prop_value, G_TYPE_FLOAT);
                    g_value_set_float (&filter_prop_value, value);
                  }
                  break;

                case FILTER_PROP_STRING:
                  {
                    gchar *value;

                    xcf_read_string (info, &value, 1);
                    g_value_init (&filter_prop_value, G_TYPE_STRING);
                    g_value_set_string (&filter_prop_value, value);

                    g_free (value);
                  }
                  break;

                case FILTER_PROP_CONFIG:
                  {
                    GObject *config;
                    gchar   *serialized;
                    GError  *error = NULL;

                    if (! g_type_is_a (pspec->value_type, GIMP_TYPE_CONFIG))
                      {
                        gimp_message (info->gimp, G_OBJECT (info->progress),
                                      GIMP_MESSAGE_WARNING,
                                      "XCF Warning: property '%s' of filter '%s' is a %s, which does not implement GimpConfig interface.\n",
                                      filter_prop_name, filter->operation_name, g_type_name (pspec->value_type));
                        valid_prop_value = FALSE;
                        break;
                      }

                    xcf_read_string (info, &serialized, 1);
                    if (! (serialized && strlen (serialized)))
                      {
                        gimp_message (info->gimp, G_OBJECT (info->progress),
                                      GIMP_MESSAGE_WARNING,
                                      "XCF Warning: failure to deserialize config object for property '%s' of filter '%s'.\n"
                                      "Serialized config could not be read.",
                                      filter_prop_name, filter->operation_name);
                        valid_prop_value = FALSE;
                        break;
                      }

                    g_value_init (&filter_prop_value, pspec->value_type);
                    config = g_object_new (pspec->value_type, NULL);
                    if (gimp_config_deserialize_string (GIMP_CONFIG (config), serialized, -1, NULL, &error))
                      {
                        g_value_set_object (&filter_prop_value, config);
                      }
                    else
                      {
                        gimp_message (info->gimp, G_OBJECT (info->progress),
                                      GIMP_MESSAGE_WARNING,
                                      "XCF Warning: failure to deserialize config object for property '%s' of filter '%s': %s\n"
                                      "Serialized config was: %s",
                                      filter_prop_name, filter->operation_name,
                                      error->message, serialized);
                        valid_prop_value = FALSE;
                      }

                    g_object_unref (config);
                    g_free (serialized);
                  }
                  break;

                case FILTER_PROP_COLOR:
                  {
                    GeglColor *color;
                    GError    *error = NULL;

                    g_value_init (&filter_prop_value, GEGL_TYPE_COLOR);

                    color = xcf_load_color (info, next_prop, &valid_prop_value, &error);
                    if (valid_prop_value)
                      {
                        g_value_set_object (&filter_prop_value, color);

                        if (color == NULL)
                          {
                            gimp_message (info->gimp, G_OBJECT (info->progress),
                                          GIMP_MESSAGE_WARNING,
                                          "XCF Warning: NULL value for color "
                                          "property '%s' of filter '%s' is "
                                          "invalid.",
                                          filter->operation_name,
                                          filter_prop_name);
                            valid_prop_value = FALSE;
                          }
                      }
                    else if (error != NULL)
                      {
                        gimp_message (info->gimp, G_OBJECT (info->progress),
                                      GIMP_MESSAGE_WARNING,
                                      "XCF Warning: invalid value for color "
                                      "property '%s' of filter '%s': %s",
                                      filter->operation_name,
                                      filter_prop_name, error->message);
                      }

                    g_clear_object (&color);
                    g_clear_error (&error);
                  }
                  break;

                default:
                  gimp_message (info->gimp, G_OBJECT (info->progress),
                                GIMP_MESSAGE_WARNING,
                                "XCF Warning: property '%s' of filter '%s' holds unsupported type %s.\n",
                                filter_prop_name, filter->operation_name, g_type_name (pspec->value_type));
                  valid_prop_value = FALSE;
                  break;
              }

set_or_seek_node_property:
            if (valid_prop_value)
              gegl_node_set_property (filter->operation, filter_prop_name,
                                      &filter_prop_value);
            else
              xcf_seek_pos (info, next_prop, NULL);

            g_value_unset (&filter_prop_value);
            g_free (filter_prop_name);
          }
          break;

        case PROP_FILTER_CLIP:
          {
            gboolean clip;

            xcf_read_int32 (info, (guint32 *) &clip, 1);
            filter->clip = clip;
          }
          break;

        default:
#ifdef GIMP_UNSTABLE
          g_printerr ("unexpected/unknown effect property: %d (skipping)\n",
                      prop_type);
#endif
          if (! xcf_skip_unknown_prop (info, prop_size))
            return FALSE;
        }
    }

  return FALSE;
}

static gboolean
xcf_load_path_props (XcfInfo    *info,
                     GimpImage  *image,
                     GimpPath  **path)
{
  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_paths = g_list_prepend (info->selected_paths, *path);
          break;

        case PROP_VISIBLE:
          {
            gboolean visible;

            xcf_read_int32 (info, (guint32 *) &visible, 1);

            gimp_item_set_visible (GIMP_ITEM (*path), visible, FALSE);
          }
          break;

        case PROP_COLOR_TAG:
          {
            GimpColorTag color_tag;

            xcf_read_int32 (info, (guint32 *) &color_tag, 1);

            gimp_item_set_color_tag (GIMP_ITEM (*path), color_tag, FALSE);
          }
          break;

        case PROP_LOCK_CONTENT:
          {
            gboolean lock_content;

            xcf_read_int32 (info, (guint32 *) &lock_content, 1);

            if (gimp_item_can_lock_content (GIMP_ITEM (*path)))
              gimp_item_set_lock_content (GIMP_ITEM (*path),
                                          lock_content, FALSE);
          }
          break;

        case PROP_LOCK_POSITION:
          {
            gboolean lock_position;

            xcf_read_int32 (info, (guint32 *) &lock_position, 1);

            if (gimp_item_can_lock_position (GIMP_ITEM (*path)))
              gimp_item_set_lock_position (GIMP_ITEM (*path),
                                           lock_position, FALSE);
          }
          break;

        case PROP_LOCK_VISIBILITY:
          {
            gboolean lock_visibility;

            xcf_read_int32 (info, (guint32 *) &lock_visibility, 1);

            if (gimp_item_can_lock_visibility (GIMP_ITEM (*path)))
              gimp_item_set_lock_visibility (GIMP_ITEM (*path),
                                             lock_visibility, FALSE);
          }
          break;

        case PROP_TATTOO:
          {
            GimpTattoo tattoo;

            xcf_read_int32 (info, (guint32 *) &tattoo, 1);

            gimp_item_set_tattoo (GIMP_ITEM (*path), tattoo);
          }
          break;

        case PROP_PARASITES:
          {
            goffset 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 (*path), p,
                                                    &error))
                  {
                    gimp_message (info->gimp, G_OBJECT (info->progress),
                                  GIMP_MESSAGE_WARNING,
                                  "Warning, invalid path parasite in XCF file: %s",
                                  error->message);
                    g_clear_error (&error);
                  }
                else
                  {
                    gimp_item_parasite_attach (GIMP_ITEM (*path), 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 path's parasites");
          }
          break;

#if 0
        case PROP_ITEM_SET_ITEM:
            {
              GimpItemList *set;
              guint32       n;

              xcf_read_int32 (info, &n, 1);
              set = g_list_nth_data (info->path_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 (*path),
                                      gimp_item_list_get_item_type (set)))
                g_printerr ("xcf: path '%s' cannot be added to item set '%s' with item type %s (skipping)\n",
                            gimp_object_get_name (*path), gimp_object_get_name (set),
                            g_type_name (gimp_item_list_get_item_type (set)));
              else
                gimp_item_list_add (set, GIMP_ITEM (*path));
            }
          break;
#endif

        default:
#ifdef GIMP_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;

  GIMP_LOG (XCF, "prop type=%d size=%u", *prop_type, *prop_size);

  return TRUE;
}

static GimpLayer *
xcf_load_layer (XcfInfo    *info,
                GimpImage  *image,
                GList      *loop_files,
                GList     **item_path,
                gint       *n_broken_effects)
{
  GimpLayer         *layer;
  GimpLayerMask     *layer_mask;
  goffset            hierarchy_offset;
  goffset            effects_offset = 0;
  goffset            layer_mask_offset;
  gboolean           apply_mask = TRUE;
  gboolean           edit_mask  = FALSE;
  gboolean           show_mask  = FALSE;
  GList             *selected;
  GList             *linked;
  GList             *filter_data_list = NULL;
  gboolean           floating;
  guint32            group_layer_flags = 0;
  guint32            text_layer_flags = 0;
  gint               filter_count = 0;
  gint               width;
  gint               height;
  gint               type;
  GimpImageBaseType  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);

  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:
      g_free (name);
      return NULL;
    }

  if (width <= 0 || height <= 0 ||
      width > GIMP_MAX_IMAGE_SIZE || height > GIMP_MAX_IMAGE_SIZE)
    {
      gboolean is_group_layer = FALSE;
      gboolean is_text_layer  = FALSE;
      gboolean is_link_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, &is_link_layer);
      if ((is_text_layer || is_group_layer || is_link_layer) &&
          xcf_seek_pos (info, saved_pos, NULL))
        {
          /* Something is wrong, but leave a chance to the layer because
           * anyway group, text and link 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 == GIMP_GRAY)
    {
      /* 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,
                                      NULL /* we will fix the space later */);
    }
  else
    {
      format = gimp_image_get_layer_format (image, has_alpha);
    }

  /* create a new layer */
  layer = gimp_layer_new (image, width, height,
                          format, name,
                          GIMP_OPACITY_OPAQUE, GIMP_LAYER_MODE_NORMAL);
  g_free (name);
  if (! layer)
    return NULL;

  /* read in the layer properties */
  if (! xcf_load_layer_props (info, image, &layer, loop_files, 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 */
  selected = g_list_find (info->selected_layers, layer);
  linked   = g_list_find (info->linked_layers, 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 (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 == GIMP_GRAY)
    gimp_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);
  if (info->file_version >= 20)
    xcf_read_offset (info, &effects_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)) &&
      /* Link layers are loaded from XCF only if they are not monitored
       * or if the link is broken.
       */
      (! GIMP_IS_LINK_LAYER (layer)                             ||
       ! gimp_link_layer_is_monitored (GIMP_LINK_LAYER (layer)) ||
       gimp_link_is_broken (gimp_link_layer_get_link (GIMP_LINK_LAYER (layer)))))
    {
      if (hierarchy_offset < cur_offset)
        {
          GIMP_LOG (XCF, "Invalid layer hierarchy offset!");
          goto error;
        }
      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);
    }

  cur_offset += info->bytes_per_offset;

  /* read in the layer mask */
  if (layer_mask_offset != 0)
    {
      if (layer_mask_offset < cur_offset)
        {
          GIMP_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;

      cur_offset += info->bytes_per_offset;

      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));
    }
  else
    {
      /* If no layer mask, move ahead to layer effects */
      cur_offset += info->bytes_per_offset;
    }

  /* read in any layer effects and effect masks */
  while (effects_offset != 0)
    {
      FilterData *filter_data;

      if (effects_offset < cur_offset)
        {
          GIMP_LOG (XCF, "Invalid effect offset: %" G_GOFFSET_FORMAT
                    " at offset: %" G_GOFFSET_FORMAT, effects_offset, cur_offset);
          goto error;
        }

      /* seek to the effect offset */
      if (! xcf_seek_pos (info, effects_offset, NULL))
        goto error;

      filter_data = xcf_load_effect (info, image, GIMP_DRAWABLE (layer));
      if (! filter_data)
        {
          (*n_broken_effects)++;
        }
      else
        {
          filter_data_list = g_list_prepend (filter_data_list, filter_data);
          filter_count++;
        }
      xcf_progress_update (info);

      /* restore the saved position so we'll be ready to
       * read the next offset.
       */
      cur_offset += info->bytes_per_offset;
      if (! xcf_seek_pos (info, cur_offset, NULL))
        goto error;

      /* read in the offset of the next effect */
      if (xcf_read_offset (info, &effects_offset, 1) < info->bytes_per_offset)
        {
          GIMP_LOG (XCF, "Failed to read effects offset"
                    " at offset: %" G_GOFFSET_FORMAT, info->cp);
          break;
        }
    }

  if (filter_count > 0)
    g_object_set_data_full (G_OBJECT (layer), "gimp-layer-effects", filter_data_list,
                            (GDestroyNotify) xcf_load_free_effects);

  /* attach the floating selection... */
  if (is_fs_drawable)
    info->floating_sel_drawable = GIMP_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 == GIMP_DRAWABLE (layer))
    info->floating_sel_drawable = NULL;

  g_object_unref (layer);

  return NULL;
}

static GimpChannel *
xcf_load_channel (XcfInfo   *info,
                  GimpImage *image)
{
  GimpChannel *channel;
  goffset      hierarchy_offset;
  gint         width;
  gint         height;
  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 and name */
  xcf_read_int32 (info, (guint32 *) &width,  1);
  xcf_read_int32 (info, (guint32 *) &height, 1);
  if (width <= 0 || height <= 0 ||
      width > GIMP_MAX_IMAGE_SIZE || height > GIMP_MAX_IMAGE_SIZE)
    {
      GIMP_LOG (XCF, "Invalid channel size %d x %d.", width, height);
      return NULL;
    }

  xcf_read_string (info, &name, 1);
  GIMP_LOG (XCF, "Channel width=%d, height=%d, name='%s'",
            width, height, name);

  /* create a new channel */
  channel = gimp_channel_new (image, width, height, name, NULL);
  g_free (name);
  if (!channel)
    return NULL;

  /* read in the channel properties */
  if (! xcf_load_channel_props (info, image, &channel, FALSE))
    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)
    {
      GIMP_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,
                         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:
  /* don't unref the selection of a partially loaded XCF */
  if (channel != gimp_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 == GIMP_DRAWABLE (channel))
        info->floating_sel_drawable = NULL;

      g_object_unref (channel);
    }

  return NULL;
}

static FilterData *
xcf_load_effect (XcfInfo      *info,
                 GimpImage    *image,
                 GimpDrawable *drawable)
{
  FilterData  *filter;
  GimpChannel *effect_mask = NULL;
  goffset      mask_offset = 0;
  gchar       *string;
  GError      *error = NULL;

  filter = g_new0 (FilterData, 1);

  /* Effect name */
  xcf_read_string (info, &string, 1);
  filter->name = string;

  /* Effect icon */
  xcf_read_string (info, &string, 1);
  filter->icon_name = string;

  /* Effect operation */
  xcf_read_string (info, &string, 1);
  filter->operation_name = string;

  if (! gimp_gegl_op_nde_allowed (filter->operation_name, &error))
    {
      filter->unsupported_operation = TRUE;

      if (filter->name)
        gimp_message (info->gimp, G_OBJECT (info->progress),
                      GIMP_MESSAGE_WARNING,
                      /* TODO: localize after string freeze. */
                      "XCF Warning: the filter \"%s\" (%s) was discarded. %s",
                      filter->name, filter->operation_name, error->message);
      else
        gimp_message (info->gimp, G_OBJECT (info->progress),
                      GIMP_MESSAGE_WARNING,
                      /* TODO: localize after string freeze. */
                      "XCF Warning: an unnamed filter (%s) was discarded. %s",
                      filter->operation_name, error->message);
      g_clear_error (&error);

      return filter;
    }

  if (info->file_version >= 22)
    {
      xcf_read_string (info, &string, 1);
      filter->op_version = string;
    }

  if (filter->op_version &&
      g_strcmp0 (gegl_operation_get_op_version (filter->operation_name),
                 filter->op_version) != 0)
    {
      filter->unsupported_operation = TRUE;

      gimp_message (info->gimp, G_OBJECT (info->progress),
                    GIMP_MESSAGE_WARNING,
                    _("XCF Warning: version '%s' of filter '%s' is unsupported. "
                      "The filter was discarded.\n"
                      "It either means that you are using an old version of the filter or "
                      "that it was updated without proper version management. "
                      "In the latter case, you should report the issue to the filter's developers."),
                    filter->op_version, filter->operation_name);

      return filter;
    }

  /* We'll set valid properties individually */
  filter->operation = gegl_node_new ();
  gegl_node_set (filter->operation,
                 "operation", filter->operation_name,
                 NULL);

  /* read in the effect properties */
  if (! xcf_load_effect_props (info, &(*filter)))
    goto error;

  /* seek to the effect mask offset */
  xcf_read_offset (info, &mask_offset, 1);

  if (mask_offset != 0)
    {
      if (! xcf_seek_pos (info, mask_offset, NULL))
        goto error;

      effect_mask = xcf_load_channel (info, image);
      if (! effect_mask)
        goto error;
    }

  filter->mask = effect_mask;

  xcf_progress_update (info);

  return filter;

  error:

  xcf_load_free_effect (filter);

  return NULL;
}

static void
xcf_load_free_effect (FilterData *data)
{
  g_free (data->name);
  g_free (data->icon_name);
  g_free (data->operation_name);
  g_free (data->op_version);
  g_clear_object (&data->operation);
  g_clear_object (&data->mask);
  g_free (data);
}

static void
xcf_load_free_effects (GList *effects)
{
  g_list_free_full (effects, (GDestroyNotify) xcf_load_free_effect);
}

static GeglColor *
xcf_load_color (XcfInfo   *info,
                goffset    next_prop,
                gboolean  *valid_prop_value,
                GError   **error)
{
  GeglColor  *color;
  const Babl *format;
  gchar      *encoding;
  guint8     *data  = NULL;
  gint        data_length;
  gint        profile_data_length;

  *valid_prop_value = TRUE;

  if (info->cp == next_prop)
    /* Up to GIMP 3.2, a NULL color would just be empty data. Though
     * it's ugly, we keep this code to load 3.0 files which may have got
     * into this edge case.
     */
    return NULL;

  xcf_read_string (info, &encoding, 1);
  if (encoding == NULL)
    return NULL;

  if (! babl_format_exists (encoding))
    {
      g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
                   "Invalid Babl format \"%s\".", encoding);

      g_free (encoding);
      *valid_prop_value = FALSE;

      return NULL;
    }

  format = babl_format (encoding);
  g_free (encoding);

  xcf_read_int32 (info, (guint32 *) &data_length, 1);
  if (data_length != babl_format_get_bytes_per_pixel (format))
    {
      g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
                    "Format \"%s\" expected %d bpp, but color "
                    "was serialized as %d bpp.",
                    babl_get_name (format),
                    babl_format_get_bytes_per_pixel (format),
                    data_length);

      *valid_prop_value = FALSE;

      return NULL;
    }

  data = g_new (guint8, data_length);
  xcf_read_int8 (info, data, data_length);

  xcf_read_int32 (info, (guint32 *) &profile_data_length, 1);
  if (profile_data_length > 0)
    {
      const Babl       *space = NULL;
      GimpColorProfile *profile;
      guint8           *profile_data;

      profile_data = g_new (guint8, profile_data_length);
      xcf_read_int8 (info, profile_data, profile_data_length);
      profile = gimp_color_profile_new_from_icc_profile (profile_data,
                                                         profile_data_length,
                                                         error);
      g_free (profile_data);

      if (profile)
        {
          space = gimp_color_profile_get_space (profile,
                                                GIMP_COLOR_RENDERING_INTENT_RELATIVE_COLORIMETRIC,
                                                error);
          g_object_unref (profile);
        }

      if (! space)
        {
          g_free (data);
          *valid_prop_value = FALSE;
          return NULL;
        }

      format = babl_format_with_space (babl_format_get_encoding (format), space);
    }

  color = gegl_color_new (NULL);
  gegl_color_set_pixel (color, format, data);

  g_free (data);

  return color;
}

static GimpData *
xcf_load_data (XcfInfo  *info,
               GType     data_type,
               GError  **error)
{
  GimpData        *data = NULL;
  GimpDataFactory *factory;
  gchar           *name;
  gchar           *collection;
  gboolean         is_internal;
  guint32          uint_val;

  xcf_read_string (info, &name,   1);
  if (name != NULL)
    {
      xcf_read_string (info, &collection,   1);
      xcf_read_int32 (info, (guint32 *) &uint_val, 1);
      is_internal = (gboolean) uint_val;

      factory = gimp_get_data_factory (info->gimp, data_type);
      data = gimp_data_factory_get_data (factory, name, collection, is_internal);
      g_free (name);
      g_free (collection);
    }

  return data;
}

/* The new path structure since XCF 18. */
static GimpPath *
xcf_load_path (XcfInfo   *info,
               GimpImage *image)
{
  GimpPath *path = 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);

  GIMP_LOG (XCF, "Path name='%s'", name);

  /* create a new path */
  path = gimp_path_new (image, name);
  g_free (name);
  if (! path)
    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_path_props (info, image, &path))
    goto error;

  GIMP_LOG (XCF, "path props loaded");

  xcf_progress_update (info);

  xcf_read_int32 (info, (guint32 *) &version, 1);

  if (version != 1)
    {
      gimp_message (info->gimp, G_OBJECT (info->progress),
                    GIMP_MESSAGE_WARNING,
                    "Unknown path 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] = 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);

      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 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");
          goto error;
        }

      control_points = gimp_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);
          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_path_stroke_add (path, stroke);

      g_object_unref (stroke);
      gimp_value_array_unref (control_points);
    }


  if (plength != info->cp - base)
    {
      gimp_message (info->gimp, G_OBJECT (info->progress),
                    GIMP_MESSAGE_WARNING,
                    "Path payload size does not match stored size (skipping)");
      goto error;
    }

  return path;

error:

  xcf_seek_pos (info, base + plength, NULL);
  g_clear_object (&path);

  return NULL;
}

static GimpLayerMask *
xcf_load_layer_mask (XcfInfo   *info,
                     GimpImage *image)
{
  GimpLayerMask *layer_mask;
  GimpChannel   *channel;
  GList         *iter;
  goffset        hierarchy_offset;
  gint           width;
  gint           height;
  gboolean       is_fs_drawable;
  gchar         *name;
  GeglColor     *color = gegl_color_new ("black");
  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 > GIMP_MAX_IMAGE_SIZE || height > GIMP_MAX_IMAGE_SIZE)
    {
      GIMP_LOG (XCF, "Invalid layer mask size %d x %d.", width, height);
      return NULL;
    }

  xcf_read_string (info, &name, 1);
  GIMP_LOG (XCF, "Layer mask width=%d, height=%d, name='%s'",
            width, height, name);

  /* create a new layer mask */
  layer_mask = gimp_layer_mask_new (image, width, height, name, color);
  g_object_unref (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, TRUE))
    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)
    {
      GIMP_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,
                         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:
  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 == GIMP_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)
    {
      GIMP_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.
   */
  if (xcf_read_offset (info, &offset, 1) < info->bytes_per_offset)
    {
      GIMP_LOG (XCF, "Failed to read tile offset"
                " at offset: %" G_GOFFSET_FORMAT, info->cp);
      return FALSE;
    }
  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
       */
      if (xcf_read_offset (info, &offset2, 1) < info->bytes_per_offset)
        {
          GIMP_LOG (XCF, "Failed to read tile offset"
                    " at offset: %" G_GOFFSET_FORMAT, info->cp);
          return FALSE;
        }

      /* 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)
        {
          gimp_message (info->gimp, G_OBJECT (info->progress),
                        GIMP_MESSAGE_ERROR,
                        "invalid tile data length: %" G_GOFFSET_FORMAT,
                        offset2 - offset);
          return FALSE;
        }

      /* get buffer rectangle to write to */
      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 */
      if (xcf_read_offset (info, &offset, 1) < info->bytes_per_offset)
        {
          GIMP_LOG (XCF, "Failed to read tile offset"
                    " at offset: %" G_GOFFSET_FORMAT, info->cp);
          return FALSE;
        }
    }

  if (offset != 0)
    {
      gimp_message (info->gimp, G_OBJECT (info->progress), GIMP_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 GimpParasite *
xcf_load_parasite (XcfInfo *info)
{
  GimpParasite *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);

  GIMP_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 || strlen (name) == 0)
    {
      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 = gimp_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,
                    GimpImage *image)
{
  guint32   num_paths;
  guint32   last_selected_row;
  GimpPath *active_path;

  xcf_read_int32 (info, &last_selected_row, 1);
  xcf_read_int32 (info, &num_paths,         1);

  GIMP_LOG (XCF, "Number of old paths: %u", num_paths);

  while (num_paths-- > 0)
    if (! xcf_load_old_path (info, image))
      return FALSE;

  active_path =
    GIMP_PATH (gimp_container_get_child_by_index (gimp_image_get_paths (image),
                                                     last_selected_row));

  if (active_path)
    {
      GList *list = g_list_prepend (NULL, active_path);
      gimp_image_set_selected_paths (image, list);
      g_list_free (list);
    }

  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;
  GimpPath               *path;
  GimpPathCompatPoint    *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 (GimpPathCompatPoint, 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;
        }
    }

  path = gimp_path_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, path);

  if (tattoo)
    gimp_item_set_tattoo (GIMP_ITEM (path), tattoo);

  gimp_image_add_path (image, path,
                       NULL, /* can't be a tree */
                       gimp_container_get_n_children (gimp_image_get_paths (image)),
                       FALSE);

  return TRUE;
}

/* Old vectors are the PROP_VECTORS property up to all GIMP 2.10 versions. */
static gboolean
xcf_load_old_vectors (XcfInfo   *info,
                      GimpImage *image)
{
  guint32   version;
  guint32   active_index;
  guint32   num_paths;
  GimpPath *active_path;

#ifdef GIMP_XCF_PATH_DEBUG
  g_printerr ("xcf_load_old_vectors\n");
#endif

  xcf_read_int32 (info, &version, 1);

  if (version != 1)
    {
      gimp_message (info->gimp, G_OBJECT (info->progress),
                    GIMP_MESSAGE_WARNING,
                    "Unknown path version: %d (skipping)", version);
      return FALSE;
    }

  xcf_read_int32 (info, &active_index, 1);
  xcf_read_int32 (info, &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_old_vector (info, image))
      return FALSE;

  /* FIXME tree */
  active_path =
    GIMP_PATH (gimp_container_get_child_by_index (gimp_image_get_paths (image),
                                                  active_index));

  if (active_path)
    {
      GList *list = g_list_prepend (NULL, active_path);
      gimp_image_set_selected_paths (image, list);
      g_list_free (list);
    }

#ifdef GIMP_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,
                     GimpImage *image)
{
  gchar      *name;
  GimpTattoo  tattoo = 0;
  guint32     visible;
  guint32     linked;
  guint32     num_parasites;
  guint32     num_strokes;
  GimpPath   *path;
  gint        i;

#ifdef GIMP_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 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

  path = gimp_path_new (image, name);
  g_free (name);

  gimp_item_set_visible (GIMP_ITEM (path), visible, FALSE);
  if (linked)
    info->linked_paths = g_list_prepend (info->linked_paths, path);

  if (tattoo)
    gimp_item_set_tattoo (GIMP_ITEM (path), 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 (path), 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 (path), 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[13] = 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);

      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 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++)
        {
          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);
          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_path_stroke_add (path, stroke);

      g_object_unref (stroke);
      gimp_value_array_unref (control_points);
    }

  gimp_image_add_path (image, path,
                       NULL, /* FIXME tree */
                       gimp_container_get_n_children (gimp_image_get_paths (image)),
                       FALSE);

  return TRUE;
}

static void
xcf_load_user_init (XcfInfo   *info,
                    GimpImage *image)
{
  gchar   *unit_strings[5] = { 0 };
  gint     n_fields        = 3;
  float    factor;
  guint32  digits;
  gint     i;

  xcf_read_float (info, &factor, 1);
  xcf_read_int32 (info, &digits, 1);

  /* Depending on XCF version, read more or less strings. */
  if (info->file_version < 21)
    n_fields = 5;
  xcf_read_string (info, unit_strings, n_fields);

  for (i = 0; i < n_fields; i++)
    if (unit_strings[i] == NULL)
      unit_strings[i] = g_strdup ("");

  if (image != NULL)
    {
      GimpUnit *unit;
      GList    *iter;

      for (iter = info->gimp->user_units; iter; iter = iter->next)
        {
          unit = iter->data;
          /* if the factor and the name 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_name (unit)) == 0 ||
               (info->file_version < 21 &&
                strcmp (unit_strings[4], gimp_unit_get_name (unit)) == 0)))
            {
              break;
            }
        }

      if (iter == NULL)
        /* No match. Create a temporary unit set with deletion
         * flag.
         */
        unit = _gimp_unit_new (info->gimp,
                               unit_strings[4] && strlen (unit_strings[4]) > 0 ? unit_strings[4] : unit_strings[0],
                               (gdouble) factor,
                               digits,
                               unit_strings[1],
                               unit_strings[2]);

      gimp_image_set_unit (image, unit);
    }

  for (i = 0; i < n_fields; i++)
    g_free (unit_strings[i]);
}

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 (GimpLayer  *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, gimp_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;
        }
    }
}

static void
xcf_load_free_vector_data (VectorLayerData *data)
{
  g_clear_object (&data->fill_color);
  g_clear_object (&data->stroke_color);
  g_clear_pointer (&data->stroke_dashes, g_free);

  g_free (data);
}
