1
1
mirror of https://gitlab.gnome.org/GNOME/gimp.git synced 2025-10-06 01:12:40 +02:00

Compare commits

...

7 Commits

Author SHA1 Message Date
Jacob Boerema
e5cf2002d5 Add json_glib as dependency for the psd plug-in 2025-06-20 15:46:36 -04:00
Jacob Boerema
e40b4b0fc4 Load Drop Shadow and Solid Fill layer effects from descriptor 2025-06-20 15:21:44 -04:00
Jacob Boerema
18808497c5 More cleanup and start preparation for separating descriptor code 2025-06-18 16:29:10 -04:00
Jacob Boerema
ab08b19494 Improve storing as json for layer effects 2025-06-17 14:26:34 -04:00
Jacob Boerema
9d08851568 more json stuff 2025-06-15 14:14:27 -04:00
Jacob Boerema
3194d27396 WIP: Start implementing saving PSD descriptor data to Json 2025-06-15 14:14:26 -04:00
Jacob Boerema
241ec734e6 WIP adding ability to handle PSD text layers
So far only most of the reading of the PSD data related to text handling
is done.
We still need to convert relevant parts to markup text that GIMP can
handle. To do that, we probably need to store all keys and values we
read, and then when converting to markup check all keys that we can
use for their values.
2025-06-15 14:14:26 -04:00
4 changed files with 1935 additions and 18 deletions

View File

