summaryrefslogtreecommitdiffstats
path: root/plug-ins/common/file-psp.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 03:13:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 03:13:10 +0000
commit3c57dd931145d43f2b0aef96c4d178135956bf91 (patch)
tree3de698981e9f0cc2c4f9569b19a5f3595e741f6b /plug-ins/common/file-psp.c
parentInitial commit. (diff)
downloadgimp-3c57dd931145d43f2b0aef96c4d178135956bf91.tar.xz
gimp-3c57dd931145d43f2b0aef96c4d178135956bf91.zip
Adding upstream version 2.10.36.upstream/2.10.36
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'plug-ins/common/file-psp.c')
-rw-r--r--plug-ins/common/file-psp.c2580
1 files changed, 2580 insertions, 0 deletions
diff --git a/plug-ins/common/file-psp.c b/plug-ins/common/file-psp.c
new file mode 100644
index 0000000..6a6b93d
--- /dev/null
+++ b/plug-ins/common/file-psp.c
@@ -0,0 +1,2580 @@
+/* GIMP plug-in to load and export Paint Shop Pro files (.PSP and .TUB)
+ *
+ * Copyright (C) 1999 Tor Lillqvist
+ *
+ * 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/>.
+ */
+
+/*
+ *
+ * Work in progress! Doesn't handle exporting yet.
+ *
+ * For a copy of the PSP file format documentation, surf to
+ * http://www.jasc.com.
+ *
+ */
+
+#define LOAD_PROC "file-psp-load"
+#define SAVE_PROC "file-psp-save"
+#define PLUG_IN_BINARY "file-psp"
+#define PLUG_IN_ROLE "gimp-file-psp"
+
+/* set to the level of debugging output you want, 0 for none */
+#define PSP_DEBUG 0
+
+#define IFDBG(level) if (PSP_DEBUG >= level)
+
+#include "config.h"
+
+#include <errno.h>
+#include <string.h>
+#include <zlib.h>
+
+#include <glib/gstdio.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+#include <libgimpbase/gimpparasiteio.h>
+
+#include "libgimp/stdplugins-intl.h"
+
+
+/* Note that the upcoming PSP version 6 writes PSP file format version
+ * 4.0, but the documentation for that apparently isn't publicly
+ * available (yet). The format is luckily designed to be somwehat
+ * downward compatible, however. The semantics of many of the
+ * additional fields and block types can be relatively easily reverse
+ * engineered.
+ */
+
+/* The following was cut and pasted from the PSP file format
+ * documentation version 3.0.(Minor stylistic changes done.)
+ *
+ *
+ * To be on the safe side, here is the whole copyright notice from the
+ * specification:
+ *
+ * The Paint Shop Pro File Format Specification (the Specification) is
+ * copyright 1998 by Jasc Software, Inc. Jasc grants you a
+ * nonexclusive license to use the Specification for the sole purposes
+ * of developing software products(s) incorporating the
+ * Specification. You are also granted the right to identify your
+ * software product(s) as incorporating the Paint Shop Pro Format
+ * (PSP) provided that your software in incorporating the
+ * Specification complies with the terms, definitions, constraints and
+ * specifications contained in the Specification and subject to the
+ * following: DISCLAIMER OF WARRANTIES. THE SPECIFICATION IS PROVIDED
+ * AS IS. JASC DISCLAIMS ALL OTHER WARRANTIES, EXPRESS OR IMPLIED,
+ * INCLUDING, BUT NOT LIMITED TO, ANY IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT.
+ *
+ * You are solely responsible for the selection, use, efficiency and
+ * suitability of the Specification for your software products. OTHER
+ * WARRANTIES EXCLUDED. JASC SHALL NOT BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, CONSEQUENTIAL, EXEMPLARY, PUNITIVE OR INCIDENTAL DAMAGES
+ * ARISING FROM ANY CAUSE EVEN IF JASC HAS BEEN ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES. CERTAIN JURISDICTIONS DO NOT PERMIT
+ * THE LIMITATION OR EXCLUSION OF INCIDENTAL DAMAGES, SO THIS
+ * LIMITATION MAY NOT APPLY TO YOU. IN NO EVENT WILL JASC BE LIABLE
+ * FOR ANY AMOUNT GREATER THAN WHAT YOU ACTUALLY PAID FOR THE
+ * SPECIFICATION. Should any warranties be found to exist, such
+ * warranties shall be limited in duration to ninety (90) days
+ * following the date you receive the Specification.
+ *
+ * Indemnification. By your inclusion of the Paint Shop Pro File
+ * Format in your software product(s) you agree to indemnify and hold
+ * Jasc Software, Inc. harmless from any and all claims of any kind or
+ * nature made by any of your customers with respect to your software
+ * product(s).
+ *
+ * Export Laws. You agree that you and your customers will not export
+ * your software or Specification except in compliance with the laws
+ * and regulations of the United States.
+ *
+ * US Government Restricted Rights. The Specification and any
+ * accompanying materials are provided with Restricted Rights. Use,
+ * duplication or disclosure by the Government is subject to
+ * restrictions as set forth in subparagraph (c)(1)(ii) of The Rights
+ * in Technical Data and Computer Software clause at DFARS
+ * 252.227-7013, or subparagraphs (c)(1) and (2) of the Commercial
+ * Computer Software - Restricted Rights at 48 CFR 52.227-19, as
+ * applicable. Contractor/manufacturer is Jasc Software, Inc., PO Box
+ * 44997, Eden Prairie MN 55344.
+ *
+ * Jasc reserves the right to amend, modify, change, revoke or
+ * withdraw the Specification at any time and from time to time. Jasc
+ * shall have no obligation to support or maintain the Specification.
+ */
+
+/* Block identifiers.
+ */
+typedef enum {
+ PSP_IMAGE_BLOCK = 0, /* General Image Attributes Block (main) */
+ PSP_CREATOR_BLOCK, /* Creator Data Block (main) */
+ PSP_COLOR_BLOCK, /* Color Palette Block (main and sub) */
+ PSP_LAYER_START_BLOCK, /* Layer Bank Block (main) */
+ PSP_LAYER_BLOCK, /* Layer Block (sub) */
+ PSP_CHANNEL_BLOCK, /* Channel Block (sub) */
+ PSP_SELECTION_BLOCK, /* Selection Block (main) */
+ PSP_ALPHA_BANK_BLOCK, /* Alpha Bank Block (main) */
+ PSP_ALPHA_CHANNEL_BLOCK, /* Alpha Channel Block (sub) */
+ PSP_THUMBNAIL_BLOCK, /* Thumbnail Block (main) */
+ PSP_EXTENDED_DATA_BLOCK, /* Extended Data Block (main) */
+ PSP_TUBE_BLOCK, /* Picture Tube Data Block (main) */
+ PSP_ADJUSTMENT_EXTENSION_BLOCK, /* Adjustment Layer Extension Block (sub) (since PSP6)*/
+ PSP_VECTOR_EXTENSION_BLOCK, /* Vector Layer Extension Block (sub) (since PSP6) */
+ PSP_SHAPE_BLOCK, /* Vector Shape Block (sub) (since PSP6) */
+ PSP_PAINTSTYLE_BLOCK, /* Paint Style Block (sub) (since PSP6) */
+ PSP_COMPOSITE_IMAGE_BANK_BLOCK, /* Composite Image Bank (main) (since PSP6) */
+ PSP_COMPOSITE_ATTRIBUTES_BLOCK, /* Composite Image Attributes (sub) (since PSP6) */
+ PSP_JPEG_BLOCK, /* JPEG Image Block (sub) (since PSP6) */
+ PSP_LINESTYLE_BLOCK, /* Line Style Block (sub) (since PSP7) */
+ PSP_TABLE_BANK_BLOCK, /* Table Bank Block (main) (since PSP7) */
+ PSP_TABLE_BLOCK, /* Table Block (sub) (since PSP7) */
+ PSP_PAPER_BLOCK, /* Vector Table Paper Block (sub) (since PSP7) */
+ PSP_PATTERN_BLOCK, /* Vector Table Pattern Block (sub) (since PSP7) */
+ PSP_GRADIENT_BLOCK, /* Vector Table Gradient Block (not used) (since PSP8) */
+ PSP_GROUP_EXTENSION_BLOCK, /* Group Layer Block (sub) (since PSP8) */
+ PSP_MASK_EXTENSION_BLOCK, /* Mask Layer Block (sub) (since PSP8) */
+ PSP_BRUSH_BLOCK, /* Brush Data Block (main) (since PSP8) */
+ PSP_ART_MEDIA_BLOCK, /* Art Media Layer Block (main) (since PSP9) */
+ PSP_ART_MEDIA_MAP_BLOCK, /* Art Media Layer map data Block (main) (since PSP9) */
+ PSP_ART_MEDIA_TILE_BLOCK, /* Art Media Layer map tile Block(main) (since PSP9) */
+ PSP_ART_MEDIA_TEXTURE_BLOCK, /* AM Layer map texture Block (main) (since PSP9) */
+ PSP_COLORPROFILE_BLOCK, /* ICC Color profile block (since PSP10) */
+ PSP_RASTER_EXTENSION_BLOCK, /* Assumed name based on usage, probably since PSP11 */
+} PSPBlockID;
+
+/* Bitmap type.
+ */
+typedef enum {
+ PSP_DIB_IMAGE = 0, /* Layer color bitmap */
+ PSP_DIB_TRANS_MASK, /* Layer transparency mask bitmap */
+ PSP_DIB_USER_MASK, /* Layer user mask bitmap */
+ PSP_DIB_SELECTION, /* Selection mask bitmap */
+ PSP_DIB_ALPHA_MASK, /* Alpha channel mask bitmap */
+ PSP_DIB_THUMBNAIL, /* Thumbnail bitmap */
+ PSP_DIB_THUMBNAIL_TRANS_MASK, /* Thumbnail transparency mask (since PSP6) */
+ PSP_DIB_ADJUSTMENT_LAYER, /* Adjustment layer bitmap (since PSP6) */
+ PSP_DIB_COMPOSITE, /* Composite image bitmap (since PSP6) */
+ PSP_DIB_COMPOSITE_TRANS_MASK, /* Composite image transparency (since PSP6) */
+ PSP_DIB_PAPER, /* Paper bitmap (since PSP7) */
+ PSP_DIB_PATTERN, /* Pattern bitmap (since PSP7) */
+ PSP_DIB_PATTERN_TRANS_MASK, /* Pattern transparency mask (since PSP7) */
+} PSPDIBType;
+
+/* Type of image in the composite image bank block. (since PSP6)
+ */
+typedef enum {
+ PSP_IMAGE_COMPOSITE = 0, /* Composite Image */
+ PSP_IMAGE_THUMBNAIL, /* Thumbnail Image */
+} PSPCompositeImageType;
+
+/* Graphic contents flags. (since PSP6)
+ */
+typedef enum {
+ /* Layer types */
+ keGCRasterLayers = 0x00000001, /* At least one raster layer */
+ keGCVectorLayers = 0x00000002, /* At least one vector layer */
+ keGCAdjustmentLayers = 0x00000004, /* At least one adjustment layer */
+ keGCGroupLayers = 0x00000008, /* at least one group layer */
+ keGCMaskLayers = 0x00000010, /* at least one mask layer */
+ keGCArtMediaLayers = 0x00000020, /* at least one art media layer */
+
+ /* Additional attributes */
+ keGCMergedCache = 0x00800000, /* merged cache (composite image) */
+ keGCThumbnail = 0x01000000, /* Has a thumbnail */
+ keGCThumbnailTransparency = 0x02000000, /* Thumbnail transp. */
+ keGCComposite = 0x04000000, /* Has a composite image */
+ keGCCompositeTransparency = 0x08000000, /* Composite transp. */
+ keGCFlatImage = 0x10000000, /* Just a background */
+ keGCSelection = 0x20000000, /* Has a selection */
+ keGCFloatingSelectionLayer = 0x40000000, /* Has float. selection */
+ keGCAlphaChannels = 0x80000000, /* Has alpha channel(s) */
+} PSPGraphicContents;
+
+/* Character style flags. (since PSP6)
+ */
+typedef enum {
+ keStyleItalic = 0x00000001, /* Italic property bit */
+ keStyleStruck = 0x00000002, /* Strike­out property bit */
+ keStyleUnderlined = 0x00000004, /* Underlined property bit */
+ keStyleWarped = 0x00000008, /* Warped property bit (since PSP8) */
+ keStyleAntiAliased = 0x00000010, /* Anti­aliased property bit (since PSP8) */
+} PSPCharacterProperties;
+
+/* Table type. (since PSP7)
+ */
+typedef enum {
+ keTTUndefined = 0, /* Undefined table type */
+ keTTGradientTable, /* Gradient table type */
+ keTTPaperTable, /* Paper table type */
+ keTTPatternTable /* Pattern table type */
+} PSPTableType;
+
+/* Layer flags. (since PSP6)
+ */
+typedef enum {
+ keVisibleFlag = 0x00000001, /* Layer is visible */
+ keMaskPresenceFlag = 0x00000002, /* Layer has a mask */
+} PSPLayerProperties;
+
+/* Shape property flags. (since PSP6)
+ */
+typedef enum {
+ keShapeAntiAliased = 0x00000001, /* Shape is anti­aliased */
+ keShapeSelected = 0x00000002, /* Shape is selected */
+ keShapeVisible = 0x00000004, /* Shape is visible */
+} PSPShapeProperties;
+
+/* Polyline node type flags. (since PSP7)
+ */
+typedef enum {
+ keNodeUnconstrained = 0x0000, /* Default node type */
+ keNodeSmooth = 0x0001, /* Node is smooth */
+ keNodeSymmetric = 0x0002, /* Node is symmetric */
+ keNodeAligned = 0x0004, /* Node is aligned */
+ keNodeActive = 0x0008, /* Node is active */
+ keNodeLocked = 0x0010, /* Node is locked */
+ keNodeSelected = 0x0020, /* Node is selected */
+ keNodeVisible = 0x0040, /* Node is visible */
+ keNodeClosed = 0x0080, /* Node is closed */
+
+ /* TODO: This might be a thinko in the spec document only or in the image
+ * format itself. Need to investigate that later
+ */
+ keNodeLockedPSP6 = 0x0016, /* Node is locked */
+ keNodeSelectedPSP6 = 0x0032, /* Node is selected */
+ keNodeVisiblePSP6 = 0x0064, /* Node is visible */
+ keNodeClosedPSP6 = 0x0128, /* Node is closed */
+
+} PSPPolylineNodeTypes;
+
+/* Blend modes. (since PSP6)
+ */
+typedef enum {
+ PSP_BLEND_NORMAL,
+ PSP_BLEND_DARKEN,
+ PSP_BLEND_LIGHTEN,
+ PSP_BLEND_HUE,
+ PSP_BLEND_SATURATION,
+ PSP_BLEND_COLOR,
+ PSP_BLEND_LUMINOSITY,
+ PSP_BLEND_MULTIPLY,
+ PSP_BLEND_SCREEN,
+ PSP_BLEND_DISSOLVE,
+ PSP_BLEND_OVERLAY,
+ PSP_BLEND_HARD_LIGHT,
+ PSP_BLEND_SOFT_LIGHT,
+ PSP_BLEND_DIFFERENCE,
+ PSP_BLEND_DODGE,
+ PSP_BLEND_BURN,
+ PSP_BLEND_EXCLUSION,
+ PSP_BLEND_TRUE_HUE, /* since PSP8 */
+ PSP_BLEND_TRUE_SATURATION, /* since PSP8 */
+ PSP_BLEND_TRUE_COLOR, /* since PSP8 */
+ PSP_BLEND_TRUE_LIGHTNESS, /* since PSP8 */
+ PSP_BLEND_ADJUST = 255,
+} PSPBlendModes;
+
+/* Adjustment layer types. (since PSP6)
+ */
+typedef enum {
+ keAdjNone = 0, /* Undefined adjustment layer type */
+ keAdjLevel, /* Level adjustment */
+ keAdjCurve, /* Curve adjustment */
+ keAdjBrightContrast, /* Brightness­contrast adjustment */
+ keAdjColorBal, /* Color balance adjustment */
+ keAdjHSL, /* HSL adjustment */
+ keAdjChannelMixer, /* Channel mixer adjustment */
+ keAdjInvert, /* Invert adjustment */
+ keAdjThreshold, /* Threshold adjustment */
+ keAdjPoster /* Posterize adjustment */
+} PSPAdjustmentLayerType;
+
+/* Vector shape types. (since PSP6)
+ */
+typedef enum {
+ keVSTUnknown = 0, /* Undefined vector type */
+ keVSTText, /* Shape represents lines of text */
+ keVSTPolyline, /* Shape represents a multiple segment line */
+ keVSTEllipse, /* Shape represents an ellipse (or circle) */
+ keVSTPolygon, /* Shape represents a closed polygon */
+ keVSTGroup, /* Shape represents a group shape (since PSP7) */
+} PSPVectorShapeType;
+
+/* Text element types. (since PSP6)
+ */
+typedef enum {
+ keTextElemUnknown = 0, /* Undefined text element type */
+ keTextElemChar, /* A single character code */
+ keTextElemCharStyle, /* A character style change */
+ keTextElemLineStyle /* A line style change */
+} PSPTextElementType;
+
+/* Text alignment types. (since PSP6)
+ */
+typedef enum {
+ keTextAlignmentLeft = 0, /* Left text alignment */
+ keTextAlignmentCenter, /* Center text alignment */
+ keTextAlignmentRight /* Right text alignment */
+} PSPTextAlignment;
+
+/* Text antialias modes. */
+typedef enum {
+ keNoAntialias = 0, /* Antialias off */
+ keSharpAntialias, /* Sharp */
+ keSmoothAntialias /* Smooth */
+} PSPAntialiasMode;
+
+/* Text flow types */
+typedef enum {
+ keTFHorizontalDown = 0, /* Horizontal then down */
+ keTFVerticalLeft, /* Vertical then left */
+ keTFVerticalRight, /* Vertical then right */
+ keTFHorizontalUp /* Horizontal then up */
+} PSPTextFlow;
+
+/* Paint style types. (since PSP6)
+ */
+typedef enum {
+ keStyleNone = 0x0000, /* No paint style info applies */
+ keStyleColor = 0x0001, /* Color paint style info */
+ keStyleGradient = 0x0002, /* Gradient paint style info */
+ keStylePattern = 0x0004, /* Pattern paint style info (since PSP7) */
+ keStylePaper = 0x0008, /* Paper paint style info (since PSP7) */
+ keStylePen = 0x0010, /* Organic pen paint style info (since PSP7) */
+} PSPPaintStyleType;
+
+/* Gradient type. (since PSP7)
+ */
+typedef enum {
+ keSGTLinear = 0, /* Linera gradient type */
+ keSGTRadial, /* Radial gradient type */
+ keSGTRectangular, /* Rectangulat gradient type */
+ keSGTSunburst /* Sunburst gradient type */
+} PSPStyleGradientType;
+
+/* Paint Style Cap Type (Start & End). (since PSP7)
+ */
+typedef enum {
+ keSCTCapFlat = 0, /* Flat cap type (was round in psp6) */
+ keSCTCapRound, /* Round cap type (was square in psp6) */
+ keSCTCapSquare, /* Square cap type (was flat in psp6) */
+ keSCTCapArrow, /* Arrow cap type */
+ keSCTCapCadArrow, /* Cad arrow cap type */
+ keSCTCapCurvedTipArrow, /* Curved tip arrow cap type */
+ keSCTCapRingBaseArrow, /* Ring base arrow cap type */
+ keSCTCapFluerDelis, /* Fluer deLis cap type */
+ keSCTCapFootball, /* Football cap type */
+ keSCTCapXr71Arrow, /* Xr71 arrow cap type */
+ keSCTCapLilly, /* Lilly cap type */
+ keSCTCapPinapple, /* Pinapple cap type */
+ keSCTCapBall, /* Ball cap type */
+ keSCTCapTulip /* Tulip cap type */
+} PSPStyleCapType;
+
+/* Paint Style Join Type. (since PSP7)
+ */
+typedef enum {
+ keSJTJoinMiter = 0,
+ keSJTJoinRound,
+ keSJTJoinBevel
+} PSPStyleJoinType;
+
+/* Organic pen type. (since PSP7)
+ */
+typedef enum {
+ keSPTOrganicPenNone = 0, /* Undefined pen type */
+ keSPTOrganicPenMesh, /* Mesh pen type */
+ keSPTOrganicPenSand, /* Sand pen type */
+ keSPTOrganicPenCurlicues, /* Curlicues pen type */
+ keSPTOrganicPenRays, /* Rays pen type */
+ keSPTOrganicPenRipple, /* Ripple pen type */
+ keSPTOrganicPenWave, /* Wave pen type */
+ keSPTOrganicPen /* Generic pen type */
+} PSPStylePenType;
+
+
+/* Channel types.
+ */
+typedef enum {
+ PSP_CHANNEL_COMPOSITE = 0, /* Channel of single channel bitmap */
+ PSP_CHANNEL_RED, /* Red channel of 24 bit bitmap */
+ PSP_CHANNEL_GREEN, /* Green channel of 24 bit bitmap */
+ PSP_CHANNEL_BLUE /* Blue channel of 24 bit bitmap */
+} PSPChannelType;
+
+/* Possible metrics used to measure resolution.
+ */
+typedef enum {
+ PSP_METRIC_UNDEFINED = 0, /* Metric unknown */
+ PSP_METRIC_INCH, /* Resolution is in inches */
+ PSP_METRIC_CM /* Resolution is in centimeters */
+} PSP_METRIC;
+
+/* Possible types of compression.
+ */
+typedef enum {
+ PSP_COMP_NONE = 0, /* No compression */
+ PSP_COMP_RLE, /* RLE compression */
+ PSP_COMP_LZ77, /* LZ77 compression */
+ PSP_COMP_JPEG /* JPEG compression (only used by thumbnail and composite image) (since PSP6) */
+} PSPCompression;
+
+/* Picture tube placement mode.
+ */
+typedef enum {
+ tpmRandom, /* Place tube images in random intervals */
+ tpmConstant /* Place tube images in constant intervals */
+} TubePlacementMode;
+
+/* Picture tube selection mode.
+ */
+typedef enum {
+ tsmRandom, /* Randomly select the next image in */
+ /* tube to display */
+ tsmIncremental, /* Select each tube image in turn */
+ tsmAngular, /* Select image based on cursor direction */
+ tsmPressure, /* Select image based on pressure */
+ /* (from pressure-sensitive pad) */
+ tsmVelocity /* Select image based on cursor speed */
+} TubeSelectionMode;
+
+/* Extended data field types.
+ */
+typedef enum {
+ PSP_XDATA_TRNS_INDEX = 0, /* Transparency index field */
+ PSP_XDATA_GRID, /* Image grid information (since PSP7) */
+ PSP_XDATA_GUIDE, /* Image guide information (since PSP7) */
+ PSP_XDATA_EXIF, /* Image Exif information (since PSP8) */
+ PSP_XDATA_IPTC, /* Image IPTC information (since PSP10) */
+} PSPExtendedDataID;
+
+/* Creator field types.
+ */
+typedef enum {
+ PSP_CRTR_FLD_TITLE = 0, /* Image document title field */
+ PSP_CRTR_FLD_CRT_DATE, /* Creation date field */
+ PSP_CRTR_FLD_MOD_DATE, /* Modification date field */
+ PSP_CRTR_FLD_ARTIST, /* Artist name field */
+ PSP_CRTR_FLD_CPYRGHT, /* Copyright holder name field */
+ PSP_CRTR_FLD_DESC, /* Image document description field */
+ PSP_CRTR_FLD_APP_ID, /* Creating app id field */
+ PSP_CRTR_FLD_APP_VER /* Creating app version field */
+} PSPCreatorFieldID;
+
+/* Grid units type. (since PSP7)
+ */
+typedef enum {
+ keGridUnitsPixels = 0, /* Grid units is pixels */
+ keGridUnitsInches, /* Grid units is inches */
+ keGridUnitsCentimeters /* Grid units is centimeters */
+} PSPGridUnitsType;
+
+/* Guide orientation type. (since PSP7)
+ */
+typedef enum {
+ keHorizontalGuide = 0,
+ keVerticalGuide
+} PSPGuideOrientationType;
+
+/* Creator application identifiers.
+ */
+typedef enum {
+ PSP_CREATOR_APP_UNKNOWN = 0, /* Creator application unknown */
+ PSP_CREATOR_APP_PAINT_SHOP_PRO /* Creator is Paint Shop Pro */
+} PSPCreatorAppID;
+
+/* Layer types.
+ */
+typedef enum {
+ PSP_LAYER_NORMAL = 0, /* Normal layer */
+ PSP_LAYER_FLOATING_SELECTION /* Floating selection layer */
+} PSPLayerTypePSP5;
+
+/* Layer types. (since PSP6)
+ */
+typedef enum {
+ keGLTUndefined = 0, /* Undefined layer type */
+ keGLTRaster, /* Standard raster layer */
+ keGLTFloatingRasterSelection, /* Floating selection (raster layer) */
+ keGLTVector, /* Vector layer */
+ keGLTAdjustment, /* Adjustment layer */
+ keGLTGroup, /* Group layer (since PSP8) */
+ keGLTMask, /* Mask layer (since PSP8) */
+ keGLTArtMedia /* Art media layer (since PSP9) */
+} PSPLayerTypePSP6;
+
+/* Art media layer map types (since PSP7) */
+typedef enum {
+keArtMediaColorMap = 0,
+keArtMediaBumpMap,
+keArtMediaShininessMap,
+keArtMediaReflectivityMap,
+keArtMediaDrynessMap
+} PSPArtMediaMapType;
+
+
+/* Truth values.
+ */
+#if 0 /* FALSE and TRUE taken by GLib */
+typedef enum {
+ FALSE = 0,
+ TRUE
+} PSP_BOOLEAN;
+#else
+typedef gboolean PSP_BOOLEAN;
+#endif
+
+/* End of cut&paste from psp spec */
+
+/* We store the various PSP data in own structures.
+ * We cannot use structs intended to be direct copies of the file block
+ * headers because of struct alignment issues.
+ */
+typedef struct
+{
+ guint32 width, height;
+ gdouble resolution;
+ guchar metric;
+ guint16 compression;
+ guint16 depth;
+ guchar grayscale;
+ guint32 active_layer;
+ guint16 layer_count;
+ guint16 bytes_per_sample;
+ GimpImageBaseType base_type;
+ GimpPrecision precision;
+} PSPimage;
+
+/* Declare some local functions.
+ */
+static void query (void);
+static void run (const gchar *name,
+ gint nparams,
+ const GimpParam *param,
+ gint *nreturn_vals,
+ GimpParam **return_vals);
+static gint32 load_image (const gchar *filename,
+ GError **error);
+static gint save_image (const gchar *filename,
+ gint32 image_ID,
+ gint32 drawable_ID,
+ GError **error);
+
+/* Various local variables...
+ */
+const GimpPlugInInfo PLUG_IN_INFO =
+{
+ NULL, /* init_proc */
+ NULL, /* quit_proc */
+ query, /* query_proc */
+ run, /* run_proc */
+};
+
+/* Save info */
+typedef struct
+{
+ PSPCompression compression;
+} PSPSaveVals;
+
+static PSPSaveVals psvals =
+{
+ PSP_COMP_LZ77
+};
+
+static guint16 psp_ver_major, psp_ver_minor;
+
+
+MAIN ()
+
+static void
+query (void)
+{
+ static const GimpParamDef load_args[] =
+ {
+ { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" },
+ { GIMP_PDB_STRING, "filename", "The name of the file to load" },
+ { GIMP_PDB_STRING, "raw-filename", "The name of the file to load" }
+ };
+ static const GimpParamDef load_return_vals[] =
+ {
+ { GIMP_PDB_IMAGE, "image", "Output image" }
+ };
+
+#if 0
+ static const GimpParamDef save_args[] =
+ {
+ { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" },
+ { GIMP_PDB_IMAGE, "image", "Input image" },
+ { GIMP_PDB_DRAWABLE, "drawable", "Drawable to export" },
+ { GIMP_PDB_STRING, "filename", "The name of the file to export the image in" },
+ { GIMP_PDB_STRING, "raw-filename", "The name of the file to export the image in" },
+ { GIMP_PDB_INT32, "compression", "Specify 0 for no compression, 1 for RLE, and 2 for LZ77" }
+ };
+#endif
+
+ gimp_install_procedure (LOAD_PROC,
+ "loads images from the Paint Shop Pro PSP file format",
+ "This plug-in loads and exports images in "
+ "Paint Shop Pro's native PSP format. "
+ "Vector layers aren't handled. Exporting isn't "
+ "yet implemented.",
+ "Tor Lillqvist",
+ "Tor Lillqvist",
+ "1999",
+ N_("Paint Shop Pro image"),
+ NULL,
+ GIMP_PLUGIN,
+ G_N_ELEMENTS (load_args),
+ G_N_ELEMENTS (load_return_vals),
+ load_args, load_return_vals);
+
+ gimp_register_file_handler_mime (LOAD_PROC, "image/x-psp");
+ gimp_register_magic_load_handler (LOAD_PROC,
+ "psp,tub,pspimage",
+ "",
+ "0,string,Paint\\040Shop\\040Pro\\040Image\\040File\n\032");
+
+ /* commented out until exporting is implemented */
+#if 0
+ gimp_install_procedure (SAVE_PROC,
+ "exports images in the Paint Shop Pro PSP file format",
+ "This plug-in loads and exports images in "
+ "Paint Shop Pro's native PSP format. "
+ "Vector layers aren't handled. Exporting isn't "
+ "yet implemented.",
+ "Tor Lillqvist",
+ "Tor Lillqvist",
+ "1999",
+ N_("Paint Shop Pro image"),
+ "RGB*, GRAY*, INDEXED*",
+ GIMP_PLUGIN,
+ G_N_ELEMENTS (save_args), 0,
+ save_args, NULL);
+
+ gimp_register_save_handler (SAVE_PROC, "psp,tub", "");
+#endif
+}
+
+static gboolean
+save_dialog (void)
+{
+ GtkWidget *dialog;
+ GtkWidget *frame;
+ gint run;
+
+ dialog = gimp_export_dialog_new (_("PSP"), PLUG_IN_BINARY, SAVE_PROC);
+
+ /* file save type */
+ frame = gimp_int_radio_group_new (TRUE, _("Data Compression"),
+ G_CALLBACK (gimp_radio_button_update),
+ &psvals.compression, psvals.compression,
+
+ C_("compression", "None"), PSP_COMP_NONE, NULL,
+ _("RLE"), PSP_COMP_RLE, NULL,
+ _("LZ77"), PSP_COMP_LZ77, NULL,
+
+ NULL);
+
+ gtk_container_set_border_width (GTK_CONTAINER (frame), 12);
+ gtk_box_pack_start (GTK_BOX (gimp_export_dialog_get_content_area (dialog)),
+ frame, FALSE, TRUE, 0);
+ gtk_widget_show (frame);
+
+ gtk_widget_show (dialog);
+
+ run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK);
+
+ gtk_widget_destroy (dialog);
+
+ return run;
+}
+
+/* This helper method is used to get the name of the block for the known block
+ * types. The enum PSPBlockID must cover the input values.
+ */
+static const gchar *
+block_name (gint id)
+{
+ static const gchar *block_names[] =
+ {
+ "IMAGE",
+ "CREATOR",
+ "COLOR",
+ "LAYER_START",
+ "LAYER",
+ "CHANNEL",
+ "SELECTION",
+ "ALPHA_BANK",
+ "ALPHA_CHANNEL",
+ "THUMBNAIL",
+ "EXTENDED_DATA",
+ "TUBE",
+ "ADJUSTMENT_EXTENSION",
+ "VECTOR_EXTENSION_BLOCK",
+ "SHAPE_BLOCK",
+ "PAINTSTYLE_BLOCK",
+ "COMPOSITE_IMAGE_BANK_BLOCK",
+ "COMPOSITE_ATTRIBUTES_BLOCK",
+ "JPEG_BLOCK",
+ "LINESTYLE_BLOCK",
+ "TABLE_BANK_BLOCK",
+ "TABLE_BLOCK",
+ "PAPER_BLOCK",
+ "PATTERN_BLOCK",
+ "GRADIENT_BLOCK",
+ "GROUP_EXTENSION_BLOCK",
+ "MASK_EXTENSION_BLOCK",
+ "BRUSH_BLOCK",
+ "ART_MEDIA_BLOCK",
+ "ART_MEDIA_MAP_BLOCK",
+ "ART_MEDIA_TILE_BLOCK",
+ "ART_MEDIA_TEXTURE_BLOCK",
+ "COLORPROFILE_BLOCK",
+ "RASTER_EXTENSION_BLOCK",
+};
+ static gchar *err_name = NULL;
+
+ if (id >= 0 && id <= PSP_RASTER_EXTENSION_BLOCK)
+ return block_names[id];
+
+ g_free (err_name);
+ err_name = g_strdup_printf ("id=%d", id);
+
+ return err_name;
+}
+
+/* This helper method is used during loading. It verifies the block we are
+ * reading has a valid header. Fills the variables init_len and total_len
+ */
+static gint
+read_block_header (FILE *f,
+ guint32 *init_len,
+ guint32 *total_len,
+ GError **error)
+{
+ guchar buf[4];
+ guint16 id;
+ long header_start;
+ guint32 len;
+
+ IFDBG(3) header_start = ftell (f);
+
+ if (fread (buf, 4, 1, f) < 1
+ || fread (&id, 2, 1, f) < 1
+ || fread (&len, 4, 1, f) < 1
+ || (psp_ver_major < 4 && fread (total_len, 4, 1, f) < 1))
+ {
+ g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
+ _("Error reading block header"));
+ return -1;
+ }
+ if (memcmp (buf, "~BK\0", 4) != 0)
+ {
+ IFDBG(3)
+ g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
+ _("Invalid block header at %ld"), header_start);
+ else
+ g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
+ _("Invalid block header"));
+ return -1;
+ }
+
+ IFDBG(3) g_message ("%s at %ld", block_name (id), header_start);
+
+ if (psp_ver_major < 4)
+ {
+ *init_len = GUINT32_FROM_LE (len);
+ *total_len = GUINT32_FROM_LE (*total_len);
+ }
+ else
+ {
+ /* Version 4.0 seems to have dropped the initial data chunk length
+ * field.
+ */
+ *init_len = 0xDEADBEEF; /* Intentionally bogus, should not be used */
+ *total_len = GUINT32_FROM_LE (len);
+ }
+
+ return GUINT16_FROM_LE (id);
+}
+
+static gint
+try_fseek (FILE *f,
+ glong pos,
+ gint whence,
+ GError **error)
+{
+ if (fseek (f, pos, whence) < 0)
+ {
+ g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
+ _("Seek error: %s"), g_strerror (errno));
+ fclose (f);
+ return -1;
+ }
+ return 0;
+}
+
+/* Read the PSP_IMAGE_BLOCK */
+static gint
+read_general_image_attribute_block (FILE *f,
+ guint init_len,
+ guint total_len,
+ PSPimage *ia,
+ GError **error)
+{
+ gchar buf[6];
+ guint64 res;
+ gchar graphics_content[4];
+ long chunk_start;
+
+ if (init_len < 38 || total_len < 38)
+ {
+ g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
+ _("Invalid general image attribute chunk size."));
+ return -1;
+ }
+
+ chunk_start = ftell (f);
+ if ((psp_ver_major >= 4
+ && (fread (&init_len, 4, 1, f) < 1 || ((init_len = GUINT32_FROM_LE (init_len)) < 46)))
+ || fread (&ia->width, 4, 1, f) < 1
+ || fread (&ia->height, 4, 1, f) < 1
+ || fread (&res, 8, 1, f) < 1
+ || fread (&ia->metric, 1, 1, f) < 1
+ || fread (&ia->compression, 2, 1, f) < 1
+ || fread (&ia->depth, 2, 1, f) < 1
+ || fread (buf, 2+4, 1, f) < 1 /* Skip plane and color count */
+ || fread (&ia->grayscale, 1, 1, f) < 1
+ || fread (buf, 4, 1, f) < 1 /* Skip total image size */
+ || fread (&ia->active_layer, 4, 1, f) < 1
+ || fread (&ia->layer_count, 2, 1, f) < 1
+ || (psp_ver_major >= 4 && fread (graphics_content, 4, 1, f) < 1)
+ || try_fseek (f, chunk_start + init_len, SEEK_SET, error) < 0)
+ {
+ g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
+ _("Error reading general image attribute block."));
+ return -1;
+ }
+ ia->width = GUINT32_FROM_LE (ia->width);
+ ia->height = GUINT32_FROM_LE (ia->height);
+
+ res = GUINT64_FROM_LE (res);
+ memcpy (&ia->resolution, &res, 8);
+ if (ia->metric == PSP_METRIC_CM)
+ ia->resolution /= 2.54;
+
+ ia->compression = GUINT16_FROM_LE (ia->compression);
+ if (ia->compression > PSP_COMP_LZ77)
+ {
+ g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
+ _("Unknown compression type %d"), ia->compression);
+ return -1;
+ }
+
+ ia->depth = GUINT16_FROM_LE (ia->depth);
+ switch (ia->depth)
+ {
+ case 24:
+ case 48:
+ ia->base_type = GIMP_RGB;
+ if (ia->depth == 24)
+ ia->precision = GIMP_PRECISION_U8_GAMMA;
+ else
+ ia->precision = GIMP_PRECISION_U16_GAMMA;
+ break;
+
+ case 1:
+ case 4:
+ case 8:
+ case 16:
+ if (ia->grayscale && ia->depth >= 8)
+ {
+ ia->base_type = GIMP_GRAY;
+ if (ia->depth == 8)
+ ia->precision = GIMP_PRECISION_U8_GAMMA;
+ else
+ ia->precision = GIMP_PRECISION_U16_GAMMA;
+ }
+ else if (ia->depth <= 8 && ! ia->grayscale)
+ {
+ ia->base_type = GIMP_INDEXED;
+ ia->precision = GIMP_PRECISION_U8_GAMMA;
+ }
+ else
+ {
+ g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
+ _("Unsupported bit depth %d"), ia->depth);
+ return -1;
+ }
+ break;
+
+ default:
+ g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
+ _("Unsupported bit depth %d"), ia->depth);
+ return -1;
+ break;
+ }
+
+ if (ia->precision == GIMP_PRECISION_U16_GAMMA)
+ ia->bytes_per_sample = 2;
+ else
+ ia->bytes_per_sample = 1;
+
+ ia->active_layer = GUINT32_FROM_LE (ia->active_layer);
+ ia->layer_count = GUINT16_FROM_LE (ia->layer_count);
+
+ return 0;
+}
+
+static gint
+read_creator_block (FILE *f,
+ gint image_ID,
+ guint total_len,
+ PSPimage *ia,
+ GError **error)
+{
+ long data_start;
+ guchar buf[4];
+ guint16 keyword;
+ guint32 length;
+ gchar *string;
+ gchar *title = NULL, *artist = NULL, *copyright = NULL, *description = NULL;
+ guint32 dword;
+ guint32 __attribute__((unused))cdate = 0;
+ guint32 __attribute__((unused))mdate = 0;
+ guint32 __attribute__((unused))appid;
+ guint32 __attribute__((unused))appver;
+ GString *comment;
+ GimpParasite *comment_parasite;
+
+ data_start = ftell (f);
+ comment = g_string_new (NULL);
+
+ while (ftell (f) < data_start + total_len)
+ {
+ if (fread (buf, 4, 1, f) < 1
+ || fread (&keyword, 2, 1, f) < 1
+ || fread (&length, 4, 1, f) < 1)
+ {
+ g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
+ _("Error reading creator keyword chunk"));
+ return -1;
+ }
+ if (memcmp (buf, "~FL\0", 4) != 0)
+ {
+ g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
+ _("Invalid keyword chunk header"));
+ return -1;
+ }
+ keyword = GUINT16_FROM_LE (keyword);
+ length = GUINT32_FROM_LE (length);
+ switch (keyword)
+ {
+ case PSP_CRTR_FLD_TITLE:
+ case PSP_CRTR_FLD_ARTIST:
+ case PSP_CRTR_FLD_CPYRGHT:
+ case PSP_CRTR_FLD_DESC:
+ string = g_malloc (length + 1);
+ if (fread (string, length, 1, f) < 1)
+ {
+ g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
+ _("Error reading creator keyword data"));
+ g_free (string);
+ return -1;
+ }
+ /* PSP does not zero terminate strings */
+ string[length] = '\0';
+ switch (keyword)
+ {
+ case PSP_CRTR_FLD_TITLE:
+ g_free (title); title = string; break;
+ case PSP_CRTR_FLD_ARTIST:
+ g_free (artist); artist = string; break;
+ case PSP_CRTR_FLD_CPYRGHT:
+ g_free (copyright); copyright = string; break;
+ case PSP_CRTR_FLD_DESC:
+ g_free (description); description = string; break;
+ default:
+ g_free (string);
+ }
+ break;
+ case PSP_CRTR_FLD_CRT_DATE:
+ case PSP_CRTR_FLD_MOD_DATE:
+ case PSP_CRTR_FLD_APP_ID:
+ case PSP_CRTR_FLD_APP_VER:
+ if (fread (&dword, 4, 1, f) < 1)
+ {
+ g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
+ _("Error reading creator keyword data"));
+ return -1;
+ }
+ switch (keyword)
+ {
+ case PSP_CRTR_FLD_CRT_DATE:
+ cdate = dword; break;
+ case PSP_CRTR_FLD_MOD_DATE:
+ mdate = dword; break;
+ case PSP_CRTR_FLD_APP_ID:
+ appid = dword; break;
+ case PSP_CRTR_FLD_APP_VER:
+ appver = dword; break;
+ }
+ break;
+ default:
+ if (try_fseek (f, length, SEEK_CUR, error) < 0)
+ {
+ return -1;
+ }
+ break;
+ }
+ }
+
+ if (title)
+ {
+ g_string_append (comment, title);
+ g_free (title);
+ g_string_append (comment, "\n");
+ }
+ if (artist)
+ {
+ g_string_append (comment, artist);
+ g_free (artist);
+ g_string_append (comment, "\n");
+ }
+ if (copyright)
+ {
+ g_string_append (comment, "Copyright ");
+ g_string_append (comment, copyright);
+ g_free (copyright);
+ g_string_append (comment, "\n");
+ }
+ if (description)
+ {
+ g_string_append (comment, description);
+ g_free (description);
+ g_string_append (comment, "\n");
+ }
+ if (comment->len > 0)
+ {
+ comment_parasite = gimp_parasite_new ("gimp-comment",
+ GIMP_PARASITE_PERSISTENT,
+ strlen (comment->str) + 1,
+ comment->str);
+ gimp_image_attach_parasite (image_ID, comment_parasite);
+ gimp_parasite_free (comment_parasite);
+ }
+
+ g_string_free (comment, FALSE);
+
+ return 0;
+}
+
+static gint
+read_color_block (FILE *f,
+ gint image_ID,
+ guint total_len,
+ PSPimage *ia,
+ GError **error)
+{
+ long block_start;
+ guint32 chunk_len, entry_count, pal_size;
+ guint32 color_palette_entries;
+ guchar *color_palette;
+
+ block_start = ftell (f);
+
+ if (psp_ver_major >= 4)
+ {
+ if (fread (&chunk_len, 4, 1, f) < 1
+ || fread (&entry_count, 4, 1, f) < 1)
+ {
+ g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
+ _("Error reading color block"));
+ return -1;
+ }
+
+ chunk_len = GUINT32_FROM_LE (chunk_len);
+
+ if (try_fseek (f, block_start + chunk_len, SEEK_SET, error) < 0)
+ {
+ g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
+ _("Error reading color block"));
+ return -1;
+ }
+ }
+ else
+ {
+ if (fread (&entry_count, 4, 1, f) < 1)
+ {
+ g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
+ _("Error reading color block"));
+ return -1;
+ }
+ }
+
+ color_palette_entries = GUINT32_FROM_LE (entry_count);
+ /* TODO: GIMP currently only supports a maximum of 256 colors
+ * in an indexed image. If this changes, we can change this check */
+ if (color_palette_entries > 256)
+ {
+ g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
+ _("Error: Unsupported palette size"));
+ return -1;
+ }
+
+ /* psp color palette entries are stored as RGBA so 4 bytes per entry
+ * where the fourth bytes is always zero */
+ pal_size = color_palette_entries * 4;
+ color_palette = g_malloc (pal_size);
+ if (fread (color_palette, pal_size, 1, f) < 1)
+ {
+ g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
+ _("Error reading color palette"));
+ return -1;
+ }
+ else
+ {
+ guchar *tmpmap;
+ gint i;
+
+ /* Convert to BGR palette */
+ tmpmap = g_malloc (3 * color_palette_entries);
+ for (i = 0; i < color_palette_entries; ++i)
+ {
+ tmpmap[i*3 ] = color_palette[i*4+2];
+ tmpmap[i*3+1] = color_palette[i*4+1];
+ tmpmap[i*3+2] = color_palette[i*4];
+ }
+
+ memcpy (color_palette, tmpmap, color_palette_entries * 3);
+ g_free (tmpmap);
+
+ gimp_image_set_colormap (image_ID, color_palette, color_palette_entries);
+ g_free (color_palette);
+ }
+
+ return 0;
+}
+
+static void inline
+swab_rect (guint32 *rect)
+{
+ rect[0] = GUINT32_FROM_LE (rect[0]);
+ rect[1] = GUINT32_FROM_LE (rect[1]);
+ rect[2] = GUINT32_FROM_LE (rect[2]);
+ rect[3] = GUINT32_FROM_LE (rect[3]);
+}
+
+static GimpLayerMode
+gimp_layer_mode_from_psp_blend_mode (PSPBlendModes mode)
+{
+ switch (mode)
+ {
+ case PSP_BLEND_NORMAL:
+ return GIMP_LAYER_MODE_NORMAL_LEGACY;
+
+ case PSP_BLEND_DARKEN:
+ return GIMP_LAYER_MODE_DARKEN_ONLY_LEGACY;
+
+ case PSP_BLEND_LIGHTEN:
+ return GIMP_LAYER_MODE_LIGHTEN_ONLY_LEGACY;
+
+ case PSP_BLEND_HUE:
+ return GIMP_LAYER_MODE_HSV_HUE_LEGACY;
+
+ case PSP_BLEND_SATURATION:
+ return GIMP_LAYER_MODE_HSV_SATURATION_LEGACY;
+
+ case PSP_BLEND_COLOR:
+ return GIMP_LAYER_MODE_HSL_COLOR_LEGACY;
+
+ case PSP_BLEND_LUMINOSITY:
+ return GIMP_LAYER_MODE_HSV_VALUE_LEGACY; /* ??? */
+
+ case PSP_BLEND_MULTIPLY:
+ return GIMP_LAYER_MODE_MULTIPLY_LEGACY;
+
+ case PSP_BLEND_SCREEN:
+ return GIMP_LAYER_MODE_SCREEN_LEGACY;
+
+ case PSP_BLEND_DISSOLVE:
+ return GIMP_LAYER_MODE_DISSOLVE;
+
+ case PSP_BLEND_OVERLAY:
+ return GIMP_LAYER_MODE_OVERLAY;
+
+ case PSP_BLEND_HARD_LIGHT:
+ return GIMP_LAYER_MODE_HARDLIGHT_LEGACY;
+
+ case PSP_BLEND_SOFT_LIGHT:
+ return GIMP_LAYER_MODE_SOFTLIGHT_LEGACY;
+
+ case PSP_BLEND_DIFFERENCE:
+ return GIMP_LAYER_MODE_DIFFERENCE_LEGACY;
+
+ case PSP_BLEND_DODGE:
+ return GIMP_LAYER_MODE_DODGE_LEGACY;
+
+ case PSP_BLEND_BURN:
+ return GIMP_LAYER_MODE_BURN_LEGACY;
+
+ case PSP_BLEND_EXCLUSION:
+ return GIMP_LAYER_MODE_EXCLUSION;
+
+ case PSP_BLEND_TRUE_HUE:
+ return GIMP_LAYER_MODE_HSV_HUE;
+
+ case PSP_BLEND_TRUE_SATURATION:
+ return GIMP_LAYER_MODE_HSV_SATURATION;
+
+ case PSP_BLEND_TRUE_COLOR:
+ return GIMP_LAYER_MODE_HSL_COLOR;
+
+ case PSP_BLEND_TRUE_LIGHTNESS:
+ return GIMP_LAYER_MODE_HSV_VALUE;
+
+ case PSP_BLEND_ADJUST:
+ return -1; /* ??? */
+ }
+ return -1;
+}
+
+static const gchar *
+blend_mode_name (PSPBlendModes mode)
+{
+ static const gchar *blend_mode_names[] =
+ {
+ "NORMAL",
+ "DARKEN",
+ "LIGHTEN",
+ "HUE",
+ "SATURATION",
+ "COLOR",
+ "LUMINOSITY",
+ "MULTIPLY",
+ "SCREEN",
+ "DISSOLVE",
+ "OVERLAY",
+ "HARD_LIGHT",
+ "SOFT_LIGHT",
+ "DIFFERENCE",
+ "DODGE",
+ "BURN",
+ "EXCLUSION",
+ "TRUE HUE",
+ "TRUE SATURATION",
+ "TRUE COLOR",
+ "TRUE LIGHTNESS",
+ /* ADJUST should always be the last one. */
+ "ADJUST"
+ };
+ static gchar *err_name = NULL;
+
+ if (mode >= 0 && mode <= PSP_BLEND_TRUE_LIGHTNESS)
+ return blend_mode_names[mode];
+ else if (mode == PSP_BLEND_ADJUST)
+ return blend_mode_names[PSP_BLEND_TRUE_LIGHTNESS+1];
+
+ g_free (err_name);
+ err_name = g_strdup_printf ("unknown layer blend mode %d", mode);
+
+ return err_name;
+}
+
+static const gchar *
+layer_type_name (PSPLayerTypePSP6 type)
+{
+ static const gchar *layer_type_names[] =
+ {
+ "Undefined",
+ "Raster",
+ "Floating Raster Selection",
+ "Vector",
+ "Adjustment",
+ "Group",
+ "Mask",
+ "Art Media",
+ };
+ static gchar *err_name = NULL;
+
+ if (type >= 0 && type <= keGLTArtMedia)
+ return layer_type_names[type];
+
+ g_free (err_name);
+ err_name = g_strdup_printf ("unknown layer type %d", type);
+
+ return err_name;
+}
+
+static const gchar *
+bitmap_type_name (gint type)
+{
+ static const gchar *bitmap_type_names[] =
+ {
+ "IMAGE",
+ "TRANS_MASK",
+ "USER_MASK",
+ "SELECTION",
+ "ALPHA_MASK",
+ "THUMBNAIL"
+ };
+ static gchar *err_name = NULL;
+
+ if (type >= 0 && type <= PSP_DIB_THUMBNAIL)
+ return bitmap_type_names[type];
+
+ g_free (err_name);
+ err_name = g_strdup_printf ("unknown bitmap type %d", type);
+
+ return err_name;
+}
+
+static const gchar *
+channel_type_name (gint type)
+{
+ static const gchar *channel_type_names[] =
+ {
+ "COMPOSITE",
+ "RED",
+ "GREEN",
+ "BLUE"
+ };
+ static gchar *err_name = NULL;
+
+ if (type >= 0 && type <= PSP_CHANNEL_BLUE)
+ return channel_type_names[type];
+
+ g_free (err_name);
+ err_name = g_strdup_printf ("unknown channel type %d", type);
+
+ return err_name;
+}
+
+static void *
+psp_zalloc (void *opaque,
+ guint items,
+ guint size)
+{
+ return g_malloc (items*size);
+}
+
+static void
+psp_zfree (void *opaque,
+ void *ptr)
+{
+ g_free (ptr);
+}
+
+static void
+upscale_indexed_sub_8 (FILE *f,
+ gint width,
+ gint height,
+ gint bpp,
+ guchar *buf)
+{
+ gint x, y, b, line_width;
+ gint bpp_zero_based = bpp - 1;
+ gint current_bit = 0;
+ guchar *tmpbuf, *buf_start, *src;
+
+ /* Scanlines for 1 and 4 bit only end on a 4-byte boundary. */
+ line_width = (((width * bpp + 7) / 8) + bpp_zero_based) / 4 * 4;
+ buf_start = g_malloc0 (width * height);
+ tmpbuf = buf_start;
+
+ for (y = 0; y < height; tmpbuf += width, ++y)
+ {
+ src = buf + y * line_width;
+ for (x = 0; x < width; ++x)
+ {
+ for (b = 0; b < bpp; b++)
+ {
+ current_bit = bpp * x + b;
+ if (src[current_bit / 8] & (128 >> (current_bit % 8)))
+ tmpbuf[x] += (1 << (bpp_zero_based - b));
+ }
+ }
+ }
+
+ memcpy (buf, buf_start, width * height);
+ g_free (buf_start);
+}
+
+static int
+read_channel_data (FILE *f,
+ PSPimage *ia,
+ guchar **pixels,
+ guint bytespp,
+ guint offset,
+ GeglBuffer *buffer,
+ guint32 compressed_len,
+ GError **error)
+{
+ gint i, y, line_width;
+ gint width = gegl_buffer_get_width (buffer);
+ gint height = gegl_buffer_get_height (buffer);
+ gint npixels = width * height;
+ guchar *buf;
+ guchar *buf2 = NULL; /* please the compiler */
+ guchar runcount, byte;
+ z_stream zstream;
+
+ g_assert (ia->bytes_per_sample <= 2);
+
+ if (ia->depth < 8)
+ {
+ /* Scanlines for 1 and 4 bit only end on a 4-byte boundary. */
+ line_width = (((width * ia->depth + 7) / 8) + ia->depth - 1) / 4 * 4;
+ }
+ else
+ {
+ line_width = width * ia->bytes_per_sample;
+ }
+
+ switch (ia->compression)
+ {
+ case PSP_COMP_NONE:
+ if (bytespp == 1)
+ {
+ fread (pixels[0], height * line_width, 1, f);
+ }
+ else
+ {
+ buf = g_malloc (line_width);
+ if (ia->bytes_per_sample == 1)
+ {
+ for (y = 0; y < height; y++)
+ {
+ guchar *p, *q;
+
+ fread (buf, width, 1, f);
+ /* Contrary to what the PSP specification seems to suggest
+ scanlines are not stored on a 4-byte boundary. */
+ p = buf;
+ q = pixels[y] + offset;
+ for (i = 0; i < width; i++)
+ {
+ *q = *p++;
+ q += bytespp;
+ }
+ }
+ }
+ else if (ia->bytes_per_sample == 2)
+ {
+ for (y = 0; y < height; y++)
+ {
+ guint16 *p, *q;
+
+ fread (buf, width * ia->bytes_per_sample, 1, f);
+ /* Contrary to what the PSP specification seems to suggest
+ scanlines are not stored on a 4-byte boundary. */
+ p = (guint16 *) buf;
+ q = (guint16 *) (pixels[y] + offset);
+ for (i = 0; i < width; i++)
+ {
+ *q = GUINT16_FROM_LE (*p++);
+ q += bytespp / 2;
+ }
+ }
+ }
+
+ g_free (buf);
+ }
+ break;
+
+ case PSP_COMP_RLE:
+ {
+ guchar *q, *endq;
+
+ q = pixels[0] + offset;
+ if (ia->depth >= 8)
+ endq = q + npixels * bytespp;
+ else
+ endq = q + line_width * height;
+
+ buf = g_malloc (128);
+ while (q < endq)
+ {
+ fread (&runcount, 1, 1, f);
+ if (runcount > 128)
+ {
+ runcount -= 128;
+ fread (&byte, 1, 1, f);
+ memset (buf, byte, runcount);
+ }
+ else
+ fread (buf, runcount, 1, f);
+
+ /* prevent buffer overflow for bogus data */
+ if (runcount > (endq - q) / bytespp + ia->bytes_per_sample - 1)
+ {
+ g_printerr ("Buffer overflow decompressing RLE data.\n");
+ break;
+ }
+
+ if (bytespp == 1)
+ {
+ memmove (q, buf, runcount);
+ q += runcount;
+ }
+ else if (ia->bytes_per_sample == 1)
+ {
+ guchar *p = buf;
+
+ for (i = 0; i < runcount; i++)
+ {
+ *q = *p++;
+ q += bytespp;
+ }
+ }
+ else if (ia->bytes_per_sample == 2)
+ {
+ guint16 *p = (guint16 *) buf;
+ guint16 *r = (guint16 *) q;
+
+ for (i = 0; i < runcount / 2; i++)
+ {
+ *r = GUINT16_FROM_LE (*p++);
+ r += bytespp / 2;
+ }
+ q = (guchar *) r;
+ }
+ }
+ g_free (buf);
+ }
+ break;
+
+ case PSP_COMP_LZ77:
+ buf = g_malloc (compressed_len);
+ fread (buf, compressed_len, 1, f);
+ zstream.next_in = buf;
+ zstream.avail_in = compressed_len;
+ zstream.zalloc = psp_zalloc;
+ zstream.zfree = psp_zfree;
+ zstream.opaque = f;
+ if (inflateInit (&zstream) != Z_OK)
+ {
+ g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
+ _("zlib error"));
+ return -1;
+ }
+ if (bytespp == 1)
+ zstream.next_out = pixels[0];
+ else
+ {
+ buf2 = g_malloc (npixels * ia->bytes_per_sample);
+ zstream.next_out = buf2;
+ }
+ zstream.avail_out = npixels * ia->bytes_per_sample;
+ if (inflate (&zstream, Z_FINISH) != Z_STREAM_END)
+ {
+ g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
+ _("zlib error"));
+ inflateEnd (&zstream);
+ return -1;
+ }
+ inflateEnd (&zstream);
+ g_free (buf);
+
+ if (bytespp > 1)
+ {
+ if (ia->bytes_per_sample == 1)
+ {
+ guchar *p, *q;
+
+ p = buf2;
+ q = pixels[0] + offset;
+ for (i = 0; i < npixels; i++)
+ {
+ *q = *p++;
+ q += bytespp;
+ }
+ g_free (buf2);
+ }
+ else if (ia->bytes_per_sample == 2)
+ {
+ guint16 *p, *q;
+
+ p = (guint16 *) buf2;
+ q = (guint16 *) (pixels[0] + offset);
+ for (i = 0; i < npixels; i++)
+ {
+ *q = GUINT16_FROM_LE (*p++);
+ q += bytespp / 2;
+ }
+ g_free (buf2);
+ }
+ }
+ break;
+ }
+
+ if (ia->base_type == GIMP_INDEXED && ia->depth < 8)
+ {
+ /* We need to convert 1 and 4 bit to 8 bit indexed */
+ upscale_indexed_sub_8 (f, width, height, ia->depth, pixels[0]);
+ }
+
+ return 0;
+}
+
+static gboolean
+read_raster_layer_info (FILE *f,
+ long layer_extension_start,
+ guint16 *bitmap_count,
+ guint16 *channel_count,
+ GError **error)
+{
+ long block_start;
+ guint32 layer_extension_len, block_len;
+ gint block_id;
+
+ if (fseek (f, layer_extension_start, SEEK_SET) < 0
+ || fread (&layer_extension_len, 4, 1, f) < 1)
+ {
+ g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
+ _("Error reading layer extension information"));
+ return FALSE;
+ }
+
+ /* Newer versions of PSP have an extra block here for raster layers
+ with block id = 0x21. Most likely this was to fix an oversight in
+ the specification since vector and adjustment layers already were
+ using a similar extension block since file version 4.
+ The old chunk with bitmap_count and channel_count starts after this block.
+ We do not know starting from which version this change was implemented
+ but most likely version 9 (could also be version 10) so we can't test
+ based on version number only.
+ Although this is kind of a hack we can safely test for the block starting
+ code since the layer_extension_len here is always a small number.
+ */
+ if (psp_ver_major > 8 && memcmp (&layer_extension_len, "~BK\0", 4) == 0)
+ {
+ if (fread (&block_id, 2, 1, f) < 1
+ || fread (&block_len, 4, 1, f) < 1)
+ {
+ g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
+ _("Error reading block information"));
+ return FALSE;
+ }
+ block_id = GUINT16_FROM_LE (block_id);
+ block_len = GUINT32_FROM_LE (block_len);
+
+ block_start = ftell (f);
+ layer_extension_start = block_start + block_len;
+
+ if (fseek (f, layer_extension_start, SEEK_SET) < 0
+ || fread (&layer_extension_len, 4, 1, f) < 1)
+ {
+ g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
+ _("Error reading layer extension information"));
+ return FALSE;
+ }
+ }
+ layer_extension_len = GUINT32_FROM_LE (layer_extension_len);
+
+ if (fread (bitmap_count, 2, 1, f) < 1
+ || fread (channel_count, 2, 1, f) < 1)
+ {
+ g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
+ _("Error reading layer extension information"));
+ return FALSE;
+ }
+ if (try_fseek (f, layer_extension_start + layer_extension_len, SEEK_SET, error) < 0)
+ {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gint
+read_layer_block (FILE *f,
+ gint image_ID,
+ guint total_len,
+ PSPimage *ia,
+ GError **error)
+{
+ gint i;
+ long block_start, sub_block_start, channel_start;
+ long layer_extension_start;
+ gint sub_id;
+ guint32 sub_init_len, sub_total_len;
+ guint32 chunk_len;
+ gchar *name = NULL;
+ gchar *layer_name = NULL;
+ guint16 namelen;
+ guchar type, opacity, blend_mode, visibility, transparency_protected;
+ guchar link_group_id, mask_linked, mask_disabled;
+ guint32 image_rect[4], saved_image_rect[4], mask_rect[4], saved_mask_rect[4];
+ gboolean null_layer, can_handle_layer;
+ guint16 bitmap_count, channel_count;
+ GimpImageType drawable_type;
+ guint32 layer_ID = 0;
+ GimpLayerMode layer_mode;
+ guint32 channel_init_len, channel_total_len;
+ guint32 compressed_len, uncompressed_len;
+ guint16 bitmap_type, channel_type;
+ gint width, height, bytespp, offset;
+ guchar **pixels, *pixel;
+ GeglBuffer *buffer;
+
+ block_start = ftell (f);
+
+ while (ftell (f) < block_start + total_len)
+ {
+ null_layer = FALSE;
+ can_handle_layer = FALSE;
+
+ /* Read the layer sub-block header */
+ sub_id = read_block_header (f, &sub_init_len, &sub_total_len, error);
+ if (sub_id == -1)
+ return -1;
+
+ if (sub_id != PSP_LAYER_BLOCK)
+ {
+ g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
+ _("Invalid layer sub-block %s, should be LAYER"),
+ block_name (sub_id));
+ return -1;
+ }
+
+ sub_block_start = ftell (f);
+
+ /* Read layer information chunk */
+ if (psp_ver_major >= 4)
+ {
+ if (fread (&chunk_len, 4, 1, f) < 1
+ || fread (&namelen, 2, 1, f) < 1
+ /* A zero length layer name is apparently valid. To not get a warning for
+ namelen < 0 always being false we use this more complicated comparison. */
+ || ((namelen = GUINT16_FROM_LE (namelen)) && (FALSE || namelen == 0))
+ || (name = g_malloc (namelen + 1)) == NULL
+ || (namelen > 0 && fread (name, namelen, 1, f) < 1)
+ || fread (&type, 1, 1, f) < 1
+ || fread (&image_rect, 16, 1, f) < 1
+ || fread (&saved_image_rect, 16, 1, f) < 1
+ || fread (&opacity, 1, 1, f) < 1
+ || fread (&blend_mode, 1, 1, f) < 1
+ || fread (&visibility, 1, 1, f) < 1
+ || fread (&transparency_protected, 1, 1, f) < 1
+ || fread (&link_group_id, 1, 1, f) < 1
+ || fread (&mask_rect, 16, 1, f) < 1
+ || fread (&saved_mask_rect, 16, 1, f) < 1
+ || fread (&mask_linked, 1, 1, f) < 1
+ || fread (&mask_disabled, 1, 1, f) < 1)
+ {
+ g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
+ _("Error reading layer information chunk"));
+ g_free (name);
+ return -1;
+ }
+
+ name[namelen] = 0;
+ layer_name = g_convert (name, -1, "utf-8", "iso8859-1", NULL, NULL, NULL);
+ g_free (name);
+
+ chunk_len = GUINT32_FROM_LE (chunk_len);
+ layer_extension_start = sub_block_start + chunk_len;
+
+ switch (type)
+ {
+ case keGLTFloatingRasterSelection:
+ g_message ("Floating selection restored as normal layer (%s)", layer_name);
+ case keGLTRaster:
+ if (! read_raster_layer_info (f, layer_extension_start,
+ &bitmap_count,
+ &channel_count,
+ error))
+ {
+ g_free (layer_name);
+ return -1;
+ }
+ can_handle_layer = TRUE;
+ break;
+ default:
+ bitmap_count = 0;
+ channel_count = 0;
+ g_message ("Unsupported layer type %s (%s)", layer_type_name(type), layer_name);
+ break;
+ }
+ }
+ else
+ {
+ name = g_malloc (257);
+ name[256] = 0;
+
+ if (fread (name, 256, 1, f) < 1
+ || fread (&type, 1, 1, f) < 1
+ || fread (&image_rect, 16, 1, f) < 1
+ || fread (&saved_image_rect, 16, 1, f) < 1
+ || fread (&opacity, 1, 1, f) < 1
+ || fread (&blend_mode, 1, 1, f) < 1
+ || fread (&visibility, 1, 1, f) < 1
+ || fread (&transparency_protected, 1, 1, f) < 1
+ || fread (&link_group_id, 1, 1, f) < 1
+ || fread (&mask_rect, 16, 1, f) < 1
+ || fread (&saved_mask_rect, 16, 1, f) < 1
+ || fread (&mask_linked, 1, 1, f) < 1
+ || fread (&mask_disabled, 1, 1, f) < 1
+ || fseek (f, 43, SEEK_CUR) < 0
+ || fread (&bitmap_count, 2, 1, f) < 1
+ || fread (&channel_count, 2, 1, f) < 1)
+ {
+ g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
+ _("Error reading layer information chunk"));
+ g_free (name);
+ return -1;
+ }
+ layer_name = g_convert (name, -1, "utf-8", "iso8859-1", NULL, NULL, NULL);
+ g_free (name);
+ if (type == PSP_LAYER_FLOATING_SELECTION)
+ g_message ("Floating selection restored as normal layer");
+ type = keGLTRaster;
+ can_handle_layer = TRUE;
+ if (try_fseek (f, sub_block_start + sub_init_len, SEEK_SET, error) < 0)
+ {
+ g_free (layer_name);
+ return -1;
+ }
+ }
+
+ swab_rect (image_rect);
+ swab_rect (saved_image_rect);
+ swab_rect (mask_rect);
+ swab_rect (saved_mask_rect);
+ bitmap_count = GUINT16_FROM_LE (bitmap_count);
+ channel_count = GUINT16_FROM_LE (channel_count);
+
+ layer_mode = gimp_layer_mode_from_psp_blend_mode (blend_mode);
+ if ((int) layer_mode == -1)
+ {
+ g_message ("Unsupported PSP layer blend mode %s "
+ "for layer %s, setting layer invisible",
+ blend_mode_name (blend_mode), layer_name);
+ layer_mode = GIMP_LAYER_MODE_NORMAL_LEGACY;
+ visibility = FALSE;
+ }
+
+ width = saved_image_rect[2] - saved_image_rect[0];
+ height = saved_image_rect[3] - saved_image_rect[1];
+
+ if ((width < 0) || (width > GIMP_MAX_IMAGE_SIZE) /* w <= 2^18 */
+ || (height < 0) || (height > GIMP_MAX_IMAGE_SIZE) /* h <= 2^18 */
+ || ((width / 256) * (height / 256) >= 8192)) /* w * h < 2^29 */
+ {
+ g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
+ _("Invalid layer dimensions: %dx%d"),
+ width, height);
+ g_free (layer_name);
+ return -1;
+ }
+
+ IFDBG(2) g_message
+ ("layer: %s %dx%d (%dx%d) @%d,%d opacity %d blend_mode %s "
+ "%d bitmaps %d channels",
+ layer_name,
+ image_rect[2] - image_rect[0], image_rect[3] - image_rect[1],
+ width, height,
+ image_rect[0]+saved_image_rect[0], image_rect[1]+saved_image_rect[1],
+ opacity, blend_mode_name (blend_mode),
+ bitmap_count, channel_count);
+
+ IFDBG(2) g_message
+ ("mask %dx%d (%dx%d) @%d,%d",
+ mask_rect[2] - mask_rect[0],
+ mask_rect[3] - mask_rect[1],
+ saved_mask_rect[2] - saved_mask_rect[0],
+ saved_mask_rect[3] - saved_mask_rect[1],
+ saved_mask_rect[0], saved_mask_rect[1]);
+
+ if (width == 0)
+ {
+ width++;
+ null_layer = TRUE;
+ }
+ if (height == 0)
+ {
+ height++;
+ null_layer = TRUE;
+ }
+
+ if (ia->base_type == GIMP_RGB)
+ if (bitmap_count == 1)
+ drawable_type = GIMP_RGB_IMAGE, bytespp = 3;
+ else
+ drawable_type = GIMP_RGBA_IMAGE, bytespp = 4;
+ else if (ia->base_type == GIMP_GRAY)
+ if (bitmap_count == 1)
+ drawable_type = GIMP_GRAY_IMAGE, bytespp = 1;
+ else
+ drawable_type = GIMP_GRAYA_IMAGE, bytespp = 2;
+ else
+ if (bitmap_count == 1)
+ drawable_type = GIMP_INDEXED_IMAGE, bytespp = 1;
+ else
+ drawable_type = GIMP_INDEXEDA_IMAGE, bytespp = 2;
+ bytespp *= ia->bytes_per_sample;
+
+ layer_ID = gimp_layer_new (image_ID, layer_name,
+ width, height,
+ drawable_type,
+ 100.0 * opacity / 255.0,
+ layer_mode);
+ g_free (layer_name);
+ if (layer_ID == -1)
+ {
+ g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
+ _("Error creating layer"));
+ return -1;
+ }
+
+ gimp_image_insert_layer (image_ID, layer_ID, -1, -1);
+
+ if (image_rect[0] != 0 || image_rect[1] != 0 || saved_image_rect[0] != 0 || saved_image_rect[1] != 0)
+ gimp_layer_set_offsets (layer_ID,
+ image_rect[0] + saved_image_rect[0], image_rect[1] + saved_image_rect[1]);
+
+ if (!visibility)
+ gimp_item_set_visible (layer_ID, FALSE);
+
+ gimp_layer_set_lock_alpha (layer_ID, transparency_protected);
+
+ if (can_handle_layer)
+ {
+ pixel = g_malloc0 (height * width * bytespp);
+ if (null_layer)
+ {
+ pixels = NULL;
+ }
+ else
+ {
+ pixels = g_new (guchar *, height);
+ for (i = 0; i < height; i++)
+ pixels[i] = pixel + width * bytespp * i;
+ }
+
+ buffer = gimp_drawable_get_buffer (layer_ID);
+
+ /* Read the layer channel sub-blocks */
+ while (ftell (f) < sub_block_start + sub_total_len)
+ {
+ sub_id = read_block_header (f, &channel_init_len,
+ &channel_total_len, error);
+ if (sub_id == -1)
+ {
+ gimp_image_delete (image_ID);
+ return -1;
+ }
+
+ if (sub_id != PSP_CHANNEL_BLOCK)
+ {
+ g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
+ _("Invalid layer sub-block %s, should be CHANNEL"),
+ block_name (sub_id));
+ return -1;
+ }
+
+ channel_start = ftell (f);
+ chunk_len = channel_init_len; /* init chunk_len for psp_ver_major == 3 */
+ if ((psp_ver_major >= 4
+ && (fread (&chunk_len, 4, 1, f) < 1
+ || ((chunk_len = GUINT32_FROM_LE (chunk_len)) < 16)))
+ || fread (&compressed_len, 4, 1, f) < 1
+ || fread (&uncompressed_len, 4, 1, f) < 1
+ || fread (&bitmap_type, 2, 1, f) < 1
+ || fread (&channel_type, 2, 1, f) < 1)
+ {
+ g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
+ _("Error reading channel information chunk"));
+ return -1;
+ }
+
+ compressed_len = GUINT32_FROM_LE (compressed_len);
+ uncompressed_len = GUINT32_FROM_LE (uncompressed_len);
+ bitmap_type = GUINT16_FROM_LE (bitmap_type);
+ channel_type = GUINT16_FROM_LE (channel_type);
+
+ if (bitmap_type > PSP_DIB_USER_MASK)
+ {
+ g_message ("Conversion of bitmap type %d is not supported.", bitmap_type);
+ }
+ else if (bitmap_type == PSP_DIB_USER_MASK)
+ {
+ /* FIXME: Add as layer mask */
+ g_message ("Conversion of layer mask is not supported");
+ }
+ else
+ {
+ if (channel_type > PSP_CHANNEL_BLUE)
+ {
+ g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
+ _("Invalid channel type %d in channel information chunk"),
+ channel_type);
+ return -1;
+ }
+
+ IFDBG(2) g_message ("channel: %s %s %d (%d) bytes %d bytespp",
+ bitmap_type_name (bitmap_type),
+ channel_type_name (channel_type),
+ uncompressed_len, compressed_len,
+ bytespp);
+
+ if (bitmap_type == PSP_DIB_TRANS_MASK || channel_type == PSP_CHANNEL_COMPOSITE)
+ offset = bytespp - ia->bytes_per_sample;
+ else
+ offset = (channel_type - PSP_CHANNEL_RED) * ia->bytes_per_sample;
+
+ if (!null_layer)
+ {
+ if (try_fseek (f, channel_start + chunk_len, SEEK_SET, error) < 0)
+ {
+ return -1;
+ }
+
+ if (read_channel_data (f, ia, pixels, bytespp, offset,
+ buffer, compressed_len, error) == -1)
+ {
+ return -1;
+ }
+ }
+ }
+ if (try_fseek (f, channel_start + channel_total_len, SEEK_SET, error) < 0)
+ {
+ return -1;
+ }
+ }
+
+ gegl_buffer_set (buffer, GEGL_RECTANGLE (0, 0, width, height), 0,
+ NULL, pixel, GEGL_AUTO_ROWSTRIDE);
+
+ g_object_unref (buffer);
+
+ g_free (pixels);
+ g_free (pixel);
+ if (psp_ver_major >= 4)
+ {
+ if (try_fseek (f, sub_block_start + sub_total_len, SEEK_SET, error) < 0)
+ {
+ return -1;
+ }
+ }
+ }
+ else
+ {
+ /* Can't handle this type of layer, skip the data so we can read the next layer. */
+ if (psp_ver_major >= 4)
+ {
+ if (try_fseek (f, sub_block_start + sub_total_len, SEEK_SET, error) < 0)
+ {
+ return -1;
+ }
+ }
+ }
+ }
+
+ if (try_fseek (f, block_start + total_len, SEEK_SET, error) < 0)
+ {
+ return -1;
+ }
+
+ return layer_ID;
+}
+
+static gint
+read_tube_block (FILE *f,
+ gint image_ID,
+ guint total_len,
+ PSPimage *ia,
+ GError **error)
+{
+ guint16 version;
+ guchar name[514];
+ guint32 step_size, column_count, row_count, cell_count;
+ guint32 placement_mode, selection_mode;
+ guint32 chunk_len;
+ gint i;
+ GimpPixPipeParams params;
+ GimpParasite *pipe_parasite;
+ gchar *parasite_text;
+
+ gimp_pixpipe_params_init (&params);
+
+ if (psp_ver_major >= 4)
+ {
+ name[0] = 0;
+ if (fread (&chunk_len, 4, 1, f) < 1
+ || fread (&version, 2, 1, f) < 1
+ || fread (&step_size, 4, 1, f) < 1
+ || fread (&column_count, 4, 1, f) < 1
+ || fread (&row_count, 4, 1, f) < 1
+ || fread (&cell_count, 4, 1, f) < 1
+ || fread (&placement_mode, 4, 1, f) < 1
+ || fread (&selection_mode, 4, 1, f) < 1)
+ {
+ g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
+ _("Error reading tube data chunk"));
+ return -1;
+ }
+ }
+ else
+ {
+ chunk_len = 0;
+ if (fread (&version, 2, 1, f) < 1
+ || fread (name, 513, 1, f) < 1
+ || fread (&step_size, 4, 1, f) < 1
+ || fread (&column_count, 4, 1, f) < 1
+ || fread (&row_count, 4, 1, f) < 1
+ || fread (&cell_count, 4, 1, f) < 1
+ || fread (&placement_mode, 4, 1, f) < 1
+ || fread (&selection_mode, 4, 1, f) < 1)
+ {
+ g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
+ _("Error reading tube data chunk"));
+ return -1;
+ }
+ name[513] = 0;
+ }
+
+ version = GUINT16_FROM_LE (version);
+ params.step = GUINT32_FROM_LE (step_size);
+ params.cols = GUINT32_FROM_LE (column_count);
+ params.rows = GUINT32_FROM_LE (row_count);
+ params.ncells = GUINT32_FROM_LE (cell_count);
+ placement_mode = GUINT32_FROM_LE (placement_mode);
+ selection_mode = GUINT32_FROM_LE (selection_mode);
+
+ for (i = 1; i < params.cols; i++)
+ gimp_image_add_vguide (image_ID, (ia->width * i)/params.cols);
+ for (i = 1; i < params.rows; i++)
+ gimp_image_add_hguide (image_ID, (ia->height * i)/params.rows);
+
+ /* We use a parasite to pass in the tube (pipe) parameters in
+ * case we will have any use of those, for instance in the gpb
+ * plug-in that exports a GIMP image pipe.
+ */
+ params.dim = 1;
+ params.cellwidth = ia->width / params.cols;
+ params.cellheight = ia->height / params.rows;
+ params.placement = (placement_mode == tpmRandom ? "random" :
+ (placement_mode == tpmConstant ? "constant" :
+ "default"));
+ params.rank[0] = params.ncells;
+ params.selection[0] = (selection_mode == tsmRandom ? "random" :
+ (selection_mode == tsmIncremental ? "incremental" :
+ (selection_mode == tsmAngular ? "angular" :
+ (selection_mode == tsmPressure ? "pressure" :
+ (selection_mode == tsmVelocity ? "velocity" :
+ "default")))));
+ parasite_text = gimp_pixpipe_params_build (&params);
+
+ IFDBG(2) g_message ("parasite: %s", parasite_text);
+
+ pipe_parasite = gimp_parasite_new ("gimp-brush-pipe-parameters",
+ GIMP_PARASITE_PERSISTENT,
+ strlen (parasite_text) + 1, parasite_text);
+ gimp_image_attach_parasite (image_ID, pipe_parasite);
+ gimp_parasite_free (pipe_parasite);
+ g_free (parasite_text);
+
+ return 0;
+}
+
+static const gchar *
+compression_name (gint compression)
+{
+ switch (compression)
+ {
+ case PSP_COMP_NONE:
+ return "no compression";
+ case PSP_COMP_RLE:
+ return "RLE";
+ case PSP_COMP_LZ77:
+ return "LZ77";
+ }
+ g_assert_not_reached ();
+
+ return NULL;
+}
+
+/* The main function for loading PSP-images
+ */
+static gint32
+load_image (const gchar *filename,
+ GError **error)
+{
+ FILE *f;
+ GStatBuf st;
+ char buf[32];
+ PSPimage ia;
+ guint32 block_init_len, block_total_len;
+ long block_start;
+ PSPBlockID id = -1;
+ gint block_number;
+ gint32 image_ID = -1;
+
+ if (g_stat (filename, &st) == -1)
+ return -1;
+
+ f = g_fopen (filename, "rb");
+ if (f == NULL)
+ {
+ g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
+ _("Could not open '%s' for reading: %s"),
+ gimp_filename_to_utf8 (filename), g_strerror (errno));
+ return -1;
+ }
+
+ /* Read the PSP File Header and determine file version */
+ if (fread (buf, 32, 1, f) < 1
+ || fread (&psp_ver_major, 2, 1, f) < 1
+ || fread (&psp_ver_minor, 2, 1, f) < 1)
+ {
+ g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
+ _("Error reading file header."));
+ goto error;
+ }
+
+ if (memcmp (buf, "Paint Shop Pro Image File\n\032\0\0\0\0\0", 32) != 0)
+ {
+ g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
+ _("Incorrect file signature."));
+ goto error;
+ }
+
+ psp_ver_major = GUINT16_FROM_LE (psp_ver_major);
+ psp_ver_minor = GUINT16_FROM_LE (psp_ver_minor);
+
+ /* We don't have the documentation for file format versions before 3.0,
+ * but newer versions should be mostly backwards compatible so that
+ * we can still read the image and skip unknown parts safely.
+ */
+ if (psp_ver_major < 3)
+ {
+ g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
+ _("Unsupported PSP file format version %d.%d."),
+ psp_ver_major, psp_ver_minor);
+ goto error;
+ }
+
+ /* Read all the blocks */
+ block_number = 0;
+
+ IFDBG(3) g_message ("size = %d", (int)st.st_size);
+ while (ftell (f) != st.st_size
+ && (id = read_block_header (f, &block_init_len,
+ &block_total_len, error)) != -1)
+ {
+ block_start = ftell (f);
+
+ if (block_start + block_total_len > st.st_size)
+ {
+ g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
+ _("Could not open '%s' for reading: %s"),
+ gimp_filename_to_utf8 (filename),
+ _("invalid block size"));
+ goto error;
+ }
+
+ if (id == PSP_IMAGE_BLOCK)
+ {
+ if (block_number != 0)
+ {
+ g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
+ _("Duplicate General Image Attributes block."));
+ goto error;
+ }
+ if (read_general_image_attribute_block (f, block_init_len,
+ block_total_len, &ia, error) == -1)
+ {
+ goto error;
+ }
+
+ IFDBG(2) g_message ("%d dpi %dx%d %s",
+ (int) ia.resolution,
+ ia.width, ia.height,
+ compression_name (ia.compression));
+
+ image_ID = gimp_image_new_with_precision (ia.width, ia.height,
+ ia.base_type, ia.precision);
+ if (image_ID == -1)
+ {
+ goto error;
+ }
+
+ gimp_image_set_filename (image_ID, filename);
+
+ gimp_image_set_resolution (image_ID, ia.resolution, ia.resolution);
+ }
+ else
+ {
+ if (block_number == 0)
+ {
+ g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
+ _("Missing General Image Attributes block."));
+ goto error;
+ }
+
+ switch (id)
+ {
+ case PSP_CREATOR_BLOCK:
+ if (read_creator_block (f, image_ID, block_total_len, &ia, error) == -1)
+ goto error;
+ break;
+
+ case PSP_COLOR_BLOCK:
+ if (read_color_block (f, image_ID, block_total_len, &ia, error) == -1)
+ goto error;
+ break;
+
+ case PSP_LAYER_START_BLOCK:
+ if (read_layer_block (f, image_ID, block_total_len, &ia, error) == -1)
+ goto error;
+ break;
+
+ case PSP_SELECTION_BLOCK:
+ break; /* Not yet implemented */
+
+ case PSP_ALPHA_BANK_BLOCK:
+ break; /* Not yet implemented */
+
+ case PSP_THUMBNAIL_BLOCK:
+ break; /* No use for it */
+
+ case PSP_EXTENDED_DATA_BLOCK:
+ break; /* Not yet implemented */
+
+ case PSP_TUBE_BLOCK:
+ if (read_tube_block (f, image_ID, block_total_len, &ia, error) == -1)
+ goto error;
+ break;
+
+ case PSP_COMPOSITE_IMAGE_BANK_BLOCK:
+ break; /* Not yet implemented */
+
+ case PSP_TABLE_BANK_BLOCK:
+ break; /* Not yet implemented */
+
+ case PSP_BRUSH_BLOCK:
+ break; /* Not yet implemented */
+
+ case PSP_ART_MEDIA_BLOCK:
+ case PSP_ART_MEDIA_MAP_BLOCK:
+ case PSP_ART_MEDIA_TILE_BLOCK:
+ case PSP_ART_MEDIA_TEXTURE_BLOCK:
+ break; /* Not yet implemented */
+
+ case PSP_COLORPROFILE_BLOCK:
+ break; /* Not yet implemented */
+
+ case PSP_LAYER_BLOCK:
+ case PSP_CHANNEL_BLOCK:
+ case PSP_ALPHA_CHANNEL_BLOCK:
+ case PSP_ADJUSTMENT_EXTENSION_BLOCK:
+ case PSP_VECTOR_EXTENSION_BLOCK:
+ case PSP_SHAPE_BLOCK:
+ case PSP_PAINTSTYLE_BLOCK:
+ case PSP_COMPOSITE_ATTRIBUTES_BLOCK:
+ case PSP_JPEG_BLOCK:
+ case PSP_LINESTYLE_BLOCK:
+ case PSP_TABLE_BLOCK:
+ case PSP_PAPER_BLOCK:
+ case PSP_PATTERN_BLOCK:
+ case PSP_GRADIENT_BLOCK:
+ case PSP_GROUP_EXTENSION_BLOCK:
+ case PSP_MASK_EXTENSION_BLOCK:
+ case PSP_RASTER_EXTENSION_BLOCK:
+ g_message ("Sub-block %s should not occur "
+ "at main level of file",
+ block_name (id));
+ break;
+
+ default:
+ g_message ("Unrecognized block id %d", id);
+ break;
+ }
+ }
+
+ if (block_start + block_total_len >= st.st_size)
+ break;
+
+ if (try_fseek (f, block_start + block_total_len, SEEK_SET, error) < 0)
+ goto error;
+
+ block_number++;
+ }
+
+ if (id == -1)
+ {
+ error:
+ fclose (f);
+ if (image_ID != -1)
+ gimp_image_delete (image_ID);
+ return -1;
+ }
+
+ fclose (f);
+
+ return image_ID;
+}
+
+static gint
+save_image (const gchar *filename,
+ gint32 image_ID,
+ gint32 drawable_ID,
+ GError **error)
+{
+ g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
+ _("Exporting not implemented yet."));
+
+ return FALSE;
+}
+
+static void
+run (const gchar *name,
+ gint nparams,
+ const GimpParam *param,
+ gint *nreturn_vals,
+ GimpParam **return_vals)
+{
+ static GimpParam values[2];
+ GimpRunMode run_mode;
+ GimpPDBStatusType status = GIMP_PDB_SUCCESS;
+ gint32 image_ID;
+ gint32 drawable_ID;
+ GimpExportReturn export = GIMP_EXPORT_CANCEL;
+ GError *error = NULL;
+
+ INIT_I18N ();
+ gegl_init (NULL, NULL);
+
+ run_mode = param[0].data.d_int32;
+
+ *nreturn_vals = 1;
+ *return_vals = values;
+
+ values[0].type = GIMP_PDB_STATUS;
+ values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR;
+
+ if (strcmp (name, LOAD_PROC) == 0)
+ {
+ image_ID = load_image (param[1].data.d_string, &error);
+
+ if (image_ID != -1)
+ {
+ *nreturn_vals = 2;
+ values[1].type = GIMP_PDB_IMAGE;
+ values[1].data.d_image = image_ID;
+ }
+ else
+ {
+ status = GIMP_PDB_EXECUTION_ERROR;
+ }
+ }
+ else if (strcmp (name, SAVE_PROC) == 0)
+ {
+ image_ID = param[1].data.d_int32;
+ drawable_ID = param[2].data.d_int32;
+
+ /* eventually export the image */
+ switch (run_mode)
+ {
+ case GIMP_RUN_INTERACTIVE:
+ case GIMP_RUN_WITH_LAST_VALS:
+ gimp_ui_init (PLUG_IN_BINARY, FALSE);
+
+ export = gimp_export_image (&image_ID, &drawable_ID, "PSP",
+ GIMP_EXPORT_CAN_HANDLE_RGB |
+ GIMP_EXPORT_CAN_HANDLE_GRAY |
+ GIMP_EXPORT_CAN_HANDLE_INDEXED |
+ GIMP_EXPORT_CAN_HANDLE_ALPHA |
+ GIMP_EXPORT_CAN_HANDLE_LAYERS);
+
+ if (export == GIMP_EXPORT_CANCEL)
+ {
+ values[0].data.d_status = GIMP_PDB_CANCEL;
+ return;
+ }
+ break;
+ default:
+ break;
+ }
+
+ switch (run_mode)
+ {
+ case GIMP_RUN_INTERACTIVE:
+ /* Possibly retrieve data */
+ gimp_get_data (SAVE_PROC, &psvals);
+
+ /* First acquire information with a dialog */
+ if (! save_dialog ())
+ status = GIMP_PDB_CANCEL;
+ break;
+
+ case GIMP_RUN_NONINTERACTIVE:
+ /* Make sure all the arguments are there! */
+ if (nparams != 6)
+ {
+ status = GIMP_PDB_CALLING_ERROR;
+ }
+ else
+ {
+ psvals.compression = (param[5].data.d_int32) ? TRUE : FALSE;
+
+ if (param[5].data.d_int32 < 0 ||
+ param[5].data.d_int32 > PSP_COMP_LZ77)
+ status = GIMP_PDB_CALLING_ERROR;
+ }
+
+ case GIMP_RUN_WITH_LAST_VALS:
+ gimp_get_data (SAVE_PROC, &psvals);
+ break;
+
+ default:
+ break;
+ }
+
+ if (status == GIMP_PDB_SUCCESS)
+ {
+ if (save_image (param[3].data.d_string, image_ID, drawable_ID,
+ &error))
+ {
+ gimp_set_data (SAVE_PROC, &psvals, sizeof (PSPSaveVals));
+ }
+ else
+ {
+ status = GIMP_PDB_EXECUTION_ERROR;
+ }
+ }
+
+ if (export == GIMP_EXPORT_EXPORT)
+ gimp_image_delete (image_ID);
+ }
+ else
+ {
+ status = GIMP_PDB_CALLING_ERROR;
+ }
+
+ if (status != GIMP_PDB_SUCCESS && error)
+ {
+ *nreturn_vals = 2;
+ values[1].type = GIMP_PDB_STRING;
+ values[1].data.d_string = error->message;
+ }
+
+ values[0].data.d_status = status;
+}