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.
This commit is contained in:
Alx Sa 2022-10-28 18:01:48 +00:00
parent ec60d376a5
commit fc202818dd
5 changed files with 331 additions and 13 deletions

View File

@ -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.

View File

@ -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;
}

View File

@ -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, &parasite_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, &parasite_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;
}
}
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);
}
}

View File

@ -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);
}

View File

@ -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"