summaryrefslogtreecommitdiffstats
path: root/libgimp/gimpexport.c
diff options
context:
space:
mode:
Diffstat (limited to 'libgimp/gimpexport.c')
-rw-r--r--libgimp/gimpexport.c1157
1 files changed, 1157 insertions, 0 deletions
diff --git a/libgimp/gimpexport.c b/libgimp/gimpexport.c
new file mode 100644
index 0000000..e5621da
--- /dev/null
+++ b/libgimp/gimpexport.c
@@ -0,0 +1,1157 @@
+/* LIBGIMP - The GIMP Library
+ * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
+ *
+ * gimpexport.c
+ * Copyright (C) 1999-2004 Sven Neumann <sven@gimp.org>
+ *
+ * This library is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see
+ * <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gtk/gtk.h>
+
+#include "gimp.h"
+#include "gimpui.h"
+
+#include "libgimp-intl.h"
+
+
+/**
+ * SECTION: gimpexport
+ * @title: gimpexport
+ * @short_description: Export an image before it is saved.
+ *
+ * This function should be called by all save_plugins unless they are
+ * able to save all image formats GIMP knows about. It takes care of
+ * asking the user if she wishes to export the image to a format the
+ * save_plugin can handle. It then performs the necessary conversions
+ * (e.g. Flatten) on a copy of the image so that the image can be
+ * saved without changing the original image.
+ *
+ * The capabilities of the save_plugin are specified by combining
+ * #GimpExportCapabilities using a bitwise OR.
+ *
+ * Make sure you have initialized GTK+ before you call this function
+ * as it will most probably have to open a dialog.
+ **/
+
+
+typedef void (* ExportFunc) (gint32 imageID,
+ gint32 *drawable_ID);
+
+
+/* the export action structure */
+typedef struct
+{
+ ExportFunc default_action;
+ ExportFunc alt_action;
+ const gchar *reason;
+ const gchar *possibilities[2];
+ gint choice;
+} ExportAction;
+
+
+/* the functions that do the actual export */
+
+static void
+export_merge (gint32 image_ID,
+ gint32 *drawable_ID)
+{
+ gint32 nlayers;
+ gint32 nvisible = 0;
+ gint32 i;
+ gint32 *layers;
+ gint32 merged;
+ gint32 transp;
+
+ layers = gimp_image_get_layers (image_ID, &nlayers);
+ for (i = 0; i < nlayers; i++)
+ {
+ if (gimp_item_get_visible (layers[i]))
+ nvisible++;
+ }
+
+ if (nvisible <= 1)
+ {
+ /* if there is only one (or zero) visible layer, add a new
+ * transparent layer that has the same size as the canvas. The
+ * merge that follows will ensure that the offset, opacity and
+ * size are correct
+ */
+ transp = gimp_layer_new (image_ID, "-",
+ gimp_image_width (image_ID),
+ gimp_image_height (image_ID),
+ gimp_drawable_type (*drawable_ID) | 1,
+ 100.0, GIMP_LAYER_MODE_NORMAL);
+ gimp_image_insert_layer (image_ID, transp, -1, 1);
+ gimp_selection_none (image_ID);
+ gimp_drawable_edit_clear (transp);
+ nvisible++;
+ }
+
+ if (nvisible > 1)
+ {
+ g_free (layers);
+ merged = gimp_image_merge_visible_layers (image_ID, GIMP_CLIP_TO_IMAGE);
+
+ if (merged != -1)
+ *drawable_ID = merged;
+ else
+ return; /* shouldn't happen */
+
+ layers = gimp_image_get_layers (image_ID, &nlayers);
+
+ /* make sure that the merged drawable matches the image size */
+ if (gimp_drawable_width (merged) != gimp_image_width (image_ID) ||
+ gimp_drawable_height (merged) != gimp_image_height (image_ID))
+ {
+ gint off_x, off_y;
+
+ gimp_drawable_offsets (merged, &off_x, &off_y);
+ gimp_layer_resize (merged,
+ gimp_image_width (image_ID),
+ gimp_image_height (image_ID),
+ off_x, off_y);
+ }
+ }
+
+ /* remove any remaining (invisible) layers */
+ for (i = 0; i < nlayers; i++)
+ {
+ if (layers[i] != *drawable_ID)
+ gimp_image_remove_layer (image_ID, layers[i]);
+ }
+ g_free (layers);
+}
+
+static void
+export_flatten (gint32 image_ID,
+ gint32 *drawable_ID)
+{
+ gint32 flattened;
+
+ flattened = gimp_image_flatten (image_ID);
+
+ if (flattened != -1)
+ *drawable_ID = flattened;
+}
+
+static void
+export_remove_alpha (gint32 image_ID,
+ gint32 *drawable_ID)
+{
+ gint32 n_layers;
+ gint32 *layers;
+ gint i;
+
+ layers = gimp_image_get_layers (image_ID, &n_layers);
+
+ for (i = 0; i < n_layers; i++)
+ {
+ if (gimp_drawable_has_alpha (layers[i]))
+ gimp_layer_flatten (layers[i]);
+ }
+
+ g_free (layers);
+}
+
+static void
+export_apply_masks (gint32 image_ID,
+ gint *drawable_ID)
+{
+ gint32 n_layers;
+ gint32 *layers;
+ gint i;
+
+ layers = gimp_image_get_layers (image_ID, &n_layers);
+
+ for (i = 0; i < n_layers; i++)
+ {
+ if (gimp_layer_get_mask (layers[i]) != -1)
+ {
+ /* we can't apply the mask directly to a layer group, so merge it
+ * first
+ */
+ if (gimp_item_is_group (layers[i]))
+ layers[i] = gimp_image_merge_layer_group (image_ID, layers[i]);
+
+ gimp_layer_remove_mask (layers[i], GIMP_MASK_APPLY);
+ }
+ }
+
+ g_free (layers);
+}
+
+static void
+export_convert_rgb (gint32 image_ID,
+ gint32 *drawable_ID)
+{
+ gimp_image_convert_rgb (image_ID);
+}
+
+static void
+export_convert_grayscale (gint32 image_ID,
+ gint32 *drawable_ID)
+{
+ gimp_image_convert_grayscale (image_ID);
+}
+
+static void
+export_convert_indexed (gint32 image_ID,
+ gint32 *drawable_ID)
+{
+ gint32 nlayers;
+
+ /* check alpha */
+ g_free (gimp_image_get_layers (image_ID, &nlayers));
+ if (nlayers > 1 || gimp_drawable_has_alpha (*drawable_ID))
+ gimp_image_convert_indexed (image_ID,
+ GIMP_CONVERT_DITHER_NONE,
+ GIMP_CONVERT_PALETTE_GENERATE,
+ 255, FALSE, FALSE, "");
+ else
+ gimp_image_convert_indexed (image_ID,
+ GIMP_CONVERT_DITHER_NONE,
+ GIMP_CONVERT_PALETTE_GENERATE,
+ 256, FALSE, FALSE, "");
+}
+
+static void
+export_convert_bitmap (gint32 image_ID,
+ gint32 *drawable_ID)
+{
+ if (gimp_image_base_type (image_ID) == GIMP_INDEXED)
+ gimp_image_convert_rgb (image_ID);
+
+ gimp_image_convert_indexed (image_ID,
+ GIMP_CONVERT_DITHER_FS,
+ GIMP_CONVERT_PALETTE_GENERATE,
+ 2, FALSE, FALSE, "");
+}
+
+static void
+export_add_alpha (gint32 image_ID,
+ gint32 *drawable_ID)
+{
+ gint32 nlayers;
+ gint32 i;
+ gint32 *layers;
+
+ layers = gimp_image_get_layers (image_ID, &nlayers);
+ for (i = 0; i < nlayers; i++)
+ {
+ if (!gimp_drawable_has_alpha (layers[i]))
+ gimp_layer_add_alpha (layers[i]);
+ }
+ g_free (layers);
+}
+
+static void
+export_crop_image (gint32 image_ID,
+ gint32 *drawable_ID)
+{
+ gimp_image_crop (image_ID,
+ gimp_image_width (image_ID),
+ gimp_image_height (image_ID),
+ 0, 0);
+}
+
+static void
+export_resize_image (gint32 image_ID,
+ gint32 *drawable_ID)
+{
+ gimp_image_resize_to_layers (image_ID);
+}
+
+static void
+export_void (gint32 image_ID,
+ gint32 *drawable_ID)
+{
+ /* do nothing */
+}
+
+
+/* a set of predefined actions */
+
+static ExportAction export_action_merge =
+{
+ export_merge,
+ NULL,
+ N_("%s plug-in can't handle layers"),
+ { N_("Merge Visible Layers"), NULL },
+ 0
+};
+
+static ExportAction export_action_merge_single =
+{
+ export_merge,
+ NULL,
+ N_("%s plug-in can't handle layer offsets, size or opacity"),
+ { N_("Merge Visible Layers"), NULL },
+ 0
+};
+
+static ExportAction export_action_animate_or_merge =
+{
+ NULL,
+ export_merge,
+ N_("%s plug-in can only handle layers as animation frames"),
+ { N_("Save as Animation"), N_("Merge Visible Layers") },
+ 0
+};
+
+static ExportAction export_action_animate_or_flatten =
+{
+ NULL,
+ export_flatten,
+ N_("%s plug-in can only handle layers as animation frames"),
+ { N_("Save as Animation"), N_("Flatten Image") },
+ 0
+};
+
+static ExportAction export_action_merge_or_flatten =
+{
+ export_flatten,
+ export_merge,
+ N_("%s plug-in can't handle layers"),
+ { N_("Flatten Image"), N_("Merge Visible Layers") },
+ 1
+};
+
+static ExportAction export_action_flatten =
+{
+ export_flatten,
+ NULL,
+ N_("%s plug-in can't handle transparency"),
+ { N_("Flatten Image"), NULL },
+ 0
+};
+
+static ExportAction export_action_remove_alpha =
+{
+ export_remove_alpha,
+ NULL,
+ N_("%s plug-in can't handle transparent layers"),
+ { N_("Flatten Image"), NULL },
+ 0
+};
+
+static ExportAction export_action_apply_masks =
+{
+ export_apply_masks,
+ NULL,
+ N_("%s plug-in can't handle layer masks"),
+ { N_("Apply Layer Masks"), NULL },
+ 0
+};
+
+static ExportAction export_action_convert_rgb =
+{
+ export_convert_rgb,
+ NULL,
+ N_("%s plug-in can only handle RGB images"),
+ { N_("Convert to RGB"), NULL },
+ 0
+};
+
+static ExportAction export_action_convert_grayscale =
+{
+ export_convert_grayscale,
+ NULL,
+ N_("%s plug-in can only handle grayscale images"),
+ { N_("Convert to Grayscale"), NULL },
+ 0
+};
+
+static ExportAction export_action_convert_indexed =
+{
+ export_convert_indexed,
+ NULL,
+ N_("%s plug-in can only handle indexed images"),
+ { N_("Convert to Indexed using default settings\n"
+ "(Do it manually to tune the result)"), NULL },
+ 0
+};
+
+static ExportAction export_action_convert_bitmap =
+{
+ export_convert_bitmap,
+ NULL,
+ N_("%s plug-in can only handle bitmap (two color) indexed images"),
+ { N_("Convert to Indexed using bitmap default settings\n"
+ "(Do it manually to tune the result)"), NULL },
+ 0
+};
+
+static ExportAction export_action_convert_rgb_or_grayscale =
+{
+ export_convert_rgb,
+ export_convert_grayscale,
+ N_("%s plug-in can only handle RGB or grayscale images"),
+ { N_("Convert to RGB"), N_("Convert to Grayscale")},
+ 0
+};
+
+static ExportAction export_action_convert_rgb_or_indexed =
+{
+ export_convert_rgb,
+ export_convert_indexed,
+ N_("%s plug-in can only handle RGB or indexed images"),
+ { N_("Convert to RGB"), N_("Convert to Indexed using default settings\n"
+ "(Do it manually to tune the result)")},
+ 0
+};
+
+static ExportAction export_action_convert_indexed_or_grayscale =
+{
+ export_convert_indexed,
+ export_convert_grayscale,
+ N_("%s plug-in can only handle grayscale or indexed images"),
+ { N_("Convert to Indexed using default settings\n"
+ "(Do it manually to tune the result)"),
+ N_("Convert to Grayscale") },
+ 0
+};
+
+static ExportAction export_action_add_alpha =
+{
+ export_add_alpha,
+ NULL,
+ N_("%s plug-in needs an alpha channel"),
+ { N_("Add Alpha Channel"), NULL},
+ 0
+};
+
+static ExportAction export_action_crop_or_resize =
+{
+ export_crop_image,
+ export_resize_image,
+ N_("%s plug-in needs to crop the layers to the image bounds"),
+ { N_("Crop Layers"), N_("Resize Image to Layers")},
+ 0
+};
+
+
+static ExportFunc
+export_action_get_func (const ExportAction *action)
+{
+ if (action->choice == 0 && action->default_action)
+ {
+ return action->default_action;
+ }
+
+ if (action->choice == 1 && action->alt_action)
+ {
+ return action->alt_action;
+ }
+
+ return export_void;
+}
+
+static void
+export_action_perform (const ExportAction *action,
+ gint32 image_ID,
+ gint32 *drawable_ID)
+{
+ export_action_get_func (action) (image_ID, drawable_ID);
+}
+
+
+/* dialog functions */
+
+static void
+export_toggle_callback (GtkWidget *widget,
+ gpointer data)
+{
+ gint *choice = (gint *) data;
+
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)))
+ *choice = FALSE;
+ else
+ *choice = TRUE;
+}
+
+static GimpExportReturn
+confirm_save_dialog (const gchar *message,
+ const gchar *format_name)
+{
+ GtkWidget *dialog;
+ GtkWidget *hbox;
+ GtkWidget *image;
+ GtkWidget *main_vbox;
+ GtkWidget *label;
+ gchar *text;
+ GimpExportReturn retval;
+
+ g_return_val_if_fail (message != NULL, GIMP_EXPORT_CANCEL);
+ g_return_val_if_fail (format_name != NULL, GIMP_EXPORT_CANCEL);
+
+ dialog = gimp_dialog_new (_("Confirm Save"), "gimp-export-image-confirm",
+ NULL, 0,
+ gimp_standard_help_func,
+ "gimp-export-confirm-dialog",
+
+ _("_Cancel"), GTK_RESPONSE_CANCEL,
+ _("C_onfirm"), GTK_RESPONSE_OK,
+
+ NULL);
+
+ gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog),
+ GTK_RESPONSE_OK,
+ GTK_RESPONSE_CANCEL,
+ -1);
+
+ gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);
+ gimp_window_set_transient (GTK_WINDOW (dialog));
+
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12);
+ gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))),
+ hbox, TRUE, TRUE, 0);
+ gtk_container_set_border_width (GTK_CONTAINER (hbox), 12);
+ gtk_widget_show (hbox);
+
+ image = gtk_image_new_from_icon_name ("dialog-warning",
+ GTK_ICON_SIZE_DIALOG);
+ gtk_misc_set_alignment (GTK_MISC (image), 0.5, 0.0);
+ gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0);
+ gtk_widget_show (image);
+
+ main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
+ gtk_box_pack_start (GTK_BOX (hbox), main_vbox, FALSE, FALSE, 0);
+ gtk_widget_show (main_vbox);
+
+ text = g_strdup_printf (message, format_name);
+ label = gtk_label_new (text);
+ g_free (text);
+
+ gimp_label_set_attributes (GTK_LABEL (label),
+ PANGO_ATTR_SCALE, PANGO_SCALE_LARGE,
+ PANGO_ATTR_WEIGHT, PANGO_WEIGHT_BOLD,
+ -1);
+ gtk_label_set_xalign (GTK_LABEL (label), 0.0);
+ gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
+ gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_LEFT);
+ gtk_box_pack_start (GTK_BOX (main_vbox), label, FALSE, FALSE, 0);
+ gtk_widget_show (label);
+
+ gtk_widget_show (dialog);
+
+ switch (gimp_dialog_run (GIMP_DIALOG (dialog)))
+ {
+ case GTK_RESPONSE_OK:
+ retval = GIMP_EXPORT_EXPORT;
+ break;
+
+ default:
+ retval = GIMP_EXPORT_CANCEL;
+ break;
+ }
+
+ gtk_widget_destroy (dialog);
+
+ return retval;
+}
+
+static GimpExportReturn
+export_dialog (GSList *actions,
+ const gchar *format_name)
+{
+ GtkWidget *dialog;
+ GtkWidget *hbox;
+ GtkWidget *image;
+ GtkWidget *main_vbox;
+ GtkWidget *label;
+ GSList *list;
+ gchar *text;
+ GimpExportReturn retval;
+
+ g_return_val_if_fail (actions != NULL, GIMP_EXPORT_CANCEL);
+ g_return_val_if_fail (format_name != NULL, GIMP_EXPORT_CANCEL);
+
+ dialog = gimp_dialog_new (_("Export File"), "gimp-export-image",
+ NULL, 0,
+ gimp_standard_help_func, "gimp-export-dialog",
+
+ _("_Ignore"), GTK_RESPONSE_NO,
+ _("_Cancel"), GTK_RESPONSE_CANCEL,
+ _("_Export"), GTK_RESPONSE_OK,
+
+ NULL);
+
+ gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog),
+ GTK_RESPONSE_NO,
+ GTK_RESPONSE_OK,
+ GTK_RESPONSE_CANCEL,
+ -1);
+
+ gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);
+ gimp_window_set_transient (GTK_WINDOW (dialog));
+
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12);
+ gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))),
+ hbox, TRUE, TRUE, 0);
+ gtk_container_set_border_width (GTK_CONTAINER (hbox), 12);
+ gtk_widget_show (hbox);
+
+ image = gtk_image_new_from_icon_name ("dialog-information",
+ GTK_ICON_SIZE_DIALOG);
+ gtk_misc_set_alignment (GTK_MISC (image), 0.5, 0.0);
+ gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0);
+ gtk_widget_show (image);
+
+ main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
+ gtk_box_pack_start (GTK_BOX (hbox), main_vbox, FALSE, FALSE, 0);
+ gtk_widget_show (main_vbox);
+
+ /* the headline */
+ text = g_strdup_printf (_("Your image should be exported before it "
+ "can be saved as %s for the following reasons:"),
+ format_name);
+ label = gtk_label_new (text);
+ g_free (text);
+
+ gimp_label_set_attributes (GTK_LABEL (label),
+ PANGO_ATTR_SCALE, PANGO_SCALE_LARGE,
+ -1);
+ gtk_label_set_xalign (GTK_LABEL (label), 0.0);
+ gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
+ gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_LEFT);
+ gtk_box_pack_start (GTK_BOX (main_vbox), label, FALSE, FALSE, 0);
+ gtk_widget_show (label);
+
+ for (list = actions; list; list = g_slist_next (list))
+ {
+ ExportAction *action = list->data;
+ GtkWidget *frame;
+ GtkWidget *vbox;
+
+ text = g_strdup_printf (gettext (action->reason), format_name);
+ frame = gimp_frame_new (text);
+ g_free (text);
+
+ gtk_box_pack_start (GTK_BOX (main_vbox), frame, FALSE, FALSE, 0);
+ gtk_widget_show (frame);
+
+ vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
+ gtk_container_add (GTK_CONTAINER (frame), vbox);
+
+ if (action->possibilities[0] && action->possibilities[1])
+ {
+ GtkWidget *button;
+ GSList *radio_group = NULL;
+
+ button = gtk_radio_button_new_with_label (radio_group,
+ gettext (action->possibilities[0]));
+ gtk_label_set_justify (GTK_LABEL (gtk_bin_get_child (GTK_BIN (button))),
+ GTK_JUSTIFY_LEFT);
+ radio_group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (button));
+ gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
+ g_signal_connect (button, "toggled",
+ G_CALLBACK (export_toggle_callback),
+ &action->choice);
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button),
+ (action->choice == 0));
+ gtk_widget_show (button);
+
+ button = gtk_radio_button_new_with_label (radio_group,
+ gettext (action->possibilities[1]));
+ gtk_label_set_justify (GTK_LABEL (gtk_bin_get_child (GTK_BIN (button))),
+ GTK_JUSTIFY_LEFT);
+ radio_group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (button));
+ gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button),
+ (action->choice == 1));
+ gtk_widget_show (button);
+ }
+ else if (action->possibilities[0])
+ {
+ label = gtk_label_new (gettext (action->possibilities[0]));
+ gtk_label_set_xalign (GTK_LABEL (label), 0.0);
+ gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
+ gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_LEFT);
+ gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);
+ gtk_widget_show (label);
+ action->choice = 0;
+ }
+
+ gtk_widget_show (vbox);
+ }
+
+ /* the footline */
+ label = gtk_label_new (_("The export conversion won't modify your "
+ "original image."));
+ gimp_label_set_attributes (GTK_LABEL (label),
+ PANGO_ATTR_STYLE, PANGO_STYLE_ITALIC,
+ -1);
+ gtk_label_set_xalign (GTK_LABEL (label), 0.0);
+ gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
+ gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_LEFT);
+ gtk_box_pack_start (GTK_BOX (main_vbox), label, FALSE, FALSE, 0);
+ gtk_widget_show (label);
+
+ gtk_widget_show (dialog);
+
+ switch (gimp_dialog_run (GIMP_DIALOG (dialog)))
+ {
+ case GTK_RESPONSE_OK:
+ retval = GIMP_EXPORT_EXPORT;
+ break;
+
+ case GTK_RESPONSE_NO:
+ retval = GIMP_EXPORT_IGNORE;
+ break;
+
+ default:
+ retval = GIMP_EXPORT_CANCEL;
+ break;
+ }
+
+ gtk_widget_destroy (dialog);
+
+ return retval;
+}
+
+/**
+ * gimp_export_image:
+ * @image_ID: Pointer to the image_ID.
+ * @drawable_ID: Pointer to the drawable_ID.
+ * @format_name: The (short) name of the image_format (e.g. JPEG or GIF).
+ * @capabilities: What can the image_format do?
+ *
+ * Takes an image and a drawable to be saved together with a
+ * description of the capabilities of the image_format. If the
+ * type of image doesn't match the capabilities of the format
+ * a dialog is opened that informs the user that the image has
+ * to be exported and offers to do the necessary conversions.
+ *
+ * If the user chooses to export the image, a copy is created.
+ * This copy is then converted, the image_ID and drawable_ID
+ * are changed to point to the new image and the procedure returns
+ * GIMP_EXPORT_EXPORT. The save_plugin has to take care of deleting the
+ * created image using gimp_image_delete() when it has saved it.
+ *
+ * If the user chooses to Ignore the export problem, the image_ID
+ * and drawable_ID is not altered, GIMP_EXPORT_IGNORE is returned and
+ * the save_plugin should try to save the original image. If the
+ * user chooses Cancel, GIMP_EXPORT_CANCEL is returned and the
+ * save_plugin should quit itself with status %GIMP_PDB_CANCEL.
+ *
+ * If @format_name is NULL, no dialogs will be shown and this function
+ * will behave as if the user clicked on the 'Export' button, if a
+ * dialog would have been shown.
+ *
+ * Returns: An enum of #GimpExportReturn describing the user_action.
+ **/
+GimpExportReturn
+gimp_export_image (gint32 *image_ID,
+ gint32 *drawable_ID,
+ const gchar *format_name,
+ GimpExportCapabilities capabilities)
+{
+ GSList *actions = NULL;
+ GimpImageBaseType type;
+ gint32 i;
+ gint32 n_layers;
+ gint32 *layers;
+ gboolean interactive = FALSE;
+ gboolean added_flatten = FALSE;
+ gboolean has_layer_masks = FALSE;
+ gboolean background_has_alpha = TRUE;
+ GimpExportReturn retval = GIMP_EXPORT_CANCEL;
+
+ g_return_val_if_fail (*image_ID > -1 && *drawable_ID > -1, FALSE);
+
+ /* do some sanity checks */
+ if (capabilities & GIMP_EXPORT_NEEDS_ALPHA)
+ capabilities |= GIMP_EXPORT_CAN_HANDLE_ALPHA;
+
+ if (capabilities & GIMP_EXPORT_CAN_HANDLE_LAYERS_AS_ANIMATION)
+ capabilities |= GIMP_EXPORT_CAN_HANDLE_LAYERS;
+
+ if (capabilities & GIMP_EXPORT_CAN_HANDLE_LAYER_MASKS)
+ capabilities |= GIMP_EXPORT_CAN_HANDLE_LAYERS;
+
+ if (format_name && g_getenv ("GIMP_INTERACTIVE_EXPORT"))
+ interactive = TRUE;
+
+ /* ask for confirmation if the user is not saving a layer (see bug #51114) */
+ if (interactive &&
+ ! gimp_item_is_layer (*drawable_ID) &&
+ ! (capabilities & GIMP_EXPORT_CAN_HANDLE_LAYERS))
+ {
+ if (gimp_item_is_layer_mask (*drawable_ID))
+ {
+ retval = confirm_save_dialog
+ (_("You are about to save a layer mask as %s.\n"
+ "This will not save the visible layers."), format_name);
+ }
+ else if (gimp_item_is_channel (*drawable_ID))
+ {
+ retval = confirm_save_dialog
+ (_("You are about to save a channel (saved selection) as %s.\n"
+ "This will not save the visible layers."), format_name);
+ }
+ else
+ {
+ /* this should not happen */
+ g_warning ("%s: unknown drawable type!", G_STRFUNC);
+ }
+
+ /* cancel - the user can then select an appropriate layer to save */
+ if (retval == GIMP_EXPORT_CANCEL)
+ return GIMP_EXPORT_CANCEL;
+ }
+
+
+ /* check alpha and layer masks */
+ layers = gimp_image_get_layers (*image_ID, &n_layers);
+
+ for (i = 0; i < n_layers; i++)
+ {
+ if (gimp_drawable_has_alpha (layers[i]))
+ {
+ if (! (capabilities & GIMP_EXPORT_CAN_HANDLE_ALPHA))
+ {
+ if (! (capabilities & GIMP_EXPORT_CAN_HANDLE_LAYERS))
+ {
+ actions = g_slist_prepend (actions, &export_action_flatten);
+ added_flatten = TRUE;
+ break;
+ }
+ else
+ {
+ actions = g_slist_prepend (actions, &export_action_remove_alpha);
+ break;
+ }
+ }
+ }
+ else
+ {
+ /* If this is the last layer, it's visible and has no alpha
+ * channel, then the image has a "flat" background
+ */
+ if (i == n_layers - 1 && gimp_item_get_visible (layers[i]))
+ background_has_alpha = FALSE;
+
+ if (capabilities & GIMP_EXPORT_NEEDS_ALPHA)
+ {
+ actions = g_slist_prepend (actions, &export_action_add_alpha);
+ break;
+ }
+ }
+ }
+
+ if (! added_flatten)
+ {
+ for (i = 0; i < n_layers; i++)
+ {
+ if (gimp_layer_get_mask (layers[i]) != -1)
+ has_layer_masks = TRUE;
+ }
+ }
+
+ if (! added_flatten)
+ {
+ gint32 n_children;
+ gint32 *children;
+
+ children = gimp_item_get_children (layers[0], &n_children);
+
+ if ((capabilities & GIMP_EXPORT_CAN_HANDLE_LAYERS) &&
+ (capabilities & GIMP_EXPORT_NEEDS_CROP))
+ {
+ GeglRectangle image_bounds;
+ gboolean needs_crop = FALSE;
+
+ image_bounds.x = 0;
+ image_bounds.y = 0;
+ image_bounds.width = gimp_image_width (*image_ID);
+ image_bounds.height = gimp_image_height (*image_ID);
+
+ for (i = 0; i < n_layers; i++)
+ {
+ GeglRectangle layer_bounds;
+
+ gimp_drawable_offsets (layers[i],
+ &layer_bounds.x, &layer_bounds.y);
+
+ layer_bounds.width = gimp_drawable_width (layers[i]);
+ layer_bounds.height = gimp_drawable_height (layers[i]);
+
+ if (! gegl_rectangle_contains (&image_bounds, &layer_bounds))
+ {
+ needs_crop = TRUE;
+
+ break;
+ }
+ }
+
+ if (needs_crop)
+ {
+ actions = g_slist_prepend (actions,
+ &export_action_crop_or_resize);
+ }
+ }
+
+ /* check if layer size != canvas size, opacity != 100%, or offsets != 0 */
+ if (n_layers == 1 &&
+ ! children &&
+ gimp_item_is_layer (*drawable_ID) &&
+ ! (capabilities & GIMP_EXPORT_CAN_HANDLE_LAYERS))
+ {
+ gint offset_x;
+ gint offset_y;
+
+ gimp_drawable_offsets (*drawable_ID, &offset_x, &offset_y);
+
+ if ((gimp_layer_get_opacity (*drawable_ID) < 100.0) ||
+ (gimp_image_width (*image_ID) !=
+ gimp_drawable_width (*drawable_ID)) ||
+ (gimp_image_height (*image_ID) !=
+ gimp_drawable_height (*drawable_ID)) ||
+ offset_x || offset_y)
+ {
+ if (capabilities & GIMP_EXPORT_CAN_HANDLE_ALPHA)
+ {
+ actions = g_slist_prepend (actions,
+ &export_action_merge_single);
+ }
+ else
+ {
+ actions = g_slist_prepend (actions,
+ &export_action_flatten);
+ }
+ }
+ }
+ /* check multiple layers */
+ else if (n_layers > 1)
+ {
+ if (capabilities & GIMP_EXPORT_CAN_HANDLE_LAYERS_AS_ANIMATION)
+ {
+ if (background_has_alpha ||
+ capabilities & GIMP_EXPORT_NEEDS_ALPHA)
+ actions = g_slist_prepend (actions,
+ &export_action_animate_or_merge);
+ else
+ actions = g_slist_prepend (actions,
+ &export_action_animate_or_flatten);
+ }
+ else if (! (capabilities & GIMP_EXPORT_CAN_HANDLE_LAYERS))
+ {
+ if (capabilities & GIMP_EXPORT_NEEDS_ALPHA)
+ actions = g_slist_prepend (actions,
+ &export_action_merge);
+ else
+ actions = g_slist_prepend (actions,
+ &export_action_merge_or_flatten);
+ }
+ }
+ /* check for a single toplevel layer group */
+ else if (children)
+ {
+ if (! (capabilities & GIMP_EXPORT_CAN_HANDLE_LAYERS))
+ {
+ if (capabilities & GIMP_EXPORT_NEEDS_ALPHA)
+ actions = g_slist_prepend (actions,
+ &export_action_merge);
+ else
+ actions = g_slist_prepend (actions,
+ &export_action_merge_or_flatten);
+ }
+ }
+
+ g_free (children);
+
+ /* check layer masks */
+ if (has_layer_masks &&
+ ! (capabilities & GIMP_EXPORT_CAN_HANDLE_LAYER_MASKS))
+ actions = g_slist_prepend (actions, &export_action_apply_masks);
+ }
+
+ g_free (layers);
+
+ /* check the image type */
+ type = gimp_image_base_type (*image_ID);
+ switch (type)
+ {
+ case GIMP_RGB:
+ if (! (capabilities & GIMP_EXPORT_CAN_HANDLE_RGB))
+ {
+ if ((capabilities & GIMP_EXPORT_CAN_HANDLE_INDEXED) &&
+ (capabilities & GIMP_EXPORT_CAN_HANDLE_GRAY))
+ actions = g_slist_prepend (actions,
+ &export_action_convert_indexed_or_grayscale);
+ else if (capabilities & GIMP_EXPORT_CAN_HANDLE_INDEXED)
+ actions = g_slist_prepend (actions,
+ &export_action_convert_indexed);
+ else if (capabilities & GIMP_EXPORT_CAN_HANDLE_GRAY)
+ actions = g_slist_prepend (actions,
+ &export_action_convert_grayscale);
+ else if (capabilities & GIMP_EXPORT_CAN_HANDLE_BITMAP)
+ actions = g_slist_prepend (actions,
+ &export_action_convert_bitmap);
+ }
+ break;
+
+ case GIMP_GRAY:
+ if (! (capabilities & GIMP_EXPORT_CAN_HANDLE_GRAY))
+ {
+ if ((capabilities & GIMP_EXPORT_CAN_HANDLE_RGB) &&
+ (capabilities & GIMP_EXPORT_CAN_HANDLE_INDEXED))
+ actions = g_slist_prepend (actions,
+ &export_action_convert_rgb_or_indexed);
+ else if (capabilities & GIMP_EXPORT_CAN_HANDLE_RGB)
+ actions = g_slist_prepend (actions,
+ &export_action_convert_rgb);
+ else if (capabilities & GIMP_EXPORT_CAN_HANDLE_INDEXED)
+ actions = g_slist_prepend (actions,
+ &export_action_convert_indexed);
+ else if (capabilities & GIMP_EXPORT_CAN_HANDLE_BITMAP)
+ actions = g_slist_prepend (actions,
+ &export_action_convert_bitmap);
+ }
+ break;
+
+ case GIMP_INDEXED:
+ if (! (capabilities & GIMP_EXPORT_CAN_HANDLE_INDEXED))
+ {
+ if ((capabilities & GIMP_EXPORT_CAN_HANDLE_RGB) &&
+ (capabilities & GIMP_EXPORT_CAN_HANDLE_GRAY))
+ actions = g_slist_prepend (actions,
+ &export_action_convert_rgb_or_grayscale);
+ else if (capabilities & GIMP_EXPORT_CAN_HANDLE_RGB)
+ actions = g_slist_prepend (actions,
+ &export_action_convert_rgb);
+ else if (capabilities & GIMP_EXPORT_CAN_HANDLE_GRAY)
+ actions = g_slist_prepend (actions,
+ &export_action_convert_grayscale);
+ else if (capabilities & GIMP_EXPORT_CAN_HANDLE_BITMAP)
+ {
+ gint n_colors;
+
+ g_free (gimp_image_get_colormap (*image_ID, &n_colors));
+
+ if (n_colors > 2)
+ actions = g_slist_prepend (actions,
+ &export_action_convert_bitmap);
+ }
+ }
+ break;
+ }
+
+ if (actions)
+ {
+ actions = g_slist_reverse (actions);
+
+ if (interactive)
+ retval = export_dialog (actions, format_name);
+ else
+ retval = GIMP_EXPORT_EXPORT;
+ }
+ else
+ {
+ retval = GIMP_EXPORT_IGNORE;
+ }
+
+ if (retval == GIMP_EXPORT_EXPORT)
+ {
+ GSList *list;
+
+ *image_ID = gimp_image_duplicate (*image_ID);
+ *drawable_ID = gimp_image_get_active_layer (*image_ID);
+
+ gimp_image_undo_disable (*image_ID);
+
+ for (list = actions; list; list = list->next)
+ {
+ export_action_perform (list->data, *image_ID, drawable_ID);
+ }
+ }
+
+ g_slist_free (actions);
+
+ return retval;
+}
+
+/**
+ * gimp_export_dialog_new:
+ * @format_name: The short name of the image_format (e.g. JPEG or PNG).
+ * @role: The dialog's @role which will be set with
+ * gtk_window_set_role().
+ * @help_id: The GIMP help id.
+ *
+ * Creates a new export dialog. All file plug-ins should use this
+ * dialog to get a consistent look on the export dialogs. Use
+ * gimp_export_dialog_get_content_area() to get a #GtkVBox to be
+ * filled with export options. The export dialog is a wrapped
+ * #GimpDialog.
+ *
+ * The dialog response when the user clicks on the Export button is
+ * %GTK_RESPONSE_OK, and when the Cancel button is clicked it is
+ * %GTK_RESPONSE_CANCEL.
+ *
+ * Returns: The new export dialog.
+ *
+ * Since: 2.8
+ **/
+GtkWidget *
+gimp_export_dialog_new (const gchar *format_name,
+ const gchar *role,
+ const gchar *help_id)
+{
+ GtkWidget *dialog;
+ /* TRANSLATORS: the %s parameter is an image format name (ex: PNG). */
+ gchar *title = g_strdup_printf (_("Export Image as %s"), format_name);
+
+ dialog = gimp_dialog_new (title, role,
+ NULL, 0,
+ gimp_standard_help_func, help_id,
+
+ _("_Cancel"), GTK_RESPONSE_CANCEL,
+ _("_Export"), GTK_RESPONSE_OK,
+
+ NULL);
+
+ gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog),
+ GTK_RESPONSE_OK,
+ GTK_RESPONSE_CANCEL,
+ -1);
+
+ gimp_window_set_transient (GTK_WINDOW (dialog));
+
+ g_free (title);
+
+ return dialog;
+}
+
+/**
+ * gimp_export_dialog_get_content_area:
+ * @dialog: A dialog created with gimp_export_dialog_new()
+ *
+ * Returns the #GtkVBox of the passed export dialog to be filled with
+ * export options.
+ *
+ * Returns: The #GtkVBox to fill with export options.
+ *
+ * Since: 2.8
+ **/
+GtkWidget *
+gimp_export_dialog_get_content_area (GtkWidget *dialog)
+{
+ return gtk_dialog_get_content_area (GTK_DIALOG (dialog));
+}