[gimp] Bug 773450 - Animated WEBP images should be able to set frame delay.



commit d516f9bef86ea915b5bb5fb16a6bbd67c0e67bf3
Author: Pascal Massimino <pascal massimino gmail com>
Date:   Fri Nov 4 15:36:02 2016 +0100

    Bug 773450 - Animated WEBP images should be able to set frame delay.
    
    animated webp saving: parse time-stamp from layer_name (instead of using
    default value of '100')
    
    also:
    - add a default delay field to UI, in case time-stamps are not present.
    - add a 'force delay' checkbox
    - revamp the whole UI to look like the GIF saving UI.

 plug-ins/file-webp/file-webp-dialog.c |  242 +++++++++++++++++++++-----------
 plug-ins/file-webp/file-webp-save.c   |   54 +++++++-
 plug-ins/file-webp/file-webp-save.h   |    2 +
 plug-ins/file-webp/file-webp.c        |   87 +++++++-----
 4 files changed, 263 insertions(+), 122 deletions(-)
---
diff --git a/plug-ins/file-webp/file-webp-dialog.c b/plug-ins/file-webp/file-webp-dialog.c
index c7b466d..7f8aff1 100644
--- a/plug-ins/file-webp/file-webp-dialog.c
+++ b/plug-ins/file-webp/file-webp-dialog.c
@@ -92,27 +92,25 @@ save_dialog (WebPSaveParams *params,
              gint32          image_ID,
              gint32          n_layers)
 {
-  GtkWidget    *dialog;
-  GtkWidget    *vbox;
-  GtkWidget    *label;
-  GtkWidget    *table;
-  GtkWidget    *expander;
-  GtkWidget    *frame;
-  GtkWidget    *vbox2;
-  GtkWidget    *save_exif;
-  GtkWidget    *save_xmp;
-  GtkWidget    *preset_label;
-  GtkListStore *preset_list;
-  GtkWidget    *preset_combo;
-  GtkWidget    *lossless_checkbox;
-  GtkWidget    *animation_checkbox;
-  GtkWidget    *loop_anim_checkbox;
-  GtkObject    *quality_scale;
-  GtkObject    *alpha_quality_scale;
-  gboolean      animation_supported = FALSE;
-  gint          slider1 , slider2;
-  gboolean      run;
-  gchar        *text;
+  GtkWidget     *dialog;
+  GtkWidget     *vbox;
+  GtkWidget     *label;
+  GtkWidget     *table;
+  GtkWidget     *expander;
+  GtkWidget     *frame;
+  GtkWidget     *vbox2;
+  GtkWidget     *save_exif;
+  GtkWidget     *save_xmp;
+  GtkWidget     *preset_label;
+  GtkListStore  *preset_list;
+  GtkWidget     *preset_combo;
+  GtkWidget     *lossless_checkbox;
+  GtkWidget     *animation_checkbox;
+  GtkObject     *quality_scale;
+  GtkObject     *alpha_quality_scale;
+  gboolean       animation_supported = FALSE;
+  gboolean       run;
+  gchar         *text;
 
   animation_supported = n_layers > 1;
 
@@ -127,7 +125,11 @@ save_dialog (WebPSaveParams *params,
   gtk_widget_show (vbox);
 
   /* Create the descriptive label at the top */
-  label = gtk_label_new (_("Use the options below to customize the image."));
+  text = g_strdup_printf ("<b>%s</b>", _("WebP Options"));
+  label = gtk_label_new (NULL);
+  gtk_label_set_markup(GTK_LABEL(label), text);
+  g_free (text);
+  gtk_label_set_xalign (GTK_LABEL (label), 0.0);
   gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);
   gtk_widget_show (label);
 
@@ -138,11 +140,49 @@ save_dialog (WebPSaveParams *params,
   gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 0);
   gtk_widget_show (table);
 
+  /* Create the slider for image quality */
+  quality_scale = gimp_scale_entry_new (GTK_TABLE (table),
+                                        0, 0,
+                                        _("Image quality:"),
+                                        125,
+                                        0,
+                                        params->quality,
+                                        0.0, 100.0,
+                                        1.0, 10.0,
+                                        0, TRUE,
+                                        0.0, 0.0,
+                                        _("Image quality"),
+                                        NULL);
+  gimp_scale_entry_set_sensitive (quality_scale, ! params->lossless);
+
+  g_signal_connect (quality_scale, "value-changed",
+                    G_CALLBACK (gimp_float_adjustment_update),
+                    &params->quality);
+
+  /* Create the slider for alpha channel quality */
+  alpha_quality_scale = gimp_scale_entry_new (GTK_TABLE (table),
+                                              0, 1,
+                                              _("Alpha quality:"),
+                                              125,
+                                              0,
+                                              params->alpha_quality,
+                                              0.0, 100.0,
+                                              1.0, 10.0,
+                                              0, TRUE,
+                                              0.0, 0.0,
+                                              _("Alpha channel quality"),
+                                              NULL);
+  gimp_scale_entry_set_sensitive (alpha_quality_scale, ! params->lossless);
+
+  g_signal_connect (alpha_quality_scale, "value-changed",
+                    G_CALLBACK (gimp_float_adjustment_update),
+                    &params->alpha_quality);
+
   /* Create the label for the selecting a preset */
   preset_label = gtk_label_new (_("Preset:"));
   gtk_label_set_xalign (GTK_LABEL (preset_label), 0.0);
   gtk_table_attach (GTK_TABLE (table), preset_label,
-                    0, 1, 0, 1,
+                    0, 1, 2, 3,
                     GTK_FILL, GTK_FILL, 0, 0);
   gtk_widget_show (preset_label);
 
@@ -154,7 +194,7 @@ save_dialog (WebPSaveParams *params,
   gimp_string_combo_box_set_active (GIMP_STRING_COMBO_BOX (preset_combo),
                                     params->preset);
   gtk_table_attach (GTK_TABLE (table), preset_combo,
-                    1, 3, 0, 1,
+                    1, 3, 2, 3,
                     GTK_FILL, GTK_FILL, 0, 0);
   gtk_widget_show (preset_combo);
 
@@ -167,7 +207,7 @@ save_dialog (WebPSaveParams *params,
   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (lossless_checkbox),
                                 params->lossless);
   gtk_table_attach (GTK_TABLE (table), lossless_checkbox,
-                    1, 3, 1, 2,
+                    0, 4, 3, 4,
                     GTK_FILL, GTK_FILL, 0, 0);
   gtk_widget_show (lossless_checkbox);
 
@@ -175,86 +215,121 @@ save_dialog (WebPSaveParams *params,
                     G_CALLBACK (gimp_toggle_button_update),
                     &params->lossless);
 
-  slider1 = 2;
-  slider2 = 3;
+  /* Enable and disable the sliders when the lossless option is selected */
+  g_signal_connect (lossless_checkbox, "toggled",
+                    G_CALLBACK (save_dialog_toggle_scale),
+                    quality_scale);
+  g_signal_connect (lossless_checkbox, "toggled",
+                    G_CALLBACK (save_dialog_toggle_scale),
+                    alpha_quality_scale);
+
   if (animation_supported)
     {
-      slider1 = 4;
-      slider2 = 5;
-
-      /* Create the animation checkbox */
-      animation_checkbox = gtk_check_button_new_with_label (_("Use animation"));
+      GtkWidget     *animation_box;
+      GtkWidget     *animation_box2;
+      GtkWidget     *animation_label;
+      GtkWidget     *loop_anim_checkbox;
+      GtkWidget     *animation_expander;
+      GtkWidget     *animation_frame;
+      GtkAdjustment *adj;
+      GtkWidget     *delay;
+      GtkWidget     *delay_label;
+      GtkWidget     *delay_label2;
+      GtkWidget     *delay_hbox;
+      GtkWidget     *delay_checkbox;
+
+      /* Create the top-level animation checkbox */
+      animation_checkbox = gtk_check_button_new_with_label (_("As animation"));
       gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (animation_checkbox),
                                     params->animation);
       gtk_table_attach (GTK_TABLE (table), animation_checkbox,
-                        1, 3, 2, 3,
+                        0, 4, 4, 5,
                         GTK_FILL, GTK_FILL, 0, 0);
-      gtk_widget_show (animation_checkbox);
-
+      gtk_widget_set_sensitive (animation_checkbox, TRUE);
       g_signal_connect (animation_checkbox, "toggled",
                         G_CALLBACK (gimp_toggle_button_update),
                         &params->animation);
+      gtk_widget_show (animation_checkbox);
 
-      /* Create the loop animation checkbox */
-      loop_anim_checkbox = gtk_check_button_new_with_label (_("Loop infinitely"));
+      text = g_strdup_printf ("<b>%s</b>", _("Animated WebP Options"));
+      animation_expander = gtk_expander_new_with_mnemonic (text);
+      gtk_expander_set_use_markup (GTK_EXPANDER (animation_expander), TRUE);
+      g_free (text);
+      gtk_box_pack_start (GTK_BOX (vbox), animation_expander, TRUE, TRUE, 0);
+      gtk_widget_show (animation_expander);
+
+      /* animation options box */
+      animation_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
+      gtk_container_set_border_width (GTK_CONTAINER (animation_box), 6);
+      gtk_container_add (GTK_CONTAINER (animation_expander), animation_box);
+      gtk_widget_show (animation_box);
+
+      animation_frame = gimp_frame_new ("<expander>");
+      gtk_box_pack_start (GTK_BOX (animation_box), animation_frame, FALSE, FALSE, 0);
+      gtk_widget_show (animation_frame);
+
+      animation_box2 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
+      gtk_container_add (GTK_CONTAINER (animation_frame), animation_box2);
+      gtk_widget_show (animation_box2);
+
+      /* loop animation checkbox */
+      loop_anim_checkbox = gtk_check_button_new_with_label (_("Loop forever"));
       gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (loop_anim_checkbox),
                                     params->loop);
-      gtk_table_attach (GTK_TABLE (table), loop_anim_checkbox,
-                        1, 3, 3, 4,
-                        GTK_FILL, GTK_FILL, 0, 0);
+      gtk_box_pack_start (GTK_BOX (animation_box2), loop_anim_checkbox, FALSE, FALSE, 0);
       gtk_widget_show (loop_anim_checkbox);
 
       g_signal_connect (loop_anim_checkbox, "toggled",
                         G_CALLBACK (gimp_toggle_button_update),
                         &params->loop);
-    }
 
