Coding continues. Add some support for PSP format version 4.0.

This commit is contained in:
Tor Lillqvist 1999-08-20 23:52:19 +00:00
parent 58d866bcde
commit 49331fbfc0
1 changed files with 460 additions and 88 deletions

View File

@ -1,4 +1,4 @@
/* GIMP plug-in to load and save Paint Shop Pro files (.PSP)
/* GIMP plug-in to load and save Paint Shop Pro files (.PSP and .TUB)
*
* Copyright (C) 1999 Tor Lillqvist
*
@ -21,6 +21,9 @@
*
* Work in progress! Doesn't do anything at all useful yet.
*
* For a copy of the PSP file format documentation, surf to
* http://www.jasc.com.
*
*/
/* set to the level of debugging output you want, 0 for none */
@ -43,39 +46,46 @@
#include <gtk/gtk.h>
#include <zlib.h>
#include <libgimp/gimp.h>
#include <libgimp/stdplugins-intl.h>
#define gimp_message_printf(x) \
do { \
G_STMT_START { \
gchar *_t = g_strdup_printf x; \
gimp_message (_t); \
g_free (_t); \
} while (0)
} G_STMT_END
/* The following was cut and pasted from the PSP file format
* documentation version 3.0.
* (Minor stylistic changes done.)
* documentation version 3.0.(Minor stylistic changes done.)
*
* 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 designed to be downward compatible,
* however. The semantics of many of the additional fields and
* block types can be deduced by reverse engineering.
*/
/*
* 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.
* 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
@ -112,8 +122,7 @@
*
* 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.
*/
* shall have no obligation to support or maintain the Specification. */
/* Block identifiers.
*/
@ -167,7 +176,6 @@ typedef enum {
PSP_COMP_NONE = 0, /* No compression */
PSP_COMP_RLE, /* RLE compression */
PSP_COMP_LZ77 /* LZ77 compression */
} PSPCompression;
/* Picture tube placement mode.
@ -235,9 +243,35 @@ typedef gboolean PSP_BOOLEAN;
/* End of cut&paste from psp spec */
/* We store the various PSP data in own structures. Of course,
* we cannot read struct directly from the file because of
* struct alignment issues.
/* The following have been reverse engineered.
* If a new version of the spec becomes available,
* change to use the type and constant names from it.
*/
typedef enum {
PSP_BLEND_NORMAL = 0,
PSP_BLEND_DARKEN,
PSP_BLEND_LIGHTEN,
PSP_BLEND_HUE,
PSP_BLEND_SATURATION,
PSP_BLEND_COLOR,
PSP_BLEND_LUMINANCE,
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
} PSPLayerBlendModes;
/* End of reverse engineered types */
/* 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
{
@ -283,19 +317,22 @@ typedef struct
typedef struct
{
gint run; /* run */
gint run;
} PSPSaveInterface;
static PSPSaveVals psvals =
{
TRUE /* raw? or ascii */
PSP_COMP_LZ77
};
static PSPSaveInterface psint =
{
FALSE /* run */
FALSE /* run */
};
static guint16 major, minor;
MAIN()
static void
@ -344,7 +381,7 @@ query ()
nload_args, nload_return_vals,
load_args, load_return_vals);
gimp_install_procedure ("file_pso_save",
gimp_install_procedure ("file_psp_save",
"saves images in the Paint Shop Pro PSP file format",
"This filter loads and saves images in "
"Paint Shop Pro's native PSP format. "
@ -528,16 +565,20 @@ block_name (int id)
static int
read_block_header (FILE *f,
guint32 *initial_length,
guint32 *total_length)
guint32 *init_len,
guint32 *total_len)
{
guchar buf[4];
guint16 id;
long header_start;
guint32 len;
IFDBG(2) header_start = ftell (f);
if (fread (buf, 4, 1, f) < 1
|| fread (&id, 2, 1, f) < 1
|| fread (initial_length, 4, 1, f) < 1
|| fread (total_length, 4, 1, f) < 1)
|| fread (&len, 4, 1, f) < 1
|| (major < 4 && fread (total_len, 4, 1, f) < 1))
{
gimp_message ("PSP: Error reading block header");
fclose (f);
@ -545,10 +586,27 @@ read_block_header (FILE *f,
}
if (memcmp (buf, "~BK\0", 4) != 0)
{
gimp_message ("PSP: Invalid block header");
gimp_message_printf (("PSP: Invalid block header at %d", header_start));
fclose (f);
return -1;
}
IFDBG(2) gimp_message_printf (("PSP: %s at %d", block_name (id),
header_start));
if (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.
*/
*total_len = *init_len = GUINT32_FROM_LE (len);
}
return id;
}
@ -575,6 +633,9 @@ read_general_image_attribute_block (FILE *f,
return -1;
}
if (major >= 4)
fseek (f, 4, SEEK_CUR);
if (fread (&ia->width, 4, 1, f) < 1
|| fread (&ia->height, 4, 1, f) < 1
|| fread (res, 8, 1, f) < 1
@ -626,10 +687,21 @@ read_general_image_attribute_block (FILE *f,
return 0;
}
static int
try_fseek (FILE *f, long pos, int whence)
{
if (fseek (f, pos, whence) < 0)
{
gimp_message ("PSP: Seek error");
fclose (f);
return -1;
}
return 0;
}
static int
read_creator_block (FILE *f,
gint image_ID,
guint init_len,
guint total_len,
PSPimage *ia)
{
@ -719,10 +791,8 @@ read_creator_block (FILE *f,
}
break;
default:
if (fseek (f, length, SEEK_CUR) < 0)
if (try_fseek (f, length, SEEK_CUR) < 0)
{
gimp_message ("PSP: Seek error");
fclose (f);
gimp_image_delete (image_ID);
return -1;
}
@ -761,39 +831,337 @@ read_creator_block (FILE *f,
g_string_free (comment, FALSE);
}
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 GLayerMode
gimp_layer_mode_from_psp_blend_mode (PSPLayerBlendModes mode)
{
switch (mode)
{
case PSP_BLEND_NORMAL:
return NORMAL_MODE;
case PSP_BLEND_DARKEN:
return DARKEN_ONLY_MODE;
case PSP_BLEND_HUE:
return HUE_MODE;
case PSP_BLEND_SATURATION:
return SATURATION_MODE;
case PSP_BLEND_COLOR:
return COLOR_MODE;
case PSP_BLEND_LUMINANCE:
return VALUE_MODE; /* ??? */
case PSP_BLEND_MULTIPLY:
return MULTIPLY_MODE;
case PSP_BLEND_SCREEN:
return SCREEN_MODE;
case PSP_BLEND_DISSOLVE:
return DISSOLVE_MODE;
case PSP_BLEND_OVERLAY:
return OVERLAY_MODE;
case PSP_BLEND_HARD_LIGHT:
case PSP_BLEND_SOFT_LIGHT:
return -1;
case PSP_BLEND_DIFFERENCE:
return DIFFERENCE_MODE;
case PSP_BLEND_DODGE:
case PSP_BLEND_BURN:
case PSP_BLEND_EXCLUSION:
return -1; /* ??? */
}
return -1;
}
static char *
blend_mode_name (PSPLayerBlendModes mode)
{
static char *blend_mode_names[] =
{
"NORMAL",
"DARKEN",
"LIGHTEN",
"HUE",
"SATURATION",
"COLOR",
"LUMINANCE",
"MULTIPLY",
"SCREEN",
"DISSOLVE",
"OVERLAY",
"HARD_LIGHT",
"SOFT_LIGHT",
"DIFFERENCE",
"DODGE",
"BURN",
"EXCLUSION"
};
static gchar *err_name = NULL;
if (mode >= 0 && mode <= PSP_BLEND_EXCLUSION)
return blend_mode_names[mode];
g_free (err_name);
err_name = g_strdup_printf ("%d", mode);
return err_name;
}
static int
read_layer_block (FILE *f,
gint image_ID,
guint init_len,
guint total_len,
PSPimage *ia)
{
long data_start;
long block_start, sub_block_start, channel_start;
int sub_id;
guint32 sub_init_len, sub_total_len;
guchar name[256];
guchar *name;
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 width_bumped = FALSE, height_bumped = FALSE;
guint16 bitmap_count, channel_count;
guint32 layer_ID;
GLayerMode layer_mode;
guint32 channel_init_len, channel_total_len;
guint32 compressed_len, uncompressed_len;
guint16 bitmap_type, channel_type;
data_start = ftell (f);
block_start = ftell (f);
while (ftell (f) < data_start + total_len)
while (ftell (f) < block_start + total_len)
{
/* Read the layer sub-block header */
sub_id = read_block_header (f, &sub_init_len, &sub_total_len);
if (sub_id == -1)
{
gimp_image_delete (image_ID);
return -1;
}
else if (sub_id != PSP_LAYER_BLOCK)
if (sub_id != PSP_LAYER_BLOCK)
{
gimp_message_printf (("PSP: Invalid layer sub-block %s, "
"should be LAYER",
block_name (sub_id)));
fclose (f);
gimp_image_delete (image_ID);
return -1;
}
fseek (f, data_start + total_len, SEEK_SET);
sub_block_start = ftell (f);
/* Read layer information chunk */
if (major >= 4)
{
if (fseek (f, 4, SEEK_CUR) < 0
|| fread (&namelen, 2, 1, f) < 1
|| ((namelen = GUINT16_FROM_LE (namelen)) && FALSE)
|| (name = g_malloc (namelen + 1)) == NULL
|| 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
|| fseek (f, 43, SEEK_CUR) < 0
|| fread (&bitmap_count, 1, 1, f) < 1
|| fread (&channel_count, 1, 1, f) < 1)
{
gimp_message ("PSP: Error reading layer information chunk");
fclose (f);
gimp_image_delete (image_ID);
return -1;
}
name[namelen] = 0;
type = PSP_LAYER_NORMAL; /* ??? */
}
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, 1, 1, f) < 1
|| fread (&channel_count, 1, 1, f) < 1)
{
gimp_message ("PSP: Error reading layer information chunk");
fclose (f);
gimp_image_delete (image_ID);
return -1;
}
}
if (type == PSP_LAYER_FLOATING_SELECTION)
gimp_message ("PSP: Floating selection restored as normal layer");
swab_rect (image_rect);
swab_rect (saved_image_rect);
swab_rect (mask_rect);
swab_rect (saved_mask_rect);
layer_mode = gimp_layer_mode_from_psp_blend_mode (blend_mode);
if ((int) layer_mode == -1)
{
gimp_message_printf (("PSP: Unsupported PSP layer blend mode %s "
"for layer %s, setting layer invisible",
blend_mode_name (blend_mode), name));
layer_mode = NORMAL_MODE;
visibility = FALSE;
}
IFDBG(2) gimp_message_printf
(("PSP: layer: %s %dx%d (%dx%d) opacity %d blend_mode %d", name,
image_rect[2] - image_rect[0],
image_rect[3] - image_rect[1],
saved_image_rect[2] - saved_image_rect[0],
saved_image_rect[3] - saved_image_rect[1],
opacity, blend_mode));
if (saved_image_rect[0] == saved_image_rect[2])
{
saved_image_rect[2]++;
width_bumped = TRUE;
}
if (saved_image_rect[1] == saved_image_rect[3])
{
saved_image_rect[3]++;
height_bumped = TRUE;
}
layer_ID = gimp_layer_new (image_ID, name,
saved_image_rect[2] - saved_image_rect[0],
saved_image_rect[3] - saved_image_rect[1],
/* XXX */
ia->greyscale ? GRAYA_IMAGE : RGBA_IMAGE,
opacity / 255.0,
/* XXX */
layer_mode);
if (layer_ID == -1)
{
gimp_message ("PSP: Error creating layer");
fclose (f);
gimp_image_delete (image_ID);
return -1;
}
if (saved_image_rect[0] != 0 || saved_image_rect[1] != 0)
gimp_layer_set_offsets (layer_ID,
saved_image_rect[0], saved_image_rect[1]);
if (!visibility)
gimp_layer_set_visible (layer_ID, FALSE);
gimp_layer_set_preserve_transparency (layer_ID, transparency_protected);
gimp_image_add_layer (image_ID, layer_ID, -1);
if (try_fseek (f, sub_block_start + sub_init_len, SEEK_SET) < 0)
{
gimp_image_delete (image_ID);
return -1;
}
/* 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);
if (sub_id == -1)
{
gimp_image_delete (image_ID);
return -1;
}
if (sub_id != PSP_CHANNEL_BLOCK)
{
gimp_message_printf (("PSP: Invalid layer sub-block %s, "
"should be CHANNEL",
block_name (sub_id)));
fclose (f);
gimp_image_delete (image_ID);
return -1;
}
channel_start = ftell (f);
if (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)
{
gimp_message ("PSP: Error reading channel information chunk");
fclose (f);
gimp_image_delete (image_ID);
return -1;
}
if (GUINT16_FROM_LE (bitmap_type) > PSP_DIB_USER_MASK)
{
gimp_message ("PSP: Invalid bitmap type "
"in channel information chunk");
fclose (f);
gimp_image_delete (image_ID);
return -1;
}
if (GUINT16_FROM_LE (channel_type) > PSP_CHANNEL_BLUE)
{
gimp_message ("PSP: Invalid channel type "
"in channel information chunk");
fclose (f);
gimp_image_delete (image_ID);
return -1;
}
if (try_fseek (f, channel_start + channel_init_len, SEEK_SET) < 0)
{
gimp_image_delete (image_ID);
return -1;
}
/* Read channel data */
if (try_fseek (f, channel_start + channel_total_len, SEEK_SET) < 0)
{
gimp_image_delete (image_ID);
return -1;
}
}
}
if (try_fseek (f, block_start + total_len, SEEK_SET) < 0)
{
gimp_image_delete (image_ID);
return -1;
}
return layer_ID;
}
static gint32
@ -802,9 +1170,9 @@ load_image (char *filename)
FILE *f;
struct stat st;
char buf[32];
guint16 major, minor;
PSPimage ia;
guint32 initial_length, total_length;
guint32 block_init_len, block_total_len;
long block_start;
PSPBlockID id;
gint block_number;
@ -835,33 +1203,46 @@ load_image (char *filename)
fclose (f);
return -1;
}
if (GUINT16_FROM_LE (major) != 3
|| GUINT16_FROM_LE (minor) != 0)
major = GUINT16_FROM_LE (major);
minor = GUINT16_FROM_LE (minor);
/* I only have the documentation for file format version 3.0,
* but PSP 6 writes version 4.0. Let's hope it's backwards compatible.
* Earlier versions probably don't have all the fields I expect
* so don't accept those.
*/
if (major < 3)
{
gimp_message_printf (("PSP: Unknown file format version %d.%d, only knows 3.0",
GUINT16_FROM_LE (major), GUINT16_FROM_LE (minor)));
gimp_message_printf (("PSP: Unsupported PSP file format version "
"%d.%d, only knows 3.0 (and later?)",
major, minor));
fclose (f);
return -1;
}
else if (major == 3)
; /* OK */
else if (major == 4 && minor == 0)
gimp_message ("PSP: Warning: PSP file format version "
"4.0. Support for this format version "
"is based on reverse engineering, "
"as no documentation has been made available");
else
{
gimp_message_printf (("PSP: Unsupported PSP file format version %d.%d",
major, minor));
fclose (f);
return -1;
}
/* Read all the blocks */
block_number = 0;
IFDBG(3) gimp_message_printf (("PSP: size = %d", st.st_size));
while (ftell (f) != st.st_size
&& (id = read_block_header (f, &initial_length,
&total_length)) != -1)
&& (id = read_block_header (f, &block_init_len,
&block_total_len)) != -1)
{
long data_start;
long handled_data_end;
data_start = ftell (f);
IFDBG(2) gimp_message_printf
(("PSP: %s, data_start = %d",
block_name (id),
data_start));
block_start = ftell (f);
if (id == PSP_IMAGE_BLOCK)
{
@ -871,8 +1252,8 @@ load_image (char *filename)
fclose (f);
return -1;
}
if (read_general_image_attribute_block (f, initial_length,
total_length, &ia) == -1)
if (read_general_image_attribute_block (f, block_init_len,
block_total_len, &ia) == -1)
return -1;
IFDBG(2) gimp_message_printf (("PSP: resolution: %d dpi "
@ -901,15 +1282,13 @@ load_image (char *filename)
switch (id)
{
case PSP_CREATOR_BLOCK:
if (read_creator_block (f, image_ID, initial_length,
total_length, &ia) == -1)
if (read_creator_block (f, image_ID, block_total_len, &ia) == -1)
return -1;
break;
case PSP_COLOR_BLOCK:
break; /* Not yet implemented */
case PSP_LAYER_START_BLOCK:
if (read_layer_block (f, image_ID, initial_length,
total_length, &ia) == -1)
if (read_layer_block (f, image_ID, block_total_len, &ia) == -1)
return -1;
break;
case PSP_SELECTION_BLOCK:
@ -937,20 +1316,13 @@ load_image (char *filename)
}
}
if (data_start + total_length >= st.st_size)
if (block_start + block_total_len >= st.st_size)
break;
handled_data_end = ftell (f);
if (handled_data_end != data_start + total_length)
if (try_fseek (f, block_start + block_total_len, SEEK_SET) < 0)
{
IFDBG(3) gimp_message_printf (("PSP: Seeking to %d + %d",
data_start, total_length));
if (fseek (f, data_start + total_length, SEEK_SET) < 0)
{
gimp_message ("PSP: Seek failed");
fclose (f);
gimp_image_delete (image_ID);
return -1;
}
gimp_image_delete (image_ID);
return -1;
}
block_number++;
}