diff options
Diffstat (limited to 'app/file/file-open.c')
-rw-r--r-- | app/file/file-open.c | 833 |
1 files changed, 833 insertions, 0 deletions
diff --git a/app/file/file-open.c b/app/file/file-open.c new file mode 100644 index 0000000..b3ae129 --- /dev/null +++ b/app/file/file-open.c @@ -0,0 +1,833 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995, 1996, 1997 Spencer Kimball and Peter Mattis + * Copyright (C) 1997 Josh MacDonald + * + * file-open.c + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gdk-pixbuf/gdk-pixbuf.h> +#include <gegl.h> + +#include "libgimpbase/gimpbase.h" + +#include "core/core-types.h" + +#include "gegl/gimp-babl.h" + +#include "core/gimp.h" +#include "core/gimpcontext.h" +#include "core/gimpdocumentlist.h" +#include "core/gimpimage.h" +#include "core/gimpimage-merge.h" +#include "core/gimpimage-undo.h" +#include "core/gimpimagefile.h" +#include "core/gimplayer.h" +#include "core/gimpparamspecs.h" +#include "core/gimpprogress.h" + +#include "pdb/gimppdb.h" + +#include "plug-in/gimppluginmanager-file.h" +#include "plug-in/gimppluginprocedure.h" + +#include "file-import.h" +#include "file-open.h" +#include "file-remote.h" +#include "gimp-file.h" + +#include "gimp-intl.h" + + +static void file_open_sanitize_image (GimpImage *image, + gboolean as_new); +static void file_open_convert_items (GimpImage *dest_image, + const gchar *basename, + GList *items); +static GList * file_open_get_layers (GimpImage *image, + gboolean merge_visible, + gint *n_visible); +static gboolean file_open_file_proc_is_import (GimpPlugInProcedure *file_proc); + + +/* public functions */ + +GimpImage * +file_open_image (Gimp *gimp, + GimpContext *context, + GimpProgress *progress, + GFile *file, + GFile *entered_file, + gboolean as_new, + GimpPlugInProcedure *file_proc, + GimpRunMode run_mode, + GimpPDBStatusType *status, + const gchar **mime_type, + GError **error) +{ + GimpValueArray *return_vals; + GimpImage *image = NULL; + GFile *local_file = NULL; + gchar *path = NULL; + gchar *entered_uri = NULL; + gboolean mounted = TRUE; + GError *my_error = NULL; + + g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL); + g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL); + g_return_val_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress), NULL); + g_return_val_if_fail (G_IS_FILE (file), NULL); + g_return_val_if_fail (G_IS_FILE (entered_file), NULL); + g_return_val_if_fail (status != NULL, NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + *status = GIMP_PDB_EXECUTION_ERROR; + + if (! g_file_is_native (file) && + ! file_remote_mount_file (gimp, file, progress, &my_error)) + { + if (my_error) + { + g_printerr ("%s: mounting remote volume failed, trying to download" + "the file: %s\n", + G_STRFUNC, my_error->message); + g_clear_error (&my_error); + + mounted = FALSE; + } + else + { + *status = GIMP_PDB_CANCEL; + + return NULL; + } + } + + /* FIXME enable these tests for remote files again, needs testing */ + if (g_file_is_native (file) && + g_file_query_exists (file, NULL)) + { + GFileInfo *info; + + info = g_file_query_info (file, + G_FILE_ATTRIBUTE_STANDARD_TYPE "," + G_FILE_ATTRIBUTE_ACCESS_CAN_READ, + G_FILE_QUERY_INFO_NONE, + NULL, error); + if (! info) + return NULL; + + if (g_file_info_get_file_type (info) != G_FILE_TYPE_REGULAR) + { + g_set_error_literal (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Not a regular file")); + g_object_unref (info); + return NULL; + } + + if (! g_file_info_get_attribute_boolean (info, + G_FILE_ATTRIBUTE_ACCESS_CAN_READ)) + { + g_set_error_literal (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Permission denied")); + g_object_unref (info); + return NULL; + } + + g_object_unref (info); + } + + if (! file_proc) + file_proc = gimp_plug_in_manager_file_procedure_find (gimp->plug_in_manager, + GIMP_FILE_PROCEDURE_GROUP_OPEN, + file, error); + + if (! file_proc || ! file_proc->handles_uri || ! mounted) + { + gchar *my_path = g_file_get_path (file); + + if (! my_path) + { + g_clear_error (error); + + local_file = file_remote_download_image (gimp, file, progress, + &my_error); + + if (! local_file) + { + if (my_error) + g_propagate_error (error, my_error); + else + *status = GIMP_PDB_CANCEL; + + return NULL; + } + + /* if we don't have a file proc yet, try again on the local + * file + */ + if (! file_proc) + file_proc = gimp_plug_in_manager_file_procedure_find (gimp->plug_in_manager, + GIMP_FILE_PROCEDURE_GROUP_OPEN, + local_file, error); + } + + g_free (my_path); + } + + if (! file_proc) + { + if (local_file) + { + g_file_delete (local_file, NULL, NULL); + g_object_unref (local_file); + } + + return NULL; + } + + if (file_proc->handles_uri) + path = g_file_get_uri (local_file ? local_file : file); + else + path = g_file_get_path (local_file ? local_file : file); + + entered_uri = g_file_get_uri (entered_file); + + if (! entered_uri) + entered_uri = g_strdup (path); + + if (progress) + g_object_add_weak_pointer (G_OBJECT (progress), (gpointer) &progress); + + return_vals = + gimp_pdb_execute_procedure_by_name (gimp->pdb, + context, progress, error, + gimp_object_get_name (file_proc), + GIMP_TYPE_INT32, run_mode, + G_TYPE_STRING, path, + G_TYPE_STRING, entered_uri, + G_TYPE_NONE); + + if (progress) + g_object_remove_weak_pointer (G_OBJECT (progress), (gpointer) &progress); + + g_free (path); + g_free (entered_uri); + + *status = g_value_get_enum (gimp_value_array_index (return_vals, 0)); + + if (*status == GIMP_PDB_SUCCESS) + image = gimp_value_get_image (gimp_value_array_index (return_vals, 1), + gimp); + + if (local_file) + { + if (image) + gimp_image_set_file (image, file); + + g_file_delete (local_file, NULL, NULL); + g_object_unref (local_file); + } + + if (*status == GIMP_PDB_SUCCESS) + { + if (image) + { + /* Only set the load procedure if it hasn't already been set. */ + if (! gimp_image_get_load_proc (image)) + gimp_image_set_load_proc (image, file_proc); + + file_proc = gimp_image_get_load_proc (image); + + if (mime_type) + *mime_type = g_slist_nth_data (file_proc->mime_types_list, 0); + } + else + { + if (error && ! *error) + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("%s plug-in returned SUCCESS but did not " + "return an image"), + gimp_procedure_get_label (GIMP_PROCEDURE (file_proc))); + + *status = GIMP_PDB_EXECUTION_ERROR; + } + } + else if (*status != GIMP_PDB_CANCEL) + { + if (error && ! *error) + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("%s plug-in could not open image"), + gimp_procedure_get_label (GIMP_PROCEDURE (file_proc))); + } + + gimp_value_array_unref (return_vals); + + if (image) + { + gimp_image_undo_disable (image); + + if (file_open_file_proc_is_import (file_proc)) + { + file_import_image (image, context, file, + run_mode == GIMP_RUN_INTERACTIVE, + progress); + } + + /* Enables undo again */ + file_open_sanitize_image (image, as_new); + } + + return image; +} + +/** + * file_open_thumbnail: + * @gimp: + * @context: + * @progress: + * @file: an image file + * @size: requested size of the thumbnail + * @mime_type: return location for image MIME type + * @image_width: return location for image width + * @image_height: return location for image height + * @format: return location for image format (set to NULL if unknown) + * @num_layers: return location for number of layers + * (set to -1 if the number of layers is not known) + * @error: + * + * Attempts to load a thumbnail by using a registered thumbnail loader. + * + * Return value: the thumbnail image + */ +GimpImage * +file_open_thumbnail (Gimp *gimp, + GimpContext *context, + GimpProgress *progress, + GFile *file, + gint size, + const gchar **mime_type, + gint *image_width, + gint *image_height, + const Babl **format, + gint *num_layers, + GError **error) +{ + GimpPlugInProcedure *file_proc; + GimpProcedure *procedure; + + g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL); + g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL); + g_return_val_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress), NULL); + g_return_val_if_fail (G_IS_FILE (file), NULL); + g_return_val_if_fail (mime_type != NULL, NULL); + g_return_val_if_fail (image_width != NULL, NULL); + g_return_val_if_fail (image_height != NULL, NULL); + g_return_val_if_fail (format != NULL, NULL); + g_return_val_if_fail (num_layers != NULL, NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + *image_width = 0; + *image_height = 0; + *format = NULL; + *num_layers = -1; + + file_proc = gimp_plug_in_manager_file_procedure_find (gimp->plug_in_manager, + GIMP_FILE_PROCEDURE_GROUP_OPEN, + file, NULL); + + if (! file_proc || ! file_proc->thumb_loader) + return NULL; + + procedure = gimp_pdb_lookup_procedure (gimp->pdb, file_proc->thumb_loader); + + if (procedure && procedure->num_args >= 2 && procedure->num_values >= 1) + { + GimpPDBStatusType status; + GimpValueArray *return_vals; + GimpImage *image = NULL; + gchar *path = NULL; + + if (! file_proc->handles_uri) + path = g_file_get_path (file); + + if (! path) + path = g_file_get_uri (file); + + return_vals = + gimp_pdb_execute_procedure_by_name (gimp->pdb, + context, progress, error, + gimp_object_get_name (procedure), + G_TYPE_STRING, path, + GIMP_TYPE_INT32, size, + G_TYPE_NONE); + + g_free (path); + + status = g_value_get_enum (gimp_value_array_index (return_vals, 0)); + + if (status == GIMP_PDB_SUCCESS && + GIMP_VALUE_HOLDS_IMAGE_ID (gimp_value_array_index (return_vals, 1))) + { + image = gimp_value_get_image (gimp_value_array_index (return_vals, 1), + gimp); + + if (gimp_value_array_length (return_vals) >= 3 && + G_VALUE_HOLDS_INT (gimp_value_array_index (return_vals, 2)) && + G_VALUE_HOLDS_INT (gimp_value_array_index (return_vals, 3))) + { + *image_width = + MAX (0, g_value_get_int (gimp_value_array_index (return_vals, 2))); + + *image_height = + MAX (0, g_value_get_int (gimp_value_array_index (return_vals, 3))); + + if (gimp_value_array_length (return_vals) >= 5 && + G_VALUE_HOLDS_INT (gimp_value_array_index (return_vals, 4))) + { + gint value = g_value_get_int (gimp_value_array_index (return_vals, 4)); + + switch (value) + { + case GIMP_RGB_IMAGE: + *format = gimp_babl_format (GIMP_RGB, + GIMP_PRECISION_U8_GAMMA, + FALSE); + break; + + case GIMP_RGBA_IMAGE: + *format = gimp_babl_format (GIMP_RGB, + GIMP_PRECISION_U8_GAMMA, + TRUE); + break; + + case GIMP_GRAY_IMAGE: + *format = gimp_babl_format (GIMP_GRAY, + GIMP_PRECISION_U8_GAMMA, + FALSE); + break; + + case GIMP_GRAYA_IMAGE: + *format = gimp_babl_format (GIMP_GRAY, + GIMP_PRECISION_U8_GAMMA, + TRUE); + break; + + case GIMP_INDEXED_IMAGE: + case GIMP_INDEXEDA_IMAGE: + { + const Babl *rgb; + const Babl *rgba; + + babl_new_palette ("-gimp-indexed-format-dummy", + &rgb, &rgba); + + if (value == GIMP_INDEXED_IMAGE) + *format = rgb; + else + *format = rgba; + } + break; + + default: + break; + } + } + + if (gimp_value_array_length (return_vals) >= 6 && + G_VALUE_HOLDS_INT (gimp_value_array_index (return_vals, 5))) + { + *num_layers = + MAX (0, g_value_get_int (gimp_value_array_index (return_vals, 5))); + } + } + + if (image) + { + file_open_sanitize_image (image, FALSE); + + *mime_type = g_slist_nth_data (file_proc->mime_types_list, 0); + +#ifdef GIMP_UNSTABLE + g_printerr ("opened thumbnail at %d x %d\n", + gimp_image_get_width (image), + gimp_image_get_height (image)); +#endif + } + } + + gimp_value_array_unref (return_vals); + + return image; + } + + return NULL; +} + +GimpImage * +file_open_with_display (Gimp *gimp, + GimpContext *context, + GimpProgress *progress, + GFile *file, + gboolean as_new, + GObject *screen, + gint monitor, + GimpPDBStatusType *status, + GError **error) +{ + return file_open_with_proc_and_display (gimp, context, progress, + file, file, as_new, NULL, + screen, monitor, + status, error); +} + +GimpImage * +file_open_with_proc_and_display (Gimp *gimp, + GimpContext *context, + GimpProgress *progress, + GFile *file, + GFile *entered_file, + gboolean as_new, + GimpPlugInProcedure *file_proc, + GObject *screen, + gint monitor, + GimpPDBStatusType *status, + GError **error) +{ + GimpImage *image; + const gchar *mime_type = NULL; + + g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL); + g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL); + g_return_val_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress), NULL); + g_return_val_if_fail (G_IS_FILE (file), NULL); + g_return_val_if_fail (G_IS_FILE (entered_file), NULL); + g_return_val_if_fail (screen == NULL || G_IS_OBJECT (screen), NULL); + g_return_val_if_fail (status != NULL, NULL); + + image = file_open_image (gimp, context, progress, + file, + entered_file, + as_new, + file_proc, + GIMP_RUN_INTERACTIVE, + status, + &mime_type, + error); + + if (image) + { + /* If the file was imported we want to set the layer name to the + * file name. For now, assume that multi-layered imported images + * have named the layers already, so only rename the layer of + * single-layered imported files. Note that this will also + * rename already named layers from e.g. single-layered PSD + * files. To solve this properly, we would need new file plug-in + * API. + */ + if (! file_proc) + file_proc = gimp_image_get_load_proc (image); + + if (file_open_file_proc_is_import (file_proc) && + gimp_image_get_n_layers (image) == 1) + { + GimpObject *layer = gimp_image_get_layer_iter (image)->data; + gchar *basename; + + basename = g_path_get_basename (gimp_file_get_utf8_name (file)); + + gimp_item_rename (GIMP_ITEM (layer), basename, NULL); + gimp_image_undo_free (image); + gimp_image_clean_all (image); + + g_free (basename); + } + + if (gimp_create_display (image->gimp, image, GIMP_UNIT_PIXEL, 1.0, + screen, monitor)) + { + /* the display owns the image now */ + g_object_unref (image); + } + + if (! as_new) + { + GimpDocumentList *documents = GIMP_DOCUMENT_LIST (gimp->documents); + GimpImagefile *imagefile; + GFile *any_file; + + imagefile = gimp_document_list_add_file (documents, file, mime_type); + + /* can only create a thumbnail if the passed file and the + * resulting image's file match. Use any_file() here so we + * create thumbnails for both XCF and imported images. + */ + any_file = gimp_image_get_any_file (image); + + if (any_file && g_file_equal (file, any_file)) + { + /* no need to save a thumbnail if there's a good one already */ + if (! gimp_imagefile_check_thumbnail (imagefile)) + { + gimp_imagefile_save_thumbnail (imagefile, mime_type, image, + NULL); + } + } + } + + /* announce that we opened this image */ + gimp_image_opened (image->gimp, file); + } + + return image; +} + +GList * +file_open_layers (Gimp *gimp, + GimpContext *context, + GimpProgress *progress, + GimpImage *dest_image, + gboolean merge_visible, + GFile *file, + GimpRunMode run_mode, + GimpPlugInProcedure *file_proc, + GimpPDBStatusType *status, + GError **error) +{ + GimpImage *new_image; + GList *layers = NULL; + const gchar *mime_type = NULL; + + g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL); + g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL); + g_return_val_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress), NULL); + g_return_val_if_fail (GIMP_IS_IMAGE (dest_image), NULL); + g_return_val_if_fail (G_IS_FILE (file), NULL); + g_return_val_if_fail (status != NULL, NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + new_image = file_open_image (gimp, context, progress, + file, file, FALSE, + file_proc, + run_mode, + status, &mime_type, error); + + if (new_image) + { + gint n_visible = 0; + + gimp_image_undo_disable (new_image); + + layers = file_open_get_layers (new_image, merge_visible, &n_visible); + + if (merge_visible && n_visible > 1) + { + GimpLayer *layer; + + g_list_free (layers); + + layer = gimp_image_merge_visible_layers (new_image, context, + GIMP_CLIP_TO_IMAGE, + FALSE, FALSE, + NULL); + + layers = g_list_prepend (NULL, layer); + } + + if (layers) + { + gchar *basename; + + basename = g_path_get_basename (gimp_file_get_utf8_name (file)); + file_open_convert_items (dest_image, basename, layers); + g_free (basename); + + gimp_document_list_add_file (GIMP_DOCUMENT_LIST (gimp->documents), + file, mime_type); + } + else + { + g_set_error_literal (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Image doesn't contain any layers")); + *status = GIMP_PDB_EXECUTION_ERROR; + } + + g_object_unref (new_image); + } + + return g_list_reverse (layers); +} + + +/* This function is called for filenames passed on the command-line + * or from the D-Bus service. + */ +gboolean +file_open_from_command_line (Gimp *gimp, + GFile *file, + gboolean as_new, + GObject *screen, + gint monitor) + +{ + GimpImage *image; + GimpObject *display; + GimpPDBStatusType status; + gboolean success = FALSE; + GError *error = NULL; + + g_return_val_if_fail (GIMP_IS_GIMP (gimp), FALSE); + g_return_val_if_fail (G_IS_FILE (file), FALSE); + g_return_val_if_fail (screen == NULL || G_IS_OBJECT (screen), FALSE); + + display = gimp_get_empty_display (gimp); + + /* show the progress in the last opened display, see bug #704896 */ + if (! display) + display = gimp_context_get_display (gimp_get_user_context (gimp)); + + if (display) + g_object_add_weak_pointer (G_OBJECT (display), (gpointer) &display); + + image = file_open_with_display (gimp, + gimp_get_user_context (gimp), + GIMP_PROGRESS (display), + file, as_new, + screen, monitor, + &status, &error); + + if (image) + { + success = TRUE; + + g_object_set_data_full (G_OBJECT (gimp), GIMP_FILE_OPEN_LAST_FILE_KEY, + g_object_ref (file), + (GDestroyNotify) g_object_unref); + } + else if (status != GIMP_PDB_CANCEL && display) + { + gimp_message (gimp, G_OBJECT (display), GIMP_MESSAGE_ERROR, + _("Opening '%s' failed: %s"), + gimp_file_get_utf8_name (file), error->message); + g_clear_error (&error); + } + + if (display) + g_object_remove_weak_pointer (G_OBJECT (display), (gpointer) &display); + + return success; +} + + +/* private functions */ + +static void +file_open_sanitize_image (GimpImage *image, + gboolean as_new) +{ + if (as_new) + gimp_image_set_file (image, NULL); + + /* clear all undo steps */ + gimp_image_undo_free (image); + + /* make sure that undo is enabled */ + while (! gimp_image_undo_is_enabled (image)) + gimp_image_undo_thaw (image); + + /* Set the image to clean. Note that export dirtiness is not set to + * clean here; we can only consider export clean after the first + * export + */ + gimp_image_clean_all (image); + + /* Make sure the projection is completely constructed from valid + * layers, this is needed in case something triggers projection or + * image preview creation before all layers are loaded, see bug #767663. + */ + gimp_image_invalidate_all (image); + + /* Make sure all image states are up-to-date */ + gimp_image_flush (image); +} + +/* Converts items from one image to another */ +static void +file_open_convert_items (GimpImage *dest_image, + const gchar *basename, + GList *items) +{ + GList *list; + + for (list = items; list; list = g_list_next (list)) + { + GimpItem *src = list->data; + GimpItem *item; + + item = gimp_item_convert (src, dest_image, G_TYPE_FROM_INSTANCE (src)); + + if (g_list_length (items) == 1) + { + gimp_object_set_name (GIMP_OBJECT (item), basename); + } + else + { + gimp_object_set_name (GIMP_OBJECT (item), + gimp_object_get_name (src)); + } + + list->data = item; + } +} + +static GList * +file_open_get_layers (GimpImage *image, + gboolean merge_visible, + gint *n_visible) +{ + GList *iter = NULL; + GList *layers = NULL; + + for (iter = gimp_image_get_layer_iter (image); + iter; + iter = g_list_next (iter)) + { + GimpItem *item = iter->data; + + if (! merge_visible) + layers = g_list_prepend (layers, item); + + if (gimp_item_get_visible (item)) + { + if (n_visible) + (*n_visible)++; + + if (! layers) + layers = g_list_prepend (layers, item); + } + } + + return layers; +} + +static gboolean +file_open_file_proc_is_import (GimpPlugInProcedure *file_proc) +{ + return !(file_proc && + file_proc->mime_types && + strcmp (file_proc->mime_types, "image/x-xcf") == 0); +} |