From ec60d376a5c2245aa05eb4e653e0ea674018979f Mon Sep 17 00:00:00 2001 From: Alx Sa Date: Wed, 28 Dec 2022 07:59:15 -0500 Subject: [PATCH 1/2] plug-ins: Create struct for PSD save options Allows more save options to be added without changing the method signatures. --- plug-ins/file-psd/psd-save.c | 58 +++++++++++++++++++----------------- 1 file changed, 31 insertions(+), 27 deletions(-) diff --git a/plug-ins/file-psd/psd-save.c b/plug-ins/file-psd/psd-save.c index 0392b233f3d..31ccab71b1b 100644 --- a/plug-ins/file-psd/psd-save.c +++ b/plug-ins/file-psd/psd-save.c @@ -121,6 +121,12 @@ typedef struct PsdImageData GList *lLayers; /* List of PSD_Layer */ } PSD_Image_Data; +typedef struct PsdResourceOptions +{ + gboolean cmyk; + gboolean duotone; +} PSD_Resource_Options; + static PSD_Image_Data PSDImageData; /* Declare some local functions. @@ -142,8 +148,8 @@ static void save_color_mode_data (GOutputStream *output, static void save_resources (GOutputStream *output, GimpImage *image, - gboolean export_cmyk, - gboolean export_duotone); + PSD_Resource_Options + *options); static void save_paths (GOutputStream *output, GimpImage *image); @@ -658,10 +664,9 @@ save_color_mode_data (GOutputStream *output, } static void -save_resources (GOutputStream *output, - GimpImage *image, - gboolean export_cmyk, - gboolean export_duotone) +save_resources (GOutputStream *output, + GimpImage *image, + PSD_Resource_Options *options) { GList *iter; gint i; @@ -695,7 +700,7 @@ save_resources (GOutputStream *output, /* --------------- Write Channel names --------------- */ - if (! export_cmyk) + if (! options->cmyk) { if (PSDImageData.nChannels > 0 || gimp_drawable_has_alpha (GIMP_DRAWABLE (PSDImageData.merged_layer))) @@ -942,10 +947,10 @@ save_resources (GOutputStream *output, { GimpColorProfile *profile = NULL; - if (! export_duotone) + if (! options->duotone) profile = gimp_image_get_effective_color_profile (image); - if (export_cmyk) + if (options->cmyk) { profile = gimp_image_get_simulation_profile (image); @@ -2038,23 +2043,22 @@ save_image (GFile *file, GObject *config, GError **error) { - GOutputStream *output; - GeglBuffer *buffer; - GList *iter; - GError *local_error = NULL; - GimpParasite *parasite = NULL; - gboolean config_cmyk; - gboolean config_duotone; + GOutputStream *output; + GeglBuffer *buffer; + GList *iter; + GError *local_error = NULL; + GimpParasite *parasite = NULL; + PSD_Resource_Options resource_options; g_object_get (config, - "cmyk", &config_cmyk, - "duotone", &config_duotone, + "cmyk", &resource_options.cmyk, + "duotone", &resource_options.duotone, NULL); IFDBG(1) g_debug ("Function: save_image"); - if (config_cmyk) - config_duotone = FALSE; + if (resource_options.cmyk) + resource_options.duotone = FALSE; if (gimp_image_get_width (image) > 30000 || gimp_image_get_height (image) > 30000) @@ -2077,13 +2081,13 @@ save_image (GFile *file, if (parasite) { if (gimp_image_get_base_type (image) != GIMP_GRAY) - config_duotone = FALSE; + resource_options.duotone = FALSE; gimp_parasite_free (parasite); } else { - config_duotone = FALSE; + resource_options.duotone = FALSE; } get_image_data (image); @@ -2137,20 +2141,20 @@ save_image (GFile *file, IFDBG(1) g_debug ("\tFile '%s' has been opened", gimp_file_get_utf8_name (file)); - save_header (output, image, config_cmyk, config_duotone); - save_color_mode_data (output, image, config_duotone); - save_resources (output, image, config_cmyk, config_duotone); + save_header (output, image, resource_options.cmyk, resource_options.duotone); + save_color_mode_data (output, image, resource_options.duotone); + save_resources (output, image, &resource_options); /* PSD format does not support layers in indexed images */ if (PSDImageData.baseType == GIMP_INDEXED) write_gint32 (output, 0, "layers info section length"); else - save_layer_and_mask (output, image, config_cmyk); + save_layer_and_mask (output, image, resource_options.cmyk); /* If this is an indexed image, write now channel and layer info */ - save_data (output, image, config_cmyk); + save_data (output, image, resource_options.cmyk); /* Delete merged image now */ -- GitLab From fc202818dd5d9e793464e4421f9952f99777de10 Mon Sep 17 00:00:00 2001 From: Alx Sa Date: Fri, 28 Oct 2022 18:01:48 +0000 Subject: [PATCH 2/2] plug-ins: Add support for PSD clipping paths Adds import and export support for clipping paths in PSD files. On import, path name and flatness value are saved in parasites. Prior settings are loaded in the GUI on export. --- plug-ins/file-psd/TODO.txt | 5 +- plug-ins/file-psd/psd-image-res-load.c | 65 +++++++ plug-ins/file-psd/psd-save.c | 241 ++++++++++++++++++++++++- plug-ins/file-psd/psd.c | 31 +++- plug-ins/file-psd/psd.h | 2 + 5 files changed, 331 insertions(+), 13 deletions(-) diff --git a/plug-ins/file-psd/TODO.txt b/plug-ins/file-psd/TODO.txt index f02ff74c981..9c5b69a4017 100644 --- a/plug-ins/file-psd/TODO.txt +++ b/plug-ins/file-psd/TODO.txt @@ -1,6 +1,6 @@ Load ==== -Photoshop 2.0 and lower files are not supported due to lack of +Photoshop 2.0 and lower files are not supported due to lack of file specs and test files. Add text names for color modes @@ -49,6 +49,3 @@ Image resources: 2000-2998 - Paths Add initial fill rule and clipboard parasites. - -2999 - Clipping path - Add as parasite to path record. diff --git a/plug-ins/file-psd/psd-image-res-load.c b/plug-ins/file-psd/psd-image-res-load.c index 7b63b92aee7..07d791b02ec 100644 --- a/plug-ins/file-psd/psd-image-res-load.c +++ b/plug-ins/file-psd/psd-image-res-load.c @@ -243,6 +243,11 @@ static gint load_resource_2000 (const PSDimageres *res_a, GInputStream *input, GError **error); +static gint load_resource_2999 (const PSDimageres *res_a, + GimpImage *image, + GInputStream *input, + GError **error); + /* Public Functions */ gint get_image_resource_header (PSDimageres *res_a, @@ -410,6 +415,10 @@ load_image_resource (PSDimageres *res_a, load_resource_1077 (res_a, image, img_a, input, error); break; + case PSD_CLIPPING_PATH: + load_resource_2999 (res_a, image, input, error); + break; + default: if (res_a->id >= 2000 && res_a->id < 2999) @@ -1685,3 +1694,59 @@ load_resource_2000 (const PSDimageres *res_a, return 0; } + +static gint +load_resource_2999 (const PSDimageres *res_a, + GimpImage *image, + GInputStream *input, + GError **error) +{ + gchar *path_name; + gint16 path_flatness_int; + gint16 path_flatness_fixed; + gfloat path_flatness; + GimpParasite *parasite; + gint32 read_len; + gint32 write_len; + + path_name = fread_pascal_string (&read_len, &write_len, 2, input, error); + if (*error) + return -1; + + /* Convert from fixed to floating point */ + if (psd_read (input, &path_flatness_int, 1, error) < 1 || + psd_read (input, &path_flatness_fixed, 1, error) < 1) + { + psd_set_error (error); + return -1; + } + path_flatness_fixed = GINT16_FROM_BE (path_flatness_fixed); + /* Converting from Adobe fixed point value to float */ + path_flatness = (path_flatness_fixed - 0.5f) / 65536.0; + path_flatness += path_flatness_int; + + /* Adobe path flatness range is 0.2 to 100.0 */ + path_flatness = CLAMP (path_flatness, 0.2f, 100.0f); + + /* Save to image parasite */ + parasite = gimp_parasite_new (PSD_PARASITE_CLIPPING_PATH, 0, + read_len, path_name); + gimp_image_attach_parasite (image, parasite); + gimp_parasite_free (parasite); + + parasite = gimp_parasite_new (PSD_PARASITE_PATH_FLATNESS, 0, + sizeof (gfloat), &path_flatness); + gimp_image_attach_parasite (image, parasite); + gimp_parasite_free (parasite); + + /* Adobe says they ignore the last two bytes, the fill rule */ + if (psd_read (input, &path_flatness_fixed, 1, error) < 1) + { + psd_set_error (error); + return -1; + } + + g_free (path_name); + + return 0; +} diff --git a/plug-ins/file-psd/psd-save.c b/plug-ins/file-psd/psd-save.c index 31ccab71b1b..ab0454d8ce8 100644 --- a/plug-ins/file-psd/psd-save.c +++ b/plug-ins/file-psd/psd-save.c @@ -125,6 +125,9 @@ typedef struct PsdResourceOptions { gboolean cmyk; gboolean duotone; + gboolean clipping_path; + gchar *clipping_path_name; + gdouble clipping_path_flatness; } PSD_Resource_Options; static PSD_Image_Data PSDImageData; @@ -153,6 +156,10 @@ static void save_resources (GOutputStream *output, static void save_paths (GOutputStream *output, GimpImage *image); +static void save_clipping_path (GOutputStream *output, + GimpImage *image, + const gchar *path_name, + gfloat path_flatness); static void save_layer_and_mask (GOutputStream *output, GimpImage *image, @@ -214,6 +221,10 @@ static const Babl * get_mask_format (GimpLayerMask *mask); static GList * image_get_all_layers (GimpImage *image, gint *n_layers); +static void update_clipping_path + (GimpIntComboBox *combo, + gpointer data); + static const gchar * psd_lmode_layer (GimpLayer *layer, gboolean section_divider) @@ -858,6 +869,28 @@ save_resources (GOutputStream *output, /* --------------- Write paths ------------------- */ save_paths (output, image); + if (options->clipping_path) + { + GimpParasite *parasite; + + save_clipping_path (output, image, + options->clipping_path_name, + options->clipping_path_flatness); + + /* Update parasites */ + parasite = gimp_parasite_new (PSD_PARASITE_CLIPPING_PATH, 0, + strlen (options->clipping_path_name) + 1, + options->clipping_path_name); + gimp_image_attach_parasite (image, parasite); + gimp_parasite_free (parasite); + + parasite = gimp_parasite_new (PSD_PARASITE_PATH_FLATNESS, 0, + sizeof (gfloat), + (gpointer) &options->clipping_path_flatness); + gimp_image_attach_parasite (image, parasite); + gimp_parasite_free (parasite); + } + /* --------------- Write resolution data ------------------- */ { gdouble xres = 0, yres = 0; @@ -1243,6 +1276,55 @@ save_paths (GOutputStream *output, g_list_free (vectors); } +static void +save_clipping_path (GOutputStream *output, + GimpImage *image, + const gchar *path_name, + gfloat path_flatness) +{ + gshort id = 0x0BB7; + gsize len; + GString *data; + gchar *tmpname; + gchar flatness[4]; + GList *paths; + GError *err = NULL; + + paths = gimp_image_list_vectors (image); + + if (! paths) + return; + + data = g_string_new ("8BIM"); + g_string_append_c (data, id / 256); + g_string_append_c (data, id % 256); + + tmpname = g_convert (path_name, -1, "iso8859-1", "utf-8", NULL, &len, &err); + + g_string_append_len (data, "\x00\x00\x00\x00", 4); + if ((len + 6 + 1) <= 255) + g_string_append_len (data, "\x00", 1); + g_string_append_c (data, len + 6 + 1); + + g_string_append_c (data, MIN (len, 255)); + g_string_append_len (data, tmpname, MIN (len, 255)); + g_free (tmpname); + + if (data->len % 2) /* padding to even size */ + g_string_append_c (data, 0); + + double_to_psd_fixed (path_flatness, flatness); + g_string_append_len (data, flatness, 4); + + /* Adobe specifications state they ignore the fill rule, + * but we'll write it anyway. + */ + g_string_append_len (data, "\x00\x01", 2); + + xfwrite (output, data->str, data->len, "clipping path resources data"); + g_string_free (data, TRUE); +} + static void save_layer_and_mask (GOutputStream *output, GimpImage *image, @@ -2051,8 +2133,11 @@ save_image (GFile *file, PSD_Resource_Options resource_options; g_object_get (config, - "cmyk", &resource_options.cmyk, - "duotone", &resource_options.duotone, + "cmyk", &resource_options.cmyk, + "duotone", &resource_options.duotone, + "clippingpath", &resource_options.clipping_path, + "clippingpathname", &resource_options.clipping_path_name, + "clippingpathflatness", &resource_options.clipping_path_flatness, NULL); IFDBG(1) g_debug ("Function: save_image"); @@ -2165,6 +2250,7 @@ save_image (GFile *file, IFDBG(1) g_debug ("----- Closing PSD file, done -----\n"); g_object_unref (output); + g_free (resource_options.clipping_path_name); gimp_progress_update (1.0); @@ -2350,13 +2436,14 @@ save_dialog (GimpImage *image, GimpColorProfile *cmyk_profile; GimpParasite *parasite = NULL; gboolean has_duotone_data = FALSE; + GList *paths; gboolean run; dialog = gimp_procedure_dialog_new (procedure, GIMP_PROCEDURE_CONFIG (config), _("Export Image as PSD")); - /* CMYK profile label. */ + /* CMYK profile label */ profile_label = gimp_procedure_dialog_get_label (GIMP_PROCEDURE_DIALOG (dialog), "profile-label", _("No soft-proofing profile")); gtk_label_set_xalign (GTK_LABEL (profile_label), 0.0); @@ -2426,6 +2513,137 @@ save_dialog (GimpImage *image, gimp_parasite_free (parasite); } + /* Clipping Path */ + paths = gimp_image_list_vectors (image); + if (paths) + { + GtkWidget *vbox; + GtkWidget *frame; + GtkWidget *entry; + GtkWidget *combo; + GtkWidget *label; + GList *list; + GtkTreeModel *model; + GtkTreeIter iter; + gint v; + gint saved_id = -1; + gchar *path_name = NULL; + + parasite = gimp_image_get_parasite (image, PSD_PARASITE_CLIPPING_PATH); + if (parasite) + { + guint32 parasite_size; + + path_name = (gchar *) gimp_parasite_get_data (parasite, ¶site_size); + path_name = g_strndup (path_name, parasite_size); + + gimp_parasite_free (parasite); + } + else + { + /* Uncheck clipping path if no parasite data saved */ + g_object_set (config, + "clippingpath", FALSE, + NULL); + } + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); + combo = gimp_vectors_combo_box_new (NULL, NULL, NULL); + + /* Alert user if they have more than 998 paths */ + if (g_list_length (paths) > 998) + { + label = gimp_procedure_dialog_get_label (GIMP_PROCEDURE_DIALOG (dialog), + "path-warning", + _("PSD files can store up to " + "998 paths. \nThe rest " + "will be discarded.")); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END); + gimp_label_set_attributes (GTK_LABEL (label), + PANGO_ATTR_STYLE, PANGO_STYLE_ITALIC, + -1); + gimp_procedure_dialog_fill (GIMP_PROCEDURE_DIALOG (dialog), + "path-warning", + NULL); + gtk_widget_show (label); + } + + /* Fixing labels */ + model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo)); + gtk_list_store_clear (GTK_LIST_STORE (model)); + for (list = paths, v = 0; + list && v <= 997; + list = g_list_next (list), v++) + { + gtk_list_store_append (GTK_LIST_STORE (model), &iter); + gtk_list_store_set (GTK_LIST_STORE (model), &iter, + GIMP_INT_STORE_VALUE, gimp_item_get_id (list->data), + GIMP_INT_STORE_LABEL, gimp_item_get_name (list->data), + GIMP_INT_STORE_PIXBUF, NULL, + GIMP_INT_STORE_USER_DATA, gimp_item_get_name (list->data), + -1); + + if (! g_strcmp0 (gimp_item_get_name (list->data), + path_name)) + saved_id = gimp_item_get_id (list->data); + } + + if (saved_id != -1) + gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (combo), + saved_id); + + gimp_int_combo_box_connect (GIMP_INT_COMBO_BOX (combo), + 0, + G_CALLBACK (update_clipping_path), + config, NULL); + gtk_widget_show (combo); + + entry = gimp_procedure_dialog_get_spin_scale (GIMP_PROCEDURE_DIALOG (dialog), + "clippingpathflatness", 1.0); + + parasite = gimp_image_get_parasite (image, PSD_PARASITE_PATH_FLATNESS); + if (parasite) + { + gfloat *path_flatness = NULL; + guint32 parasite_size; + + path_flatness = (gfloat *) gimp_parasite_get_data (parasite, ¶site_size); + if (path_flatness && *path_flatness > 0) + { + gtk_spin_button_set_value (GTK_SPIN_BUTTON (entry), *path_flatness); + g_object_set (config, + "clippingpathflatness", *path_flatness, + NULL); + } + + gimp_parasite_free (parasite); + } + + gtk_widget_show (entry); + + gimp_procedure_dialog_fill_frame (GIMP_PROCEDURE_DIALOG (dialog), + "clipping-path-frame", "clippingpath", FALSE, + NULL); + frame = gimp_procedure_dialog_fill_frame (GIMP_PROCEDURE_DIALOG (dialog), + "clipping-path-subframe", NULL, FALSE, + NULL); + + gtk_container_add (GTK_CONTAINER (frame), vbox); + gtk_box_pack_start (GTK_BOX (vbox), combo, FALSE, FALSE, 0); + gtk_box_pack_start (GTK_BOX (vbox), entry, FALSE, FALSE, 0); + gtk_widget_show (vbox); + + gimp_procedure_dialog_fill (GIMP_PROCEDURE_DIALOG (dialog), + "clipping-path-frame", + "clipping-path-subframe", + NULL); + + gimp_procedure_dialog_set_sensitive (GIMP_PROCEDURE_DIALOG (dialog), + "clipping-path-subframe", + TRUE, config, "clippingpath", FALSE); + } + if (has_duotone_data) gimp_procedure_dialog_fill (GIMP_PROCEDURE_DIALOG (dialog), "cmyk-frame", @@ -2443,4 +2661,19 @@ save_dialog (GimpImage *image, gtk_widget_destroy (dialog); return run; -} \ No newline at end of file +} + +static void update_clipping_path (GimpIntComboBox *combo, + gpointer data) +{ + gpointer value; + + if (gimp_int_combo_box_get_active_user_data (GIMP_INT_COMBO_BOX (combo), + &value)) + { + GObject *config = G_OBJECT (data); + g_object_set (config, + "clippingpathname", (gchar *) value, + NULL); + } +} diff --git a/plug-ins/file-psd/psd.c b/plug-ins/file-psd/psd.c index 2e408a9748e..871a6767fb1 100644 --- a/plug-ins/file-psd/psd.c +++ b/plug-ins/file-psd/psd.c @@ -224,16 +224,37 @@ psd_create_procedure (GimpPlugIn *plug_in, gimp_file_procedure_set_extensions (GIMP_FILE_PROCEDURE (procedure), "psd"); + GIMP_PROC_ARG_BOOLEAN (procedure, "clippingpath", + _("Assign a Clipping _Path"), + _("Select a path to be the " + "clipping path"), + FALSE, + G_PARAM_READWRITE); + + GIMP_PROC_ARG_STRING (procedure, "clippingpathname", + _("Clipping Path _Name"), + _("Clipping path name\n" + "(ignored if no clipping path)"), + NULL, + G_PARAM_READWRITE); + + GIMP_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); + GIMP_PROC_ARG_BOOLEAN (procedure, "cmyk", - "Export as _CMYK", - "Export a CMYK PSD image using the soft-proofing color profile", + _("Export as _CMYK"), + _("Export a CMYK PSD image using the soft-proofing color profile"), FALSE, G_PARAM_READWRITE); GIMP_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.", + _("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); } diff --git a/plug-ins/file-psd/psd.h b/plug-ins/file-psd/psd.h index 1236faa4b69..8ac4cd3b82d 100644 --- a/plug-ins/file-psd/psd.h +++ b/plug-ins/file-psd/psd.h @@ -41,6 +41,8 @@ #define GIMP_PARASITE_COMMENT "gimp-comment" #define PSD_PARASITE_DUOTONE_DATA "psd-duotone-data" +#define PSD_PARASITE_CLIPPING_PATH "psd-clipping-path" +#define PSD_PARASITE_PATH_FLATNESS "psd-path-flatness" /* Copied from app/base/gimpimage-quick-mask.h - internal identifier for quick mask channel */ #define GIMP_IMAGE_QUICK_MASK_NAME "Qmask" -- GitLab