-  /* Create the slider for image quality */
-  quality_scale = gimp_scale_entry_new (GTK_TABLE (table),
-                                        0, slider1,
-                                        _("Image quality:"),
-                                        125,
-                                        0,
-                                        params->quality,
-                                        0.0, 100.0,
-                                        1.0, 10.0,
-                                        0, TRUE,
-                                        0.0, 0.0,
-                                        _("Image quality"),
-                                        NULL);
-  gimp_scale_entry_set_sensitive (quality_scale, ! params->lossless);
-
-  g_signal_connect (quality_scale, "value-changed",
-                    G_CALLBACK (gimp_float_adjustment_update),
-                    &params->quality);
+      /* create a hbox for delay */
+      delay_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12);
+      gtk_box_pack_start (GTK_BOX (animation_box2), delay_hbox, FALSE, FALSE, 0);
+      gtk_widget_show (delay_hbox);
+
+      /* label for 'delay' adjustment */
+      delay_label = gtk_label_new (_("Delay between frames where unspecified:"));
+      gtk_label_set_xalign (GTK_LABEL (delay_label), 0.0);
+      gtk_box_pack_start (GTK_BOX (delay_hbox), delay_label, FALSE, FALSE, 0);
+      gtk_widget_show (delay_label);
+
+      /* default delay */
+      adj = (GtkAdjustment *) gtk_adjustment_new (params->delay, 1, 10000, 1, 10, 0);
+      delay = gtk_spin_button_new (adj, 1, 0);
+      gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (delay), TRUE);
+      gtk_box_pack_start (GTK_BOX (delay_hbox), delay, FALSE, FALSE, 0);
+      gtk_widget_show (delay);
+
+      g_signal_connect (adj, "value-changed",
+                        G_CALLBACK (gimp_int_adjustment_update),
+                        &params->delay);
+
+      /* label for 'ms' adjustment */
+      delay_label2 = gtk_label_new (_("milliseconds"));
+      gtk_box_pack_start (GTK_BOX (delay_hbox), delay_label2, FALSE, FALSE, 0);
+      gtk_widget_show (delay_label2);
+
+      /* Create the force-delay checkbox */
+      delay_checkbox = gtk_check_button_new_with_label (_("Use delay entered above for all frames"));
+      gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (delay_checkbox),
+                                    params->force_delay);
+      gtk_box_pack_start (GTK_BOX (animation_box2), delay_checkbox, FALSE, FALSE, 0);
+      gtk_widget_show (delay_checkbox);
+
+      g_signal_connect (delay_checkbox, "toggled",
+                        G_CALLBACK (gimp_toggle_button_update),
+                        &params->force_delay);
 
