[gimp/alxsa-import-export-ani-files: 75/75] plug-ins: Add .ani file import/export




commit e4b959fb7811cc65ed09606e99ff61b51c1d9ad2
Author: Alx Sa <cmyk student gmail com>
Date:   Wed Jun 22 10:33:49 2022 +0000

    plug-ins: Add .ani file import/export
    
    This patch ports a 2.4-based plug-in from James Huang into file-ico.
    It largely reads/writes the .ani header, then uses the existing
    ico/cur functions to load/export the icon data.
    A variable to note current file offsets are added to existing code
    for this purpose.
    Metadata on the cursor and author's name as well as the animation speed
    are also loaded and optionally exported.

 plug-ins/file-ico/ico-dialog.c | 118 ++++++++++++-
 plug-ins/file-ico/ico-dialog.h |  10 +-
 plug-ins/file-ico/ico-load.c   | 195 +++++++++++++++++++--
 plug-ins/file-ico/ico-load.h   |   7 +
 plug-ins/file-ico/ico-save.c   | 380 ++++++++++++++++++++++++++++++++++++-----
 plug-ins/file-ico/ico-save.h   |  11 ++
 plug-ins/file-ico/ico.c        | 303 +++++++++++++++++++++++++++++++-
 plug-ins/file-ico/ico.h        |  16 ++
 8 files changed, 979 insertions(+), 61 deletions(-)
---
diff --git a/plug-ins/file-ico/ico-dialog.c b/plug-ins/file-ico/ico-dialog.c
index fe48e736e4..2cb893561f 100644
--- a/plug-ins/file-ico/ico-dialog.c
+++ b/plug-ins/file-ico/ico-dialog.c
@@ -38,10 +38,16 @@ static void   ico_dialog_toggle_compress (GtkWidget   *checkbox,
                                           GObject     *hbox);
 static void   ico_dialog_check_compat    (GtkWidget   *dialog,
                                           IcoSaveInfo *info);
+static void   ico_dialog_ani_update_inam (GtkEntry    *entry,
+                                          gpointer     data);
+static void   ico_dialog_ani_update_iart (GtkEntry    *entry,
+                                          gpointer     data);
 
 
 GtkWidget *
