[gimp] Bug 773450 - Animated WEBP images should be able to set frame delay.
- From: Jehan Pagès <jehanp src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gimp] Bug 773450 - Animated WEBP images should be able to set frame delay.
- Date: Tue, 8 Nov 2016 00:40:50 +0000 (UTC)
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),
+ ¶ms->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),
+ ¶ms->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),
¶ms->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),
¶ms->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),
¶ms->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),
- ¶ms->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),
+ ¶ms->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),
+ ¶ms->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),
- ¶ms->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),
¶ms->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, ¶ms);
+ 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 (¶ms, 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,
- ¶ms,
- &error))
+ layers = gimp_image_get_layers (image_ID, &n_layers);
+ if (run_mode == GIMP_RUN_INTERACTIVE)
{
- status = GIMP_PDB_EXECUTION_ERROR;
+ if (! save_dialog (¶ms, 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,
+ ¶ms,
+ &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, ¶ms, 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]