plug-ins: Add export support for PSB images

Photoshop's PSB format is nearly the same
as PSD. The main differences are that it
allows you to save images up to
300,000 by 300,000 pixels, and it doubles
the size of pointers related to pixel data
(such as layers and channels).
This commit is contained in:
Alx Sa 2025-06-16 18:57:22 +00:00
parent 1b9c78dc12
commit f94b43ecbf
4 changed files with 276 additions and 118 deletions

View file

@ -123,12 +123,13 @@ typedef struct PsdImageData
typedef struct PsdResourceOptions
{
gboolean psb;
gboolean cmyk;
gboolean duotone;
gboolean clipping_path;
gchar *clipping_path_name;
gdouble clipping_path_flatness;
} PSD_Resource_Options;
} PSDResourceOptions;
static PSD_Image_Data PSDImageData;
@ -142,8 +143,8 @@ static void reshuffle_cmap_write (guchar *mapGimp);
static void save_header (GOutputStream *output,
GimpImage *image,
gboolean export_cmyk,
gboolean export_duotone);
PSDResourceOptions
*options);
static void save_color_mode_data (GOutputStream *output,
GimpImage *image,
@ -151,7 +152,7 @@ static void save_color_mode_data (GOutputStream *output,
static void save_resources (GOutputStream *output,
GimpImage *image,
PSD_Resource_Options
PSDResourceOptions
*options);
static void save_paths (GOutputStream *output,
@ -163,11 +164,13 @@ static void save_clipping_path (GOutputStream *output,
static void save_layer_and_mask (GOutputStream *output,
GimpImage *image,
gboolean export_cmyk);
PSDResourceOptions
*options);
static void save_data (GOutputStream *output,
GimpImage *image,
gboolean export_cmyk);
PSDResourceOptions
*options);
static void double_to_psd_fixed (gdouble value,
gchar *target);
@ -198,6 +201,10 @@ static void write_gint32 (GOutputStream *output,
gint32 val,
const gchar *why);
static void write_gint64 (GOutputStream *output,
gint64 val,
const gchar *why);
static void write_datablock_luni (GOutputStream *output,
const gchar *val,
const gchar *why);
@ -209,7 +216,8 @@ static void write_pixel_data (GOutputStream *output,
goffset *ChanLenPosition,
goffset rowlenOffset,
gboolean write_mask,
gboolean export_cmyk);
PSDResourceOptions
*options);
static GimpLayer * create_merged_image (GimpImage *image);
@ -383,6 +391,32 @@ write_gint32 (GOutputStream *output,
}
}
static void
write_gint64 (GOutputStream *output,
gint64 val,
const gchar *why)
{
guchar b[8];
gsize bytes_written;
b[7] = val & 255;
b[6] = (val >> 8) & 255;
b[5] = (val >> 16) & 255;
b[4] = (val >> 24) & 255;
b[3] = (val >> 32) & 255;
b[2] = (val >> 40) & 255;
b[1] = (val >> 48) & 255;
b[0] = (val >> 56) & 255;
/* FIXME: Use error */
if (! g_output_stream_write_all (output, &b, 8,
&bytes_written, NULL, NULL))
{
g_printerr ("%s: Error while writing '%s'\n", G_STRFUNC, why);
gimp_quit ();
}
}
static void
write_datablock_luni (GOutputStream *output,
const gchar *val,
@ -555,10 +589,9 @@ reshuffle_cmap_write (guchar *mapGimp)
}
static void
save_header (GOutputStream *output,
GimpImage *image,
gboolean export_cmyk,
gboolean export_duotone)
save_header (GOutputStream *output,
GimpImage *image,
PSDResourceOptions *options)
{
gint nChannels;
@ -570,7 +603,7 @@ save_header (GOutputStream *output,
PSDImageData.image_height, PSDImageData.image_width,
PSDImageData.baseType, PSDImageData.nChannels);
if (export_cmyk)
if (options->cmyk)
{
nChannels =
gimp_drawable_has_alpha (GIMP_DRAWABLE (PSDImageData.merged_layer)) ?
@ -583,16 +616,16 @@ save_header (GOutputStream *output,
}
xfwrite (output, "8BPS", 4, "signature");
write_gint16 (output, 1, "version");
write_gint16 (output, options->psb ? 2 : 1, "version");
write_gint32 (output, 0, "reserved 1"); /* 6 for the 'reserved' field + 4 bytes for a long */
write_gint16 (output, 0, "reserved 1"); /* and 2 bytes for a short */
write_gint16 (output, nChannels, "channels");
write_gint32 (output, PSDImageData.image_height, "rows");
write_gint32 (output, PSDImageData.image_width, "columns");
write_gint16 (output, 8 * get_bpc (image), "depth");
if (export_cmyk)
if (options->cmyk)
write_gint16 (output, PSD_CMYK, "mode");
else if (export_duotone)
else if (options->duotone)
write_gint16 (output, PSD_DUOTONE, "mode");
else
write_gint16 (output, gimpBaseTypeToPsdMode (PSDImageData.baseType), "mode");
@ -676,9 +709,9 @@ save_color_mode_data (GOutputStream *output,
}
static void
save_resources (GOutputStream *output,
GimpImage *image,
PSD_Resource_Options *options)
save_resources (GOutputStream *output,
GimpImage *image,
PSDResourceOptions *options)
{
GList *iter;
gint i;
@ -1033,7 +1066,7 @@ get_compress_channel_data (guchar *channel_data,
gint32 channel_rows,
gint32 stride,
gint32 bpc,
gint16 *LengthsTable,
gint32 *LengthsTable,
guchar *remdata)
{
gint i;
@ -1335,9 +1368,9 @@ save_clipping_path (GOutputStream *output,
}
static void
save_layer_and_mask (GOutputStream *output,
GimpImage *image,
gboolean export_cmyk)
save_layer_and_mask (GOutputStream *output,
GimpImage *image,
PSDResourceOptions *options)
{
gint i,j;
gint idChannel;
@ -1349,7 +1382,7 @@ save_layer_and_mask (GOutputStream *output,
guchar layerOpacity; /* Opacity of the layer */
guchar flags; /* Layer flags */
gint nChannelsLayer; /* Number of channels of a layer */
gint32 ChanSize; /* Data length for a channel */
gint64 ChanSize; /* Data length for a channel */
gchar *layerName; /* Layer name */
GimpLayerMask *mask; /* Layer mask */
gint depth; /* Layer group nesting depth */
@ -1372,12 +1405,18 @@ save_layer_and_mask (GOutputStream *output,
/* Layer and mask information section */
LayerMaskPos = g_seekable_tell (G_SEEKABLE (output));
write_gint32 (output, 0, "layers & mask information length");
if (! options->psb)
write_gint32 (output, 0, "layers & mask information length");
else
write_gint64 (output, 0, "layers & mask information length");
/* Layer info section */
LayerInfoPos = g_seekable_tell (G_SEEKABLE (output));
write_gint32 (output, 0, "layers info section length");
if (! options->psb)
write_gint32 (output, 0, "layers info section length");
else
write_gint64 (output, 0, "layers info section length");
/* Layer structure section */
@ -1454,7 +1493,7 @@ save_layer_and_mask (GOutputStream *output,
/* Manually set channels to 4 or 5 when export as CMYK;
* Can be removed once CMYK channels are accessible in GIMP
*/
if (export_cmyk)
if (options->cmyk)
{
nChannelsLayer =
gimp_drawable_has_alpha (GIMP_DRAWABLE (psd_layer->layer)) ?
@ -1486,10 +1525,17 @@ save_layer_and_mask (GOutputStream *output,
will modify it later when writing data. */
ChannelLengthPos[i-1][j] = g_seekable_tell (G_SEEKABLE (output));
ChanSize = sizeof (gint16) + (layerWidth * layerHeight * bpc);
write_gint32 (output, ChanSize, "Channel Size");
IFDBG(1) g_debug ("\t\t\tLength: %d", ChanSize);
if (! options->psb)
{
ChanSize = sizeof (gint16) + (layerWidth * layerHeight * bpc);
write_gint32 (output, ChanSize, "Channel Size");
}
else
{
ChanSize = sizeof (gint32) + (layerWidth * layerHeight * bpc);
write_gint64 (output, ChanSize, "Channel Size");
}
IFDBG(1) g_debug ("\t\t\tLength: %" G_GSIZE_FORMAT, ChanSize);
}
xfwrite (output, "8BIM", 4, "blend mode signature");
@ -1661,7 +1707,7 @@ save_layer_and_mask (GOutputStream *output,
IFDBG(1) g_debug ("\t\tWriting pixel data for layer slot %d", i-1);
write_pixel_data (output, image, GIMP_DRAWABLE (psd_layer->layer), ChannelLengthPos[i-1], 0,
psd_layer->type != PSD_LAYER_TYPE_GROUP_END, export_cmyk);
psd_layer->type != PSD_LAYER_TYPE_GROUP_END, options);
g_free (ChannelLengthPos[i-1]);
}
@ -1673,18 +1719,42 @@ save_layer_and_mask (GOutputStream *output,
g_seekable_seek (G_SEEKABLE (output),
LayerInfoPos, G_SEEK_SET,
NULL, NULL /*FIXME: error*/);
write_gint32 (output, eof_pos - LayerInfoPos - sizeof (gint32), "layers info section length");
IFDBG(1) g_debug ("\t\tTotal layers info section length: %d",
(int) (eof_pos - LayerInfoPos - sizeof (gint32)));
if (! options->psb)
{
write_gint32 (output, eof_pos - LayerInfoPos - sizeof (gint32),
"layers info section length");
IFDBG(1) g_debug ("\t\tTotal layers info section length: %d",
(gint) (eof_pos - LayerInfoPos - sizeof (gint32)));
}
else
{
write_gint64 (output, eof_pos - LayerInfoPos - sizeof (gint64),
"layers info section length");
IFDBG(1) g_debug ("\t\tTotal layers info section length: %"
G_GSIZE_FORMAT,
(gint64) (eof_pos - LayerInfoPos - sizeof (gint64)));
}
/* Write actual size of Layer and mask information section */
g_seekable_seek (G_SEEKABLE (output),
LayerMaskPos, G_SEEK_SET,
NULL, NULL /*FIXME: error*/);
write_gint32 (output, eof_pos - LayerMaskPos - sizeof (gint32), "layers & mask information length");
IFDBG(1) g_debug ("\t\tTotal layers & mask information length: %d",
(int) (eof_pos - LayerMaskPos - sizeof (gint32)));
if (! options->psb)
{
write_gint32 (output, eof_pos - LayerMaskPos - sizeof (gint32),
"layers & mask information length");
IFDBG(1) g_debug ("\t\tTotal layers & mask information length: %d",
(gint) (eof_pos - LayerMaskPos - sizeof (gint32)));
}
else
{
write_gint64 (output, eof_pos - LayerMaskPos - sizeof (gint64),
"layers & mask information length");
IFDBG(1) g_debug ("\t\tTotal layers & mask information length: %"
G_GSIZE_FORMAT,
(gint64) (eof_pos - LayerMaskPos - sizeof (gint64)));
}
/* Return to EOF to continue writing */
@ -1694,13 +1764,13 @@ save_layer_and_mask (GOutputStream *output,
}
static void
write_pixel_data (GOutputStream *output,
GimpImage *image,
GimpDrawable *drawable,
goffset *ChanLenPosition,
goffset ltable_offset,
gboolean write_mask,
gboolean export_cmyk)
write_pixel_data (GOutputStream *output,
GimpImage *image,
GimpDrawable *drawable,
goffset *ChanLenPosition,
goffset ltable_offset,
gboolean write_mask,
PSDResourceOptions *options)
{
GeglBuffer *buffer = gimp_drawable_get_buffer (drawable);
const Babl *format;
@ -1717,7 +1787,7 @@ write_pixel_data (GOutputStream *output,
gint32 colors;
gint32 y;
gsize len; /* Length of compressed data */
gint16 *LengthsTable; /* Lengths of every compressed row */
gint32 *LengthsTable; /* Lengths of every compressed row */
guchar *rledata; /* Compressed data from a region */
guchar *data; /* Temporary copy of pixel data */
goffset length_table_pos; /* position in file of the length table */
@ -1743,7 +1813,7 @@ write_pixel_data (GOutputStream *output,
else
format = get_pixel_format (drawable);
if (export_cmyk && ! gimp_item_is_channel (GIMP_ITEM (drawable)))
if (options->cmyk && ! gimp_item_is_channel (GIMP_ITEM (drawable)))
{
profile = gimp_image_get_simulation_profile (image);
if (profile && gimp_color_profile_is_cmyk (profile))
@ -1790,7 +1860,7 @@ write_pixel_data (GOutputStream *output,
! gimp_drawable_is_indexed (drawable))
colors -= 1;
LengthsTable = g_new (gint16, height);
LengthsTable = g_new (gint32, height);
rledata = g_new (guchar, (MIN (height, tile_height) *
(width + 10 + (width / 100))) * bpc);
@ -1833,15 +1903,26 @@ write_pixel_data (GOutputStream *output,
if (ltable_offset > 0)
{
length_table_pos = ltable_offset + 2 * chan * height;
gint byte_offset = (! options->psb) ? 2 : 4;
length_table_pos = ltable_offset + byte_offset * chan * height;
}
else
{
length_table_pos = g_seekable_tell (G_SEEKABLE (output));
xfwrite (output, LengthsTable, height * sizeof(gint16),
"Dummy RLE length");
len += height * sizeof(gint16);
if (! options->psb)
{
xfwrite (output, LengthsTable, height * sizeof (gint16),
"Dummy RLE length");
len += height * sizeof (gint16);
}
else
{
xfwrite (output, LengthsTable, height * sizeof (gint32),
"Dummy RLE length");
len += height * sizeof (gint32);
}
IFDBG(3) g_debug ("\t\t\t\t. ltable, pos %" G_GOFFSET_FORMAT
" len %" G_GSIZE_FORMAT,
length_table_pos, len);
@ -1872,14 +1953,23 @@ write_pixel_data (GOutputStream *output,
length_table_pos, G_SEEK_SET,
NULL, NULL /*FIXME: error*/);
for (j = 0; j < height; j++) /* write real length table */
write_gint16 (output, LengthsTable[j], "RLE length");
{
if (! options->psb)
write_gint16 (output, LengthsTable[j], "RLE length");
else
write_gint32 (output, LengthsTable[j], "RLE length");
}
if (ChanLenPosition) /* Update total compressed length */
{
g_seekable_seek (G_SEEKABLE (output),
ChanLenPosition[i], G_SEEK_SET,
NULL, NULL /*FIXME: error*/);
write_gint32 (output, len, "channel data length");
if (! options->psb)
write_gint32 (output, len, "channel data length");
else
write_gint64 (output, len, "channel data length");
IFDBG(1) g_debug ("\t\tUpdating data len to %" G_GSIZE_FORMAT, len);
}
g_seekable_seek (G_SEEKABLE (output),
@ -1950,7 +2040,10 @@ write_pixel_data (GOutputStream *output,
NULL, NULL /*FIXME: error*/);
for (j = 0; j < height; j++) /* write real length table */
{
write_gint16 (output, LengthsTable[j], "RLE length");
if (! options->psb)
write_gint16 (output, LengthsTable[j], "RLE length");
else
write_gint32 (output, LengthsTable[j], "RLE length");
IFDBG(3) g_debug ("\t\t\t\t. Updating RLE len %d",
LengthsTable[j]);
}
@ -1962,7 +2055,10 @@ write_pixel_data (GOutputStream *output,
ChanLenPosition[components], G_SEEK_SET,
NULL, NULL /*FIXME: error*/);
write_gint32 (output, len, "channel data length");
if (! options->psb)
write_gint32 (output, len, "channel data length");
else
write_gint64 (output, len, "channel data length");
IFDBG(1) g_debug ("\t\tUpdating data len to %" G_GSIZE_FORMAT
", at %" G_GOFFSET_FORMAT,
len, g_seekable_tell (G_SEEKABLE (output)));
@ -1984,9 +2080,9 @@ write_pixel_data (GOutputStream *output,
}
static void
save_data (GOutputStream *output,
GimpImage *image,
gboolean export_cmyk)
save_data (GOutputStream *output,
GimpImage *image,
PSDResourceOptions *options)
{
GList *iter;
gint ChanCount;
@ -1997,7 +2093,7 @@ save_data (GOutputStream *output,
IFDBG(1) g_debug ("Function: save_data");
if (! export_cmyk)
if (! options->cmyk)
chan = nChansLayer (PSDImageData.baseType,
gimp_drawable_has_alpha (GIMP_DRAWABLE (PSDImageData.merged_layer)), 0);
else
@ -2015,18 +2111,25 @@ save_data (GOutputStream *output,
for (i = 0; i < ChanCount; i++)
for (j = 0; j < imageHeight; j++)
write_gint16 (output, 0, "junk line lengths");
{
if (! options->psb)
write_gint16 (output, 0, "junk line lengths");
else
write_gint32 (output, 0, "junk line lengths");
}
IFDBG(1) g_debug ("\t\tWriting compressed image data");
write_pixel_data (output, image, GIMP_DRAWABLE (PSDImageData.merged_layer),
NULL, offset, FALSE, export_cmyk);
NULL, offset, FALSE, options);
for (iter = PSDImageData.lChannels; iter; iter = g_list_next (iter))
{
gint bpc = (! options->psb) ? 2 : 4;
IFDBG(1) g_debug ("\t\tWriting compressed channel data for channel %d", i);
write_pixel_data (output, image, iter->data, NULL,
offset + 2*imageHeight*chan, FALSE,
export_cmyk); //check how imgs are channels here
offset + bpc * imageHeight * chan, FALSE,
options); //check how imgs are channels here
chan++;
}
}
@ -2129,18 +2232,22 @@ clear_image_data (void)
}
gboolean
export_image (GFile *file,
GimpImage *image,
GObject *config,
GError **error)
export_image (GFile *file,
GimpImage *image,
GimpProcedure *procedure,
GObject *config,
GError **error)
{
GOutputStream *output;
GeglBuffer *buffer;
GList *iter;
GError *local_error = NULL;
GimpParasite *parasite = NULL;
PSD_Resource_Options resource_options;
GOutputStream *output;
GeglBuffer *buffer;
GList *iter;
GError *local_error = NULL;
GimpParasite *parasite = NULL;
PSDResourceOptions resource_options;
gsize max_dim;
resource_options.psb =
(! strcmp (gimp_procedure_get_name (procedure), EXPORT_PSB_PROC));
g_object_get (config,
"cmyk", &resource_options.cmyk,
"duotone", &resource_options.duotone,
@ -2154,14 +2261,23 @@ export_image (GFile *file,
if (resource_options.cmyk)
resource_options.duotone = FALSE;
if (gimp_image_get_width (image) > 30000 ||
gimp_image_get_height (image) > 30000)
max_dim = (! resource_options.psb) ? 30000 : 300000;
if (gimp_image_get_width (image) > max_dim ||
gimp_image_get_height (image) > max_dim)
{
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
_("Unable to export '%s'. The PSD file format does not "
"support images that are more than 30,000 pixels wide "
"or tall."),
gimp_file_get_utf8_name (file));
if (! resource_options.psb)
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
_("Unable to export '%s'. The PSD file format does not "
"support images that are more than 30,000 pixels wide "
"or tall."),
gimp_file_get_utf8_name (file));
else
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
_("Unable to export '%s'. The PSB file format does not "
"support images that are more than 300,000 pixels wide "
"or tall."),
gimp_file_get_utf8_name (file));
return FALSE;
}
@ -2195,14 +2311,21 @@ export_image (GFile *file,
{
buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (layer->layer));
if (gegl_buffer_get_width (buffer) > 30000 ||
gegl_buffer_get_height (buffer) > 30000)
if (gegl_buffer_get_width (buffer) > max_dim ||
gegl_buffer_get_height (buffer) > max_dim)
{
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
_("Unable to export '%s'. The PSD file format does not "
"support images with layers that are more than 30,000 "
"pixels wide or tall."),
gimp_file_get_utf8_name (file));
if (! resource_options.psb)
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
_("Unable to export '%s'. The PSD file format "
"does not support images with layers that are "
"more than 30,000 pixels wide or tall."),
gimp_file_get_utf8_name (file));
else
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
_("Unable to export '%s'. The PSB file format "
"does not support images with layers that are "
"more than 300,000 pixels wide or tall."),
gimp_file_get_utf8_name (file));
clear_image_data ();
return FALSE;
}
@ -2235,20 +2358,27 @@ export_image (GFile *file,
IFDBG(1) g_debug ("\tFile '%s' has been opened",
gimp_file_get_utf8_name (file));
save_header (output, image, resource_options.cmyk, resource_options.duotone);
save_header (output, image, &resource_options);
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");
{
if (! resource_options.psb)
write_gint32 (output, 0, "layers info section length");
else
write_gint64 (output, 0, "layers info section length");
}
else
save_layer_and_mask (output, image, resource_options.cmyk);
{
save_layer_and_mask (output, image, &resource_options);
}
/* If this is an indexed image, write now channel and layer info */
save_data (output, image, resource_options.cmyk);
save_data (output, image, &resource_options);
/* Delete merged image now */

View file

@ -19,13 +19,14 @@
#define __PSD_EXPORT_H__
gboolean export_image (GFile *file,
GimpImage *image,
GObject *config,
GError **error);
gboolean export_image (GFile *file,
GimpImage *image,
GimpProcedure *procedure,
GObject *config,
GError **error);
gboolean save_dialog (GimpImage *image,
GimpProcedure *procedure,
GObject *config);
gboolean save_dialog (GimpImage *image,
GimpProcedure *procedure,
GObject *config);
#endif /* __PSD_EXPORT_H__ */

View file

@ -115,6 +115,7 @@ psd_query_procedures (GimpPlugIn *plug_in)
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 (EXPORT_PROC));
list = g_list_append (list, g_strdup (EXPORT_PSB_PROC));
list = g_list_append (list, g_strdup (LOAD_METADATA_PROC));
return list;
@ -204,7 +205,8 @@ psd_create_procedure (GimpPlugIn *plug_in,
"John Marshall",
"2007");
}
else if (! strcmp (name, EXPORT_PROC))
else if (! strcmp (name, EXPORT_PROC) ||
! strcmp (name, EXPORT_PSB_PROC))
{
procedure = gimp_export_procedure_new (plug_in, name,
GIMP_PDB_PROC_TYPE_PLUGIN,
@ -212,30 +214,54 @@ psd_create_procedure (GimpPlugIn *plug_in,
gimp_procedure_set_image_types (procedure, "*");
gimp_procedure_set_menu_label (procedure, _("Photoshop image"));
gimp_file_procedure_set_format_name (GIMP_FILE_PROCEDURE (procedure),
_("Photoshop image"));
if (! strcmp (name, EXPORT_PROC))
{
gimp_procedure_set_menu_label (procedure, _("Photoshop image"));
gimp_file_procedure_set_format_name (GIMP_FILE_PROCEDURE (procedure),
_("Photoshop image"));
gimp_procedure_set_documentation (procedure,
_("Saves files in the Photoshop (TM) "
"PSD file format"),
_("This plug-in saves files of Adobe "
"Photoshop (TM) native PSD format. "
"These files may be of any image type "
"supported by GIMP, with or without "
"layers, layer masks, aux channels "
"and guides."),
name);
gimp_file_procedure_set_mime_types (GIMP_FILE_PROCEDURE (procedure),
"image/x-psd");
gimp_file_procedure_set_extensions (GIMP_FILE_PROCEDURE (procedure),
"psd");
}
else
{
gimp_procedure_set_menu_label (procedure, _("Photoshop Large image"));
gimp_file_procedure_set_format_name (GIMP_FILE_PROCEDURE (procedure),
_("Photoshop Large image"));
gimp_procedure_set_documentation (procedure,
_("Saves files in the Photoshop (TM) "
"Large PSB file format"),
_("This plug-in saves files of Adobe "
"Photoshop (TM) Large native PSB format. "
"These files may be of any image type "
"supported by GIMP, with or without "
"layers, layer masks, aux channels "
"and guides."),
name);
gimp_file_procedure_set_mime_types (GIMP_FILE_PROCEDURE (procedure),
"image/x-psb");
gimp_file_procedure_set_extensions (GIMP_FILE_PROCEDURE (procedure),
"psb");
}
gimp_procedure_set_documentation (procedure,
_("Saves files in the Photoshop (TM) "
"PSD file format"),
_("This plug-in saves files of Adobe "
"Photoshop (TM) native PSD format. "
"These files may be of any image type "
"supported by GIMP, with or without "
"layers, layer masks, aux channels "
"and guides."),
name);
gimp_procedure_set_attribution (procedure,
"Monigotes",
"Monigotes",
"2000");
gimp_file_procedure_set_mime_types (GIMP_FILE_PROCEDURE (procedure),
"image/x-psd");
gimp_file_procedure_set_extensions (GIMP_FILE_PROCEDURE (procedure),
"psd");
gimp_export_procedure_set_capabilities (GIMP_EXPORT_PROCEDURE (procedure),
GIMP_EXPORT_CAN_HANDLE_RGB |
GIMP_EXPORT_CAN_HANDLE_GRAY |
@ -463,7 +489,7 @@ psd_export (GimpProcedure *procedure,
export = gimp_export_options_get_image (options, &image);
drawables = gimp_image_list_layers (image);
if (export_image (file, image, G_OBJECT (config), &error))
if (export_image (file, image, procedure, G_OBJECT (config), &error))
{
if (metadata)
gimp_metadata_set_bits_per_sample (metadata, 8);

View file

@ -35,6 +35,7 @@
#define LOAD_MERGED_PROC "file-psd-load-merged"
#define LOAD_THUMB_PROC "file-psd-load-thumb"
#define EXPORT_PROC "file-psd-export"
#define EXPORT_PSB_PROC "file-psb-export"
#define LOAD_METADATA_PROC "file-psd-load-metadata"
#define PLUG_IN_BINARY "file-psd"
#define PLUG_IN_ROLE "gimp-file-psd"