2
* pcx.c GIMP plug-in for loading & saving PCX files
4
* This program is free software; you can redistribute it and/or modify
5
* it under the terms of the GNU General Public License as published by
6
* the Free Software Foundation; either version 2 of the License, or
7
* (at your option) any later version.
9
* This program is distributed in the hope that it will be useful,
10
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
* GNU General Public License for more details.
14
* You should have received a copy of the GNU General Public License
15
* along with this program; if not, write to the Free Software
16
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19
/* This code is based in parts on code by Francisco Bustamante, but the
20
largest portion of the code has been rewritten and is now maintained
21
occasionally by Nick Lamb njl195@zepler.org.uk */
28
#include <glib/gstdio.h>
30
#include <libgimp/gimp.h>
31
#include <libgimp/gimpui.h>
33
#include "libgimp/stdplugins-intl.h"
36
#define LOAD_PROC "file-pcx-load"
37
#define SAVE_PROC "file-pcx-save"
38
#define PLUG_IN_BINARY "file-pcx"
40
/* Declare local functions. */
42
static void query (void);
43
static void run (const gchar *name,
45
const GimpParam *param,
47
GimpParam **return_vals);
49
static gint32 load_image (const gchar *filename,
52
static void load_1 (FILE *fp,
57
static void load_4 (FILE *fp,
62
static void load_8 (FILE *fp,
67
static void load_24 (FILE *fp,
72
static void readline (FILE *fp,
76
static gint save_image (const gchar *filename,
81
static void save_8 (FILE *fp,
85
static void save_24 (FILE *fp,
89
static void writeline (FILE *fp,
94
const GimpPlugInInfo PLUG_IN_INFO =
98
query, /* query_proc */
107
static const GimpParamDef load_args[] =
109
{ GIMP_PDB_INT32, "run-mode", "Interactive, non-interactive" },
110
{ GIMP_PDB_STRING, "filename", "The name of the file to load" },
111
{ GIMP_PDB_STRING, "raw-filename", "The name entered" }
113
static const GimpParamDef load_return_vals[] =
115
{ GIMP_PDB_IMAGE, "image", "Output image" }
118
static const GimpParamDef save_args[] =
120
{ GIMP_PDB_INT32, "run-mode", "Interactive, non-interactive" },
121
{ GIMP_PDB_IMAGE, "image", "Input image" },
122
{ GIMP_PDB_DRAWABLE, "drawable", "Drawable to save" },
123
{ GIMP_PDB_STRING, "filename", "The name of the file to save the image in" },
124
{ GIMP_PDB_STRING, "raw-filename", "The name entered" }
127
gimp_install_procedure (LOAD_PROC,
128
"Loads files in Zsoft PCX file format",
129
"FIXME: write help for pcx_load",
130
"Francisco Bustamante & Nick Lamb",
131
"Nick Lamb <njl195@zepler.org.uk>",
133
N_("ZSoft PCX image"),
136
G_N_ELEMENTS (load_args),
137
G_N_ELEMENTS (load_return_vals),
138
load_args, load_return_vals);
140
gimp_register_file_handler_mime (LOAD_PROC, "image/x-pcx");
141
gimp_register_magic_load_handler (LOAD_PROC,
144
"0&,byte,10,2&,byte,1,3&,byte,>0,3,byte,<9");
146
gimp_install_procedure (SAVE_PROC,
147
"Saves files in ZSoft PCX file format",
148
"FIXME: write help for pcx_save",
149
"Francisco Bustamante & Nick Lamb",
150
"Nick Lamb <njl195@zepler.org.uk>",
152
N_("ZSoft PCX image"),
153
"INDEXED, RGB, GRAY",
155
G_N_ELEMENTS (save_args), 0,
158
gimp_register_file_handler_mime (SAVE_PROC, "image/x-pcx");
159
gimp_register_save_handler (SAVE_PROC, "pcx,pcc", "");
163
run (const gchar *name,
165
const GimpParam *param,
167
GimpParam **return_vals)
169
static GimpParam values[2];
170
GimpRunMode run_mode;
171
GimpPDBStatusType status = GIMP_PDB_SUCCESS;
174
GimpExportReturn export = GIMP_EXPORT_CANCEL;
175
GError *error = NULL;
177
run_mode = param[0].data.d_int32;
182
*return_vals = values;
184
values[0].type = GIMP_PDB_STATUS;
185
values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR;
187
if (strcmp (name, LOAD_PROC) == 0)
189
image_ID = load_image (param[1].data.d_string, &error);
194
values[1].type = GIMP_PDB_IMAGE;
195
values[1].data.d_image = image_ID;
199
status = GIMP_PDB_EXECUTION_ERROR;
202
else if (strcmp (name, SAVE_PROC) == 0)
204
image_ID = param[1].data.d_int32;
205
drawable_ID = param[2].data.d_int32;
207
/* eventually export the image */
210
case GIMP_RUN_INTERACTIVE:
211
case GIMP_RUN_WITH_LAST_VALS:
212
gimp_ui_init (PLUG_IN_BINARY, FALSE);
214
export = gimp_export_image (&image_ID, &drawable_ID, "PCX",
215
(GIMP_EXPORT_CAN_HANDLE_RGB |
216
GIMP_EXPORT_CAN_HANDLE_GRAY |
217
GIMP_EXPORT_CAN_HANDLE_INDEXED));
218
if (export == GIMP_EXPORT_CANCEL)
220
values[0].data.d_status = GIMP_PDB_CANCEL;
230
case GIMP_RUN_INTERACTIVE:
233
case GIMP_RUN_NONINTERACTIVE:
235
status = GIMP_PDB_CALLING_ERROR;
238
case GIMP_RUN_WITH_LAST_VALS:
245
if (status == GIMP_PDB_SUCCESS)
247
if (! save_image (param[3].data.d_string, image_ID, drawable_ID,
250
status = GIMP_PDB_EXECUTION_ERROR;
254
if (export == GIMP_EXPORT_EXPORT)
255
gimp_image_delete (image_ID);
259
status = GIMP_PDB_CALLING_ERROR;
262
if (status != GIMP_PDB_SUCCESS && error)
265
values[1].type = GIMP_PDB_STRING;
266
values[1].data.d_string = error->message;
269
values[0].data.d_status = status;
272
static guchar mono[6]= { 0, 0, 0, 255, 255, 255 };
287
guint16 bytesperline;
295
} pcx_header_buf_xlate[] = {
296
{ 1, &pcx_header.manufacturer },
297
{ 1, &pcx_header.version },
298
{ 1, &pcx_header.compression },
299
{ 1, &pcx_header.bpp },
300
{ 2, &pcx_header.x1 },
301
{ 2, &pcx_header.y1 },
302
{ 2, &pcx_header.x2 },
303
{ 2, &pcx_header.y2 },
304
{ 2, &pcx_header.hdpi },
305
{ 2, &pcx_header.vdpi },
306
{ 48, &pcx_header.colormap },
307
{ 1, &pcx_header.reserved },
308
{ 1, &pcx_header.planes },
309
{ 2, &pcx_header.bytesperline },
310
{ 2, &pcx_header.color },
311
{ 58, &pcx_header.filler },
316
pcx_header_from_buffer (guint8 *buf)
321
for (i = 0; pcx_header_buf_xlate[i].size != 0; i++)
323
g_memmove (pcx_header_buf_xlate[i].address, buf + buf_offset,
324
pcx_header_buf_xlate[i].size);
325
buf_offset += pcx_header_buf_xlate[i].size;
330
pcx_header_to_buffer (guint8 *buf)
335
for (i = 0; pcx_header_buf_xlate[i].size != 0; i++)
337
g_memmove (buf + buf_offset, pcx_header_buf_xlate[i].address,
338
pcx_header_buf_xlate[i].size);
339
buf_offset += pcx_header_buf_xlate[i].size;
344
load_image (const gchar *filename,
348
GimpDrawable *drawable;
349
GimpPixelRgn pixel_rgn;
350
guint16 offset_x, offset_y, bytesperline;
351
gint32 width, height;
353
guchar *dest, cmap[768];
354
guint8 header_buf[128];
356
fd = g_fopen (filename, "rb");
360
g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
361
_("Could not open '%s' for reading: %s"),
362
gimp_filename_to_utf8 (filename), g_strerror (errno));
366
gimp_progress_init_printf (_("Opening '%s'"),
367
gimp_filename_to_utf8 (filename));
369
if (fread (header_buf, 128, 1, fd) == 0)
371
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
372
_("Could not read header from '%s'"),
373
gimp_filename_to_utf8 (filename));
377
pcx_header_from_buffer (header_buf);
379
if (pcx_header.manufacturer != 10)
381
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
382
_("'%s' is not a PCX file"),
383
gimp_filename_to_utf8 (filename));
387
offset_x = GUINT16_FROM_LE (pcx_header.x1);
388
offset_y = GUINT16_FROM_LE (pcx_header.y1);
389
width = GUINT16_FROM_LE (pcx_header.x2) - offset_x + 1;
390
height = GUINT16_FROM_LE (pcx_header.y2) - offset_y + 1;
391
bytesperline = GUINT16_FROM_LE (pcx_header.bytesperline);
393
if ((width < 0) || (width > GIMP_MAX_IMAGE_SIZE))
395
g_message (_("Unsupported or invalid image width: %d"), width);
398
if ((height < 0) || (height > GIMP_MAX_IMAGE_SIZE))
400
g_message (_("Unsupported or invalid image height: %d"), height);
403
if (bytesperline < (width * pcx_header.bpp) / 8)
405
g_message (_("Invalid number of bytes per line in PCX header"));
409
if (pcx_header.planes == 3 && pcx_header.bpp == 8)
411
image= gimp_image_new (width, height, GIMP_RGB);
412
layer= gimp_layer_new (image, _("Background"), width, height,
413
GIMP_RGB_IMAGE, 100, GIMP_NORMAL_MODE);
417
image= gimp_image_new (width, height, GIMP_INDEXED);
418
layer= gimp_layer_new (image, _("Background"), width, height,
419
GIMP_INDEXED_IMAGE, 100, GIMP_NORMAL_MODE);
421
gimp_image_set_filename (image, filename);
422
gimp_image_add_layer (image, layer, 0);
423
gimp_layer_set_offsets (layer, offset_x, offset_y);
424
drawable = gimp_drawable_get (layer);
426
if (pcx_header.planes == 1 && pcx_header.bpp == 1)
428
dest = (guchar *) g_malloc (width * height);
429
load_1 (fd, width, height, dest, bytesperline);
430
gimp_image_set_colormap (image, mono, 2);
432
else if (pcx_header.planes == 4 && pcx_header.bpp == 1)
434
dest = (guchar *) g_malloc (width * height);
435
load_4 (fd, width, height, dest, bytesperline);
436
gimp_image_set_colormap (image, pcx_header.colormap, 16);
438
else if (pcx_header.planes == 1 && pcx_header.bpp == 8)
440
dest = (guchar *) g_malloc (width * height);
441
load_8 (fd, width, height, dest, bytesperline);
442
fseek (fd, -768L, SEEK_END);
443
fread (cmap, 768, 1, fd);
444
gimp_image_set_colormap (image, cmap, 256);
446
else if (pcx_header.planes == 3 && pcx_header.bpp == 8)
448
dest = (guchar *) g_malloc (width * height * 3);
449
load_24 (fd, width, height, dest, bytesperline);
453
g_message (_("Unusual PCX flavour, giving up"));
457
gimp_pixel_rgn_init (&pixel_rgn, drawable, 0, 0, width, height, TRUE, FALSE);
458
gimp_pixel_rgn_set_rect (&pixel_rgn, dest, 0, 0, width, height);
462
gimp_drawable_flush (drawable);
463
gimp_drawable_detach (drawable);
476
guchar *line = g_new (guchar, bytes);
478
for (row = 0; row < height; buffer += width, ++row)
480
readline (fp, line, bytes);
481
memcpy (buffer, line, width);
482
gimp_progress_update ((double) row / (double) height);
496
guchar *line = g_new (guchar, bytes);
498
for (y = 0; y < height; buffer += width * 3, ++y)
500
for (c = 0; c < 3; ++c)
502
readline (fp, line, bytes);
503
for (x = 0; x < width; ++x)
505
buffer[x * 3 + c] = line[x];
508
gimp_progress_update ((double) y / (double) height);
522
guchar *line = g_new (guchar, bytes);
524
for (y = 0; y < height; buffer += width, ++y)
526
readline (fp, line, bytes);
527
for (x = 0; x < width; ++x)
529
if (line[x / 8] & (128 >> (x % 8)))
534
gimp_progress_update ((double) y / (double) height);
548
guchar *line = g_new (guchar, bytes);
550
for (y = 0; y < height; buffer += width, ++y)
552
for (x = 0; x < width; ++x)
554
for (c = 0; c < 4; ++c)
556
readline(fp, line, bytes);
557
for (x = 0; x < width; ++x)
559
if (line[x / 8] & (128 >> (x % 8)))
560
buffer[x] += (1 << c);
563
gimp_progress_update ((double) y / (double) height);
574
static guchar count = 0, value = 0;
576
if (pcx_header.compression)
589
count = value - 0xc0;
599
fread (buffer, bytes, 1, fp);
604
save_image (const gchar *filename,
610
GimpPixelRgn pixel_rgn;
611
GimpDrawable *drawable;
612
GimpImageType drawable_type;
615
gint offset_x, offset_y;
618
guint8 header_buf[128];
620
drawable = gimp_drawable_get (layer);
621
drawable_type = gimp_drawable_type (layer);
622
gimp_drawable_offsets (layer, &offset_x, &offset_y);
623
width = drawable->width;
624
height = drawable->height;
625
gimp_pixel_rgn_init (&pixel_rgn, drawable, 0, 0, width, height, FALSE, FALSE);
627
gimp_progress_init_printf (_("Saving '%s'"),
628
gimp_filename_to_utf8 (filename));
630
pcx_header.manufacturer = 0x0a;
631
pcx_header.version = 5;
632
pcx_header.compression = 1;
634
switch (drawable_type)
636
case GIMP_INDEXED_IMAGE:
637
cmap = gimp_image_get_colormap (image, &colors);
639
pcx_header.bytesperline = GUINT16_TO_LE (width);
640
pcx_header.planes = 1;
641
pcx_header.color = GUINT16_TO_LE (1);
646
pcx_header.planes = 3;
647
pcx_header.color = GUINT16_TO_LE (1);
648
pcx_header.bytesperline = GUINT16_TO_LE (width);
651
case GIMP_GRAY_IMAGE:
653
pcx_header.planes = 1;
654
pcx_header.color = GUINT16_TO_LE (2);
655
pcx_header.bytesperline = GUINT16_TO_LE (width);
659
g_message (_("Cannot save images with alpha channel."));
663
if ((fp = g_fopen (filename, "wb")) == NULL)
665
g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
666
_("Could not open '%s' for writing: %s"),
667
gimp_filename_to_utf8 (filename), g_strerror (errno));
671
pixels = (guchar *) g_malloc (width * height * pcx_header.planes);
672
gimp_pixel_rgn_get_rect (&pixel_rgn, pixels, 0, 0, width, height);
674
if ((offset_x < 0) || (offset_x > (1<<16)))
676
g_message (_("Invalid X offset: %d"), offset_x);
680
if ((offset_y < 0) || (offset_y > (1<<16)))
682
g_message (_("Invalid Y offset: %d"), offset_y);
686
if (offset_x + width - 1 > (1<<16))
688
g_message (_("Right border out of bounds (must be < %d): %d"), (1<<16),
689
offset_x + width - 1);
693
if (offset_y + height - 1 > (1<<16))
695
g_message (_("Bottom border out of bounds (must be < %d): %d"), (1<<16),
696
offset_y + height - 1);
700
pcx_header.x1 = GUINT16_TO_LE ((guint16)offset_x);
701
pcx_header.y1 = GUINT16_TO_LE ((guint16)offset_y);
702
pcx_header.x2 = GUINT16_TO_LE ((guint16)(offset_x + width - 1));
703
pcx_header.y2 = GUINT16_TO_LE ((guint16)(offset_y + height - 1));
705
pcx_header.hdpi = GUINT16_TO_LE (300);
706
pcx_header.vdpi = GUINT16_TO_LE (300);
707
pcx_header.reserved = 0;
709
pcx_header_to_buffer (header_buf);
711
fwrite (header_buf, 128, 1, fp);
713
switch (drawable_type)
715
case GIMP_INDEXED_IMAGE:
716
save_8 (fp, width, height, pixels);
718
fwrite (cmap, colors, 3, fp);
719
for (i = colors; i < 256; i++)
728
save_24 (fp, width, height, pixels);
731
case GIMP_GRAY_IMAGE:
732
save_8 (fp, width, height, pixels);
734
for (i = 0; i < 256; i++)
736
fputc ((guchar) i, fp);
737
fputc ((guchar) i, fp);
738
fputc ((guchar) i, fp);
746
gimp_drawable_detach (drawable);
749
if (fclose (fp) != 0)
751
g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
752
_("Writing to file '%s' failed: %s"),
753
gimp_filename_to_utf8 (filename), g_strerror (errno));
768
for (row = 0; row < height; ++row)
770
writeline (fp, buffer, width);
772
gimp_progress_update ((double) row / (double) height);
785
line = (guchar *) g_malloc (width);
787
for (y = 0; y < height; ++y)
789
for (c = 0; c < 3; ++c)
791
for (x = 0; x < width; ++x)
793
line[x] = buffer[(3*x) + c];
795
writeline (fp, line, width);
798
gimp_progress_update ((double) y / (double) height);
809
guchar *finish = buffer + bytes;
811
while (buffer < finish)
816
while (buffer < finish && count < 63 && *buffer == value)
821
if (value < 0xc0 && count == 1)
827
fputc (0xc0 + count, fp);