diff --git a/plug-ins/file-psd/TODO.txt b/plug-ins/file-psd/TODO.txt index f02ff74c98..9c5b69a401 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 7b63b92aee..07d791b02e 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 31ccab71b1..ab0454d8ce 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 2e408a9748..871a6767fb 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 1236faa4b6..8ac4cd3b82 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"