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

View file

@ -19,13 +19,14 @@
#define __PSD_EXPORT_H__ #define __PSD_EXPORT_H__
gboolean export_image (GFile *file, gboolean export_image (GFile *file,
GimpImage *image, GimpImage *image,
GObject *config, GimpProcedure *procedure,
GError **error); GObject *config,
GError **error);
gboolean save_dialog (GimpImage *image, gboolean save_dialog (GimpImage *image,
GimpProcedure *procedure, GimpProcedure *procedure,
GObject *config); GObject *config);
#endif /* __PSD_EXPORT_H__ */ #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_PROC));
list = g_list_append (list, g_strdup (LOAD_MERGED_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_PROC));
list = g_list_append (list, g_strdup (EXPORT_PSB_PROC));
list = g_list_append (list, g_strdup (LOAD_METADATA_PROC)); list = g_list_append (list, g_strdup (LOAD_METADATA_PROC));
return list; return list;
@ -204,7 +205,8 @@ psd_create_procedure (GimpPlugIn *plug_in,
"John Marshall", "John Marshall",
"2007"); "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, procedure = gimp_export_procedure_new (plug_in, name,
GIMP_PDB_PROC_TYPE_PLUGIN, GIMP_PDB_PROC_TYPE_PLUGIN,
@ -212,30 +214,54 @@ psd_create_procedure (GimpPlugIn *plug_in,
gimp_procedure_set_image_types (procedure, "*"); gimp_procedure_set_image_types (procedure, "*");
gimp_procedure_set_menu_label (procedure, _("Photoshop image")); if (! strcmp (name, EXPORT_PROC))
gimp_file_procedure_set_format_name (GIMP_FILE_PROCEDURE (procedure), {
_("Photoshop image")); 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, gimp_procedure_set_attribution (procedure,
"Monigotes", "Monigotes",
"Monigotes", "Monigotes",
"2000"); "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_procedure_set_capabilities (GIMP_EXPORT_PROCEDURE (procedure),
GIMP_EXPORT_CAN_HANDLE_RGB | GIMP_EXPORT_CAN_HANDLE_RGB |
GIMP_EXPORT_CAN_HANDLE_GRAY | GIMP_EXPORT_CAN_HANDLE_GRAY |
@ -463,7 +489,7 @@ psd_export (GimpProcedure *procedure,
export = gimp_export_options_get_image (options, &image); export = gimp_export_options_get_image (options, &image);
drawables = gimp_image_list_layers (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) if (metadata)
gimp_metadata_set_bits_per_sample (metadata, 8); gimp_metadata_set_bits_per_sample (metadata, 8);

View file

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