PIKApp/plug-ins/common/file-pcx.c

1413 lines
44 KiB
C

/*
* pcx.c PIKA plug-in for loading & exporting PCX files
*
* 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/>.
*/
/* This code is based in parts on code by Francisco Bustamante, but the
largest portion of the code has been rewritten and is now maintained
occasionally by Nick Lamb njl195@zepler.org.uk */
#include "config.h"
#include <errno.h>
#include <string.h>
#include <glib/gstdio.h>
#include <libpika/pika.h>
#include <libpika/pikaui.h>
#include "libpika/stdplugins-intl.h"
#define LOAD_PROC "file-pcx-load"
#define LOAD_PROC_DCX "file-dcx-load"
#define SAVE_PROC "file-pcx-save"
#define PLUG_IN_BINARY "file-pcx"
#define PLUG_IN_ROLE "pika-file-pcx"
typedef struct _Pcx Pcx;
typedef struct _PcxClass PcxClass;
struct _Pcx
{
PikaPlugIn parent_instance;
};
struct _PcxClass
{
PikaPlugInClass parent_class;
};
#define PCX_TYPE (pcx_get_type ())
#define PCX(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PCX_TYPE, Pcx))
GType pcx_get_type (void) G_GNUC_CONST;
static GList * pcx_query_procedures (PikaPlugIn *plug_in);
static PikaProcedure * pcx_create_procedure (PikaPlugIn *plug_in,
const gchar *name);
static PikaValueArray * pcx_load (PikaProcedure *procedure,
PikaRunMode run_mode,
GFile *file,
PikaMetadata *metadata,
PikaMetadataLoadFlags *flags,
PikaProcedureConfig *config,
gpointer run_data);
static PikaValueArray * dcx_load (PikaProcedure *procedure,
PikaRunMode run_mode,
GFile *file,
PikaMetadata *metadata,
PikaMetadataLoadFlags *flags,
PikaProcedureConfig *config,
gpointer run_data);
static PikaValueArray * pcx_save (PikaProcedure *procedure,
PikaRunMode run_mode,
PikaImage *image,
gint n_drawables,
PikaDrawable **drawables,
GFile *file,
PikaMetadata *metadata,
PikaProcedureConfig *config,
gpointer run_data);
static PikaImage * load_single (PikaProcedure *procedure,
GFile *file,
GObject *config,
gint run_mode,
GError **error);
static PikaImage * load_multi (PikaProcedure *procedure,
GFile *file,
GObject *config,
gint run_mode,
GError **error);
static PikaImage * load_image (PikaProcedure *procedure,
FILE *fd,
const gchar *filename,
GObject *config,
gint run_mode,
gint image_num,
GError **error);
static gboolean pcx_load_dialog (PikaProcedure *procedure,
GObject *config);
static void load_1 (FILE *fp,
gint width,
gint height,
guchar *buf,
guint16 bytes);
static void load_4 (FILE *fp,
gint width,
gint height,
guchar *buf,
guint16 bytes);
static void load_sub_8 (FILE *fp,
gint width,
gint height,
gint bpp,
gint plane,
guchar *buf,
guint16 bytes);
static void load_8 (FILE *fp,
gint width,
gint height,
guchar *buf,
guint16 bytes);
static void load_24 (FILE *fp,
gint width,
gint height,
guchar *buf,
guint16 bytes,
guint8 planes);
static void readline (FILE *fp,
guchar *buf,
gint bytes);
static gboolean save_image (GFile *file,
PikaImage *image,
PikaDrawable *drawable,
GError **error);
static void save_less_than_8 (FILE *fp,
gint width,
gint height,
const gint bpp,
const guchar *buf,
gboolean padding);
static void save_8 (FILE *fp,
gint width,
gint height,
const guchar *buf,
gboolean padding);
static void save_24 (FILE *fp,
gint width,
gint height,
const guchar *buf,
gboolean padding);
static void writeline (FILE *fp,
const guchar *buf,
gint bytes);
G_DEFINE_TYPE (Pcx, pcx, PIKA_TYPE_PLUG_IN)
PIKA_MAIN (PCX_TYPE)
DEFINE_STD_SET_I18N
static void
pcx_class_init (PcxClass *klass)
{
PikaPlugInClass *plug_in_class = PIKA_PLUG_IN_CLASS (klass);
plug_in_class->query_procedures = pcx_query_procedures;
plug_in_class->create_procedure = pcx_create_procedure;
plug_in_class->set_i18n = STD_SET_I18N;
}
static void
pcx_init (Pcx *pcx)
{
}
static GList *
pcx_query_procedures (PikaPlugIn *plug_in)
{
GList *list = NULL;
list = g_list_append (list, g_strdup (LOAD_PROC));
list = g_list_append (list, g_strdup (LOAD_PROC_DCX));
list = g_list_append (list, g_strdup (SAVE_PROC));
return list;
}
static PikaProcedure *
pcx_create_procedure (PikaPlugIn *plug_in,
const gchar *name)
{
PikaProcedure *procedure = NULL;
if (! strcmp (name, LOAD_PROC))
{
procedure = pika_load_procedure_new (plug_in, name,
PIKA_PDB_PROC_TYPE_PLUGIN,
pcx_load, NULL, NULL);
pika_procedure_set_menu_label (procedure, _("ZSoft PCX image"));
pika_procedure_set_documentation (procedure,
_("Loads files in Zsoft PCX file format"),
"FIXME: write help for pcx_load",
name);
pika_procedure_set_attribution (procedure,
"Francisco Bustamante & Nick Lamb",
"Nick Lamb <njl195@zepler.org.uk>",
"January 1997");
PIKA_PROC_ARG_INT (procedure, "override-palette",
_("Palette Options"),
_("Use built-in palette (0) or override with "
"black/white (1)"),
0, 1, 0,
G_PARAM_READWRITE);
pika_file_procedure_set_mime_types (PIKA_FILE_PROCEDURE (procedure),
"image/x-pcx");
pika_file_procedure_set_extensions (PIKA_FILE_PROCEDURE (procedure),
"pcx,pcc");
pika_file_procedure_set_magics (PIKA_FILE_PROCEDURE (procedure),
"0&,byte,10,2&,byte,1,3&,byte,>0,3,byte,<9");
}
else if (! strcmp (name, LOAD_PROC_DCX))
{
procedure = pika_load_procedure_new (plug_in, name,
PIKA_PDB_PROC_TYPE_PLUGIN,
dcx_load, NULL, NULL);
pika_procedure_set_menu_label (procedure, _("ZSoft DCX image"));
pika_procedure_set_documentation (procedure,
_("Loads files in Zsoft DCX file format"),
"FIXME: write help for dcx_load",
name);
pika_procedure_set_attribution (procedure,
"Francisco Bustamante, Nick Lamb, Alex S.",
"Alex S.",
"2023");
PIKA_PROC_ARG_INT (procedure, "override-palette",
_("Palette Options"),
_("Use built-in palette (0) or override with "
"black/white (1)"),
0, 1, 0,
G_PARAM_READWRITE);
pika_file_procedure_set_mime_types (PIKA_FILE_PROCEDURE (procedure),
"image/x-dcx");
pika_file_procedure_set_extensions (PIKA_FILE_PROCEDURE (procedure),
"dcx");
pika_file_procedure_set_magics (PIKA_FILE_PROCEDURE (procedure),
"0,string,\xB1\x68\xDE\x3A");
}
else if (! strcmp (name, SAVE_PROC))
{
procedure = pika_save_procedure_new (plug_in, name,
PIKA_PDB_PROC_TYPE_PLUGIN,
FALSE, pcx_save, NULL, NULL);
pika_procedure_set_image_types (procedure, "INDEXED, RGB, GRAY");
pika_procedure_set_menu_label (procedure, _("ZSoft PCX image"));
pika_procedure_set_documentation (procedure,
"Exports files in ZSoft PCX file format",
"FIXME: write help for pcx_save",
name);
pika_procedure_set_attribution (procedure,
"Francisco Bustamante & Nick Lamb",
"Nick Lamb <njl195@zepler.org.uk>",
"January 1997");
pika_file_procedure_set_mime_types (PIKA_FILE_PROCEDURE (procedure),
"image/x-pcx");
pika_file_procedure_set_extensions (PIKA_FILE_PROCEDURE (procedure),
"pcx,pcc");
}
return procedure;
}
static PikaValueArray *
pcx_load (PikaProcedure *procedure,
PikaRunMode run_mode,
GFile *file,
PikaMetadata *metadata,
PikaMetadataLoadFlags *flags,
PikaProcedureConfig *config,
gpointer run_data)
{
PikaValueArray *return_vals;
PikaImage *image;
GError *error = NULL;
gegl_init (NULL, NULL);
image = load_single (procedure, file, G_OBJECT (config), run_mode, &error);
if (! image)
return pika_procedure_new_return_values (procedure,
PIKA_PDB_EXECUTION_ERROR,
error);
return_vals = pika_procedure_new_return_values (procedure,
PIKA_PDB_SUCCESS,
NULL);
PIKA_VALUES_SET_IMAGE (return_vals, 1, image);
return return_vals;
}
static PikaValueArray *
dcx_load (PikaProcedure *procedure,
PikaRunMode run_mode,
GFile *file,
PikaMetadata *metadata,
PikaMetadataLoadFlags *flags,
PikaProcedureConfig *config,
gpointer run_data)
{
PikaValueArray *return_vals;
PikaImage *image;
GError *error = NULL;
gegl_init (NULL, NULL);
image = load_multi (procedure, file, G_OBJECT (config), run_mode, &error);
if (! image)
return pika_procedure_new_return_values (procedure,
PIKA_PDB_EXECUTION_ERROR,
error);
return_vals = pika_procedure_new_return_values (procedure,
PIKA_PDB_SUCCESS,
NULL);
PIKA_VALUES_SET_IMAGE (return_vals, 1, image);
return return_vals;
}
static PikaValueArray *
pcx_save (PikaProcedure *procedure,
PikaRunMode run_mode,
PikaImage *image,
gint n_drawables,
PikaDrawable **drawables,
GFile *file,
PikaMetadata *metadata,
PikaProcedureConfig *config,
gpointer run_data)
{
PikaPDBStatusType status = PIKA_PDB_SUCCESS;
PikaExportReturn export = PIKA_EXPORT_CANCEL;
GError *error = NULL;
gegl_init (NULL, NULL);
switch (run_mode)
{
case PIKA_RUN_INTERACTIVE:
case PIKA_RUN_WITH_LAST_VALS:
pika_ui_init (PLUG_IN_BINARY);
export = pika_export_image (&image, &n_drawables, &drawables, "PCX",
PIKA_EXPORT_CAN_HANDLE_RGB |
PIKA_EXPORT_CAN_HANDLE_GRAY |
PIKA_EXPORT_CAN_HANDLE_INDEXED);
if (export == PIKA_EXPORT_CANCEL)
return pika_procedure_new_return_values (procedure,
PIKA_PDB_CANCEL,
NULL);
break;
default:
break;
}
if (n_drawables != 1)
{
g_set_error (&error, G_FILE_ERROR, 0,
_("PCX format does not support multiple layers."));
return pika_procedure_new_return_values (procedure,
PIKA_PDB_CALLING_ERROR,
error);
}
if (! save_image (file,
image, drawables[0],
&error))
{
status = PIKA_PDB_EXECUTION_ERROR;
}
if (export == PIKA_EXPORT_EXPORT)
{
pika_image_delete (image);
g_free (drawables);
}
return pika_procedure_new_return_values (procedure, status, error);
}
static struct
{
guint8 manufacturer;
guint8 version;
guint8 compression;
guint8 bpp;
guint16 x1, y1;
guint16 x2, y2;
guint16 hdpi;
guint16 vdpi;
guint8 colormap[48];
guint8 reserved;
guint8 planes;
guint16 bytesperline;
guint16 color;
guint8 filler[58];
} pcx_header;
static struct {
size_t size;
gpointer address;
} const pcx_header_buf_xlate[] = {
{ 1, &pcx_header.manufacturer },
{ 1, &pcx_header.version },
{ 1, &pcx_header.compression },
{ 1, &pcx_header.bpp },
{ 2, &pcx_header.x1 },
{ 2, &pcx_header.y1 },
{ 2, &pcx_header.x2 },
{ 2, &pcx_header.y2 },
{ 2, &pcx_header.hdpi },
{ 2, &pcx_header.vdpi },
{ 48, &pcx_header.colormap },
{ 1, &pcx_header.reserved },
{ 1, &pcx_header.planes },
{ 2, &pcx_header.bytesperline },
{ 2, &pcx_header.color },
{ 58, &pcx_header.filler },
{ 0, NULL }
};
static void
pcx_header_from_buffer (guint8 *buf)
{
gint i;
gint buf_offset = 0;
for (i = 0; pcx_header_buf_xlate[i].size != 0; i++)
{
memmove (pcx_header_buf_xlate[i].address, buf + buf_offset,
pcx_header_buf_xlate[i].size);
buf_offset += pcx_header_buf_xlate[i].size;
}
}
static void
pcx_header_to_buffer (guint8 *buf)
{
gint i;
gint buf_offset = 0;
for (i = 0; pcx_header_buf_xlate[i].size != 0; i++)
{
memmove (buf + buf_offset, pcx_header_buf_xlate[i].address,
pcx_header_buf_xlate[i].size);
buf_offset += pcx_header_buf_xlate[i].size;
}
}
static PikaImage *
load_single (PikaProcedure *procedure,
GFile *file,
GObject *config,
gint run_mode,
GError **error)
{
FILE *fd;
PikaImage *image;
pika_progress_init_printf (_("Opening '%s'"),
pika_file_get_utf8_name (file));
fd = g_fopen (g_file_peek_path (file), "rb");
if (! fd)
{
g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
_("Could not open '%s' for reading: %s"),
pika_file_get_utf8_name (file), g_strerror (errno));
return NULL;
}
image = load_image (procedure, fd, pika_file_get_utf8_name (file),
G_OBJECT (config), run_mode, 0, error);
fclose (fd);
if (! image)
{
g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
_("Could not load PCX image"));
return NULL;
}
pika_progress_update (1.0);
return image;
}
static PikaImage *
load_multi (PikaProcedure *procedure,
GFile *file,
GObject *config,
gint run_mode,
GError **error)
{
FILE *fd;
PikaImage *image = NULL;
PikaImage *temp_image = NULL;
gint offset;
gint next_id = 8;
gint valid_offset;
pika_progress_init_printf (_("Opening '%s'"),
pika_file_get_utf8_name (file));
fd = g_fopen (g_file_peek_path (file), "rb");
if (! fd)
{
g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
_("Could not open '%s' for reading: %s"),
pika_file_get_utf8_name (file), g_strerror (errno));
return NULL;
}
/* Skip header */
fread (&offset, 1, 4, fd);
/* Read the first offset */
fread (&offset, 1, 4, fd);
valid_offset = fseek (fd, offset, SEEK_SET);
if (valid_offset != 0)
{
fclose (fd);
g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
_("DCX image offset exceeds the file size"));
return NULL;
}
image = load_image (procedure, fd, pika_file_get_utf8_name (file),
G_OBJECT (config), run_mode, 0, error);
if (! image)
{
fclose (fd);
g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
_("Could not load DCX image"));
return NULL;
}
fseek (fd, next_id, SEEK_SET);
/* DCX can hold a maximum of 1023 images,
* plus a terminal value of 0 */
for (gint i = 1; i < 1023; i++)
{
PikaLayer **layers;
PikaLayer *new_layer;
gint n_layers;
fread (&offset, 1, 4, fd);
if (offset == 0)
break;
valid_offset = fseek (fd, offset, SEEK_SET);
if (valid_offset != 0)
{
fclose (fd);
g_message (_("%s: DCX image offset exceeds the file size: %s\n"),
G_STRFUNC, (*error)->message);
g_clear_error (error);
return image;
}
temp_image = load_image (procedure, fd, pika_file_get_utf8_name (file),
G_OBJECT (config), run_mode, i, error);
if (temp_image)
{
layers = pika_image_get_layers (temp_image, &n_layers);
new_layer = pika_layer_new_from_drawable (PIKA_DRAWABLE (layers[0]),
image);
pika_item_set_name (PIKA_ITEM (new_layer),
g_file_get_basename (file));
if (! pika_image_insert_layer (image, new_layer, NULL, 0))
g_message (_("Mixed-mode DCX image not loaded"));
g_free (layers);
}
else
{
fclose (fd);
g_message (_("%s: Could not load all DCX images: %s\n"),
G_STRFUNC, (*error)->message);
g_clear_error (error);
return image;
}
next_id += 4;
fseek (fd, next_id, SEEK_SET);
}
if (image)
fclose (fd);
pika_progress_update (1.0);
return image;
}
static PikaImage *
load_image (PikaProcedure *procedure,
FILE *fd,
const gchar *filename,
GObject *config,
gint run_mode,
gint image_num,
GError **error)
{
GeglBuffer *buffer;
guint16 offset_x, offset_y, bytesperline;
gint32 width, height;
guint16 resolution_x, resolution_y;
PikaImage *image;
PikaLayer *layer;
guchar *dest, cmap[768];
guint8 header_buf[128];
gboolean override_palette = FALSE;
if (fread (header_buf, 128, 1, fd) == 0)
{
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
_("Could not read header from '%s'"),
filename);
fclose (fd);
return NULL;
}
pcx_header_from_buffer (header_buf);
if (pcx_header.manufacturer != 10)
{
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
_("'%s' is not a PCX file"),
filename);
fclose (fd);
return NULL;
}
offset_x = GUINT16_FROM_LE (pcx_header.x1);
offset_y = GUINT16_FROM_LE (pcx_header.y1);
width = GUINT16_FROM_LE (pcx_header.x2) - offset_x + 1;
height = GUINT16_FROM_LE (pcx_header.y2) - offset_y + 1;
bytesperline = GUINT16_FROM_LE (pcx_header.bytesperline);
resolution_x = GUINT16_FROM_LE (pcx_header.hdpi);
resolution_y = GUINT16_FROM_LE (pcx_header.vdpi);
if ((width <= 0) || (width > PIKA_MAX_IMAGE_SIZE))
{
g_message (_("Unsupported or invalid image width: %d"), width);
fclose (fd);
return NULL;
}
if ((height <= 0) || (height > PIKA_MAX_IMAGE_SIZE))
{
g_message (_("Unsupported or invalid image height: %d"), height);
fclose (fd);
return NULL;
}
if ((bytesperline + 1) < ((width * pcx_header.bpp + 7) / 8))
{
g_message (_("Invalid number of bytes per line in PCX header"));
fclose (fd);
return NULL;
}
if ((resolution_x < 1) || (resolution_x > PIKA_MAX_RESOLUTION) ||
(resolution_y < 1) || (resolution_y > PIKA_MAX_RESOLUTION))
{
g_message (_("Resolution out of bounds in XCX header, using 72x72"));
resolution_x = 72;
resolution_y = 72;
}
/* Shield against potential buffer overflows in load_*() functions. */
if (G_MAXSIZE / width / height < 3)
{
g_message (_("Image dimensions too large: width %d x height %d"), width, height);
fclose (fd);
return NULL;
}
if (pcx_header.planes == 3 && pcx_header.bpp == 8)
{
image = pika_image_new (width, height, PIKA_RGB);
layer = pika_layer_new (image, _("Background"), width, height,
PIKA_RGB_IMAGE,
100,
pika_image_get_default_new_layer_mode (image));
}
else if (pcx_header.planes == 4 && pcx_header.bpp == 8)
{
image = pika_image_new (width, height, PIKA_RGB);
layer = pika_layer_new (image, _("Background"), width, height,
PIKA_RGBA_IMAGE,
100,
pika_image_get_default_new_layer_mode (image));
}
else
{
image = pika_image_new (width, height, PIKA_INDEXED);
layer = pika_layer_new (image, _("Background"), width, height,
PIKA_INDEXED_IMAGE,
100,
pika_image_get_default_new_layer_mode (image));
}
pika_image_set_resolution (image, resolution_x, resolution_y);
pika_image_insert_layer (image, layer, NULL, 0);
pika_layer_set_offsets (layer, offset_x, offset_y);
buffer = pika_drawable_get_buffer (PIKA_DRAWABLE (layer));
if (pcx_header.planes == 1 && pcx_header.bpp == 1)
{
const guint8 *colormap = pcx_header.colormap;
dest = g_new (guchar, ((gsize) width) * height);
load_1 (fd, width, height, dest, bytesperline);
if (run_mode == PIKA_RUN_INTERACTIVE)
{
g_object_get (config,
"override-palette", &override_palette,
NULL);
/* Only show dialogue once for DCX import */
if (image_num == 0 && pcx_load_dialog (procedure, config))
{
g_object_get (config,
"override-palette", &override_palette,
NULL);
}
}
/* Monochrome does not mean necessarily B&W. Therefore we still
* want to check the header palette, even for just 2 colors.
* Hopefully the header palette will always be filled with
* meaningful colors and the creator software did not just assume
* B&W by being monochrome.
* Until now test samples showed that even when B&W the header
* palette was correctly filled with these 2 colors and we didn't
* find counter-examples.
* See bug 159947, comment 21 and 23.
*/
/* ... Actually, there *are* files out there with a zeroed 1-bit palette,
* which are supposed to be displayed as B&W (see issue #2997.) These
* files *might* be in the wrong (who knows...) but the fact is that
* other software, including older versions of PIKA, do display them
* "correctly", so let's follow suit: if the two palette colors are
* equal (or if the user chooses the option in the load dialog),
* use a B&W palette instead.
*/
if (! memcmp (colormap, colormap + 3, 3) || override_palette)
{
static const guint8 bw_colormap[6] = { 0, 0, 0,
255, 255, 255};
colormap = bw_colormap;
}
pika_image_set_colormap (image, colormap, 2);
}
else if (pcx_header.bpp == 1 && pcx_header.planes == 2)
{
dest = g_new (guchar, ((gsize) width) * height);
load_sub_8 (fd, width, height, 1, 2, dest, bytesperline);
pika_image_set_colormap (image, pcx_header.colormap, 4);
}
else if (pcx_header.bpp == 2 && pcx_header.planes == 1)
{
dest = g_new (guchar, ((gsize) width) * height);
load_sub_8 (fd, width, height, 2, 1, dest, bytesperline);
pika_image_set_colormap (image, pcx_header.colormap, 4);
}
else if (pcx_header.bpp == 1 && pcx_header.planes == 3)
{
dest = g_new (guchar, ((gsize) width) * height);
load_sub_8 (fd, width, height, 1, 3, dest, bytesperline);
pika_image_set_colormap (image, pcx_header.colormap, 8);
}
else if (pcx_header.bpp == 1 && pcx_header.planes == 4)
{
dest = g_new (guchar, ((gsize) width) * height);
load_4 (fd, width, height, dest, bytesperline);
pika_image_set_colormap (image, pcx_header.colormap, 16);
}
else if (pcx_header.bpp == 4 && pcx_header.planes == 1)
{
dest = g_new (guchar, ((gsize) width) * height);
load_sub_8 (fd, width, height, 4, 1, dest, bytesperline);
pika_image_set_colormap (image, pcx_header.colormap, 16);
}
else if (pcx_header.bpp == 8 && pcx_header.planes == 1)
{
dest = g_new (guchar, ((gsize) width) * height);
load_8 (fd, width, height, dest, bytesperline);
fseek (fd, -768L, SEEK_END);
fread (cmap, 768, 1, fd);
pika_image_set_colormap (image, cmap, 256);
}
else if (pcx_header.bpp == 8 && (pcx_header.planes == 3 || pcx_header.planes == 4))
{
dest = g_new (guchar, ((gsize) width) * height * pcx_header.planes);
load_24 (fd, width, height, dest, bytesperline, pcx_header.planes);
}
else
{
g_message (_("Unusual PCX flavour, giving up"));
fclose (fd);
return NULL;
}
gegl_buffer_set (buffer, GEGL_RECTANGLE (0, 0, width, height), 0,
NULL, dest, GEGL_AUTO_ROWSTRIDE);
g_free (dest);
g_object_unref (buffer);
return image;
}
static gboolean
pcx_load_dialog (PikaProcedure *procedure,
GObject *config)
{
GtkWidget *dialog;
GtkListStore *store;
gboolean run;
pika_ui_init (PLUG_IN_BINARY);
dialog = pika_procedure_dialog_new (procedure,
PIKA_PROCEDURE_CONFIG (config),
_("Import from PCX"));
pika_dialog_set_alternative_button_order (GTK_DIALOG (dialog),
GTK_RESPONSE_OK,
GTK_RESPONSE_CANCEL,
-1);
pika_window_set_transient (GTK_WINDOW (dialog));
store = pika_int_store_new (_("Use PCX image's built-in palette"), 0,
_("Use black and white palette"), 1,
NULL);
pika_procedure_dialog_get_int_radio (PIKA_PROCEDURE_DIALOG (dialog),
"override-palette", PIKA_INT_STORE (store));
pika_procedure_dialog_fill (PIKA_PROCEDURE_DIALOG (dialog),
NULL);
gtk_widget_show (dialog);
run = pika_procedure_dialog_run (PIKA_PROCEDURE_DIALOG (dialog));
gtk_widget_destroy (dialog);
return run;
}
static void
load_8 (FILE *fp,
gint width,
gint height,
guchar *buf,
guint16 bytes)
{
gint row;
guchar *line = g_new (guchar, bytes);
for (row = 0; row < height; buf += width, ++row)
{
readline (fp, line, bytes);
memcpy (buf, line, width);
pika_progress_update ((double) row / (double) height);
}
g_free (line);
}
static void
load_24 (FILE *fp,
gint width,
gint height,
guchar *buf,
guint16 bytes,
guint8 planes)
{
gint x, y, c;
guchar *line = g_new (guchar, bytes);
for (y = 0; y < height; buf += width * planes, ++y)
{
for (c = 0; c < planes; ++c)
{
readline (fp, line, bytes);
for (x = 0; x < width; ++x)
{
buf[x * planes + c] = line[x];
}
}
pika_progress_update ((double) y / (double) height);
}
g_free (line);
}
static void
load_1 (FILE *fp,
gint width,
gint height,
guchar *buf,
guint16 bytes)
{
gint x, y;
guchar *line = g_new (guchar, bytes);
for (y = 0; y < height; buf += width, ++y)
{
readline (fp, line, bytes);
for (x = 0; x < width; ++x)
{
if (line[x / 8] & (128 >> (x % 8)))
buf[x] = 1;
else
buf[x] = 0;
}
pika_progress_update ((double) y / (double) height);
}
g_free (line);
}
static void
load_4 (FILE *fp,
gint width,
gint height,
guchar *buf,
guint16 bytes)
{
gint x, y, c;
guchar *line = g_new (guchar, bytes);
for (y = 0; y < height; buf += width, ++y)
{
for (x = 0; x < width; ++x)
buf[x] = 0;
for (c = 0; c < 4; ++c)
{
readline(fp, line, bytes);
for (x = 0; x < width; ++x)
{
if (line[x / 8] & (128 >> (x % 8)))
buf[x] += (1 << c);
}
}
pika_progress_update ((double) y / (double) height);
}
g_free (line);
}
static void
load_sub_8 (FILE *fp,
gint width,
gint height,
gint bpp,
gint plane,
guchar *buf,
guint16 bytes)
{
gint x, y, c, b;
guchar *line = g_new (guchar, bytes);
gint real_bpp = bpp - 1;
gint current_bit = 0;
for (y = 0; y < height; buf += width, ++y)
{
for (x = 0; x < width; ++x)
buf[x] = 0;
for (c = 0; c < plane; ++c)
{
readline (fp, line, bytes);
for (x = 0; x < width; ++x)
{
for (b = 0; b < bpp; b++)
{
current_bit = bpp * x + b;
if (line[current_bit / 8] & (128 >> (current_bit % 8)))
buf[x] += (1 << (real_bpp - b + c));
}
}
}
pika_progress_update ((double) y / (double) height);
}
g_free (line);
}
static void
readline (FILE *fp,
guchar *buf,
gint bytes)
{
static guchar count = 0, value = 0;
if (pcx_header.compression)
{
while (bytes--)
{
if (count == 0)
{
value = fgetc (fp);
if (value < 0xc0)
{
count = 1;
}
else
{
count = value - 0xc0;
value = fgetc (fp);
}
}
count--;
*(buf++) = value;
}
}
else
{
fread (buf, bytes, 1, fp);
}
}
static gboolean
save_image (GFile *file,
PikaImage *image,
PikaDrawable *drawable,
GError **error)
{
FILE *fp;
GeglBuffer *buffer;
const Babl *format;
PikaImageType drawable_type;
guchar *cmap= NULL;
guchar *pixels;
gint offset_x, offset_y;
guint width, height;
gdouble resolution_x, resolution_y;
gint colors, i;
guint8 header_buf[128];
gboolean padding = FALSE;
drawable_type = pika_drawable_type (drawable);
pika_drawable_get_offsets (drawable, &offset_x, &offset_y);
buffer = pika_drawable_get_buffer (drawable);
width = gegl_buffer_get_width (buffer);
height = gegl_buffer_get_height (buffer);
pika_progress_init_printf (_("Exporting '%s'"),
pika_file_get_utf8_name (file));
pcx_header.manufacturer = 0x0a;
pcx_header.version = 5;
pcx_header.compression = 1;
switch (drawable_type)
{
case PIKA_INDEXED_IMAGE:
cmap = pika_image_get_colormap (image, NULL, &colors);
if (colors > 16)
{
pcx_header.bpp = 8;
pcx_header.planes = 1;
pcx_header.bytesperline = width;
}
else if (colors > 2)
{
pcx_header.bpp = 4;
pcx_header.planes = 1;
pcx_header.bytesperline = (width + 1) / 2;
}
else
{
pcx_header.bpp = 1;
pcx_header.planes = 1;
pcx_header.bytesperline = (width + 7) / 8;
}
pcx_header.color = GUINT16_TO_LE (1);
format = NULL;
/* Some references explain that 2bpp/1plane and 4bpp/1plane files
* would use the palette at EOF (not the one from the header) if
* we are in version 5 of PCX. Other sources affirm that even in
* version 5, EOF palette must be used only when there are more
* than 16 colors. We go with this second assumption.
* See bug 159947, comment 21 and 23.
*/
if (colors <= 16)
{
for (i = 0; i < (colors * 3); i++)
{
pcx_header.colormap[i] = cmap[i];
}
}
break;
case PIKA_RGB_IMAGE:
pcx_header.bpp = 8;
pcx_header.planes = 3;
pcx_header.color = GUINT16_TO_LE (1);
pcx_header.bytesperline = width;
format = babl_format ("R'G'B' u8");
break;
case PIKA_GRAY_IMAGE:
pcx_header.bpp = 8;
pcx_header.planes = 1;
pcx_header.color = GUINT16_TO_LE (2);
pcx_header.bytesperline = width;
format = babl_format ("Y' u8");
break;
default:
g_message (_("Cannot export images with alpha channel."));
return FALSE;
}
/* Bytes per Line must be an even number, according to spec */
if (pcx_header.bytesperline % 2 != 0)
{
pcx_header.bytesperline++;
padding = TRUE;
}
pcx_header.bytesperline = GUINT16_TO_LE (pcx_header.bytesperline);
pixels = (guchar *) g_malloc (width * height * pcx_header.planes);
gegl_buffer_get (buffer, GEGL_RECTANGLE (0, 0, width, height), 1.0,
format, pixels,
GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
if ((offset_x < 0) || (offset_x > (1<<16)))
{
g_message (_("Invalid X offset: %d"), offset_x);
return FALSE;
}
if ((offset_y < 0) || (offset_y > (1<<16)))
{
g_message (_("Invalid Y offset: %d"), offset_y);
return FALSE;
}
if (offset_x + width - 1 > (1<<16))
{
g_message (_("Right border out of bounds (must be < %d): %d"), (1<<16),
offset_x + width - 1);
return FALSE;
}
if (offset_y + height - 1 > (1<<16))
{
g_message (_("Bottom border out of bounds (must be < %d): %d"), (1<<16),
offset_y + height - 1);
return FALSE;
}
fp = g_fopen (g_file_peek_path (file), "wb");
if (! fp)
{
g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
_("Could not open '%s' for writing: %s"),
pika_file_get_utf8_name (file), g_strerror (errno));
return FALSE;
}
pcx_header.x1 = GUINT16_TO_LE ((guint16)offset_x);
pcx_header.y1 = GUINT16_TO_LE ((guint16)offset_y);
pcx_header.x2 = GUINT16_TO_LE ((guint16)(offset_x + width - 1));
pcx_header.y2 = GUINT16_TO_LE ((guint16)(offset_y + height - 1));
pika_image_get_resolution (image, &resolution_x, &resolution_y);
pcx_header.hdpi = GUINT16_TO_LE (RINT (MAX (resolution_x, 1.0)));
pcx_header.vdpi = GUINT16_TO_LE (RINT (MAX (resolution_y, 1.0)));
pcx_header.reserved = 0;
pcx_header_to_buffer (header_buf);
fwrite (header_buf, 128, 1, fp);
switch (drawable_type)
{
case PIKA_INDEXED_IMAGE:
if (colors > 16)
{
save_8 (fp, width, height, pixels, padding);
fputc (0x0c, fp);
fwrite (cmap, colors, 3, fp);
for (i = colors; i < 256; i++)
{
fputc (0, fp);
fputc (0, fp);
fputc (0, fp);
}
}
else /* Covers 1 and 4 bpp */
{
save_less_than_8 (fp, width, height, pcx_header.bpp, pixels, padding);
}
break;
case PIKA_RGB_IMAGE:
save_24 (fp, width, height, pixels, padding);
break;
case PIKA_GRAY_IMAGE:
save_8 (fp, width, height, pixels, padding);
fputc (0x0c, fp);
for (i = 0; i < 256; i++)
{
fputc ((guchar) i, fp);
fputc ((guchar) i, fp);
fputc ((guchar) i, fp);
}
break;
default:
return FALSE;
}
g_object_unref (buffer);
g_free (pixels);
if (fclose (fp) != 0)
{
g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
_("Writing to file '%s' failed: %s"),
pika_file_get_utf8_name (file), g_strerror (errno));
return FALSE;
}
return TRUE;
}
static void
save_less_than_8 (FILE *fp,
gint width,
gint height,
const gint bpp,
const guchar *buf,
gboolean padding)
{
const gint bit_limit = (8 - bpp);
const gint buf_size = width * height;
const gint line_end = width - 1;
gint j = bit_limit;
gint count = 0;
guchar byte_to_write = 0x00;
guchar *line;
gint x;
line = (guchar *) g_malloc (((width + 7) / 8) * bpp);
for (x = 0; x < buf_size; x++)
{
byte_to_write |= (buf[x] << j);
j -= bpp;
if (j < 0 || (x % width == line_end))
{
line[count] = byte_to_write;
count++;
byte_to_write = 0x00;
j = bit_limit;
if ((x % width == line_end))
{
writeline (fp, line, count);
count = 0;
if (padding)
fputc ('\0', fp);
pika_progress_update ((double) x / (double) buf_size);
}
}
}
g_free (line);
}
static void
save_8 (FILE *fp,
gint width,
gint height,
const guchar *buf,
gboolean padding)
{
int row;
for (row = 0; row < height; ++row)
{
writeline (fp, buf, width);
buf += width;
if (padding)
fputc ('\0', fp);
pika_progress_update ((double) row / (double) height);
}
}
static void
save_24 (FILE *fp,
gint width,
gint height,
const guchar *buf,
gboolean padding)
{
int x, y, c;
guchar *line;
line = (guchar *) g_malloc (width);
for (y = 0; y < height; ++y)
{
for (c = 0; c < 3; ++c)
{
for (x = 0; x < width; ++x)
{
line[x] = buf[(3*x) + c];
}
writeline (fp, line, width);
if (padding)
fputc ('\0', fp);
}
buf += width * 3;
pika_progress_update ((double) y / (double) height);
}
g_free (line);
}
static void
writeline (FILE *fp,
const guchar *buf,
gint bytes)
{
const guchar *finish = buf + bytes;
guchar value;
guchar count;
while (buf < finish)
{
value = *(buf++);
count = 1;
while (buf < finish && count < 63 && *buf == value)
{
count++; buf++;
}
if (value < 0xc0 && count == 1)
{
fputc (value, fp);
}
else
{
fputc (0xc0 + count, fp);
fputc (value, fp);
}
}
}