/* LIBPIKA - The PIKA Library * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball * * pikaexport.c * Copyright (C) 1999-2004 Sven Neumann * * 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 * . */ #include "config.h" #include #include #include "pika.h" #include "pikaui.h" #include "libpika-intl.h" /** * SECTION: pikaexport * @title: pikaexport * @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 PIKA 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 * #PikaExportCapabilities 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) (PikaImage *image, GList **drawables); /* 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 (PikaImage *image, GList **drawables) { GList *layers; GList *iter; gint32 nvisible = 0; layers = pika_image_list_layers (image); for (iter = layers; iter; iter = g_list_next (iter)) { if (pika_item_get_visible (PIKA_ITEM (iter->data))) nvisible++; } if (nvisible <= 1) { PikaLayer *transp; PikaImageType layer_type; /* 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 */ switch (pika_image_get_base_type (image)) { case PIKA_RGB: layer_type = PIKA_RGBA_IMAGE; break; case PIKA_GRAY: layer_type = PIKA_GRAYA_IMAGE; break; case PIKA_INDEXED: layer_type = PIKA_INDEXEDA_IMAGE; break; default: /* In case we add a new type in future. */ g_return_if_reached (); } transp = pika_layer_new (image, "-", pika_image_get_width (image), pika_image_get_height (image), layer_type, 100.0, PIKA_LAYER_MODE_NORMAL); pika_image_insert_layer (image, transp, NULL, 1); pika_selection_none (image); pika_drawable_edit_clear (PIKA_DRAWABLE (transp)); nvisible++; } if (nvisible > 1) { PikaLayer *merged; merged = pika_image_merge_visible_layers (image, PIKA_CLIP_TO_IMAGE); g_return_if_fail (merged != NULL); *drawables = g_list_prepend (NULL, merged); g_list_free (layers); layers = pika_image_list_layers (image); /* make sure that the merged drawable matches the image size */ if (pika_drawable_get_width (PIKA_DRAWABLE (merged)) != pika_image_get_width (image) || pika_drawable_get_height (PIKA_DRAWABLE (merged)) != pika_image_get_height (image)) { gint off_x, off_y; pika_drawable_get_offsets (PIKA_DRAWABLE (merged), &off_x, &off_y); pika_layer_resize (merged, pika_image_get_width (image), pika_image_get_height (image), off_x, off_y); } } /* remove any remaining (invisible) layers */ for (iter = layers; iter; iter = iter->next) { if (! g_list_find (*drawables, iter->data)) pika_image_remove_layer (image, iter->data); } g_list_free (layers); } static void export_flatten (PikaImage *image, GList **drawables) { PikaLayer *flattened; flattened = pika_image_flatten (image); if (flattened != NULL) *drawables = g_list_prepend (NULL, flattened); } static void export_remove_alpha (PikaImage *image, GList **drawables) { GList *layers; GList *iter; layers = pika_image_list_layers (image); for (iter = layers; iter; iter = iter->next) { if (pika_drawable_has_alpha (PIKA_DRAWABLE (iter->data))) pika_layer_flatten (iter->data); } g_list_free (layers); } static void export_apply_masks (PikaImage *image, GList **drawables) { GList *layers; GList *iter; layers = pika_image_list_layers (image); for (iter = layers; iter; iter = iter->next) { PikaLayerMask *mask; mask = pika_layer_get_mask (iter->data); if (mask) { /* we can't apply the mask directly to a layer group, so merge it * first */ if (pika_item_is_group (iter->data)) iter->data = pika_image_merge_layer_group (image, iter->data); pika_layer_remove_mask (iter->data, PIKA_MASK_APPLY); } } g_list_free (layers); } static void export_convert_rgb (PikaImage *image, GList **drawables) { pika_image_convert_rgb (image); } static void export_convert_grayscale (PikaImage *image, GList **drawables) { pika_image_convert_grayscale (image); } static void export_convert_indexed (PikaImage *image, GList **drawables) { GList *layers; GList *iter; gboolean has_alpha = FALSE; /* check alpha */ layers = pika_image_list_layers (image); for (iter = *drawables; iter; iter = iter->next) { if (pika_drawable_has_alpha (iter->data)) { has_alpha = TRUE; break; } } if (layers || has_alpha) pika_image_convert_indexed (image, PIKA_CONVERT_DITHER_NONE, PIKA_CONVERT_PALETTE_GENERATE, 255, FALSE, FALSE, ""); else pika_image_convert_indexed (image, PIKA_CONVERT_DITHER_NONE, PIKA_CONVERT_PALETTE_GENERATE, 256, FALSE, FALSE, ""); g_list_free (layers); } static void export_convert_bitmap (PikaImage *image, GList **drawables) { if (pika_image_get_base_type (image) == PIKA_INDEXED) pika_image_convert_rgb (image); pika_image_convert_indexed (image, PIKA_CONVERT_DITHER_FS, PIKA_CONVERT_PALETTE_GENERATE, 2, FALSE, FALSE, ""); } static void export_add_alpha (PikaImage *image, GList **drawables) { GList *layers; GList *iter; layers = pika_image_list_layers (image); for (iter = layers; iter; iter = iter->next) { if (! pika_drawable_has_alpha (PIKA_DRAWABLE (iter->data))) pika_layer_add_alpha (PIKA_LAYER (iter->data)); } g_list_free (layers); } static void export_crop_image (PikaImage *image, GList **drawables) { pika_image_crop (image, pika_image_get_width (image), pika_image_get_height (image), 0, 0); } static void export_resize_image (PikaImage *image, GList **drawables) { pika_image_resize_to_layers (image); } static void export_void (PikaImage *image, GList **drawables) { /* 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, PikaImage *image, GList **drawables) { export_action_get_func (action) (image, drawables); } /* 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 PikaExportReturn confirm_save_dialog (const gchar *message, const gchar *format_name) { GtkWidget *dialog; GtkWidget *hbox; GtkWidget *image; GtkWidget *main_vbox; GtkWidget *label; gchar *text; PikaExportReturn retval; g_return_val_if_fail (message != NULL, PIKA_EXPORT_CANCEL); g_return_val_if_fail (format_name != NULL, PIKA_EXPORT_CANCEL); dialog = pika_dialog_new (_("Confirm Save"), "pika-export-image-confirm", NULL, 0, pika_standard_help_func, "pika-export-confirm-dialog", _("_Cancel"), GTK_RESPONSE_CANCEL, _("C_onfirm"), GTK_RESPONSE_OK, NULL); pika_dialog_set_alternative_button_order (GTK_DIALOG (dialog), GTK_RESPONSE_OK, GTK_RESPONSE_CANCEL, -1); gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE); pika_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_widget_set_valign (image, GTK_ALIGN_START); 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); pika_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 (pika_dialog_run (PIKA_DIALOG (dialog))) { case GTK_RESPONSE_OK: retval = PIKA_EXPORT_EXPORT; break; default: retval = PIKA_EXPORT_CANCEL; break; } gtk_widget_destroy (dialog); return retval; } static PikaExportReturn export_dialog (GSList *actions, const gchar *format_name) { GtkWidget *dialog; GtkWidget *hbox; GtkWidget *image; GtkWidget *main_vbox; GtkWidget *label; GSList *list; gchar *text; PikaExportReturn retval; g_return_val_if_fail (actions != NULL, PIKA_EXPORT_CANCEL); g_return_val_if_fail (format_name != NULL, PIKA_EXPORT_CANCEL); dialog = pika_dialog_new (_("Export File"), "pika-export-image", NULL, 0, pika_standard_help_func, "pika-export-dialog", _("_Ignore"), GTK_RESPONSE_NO, _("_Cancel"), GTK_RESPONSE_CANCEL, _("_Export"), GTK_RESPONSE_OK, NULL); pika_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); pika_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_widget_set_valign (image, GTK_ALIGN_START); 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); pika_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 = pika_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.")); pika_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 (pika_dialog_run (PIKA_DIALOG (dialog))) { case GTK_RESPONSE_OK: retval = PIKA_EXPORT_EXPORT; break; case GTK_RESPONSE_NO: retval = PIKA_EXPORT_IGNORE; break; default: retval = PIKA_EXPORT_CANCEL; break; } gtk_widget_destroy (dialog); return retval; } /** * pika_export_image: * @image: Pointer to the image. * @n_drawables: Size of @drawables. * @drawables: (array length=n_drawables): Array of pointers to drawables. * @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, @image and @drawables are changed to * point to the new image and the procedure returns PIKA_EXPORT_EXPORT. * The save_plugin has to take care of deleting the created image using * pika_image_delete() and the drawables list with g_free() once the * image has been saved. * * If the user chooses to Ignore the export problem, @image and * @drawables are not altered, PIKA_EXPORT_IGNORE is returned and the * save_plugin should try to save the original image. If the user * chooses Cancel, PIKA_EXPORT_CANCEL is returned and the save_plugin * should quit itself with status %PIKA_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 #PikaExportReturn describing the user_action. **/ PikaExportReturn pika_export_image (PikaImage **image, gint *n_drawables, PikaDrawable ***drawables, const gchar *format_name, PikaExportCapabilities capabilities) { GSList *actions = NULL; PikaImageBaseType type; GList *layers; GList *iter; GType drawables_type = G_TYPE_NONE; gboolean interactive = FALSE; gboolean added_flatten = FALSE; gboolean has_layer_masks = FALSE; gboolean background_has_alpha = TRUE; PikaExportReturn retval = PIKA_EXPORT_CANCEL; gint i; g_return_val_if_fail (pika_image_is_valid (*image) && drawables && n_drawables && *n_drawables > 0, FALSE); for (i = 0; i < *n_drawables; i++) { g_return_val_if_fail (pika_item_is_valid (PIKA_ITEM ((*drawables)[i])), FALSE); if (drawables_type == G_TYPE_NONE || g_type_is_a (drawables_type, G_OBJECT_TYPE ((*drawables)[i]))) drawables_type = G_OBJECT_TYPE ((*drawables)[i]); else g_return_val_if_fail (g_type_is_a (G_OBJECT_TYPE ((*drawables)[i]), drawables_type), FALSE); } /* do some sanity checks */ if (capabilities & PIKA_EXPORT_NEEDS_ALPHA) capabilities |= PIKA_EXPORT_CAN_HANDLE_ALPHA; if (capabilities & PIKA_EXPORT_CAN_HANDLE_LAYERS_AS_ANIMATION) capabilities |= PIKA_EXPORT_CAN_HANDLE_LAYERS; if (capabilities & PIKA_EXPORT_CAN_HANDLE_LAYER_MASKS) capabilities |= PIKA_EXPORT_CAN_HANDLE_LAYERS; if (format_name && g_getenv ("PIKA_INTERACTIVE_EXPORT")) interactive = TRUE; /* ask for confirmation if the user is not saving a layer (see bug #51114) */ if (interactive && ! g_type_is_a (drawables_type, PIKA_TYPE_LAYER) && ! (capabilities & PIKA_EXPORT_CAN_HANDLE_LAYERS)) { if (g_type_is_a (drawables_type, PIKA_TYPE_LAYER_MASK)) { 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 (g_type_is_a (drawables_type, PIKA_TYPE_CHANNEL)) { 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 == PIKA_EXPORT_CANCEL) return PIKA_EXPORT_CANCEL; } /* check alpha and layer masks */ layers = pika_image_list_layers (*image); for (iter = layers; iter; iter = iter->next) { PikaLayer *layer = PIKA_LAYER (iter->data); if (pika_drawable_has_alpha (PIKA_DRAWABLE (layer))) { if (! (capabilities & PIKA_EXPORT_CAN_HANDLE_ALPHA)) { if (! (capabilities & PIKA_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 (iter->next == NULL && pika_item_get_visible (PIKA_ITEM (layer))) background_has_alpha = FALSE; if (capabilities & PIKA_EXPORT_NEEDS_ALPHA) { actions = g_slist_prepend (actions, &export_action_add_alpha); break; } } } if (! added_flatten) { for (iter = layers; iter; iter = iter->next) { if (pika_layer_get_mask (iter->data)) has_layer_masks = TRUE; } } if (! added_flatten) { PikaLayer *layer = PIKA_LAYER (layers->data); GList *children; children = pika_item_list_children (PIKA_ITEM (layer)); if ((capabilities & PIKA_EXPORT_CAN_HANDLE_LAYERS) && (capabilities & PIKA_EXPORT_NEEDS_CROP)) { GeglRectangle image_bounds; gboolean needs_crop = FALSE; image_bounds.x = 0; image_bounds.y = 0; image_bounds.width = pika_image_get_width (*image); image_bounds.height = pika_image_get_height (*image); for (iter = layers; iter; iter = iter->next) { PikaDrawable *drawable = iter->data; GeglRectangle layer_bounds; pika_drawable_get_offsets (drawable, &layer_bounds.x, &layer_bounds.y); layer_bounds.width = pika_drawable_get_width (drawable); layer_bounds.height = pika_drawable_get_height (drawable); 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 (g_list_length (layers) == 1 && ! children && g_type_is_a (drawables_type, PIKA_TYPE_LAYER) && ! (capabilities & PIKA_EXPORT_CAN_HANDLE_LAYERS)) { PikaDrawable *drawable = (*drawables)[0]; gint offset_x; gint offset_y; pika_drawable_get_offsets (drawable, &offset_x, &offset_y); if ((pika_layer_get_opacity (PIKA_LAYER (drawable)) < 100.0) || (pika_image_get_width (*image) != pika_drawable_get_width (drawable)) || (pika_image_get_height (*image) != pika_drawable_get_height (drawable)) || offset_x || offset_y) { if (capabilities & PIKA_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 (layers && layers->next != NULL) { if (capabilities & PIKA_EXPORT_CAN_HANDLE_LAYERS_AS_ANIMATION) { if (background_has_alpha || capabilities & PIKA_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 & PIKA_EXPORT_CAN_HANDLE_LAYERS)) { if (capabilities & PIKA_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 & PIKA_EXPORT_CAN_HANDLE_LAYERS)) { if (capabilities & PIKA_EXPORT_NEEDS_ALPHA) actions = g_slist_prepend (actions, &export_action_merge); else actions = g_slist_prepend (actions, &export_action_merge_or_flatten); } } g_list_free (children); /* check layer masks */ if (has_layer_masks && ! (capabilities & PIKA_EXPORT_CAN_HANDLE_LAYER_MASKS)) actions = g_slist_prepend (actions, &export_action_apply_masks); } g_list_free (layers); /* check the image type */ type = pika_image_get_base_type (*image); switch (type) { case PIKA_RGB: if (! (capabilities & PIKA_EXPORT_CAN_HANDLE_RGB)) { if ((capabilities & PIKA_EXPORT_CAN_HANDLE_INDEXED) && (capabilities & PIKA_EXPORT_CAN_HANDLE_GRAY)) actions = g_slist_prepend (actions, &export_action_convert_indexed_or_grayscale); else if (capabilities & PIKA_EXPORT_CAN_HANDLE_INDEXED) actions = g_slist_prepend (actions, &export_action_convert_indexed); else if (capabilities & PIKA_EXPORT_CAN_HANDLE_GRAY) actions = g_slist_prepend (actions, &export_action_convert_grayscale); else if (capabilities & PIKA_EXPORT_CAN_HANDLE_BITMAP) actions = g_slist_prepend (actions, &export_action_convert_bitmap); } break; case PIKA_GRAY: if (! (capabilities & PIKA_EXPORT_CAN_HANDLE_GRAY)) { if ((capabilities & PIKA_EXPORT_CAN_HANDLE_RGB) && (capabilities & PIKA_EXPORT_CAN_HANDLE_INDEXED)) actions = g_slist_prepend (actions, &export_action_convert_rgb_or_indexed); else if (capabilities & PIKA_EXPORT_CAN_HANDLE_RGB) actions = g_slist_prepend (actions, &export_action_convert_rgb); else if (capabilities & PIKA_EXPORT_CAN_HANDLE_INDEXED) actions = g_slist_prepend (actions, &export_action_convert_indexed); else if (capabilities & PIKA_EXPORT_CAN_HANDLE_BITMAP) actions = g_slist_prepend (actions, &export_action_convert_bitmap); } break; case PIKA_INDEXED: if (! (capabilities & PIKA_EXPORT_CAN_HANDLE_INDEXED)) { if ((capabilities & PIKA_EXPORT_CAN_HANDLE_RGB) && (capabilities & PIKA_EXPORT_CAN_HANDLE_GRAY)) actions = g_slist_prepend (actions, &export_action_convert_rgb_or_grayscale); else if (capabilities & PIKA_EXPORT_CAN_HANDLE_RGB) actions = g_slist_prepend (actions, &export_action_convert_rgb); else if (capabilities & PIKA_EXPORT_CAN_HANDLE_GRAY) actions = g_slist_prepend (actions, &export_action_convert_grayscale); else if (capabilities & PIKA_EXPORT_CAN_HANDLE_BITMAP) { gint n_colors; g_free (pika_image_get_colormap (*image, NULL, &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 = PIKA_EXPORT_EXPORT; } else { retval = PIKA_EXPORT_IGNORE; } if (retval == PIKA_EXPORT_EXPORT) { GSList *list; GList *drawables_in; GList *drawables_out; gint i; *image = pika_image_duplicate (*image); drawables_in = pika_image_list_selected_layers (*image); drawables_out = drawables_in; pika_image_undo_disable (*image); for (list = actions; list; list = list->next) { export_action_perform (list->data, *image, &drawables_out); if (drawables_in != drawables_out) { g_list_free (drawables_in); drawables_in = drawables_out; } } *n_drawables = g_list_length (drawables_out); *drawables = g_new (PikaDrawable *, *n_drawables); for (iter = drawables_out, i = 0; iter; iter = iter->next, i++) (*drawables)[i] = iter->data; g_list_free (drawables_out); } g_slist_free (actions); return retval; } /** * pika_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 PIKA 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 * pika_export_dialog_get_content_area() to get a vertical #GtkBox to be * filled with export options. The export dialog is a wrapped * #PikaDialog. * * 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: (transfer full): The new export dialog. * * Since: 2.8 **/ GtkWidget * pika_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 = pika_dialog_new (title, role, NULL, 0, pika_standard_help_func, help_id, _("_Cancel"), GTK_RESPONSE_CANCEL, _("_Export"), GTK_RESPONSE_OK, NULL); pika_dialog_set_alternative_button_order (GTK_DIALOG (dialog), GTK_RESPONSE_OK, GTK_RESPONSE_CANCEL, -1); pika_window_set_transient (GTK_WINDOW (dialog)); g_free (title); return dialog; } /** * pika_export_dialog_get_content_area: * @dialog: A dialog created with pika_export_dialog_new() * * Returns the vertical #GtkBox of the passed export dialog to be filled with * export options. * * Returns: (transfer none): The #GtkBox to fill with export options. * * Since: 2.8 **/ GtkWidget * pika_export_dialog_get_content_area (GtkWidget *dialog) { return gtk_dialog_get_content_area (GTK_DIALOG (dialog)); }