summaryrefslogtreecommitdiffstats
path: root/plug-ins/file-psd/psd-save.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--plug-ins/file-psd/psd-save.c2097
1 files changed, 2097 insertions, 0 deletions
diff --git a/plug-ins/file-psd/psd-save.c b/plug-ins/file-psd/psd-save.c
new file mode 100644
index 0000000..e506610
--- /dev/null
+++ b/plug-ins/file-psd/psd-save.c
@@ -0,0 +1,2097 @@
+/*
+ * PSD Export Plugin version 1.0 (BETA)
+ * This GIMP plug-in is designed to export Adobe Photoshop(tm) files (.PSD)
+ *
+ * Monigotes
+ *
+ * If this plug-in fails to export a file which you think it should,
+ * please tell me what seemed to go wrong, and anything you know
+ * about the image you tried to export. Please don't send big PSD
+ * files to me without asking first.
+ *
+ * Copyright (C) 2000 Monigotes
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/*
+ * Adobe and Adobe Photoshop are trademarks of Adobe Systems
+ * Incorporated that may be registered in certain jurisdictions.
+ */
+
+/*
+ * Revision history:
+ *
+ * 2000.02 / v1.0 / Monigotes
+ * First version.
+ *
+ * 2003-05-10 Pedro Gimeno <pggimeno@wanadoo.es>
+ * - Cleaned up and GNUstylized.
+ * - Translated all comments and vars in Spanish to English.
+ *
+ * 2005-2-11 Jay Cox <jaycox@gimp.org>
+ * Rewrote all the code that deals with pixels to be stingy with
+ * memory and operate on tile-size chunks. Create a flattened
+ * copy of the image when necessary. Fixes file corruption bug
+ * #167139 and memory bug #121871
+ *
+ * 2006-03-29 Guillermo S. Romero <gsr.bugs@infernal-iceberg.com>
+ * - Added/enabled basic support for layer masks based in psd.c
+ * and whatever was already here.
+ * Layers that are not canvas sized need investigation, here
+ * or in the import plugin something seems wrong.
+ * - Cosmetic changes about the debug messages, more like psd.c.
+ */
+
+/*
+ * TODO:
+ * Export preview
+ */
+
+/*
+ * BUGS:
+ */
+
+#include "config.h"
+
+#include <errno.h>
+#include <string.h>
+
+#include <glib/gstdio.h>
+
+#include "libgimp/gimp.h"
+#include "libgimp/gimpui.h"
+
+#include "libgimpmath/gimpmath.h"
+
+#include "psd.h"
+#include "psd-util.h"
+#include "psd-save.h"
+
+#include "libgimp/stdplugins-intl.h"
+
+
+/* set to TRUE if you want debugging, FALSE otherwise */
+#define DEBUG FALSE
+
+/* 1: Normal debuggin, 2: Deep debuggin */
+#define DEBUG_LEVEL 2
+
+#undef IFDBG /* previously defined in psd.h */
+
+#define IFDBG if (DEBUG)
+#define IF_DEEP_DBG if (DEBUG && DEBUG_LEVEL == 2)
+
+#define PSD_UNIT_INCH 1
+#define PSD_UNIT_CM 2
+
+
+/* Local types etc
+ */
+
+typedef enum PsdLayerType
+{
+ PSD_LAYER_TYPE_LAYER,
+ PSD_LAYER_TYPE_GROUP_START,
+ PSD_LAYER_TYPE_GROUP_END
+} PSD_Layer_Type;
+
+typedef struct PsdLayer
+{
+ gint id;
+ PSD_Layer_Type type;
+} PSD_Layer;
+
+typedef struct PsdImageData
+{
+ gboolean compression;
+
+ gint32 image_height;
+ gint32 image_width;
+
+ GimpImageBaseType baseType;
+
+ gint32 merged_layer;/* Merged image,
+ to be used for the image data section */
+
+ gint nChannels; /* Number of user channels in the image */
+ gint32 *lChannels; /* User channels in the image */
+
+ gint nLayers; /* Number of layers in the image */
+ PSD_Layer *lLayers; /* Layer list */
+} PSD_Image_Data;
+
+static PSD_Image_Data PSDImageData;
+
+/* Declare some local functions.
+ */
+
+static const gchar * psd_lmode_layer (gint32 idLayer,
+ gboolean section_divider);
+
+static void reshuffle_cmap_write (guchar *mapGimp);
+
+static void save_header (FILE *fd,
+ gint32 image_id);
+
+static void save_color_mode_data (FILE *fd,
+ gint32 image_id);
+
+static void save_resources (FILE *fd,
+ gint32 image_id);
+
+static void save_paths (FILE *fd,
+ gint32 image_id);
+
+static void save_layer_and_mask (FILE *fd,
+ gint32 image_id);
+
+static void save_data (FILE *fd,
+ gint32 image_id);
+
+static void double_to_psd_fixed (gdouble value,
+ gchar *target);
+
+static void xfwrite (FILE *fd,
+ gconstpointer buf,
+ glong len,
+ const gchar *why);
+
+static void write_pascalstring (FILE *fd,
+ const gchar *val,
+ gint padding,
+ const gchar *why);
+
+static void write_string (FILE *fd,
+ const gchar *val,
+ const gchar *why);
+
+static void write_gchar (FILE *fd,
+ guchar val,
+ const gchar *why);
+
+static void write_gint16 (FILE *fd,
+ gint16 val,
+ const gchar *why);
+
+static void write_gint32 (FILE *fd,
+ gint32 val,
+ const gchar *why);
+
+static void write_datablock_luni (FILE *fd,
+ const gchar *val,
+ const gchar *why);
+
+
+static void write_pixel_data (FILE *fd,
+ gint32 drawableID,
+ glong *ChanLenPosition,
+ gint32 rowlenOffset,
+ gboolean write_mask);
+
+static gint32 create_merged_image (gint32 imageID);
+
+static gint get_bpc (gint32 imageID);
+static const Babl * get_pixel_format (gint32 drawableID);
+static const Babl * get_channel_format (gint32 drawableID);
+static const Babl * get_mask_format (gint32 drawableID);
+
+static PSD_Layer * image_get_all_layers (gint32 imageID,
+ gint *n_layers);
+
+static const gchar *
+psd_lmode_layer (gint32 idLayer,
+ gboolean section_divider)
+{
+ LayerModeInfo mode_info;
+
+ mode_info.mode = gimp_layer_get_mode (idLayer);
+ mode_info.blend_space = gimp_layer_get_blend_space (idLayer);
+ mode_info.composite_space = gimp_layer_get_composite_space (idLayer);
+ mode_info.composite_mode = gimp_layer_get_composite_mode (idLayer);
+
+ /* pass-through groups use normal mode in their layer record; the
+ * pass-through mode is specified in their section divider resource.
+ */
+ if (mode_info.mode == GIMP_LAYER_MODE_PASS_THROUGH && ! section_divider)
+ mode_info.mode = GIMP_LAYER_MODE_NORMAL;
+
+ return gimp_to_psd_blend_mode (&mode_info);
+}
+
+static void
+write_string (FILE *fd,
+ const gchar *val,
+ const gchar *why)
+{
+ write_gchar (fd, strlen (val), why);
+ xfwrite (fd, val, strlen (val), why);
+}
+
+static void
+write_pascalstring (FILE *fd,
+ const gchar *val,
+ gint padding,
+ const gchar *why)
+{
+ guchar len;
+ gint i;
+
+ /* Calculate string length to write and limit it to 255 */
+
+ len = (strlen (val) > 255) ? 255 : (guchar) strlen (val);
+
+ /* Perform actual writing */
+
+ if (len != 0)
+ {
+ write_gchar (fd, len, why);
+ xfwrite (fd, val, len, why);
+ }
+ else
+ {
+ write_gchar (fd, 0, why);
+ }
+
+ /* If total length (length byte + content) is not a multiple of PADDING,
+ add zeros to pad it. */
+
+ len++; /* Add the length field */
+
+ if ((len % padding) == 0)
+ return;
+
+ for (i = 0; i < (padding - (len % padding)); i++)
+ write_gchar (fd, 0, why);
+}
+
+static void
+xfwrite (FILE *fd,
+ gconstpointer buf,
+ glong len,
+ const gchar *why)
+{
+ if (len == 0)
+ return;
+
+ if (fwrite (buf, len, 1, fd) == 0)
+ {
+ g_printerr ("%s: Error while writing '%s'\n", G_STRFUNC, why);
+ gimp_quit ();
+ }
+}
+
+static void
+write_gchar (FILE *fd,
+ guchar val,
+ const gchar *why)
+{
+ guchar b[2];
+ glong pos;
+
+ b[0] = val;
+ b[1] = 0;
+
+ pos = ftell (fd);
+ if (fwrite (&b, 1, 2, fd) == 0)
+ {
+ g_printerr ("%s: Error while writing '%s'\n", G_STRFUNC, why);
+ gimp_quit ();
+ }
+ fseek (fd, pos + 1, SEEK_SET);
+}
+
+static void
+write_gint16 (FILE *fd,
+ gint16 val,
+ const gchar *why)
+{
+ guchar b[2];
+ /* b[0] = val & 255;
+ b[1] = (val >> 8) & 255;*/
+
+ b[1] = val & 255;
+ b[0] = (val >> 8) & 255;
+
+ if (fwrite (&b, 1, 2, fd) == 0)
+ {
+ g_printerr ("%s: Error while writing '%s'\n", G_STRFUNC, why);
+ gimp_quit ();
+ }
+}
+
+static void
+write_gint32 (FILE *fd,
+ gint32 val,
+ const gchar *why)
+{
+ guchar b[4];
+
+ b[3] = val & 255;
+ b[2] = (val >> 8) & 255;
+ b[1] = (val >> 16) & 255;
+ b[0] = (val >> 24) & 255;
+
+ if (fwrite (&b, 1, 4, fd) == 0)
+ {
+ g_printerr ("%s: Error while writing '%s'\n", G_STRFUNC, why);
+ gimp_quit ();
+ }
+}
+
+static void
+write_datablock_luni (FILE *fd,
+ const gchar *val,
+ const gchar *why)
+{
+ if (val)
+ {
+ guint32 count;
+ guint32 xdBlockSize;
+ glong numchars;
+ gunichar2 *luniName;
+
+ luniName = g_utf8_to_utf16 (val, -1, NULL, &numchars, NULL);
+
+ if (luniName)
+ {
+ guchar len = MIN (numchars, 255);
+
+ /* Only pad to even num of chars */
+ if( len % 2 )
+ xdBlockSize = len + 1;
+ else
+ xdBlockSize = len;
+
+ /* 2 bytes / char + 4 bytes for pascal num chars */
+ xdBlockSize = (xdBlockSize * 2) + 4;
+
+ xfwrite (fd, "8BIMluni", 8, "luni xdb signature");
+ write_gint32 (fd, xdBlockSize, "luni xdb size");
+ write_gint32 (fd, len, "luni xdb pascal string");
+
+ for (count = 0; count < len; count++)
+ write_gint16 (fd, luniName[count], "luni xdb pascal string");
+
+ /* Pad to an even number of chars */
+ if (len % 2)
+ write_gint16 (fd, 0x0000, "luni xdb pascal string padding");
+ }
+ }
+}
+
+static gint32
+pack_pb_line (guchar *start,
+ gint32 length,
+ guchar *dest_ptr)
+{
+ gint32 remaining = length;
+ gint i, j;
+
+ length = 0;
+ while (remaining > 0)
+ {
+ /* Look for characters matching the first */
+
+ i = 0;
+ while ((i < 128) &&
+ (remaining - i > 0) &&
+ (start[0] == start[i]))
+ i++;
+
+ if (i > 1) /* Match found */
+ {
+
+ *dest_ptr++ = -(i - 1);
+ *dest_ptr++ = *start;
+
+ start += i;
+ remaining -= i;
+ length += 2;
+ }
+ else /* Look for characters different from the previous */
+ {
+ i = 0;
+ while ((i < 128) &&
+ (remaining - (i + 1) > 0) &&
+ (start[i] != start[i + 1] ||
+ remaining - (i + 2) <= 0 || start[i] != start[i+2]))
+ i++;
+
+ /* If there's only 1 remaining, the previous WHILE stmt doesn't
+ catch it */
+
+ if (remaining == 1)
+ {
+ i = 1;
+ }
+
+ if (i > 0) /* Some distinct ones found */
+ {
+ *dest_ptr++ = i - 1;
+ for (j = 0; j < i; j++)
+ {
+ *dest_ptr++ = start[j];
+ }
+ start += i;
+ remaining -= i;
+ length += i + 1;
+ }
+
+ }
+ }
+ return length;
+}
+
+static gint
+gimpBaseTypeToPsdMode (GimpImageBaseType gimpBaseType)
+{
+ switch (gimpBaseType)
+ {
+ case GIMP_RGB:
+ return 3; /* RGB */
+ case GIMP_GRAY:
+ return 1; /* Grayscale */
+ case GIMP_INDEXED:
+ return 2; /* Indexed */
+ default:
+ g_message (_("Error: Can't convert GIMP base imagetype to PSD mode"));
+ IFDBG printf ("PSD Export: gimpBaseType value is %d, "
+ "can't convert to PSD mode", gimpBaseType);
+ gimp_quit ();
+ return 3; /* Return RGB by default */
+ }
+}
+
+static gint
+nChansLayer (gint gimpBaseType,
+ gint hasAlpha,
+ gint hasMask)
+{
+ gint incAlpha = 0;
+ gint incMask = 0;
+
+ incAlpha = (hasAlpha == 0) ? 0 : 1;
+ incMask = (hasMask == 0) ? 0 : 1;
+
+ switch (gimpBaseType)
+ {
+ case GIMP_RGB:
+ return 3 + incAlpha + incMask; /* R,G,B & Alpha & Mask (if any) */
+ case GIMP_GRAY:
+ return 1 + incAlpha + incMask; /* G & Alpha & Mask (if any) */
+ case GIMP_INDEXED:
+ return 1 + incAlpha + incMask; /* I & Alpha & Mask (if any) */
+ default:
+ return 0; /* Return 0 channels by default */
+ }
+}
+
+static void
+reshuffle_cmap_write (guchar *mapGimp)
+{
+ guchar *mapPSD;
+ gint i;
+
+ mapPSD = g_malloc (768);
+
+ for (i = 0; i < 256; i++)
+ {
+ mapPSD[i] = mapGimp[i * 3];
+ mapPSD[i + 256] = mapGimp[i * 3 + 1];
+ mapPSD[i + 512] = mapGimp[i * 3 + 2];
+ }
+
+ for (i = 0; i < 768; i++)
+ {
+ mapGimp[i] = mapPSD[i];
+ }
+
+ g_free (mapPSD);
+}
+
+static void
+save_header (FILE *fd,
+ gint32 image_id)
+{
+ IFDBG printf (" Function: save_header\n");
+ IFDBG printf ("\tRows: %d\n", PSDImageData.image_height);
+ IFDBG printf ("\tColumns: %d\n", PSDImageData.image_width);
+ IFDBG printf ("\tBase type: %d\n", PSDImageData.baseType);
+ IFDBG printf ("\tNumber of channels: %d\n", PSDImageData.nChannels);
+
+ xfwrite (fd, "8BPS", 4, "signature");
+ write_gint16 (fd, 1, "version");
+ write_gint32 (fd, 0, "reserved 1"); /* 6 for the 'reserved' field + 4 bytes for a long */
+ write_gint16 (fd, 0, "reserved 1"); /* and 2 bytes for a short */
+ write_gint16 (fd, (PSDImageData.nChannels +
+ nChansLayer (PSDImageData.baseType,
+ gimp_drawable_has_alpha (PSDImageData.merged_layer), 0)),
+ "channels");
+ write_gint32 (fd, PSDImageData.image_height, "rows");
+ write_gint32 (fd, PSDImageData.image_width, "columns");
+ write_gint16 (fd, 8 * get_bpc (image_id), "depth");
+ write_gint16 (fd, gimpBaseTypeToPsdMode (PSDImageData.baseType), "mode");
+}
+
+static void
+save_color_mode_data (FILE *fd,
+ gint32 image_id)
+{
+ guchar *cmap;
+ guchar *cmap_modified;
+ gint i;
+ gint32 nColors;
+
+ IFDBG printf (" Function: save_color_mode_data\n");
+
+ switch (PSDImageData.baseType)
+ {
+ case GIMP_INDEXED:
+ IFDBG printf ("\tImage type: INDEXED\n");
+
+ cmap = gimp_image_get_colormap (image_id, &nColors);
+ IFDBG printf ("\t\tLength of colormap returned by gimp_image_get_colormap: %d\n", nColors);
+
+ if (nColors == 0)
+ {
+ IFDBG printf ("\t\tThe indexed image lacks a colormap\n");
+ write_gint32 (fd, 0, "color data length");
+ }
+ else if (nColors != 256)
+ {
+ IFDBG printf ("\t\tThe indexed image has %d!=256 colors\n", nColors);
+ IFDBG printf ("\t\tPadding with zeros up to 256\n");
+ write_gint32 (fd, 768, "color data length");
+ /* For this type, length is always 768 */
+
+ cmap_modified = g_malloc (768);
+ for (i = 0; i < nColors * 3; i++)
+ cmap_modified[i] = cmap[i];
+
+ for (i = nColors * 3; i < 768; i++)
+ cmap_modified[i] = 0;
+
+ reshuffle_cmap_write (cmap_modified);
+ xfwrite (fd, cmap_modified, 768, "colormap"); /* Write readjusted colormap */
+
+ g_free (cmap_modified);
+ }
+ else /* nColors equals 256 */
+ {
+ write_gint32 (fd, 768, "color data length"); /* For this type, length is always 768 */
+ reshuffle_cmap_write (cmap);
+ xfwrite (fd, cmap, 768, "colormap"); /* Write readjusted colormap */
+ }
+ break;
+
+ default:
+ IFDBG printf ("\tImage type: Not INDEXED\n");
+ write_gint32 (fd, 0, "color data length");
+ }
+}
+
+static void
+save_resources (FILE *fd,
+ gint32 image_id)
+{
+ gint i;
+ gchar *fileName; /* Image file name */
+ gint32 idActLayer; /* Id of the active layer */
+ guint nActiveLayer = 0; /* Number of the active layer */
+ gboolean ActiveLayerPresent; /* TRUE if there's an active layer */
+
+ glong eof_pos; /* Position for End of file */
+ glong rsc_pos; /* Position for Lengths of Resources section */
+ glong name_sec; /* Position for Lengths of Channel Names */
+
+
+ /* Only relevant resources in GIMP are: 0x03EE, 0x03F0 & 0x0400 */
+ /* For Adobe Photoshop version 4.0 these can also be considered:
+ 0x0408, 0x040A & 0x040B (1006, 1008, 1024, 1032, 1034, and 1035) */
+
+ IFDBG printf (" Function: save_resources\n");
+
+
+ /* Get the image title from its filename */
+
+ fileName = gimp_image_get_filename (image_id);
+ IFDBG printf ("\tImage title: %s\n", fileName);
+
+ /* Get the active layer number id */
+
+ idActLayer = gimp_image_get_active_layer (image_id);
+ IFDBG printf ("\tCurrent layer id: %d\n", idActLayer);
+
+ ActiveLayerPresent = FALSE;
+ for (i = 0; i < PSDImageData.nLayers; i++)
+ if (idActLayer == PSDImageData.lLayers[i].id)
+ {
+ nActiveLayer = PSDImageData.nLayers - i - 1;
+ ActiveLayerPresent = TRUE;
+ break;
+ }
+
+ if (ActiveLayerPresent)
+ {
+ IFDBG printf ("\t\tActive layer is number %d\n", nActiveLayer);
+ }
+ else
+ {
+ IFDBG printf ("\t\tNo active layer\n");
+ }
+
+
+ /* Here's where actual writing starts */
+
+ rsc_pos = ftell (fd);
+ write_gint32 (fd, 0, "image resources length");
+
+
+ /* --------------- Write Channel names --------------- */
+
+ if (PSDImageData.nChannels > 0 ||
+ gimp_drawable_has_alpha (PSDImageData.merged_layer))
+ {
+ xfwrite (fd, "8BIM", 4, "imageresources signature");
+ write_gint16 (fd, 0x03EE, "0x03EE Id"); /* 1006 */
+ /* write_pascalstring (fd, Name, "Id name"); */
+ write_gint16 (fd, 0, "Id name"); /* Set to null string (two zeros) */
+
+ /* Mark current position in the file */
+
+ name_sec = ftell (fd);
+ write_gint32 (fd, 0, "0x03EE resource size");
+
+ /* Write all strings */
+
+ /* if the merged_image contains transparency, write a name for it first */
+ if (gimp_drawable_has_alpha (PSDImageData.merged_layer))
+ write_string (fd, "Transparency", "channel name");
+
+ for (i = 0; i < PSDImageData.nChannels; i++)
+ {
+ char *chName = gimp_item_get_name (PSDImageData.lChannels[i]);
+ write_string (fd, chName, "channel name");
+ g_free (chName);
+ }
+ /* Calculate and write actual resource's length */
+
+ eof_pos = ftell (fd);
+
+ fseek (fd, name_sec, SEEK_SET);
+ write_gint32 (fd, eof_pos - name_sec - sizeof (gint32), "0x03EE resource size");
+ IFDBG printf ("\tTotal length of 0x03EE resource: %d\n",
+ (int) (eof_pos - name_sec - sizeof (gint32)));
+
+ /* Return to EOF to continue writing */
+
+ fseek (fd, eof_pos, SEEK_SET);
+
+ /* Pad if length is odd */
+
+ if ((eof_pos - name_sec - sizeof (gint32)) & 1)
+ write_gchar (fd, 0, "pad byte");
+ }
+
+ /* --------------- Write Channel properties --------------- */
+
+ if (PSDImageData.nChannels > 0 ||
+ gimp_drawable_has_alpha (PSDImageData.merged_layer))
+ {
+ xfwrite (fd, "8BIM", 4, "imageresources signature");
+ write_gint16 (fd, 0x0435, "0x0435 Id"); /* 1077 */
+ /* write_pascalstring (fd, Name, "Id name"); */
+ write_gint16 (fd, 0, "Id name"); /* Set to null string (two zeros) */
+ write_gint32 (fd,
+ 4 +
+ 13 * (gimp_drawable_has_alpha (PSDImageData.merged_layer) +
+ PSDImageData.nChannels),
+ "0x0435 resource size");
+
+ /* The function of the first 4 bytes is unclear. As per
+ * load_resource_1077() in psd-image-res-load.c, it seems to be a version
+ * number that is always one.
+ */
+ write_gint32 (fd, 1, "0x0435 version");
+
+ /* Write all channel properties */
+
+ #define DOUBLE_TO_INT16(x) ROUND (SAFE_CLAMP (x, 0.0, 1.0) * 0xffff)
+
+ /* if the merged_image contains transparency, write its properties first */
+ if (gimp_drawable_has_alpha (PSDImageData.merged_layer))
+ {
+ write_gint16 (fd, PSD_CS_RGB, "channel color space");
+ write_gint16 (fd, DOUBLE_TO_INT16 (1.0), "channel color r");
+ write_gint16 (fd, DOUBLE_TO_INT16 (0.0), "channel color g");
+ write_gint16 (fd, DOUBLE_TO_INT16 (0.0), "channel color b");
+ write_gint16 (fd, 0, "channel color padding");
+ write_gint16 (fd, 100, "channel opacity");
+ write_gchar (fd, 1, "channel mode");
+ }
+
+ for (i = 0; i < PSDImageData.nChannels; i++)
+ {
+ GimpRGB color;
+ gdouble opacity;
+
+ gimp_channel_get_color (PSDImageData.lChannels[i], &color);
+ opacity = gimp_channel_get_opacity (PSDImageData.lChannels[i]);
+
+ write_gint16 (fd, PSD_CS_RGB, "channel color space");
+ write_gint16 (fd, DOUBLE_TO_INT16 (color.r), "channel color r");
+ write_gint16 (fd, DOUBLE_TO_INT16 (color.g), "channel color g");
+ write_gint16 (fd, DOUBLE_TO_INT16 (color.b), "channel color b");
+ write_gint16 (fd, 0, "channel color padding");
+ write_gint16 (fd, ROUND (opacity), "channel opacity");
+ write_gchar (fd, 1, "channel mode");
+ }
+
+ #undef DOUBLE_TO_INT16
+
+ /* Pad if length is odd */
+
+ if (ftell (fd) & 1)
+ write_gchar (fd, 0, "pad byte");
+ }
+
+ /* --------------- Write Guides --------------- */
+ if (gimp_image_find_next_guide(image_id, 0))
+ {
+ gint n_guides = 0;
+ gint guide_id =0;
+
+ /* Count the guides */
+ while ((guide_id = gimp_image_find_next_guide(image_id, guide_id)))
+ n_guides++;
+
+ xfwrite (fd, "8BIM", 4, "imageresources signature");
+ write_gint16 (fd, 0x0408, "0x0408 Id (Guides)"); /* 1032 */
+ /* write_pascalstring (fd, Name, "Id name"); */
+ write_gint16 (fd, 0, "Id name"); /* Set to null string (two zeros) */
+ write_gint32 (fd, 16 + 5 * n_guides, "0x0408 resource size");
+ /* Save grid and guide header */
+ write_gint32 (fd, 1, "grid/guide header version");
+ write_gint32 (fd, 576, "grid custom spacing horizontal");/* dpi*32/4??*/
+ write_gint32 (fd, 576, "grid custom spacing vertical"); /* dpi*32/4??*/
+ write_gint32 (fd, n_guides, "number of guides");
+
+ /* write the guides */
+ while ((guide_id = gimp_image_find_next_guide(image_id, guide_id)))
+ {
+ gchar orientation;
+ gint32 position;
+ orientation = gimp_image_get_guide_orientation(image_id, guide_id);
+ position = 32 * gimp_image_get_guide_position(image_id, guide_id);
+ orientation ^= 1; /* in the psd vert =0 , horiz = 1 */
+ write_gint32 (fd, position, "Position of guide");
+ write_gchar (fd, orientation, "Orientation of guide");
+ n_guides--;
+ }
+ if ((ftell(fd) & 1))
+ write_gchar(fd, 0, "pad byte");
+ if (n_guides != 0)
+ g_warning("Screwed up guide resource:: wrong number of guides\n");
+ IFDBG printf ("\tTotal length of 0x0400 resource: %d\n", (int) sizeof (gint16));
+ }
+
+ /* --------------- Write paths ------------------- */
+ save_paths (fd, image_id);
+
+ /* --------------- Write resolution data ------------------- */
+ {
+ gdouble xres = 0, yres = 0;
+ guint32 xres_fix, yres_fix;
+ GimpUnit g_unit;
+ gint16 psd_unit;
+
+ g_unit = gimp_image_get_unit (image_id);
+ gimp_image_get_resolution (image_id, &xres, &yres);
+
+ if (g_unit == GIMP_UNIT_MM)
+ {
+ psd_unit = PSD_UNIT_CM;
+ }
+ else
+ {
+ psd_unit = PSD_UNIT_INCH;
+ }
+
+ /* Don't convert resolution based on g_unit which is a display unit.
+ * PSD resolution is always in pixels/inch. */
+ xres_fix = xres * 65536.0 + .5; /* Convert to 16.16 fixed point */
+ yres_fix = yres * 65536.0 + .5; /* Convert to 16.16 fixed point */
+
+ xfwrite (fd, "8BIM", 4, "imageresources signature (for resolution)");
+ write_gint16(fd, 0x03ed, "0x03ed Id (resolution)"); /* 1005 */
+ write_gint16 (fd, 0, "Id name"); /* Set to null string (two zeros) */
+ write_gint32 (fd, 16, "0x0400 resource size");
+ write_gint32 (fd, xres_fix, "hRes (16.16 fixed point)");
+ write_gint16 (fd, psd_unit, "hRes unit");
+ write_gint16 (fd, psd_unit, "width unit");
+ write_gint32 (fd, yres_fix, "vRes (16.16 fixed point)");
+ write_gint16 (fd, psd_unit, "vRes unit");
+ write_gint16 (fd, psd_unit, "height unit");
+ }
+
+ /* --------------- Write Active Layer Number --------------- */
+
+ if (ActiveLayerPresent)
+ {
+ xfwrite (fd, "8BIM", 4, "imageresources signature");
+ write_gint16 (fd, 0x0400, "0x0400 Id"); /* 1024 */
+ /* write_pascalstring (fd, Name, "Id name"); */
+ write_gint16 (fd, 0, "Id name"); /* Set to null string (two zeros) */
+ write_gint32 (fd, sizeof (gint16), "0x0400 resource size");
+
+ /* Save title as gint16 (length always even) */
+
+ write_gint16 (fd, nActiveLayer, "active layer");
+
+ IFDBG printf ("\tTotal length of 0x0400 resource: %d\n", (int) sizeof (gint16));
+ }
+
+ /* --------------- Write ICC profile data ------------------- */
+ {
+ GimpColorProfile *profile;
+
+ profile = gimp_image_get_effective_color_profile (image_id);
+
+ if (profile)
+ {
+ const guint8 *icc_data;
+ gsize icc_length;
+
+ icc_data = gimp_color_profile_get_icc_profile (profile, &icc_length);
+
+ xfwrite (fd, "8BIM", 4, "imageresources signature");
+ write_gint16 (fd, 0x040f, "0x040f Id");
+ write_gint16 (fd, 0, "Id name"); /* Set to null string (two zeros) */
+ write_gint32 (fd, icc_length, "0x040f resource size");
+ xfwrite (fd, icc_data, icc_length, "ICC profile");
+
+ g_object_unref (profile);
+ }
+ }
+
+ /* --------------- Write Total Section Length --------------- */
+
+ eof_pos = ftell (fd);
+
+ fseek (fd, rsc_pos, SEEK_SET);
+ write_gint32 (fd, eof_pos - rsc_pos - sizeof (gint32), "image resources length");
+ IFDBG printf ("\tResource section total length: %d\n",
+ (int) (eof_pos - rsc_pos - sizeof (gint32)));
+
+ /* Return to EOF to continue writing */
+
+ fseek (fd, eof_pos, SEEK_SET);
+}
+
+static int
+get_compress_channel_data (guchar *channel_data,
+ gint32 channel_cols,
+ gint32 channel_rows,
+ gint32 stride,
+ gint32 bpc,
+ gint16 *LengthsTable,
+ guchar *remdata)
+{
+ gint i;
+ gint32 len; /* Length of compressed data */
+ guchar *start; /* Starting position of a row in channel_data */
+
+ stride /= bpc;
+
+ /* Pack channel data, and perform byte-order conversion */
+ switch (bpc)
+ {
+ case 1:
+ {
+ if (stride > 1)
+ {
+ const guint8 *src = (const guint8 *) channel_data;
+ guint8 *dest = (guint8 *) channel_data;
+
+ for (i = 0; i < channel_rows * channel_cols; i++)
+ {
+ *dest = *src;
+
+ dest++;
+ src += stride;
+ }
+ }
+ }
+ break;
+
+ case 2:
+ {
+ const guint16 *src = (const guint16 *) channel_data;
+ guint16 *dest = (guint16 *) channel_data;
+
+ for (i = 0; i < channel_rows * channel_cols; i++)
+ {
+ *dest = GUINT16_TO_BE (*src);
+
+ dest++;
+ src += stride;
+ }
+ }
+ break;
+
+ case 4:
+ {
+ const guint32 *src = (const guint32 *) channel_data;
+ guint32 *dest = (guint32 *) channel_data;
+
+ for (i = 0; i < channel_rows * channel_cols; i++)
+ {
+ *dest = GUINT32_TO_BE (*src);
+
+ dest++;
+ src += stride;
+ }
+ }
+ break;
+
+ default:
+ g_return_val_if_reached (0);
+ }
+
+ /* For every row in the channel */
+
+ len = 0;
+ for (i = 0; i < channel_rows; i++)
+ {
+ start = channel_data + i * channel_cols * bpc;
+
+ /* Create packed data for this row */
+ LengthsTable[i] = pack_pb_line (start, channel_cols * bpc,
+ &remdata[len]);
+ len += LengthsTable[i];
+ }
+
+ /* return((len + channel_rows * sizeof (gint16)) + sizeof (gint16));*/
+ return len;
+}
+
+/* Ported /from plug-ins/file-tiff/file-tiff-save.c */
+static void
+double_to_psd_fixed (gdouble value,
+ gchar *target)
+{
+ gdouble in, frac;
+ gint i, f;
+
+ frac = modf (value, &in);
+ if (frac < 0)
+ {
+ in -= 1;
+ frac += 1;
+ }
+
+ i = (gint) CLAMP (in, -16, 15);
+ f = CLAMP ((gint) (frac * 0xFFFFFF), 0, 0xFFFFFF);
+
+ target[0] = i & 0xFF;
+ target[1] = (f >> 16) & 0xFF;
+ target[2] = (f >> 8) & 0xFF;
+ target[3] = f & 0xFF;
+}
+
+/* Ported from /plug-ins/file-tiff/file-tiff-save.c */
+static void
+save_paths (FILE *fd,
+ gint32 image_id)
+{
+ gshort id = 0x07D0; /* Photoshop paths have IDs >= 2000 */
+ gdouble width = gimp_image_width (image_id);
+ gdouble height = gimp_image_height (image_id);
+ gint num_vectors;
+ gint *vectors;
+ gint v;
+ gint num_strokes;
+ gint *strokes;
+ gint s;
+
+ vectors = gimp_image_get_vectors (image_id, &num_vectors);
+
+ if (num_vectors <= 0)
+ return;
+
+ /* Only up to 997 paths supported */
+ for (v = 0; v < MIN (num_vectors, 1000); v++)
+ {
+ GString *data;
+ gchar *name, *nameend;
+ gsize len;
+ gint lenpos;
+ gchar pointrecord[26] = { 0, };
+ gchar *tmpname;
+ GError *err = NULL;
+
+ data = g_string_new ("8BIM");
+ g_string_append_c (data, id / 256);
+ g_string_append_c (data, id % 256);
+
+ /*
+ * - use iso8859-1 if possible
+ * - otherwise use UTF-8, prepended with \xef\xbb\xbf (Byte-Order-Mark)
+ */
+ name = gimp_item_get_name (vectors[v]);
+ tmpname = g_convert (name, -1, "iso8859-1", "utf-8", NULL, &len, &err);
+
+ if (tmpname && err == NULL)
+ {
+ g_string_append_c (data, MIN (len, 255));
+ g_string_append_len (data, tmpname, MIN (len, 255));
+ g_free (tmpname);
+ }
+ else
+ {
+ /* conversion failed, we fall back to UTF-8 */
+ len = g_utf8_strlen (name, 255 - 3); /* need three marker-bytes */
+
+ nameend = g_utf8_offset_to_pointer (name, len);
+ len = nameend - name; /* in bytes */
+ g_assert (len + 3 <= 255);
+
+ g_string_append_c (data, len + 3);
+ g_string_append_len (data, "\xEF\xBB\xBF", 3); /* Unicode 0xfeff */
+ g_string_append_len (data, name, len);
+
+ if (tmpname)
+ g_free (tmpname);
+ }
+
+ if (data->len % 2) /* padding to even size */
+ g_string_append_c (data, 0);
+ g_free (name);
+
+ lenpos = data->len;
+ g_string_append_len (data, "\0\0\0\0", 4); /* will be filled in later */
+ len = data->len; /* to calculate the data size later */
+
+ pointrecord[1] = 6; /* fill rule record */
+ g_string_append_len (data, pointrecord, 26);
+
+ strokes = gimp_vectors_get_strokes (vectors[v], &num_strokes);
+
+ for (s = 0; s < num_strokes; s++)
+ {
+ GimpVectorsStrokeType type;
+ gdouble *points;
+ gint num_points;
+ gboolean closed;
+ gint p = 0;
+
+ type = gimp_vectors_stroke_get_points (vectors[v], strokes[s],
+ &num_points, &points, &closed);
+
+ if (type != GIMP_VECTORS_STROKE_TYPE_BEZIER ||
+ num_points > 65535 ||
+ num_points % 6)
+ {
+ g_printerr ("psd-save: unsupported stroke type: "
+ "%d (%d points)\n", type, num_points);
+ continue;
+ }
+
+ memset (pointrecord, 0, 26);
+ pointrecord[1] = closed ? 0 : 3;
+ pointrecord[2] = (num_points / 6) / 256;
+ pointrecord[3] = (num_points / 6) % 256;
+ g_string_append_len (data, pointrecord, 26);
+
+ for (p = 0; p < num_points; p += 6)
+ {
+ pointrecord[1] = closed ? 2 : 5;
+
+ double_to_psd_fixed (points[p+1] / height, pointrecord + 2);
+ double_to_psd_fixed (points[p+0] / width, pointrecord + 6);
+ double_to_psd_fixed (points[p+3] / height, pointrecord + 10);
+ double_to_psd_fixed (points[p+2] / width, pointrecord + 14);
+ double_to_psd_fixed (points[p+5] / height, pointrecord + 18);
+ double_to_psd_fixed (points[p+4] / width, pointrecord + 22);
+
+ g_string_append_len (data, pointrecord, 26);
+ }
+ }
+
+ g_free (strokes);
+
+ /* fix up the length */
+
+ len = data->len - len;
+ data->str[lenpos + 0] = (len & 0xFF000000) >> 24;
+ data->str[lenpos + 1] = (len & 0x00FF0000) >> 16;
+ data->str[lenpos + 2] = (len & 0x0000FF00) >> 8;
+ data->str[lenpos + 3] = (len & 0x000000FF) >> 0;
+
+ xfwrite (fd, data->str, data->len, "path resources data");
+ g_string_free (data, TRUE);
+ id += 0x01;
+ }
+
+ g_free (vectors);
+}
+
+static void
+save_layer_and_mask (FILE *fd,
+ gint32 image_id)
+{
+ gint i,j;
+ gint idChannel;
+ gint offset_x; /* X offset for each layer */
+ gint offset_y; /* Y offset for each layer */
+ gint32 layerWidth; /* Width of each layer */
+ gint32 layerHeight; /* Height of each layer */
+ const gchar *blendMode; /* Blending mode of the layer */
+ 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 */
+ gchar *layerName; /* Layer name */
+ gint mask; /* Layer mask */
+ gint depth; /* Layer group nesting depth */
+ gint bpc; /* Image BPC */
+
+ glong eof_pos; /* Position: End of file */
+ glong ExtraDataPos; /* Position: Extra data length */
+ glong LayerMaskPos; /* Position: Layer & Mask section length */
+ glong LayerInfoPos; /* Position: Layer info section length*/
+ glong **ChannelLengthPos; /* Position: Channel length */
+
+
+ IFDBG printf (" Function: save_layer_and_mask\n");
+
+ /* Create first array dimension (layers, channels) */
+
+ ChannelLengthPos = g_newa (glong *, PSDImageData.nLayers);
+
+ /* Layer and mask information section */
+
+ LayerMaskPos = ftell (fd);
+ write_gint32 (fd, 0, "layers & mask information length");
+
+ /* Layer info section */
+
+ LayerInfoPos = ftell (fd);
+ write_gint32 (fd, 0, "layers info section length");
+
+ /* Layer structure section */
+
+ if (gimp_drawable_has_alpha (PSDImageData.merged_layer))
+ write_gint16 (fd, -PSDImageData.nLayers, "Layer structure count");
+ else
+ write_gint16 (fd, PSDImageData.nLayers, "Layer structure count");
+
+ depth = 0;
+
+ bpc = get_bpc (image_id);
+
+ /* Layer records section */
+ /* GIMP layers must be written in reverse order */
+
+ for (i = PSDImageData.nLayers - 1; i >= 0; i--)
+ {
+ gint hasMask = 0;
+
+ if (PSDImageData.lLayers[i].type == PSD_LAYER_TYPE_LAYER)
+ {
+ gimp_drawable_offsets (PSDImageData.lLayers[i].id, &offset_x, &offset_y);
+ layerWidth = gimp_drawable_width (PSDImageData.lLayers[i].id);
+ layerHeight = gimp_drawable_height (PSDImageData.lLayers[i].id);
+ }
+ else
+ {
+ /* groups don't specify their dimensions, and have empty channel
+ * data
+ */
+ offset_x = 0;
+ offset_y = 0;
+ layerWidth = 0;
+ layerHeight = 0;
+ }
+
+ IFDBG printf ("\tLayer number: %d\n", i);
+ IFDBG
+ {
+ const gchar *type;
+
+ switch (PSDImageData.lLayers[i].type)
+ {
+ case PSD_LAYER_TYPE_LAYER: type = "normal layer"; break;
+ case PSD_LAYER_TYPE_GROUP_START: type = "group start marker"; break;
+ case PSD_LAYER_TYPE_GROUP_END: type = "group end marker"; break;
+ }
+
+ printf ("\t\tType: %s\n", type);
+ }
+ IFDBG printf ("\t\tX offset: %d\n", offset_x);
+ IFDBG printf ("\t\tY offset: %d\n", offset_y);
+ IFDBG printf ("\t\tWidth: %d\n", layerWidth);
+ IFDBG printf ("\t\tHeight: %d\n", layerHeight);
+
+ write_gint32 (fd, offset_y, "Layer top");
+ write_gint32 (fd, offset_x, "Layer left");
+ write_gint32 (fd, offset_y + layerHeight, "Layer bottom");
+ write_gint32 (fd, offset_x + layerWidth, "Layer right");
+
+ hasMask = (PSDImageData.lLayers[i].type != PSD_LAYER_TYPE_GROUP_END &&
+ gimp_layer_get_mask (PSDImageData.lLayers[i].id) != -1);
+ nChannelsLayer = nChansLayer (PSDImageData.baseType,
+ gimp_drawable_has_alpha (PSDImageData.lLayers[i].id),
+ hasMask);
+
+
+ write_gint16 (fd, nChannelsLayer, "Number channels in the layer");
+ IFDBG printf ("\t\tNumber of channels: %d\n", nChannelsLayer);
+
+ /* Create second array dimension (layers, channels) */
+
+ ChannelLengthPos[i] = g_new (glong, nChannelsLayer);
+
+ /* Try with gimp_drawable_bpp() */
+
+ for (j = 0; j < nChannelsLayer; j++)
+ {
+ if (gimp_drawable_has_alpha (PSDImageData.lLayers[i].id))
+ idChannel = j - 1;
+ else
+ idChannel = j;
+ if (hasMask && (j+1 == nChannelsLayer)) /* Last channel ... */
+ idChannel = -2; /* ... will be layer mask */
+
+ write_gint16 (fd, idChannel, "Channel ID");
+ IFDBG printf ("\t\t\tChannel Identifier: %d\n", idChannel);
+
+ /* Write the length assuming no compression. In case there is,
+ will modify it later when writing data. */
+
+ ChannelLengthPos[i][j] = ftell (fd);
+ ChanSize = sizeof (gint16) + (layerWidth * layerHeight * bpc);
+
+ write_gint32 (fd, ChanSize, "Channel Size");
+ IFDBG printf ("\t\t\tLength: %d\n", ChanSize);
+ }
+
+ xfwrite (fd, "8BIM", 4, "blend mode signature");
+
+ blendMode = psd_lmode_layer (PSDImageData.lLayers[i].id, FALSE);
+ IFDBG printf ("\t\tBlend mode: %s\n", blendMode);
+ xfwrite (fd, blendMode, 4, "blend mode key");
+
+ layerOpacity = RINT ((gimp_layer_get_opacity (PSDImageData.lLayers[i].id) * 255.0) / 100.0);
+ IFDBG printf ("\t\tOpacity: %u\n", layerOpacity);
+ write_gchar (fd, layerOpacity, "Opacity");
+
+ if (gimp_layer_get_composite_mode (PSDImageData.lLayers[i].id) == GIMP_LAYER_COMPOSITE_CLIP_TO_BACKDROP)
+ write_gchar (fd, 1, "Clipping");
+ else
+ write_gchar (fd, 0, "Clipping");
+
+ flags = 0;
+ if (gimp_layer_get_lock_alpha (PSDImageData.lLayers[i].id)) flags |= 1;
+ if (! gimp_item_get_visible (PSDImageData.lLayers[i].id)) flags |= 2;
+ if (PSDImageData.lLayers[i].type != PSD_LAYER_TYPE_LAYER) flags |= 0x18;
+ IFDBG printf ("\t\tFlags: %u\n", flags);
+ write_gchar (fd, flags, "Flags");
+
+ /* Padding byte to make the length even */
+ write_gchar (fd, 0, "Filler");
+
+ ExtraDataPos = ftell (fd); /* Position of Extra Data size */
+ write_gint32 (fd, 0, "Extra data size");
+
+ if (hasMask)
+ {
+ gint maskOffset_x;
+ gint maskOffset_y;
+ gint maskWidth;
+ gint maskHeight;
+ gboolean apply;
+
+ mask = gimp_layer_get_mask (PSDImageData.lLayers[i].id);
+
+ gimp_drawable_offsets (mask, &maskOffset_x, &maskOffset_y);
+
+ maskWidth = gimp_drawable_width (mask);
+ maskHeight = gimp_drawable_height (mask);
+ apply = gimp_layer_get_apply_mask (PSDImageData.lLayers[i].id);
+
+ IFDBG printf ("\t\tLayer mask size: %d\n", 20);
+ write_gint32 (fd, 20, "Layer mask size");
+ write_gint32 (fd, maskOffset_y, "Layer mask top");
+ write_gint32 (fd, maskOffset_x, "Layer mask left");
+ write_gint32 (fd, maskOffset_y + maskHeight, "Layer mask bottom");
+ write_gint32 (fd, maskOffset_x + maskWidth, "Layer mask right");
+ write_gchar (fd, 0, "Layer mask default color");
+ flags = (0 | /* position relative to layer */
+ (apply ? 0 : 1) << 1 | /* layer mask disabled */
+ 0 << 2); /* invert layer mask */
+ write_gchar (fd, flags, "Layer mask flags");
+ write_gint16 (fd, 0, "Layer mask Padding");
+ }
+ else
+ {
+ /* NOTE Writing empty Layer mask / adjustment layer data */
+ write_gint32 (fd, 0, "Layer mask size");
+ IFDBG printf ("\t\tLayer mask size: %d\n", 0);
+ }
+
+ /* NOTE Writing empty Layer blending ranges data */
+ write_gint32 (fd, 0, "Layer blending size");
+ IFDBG printf ("\t\tLayer blending size: %d\n", 0);
+
+ if (PSDImageData.lLayers[i].type != PSD_LAYER_TYPE_GROUP_END)
+ layerName = gimp_item_get_name (PSDImageData.lLayers[i].id);
+ else
+ layerName = g_strdup ("</Layer group>");
+ write_pascalstring (fd, layerName, 4, "layer name");
+ IFDBG printf ("\t\tLayer name: %s\n", layerName);
+
+ /* Additional layer information blocks */
+ /* Unicode layer name */
+ write_datablock_luni(fd, layerName, "luni extra data block");
+
+ g_free (layerName);
+
+ /* Layer color tag */
+ xfwrite (fd, "8BIMlclr", 8, "sheet color signature");
+ write_gint32 (fd, 8, "sheet color size");
+ write_gint16 (fd,
+ gimp_to_psd_layer_color_tag(gimp_item_get_color_tag(PSDImageData.lLayers[i].id)),
+ "sheet color code");
+ write_gint16 (fd, 0, "sheet color unused value");
+ write_gint16 (fd, 0, "sheet color unused value");
+ write_gint16 (fd, 0, "sheet color unused value");
+
+ /* Group layer section divider */
+ if (PSDImageData.lLayers[i].type != PSD_LAYER_TYPE_LAYER)
+ {
+ gint32 size;
+ gint32 type;
+
+ size = 12;
+
+ if (PSDImageData.lLayers[i].type == PSD_LAYER_TYPE_GROUP_START)
+ {
+ type = gimp_item_get_expanded (PSDImageData.lLayers[i].id) ? 1 : 2;
+
+ depth--;
+ }
+ else
+ {
+ type = 3;
+
+ depth++;
+ }
+
+ blendMode = psd_lmode_layer (PSDImageData.lLayers[i].id, TRUE);
+
+ if (type < 3 || depth <= 5)
+ {
+ xfwrite (fd, "8BIMlsct", 8, "section divider");
+ }
+ else
+ {
+ /* layer groups whose nesting depth is above 5 are only supported
+ * by Photoshop CS5 and up, and their end markers use the
+ * (undocumented) "lsdk" key, instead of "lsct".
+ */
+ xfwrite (fd, "8BIMlsdk", 8, "nested section divider");
+ }
+ write_gint32 (fd, size, "section divider size");
+ write_gint32 (fd, type, "section divider type");
+ xfwrite (fd, "8BIM", 4, "section divider blend mode signature");
+ xfwrite (fd, blendMode, 4, "section divider blend mode key");
+ }
+
+ /* Write real length for: Extra data */
+
+ eof_pos = ftell (fd);
+
+ fseek (fd, ExtraDataPos, SEEK_SET);
+ write_gint32 (fd, eof_pos - ExtraDataPos - sizeof (gint32), "Extra data size");
+ IFDBG printf ("\t\tExtraData size: %d\n",
+ (int) (eof_pos - ExtraDataPos - sizeof (gint32)));
+
+ /* Return to EOF to continue writing */
+
+ fseek (fd, eof_pos, SEEK_SET);
+ }
+
+
+ /* Channel image data section */
+ /* Gimp layers must be written in reverse order */
+
+ for (i = PSDImageData.nLayers - 1; i >= 0; i--)
+ {
+ gimp_progress_update ((PSDImageData.nLayers - i - 1.0) / (PSDImageData.nLayers + 1.0));
+
+ IFDBG printf ("\t\tWriting pixel data for layer slot %d\n", i);
+ write_pixel_data (fd, PSDImageData.lLayers[i].id, ChannelLengthPos[i], 0,
+ PSDImageData.lLayers[i].type != PSD_LAYER_TYPE_GROUP_END);
+ g_free (ChannelLengthPos[i]);
+ }
+
+ gimp_progress_update (PSDImageData.nLayers / (PSDImageData.nLayers + 1.0));
+ eof_pos = ftell (fd);
+
+ /* Write actual size of Layer info section */
+
+ fseek (fd, LayerInfoPos, SEEK_SET);
+ write_gint32 (fd, eof_pos - LayerInfoPos - sizeof (gint32), "layers info section length");
+ IFDBG printf ("\t\tTotal layers info section length: %d\n",
+ (int) (eof_pos - LayerInfoPos - sizeof (gint32)));
+
+ /* Write actual size of Layer and mask information section */
+
+ fseek (fd, LayerMaskPos, SEEK_SET);
+ write_gint32 (fd, eof_pos - LayerMaskPos - sizeof (gint32), "layers & mask information length");
+ IFDBG printf ("\t\tTotal layers & mask information length: %d\n",
+ (int) (eof_pos - LayerMaskPos - sizeof (gint32)));
+
+ /* Return to EOF to continue writing */
+
+ fseek (fd, eof_pos, SEEK_SET);
+}
+
+static void
+write_pixel_data (FILE *fd,
+ gint32 drawableID,
+ glong *ChanLenPosition,
+ gint32 ltable_offset,
+ gboolean write_mask)
+{
+ GeglBuffer *buffer = gimp_drawable_get_buffer (drawableID);
+ const Babl *format;
+ gint32 maskID;
+ gint32 tile_height = gimp_tile_height ();
+ gint32 height = gegl_buffer_get_height (buffer);
+ gint32 width = gegl_buffer_get_width (buffer);
+ gint32 bytes;
+ gint32 components;
+ gint32 bpc;
+ gint32 colors;
+ gint32 y;
+ gint32 len; /* Length of compressed data */
+ gint16 *LengthsTable; /* Lengths of every compressed row */
+ guchar *rledata; /* Compressed data from a region */
+ guchar *data; /* Temporary copy of pixel data */
+ glong length_table_pos; /* position in file of the length table */
+ int i, j;
+
+ IFDBG printf (" Function: write_pixel_data, drw %d, lto %d\n",
+ drawableID, ltable_offset);
+
+ if (write_mask)
+ maskID = gimp_layer_get_mask (drawableID);
+ else
+ maskID = -1;
+
+ /* groups have empty channel data, but may have a mask */
+ if (gimp_item_is_group (drawableID) && maskID == -1)
+ {
+ width = 0;
+ height = 0;
+ }
+
+ if (gimp_item_is_channel (drawableID))
+ format = get_channel_format (drawableID);
+ else
+ format = get_pixel_format (drawableID);
+
+ bytes = babl_format_get_bytes_per_pixel (format);
+ components = babl_format_get_n_components (format);
+ bpc = bytes / components;
+
+ colors = components;
+
+ if (gimp_drawable_has_alpha (drawableID) &&
+ ! gimp_drawable_is_indexed (drawableID))
+ colors -= 1;
+
+ LengthsTable = g_new (gint16, height);
+ rledata = g_new (guchar, (MIN (height, tile_height) *
+ (width + 10 + (width / 100))) * bpc);
+
+ data = g_new (guchar, MIN (height, tile_height) * width * bytes);
+
+ /* groups have empty channel data */
+ if (gimp_item_is_group (drawableID))
+ {
+ width = 0;
+ height = 0;
+ }
+
+ for (i = 0; i < components; i++)
+ {
+ gint chan;
+
+ len = 0;
+
+ if (components != colors && ltable_offset == 0) /* Need to write alpha channel first, except in image data section */
+ {
+ if (i == 0)
+ {
+ chan = components - 1;
+ }
+ else
+ {
+ chan = i - 1;
+ }
+ }
+ else
+ {
+ chan = i;
+ }
+
+ if (ChanLenPosition)
+ {
+ write_gint16 (fd, 1, "Compression type (RLE)");
+ len += 2;
+ }
+
+ if (ltable_offset > 0)
+ {
+ length_table_pos = ltable_offset + 2 * chan * height;
+ }
+ else
+ {
+ length_table_pos = ftell(fd);
+
+ xfwrite (fd, LengthsTable, height * sizeof(gint16),
+ "Dummy RLE length");
+ len += height * sizeof(gint16);
+ IF_DEEP_DBG printf ("\t\t\t\t. ltable, pos %ld len %d\n", length_table_pos, len);
+ }
+
+ for (y = 0; y < height; y += tile_height)
+ {
+ int tlen;
+ gegl_buffer_get (buffer,
+ GEGL_RECTANGLE (0, y,
+ width,
+ MIN (height - y, tile_height)),
+ 1.0, format, data,
+ GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
+ tlen = get_compress_channel_data (&data[chan * bpc],
+ width,
+ MIN(height - y, tile_height),
+ bytes, bpc,
+ &LengthsTable[y],
+ rledata);
+ len += tlen;
+ xfwrite (fd, rledata, tlen, "Compressed pixel data");
+ IF_DEEP_DBG printf ("\t\t\t\t. Writing compressed pixels, stream of %d\n", tlen);
+ }
+
+ /* Write compressed lengths table */
+ fseek (fd, length_table_pos, SEEK_SET);
+ for (j = 0; j < height; j++) /* write real length table */
+ write_gint16 (fd, LengthsTable[j], "RLE length");
+
+ if (ChanLenPosition) /* Update total compressed length */
+ {
+ fseek (fd, ChanLenPosition[i], SEEK_SET);
+ write_gint32 (fd, len, "channel data length");
+ IFDBG printf ("\t\tUpdating data len to %d\n", len);
+ }
+ fseek (fd, 0, SEEK_END);
+ IF_DEEP_DBG printf ("\t\t\t\t. Cur pos %ld\n", ftell(fd));
+ }
+
+ /* Write layer mask, as last channel, id -2 */
+ if (maskID != -1)
+ {
+ GeglBuffer *mbuffer = gimp_drawable_get_buffer (maskID);
+ const Babl *mformat = get_mask_format(maskID);
+
+ width = gegl_buffer_get_width (buffer);
+ height = gegl_buffer_get_height (buffer);
+
+ len = 0;
+
+ if (ChanLenPosition)
+ {
+ write_gint16 (fd, 1, "Compression type (RLE)");
+ len += 2;
+ IF_DEEP_DBG printf ("\t\t\t\t. ChanLenPos, len %d\n", len);
+ }
+
+ if (ltable_offset > 0)
+ {
+ length_table_pos = ltable_offset + 2 * (components+1) * height;
+ IF_DEEP_DBG printf ("\t\t\t\t. ltable, pos %ld\n",
+ length_table_pos);
+ }
+ else
+ {
+ length_table_pos = ftell(fd);
+
+ xfwrite (fd, LengthsTable, height * sizeof(gint16),
+ "Dummy RLE length");
+ len += height * sizeof(gint16);
+ IF_DEEP_DBG printf ("\t\t\t\t. ltable, pos %ld len %d\n",
+ length_table_pos, len);
+ }
+
+ for (y = 0; y < height; y += tile_height)
+ {
+ int tlen;
+ gegl_buffer_get (mbuffer,
+ GEGL_RECTANGLE (0, y,
+ width,
+ MIN (height - y, tile_height)),
+ 1.0, mformat, data,
+ GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
+ tlen = get_compress_channel_data (&data[0],
+ width,
+ MIN(height - y, tile_height),
+ bpc, bpc,
+ &LengthsTable[y],
+ rledata);
+ len += tlen;
+ xfwrite (fd, rledata, tlen, "Compressed mask data");
+ IF_DEEP_DBG printf ("\t\t\t\t. Writing compressed mask, stream of %d\n", tlen);
+ }
+
+ /* Write compressed lengths table */
+ fseek (fd, length_table_pos, SEEK_SET); /*POS WHERE???*/
+ for (j = 0; j < height; j++) /* write real length table */
+ {
+ write_gint16 (fd, LengthsTable[j], "RLE length");
+ IF_DEEP_DBG printf ("\t\t\t\t. Updating RLE len %d\n",
+ LengthsTable[j]);
+ }
+
+ if (ChanLenPosition) /* Update total compressed length */
+ {
+ /* Mask follows other components so use that as offset. */
+ fseek (fd, ChanLenPosition[components], SEEK_SET);
+
+ write_gint32 (fd, len, "channel data length");
+ IFDBG printf ("\t\tUpdating data len to %d, at %ld\n", len, ftell(fd));
+ }
+ fseek (fd, 0, SEEK_END);
+ IF_DEEP_DBG printf ("\t\t\t\t. Cur pos %ld\n", ftell(fd));
+
+ g_object_unref (mbuffer);
+ }
+
+ g_object_unref (buffer);
+
+ g_free (data);
+ g_free (rledata);
+ g_free (LengthsTable);
+}
+
+static void
+save_data (FILE *fd,
+ gint32 image_id)
+{
+ gint ChanCount;
+ gint i, j;
+ gint32 imageHeight; /* Height of image */
+ glong offset; /* offset in file of rle lengths */
+ gint chan;
+
+ IFDBG printf (" Function: save_data\n");
+
+ ChanCount = (PSDImageData.nChannels +
+ nChansLayer (PSDImageData.baseType,
+ gimp_drawable_has_alpha (PSDImageData.merged_layer),
+ 0));
+
+ imageHeight = gimp_image_height (image_id);
+
+ write_gint16 (fd, 1, "RLE compression");
+
+ /* All line lengths go before the rle pixel data */
+
+ offset = ftell(fd); /* Offset in file of line lengths */
+
+ for (i = 0; i < ChanCount; i++)
+ for (j = 0; j < imageHeight; j++)
+ write_gint16 (fd, 0, "junk line lengths");
+
+ IFDBG printf ("\t\tWriting compressed image data\n");
+ write_pixel_data (fd, PSDImageData.merged_layer,
+ NULL, offset, FALSE);
+
+ chan = nChansLayer (PSDImageData.baseType,
+ gimp_drawable_has_alpha(PSDImageData.merged_layer), 0);
+
+ for (i = 0; i < PSDImageData.nChannels; i++)
+ {
+ IFDBG printf ("\t\tWriting compressed channel data for channel %d\n",
+ i);
+ write_pixel_data (fd, PSDImageData.lChannels[i], NULL,
+ offset + 2*imageHeight*chan, FALSE); //check how imgs are channels here
+ chan++;
+ }
+}
+
+static gint32
+create_merged_image (gint32 image_id)
+{
+ gint32 projection;
+
+ projection = gimp_layer_new_from_visible (image_id, image_id, "psd-save");
+
+ if (! gimp_drawable_has_alpha (projection))
+ return projection;
+
+ if (gimp_image_base_type (image_id) != GIMP_INDEXED)
+ {
+ GeglBuffer *buffer = gimp_drawable_get_buffer (projection);
+ const Babl *format = get_pixel_format (projection);
+ gboolean transparency_found = FALSE;
+ gint bpp = babl_format_get_bytes_per_pixel (format);
+ GeglBufferIterator *iter;
+
+ iter = gegl_buffer_iterator_new (buffer, NULL, 0, format,
+ GEGL_ACCESS_READWRITE, GEGL_ABYSS_NONE, 1);
+
+ while (gegl_buffer_iterator_next (iter))
+ {
+ guchar *d = iter->items[0].data;
+ gint i;
+
+ for (i = 0; i < iter->length; i++)
+ {
+ gint32 alpha = d[bpp - 1];
+
+ if (alpha < 255)
+ {
+ gint c;
+
+ transparency_found = TRUE;
+
+ /* blend against white, photoshop does this. */
+ for (c = 0; c < bpp - 1; c++)
+ d[c] = ((guint32) d[c] * alpha + 128) / 255 + 255 - alpha;
+ }
+
+ d += bpp;
+ }
+ }
+
+ g_object_unref (buffer);
+
+ if (! transparency_found)
+ gimp_layer_flatten (projection);
+ }
+ else
+ {
+ gimp_layer_flatten (projection); /* PSDs don't support transparency information in indexed images*/
+ }
+
+ return projection;
+}
+
+static void
+get_image_data (gint32 image_id)
+{
+ IFDBG printf (" Function: get_image_data\n");
+
+ PSDImageData.compression = FALSE;
+
+ PSDImageData.image_height = gimp_image_height (image_id);
+ IFDBG printf ("\tGot number of rows: %d\n", PSDImageData.image_height);
+
+ PSDImageData.image_width = gimp_image_width (image_id);
+ IFDBG printf ("\tGot number of cols: %d\n", PSDImageData.image_width);
+
+ PSDImageData.baseType = gimp_image_base_type (image_id);
+ IFDBG printf ("\tGot base type: %d\n", PSDImageData.baseType);
+
+ PSDImageData.merged_layer = create_merged_image (image_id);
+
+ PSDImageData.lChannels = gimp_image_get_channels (image_id,
+ &PSDImageData.nChannels);
+ IFDBG printf ("\tGot number of channels: %d\n", PSDImageData.nChannels);
+
+ PSDImageData.lLayers = image_get_all_layers (image_id,
+ &PSDImageData.nLayers);
+ IFDBG printf ("\tGot number of layers: %d\n", PSDImageData.nLayers);
+}
+
+static void
+clear_image_data (void)
+{
+ IFDBG printf (" Function: clear_image_data\n");
+
+ g_free (PSDImageData.lChannels);
+ PSDImageData.lChannels = NULL;
+
+ g_free (PSDImageData.lLayers);
+ PSDImageData.lLayers = NULL;
+}
+
+gboolean
+save_image (const gchar *filename,
+ gint32 image_id,
+ GError **error)
+{
+ FILE *fd;
+ gint i;
+ GeglBuffer *buffer;
+
+ IFDBG printf (" Function: save_image\n");
+
+ if (gimp_image_width (image_id) > 30000 ||
+ gimp_image_height (image_id) > 30000)
+ {
+ 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_filename_to_utf8 (filename));
+ return FALSE;
+ }
+
+ gimp_progress_init_printf (_("Exporting '%s'"),
+ gimp_filename_to_utf8 (filename));
+
+ get_image_data (image_id);
+
+ /* Need to check each of the layers size individually also */
+ for (i = 0; i < PSDImageData.nLayers; i++)
+ {
+ if (PSDImageData.lLayers[i].type == PSD_LAYER_TYPE_LAYER)
+ {
+ buffer = gimp_drawable_get_buffer (PSDImageData.lLayers[i].id);
+ if (gegl_buffer_get_width (buffer) > 30000 || gegl_buffer_get_height (buffer) > 30000)
+ {
+ 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_filename_to_utf8 (filename));
+ clear_image_data ();
+ return FALSE;
+ }
+ g_object_unref (buffer);
+ }
+ }
+
+ fd = g_fopen (filename, "wb");
+ if (fd == NULL)
+ {
+ g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
+ _("Could not open '%s' for writing: %s"),
+ gimp_filename_to_utf8 (filename), g_strerror (errno));
+ clear_image_data ();
+ return FALSE;
+ }
+
+ IFDBG g_print ("\tFile '%s' has been opened\n",
+ gimp_filename_to_utf8 (filename));
+
+ save_header (fd, image_id);
+ save_color_mode_data (fd, image_id);
+ save_resources (fd, image_id);
+
+ /* PSD format does not support layers in indexed images */
+
+ if (PSDImageData.baseType == GIMP_INDEXED)
+ write_gint32 (fd, 0, "layers info section length");
+ else
+ save_layer_and_mask (fd, image_id);
+
+ /* If this is an indexed image, write now channel and layer info */
+
+ save_data (fd, image_id);
+
+ /* Delete merged image now */
+
+ gimp_item_delete (PSDImageData.merged_layer);
+
+ clear_image_data ();
+
+ IFDBG printf ("----- Closing PSD file, done -----\n\n");
+
+ fclose (fd);
+
+ gimp_progress_update (1.0);
+
+ return TRUE;
+}
+
+static gint
+get_bpc (gint32 image_id)
+{
+ switch (gimp_image_get_precision (image_id))
+ {
+ case GIMP_PRECISION_U8_LINEAR:
+ case GIMP_PRECISION_U8_GAMMA:
+ return 1;
+
+ case GIMP_PRECISION_U16_LINEAR:
+ case GIMP_PRECISION_U16_GAMMA:
+ case GIMP_PRECISION_HALF_LINEAR:
+ case GIMP_PRECISION_HALF_GAMMA:
+ return 2;
+
+ case GIMP_PRECISION_U32_LINEAR:
+ case GIMP_PRECISION_U32_GAMMA:
+ case GIMP_PRECISION_FLOAT_LINEAR:
+ case GIMP_PRECISION_FLOAT_GAMMA:
+ default:
+ /* FIXME: we *should* encode the image as u32 in this case, but simply
+ * using the same code as for the other cases produces invalid psd files
+ * (they're rejected by photoshop, although they can be read by the
+ * corresponding psd-load.c code, which in turn can't actually read
+ * photoshop-generated u32 files.)
+ *
+ * simply encode the image as u16 for now.
+ */
+ /* return 4; */
+ return 2;
+ }
+}
+
+static const Babl *
+get_pixel_format (gint32 drawableID)
+{
+ gint32 image_id = gimp_item_get_image (drawableID);
+ const gchar *model;
+ gint bpc;
+ gchar format[32];
+
+ switch (gimp_drawable_type (drawableID))
+ {
+ case GIMP_GRAY_IMAGE:
+ model = "Y'";
+ break;
+
+ case GIMP_GRAYA_IMAGE:
+ model = "Y'A";
+ break;
+
+ case GIMP_RGB_IMAGE:
+ model = "R'G'B'";
+ break;
+
+ case GIMP_RGBA_IMAGE:
+ model = "R'G'B'A";
+ break;
+
+ case GIMP_INDEXED_IMAGE:
+ case GIMP_INDEXEDA_IMAGE:
+ return gimp_drawable_get_format (drawableID);
+
+ default:
+ g_return_val_if_reached (NULL);
+ }
+
+ bpc = get_bpc (image_id);
+
+ sprintf (format, "%s u%d", model, 8 * bpc);
+
+ return babl_format (format);
+}
+
+static const Babl *
+get_channel_format (gint32 drawableID)
+{
+ gint32 image_id = gimp_item_get_image (drawableID);
+ gint bpc;
+ gchar format[32];
+
+ /* see gimp_image_get_channel_format() */
+ if (gimp_image_get_precision (image_id) == GIMP_PRECISION_U8_GAMMA)
+ return babl_format ("Y' u8");
+
+ bpc = get_bpc (image_id);
+
+ sprintf (format, "Y u%d", 8 * bpc);
+
+ return babl_format (format);
+}
+
+static const Babl *
+get_mask_format (gint32 drawableID)
+{
+ gint32 image_id = gimp_item_get_image (drawableID);
+ gint bpc;
+ gchar format[32];
+
+ bpc = get_bpc (image_id);
+
+ sprintf (format, "Y u%d", 8 * bpc);
+
+ return babl_format (format);
+}
+
+static void
+append_layers (const gint *layers,
+ gint n_layers,
+ GArray *array)
+{
+ gint i;
+
+ for (i = 0; i < n_layers; i++)
+ {
+ PSD_Layer layer = {};
+ gboolean is_group;
+
+ layer.id = layers[i];
+
+ is_group = gimp_item_is_group (layer.id);
+
+ if (! is_group)
+ layer.type = PSD_LAYER_TYPE_LAYER;
+ else
+ layer.type = PSD_LAYER_TYPE_GROUP_START;
+
+ g_array_append_val (array, layer);
+
+ if (is_group)
+ {
+ gint32 *group_layers;
+ gint n;
+
+ group_layers = gimp_item_get_children (layer.id, &n);
+ append_layers (group_layers, n, array);
+ g_free (group_layers);
+
+ layer.type = PSD_LAYER_TYPE_GROUP_END;
+ g_array_append_val (array, layer);
+ }
+ }
+}
+
+static PSD_Layer *
+image_get_all_layers (gint32 image_id,
+ gint *n_layers)
+{
+ GArray *array = g_array_new (FALSE, FALSE, sizeof (PSD_Layer));
+ gint32 *layers;
+ gint n;
+
+ layers = gimp_image_get_layers (image_id, &n);
+
+ append_layers (layers, n, array);
+
+ g_free (layers);
+
+ *n_layers = array->len;
+
+ return (PSD_Layer *) g_array_free (array, FALSE);
+}