-  /* Create the slider for alpha channel quality */
-  alpha_quality_scale = gimp_scale_entry_new (GTK_TABLE (table),
-                                              0, slider2,
-                                              _("Alpha quality:"),
-                                              125,
-                                              0,
-                                              params->alpha_quality,
-                                              0.0, 100.0,
-                                              1.0, 10.0,
-                                              0, TRUE,
-                                              0.0, 0.0,
-                                              _("Alpha channel quality"),
-                                              NULL);
-  gimp_scale_entry_set_sensitive (alpha_quality_scale, ! params->lossless);
 
-  g_signal_connect (alpha_quality_scale, "value-changed",
-                    G_CALLBACK (gimp_float_adjustment_update),
-                    &params->alpha_quality);
 
-  /* Enable and disable the sliders when the lossless option is selected */
-  g_signal_connect (lossless_checkbox, "toggled",
-                    G_CALLBACK (save_dialog_toggle_scale),
-                    quality_scale);
-  g_signal_connect (lossless_checkbox, "toggled",
-                    G_CALLBACK (save_dialog_toggle_scale),
-                    alpha_quality_scale);
+      /* bind the animation checkbox to the parameters */
+      g_object_bind_property (animation_checkbox, "active",
+                              animation_expander,  "sensitive",
+                              G_BINDING_SYNC_CREATE);
+  }
 
