/* PIKA - Photo and Image Kooker Application * a rebranding of The GNU Image Manipulation Program (created with heckimp) * A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio * * Original copyright, applying to most contents (license remains unchanged): * Copyright (C) 1995 Spencer Kimball and Peter Mattis * * PIKA PSD Plug-in * Copyright 2007 by John Marshall * * 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 . */ #include "config.h" #include #include #include #include "psd.h" #include "psd-load.h" #include "psd-save.h" #include "psd-thumb-load.h" #include "libpika/stdplugins-intl.h" typedef struct _Psd Psd; typedef struct _PsdClass PsdClass; struct _Psd { PikaPlugIn parent_instance; }; struct _PsdClass { PikaPlugInClass parent_class; }; #define PSD_TYPE (psd_get_type ()) #define PSD(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PSD_TYPE, Psd)) GType psd_get_type (void) G_GNUC_CONST; static GList * psd_query_procedures (PikaPlugIn *plug_in); static PikaProcedure * psd_create_procedure (PikaPlugIn *plug_in, const gchar *name); static PikaValueArray * psd_load (PikaProcedure *procedure, PikaRunMode run_mode, GFile *file, PikaMetadata *metadata, PikaMetadataLoadFlags *flags, PikaProcedureConfig *config, gpointer run_data); static PikaValueArray * psd_load_thumb (PikaProcedure *procedure, GFile *file, gint size, PikaProcedureConfig *config, gpointer run_data); static PikaValueArray * psd_save (PikaProcedure *procedure, PikaRunMode run_mode, PikaImage *image, gint n_drawables, PikaDrawable **drawables, GFile *file, PikaMetadata *metadata, PikaProcedureConfig *config, gpointer run_data); static PikaValueArray * psd_load_metadata (PikaProcedure *procedure, PikaRunMode run_mode, GFile *file, PikaMetadata *metadata, PikaMetadataLoadFlags *flags, PikaProcedureConfig *config, gpointer run_data); G_DEFINE_TYPE (Psd, psd, PIKA_TYPE_PLUG_IN) PIKA_MAIN (PSD_TYPE) DEFINE_STD_SET_I18N static void psd_class_init (PsdClass *klass) { PikaPlugInClass *plug_in_class = PIKA_PLUG_IN_CLASS (klass); plug_in_class->query_procedures = psd_query_procedures; plug_in_class->create_procedure = psd_create_procedure; plug_in_class->set_i18n = STD_SET_I18N; } static void psd_init (Psd *psd) { } static GList * psd_query_procedures (PikaPlugIn *plug_in) { GList *list = NULL; list = g_list_append (list, g_strdup (LOAD_THUMB_PROC)); list = g_list_append (list, g_strdup (LOAD_PROC)); list = g_list_append (list, g_strdup (LOAD_MERGED_PROC)); list = g_list_append (list, g_strdup (SAVE_PROC)); list = g_list_append (list, g_strdup (LOAD_METADATA_PROC)); return list; } static PikaProcedure * psd_create_procedure (PikaPlugIn *plug_in, const gchar *name) { PikaProcedure *procedure = NULL; if (! strcmp (name, LOAD_PROC)) { procedure = pika_load_procedure_new (plug_in, name, PIKA_PDB_PROC_TYPE_PLUGIN, psd_load, NULL, NULL); pika_procedure_set_menu_label (procedure, _("Photoshop image")); pika_procedure_set_documentation (procedure, "Loads images from the Photoshop " "PSD and PSB file formats", "This plug-in loads images in Adobe " "Photoshop (TM) native PSD and PSB format.", name); pika_procedure_set_attribution (procedure, "John Marshall", "John Marshall", "2007"); pika_file_procedure_set_mime_types (PIKA_FILE_PROCEDURE (procedure), "image/x-psd"); pika_file_procedure_set_extensions (PIKA_FILE_PROCEDURE (procedure), "psd, psb"); pika_file_procedure_set_magics (PIKA_FILE_PROCEDURE (procedure), "0,string,8BPS"); pika_load_procedure_set_thumbnail_loader (PIKA_LOAD_PROCEDURE (procedure), LOAD_THUMB_PROC); } else if (! strcmp (name, LOAD_MERGED_PROC)) { procedure = pika_load_procedure_new (plug_in, name, PIKA_PDB_PROC_TYPE_PLUGIN, psd_load, NULL, NULL); pika_procedure_set_menu_label (procedure, _("Photoshop image (merged)")); pika_procedure_set_documentation (procedure, "Loads images from the Photoshop " "PSD and PSB file formats", "This plug-in loads the merged image " "data in Adobe Photoshop (TM) native " "PSD and PSB format.", name); pika_procedure_set_attribution (procedure, "Ell", "Ell", "2018"); pika_file_procedure_set_mime_types (PIKA_FILE_PROCEDURE (procedure), "image/x-psd"); pika_file_procedure_set_extensions (PIKA_FILE_PROCEDURE (procedure), "psd, psb"); pika_file_procedure_set_magics (PIKA_FILE_PROCEDURE (procedure), "0,string,8BPS"); pika_file_procedure_set_priority (PIKA_FILE_PROCEDURE (procedure), +1); pika_load_procedure_set_thumbnail_loader (PIKA_LOAD_PROCEDURE (procedure), LOAD_THUMB_PROC); } else if (! strcmp (name, LOAD_THUMB_PROC)) { procedure = pika_thumbnail_procedure_new (plug_in, name, PIKA_PDB_PROC_TYPE_PLUGIN, psd_load_thumb, NULL, NULL); pika_procedure_set_documentation (procedure, "Loads thumbnails from the " "Photoshop PSD file format", "This plug-in loads thumbnail images " "from Adobe Photoshop (TM) native " "PSD format files.", name); pika_procedure_set_attribution (procedure, "John Marshall", "John Marshall", "2007"); } else if (! strcmp (name, SAVE_PROC)) { procedure = pika_save_procedure_new (plug_in, name, PIKA_PDB_PROC_TYPE_PLUGIN, TRUE, psd_save, NULL, NULL); pika_procedure_set_image_types (procedure, "*"); pika_procedure_set_menu_label (procedure, _("Photoshop image")); pika_procedure_set_documentation (procedure, "Saves files in the Photoshop(tm) " "PSD file format", "This filter saves files of Adobe " "Photoshop(tm) native PSD format. " "These files may be of any image type " "supported by PIKA, with or without " "layers, layer masks, aux channels " "and guides.", name); pika_procedure_set_attribution (procedure, "Monigotes", "Monigotes", "2000"); pika_file_procedure_set_mime_types (PIKA_FILE_PROCEDURE (procedure), "image/x-psd"); pika_file_procedure_set_extensions (PIKA_FILE_PROCEDURE (procedure), "psd"); PIKA_PROC_ARG_BOOLEAN (procedure, "clippingpath", _("Assign a Clipping _Path"), _("Select a path to be the " "clipping path"), FALSE, G_PARAM_READWRITE); PIKA_PROC_ARG_STRING (procedure, "clippingpathname", _("Clipping Path _Name"), _("Clipping path name\n" "(ignored if no clipping path)"), NULL, G_PARAM_READWRITE); PIKA_PROC_ARG_DOUBLE (procedure, "clippingpathflatness", _("Path _Flatness"), _("Clipping path flatness in device pixels\n" "(ignored if no clipping path)"), 0.0, 100.0, 0.2, G_PARAM_READWRITE); PIKA_PROC_ARG_BOOLEAN (procedure, "cmyk", _("Export as _CMYK"), _("Export a CMYK PSD image using the soft-proofing color profile"), FALSE, G_PARAM_READWRITE); PIKA_PROC_ARG_BOOLEAN (procedure, "duotone", _("Export as _Duotone"), _("Export as a Duotone PSD file if Duotone color space information " "was attached to the image when originally imported."), FALSE, G_PARAM_READWRITE); } else if (! strcmp (name, LOAD_METADATA_PROC)) { procedure = pika_load_procedure_new (plug_in, name, PIKA_PDB_PROC_TYPE_PLUGIN, psd_load_metadata, NULL, NULL); pika_procedure_set_documentation (procedure, "Loads Photoshop-format metadata " "from other file formats.", "Loads Photoshop-format metadata " "from other file formats.", name); pika_procedure_set_attribution (procedure, "John Marshall", "John Marshall", "2007"); PIKA_PROC_ARG_INT (procedure, "size", "Metadata size", NULL, 0, G_MAXINT, 0, G_PARAM_READWRITE); pika_procedure_add_argument (procedure, pika_param_spec_image ("image", "image", "The image", FALSE, PIKA_PARAM_READWRITE)); PIKA_PROC_ARG_BOOLEAN (procedure, "metadata-type", "Metadata type", "If the metadata contains image or " "layer PSD resources.", FALSE, G_PARAM_READWRITE); PIKA_PROC_ARG_BOOLEAN (procedure, "cmyk", "CMYK", "If the layer metadata needs to be " "converted from CMYK colorspace.", FALSE, G_PARAM_READWRITE); } return procedure; } static PikaValueArray * psd_load (PikaProcedure *procedure, PikaRunMode run_mode, GFile *file, PikaMetadata *metadata, PikaMetadataLoadFlags *flags, PikaProcedureConfig *config, gpointer run_data) { PikaValueArray *return_vals; gboolean resolution_loaded = FALSE; gboolean profile_loaded = FALSE; PikaImage *image; PikaParasite *parasite = NULL; GError *error = NULL; PSDSupport unsupported_features; gegl_init (NULL, NULL); switch (run_mode) { case PIKA_RUN_INTERACTIVE: case PIKA_RUN_WITH_LAST_VALS: pika_ui_init (PLUG_IN_BINARY); break; default: break; } image = load_image (file, strcmp (pika_procedure_get_name (procedure), LOAD_MERGED_PROC) == 0, &resolution_loaded, &profile_loaded, &unsupported_features, &error); if (! image) return pika_procedure_new_return_values (procedure, PIKA_PDB_EXECUTION_ERROR, error); /* If image was Duotone, notify user of compatibility */ if (run_mode == PIKA_RUN_INTERACTIVE) { parasite = pika_image_get_parasite (image, PSD_PARASITE_DUOTONE_DATA); if (parasite) unsupported_features.duotone_mode = TRUE; if (unsupported_features.duotone_mode || unsupported_features.show_gui) load_dialog (_("Import PSD"), &unsupported_features); if (parasite) pika_parasite_free (parasite); } if (resolution_loaded) *flags &= ~PIKA_METADATA_LOAD_RESOLUTION; if (profile_loaded) *flags &= ~PIKA_METADATA_LOAD_COLORSPACE; return_vals = pika_procedure_new_return_values (procedure, PIKA_PDB_SUCCESS, NULL); PIKA_VALUES_SET_IMAGE (return_vals, 1, image); return return_vals; } static PikaValueArray * psd_load_thumb (PikaProcedure *procedure, GFile *file, gint size, PikaProcedureConfig *config, gpointer run_data) { PikaValueArray *return_vals; gint width = 0; gint height = 0; PikaImage *image; GError *error = NULL; gegl_init (NULL, NULL); image = load_thumbnail_image (file, &width, &height, &error); if (! image) return pika_procedure_new_return_values (procedure, PIKA_PDB_EXECUTION_ERROR, error); return_vals = pika_procedure_new_return_values (procedure, PIKA_PDB_SUCCESS, NULL); PIKA_VALUES_SET_IMAGE (return_vals, 1, image); PIKA_VALUES_SET_INT (return_vals, 2, width); PIKA_VALUES_SET_INT (return_vals, 3, height); pika_value_array_truncate (return_vals, 4); return return_vals; } static PikaValueArray * psd_save (PikaProcedure *procedure, PikaRunMode run_mode, PikaImage *image, gint n_drawables, PikaDrawable **drawables, GFile *file, PikaMetadata *metadata, PikaProcedureConfig *config, gpointer run_data) { PikaPDBStatusType status = PIKA_PDB_SUCCESS; PikaExportReturn export = PIKA_EXPORT_IGNORE; GError *error = NULL; gegl_init (NULL, NULL); switch (run_mode) { case PIKA_RUN_INTERACTIVE: case PIKA_RUN_WITH_LAST_VALS: pika_ui_init (PLUG_IN_BINARY); export = pika_export_image (&image, &n_drawables, &drawables, "PSD", PIKA_EXPORT_CAN_HANDLE_RGB | PIKA_EXPORT_CAN_HANDLE_GRAY | PIKA_EXPORT_CAN_HANDLE_INDEXED | PIKA_EXPORT_CAN_HANDLE_ALPHA | PIKA_EXPORT_CAN_HANDLE_LAYERS | PIKA_EXPORT_CAN_HANDLE_LAYER_MASKS); if (export == PIKA_EXPORT_CANCEL) return pika_procedure_new_return_values (procedure, PIKA_PDB_CANCEL, NULL); break; default: break; } if (run_mode == PIKA_RUN_INTERACTIVE) { if (! save_dialog (image, procedure, G_OBJECT (config))) return pika_procedure_new_return_values (procedure, PIKA_PDB_CANCEL, NULL); } if (save_image (file, image, G_OBJECT (config), &error)) { if (metadata) pika_metadata_set_bits_per_sample (metadata, 8); } else { status = PIKA_PDB_EXECUTION_ERROR; } if (export == PIKA_EXPORT_EXPORT) { pika_image_delete (image); g_free (drawables); } return pika_procedure_new_return_values (procedure, status, error); } static PikaValueArray * psd_load_metadata (PikaProcedure *procedure, PikaRunMode run_mode, GFile *file, PikaMetadata *metadata, PikaMetadataLoadFlags *flags, PikaProcedureConfig *config, gpointer run_data) { PikaValueArray *return_vals; PikaImage *image; gint data_length; PSDSupport unsupported_features; gboolean is_layer = FALSE; gboolean is_cmyk = FALSE; GError *error = NULL; gegl_init (NULL, NULL); /* Retrieve image */ g_object_get (config, "image", &image, "size", &data_length, "metadata-type", &is_layer, "cmyk", &is_cmyk, NULL); image = load_image_metadata (file, data_length, image, is_layer, is_cmyk, &unsupported_features, &error); /* Check for unsupported layers */ if (is_layer && unsupported_features.show_gui) { /* Metadata doesn't store rasterized versions of fill layers, * (unlike PSDs) so we can't display them for now. */ if (unsupported_features.fill_layer || unsupported_features.text_layer) { unsupported_features.psd_metadata = TRUE; unsupported_features.fill_layer = FALSE; unsupported_features.text_layer = FALSE; } switch (run_mode) { case PIKA_RUN_INTERACTIVE: pika_ui_init (PLUG_IN_BINARY); load_dialog (_("Import PSD metadata"), &unsupported_features); break; default: g_printerr ("[%s] %s\n", "file-psd-load-metadata", _("Metadata fill layers are not supported " "and will be dropped.")); break; } } if (! image) return pika_procedure_new_return_values (procedure, PIKA_PDB_EXECUTION_ERROR, error); return_vals = pika_procedure_new_return_values (procedure, PIKA_PDB_SUCCESS, NULL); PIKA_VALUES_SET_IMAGE (return_vals, 1, image); return return_vals; }