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



commit aa51b9e19ece8a8c54a513fe33b6d65abcb0fbfb
Author: Alx Sa <cmyk student gmail com>
Date:   Mon Aug 8 18:31:45 2022 +0000

    plug-ins: Add .ani file import/export

 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]