+  /* Advanced options */
   text = g_strdup_printf ("<b>%s</b>", _("_Advanced Options"));
   expander = gtk_expander_new_with_mnemonic (text);
   gtk_expander_set_use_markup (GTK_EXPANDER (expander), TRUE);
@@ -295,6 +370,7 @@ save_dialog (WebPSaveParams *params,
                     G_CALLBACK (gimp_toggle_button_update),
                     &params->xmp);
 
+
   gtk_widget_show (dialog);
 
   run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK);
diff --git a/plug-ins/file-webp/file-webp-save.c b/plug-ins/file-webp/file-webp-save.c
index b864296..ea072e0 100644
--- a/plug-ins/file-webp/file-webp-save.c
+++ b/plug-ins/file-webp/file-webp-save.c
@@ -368,6 +368,53 @@ save_layer (const gchar    *filename,
   return status;
 }
 
+static gint
+parse_ms_tag (const gchar *str)
+{
+  gint sum    = 0;
+  gint offset = 0;
+  gint length;
+
+  length = strlen (str);
+
+ find_another_bra:
+
+  while ((offset < length) && (str[offset] != '('))
+    offset++;
+
+  if (offset >= length)
+    return(-1);
+
+  if (! g_ascii_isdigit (str[++offset]))
+    goto find_another_bra;
+
+  do
+    {
+      sum *= 10;
+      sum += str[offset] - '0';
+      offset++;
+    }
+  while ((offset < length) && (g_ascii_isdigit (str[offset])));
+
+  if (length - offset <= 2)
+    return(-3);
+
+  if ((g_ascii_toupper (str[offset])     != 'M') ||
+      (g_ascii_toupper (str[offset + 1]) != 'S'))
+    return -4;
+
+  return sum;
+}
+
+static gint
+get_layer_delay(gint32 layer)
+{
+  gchar *layer_name = gimp_item_get_name (layer);
+  gint  delay_ms = parse_ms_tag (layer_name);
+  g_free (layer_name);
+  return delay_ms;
+}
+
 gboolean
 save_animation (const gchar    *filename,
                 gint32          nLayers,
@@ -399,7 +446,9 @@ save_animation (const gchar    *filename,
 
   do
     {
-      gint loop;
+      gint     loop;
+      gint     default_delay = params->delay;
+      gboolean force_delay = params->force_delay;
 
       /* Begin displaying export progress */
       gimp_progress_init_printf (_("Saving '%s'"),
@@ -438,6 +487,7 @@ save_animation (const gchar    *filename,
           WebPPicture       picture;
           WebPMemoryWriter  mw = { 0 };
           gint32            drawable = allLayers[nLayers - 1 - loop];
+          gint              delay = get_layer_delay (drawable);
 
           /* Obtain the drawable type */
           has_alpha = gimp_drawable_has_alpha (drawable);
@@ -537,7 +587,7 @@ save_animation (const gchar    *filename,
           g_object_unref (geglbuffer);
 
           gimp_progress_update ((loop + 1.0) / nLayers);
-          frame_timestamp += 100;    /* TODO: should extract the real time stamp from layer */
+          frame_timestamp += (delay <= 0 || force_delay) ? default_delay : delay;
         }
 
       if (status == FALSE)
diff --git a/plug-ins/file-webp/file-webp-save.h b/plug-ins/file-webp/file-webp-save.h
index 98fb202..006996b 100644
--- a/plug-ins/file-webp/file-webp-save.h
+++ b/plug-ins/file-webp/file-webp-save.h
@@ -34,6 +34,8 @@ typedef struct
   gboolean  exif;
   gboolean  iptc;
   gboolean  xmp;
+  gint      delay;
+  gboolean  force_delay;
 } WebPSaveParams;
 
 
diff --git a/plug-ins/file-webp/file-webp.c b/plug-ins/file-webp/file-webp.c
index 12853f6..0247e61 100644
--- a/plug-ins/file-webp/file-webp.c
+++ b/plug-ins/file-webp/file-webp.c
@@ -44,7 +44,7 @@ static void   run   (const gchar      *name,
                      GimpParam       **return_vals);
 
 
-GimpPlugInInfo PLUG_IN_INFO =
+const GimpPlugInInfo PLUG_IN_INFO =
 {
   NULL,
   NULL,
@@ -85,7 +85,9 @@ query (void)
     { GIMP_PDB_INT32,    "anim-loop",     "Loop animation infinitely (0/1)" },
     { GIMP_PDB_INT32,    "exif",          "Toggle saving exif data (0/1)" },
     { GIMP_PDB_INT32,    "iptc",          "Toggle saving iptc data (0/1)" },
-    { GIMP_PDB_INT32,    "xmp",           "Toggle saving xmp data (0/1)" }
+    { GIMP_PDB_INT32,    "xmp",           "Toggle saving xmp data (0/1)" },
+    { GIMP_PDB_INT32,    "delay",         "Delay to use when timestamp are not available or forced" },
+    { GIMP_PDB_INT32,    "force-delay",   "Toggle to for delay" }
   };
 
   gimp_install_procedure (LOAD_PROC,
@@ -172,28 +174,23 @@ run (const gchar      *name,
     {
       WebPSaveParams    params;
       GimpExportReturn  export = GIMP_EXPORT_CANCEL;
-      gint32           *layers;
+      gint32           *layers = NULL;
       gint32            n_layers;
 
-      /* Initialize the parameters to their defaults */
-      params.preset        = g_strdup ("default");
-      params.lossless      = FALSE;
-      params.animation     = FALSE;
-      params.loop          = TRUE;
-      params.quality       = 90.0f;
-      params.alpha_quality = 100.0f;
-      params.exif          = TRUE;
-      params.iptc          = TRUE;
-      params.xmp           = TRUE;
+      if (run_mode == GIMP_RUN_INTERACTIVE ||
+          run_mode == GIMP_RUN_WITH_LAST_VALS)
+        gimp_ui_init (PLUG_IN_BINARY, FALSE);
 
       image_ID    = param[1].data.d_int32;
       drawable_ID = param[2].data.d_int32;
 
       switch (run_mode)
         {
-        case GIMP_RUN_INTERACTIVE:
         case GIMP_RUN_WITH_LAST_VALS:
-          gimp_ui_init (PLUG_IN_BINARY, FALSE);
+        case GIMP_RUN_INTERACTIVE:
+          /*  Possibly retrieve data  */
+          gimp_get_data (SAVE_PROC, &params);
+          params.preset = g_strdup ("default");  /* can't serialize strings, so restore default */
 
           export = gimp_export_image (&image_ID, &drawable_ID, "WebP",
                                       GIMP_EXPORT_CAN_HANDLE_RGB     |
@@ -205,31 +202,19 @@ run (const gchar      *name,
           if (export == GIMP_EXPORT_CANCEL)
             {
               values[0].data.d_status = GIMP_PDB_CANCEL;
-              return;
+              status = GIMP_PDB_CANCEL;
+              break;
             }
-          break;
-
-        default:
-          break;
-        }
-
-      layers = gimp_image_get_layers (image_ID, &n_layers);
 
-      switch (run_mode)
-        {
-        case GIMP_RUN_INTERACTIVE:
-          if (! save_dialog (&params, image_ID, n_layers))
-            status = GIMP_PDB_CANCEL;
           break;
 
         case GIMP_RUN_NONINTERACTIVE:
-          if (nparams != 10)
+          if (nparams != 16)
             {
               status = GIMP_PDB_CALLING_ERROR;
             }
           else
             {
-              g_free (params.preset);
               params.preset        = g_strdup (param[5].data.d_string);
               params.lossless      = param[6].data.d_int32;
               params.quality       = param[7].data.d_float;
@@ -239,6 +224,8 @@ run (const gchar      *name,
               params.exif          = param[11].data.d_int32;
               params.iptc          = param[12].data.d_int32;
               params.xmp           = param[13].data.d_int32;
+              params.delay         = param[14].data.d_int32;
+              params.force_delay   = param[15].data.d_int32;
             }
           break;
 
@@ -246,24 +233,50 @@ run (const gchar      *name,
           break;
         }
 
+
       if (status == GIMP_PDB_SUCCESS)
         {
-          if (! save_image (param[3].data.d_string,
-                            n_layers, layers,
-                            image_ID,
-                            drawable_ID,
-                            &params,
-                            &error))
+          layers = gimp_image_get_layers (image_ID, &n_layers);
+          if (run_mode == GIMP_RUN_INTERACTIVE)
             {
-              status = GIMP_PDB_EXECUTION_ERROR;
+              if (! save_dialog (&params, image_ID, n_layers))
+                {
+                  status = GIMP_PDB_CANCEL;
+                }
             }
         }
 
+      if (status != GIMP_PDB_SUCCESS)
+        {
+          g_free(params.preset);
+          g_free (layers);
+          return;
+        }
+
+      if (! save_image (param[3].data.d_string,
+                        n_layers, layers,
+                        image_ID,
+                        drawable_ID,
+                        &params,
+                        &error))
+        {
+          status = GIMP_PDB_EXECUTION_ERROR;
+        }
+
       g_free (params.preset);
+      params.preset = NULL;
+
       g_free (layers);
 
       if (export == GIMP_EXPORT_EXPORT)
         gimp_image_delete (image_ID);
+
+      if (status == GIMP_PDB_SUCCESS)
+        {
+          /* save parameters for later */
+          /* we can't serialize strings this way. params.preset isn't saved. */
+          gimp_set_data (SAVE_PROC, &params, sizeof (params));
+        }
     }
 
   /* If an error was supplied, include it in the return values */


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