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;
  GList              *broken_paths            = NULL;
  GList              *group_layers            = NULL;
  GList              *syms;
  GList              *iter;

  /* read in the image width, height and type */
  xcf_read_int32 (info, (guint32 *) &width, 1);
  xcf_read_int32 (info, (guint32 *) &height, 1);
  xcf_read_int32 (info, (guint32 *) &image_type, 1);
  if (image_type < GIMP_RGB || image_type > GIMP_INDEXED ||
      width <= 0 || height <= 0)
    goto hard_error;

  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:
              goto hard_error;
            }
        }
      else if (info->file_version == 5 ||
               info->file_version == 6)
        {
          switch (p)
            {
            case 100: precision = GIMP_PRECISION_U8_LINEAR;        break;
            case 150: precision = GIMP_PRECISION_U8_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:
              goto hard_error;
            }
        }
      else
        {
          precision = p;
        }
    }

  GIMP_LOG (XCF, "version=%d, width=%d, height=%d, image_type=%d, precision=%d",
            info->file_version, width, height, image_type, precision);

  if (! gimp_babl_is_valid (image_type, precision))
    {
      gimp_message_literal (gimp, G_OBJECT (info->progress),
                            GIMP_MESSAGE_ERROR,
                            _("Invalid image mode and precision combination."));
      goto hard_error;
    }

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

      if (metadata)
        {
          has_metadata = TRUE;

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

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

  /* 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 */
      xcf_read_offset (info, &offset, 1);

      /* if the offset is 0 then we are at the end
       *  of the layer list.
       */
      if (offset == 0)
        break;

      /* save the current position as it is where the
       *  next layer offset is stored.
       */
      saved_pos = info->cp;

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

      /* read in the layer */
      layer = xcf_load_layer (info, image, &item_path);
      if (! layer)
        {
          n_broken_layers++;

          if (! xcf_seek_pos (info, saved_pos, NULL))
            {
              if (item_path)
                g_list_free (item_path);

              goto error;
            }

          /* Don't just stop at the first broken layer. Load as much as
           * possible.
           */
          if (! item_path)
            {
              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;
    }

  /* 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 */
      xcf_read_offset (info, &offset, 1);

      /* if the offset is 0 then we are at the end
       *  of the channel list.
       */
      if (offset == 0)
        break;

      /* save the current position as it is where the
       *  next channel offset is stored.
       */
      saved_pos = info->cp;

      /* 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++;

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

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

  /* 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 vectors are really implemented as
       * exception in our XCF spec and building over it seems like a
       * mistake. Since I'm seriously not sure this would be much of an
       * issue, I'll let it as it for now.
       * Note that it's still possible to multi-select paths. It's only
       * not possible to store these selections.
       *
       * Only warn for more than 1 linked path. Less is kind of
       * pointless and doesn't deserve worrying people for no reason.
       */
      if (g_list_length (info->linked_paths) > 1)
        g_printerr ("xcf: some paths were linked. "
                    "GIMP does not support linked paths since version 3.0.\n");

#if 0
      GimpItemList *set;

      set = gimp_item_list_named_new (image, GIMP_TYPE_VECTORS,
                                      _("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)
    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);

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

  gimp_image_undo_enable (image);

  return image;

 hard_error:
  g_clear_pointer (&group_layers, g_list_free);

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

  g_set_error_literal (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
                       _("This XCF file is corrupt!  I could not even "
                         "salvage any partial image data from it."));

  g_clear_object (&image);

  return NULL;
}