diff --git a/plug-ins/file-webp/file-webp-dialog.c b/plug-ins/file-webp/file-webp-dialog.c index c7b466d44a..7f8aff1f1a 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 ("%s", _("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,80 +140,9 @@ save_dialog (WebPSaveParams *params, gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 0); gtk_widget_show (table); - /* 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, - GTK_FILL, GTK_FILL, 0, 0); - gtk_widget_show (preset_label); - - /* Create the combobox containing the presets */ - preset_list = save_dialog_presets (); - preset_combo = gimp_string_combo_box_new (GTK_TREE_MODEL (preset_list), 0, 1); - g_object_unref (preset_list); - - 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, - GTK_FILL, GTK_FILL, 0, 0); - gtk_widget_show (preset_combo); - - g_signal_connect (preset_combo, "changed", - G_CALLBACK (save_dialog_preset_changed), - ¶ms->preset); - - /* Create the lossless checkbox */ - lossless_checkbox = gtk_check_button_new_with_label (_("Lossless")); - gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (lossless_checkbox), - params->lossless); - gtk_table_attach (GTK_TABLE (table), lossless_checkbox, - 1, 3, 1, 2, - GTK_FILL, GTK_FILL, 0, 0); - gtk_widget_show (lossless_checkbox); - - g_signal_connect (lossless_checkbox, "toggled", - G_CALLBACK (gimp_toggle_button_update), - ¶ms->lossless); - - slider1 = 2; - slider2 = 3; - if (animation_supported) - { - slider1 = 4; - slider2 = 5; - - /* Create the animation checkbox */ - animation_checkbox = gtk_check_button_new_with_label (_("Use 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, - GTK_FILL, GTK_FILL, 0, 0); - gtk_widget_show (animation_checkbox); - - g_signal_connect (animation_checkbox, "toggled", - G_CALLBACK (gimp_toggle_button_update), - ¶ms->animation); - - /* Create the loop animation checkbox */ - loop_anim_checkbox = gtk_check_button_new_with_label (_("Loop infinitely")); - 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_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, + 0, 0, _("Image quality:"), 125, 0, @@ -230,7 +161,7 @@ save_dialog (WebPSaveParams *params, /* Create the slider for alpha channel quality */ alpha_quality_scale = gimp_scale_entry_new (GTK_TABLE (table), - 0, slider2, + 0, 1, _("Alpha quality:"), 125, 0, @@ -247,6 +178,43 @@ save_dialog (WebPSaveParams *params, 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, 2, 3, + GTK_FILL, GTK_FILL, 0, 0); + gtk_widget_show (preset_label); + + /* Create the combobox containing the presets */ + preset_list = save_dialog_presets (); + preset_combo = gimp_string_combo_box_new (GTK_TREE_MODEL (preset_list), 0, 1); + g_object_unref (preset_list); + + gimp_string_combo_box_set_active (GIMP_STRING_COMBO_BOX (preset_combo), + params->preset); + gtk_table_attach (GTK_TABLE (table), preset_combo, + 1, 3, 2, 3, + GTK_FILL, GTK_FILL, 0, 0); + gtk_widget_show (preset_combo); + + g_signal_connect (preset_combo, "changed", + G_CALLBACK (save_dialog_preset_changed), + ¶ms->preset); + + /* Create the lossless checkbox */ + lossless_checkbox = gtk_check_button_new_with_label (_("Lossless")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (lossless_checkbox), + params->lossless); + gtk_table_attach (GTK_TABLE (table), lossless_checkbox, + 0, 4, 3, 4, + GTK_FILL, GTK_FILL, 0, 0); + gtk_widget_show (lossless_checkbox); + + g_signal_connect (lossless_checkbox, "toggled", + G_CALLBACK (gimp_toggle_button_update), + ¶ms->lossless); + /* Enable and disable the sliders when the lossless option is selected */ g_signal_connect (lossless_checkbox, "toggled", G_CALLBACK (save_dialog_toggle_scale), @@ -255,6 +223,113 @@ save_dialog (WebPSaveParams *params, G_CALLBACK (save_dialog_toggle_scale), alpha_quality_scale); + if (animation_supported) + { + 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, + 0, 4, 4, 5, + GTK_FILL, GTK_FILL, 0, 0); + 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); + + text = g_strdup_printf ("%s", _("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 (""); + 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_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 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); + + + + /* 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 ("%s", _("_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 b864296198..ea072e0ad2 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 98fb202da9..006996bca1 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 12853f6af6..0247e61586 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 */