@@ -29,6 +29,7 @@ plugin_exe = executable(plugin_name,
plugin_sources,
c_args: '-DG_LOG_DOMAIN="file-psd"',
dependencies: [
json_glib,
libgimpui_dep,
libjpeg,
math,

File diff suppressed because it is too large Load Diff

View File

@@ -93,6 +93,9 @@ static gint add_layers (GimpImage *image,
static void add_legacy_layer_effects (GimpLayer *layer,
PSDlayer *lyr_a,
gboolean ibm_pc_format);
static void add_layer_effects (GimpLayer *layer,
PSDlayer *lyr_a,
gboolean ibm_pc_format);
static gint add_merged_image (GimpImage *image,
PSDimage *img_a,
@@ -817,6 +820,8 @@ read_layer_info (PSDimage *img_a,
/* Initialise record */
lyr_a[lidx]->id = 0;
lyr_a[lidx]->group_type = 0;
lyr_a[lidx]->text.info = NULL;
lyr_a[lidx]->layer_effects = NULL;
if (psd_read (input, &lyr_a[lidx]->top, 4, error) < 4 ||
psd_read (input, &lyr_a[lidx]->left, 4, error) < 4 ||
@@ -2471,9 +2476,43 @@ add_layers (GimpImage *image,
image_type = get_gimp_image_type (img_a->base_type, TRUE);
IFDBG(3) g_debug ("Layer type %d", image_type);
#if 1
layer = gimp_layer_new (image, lyr_a[lidx]->name,
l_w, l_h, image_type,
100, GIMP_LAYER_MODE_NORMAL);
#else
g_printerr ("Layer #%d\n ", lidx);
if (lyr_a[lidx]->text.info)
{
GimpTextLayer *textlayer;
GimpFont *font;
GimpUnit *unit;
/* We have a text layer! */
/* For testing purposes, just add the text with a fixed
font and size. */
g_printerr ("Text layer text: %s\n", lyr_a[lidx]->text.info);
font = gimp_font_get_by_name ("Tahoma");
unit = gimp_unit_get_by_id (GIMP_UNIT_POINT);
textlayer = gimp_text_layer_new (image, lyr_a[lidx]->text.info,
font, 30.0, unit);
if (! textlayer)
{
g_warning ("Failed to create text layer!\n");
}
else
{
/*gimp_image_insert_layer (image, GIMP_LAYER (textlayer), parent_group, 0);*/
}
layer = GIMP_LAYER (textlayer);
}
else
{
layer = gimp_layer_new (image, lyr_a[lidx]->name,
l_w, l_h, image_type,
100, GIMP_LAYER_MODE_NORMAL);
}
#endif
}
if (layer != NULL)
@@ -2734,7 +2773,10 @@ add_layers (GimpImage *image,
/* Add legacy layer styles if applicable.
* TODO: When we can load modern layer styles, only load these if
* the file doesn't have modern layer style data. */
if (lyr_a[lidx]->layer_styles->count > 0)
if (lyr_a[lidx]->layer_effects)
add_layer_effects (layer, lyr_a[lidx],
img_a->ibm_pc_format);
else if (lyr_a[lidx]->layer_styles->count > 0)
add_legacy_layer_effects (layer, lyr_a[lidx],
img_a->ibm_pc_format);
@@ -2809,6 +2851,8 @@ add_legacy_layer_effects (GimpLayer *layer,
dsdw = lyr_a->layer_styles->dsdw;
g_printerr ("Drop Shadow: legacy intensity value: %u\n", dsdw.intensity);
/* Photoshop uses an angle slider that goes from 0 to 180,
* then -179 to 0. Since GEGL uses X/Y coordinates for distance,
* we convert the Photoshop angle and distance and then flip the
@@ -2851,6 +2895,432 @@ add_legacy_layer_effects (GimpLayer *layer,
}
}
static gboolean
get_json_boolean (JsonReader *reader, gchar *key, gboolean default_value)
{
gboolean result = default_value;
if (json_reader_read_member (reader, key))
{
result = json_reader_get_boolean_value (reader);
}
json_reader_end_member (reader);
return result;
}
static gdouble
get_json_double (JsonReader *reader, gchar *key, gdouble default_value)
{
gdouble result = default_value;
if (json_reader_read_member (reader, key))
{
result = json_reader_get_double_value (reader);
}
json_reader_end_member (reader);
return result;
}
static const gchar *
get_json_string (JsonReader *reader, gchar *key, const gchar *default_value)
{
const gchar *result = default_value;
if (json_reader_read_member (reader, key))
{
result = json_reader_get_string_value (reader);
}
else
{
const GError *error = json_reader_get_error (reader);
g_printerr ("Unable to read the element: %s\n", error->message);
}
json_reader_end_member (reader);
return result;
}
static GeglColor *
get_json_color (JsonReader *reader, Babl *space)
{
GeglColor *color = gegl_color_new ("none");
/* Expected ClassID here is RGBC, but what if it is e.g. CMYK?
* Examples: see psd_tools/composite/vector.py
*/
if (json_reader_read_member (reader, "descriptor"))
{
gint cnt = json_reader_count_elements (reader);
gdouble pixel[4] = {0.0, 0.0, 0.0, 1.0};
gint i;
for (i = 0; i < cnt; i++)
{
if (json_reader_read_element (reader, i))
{
JsonReader *color_reader = json_reader_new (json_reader_get_current_node (reader));
const gchar *key = get_json_string (color_reader, "key", "");
if (json_string_equal (key, "Rd "))
pixel[0] = get_json_double (color_reader, "value", 0.0) / 255.0;
else if (json_string_equal (key, "Grn "))
pixel[1] = get_json_double (color_reader, "value", 0.0) / 255.0;
else if (json_string_equal (key, "Bl "))
pixel[2] = get_json_double (color_reader, "value", 0.0) / 255.0;
g_object_unref (color_reader);
}
json_reader_end_element (reader);
}
pixel[3] = 1.0; /* I guess PS doesn't set an alpha channel? */
g_debug ("Pixel values: R: %f, G: %f, B: %f\n", pixel[0], pixel[1], pixel[2]);
/* This is not specified in the documentation, but based on sample files,
* the color is assumed to be in the drawable's color space. */
/* FIXME: Since this is double, should we set a linear space? */
gegl_color_set_pixel (color, babl_format_with_space ("R'G'B' double", space),
&pixel);
}
json_reader_end_member (reader);
return color;
}
static void
json_read_dropshadow (JsonReader *reader, const Babl *space, GimpLayer *layer)
{
gboolean enabled = FALSE;
gboolean present = TRUE; /* Not always present in descriptor*/
const gchar *mode = NULL;
GeglColor *color = NULL;
/* FIXME: Figure out below what the real default values are in Photoshop */
gdouble opacity = 100.0;
gdouble angle = 90.0;
gdouble distance = 18.0;
gdouble intensity = 0.0;
gdouble blur = 40.0;
if (json_reader_read_member (reader, "descriptor"))
{
gint cnt = json_reader_count_elements (reader);
gint i;
g_debug ("Parse dropshadow (%d elements)...\n", cnt);
for (i = 0; i < cnt; i++)
{
if (json_reader_read_element (reader, i))
{
/* Since each element is an object, we need to init a new reader. */
JsonReader *obj_reader = json_reader_new (json_reader_get_current_node (reader));
const gchar *key = get_json_string (obj_reader, "key", "");
g_debug ("Index %d - Member key: %s.\n", i, key);
/* For documentation purposes I also list the members we currently do not
* interpret:
* - "showInDialog", boolean. Seems to be always true.
* - "uglg"", boolean (true)
* - "Ckmt", float, pxl (0.0)
* - "AntA", boolean (false). Seems to mean anti-alias.
* - "TrnS", descriptor with several members, including curves.
* - "layerConceals" - boolean.
*/
if (json_string_equal (key, "enab"))
enabled = get_json_boolean (obj_reader, "value", FALSE);
else if (json_string_equal (key, "present"))
present = get_json_boolean (obj_reader, "value", TRUE);
else if (json_string_equal (key, "Md "))
mode = get_json_string (obj_reader, "value", "Nrml");
else if (json_string_equal (key, "Clr "))
color = get_json_color (obj_reader, space);
else if (json_string_equal (key, "Opct"))
opacity = (gfloat) get_json_double (obj_reader, "value", 100.0);
else if (json_string_equal (key, "lagl"))
angle = (gfloat) get_json_double (obj_reader, "value", 90.0);
else if (json_string_equal (key, "Dstn"))
distance = (gfloat) get_json_double (obj_reader, "value", 18.0);
else if (json_string_equal (key, "Nose"))
/* Uncertain which member intensity, is it Nose, or Ckmt?.
* Since the legacy value is percent, I chose Nose. */
intensity = (gfloat) get_json_double (obj_reader, "value", 0.0);
else if (json_string_equal (key, "blur"))
blur = (gfloat) get_json_double (obj_reader, "value", 40.0);
g_object_unref (obj_reader);
}
else
{
const GError *error = json_reader_get_error (reader);
g_printerr ("Array index %d. Unable to read the element: %s\n", i, error->message);
}
json_reader_end_element (reader);
}
if (present && enabled)
{
GimpDrawableFilter *filter;
gdouble x;
gdouble y;
gdouble gegl_blur;
gdouble radians;
radians = (M_PI / 180) * angle;
x = distance * cos (radians);
y = distance * sin (radians);
/* FIXME: Do we need this check done for the legacy filter? */
/*if (angle >= 0xFF00)
angle = (angle - 0xFF00) * -1;*/
g_debug ("Dropshadow angle: %f\n", angle);
if (angle > 90.0 && angle < 180.0)
y *= -1;
else if (angle < -90.0 && angle > -180.0)
x *= -1;
gegl_blur = (blur / 250.0) * 100.0;
/* FIXME Besides certain modes not working for Dropshadow in GIMP,
* an additional issue is that the mode names in the descriptor
* are different than elsewhere in PSD's, so we need a new
* conversion function.
*/
/*convert_psd_mode (dsdw.blendsig, &mode);*/
g_printerr ("Drop Shadow: setting layer mode to Replace instead of %.4s.\n", mode);
/* Nose (intensity?) is not used in GEGL. */
g_debug ("Drop Shadow: Nose value: %f\n", intensity);
if (! color)
{
color = gegl_color_new ("none");
g_printerr ("WARNING: Color not initialized!\n");
}
filter = gimp_drawable_append_new_filter (GIMP_DRAWABLE (layer),
"gegl:dropshadow",
NULL,
GIMP_LAYER_MODE_REPLACE,
1.0,
"x", x,
"y", y,
"radius", gegl_blur,
"color", color,
"opacity", opacity / 100.0,
NULL);
g_object_unref (filter);
}
g_object_unref (color);
}
else
{
g_printerr ("No descriptor found!\n");
}
json_reader_end_member (reader);
}
static void
json_read_solidfill (JsonReader *reader, const Babl *space, GimpLayer *layer)
{
gboolean enabled = FALSE;
gboolean present = TRUE; /* Not always present in descriptor*/
const gchar *mode = NULL;
GeglColor *color = NULL;
/* FIXME: Figure out below what the real default values are in Photoshop */
gdouble opacity = 100.0;
if (json_reader_read_member (reader, "descriptor"))
{
gint cnt = json_reader_count_elements (reader);
gint i;
g_debug ("Parse Solid Fill (%d elements)...\n", cnt);
for (i = 0; i < cnt; i++)
{
if (json_reader_read_element (reader, i))
{
/* Since each element is an object, we need to init a new reader. */
JsonReader *obj_reader = json_reader_new (json_reader_get_current_node (reader));
const gchar *key = get_json_string (obj_reader, "key", "");
g_debug ("Index %d - Member key: %s.\n", i, key);
/* For documentation purposes I also list the members we currently do not
* interpret:
* - "showInDialog", boolean. Seems to be always true.
*/
if (json_string_equal (key, "enab"))
enabled = get_json_boolean (obj_reader, "value", FALSE);
else if (json_string_equal (key, "present"))
present = get_json_boolean (obj_reader, "value", TRUE);
else if (json_string_equal (key, "Md "))
mode = get_json_string (obj_reader, "value", "Nrml");
else if (json_string_equal (key, "Clr "))
color = get_json_color (obj_reader, space);
else if (json_string_equal (key, "Opct"))
opacity = (gfloat) get_json_double (obj_reader, "value", 100.0);
g_object_unref (obj_reader);
}
else
{
const GError *error = json_reader_get_error (reader);
g_printerr ("Array index %d. Unable to read the element: %s\n", i, error->message);
}
json_reader_end_element (reader);
}
if (present && enabled)
{
GimpDrawableFilter *filter;
GimpLayerMode layer_mode;
/* FIXME The blend mode names in the descriptor
* are different than elsewhere in PSD's, so we need a new
* conversion function.
*/
/*convert_psd_mode (mode, &layer_mode);*/
g_printerr ("Solid Fill: setting layer mode to Normal instead of %.4s.\n", mode);
layer_mode = GIMP_LAYER_MODE_NORMAL;
if (! color)
{
color = gegl_color_new ("none");
g_printerr ("WARNING: Color not initialized!\n");
}
filter = gimp_drawable_append_new_filter (GIMP_DRAWABLE (layer),
"gegl:color-overlay",
NULL,
layer_mode,
opacity / 100.0,
"value", color,
NULL);
g_object_unref (filter);
}
g_object_unref (color);
}
else
{
g_printerr ("No descriptor found!\n");
}
json_reader_end_member (reader);
}
static void
add_layer_effects (GimpLayer *layer,
PSDlayer *lyr_a,
gboolean ibm_pc_format)
{
const Babl *format = gimp_drawable_get_format (GIMP_DRAWABLE (layer));
const Babl *space = babl_format_get_space (format);
if (lyr_a->layer_effects && lyr_a->layer_effects->effects)
{
JsonReader *root_reader = NULL;
root_reader = json_reader_new (lyr_a->layer_effects->effects);
if (json_reader_read_member (root_reader, "descriptor"))
{
gint cnt = json_reader_count_elements (root_reader);
gint i;
g_debug ("Descriptor has %d elelements.\n", cnt);
for (i = cnt-1; i >=0; i--)
{
if (json_reader_read_element (root_reader, i))
{
JsonReader *reader = json_reader_new (json_reader_get_current_node (root_reader));
/* We read classID here instead of key, because I have seen cases
* where e.g. key is "innerShadowMulti" while classID is "IrSh".
* (The multi versions seem to be cases where more than one of
* that same effect can be defined. The trouble is, that in that
* case the classID can be (but not always is) one layer deeper
* in a list.)
* For each of the effects there are "enab" and "present" keys.
* I will assume for now that "enab" means visible, and
* "present" means the effect is used, but this needs confirmation.
* Apparently present is not always included, so maybe I'm wrong.
* For now I'll set it to TRUE by default.
*/
if (json_reader_read_member (reader, "classID"))
{
const gchar *str = json_reader_get_string_value (reader);
json_reader_end_member (reader);
g_debug ("Member classID: %s.\n", str);
if (json_string_equal (str, "SoFi"))
{
/* Read Solid Fill settings */
json_read_solidfill (reader, space, layer);
}
else if (json_string_equal (str, "DrSh"))
{
/* Read DropShadow settings */
json_read_dropshadow (reader, space, layer);
}
else if (json_string_equal (str, "IrSh"))
{
/* Read InnerShadow settings */
}
else if (json_string_equal (str, "OrGl"))
{
/* Read OuterGlow settings */
}
else if (json_string_equal (str, "IrGl"))
{
/* Read InnerGlow settings */
}
else if (json_string_equal (str, "GrFl"))
{
/* Read GradientFill settings */
}
else if (json_string_equal (str, "ebbl"))
{
/* Read (bevel & emboss?) settings */
}
else if (json_string_equal (str, "patternFill"))
{
/* Read patternFill settings */
}
else if (json_string_equal (str, "FrFX"))
{
/* Read FrameFX settings */
}
else if (json_string_equal (str, "ChFX"))
{
/* Read ChFX settings */
}
}
else
{
json_reader_end_member (reader);
}
g_object_unref (reader);
}
json_reader_end_element (root_reader);
}
}
json_reader_end_member (root_reader);
g_object_unref (root_reader);
}
}
static gint
add_merged_image (GimpImage *image,
PSDimage *img_a,

View File

@@ -21,6 +21,8 @@
#ifndef __PSD_H__
#define __PSD_H__
#include <json-glib/json-glib.h>
/* Set to the level of debugging output you want, 0 for none.
* Setting higher than 2 will result in a very large amount of debug
@@ -128,6 +130,8 @@
#define PSD_LOTH_VECTOR_STROKE "vscg" /* Vector stroke data (PS13) */
#define PSD_LOTH_ALIGN_RENDER "sn2P" /* Aligned rendering flag (?) */
#define PSD_LOTH_USER_MASK "LMsk" /* User mask (?) */
#define PSD_LOTH_COMPOSITOR "cinf" /* Compositor Used (Photoshop 2020) */
/* Effects layer resource IDs */
#define PSD_LFX_COMMON "cmnS" /* Effects layer - common state (PS5) */
@@ -596,8 +600,15 @@ typedef struct
guint32 intensity;
gint32 angle;
guint32 distance;
/*
gfloat blur;
gfloat intensity;
gfloat angle;
gfloat distance;
*/
guint16 color[5];
gchar blendsig[4];
/* guint32 effect;*/
guchar effecton;
guchar anglefx;
guchar opacity;
@@ -670,6 +681,12 @@ typedef struct
PSDLayerStyleSolidFill sofi; /* Solid Fill */
} PSDLayerStyles;
typedef struct
{
JsonNode *effects;
} PSDLayerEffects;
/* Partially or Unsupported Features */
typedef struct
{
@@ -715,6 +732,7 @@ typedef struct
guchar group_type; /* 0 -> not a group; 1 -> open folder; 2 -> closed folder; 3 -> end of group */
guint16 color_tag[4]; /* 4 * 16 bit color components */
PSDLayerStyles *layer_styles; /* Older format of layer styles */
PSDLayerEffects *layer_effects;
PSDSupport *unsupported_features;
} PSDlayer;