-ico_dialog_new (IcoSaveInfo *info)
+ico_dialog_new (IcoSaveInfo   *info,
+                AniFileHeader *ani_header,
+                AniSaveInfo   *ani_info)
 {
   GtkWidget     *dialog;
   GtkWidget     *main_vbox;
@@ -51,7 +57,8 @@ ico_dialog_new (IcoSaveInfo *info)
   GtkWidget     *viewport;
   GtkWidget     *warning;
 
-  dialog = gimp_export_dialog_new (info->is_cursor ?
+  dialog = gimp_export_dialog_new (ani_header ?
+                                   _("Windows Animated Cursor") : info->is_cursor ?
                                    _("Windows Cursor") : _("Windows Icon"),
                                    PLUG_IN_BINARY,
                                    "plug-in-winicon");
@@ -65,6 +72,11 @@ ico_dialog_new (IcoSaveInfo *info)
   */
 
   g_object_set_data (G_OBJECT (dialog), "save_info", info);
+  if (ani_header)
+    {
+      g_object_set_data (G_OBJECT (dialog), "save_ani_header", ani_header);
+      g_object_set_data (G_OBJECT (dialog), "save_ani_info", ani_info);
+    }
 
   main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
   gtk_container_set_border_width (GTK_CONTAINER (main_vbox), 6);
@@ -72,6 +84,82 @@ ico_dialog_new (IcoSaveInfo *info)
                       main_vbox, TRUE, TRUE, 0);
   gtk_widget_show (main_vbox);
 
+  /*Animated Cursor */
+  if (ani_header)
+    {
+      GtkWidget     *grid;
+      GtkAdjustment *adjustment;
+      GtkWidget     *spin;
+      GtkWidget     *label;
+      GtkWidget     *hbox;
+      GtkWidget     *entry;
+
+      frame = gimp_frame_new (_("Animated Cursor Settings"));
+      gtk_box_pack_start (GTK_BOX (main_vbox), frame, FALSE, FALSE, 0);
+      gtk_widget_show (frame);
+
+      grid = gtk_grid_new ();
+      gtk_grid_set_column_spacing (GTK_GRID (grid), 6);
+      gtk_grid_set_row_spacing (GTK_GRID (grid), 6);
+      gtk_container_add (GTK_CONTAINER (frame), grid);
+      gtk_widget_show (grid);
+
+      /* Cursor Name */
+      hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
+      gimp_grid_attach_aligned (GTK_GRID (grid), 0, 1,
+                                _("_Cursor Name (Optional)"),
+                                0.0, 0.5,
+                                hbox, 1);
+
+      entry = gtk_entry_new ();
+      gtk_entry_set_text (GTK_ENTRY (entry),
+                          ani_info->inam ? ani_info->inam : "");
+      gtk_box_pack_start (GTK_BOX (hbox), entry, FALSE, FALSE, 0);
+      gtk_widget_show (entry);
+
+      g_signal_connect (GTK_ENTRY (entry), "focus-out-event",
+                        G_CALLBACK (ico_dialog_ani_update_inam),
+                        NULL);
+
+      /* Author Name */
+      hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
+      gimp_grid_attach_aligned (GTK_GRID (grid), 0, 3,
+                                _("_Author Name (Optional)"),
+                                0.0, 0.5,
+                                hbox, 1);
+
+      entry = gtk_entry_new ();
+      gtk_entry_set_text (GTK_ENTRY (entry),
+                          ani_info->iart ? ani_info->iart : "");
+      gtk_box_pack_start (GTK_BOX (hbox), entry, FALSE, FALSE, 0);
+      gtk_widget_show (entry);
+
+      g_signal_connect (GTK_ENTRY (entry), "focus-out-event",
+                        G_CALLBACK (ico_dialog_ani_update_iart),
+                        NULL);
+
+      /* Default delay spin */
+      hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
+      gimp_grid_attach_aligned (GTK_GRID (grid), 0, 5,
+                                _("_Delay between frames:"),
+                                0.0, 0.5,
+                                hbox, 1);
+
+      adjustment = gtk_adjustment_new (ani_header->jif_rate, 1, G_MAXINT,
+                                       1, 10, 0);
+      spin = gimp_spin_button_new (adjustment, 1, 0);
+      gtk_box_pack_start (GTK_BOX (hbox), spin, FALSE, FALSE, 0);
+      gtk_widget_show (spin);
+
+      label = gtk_label_new (_(" jiffies (16.66 ms)"));
+      gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
+      gtk_widget_show (label);
+
+      g_signal_connect (adjustment, "value-changed",
+                        G_CALLBACK (gimp_int_adjustment_update),
+                        &ani_header->jif_rate);
+    }
+
   /* Cursor */
   frame = gimp_frame_new (_("Icon Details"));
   gtk_box_pack_start (GTK_BOX (main_vbox), frame, TRUE, TRUE, 4);
@@ -572,3 +660,29 @@ ico_dialog_check_compat (GtkWidget   *dialog,
 
   gtk_widget_set_visible (warning, warn);
 }
+
+static void
+ico_dialog_ani_update_inam (GtkEntry *entry,
+                            gpointer  data)
+{
+  AniSaveInfo *ani_info;
+  GtkWidget   *dialog;
+
+  dialog = gtk_widget_get_toplevel (GTK_WIDGET (entry));
+
+  ani_info = g_object_get_data (G_OBJECT (dialog), "save_ani_info");
+  ani_info->inam = g_strdup_printf ("%s", gtk_entry_get_text (entry));
+}
+
+static void
+ico_dialog_ani_update_iart (GtkEntry *entry,
+                            gpointer  data)
+{
+  AniSaveInfo *ani_info;
+  GtkWidget   *dialog;
+
+  dialog = gtk_widget_get_toplevel (GTK_WIDGET (entry));
+
+  ani_info = g_object_get_data (G_OBJECT (dialog), "save_ani_info");
+  ani_info->iart = g_strdup_printf ("%s", gtk_entry_get_text (entry));
+}
diff --git a/plug-ins/file-ico/ico-dialog.h b/plug-ins/file-ico/ico-dialog.h
index f39315d80c..ff071af5fb 100644
--- a/plug-ins/file-ico/ico-dialog.h
+++ b/plug-ins/file-ico/ico-dialog.h
@@ -22,9 +22,11 @@
 #define __ICO_DIALOG_H__
 
 
-GtkWidget * ico_dialog_new                 (IcoSaveInfo  *info);
-void        ico_dialog_add_icon            (GtkWidget    *dialog,
-                                            GimpDrawable *layer,
-                                            gint          layer_num);
+GtkWidget * ico_dialog_new                 (IcoSaveInfo   *info,
+                                            AniFileHeader *ani_header,
+                                            AniSaveInfo   *ani_info);
+void        ico_dialog_add_icon            (GtkWidget     *dialog,
+                                            GimpDrawable  *layer,
+                                            gint           layer_num);
 
 #endif /* __ICO_DIALOG_H__ */
diff --git a/plug-ins/file-ico/ico-load.c b/plug-ins/file-ico/ico-load.c
index 36c3b402bb..b29dab85a8 100644
--- a/plug-ins/file-ico/ico-load.c
+++ b/plug-ins/file-ico/ico-load.c
@@ -142,6 +142,7 @@ ico_read_init (FILE *fp)
 
 static gboolean
 ico_read_size (FILE        *fp,
+               gint32       file_offset,
                IcoLoadInfo *info)
 {
   png_structp png_ptr;
@@ -151,7 +152,7 @@ ico_read_size (FILE        *fp,
   gint32      color_type;
   guint32     magic;
 
-  if (fseek (fp, info->offset, SEEK_SET) < 0)
+  if (fseek (fp, info->offset + file_offset, SEEK_SET) < 0)
     return FALSE;
 
   ico_read_int32 (fp, &magic, 1);
@@ -208,6 +209,7 @@ ico_read_size (FILE        *fp,
 static IcoLoadInfo*
 ico_read_info (FILE    *fp,
                gint     icon_count,
+               gint32   file_offset,
                GError **error)
 {
   gint            i;
@@ -237,7 +239,7 @@ ico_read_info (FILE    *fp,
 
       if (info[i].width == 0 || info[i].height == 0)
         {
-          ico_read_size (fp, info + i);
+          ico_read_size (fp, file_offset, info + i);
         }
 
       D(("ico_read_info: %ix%i (%i bits, size: %i, offset: %i)\n",
@@ -605,6 +607,7 @@ ico_load_layer (FILE        *fp,
                 gint32       icon_num,
                 guchar      *buf,
                 gint         maxsize,
+                gint32       file_offset,
                 IcoLoadInfo *info)
 {
   gint        width, height;
@@ -613,7 +616,7 @@ ico_load_layer (FILE        *fp,
   GeglBuffer *buffer;
   gchar       name[ICO_MAXBUF];
 
-  if (fseek (fp, info->offset, SEEK_SET) < 0 ||
+  if (fseek (fp, info->offset + file_offset, SEEK_SET) < 0 ||
       ! ico_read_int32 (fp, &first_bytes, 1))
     return NULL;
 
@@ -653,6 +656,7 @@ ico_load_layer (FILE        *fp,
 
 GimpImage *
 ico_load_image (GFile        *file,
+                gint32       *file_offset,
                 GError      **error)
 {
   FILE          *fp;
@@ -666,8 +670,9 @@ ico_load_image (GFile        *file,
   gint           maxsize;
   gchar         *str;
 
-  gimp_progress_init_printf (_("Opening '%s'"),
-                             gimp_file_get_utf8_name (file));
+  if (! file_offset)
+    gimp_progress_init_printf (_("Opening '%s'"),
+                               gimp_file_get_utf8_name (file));
 
   fp = g_fopen (g_file_peek_path (file), "rb");
 
@@ -679,6 +684,9 @@ ico_load_image (GFile        *file,
       return NULL;
     }
 
+  if (file_offset)
+    fseek (fp, *file_offset, SEEK_SET);
+
   header = ico_read_init (fp);
   icon_count = header.icon_count;
   if (!icon_count)
@@ -687,7 +695,7 @@ ico_load_image (GFile        *file,
       return NULL;
     }
 
-  info = ico_read_info (fp, icon_count, error);
+  info = ico_read_info (fp, icon_count, file_offset ? *file_offset : 0, error);
   if (! info)
     {
       fclose (fp);
@@ -713,7 +721,8 @@ ico_load_image (GFile        *file,
   D(("image size: %ix%i\n", max_width, max_height));
 
   image = gimp_image_new (max_width, max_height, GIMP_RGB);
-  gimp_image_set_file (image, file);
+  if (! file_offset)
+    gimp_image_set_file (image, file);
 
   maxsize = max_width * max_height * 4;
   buf = g_new (guchar, max_width * max_height * 4);
@@ -721,7 +730,7 @@ ico_load_image (GFile        *file,
     {
       GimpLayer *layer;
 
-      layer = ico_load_layer (fp, image, i, buf, maxsize, info+i);
+      layer = ico_load_layer (fp, image, i, buf, maxsize, file_offset ? *file_offset : 0, info+i);
 
       /* Save CUR hot spot information */
       if (header.resource_type == 2)
@@ -737,10 +746,172 @@ ico_load_image (GFile        *file,
           gimp_parasite_free (parasite);
         }
     }
+
+  if (file_offset)
+    *file_offset = ftell (fp);
+
   g_free (buf);
   g_free (info);
   fclose (fp);
 
+  /* Don't update progress here if .ani file */
+  if (! file_offset)
+    gimp_progress_update (1.0);
+
+  return image;
+}
+
+/* Ported from James Huang's ani.c code, under the GPL license, version 3
+ * or any later version of the license */
+GimpImage *
+ani_load_image (GFile   *file,
+                gboolean load_thumb,
+                gint    *width,
+                gint    *height,
+                GError **error)
+{
+  FILE         *fp;
+  GimpImage    *image = NULL;
+  GimpParasite *parasite;
+  gchar         id[4];
+  guint32       size;
+  gint32        file_offset;
+  gint          frame = 1;
+  AniFileHeader header;
+  gchar         inam[G_MAXSHORT] = {0};
+  gchar         iart[G_MAXSHORT] = {0};
+  gchar        *str;
+
+  gimp_progress_init_printf (_("Opening '%s'"),
+                             gimp_file_get_utf8_name (file));
+
+  fp = g_fopen (g_file_peek_path (file), "rb");
+
+  if (! fp)
+    {
+      g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
+                   _("Could not open '%s' for reading: %s"),
+                   gimp_file_get_utf8_name (file), g_strerror (errno));
+      return NULL;
+    }
+
+  while (fread (id, 1, 4, fp) == 4)
+    {
+      if (memcmp (id, "RIFF", 4) == 0 )
+        {
+          fread (&size, sizeof (size), 1, fp);
+        }
+      else if (memcmp (id, "anih", 4) == 0)
+        {
+          fread (&size, sizeof (size), 1, fp);
+          fread (&header, sizeof (header), 1, fp);
+        }
+      else if (memcmp (id, "rate", 4) == 0)
+        {
+          fread (&size, sizeof (size), 1, fp);
+          fseek (fp, size, SEEK_CUR);
+        }
+      else if (memcmp (id, "seq ", 4) == 0)
+        {
+          fread (&size, sizeof (size), 1, fp);
+          fseek (fp, size, SEEK_CUR);
+        }
+      else if (memcmp (id, "LIST", 4) == 0)
+        {
+          fread (&size, sizeof (size), 1, fp);
+        }
+      else if (memcmp (id, "INAM", 4) == 0)
+        {
+          fread (&size, sizeof (size), 1, fp);
+          fread (&inam, sizeof (char), size, fp);
+          inam[size] = '\0';
+        }
+      else if (memcmp (id, "IART", 4) == 0)
+        {
+          fread (&size, sizeof (size), 1, fp);
+          fread (&iart, sizeof (char), size, fp);
+          iart[size] = '\0';
+        }
+      else if (memcmp (id, "icon", 4) == 0)
+        {
+          fread (&size, sizeof (size), 1, fp);
+          file_offset = ftell (fp);
+          if (load_thumb)
+            {
+              image = ico_load_thumbnail_image (file, width, height, file_offset, error);
+              break;
+            }
+           else
+             {
+               if (! image)
+                 {
+                   image = ico_load_image (file, &file_offset, error);
+                 }
+               else
+                 {
+                   GimpImage    *temp_image = NULL;
+                   GimpLayer   **layers;
+                   GimpLayer    *new_layer;
+                   gint          nlayers;
+
+                   temp_image = ico_load_image (file, &file_offset, error);
+                   layers = gimp_image_get_layers (temp_image, &nlayers);
+                   if (layers)
+                     {
+                       for (gint i = 0; i < nlayers; i++)
+                         {
+                           new_layer = gimp_layer_new_from_drawable (GIMP_DRAWABLE (layers[i]),
+                                                                     image);
+                           gimp_image_insert_layer (image, new_layer, NULL, frame);
+                           frame++;
+                         }
+                     }
+                   gimp_image_delete (temp_image);
+                 }
+
+               /* Update position after reading icon data */
+               fseek (fp, file_offset, SEEK_SET);
+               if (header.frames > 0)
+                 gimp_progress_update ((gdouble) frame /
+                                       (gdouble) header.frames);
+             }
+        }
+    }
+  fclose (fp);
+
+  /* Saving header metadata */
+  str = g_strdup_printf ("%d", header.jif_rate);
+  parasite = gimp_parasite_new ("ani-header",
+                                GIMP_PARASITE_PERSISTENT,
+                                strlen (str) + 1, (gpointer) str);
+  g_free (str);
+  gimp_image_attach_parasite (image, parasite);
+  gimp_parasite_free (parasite);
+
+  /* Saving INFO block */
+  if (strlen (inam) > 0)
+    {
+      str = g_strdup_printf ("%s", inam);
+      parasite = gimp_parasite_new ("ani-info-inam",
+                                    GIMP_PARASITE_PERSISTENT,
+                                    strlen (str) + 1, (gpointer) str);
+      g_free (str);
+      gimp_image_attach_parasite (image, parasite);
+      gimp_parasite_free (parasite);
+    }
+
+  if (strlen (iart) > 0)
+    {
+      str = g_strdup_printf ("%s", iart);
+      parasite = gimp_parasite_new ("ani-info-iart",
+                                    GIMP_PARASITE_PERSISTENT,
+                                    strlen (str) + 1, (gpointer) str);
+      g_free (str);
+      gimp_image_attach_parasite (image, parasite);
+      gimp_parasite_free (parasite);
+    }
+
+  gimp_image_set_file (image, file);
   gimp_progress_update (1.0);
 
   return image;
@@ -750,6 +921,7 @@ GimpImage *
 ico_load_thumbnail_image (GFile   *file,
                           gint    *width,
                           gint    *height,
+                          gint32   file_offset,
                           GError **error)
 {
   FILE          *fp;
@@ -776,6 +948,9 @@ ico_load_thumbnail_image (GFile   *file,
       return NULL;
     }
 
+  if (file_offset > 0)
+    fseek (fp, file_offset, SEEK_SET);
+
   header = ico_read_init (fp);
   icon_count = header.icon_count;
   if (! icon_count)
@@ -787,7 +962,7 @@ ico_load_thumbnail_image (GFile   *file,
   D(("*** %s: Microsoft icon file, containing %i icon(s)\n",
      gimp_file_get_utf8_name (file), icon_count));
 
-  info = ico_read_info (fp, icon_count, error);
+  info = ico_read_info (fp, icon_count, file_offset, error);
   if (! info)
     {
       fclose (fp);
@@ -821,7 +996,7 @@ ico_load_thumbnail_image (GFile   *file,
 
   image = gimp_image_new (w, h, GIMP_RGB);
   buf = g_new (guchar, w*h*4);
-  ico_load_layer (fp, image, match, buf, w*h*4, info+match);
+  ico_load_layer (fp, image, match, buf, w*h*4, file_offset, info+match);
   g_free (buf);
 
   *width  = w;
diff --git a/plug-ins/file-ico/ico-load.h b/plug-ins/file-ico/ico-load.h
index 5c97cfdf67..88f9f68d61 100644
--- a/plug-ins/file-ico/ico-load.h
+++ b/plug-ins/file-ico/ico-load.h
@@ -23,10 +23,17 @@
 
 
 GimpImage * ico_load_image           (GFile         *file,
+                                      gint32        *file_offset,
+                                      GError       **error);
+GimpImage * ani_load_image           (GFile         *file,
+                                      gboolean       load_thumb,
+                                      gint          *width,
+                                      gint          *height,
                                       GError       **error);
 GimpImage * ico_load_thumbnail_image (GFile         *file,
                                       gint          *width,
                                       gint          *height,
+                                      gint32         file_offset,
                                       GError       **error);
 
 gint        ico_get_bit_from_data    (const guint8  *data,
diff --git a/plug-ins/file-ico/ico-save.c b/plug-ins/file-ico/ico-save.c
index 4993166732..915c61b373 100644
--- a/plug-ins/file-ico/ico-save.c
+++ b/plug-ins/file-ico/ico-save.c
@@ -82,12 +82,15 @@ static gboolean ico_save_init             (GimpImage    *image,
                                            GError      **error);
 static GimpPDBStatusType
                 shared_save_image         (GFile         *file,
+                                           FILE          *fp_ani,
                                            GimpImage     *image,
                                            gint32         run_mode,
                                            gint          *n_hot_spot_x,
                                            gint32       **hot_spot_x,
                                            gint          *n_hot_spot_y,
                                            gint32       **hot_spot_y,
+                                           gint32         file_offset,
+                                           gint           icon_index,
                                            GError       **error,
                                            IcoSaveInfo   *info);
 
@@ -302,8 +305,10 @@ ico_save_init (GimpImage   *image,
 
 
 static gboolean
-ico_save_dialog (GimpImage      *image,
-                 IcoSaveInfo    *info)
+ico_save_dialog (GimpImage     *image,
+                 IcoSaveInfo   *info,
+                 AniFileHeader *ani_header,
+                 AniSaveInfo   *ani_info)
 {
   GtkWidget     *dialog;
   GList         *iter;
@@ -312,7 +317,7 @@ ico_save_dialog (GimpImage      *image,
 
   gimp_ui_init (PLUG_IN_BINARY);
 
-  dialog = ico_dialog_new (info);
+  dialog = ico_dialog_new (info, ani_header, ani_info);
   for (iter = info->layers, i = 0;
        iter;
        iter = g_list_next (iter), i++)
@@ -1174,9 +1179,9 @@ ico_save_image (GFile      *file,
 
   info.is_cursor = FALSE;
 
-  return shared_save_image (file, image, run_mode,
+  return shared_save_image (file, NULL, image, run_mode,
                             0, NULL, 0, NULL,
-                            error, &info);
+                            0, 0, error, &info);
 }
 
 GimpPDBStatusType
@@ -1196,20 +1201,285 @@ cur_save_image (GFile      *file,
 
   info.is_cursor = TRUE;
 
-  return shared_save_image (file, image, run_mode,
+  return shared_save_image (file, NULL, image, run_mode,
                             n_hot_spot_x, hot_spot_x,
                             n_hot_spot_y, hot_spot_y,
-                            error, &info);
+                            0, 0, error, &info);
+}
+
+/* Ported from James Huang's ani.c code, under the GPL v3 license */
+GimpPDBStatusType
+ani_save_image (GFile         *file,
+                GimpImage     *image,
+                gint32         run_mode,
+                gint          *n_hot_spot_x,
+                gint32       **hot_spot_x,
+                gint          *n_hot_spot_y,
+                gint32       **hot_spot_y,
+                AniFileHeader *header,
+                AniSaveInfo   *ani_info,
+                GError       **error)
+{
+  FILE         *fp;
+  gint32        i;
+  gchar        *str;
+  GimpParasite *parasite = NULL;
+  gchar         id[5];
+  guint32       size;
+  gint32        offset, ofs_size_riff, ofs_size_list, ofs_size_icon;
+  gint32        ofs_size_info = 0;
+  IcoSaveInfo   info;
+
+  if (! ico_save_init (image, run_mode, &info,
+                        *n_hot_spot_x, *hot_spot_x,
+                        *n_hot_spot_y, *hot_spot_y,
+                        error))
+    {
+      return GIMP_PDB_EXECUTION_ERROR;
+    }
+
+  /* Save individual frames as .cur so we can retain
+   * the hotspot information
+   */
+  info.is_cursor = TRUE;
+
+  /* Default header values */
+  header->bSizeOf = sizeof (*header);
+  header->frames = info.num_icons;
+  header->steps = info.num_icons;
+  header->x = 0;
+  header->y = 0;
+  if (info.depths[0] == 24)
+    {
+      header->bpp = 4;
+      header->planes = 1;
+    }
+  else
+    {
+      header->bpp = 0;
+      header->planes = 0;
+    }
+  header->flags = 1;
+
+  /* Load metadata from parasite */
+  parasite = gimp_image_get_parasite (image, "ani-header");
+  if (parasite)
+    {
+      gchar   *parasite_data;
+      guint32  parasite_size;
+      gint     jif_rate;
+
+      parasite_data = (gchar *) gimp_parasite_get_data (parasite, &parasite_size);
+      parasite_data = g_strndup (parasite_data, parasite_size);
+
+      if (sscanf (parasite_data, "%i", &jif_rate) == 1)
+        {
+          header->jif_rate = jif_rate;
+        }
+
+      gimp_parasite_free (parasite);
+      g_free (parasite_data);
+    }
+
+  parasite = gimp_image_get_parasite (image, "ani-info-inam");
+  if (parasite)
+    {
+      guint32  parasite_size;
+      gchar   *inam = NULL;
+
+      inam = (gchar *) gimp_parasite_get_data (parasite, &parasite_size);
+      ani_info->inam = g_strndup (inam, parasite_size);
+
+      gimp_parasite_free (parasite);
+    }
+
+  parasite = gimp_image_get_parasite (image, "ani-info-iart");
+  if (parasite)
+    {
+      guint32  parasite_size;
+      gchar   *iart = NULL;
+
+      iart = (gchar *) gimp_parasite_get_data (parasite, &parasite_size);
+      ani_info->iart = g_strndup (iart, parasite_size);
+
+      gimp_parasite_free (parasite);
+    }
+
+  if (run_mode == GIMP_RUN_INTERACTIVE)
+    {
+      if (! ico_save_dialog (image, &info,
+                             header, ani_info))
+        return GIMP_PDB_CANCEL;
+
+      for (i = 1; i < info.num_icons; i++)
+        {
+          info.depths[i] = info.depths[0];
+          info.default_depths[i] = info.default_depths[0];
+          info.compress[i] = info.compress[0];
+        }
+    }
+
+  gimp_progress_init_printf (_("Exporting '%s'"),
+                             gimp_file_get_utf8_name (file));
+
+  fp = g_fopen (g_file_peek_path (file), "wb");
+
+  if (! fp)
+    {
+      g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
+                   _("Could not open '%s' for writing: %s"),
+                   gimp_file_get_utf8_name (file), g_strerror (errno));
+      return GIMP_PDB_EXECUTION_ERROR;
+    }
+
+  /* Writing the .ani header data */
+  strcpy (id, "RIFF");
+  size = 0;
+  fwrite (id, 4, 1, fp);
+  ofs_size_riff = ftell (fp);
+  fwrite (&size, sizeof (size), 1, fp);
+
+  strcpy (id, "ACON");
+  fwrite (id, 4, 1, fp);
+
+  if ((ani_info->inam && strlen (ani_info->inam) > 0) ||
+      (ani_info->iart && strlen (ani_info->iart) > 0))
+    {
+      gint32 string_size;
+
+      strcpy (id, "LIST");
+      fwrite (id, 4, 1, fp);
+      ofs_size_info = ftell (fp);
+      fwrite (&size, sizeof (size), 1, fp);
+
+      strcpy (id, "INFO");
+      fwrite (id, 4, 1, fp);
+      if (ani_info->inam && strlen (ani_info->inam) > 0) /* Cursor name */
+        {
+          strcpy (id, "INAM");
+          fwrite (id, 4, 1, fp);
+          string_size = strlen (ani_info->inam) + 1;
+          fwrite (&string_size, 4, 1, fp);
+          fwrite (ani_info->inam, string_size, 1, fp);
+        }
+      if (ani_info->iart && strlen (ani_info->iart) > 0) /* Author name */
+        {
+          strcpy (id, "IART");
+          fwrite (id, 4, 1, fp);
+          string_size = strlen (ani_info->iart) + 1;
+          fwrite (&string_size, 4, 1, fp);
+          fwrite (ani_info->iart, string_size, 1, fp);
+        }
+
+      /* Go back and update info list size */
+      fseek (fp, 0L, SEEK_END);
+      size = ftell (fp) - ofs_size_info - 4;
+      fseek (fp, ofs_size_info, SEEK_SET);
+      fwrite (&size, sizeof (size), 1, fp);
+      fseek (fp, 0L, SEEK_END);
+    }
+
+  strcpy (id, "anih");
+  size = sizeof (*header);
+  fwrite (id, 4, 1, fp);
+  fwrite (&size, sizeof (size), 1, fp);
+  fwrite (header, sizeof (*header), 1, fp);
+
+  strcpy (id, "LIST");
+  fwrite (id, 4, 1, fp);
+  ofs_size_list = ftell (fp);
+  fwrite (&size, sizeof (size), 1, fp);
+
+  strcpy (id, "fram");
+  fwrite (id, 4, 1, fp);
+
+  strcpy (id, "icon");
+  for (i = 0; i < info.num_icons; i++ )
+    {
+      GimpPDBStatusType status;
+      fwrite (id, 4, 1, fp);
+      ofs_size_icon = ftell (fp);
+      fwrite (&size, sizeof (size), 1, fp);
+      offset = ftell (fp);
+      status = shared_save_image (file, fp, image, run_mode,
+                                  n_hot_spot_x, hot_spot_x,
+                                  n_hot_spot_y, hot_spot_y,
+                                  offset, i, error, &info);
+
+      if (status != GIMP_PDB_SUCCESS)
+        {
+          ico_save_info_free (&info);
+          g_free (ani_info->inam);
+          g_free (ani_info->iart);
+          fclose (fp);
+          return GIMP_PDB_EXECUTION_ERROR;
+        }
+      fseek (fp, 0L, SEEK_END);
+      size = ftell (fp) - offset;
+      fseek (fp, ofs_size_icon, SEEK_SET);
+      fwrite (&size, sizeof (size), 1, fp);
+      fseek (fp, 0L, SEEK_END);
+
+      gimp_progress_update ((gdouble) i / (gdouble) info.num_icons);
+    }
+  ico_save_info_free (&info);
+
+  fseek (fp, 0L, SEEK_END);
+  size = ftell (fp);
+  fseek (fp, ofs_size_riff, SEEK_SET);
+  fwrite (&size, sizeof (size), 1, fp);
+
+  size -= ofs_size_list;
+  fseek (fp, ofs_size_list, SEEK_SET);
+  fwrite (&size, sizeof (size), 1, fp);
+  fclose (fp);
+
+  /* Update metadata if needed */
+  str = g_strdup_printf ("%d", header->jif_rate);
+  parasite = gimp_parasite_new ("ani-header",
+                                GIMP_PARASITE_PERSISTENT,
+                                strlen (str) + 1, (gpointer) str);
+  g_free (str);
+  gimp_image_attach_parasite (image, parasite);
+  gimp_parasite_free (parasite);
+
+  if (ani_info->inam && strlen (ani_info->inam) > 0)
+    {
+      str = g_strdup_printf ("%s", ani_info->inam);
+      parasite = gimp_parasite_new ("ani-info-inam",
+                                    GIMP_PARASITE_PERSISTENT,
+                                    strlen (ani_info->inam) + 1, (gpointer) str);
+      g_free (str);
+      gimp_image_attach_parasite (image, parasite);
+      gimp_parasite_free (parasite);
+    }
+  if (ani_info->iart && strlen (ani_info->iart) > 0)
+    {
+      str = g_strdup_printf ("%s", ani_info->iart);
+      parasite = gimp_parasite_new ("ani-info-iart",
+                                    GIMP_PARASITE_PERSISTENT,
+                                    strlen (ani_info->iart) + 1, (gpointer) str);
+      g_free (str);
+      gimp_image_attach_parasite (image, parasite);
+      gimp_parasite_free (parasite);
+    }
+
+  gimp_progress_update (1.0);
+
+  return GIMP_PDB_SUCCESS;
 }
 
 GimpPDBStatusType
 shared_save_image (GFile        *file,
+                   FILE         *fp_ani,
                    GimpImage    *image,
                    gint32        run_mode,
                    gint         *n_hot_spot_x,
                    gint32      **hot_spot_x,
                    gint         *n_hot_spot_y,
                    gint32      **hot_spot_y,
+                   gint32        file_offset,
+                   gint          icon_index,
                    GError      **error,
                    IcoSaveInfo  *info)
 {
@@ -1221,10 +1491,12 @@ shared_save_image (GFile        *file,
   IcoFileEntry  *entries;
   gboolean       saved;
   gint           i;
+  gint           num_icons;
   GimpParasite  *parasite = NULL;
   gchar         *str;
 
-  if (! ico_save_init (image, run_mode, info,
+  if (! fp_ani &&
+      ! ico_save_init (image, run_mode, info,
                        n_hot_spot_x ? *n_hot_spot_x : 0,
                        hot_spot_x   ? *hot_spot_x   : NULL,
                        n_hot_spot_y ? *n_hot_spot_y : 0,
@@ -1234,17 +1506,29 @@ shared_save_image (GFile        *file,
       return GIMP_PDB_EXECUTION_ERROR;
     }
 
-  if (run_mode == GIMP_RUN_INTERACTIVE)
+  if (run_mode == GIMP_RUN_INTERACTIVE && ! fp_ani)
     {
       /* Allow user to override default values */
-      if ( !ico_save_dialog (image, info))
+      if ( !ico_save_dialog (image, info,
+                             NULL, NULL))
         return GIMP_PDB_CANCEL;
     }
 
-  gimp_progress_init_printf (_("Exporting '%s'"),
-                             gimp_file_get_utf8_name (file));
+  num_icons = (fp_ani) ? 1 : info->num_icons;
 
-  fp = g_fopen (g_file_peek_path (file), "wb");
+  if (! fp_ani)
+    gimp_progress_init_printf (_("Exporting '%s'"),
+                               gimp_file_get_utf8_name (file));
+
+  /* If saving an .ani file, we append the next icon frame. */
+  if (! fp_ani)
+    {
+      fp = g_fopen (g_file_peek_path (file), "wb");
+    }
+  else
+    {
+      fp = fp_ani;
+    }
 
   if (! fp)
     {
@@ -1258,7 +1542,7 @@ shared_save_image (GFile        *file,
   header.resource_type = 1;
   if (info->is_cursor)
     header.resource_type = 2;
-  header.icon_count = info->num_icons;
+  header.icon_count = num_icons;
   if (! ico_write_int16 (fp, &header.reserved, 1)      ||
       ! ico_write_int16 (fp, &header.resource_type, 1) ||
       ! ico_write_int16 (fp, &header.icon_count, 1))
@@ -1268,8 +1552,8 @@ shared_save_image (GFile        *file,
       return GIMP_PDB_EXECUTION_ERROR;
     }
 
-  entries = g_new0 (IcoFileEntry, info->num_icons);
-  if (fwrite (entries, sizeof (IcoFileEntry), info->num_icons, fp) <= 0)
+  entries = g_new0 (IcoFileEntry, num_icons);
+  if (fwrite (entries, sizeof (IcoFileEntry), num_icons, fp) <= 0)
     {
       ico_save_info_free (info);
       g_free (entries);
@@ -1281,7 +1565,12 @@ shared_save_image (GFile        *file,
        iter;
        iter = g_list_next (iter), i++)
     {
-      gimp_progress_update ((gdouble)i / (gdouble)info->num_icons);
+      if (! fp_ani)
+        gimp_progress_update ((gdouble)i / (gdouble)info->num_icons);
+
+      /* If saving .ani file, jump to the correct frame */
+      if (fp_ani)
+        iter = g_list_nth (info->layers, icon_index);
 
       width = gimp_drawable_get_width (iter->data);
       height = gimp_drawable_get_height (iter->data);
@@ -1305,10 +1594,12 @@ shared_save_image (GFile        *file,
       /* .cur file reuses these fields for cursor offsets */
       if (info->is_cursor)
         {
-          entries[i].planes = info->hot_spot_x[i];
-          entries[i].bpp = info->hot_spot_y[i];
+          gint hot_spot_index = icon_index ? icon_index : i;
+
+          entries[i].planes = info->hot_spot_x[hot_spot_index];
+          entries[i].bpp = info->hot_spot_y[hot_spot_index];
         }
-      entries[i].offset = ftell (fp);
+      entries[i].offset = ftell (fp) - file_offset;
 
       if (info->compress[i])
         saved = ico_write_png (fp, iter->data, info->depths[i]);
@@ -1322,10 +1613,13 @@ shared_save_image (GFile        *file,
           return GIMP_PDB_EXECUTION_ERROR;
         }
 
-      entries[i].size = ftell (fp) - entries[i].offset;
+      entries[i].size = ftell (fp) - file_offset - entries[i].offset;
+
+      if (fp_ani)
+        break;
     }
 
-  for (i = 0; i < info->num_icons; i++)
+  for (i = 0; i < num_icons; i++)
     {
       entries[i].planes = GUINT16_TO_LE (entries[i].planes);
       entries[i].bpp    = GUINT16_TO_LE (entries[i].bpp);
@@ -1333,15 +1627,16 @@ shared_save_image (GFile        *file,
       entries[i].offset = GUINT32_TO_LE (entries[i].offset);
     }
 
-  if (fseek (fp, sizeof(IcoFileHeader), SEEK_SET) < 0
-      || fwrite (entries, sizeof (IcoFileEntry), info->num_icons, fp) <= 0)
+  if (fseek (fp, sizeof (IcoFileHeader) + file_offset, SEEK_SET) < 0 ||
+      fwrite (entries, sizeof (IcoFileEntry), num_icons, fp) <= 0)
     {
       ico_save_info_free (info);
       fclose (fp);
       return GIMP_PDB_EXECUTION_ERROR;
     }
 
-  gimp_progress_update (1.0);
+  if (! fp_ani)
+    gimp_progress_update (1.0);
 
   /* Updating parasite hot spots if needed */
   if (info->is_cursor)
@@ -1360,23 +1655,32 @@ shared_save_image (GFile        *file,
         }
     }
 
-  if (hot_spot_x)
+  if (! fp_ani)
     {
-      *hot_spot_x = info->hot_spot_x;
-      info->hot_spot_x = NULL;
+      if (hot_spot_x)
+        {
+          *hot_spot_x = info->hot_spot_x;
+          info->hot_spot_x = NULL;
+        }
+      if (hot_spot_y)
+        {
+          *hot_spot_y = info->hot_spot_y;
+          info->hot_spot_y = NULL;
+        }
+      if (n_hot_spot_x)
+        *n_hot_spot_x = num_icons;
+      if (n_hot_spot_y)
+        *n_hot_spot_y = num_icons;
     }
-  if (hot_spot_y)
+
+  /* If saving .ani file, don't clear until
+   * all icons are saved in ani_save_image ()
+   */
+  if (! file_offset)
     {
-      *hot_spot_y = info->hot_spot_y;
-      info->hot_spot_y = NULL;
+      ico_save_info_free (info);
+      fclose (fp);
     }
-  if (n_hot_spot_x)
-    *n_hot_spot_x = info->num_icons;
-  if (n_hot_spot_y)
-    *n_hot_spot_y = info->num_icons;
-
-  ico_save_info_free (info);
-  fclose (fp);
   g_free (entries);
 
   return GIMP_PDB_SUCCESS;
diff --git a/plug-ins/file-ico/ico-save.h b/plug-ins/file-ico/ico-save.h
index 1e1ed82dc9..ff9b3d7a30 100644
--- a/plug-ins/file-ico/ico-save.h
+++ b/plug-ins/file-ico/ico-save.h
@@ -36,6 +36,17 @@ GimpPDBStatusType cur_save_image          (GFile         *file,
                                            gint32       **hot_spot_y,
                                            GError       **error);
 
+GimpPDBStatusType ani_save_image          (GFile         *file,
+                                           GimpImage     *image,
+                                           gint32         run_mode,
+                                           gint          *n_hot_spot_x,
+                                           gint32       **hot_spot_x,
+                                           gint          *n_hot_spot_y,
+                                           gint32       **hot_spot_y,
+                                           AniFileHeader *header,
+                                           AniSaveInfo   *ani_info,
+                                           GError       **error);
+
 gboolean          ico_cmap_contains_black (const guchar  *cmap,
                                            gint           num_colors);
 
diff --git a/plug-ins/file-ico/ico.c b/plug-ins/file-ico/ico.c
index 1ac292760d..0b4efda25d 100644
--- a/plug-ins/file-ico/ico.c
+++ b/plug-ins/file-ico/ico.c
@@ -20,8 +20,11 @@
 
 #include "config.h"
 
+#include <stdlib.h>
 #include <string.h>
 
+#include <glib/gstdio.h>
+
 #include <libgimp/gimp.h>
 #include <libgimp/gimpui.h>
 
@@ -33,11 +36,14 @@
 
 #include "libgimp/stdplugins-intl.h"
 
-#define LOAD_PROC        "file-ico-load"
-#define LOAD_CUR_PROC    "file-cur-load"
-#define LOAD_THUMB_PROC  "file-ico-load-thumb"
-#define SAVE_PROC        "file-ico-save"
-#define SAVE_CUR_PROC    "file-cur-save"
+#define LOAD_PROC           "file-ico-load"
+#define LOAD_CUR_PROC       "file-cur-load"
+#define LOAD_ANI_PROC       "file-ani-load"
+#define LOAD_THUMB_PROC     "file-ico-load-thumb"
+#define LOAD_ANI_THUMB_PROC "file-ani-load-thumb"
+#define SAVE_PROC           "file-ico-save"
+#define SAVE_CUR_PROC       "file-cur-save"
+#define SAVE_ANI_PROC       "file-ani-save"
 
 
 typedef struct _Ico      Ico;
@@ -68,11 +74,21 @@ static GimpValueArray * ico_load             (GimpProcedure        *procedure,
                                               GFile                *file,
                                               const GimpValueArray *args,
                                               gpointer              run_data);
+static GimpValueArray * ani_load             (GimpProcedure        *procedure,
+                                              GimpRunMode           run_mode,
+                                              GFile                *file,
+                                              const GimpValueArray *args,
+                                              gpointer              run_data);
 static GimpValueArray * ico_load_thumb       (GimpProcedure        *procedure,
                                               GFile                *file,
                                               gint                  size,
                                               const GimpValueArray *args,
                                               gpointer              run_data);
+static GimpValueArray * ani_load_thumb       (GimpProcedure        *procedure,
+                                              GFile                *file,
+                                              gint                  size,
+                                              const GimpValueArray *args,
+                                              gpointer              run_data);
 static GimpValueArray * ico_save             (GimpProcedure        *procedure,
                                               GimpRunMode           run_mode,
                                               GimpImage            *image,
@@ -89,6 +105,14 @@ static GimpValueArray * cur_save             (GimpProcedure        *procedure,
                                               GFile                *file,
                                               const GimpValueArray *args,
                                               gpointer              run_data);
+static GimpValueArray * ani_save             (GimpProcedure        *procedure,
+                                              GimpRunMode           run_mode,
+                                              GimpImage            *image,
+                                              gint                  n_drawables,
+                                              GimpDrawable        **drawables,
+                                              GFile                *file,
+                                              const GimpValueArray *args,
+                                              gpointer              run_data);
 
 
 G_DEFINE_TYPE (Ico, ico, GIMP_TYPE_PLUG_IN)
@@ -118,10 +142,13 @@ ico_query_procedures (GimpPlugIn *plug_in)
   GList *list = NULL;
 
   list = g_list_append (list, g_strdup (LOAD_THUMB_PROC));
+  list = g_list_append (list, g_strdup (LOAD_ANI_THUMB_PROC));
   list = g_list_append (list, g_strdup (LOAD_PROC));
   list = g_list_append (list, g_strdup (LOAD_CUR_PROC));
+  list = g_list_append (list, g_strdup (LOAD_ANI_PROC));
   list = g_list_append (list, g_strdup (SAVE_PROC));
   list = g_list_append (list, g_strdup (SAVE_CUR_PROC));
+  list = g_list_append (list, g_strdup (SAVE_ANI_PROC));
 
   return list;
 }
@@ -190,6 +217,36 @@ ico_create_procedure (GimpPlugIn  *plug_in,
       gimp_load_procedure_set_thumbnail_loader (GIMP_LOAD_PROCEDURE (procedure),
                                                 LOAD_THUMB_PROC);
     }
+  else if (! strcmp (name, LOAD_ANI_PROC))
+    {
+      procedure = gimp_load_procedure_new (plug_in, name,
+                                           GIMP_PDB_PROC_TYPE_PLUGIN,
+                                           ani_load, NULL, NULL);
+
+      gimp_procedure_set_menu_label (procedure, _("Microsoft Windows animated cursor"));
+      gimp_procedure_set_icon_name (procedure, GIMP_ICON_BRUSH);
+
+      gimp_procedure_set_documentation (procedure,
+                                        _("Loads files of Windows ANI file format"),
+                                        "Loads files of Windows ANI file format",
+                                        name);
+      gimp_procedure_set_attribution (procedure,
+                                      "Christian Kreibich <christian whoop org>, "
+                                      "James Huang, Alex S.",
+                                      "Christian Kreibich <christian whoop org>, "
+                                      "James Huang, Alex S.",
+                                      "2007-2022");
+
+      gimp_file_procedure_set_mime_types (GIMP_FILE_PROCEDURE (procedure),
+                                          "application/x-navi-animation");
+      gimp_file_procedure_set_extensions (GIMP_FILE_PROCEDURE (procedure),
+                                          "ani");
+      gimp_file_procedure_set_magics (GIMP_FILE_PROCEDURE (procedure),
+                                      "0,string,RIFF");
+
+      gimp_load_procedure_set_thumbnail_loader (GIMP_LOAD_PROCEDURE (procedure),
+                                                LOAD_ANI_THUMB_PROC);
+    }
   else if (! strcmp (name, LOAD_THUMB_PROC))
     {
       procedure = gimp_thumbnail_procedure_new (plug_in, name,
@@ -205,6 +262,24 @@ ico_create_procedure (GimpPlugIn  *plug_in,
                                       "Sven Neumann <sven gimp org>",
                                       "2005");
     }
+  else if (! strcmp (name, LOAD_ANI_THUMB_PROC))
+    {
+      procedure = gimp_thumbnail_procedure_new (plug_in, name,
+                                                GIMP_PDB_PROC_TYPE_PLUGIN,
+                                                ani_load_thumb, NULL, NULL);
+
+      gimp_procedure_set_documentation (procedure,
+                                        _("Loads a preview from a Windows ANI files"),
+                                        "",
+                                        name);
+      gimp_procedure_set_attribution (procedure,
+                                      "Dom Lachowicz, Sven Neumann, James Huang, "
+                                      "Alex S.",
+                                      "Dom Lachowicz, "
+                                      "Sven Neumann <sven gimp org>, "
+                                      "James Huang, Alex S.",
+                                      "2007-2022");
+    }
   else if (! strcmp (name, SAVE_PROC))
     {
       procedure = gimp_save_procedure_new (plug_in, name,
@@ -277,6 +352,72 @@ ico_create_procedure (GimpPlugIn  *plug_in,
                                  "Y coordinates of hot spot (one per layer)",
                                  G_PARAM_READWRITE);
     }
+  else if (! strcmp (name, SAVE_ANI_PROC))
+    {
+      procedure = gimp_save_procedure_new (plug_in, name,
+                                           GIMP_PDB_PROC_TYPE_PLUGIN,
+                                           ani_save, NULL, NULL);
+
+      gimp_procedure_set_image_types (procedure, "*");
+
+      gimp_procedure_set_menu_label (procedure, _("Microsoft Windows animated cursor"));
+      gimp_procedure_set_icon_name (procedure, GIMP_ICON_BRUSH);
+
+      gimp_procedure_set_documentation (procedure,
+                                        _("Saves files in Windows ANI file format"),
+                                        _("Saves files in Windows ANI file format"),
+                                        name);
+      gimp_procedure_set_attribution (procedure,
+                                      "Christian Kreibich <christian whoop org>, "
+                                      "James Huang, Alex S.",
+                                      "Christian Kreibich <christian whoop org>, "
+                                      "James Huang, Alex S.",
+                                      "2007-2022");
+
+      gimp_file_procedure_set_mime_types (GIMP_FILE_PROCEDURE (procedure),
+                                          "application/x-navi-animation");
+      gimp_file_procedure_set_extensions (GIMP_FILE_PROCEDURE (procedure),
+                                          "ani");
+
+      GIMP_PROC_ARG_STRING (procedure, "cursor-name",
+                            "Cursor Name",
+                            _("Cursor Name (Optional)"),
+                            NULL,
+                            G_PARAM_READWRITE);
+
+      GIMP_PROC_ARG_STRING (procedure, "author-name",
+                            "Cursor Author",
+                            _("Cursor Author (Optional)"),
+                            NULL,
+                            G_PARAM_READWRITE);
+
+      GIMP_PROC_ARG_INT (procedure, "default-delay",
+                         "Default delay",
+                         "Default delay between frames "
+                         "in jiffies (1/60 of a second)",
+                         0, G_MAXINT, 8,
+                         G_PARAM_READWRITE);
+
+      GIMP_PROC_ARG_INT (procedure, "n-hot-spot-x",
+                         "Number of hot spot's X coordinates",
+                         "Number of hot spot's X coordinates",
+                         0, G_MAXINT, 0,
+                         G_PARAM_READWRITE);
+      GIMP_PROC_ARG_INT32_ARRAY (procedure, "hot-spot-x",
+                                 "Hot spot X",
+                                 "X coordinates of hot spot (one per layer)",
+                                 G_PARAM_READWRITE);
+
+      GIMP_PROC_ARG_INT (procedure, "n-hot-spot-y",
+                         "Number of hot spot's Y coordinates",
+                         "Number of hot spot's Y coordinates",
+                         0, G_MAXINT, 0,
+                         G_PARAM_READWRITE);
+      GIMP_PROC_ARG_INT32_ARRAY (procedure, "hot-spot-y",
+                                 "Hot spot Y",
+                                 "Y coordinates of hot spot (one per layer)",
+                                 G_PARAM_READWRITE);
+    }
 
   return procedure;
 }
@@ -294,7 +435,37 @@ ico_load (GimpProcedure        *procedure,
 
   gegl_init (NULL, NULL);
 
-  image = ico_load_image (file, &error);
+  image = ico_load_image (file, NULL, &error);
+
+  if (! image)
+    return gimp_procedure_new_return_values (procedure,
+                                             GIMP_PDB_EXECUTION_ERROR,
+                                             error);
+
+  return_vals = gimp_procedure_new_return_values (procedure,
+                                                  GIMP_PDB_SUCCESS,
+                                                  NULL);
+
+  GIMP_VALUES_SET_IMAGE (return_vals, 1, image);
+
+  return return_vals;
+}
+
+static GimpValueArray *
+ani_load (GimpProcedure        *procedure,
+          GimpRunMode           run_mode,
+          GFile                *file,
+          const GimpValueArray *args,
+          gpointer              run_data)
+{
+  GimpValueArray *return_vals;
+  GimpImage      *image;
+  GError         *error = NULL;
+
+  gegl_init (NULL, NULL);
+
+  image = ani_load_image (file, FALSE,
+                          NULL, NULL, &error);
 
   if (! image)
     return gimp_procedure_new_return_values (procedure,
@@ -329,7 +500,46 @@ ico_load_thumb (GimpProcedure        *procedure,
   height = size;
 
   image = ico_load_thumbnail_image (file,
-                                    &width, &height, &error);
+                                    &width, &height, 0, &error);
+
+  if (image)
+    return gimp_procedure_new_return_values (procedure,
+                                             GIMP_PDB_EXECUTION_ERROR,
+                                             error);
+
+  return_vals = gimp_procedure_new_return_values (procedure,
+                                                  GIMP_PDB_SUCCESS,
+                                                  NULL);
+
+  GIMP_VALUES_SET_IMAGE (return_vals, 1, image);
+  GIMP_VALUES_SET_INT   (return_vals, 2, width);
+  GIMP_VALUES_SET_INT   (return_vals, 3, height);
+
+  gimp_value_array_truncate (return_vals, 4);
+
+  return return_vals;
+}
+
+static GimpValueArray *
+ani_load_thumb (GimpProcedure        *procedure,
+                GFile                *file,
+                gint                  size,
+                const GimpValueArray *args,
+                gpointer              run_data)
+{
+  GimpValueArray *return_vals;
+  gint            width;
+  gint            height;
+  GimpImage      *image;
+  GError         *error = NULL;
+
+  gegl_init (NULL, NULL);
+
+  width  = size;
+  height = size;
+
+  image = ani_load_image (file, TRUE,
+                          &width, &height, &error);
 
   if (image)
     return gimp_procedure_new_return_values (procedure,
@@ -426,6 +636,85 @@ cur_save (GimpProcedure        *procedure,
   return gimp_procedure_new_return_values (procedure, status, error);
 }
 
+static GimpValueArray *
+ani_save (GimpProcedure        *procedure,
+          GimpRunMode           run_mode,
+          GimpImage            *image,
+          gint                  n_drawables,
+          GimpDrawable        **drawables,
+          GFile                *file,
+          const GimpValueArray *args,
+          gpointer              run_data)
+{
+  GimpProcedureConfig *config;
+  GimpPDBStatusType    status;
+  GError              *error        = NULL;
+  gchar               *inam         = NULL;
+  gchar               *iart         = NULL;
+  gint                 jif_rate     = 0;
+  gint32              *hot_spot_x   = NULL;
+  gint32              *hot_spot_y   = NULL;
+  gint                 n_hot_spot_x = 0;
+  gint                 n_hot_spot_y = 0;
+  AniFileHeader        header;
+  AniSaveInfo          ani_info;
+
+  gegl_init (NULL, NULL);
+
+  config = gimp_procedure_create_config (procedure);
+  gimp_procedure_config_begin_run (config, image, run_mode, args);
+
+  g_object_get (config,
+                "cursor-name",   &inam,
+                "author-name",   &iart,
+                "default-delay", &jif_rate,
+                "n-hot-spot-x",  &n_hot_spot_x,
+                "n-hot-spot-y",  &n_hot_spot_y,
+                "hot-spot-x",    &hot_spot_x,
+                "hot-spot-y",    &hot_spot_y,
+                NULL);
+
+  /* Jiffies (1/60th of a second) used if rate chunk not present. */
+  header.jif_rate = jif_rate;
+  ani_info.inam = inam;
+  ani_info.iart = iart;
+
+  status = ani_save_image (file, image, run_mode,
+                           &n_hot_spot_x, &hot_spot_x,
+                           &n_hot_spot_y, &hot_spot_y,
+                           &header, &ani_info, &error);
+
+  if (status == GIMP_PDB_SUCCESS)
+    {
+      /* XXX: seems libgimpconfig is not able to serialize
+       * GimpInt32Array args yet anyway. Still leave this here for now,
+       * as reminder of missing feature when we see the warnings.
+       */
+      g_object_set (config,
+                    "cursor-name",   NULL,
+                    "author-name",   NULL,
+                    "default-delay", header.jif_rate,
+                    "n-hot-spot-x",  n_hot_spot_x,
+                    "n-hot-spot-y",  n_hot_spot_y,
+                    /*"hot-spot-x",   hot_spot_x,*/
+                    /*"hot-spot-y",   hot_spot_y,*/
+                    NULL);
+      g_free (hot_spot_x);
+      g_free (hot_spot_y);
+
+      g_free (inam);
+      g_free (iart);
+      g_free (ani_info.inam);
+      g_free (ani_info.iart);
+      memset (&ani_info, 0, sizeof (AniSaveInfo));
+    }
+
+  gimp_procedure_config_end_run (config, status);
+  g_object_unref (config);
+
+  return gimp_procedure_new_return_values (procedure, status, error);
+}
+
 gint
 ico_rowstride (gint width,
                gint bpp)
diff --git a/plug-ins/file-ico/ico.h b/plug-ins/file-ico/ico.h
index 07465d853b..dd8a40d400 100644
--- a/plug-ins/file-ico/ico.h
+++ b/plug-ins/file-ico/ico.h
@@ -97,6 +97,22 @@ typedef struct _IcoSaveInfo
     gint        *hot_spot_y;
 } IcoSaveInfo;
 
+typedef struct _AniFileHeader
+{
+    guint32 bSizeOf;     /* Number of bytes in AniFileHeader (36 bytes) */
+    guint32 frames;      /* Number of unique icons in this cursor */
+    guint32 steps;       /* Number of Blits before the animation cycles */
+    guint32 x, y;        /* Reserved, must be zero. */
+    guint32 bpp, planes; /* Reserved, must be zero. */
+    guint32 jif_rate;    /* Default Jiffies (1/60th of a second) if rate chunk's not present. */
+    guint32 flags;       /* Animation Flag */
+} AniFileHeader;
+
+typedef struct _AniSaveInfo
+{
+    gchar *inam;   /* Cursor name metadata */
+    gchar *iart;   /* Author name metadata */
+} AniSaveInfo;
 
 /* Miscellaneous helper functions below: */
 


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]