1 /*
2 * pcx.c GIMP plug-in for loading & exporting PCX files
3 *
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 3 of the License, or
7 * (at your option) any later version.
8 *
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.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 */
17
18 /* This code is based in parts on code by Francisco Bustamante, but the
19 largest portion of the code has been rewritten and is now maintained
20 occasionally by Nick Lamb njl195@zepler.org.uk */
21
22 #include "config.h"
23
24 #include <errno.h>
25 #include <string.h>
26
27 #include <glib/gstdio.h>
28
29 #include <libgimp/gimp.h>
30 #include <libgimp/gimpui.h>
31
32 #include "libgimp/stdplugins-intl.h"
33
34
35 #define LOAD_PROC "file-pcx-load"
36 #define SAVE_PROC "file-pcx-save"
37 #define PLUG_IN_BINARY "file-pcx"
38 #define PLUG_IN_ROLE "gimp-file-pcx"
39
40
41 /* Declare local functions. */
42
43 static void query (void);
44 static void run (const gchar *name,
45 gint nparams,
46 const GimpParam *param,
47 gint *nreturn_vals,
48 GimpParam **return_vals);
49
50 static gint32 load_image (const gchar *filename,
51 GError **error);
52
53 static void load_1 (FILE *fp,
54 gint width,
55 gint height,
56 guchar *buf,
57 guint16 bytes);
58 static void load_4 (FILE *fp,
59 gint width,
60 gint height,
61 guchar *buf,
62 guint16 bytes);
63 static void load_sub_8 (FILE *fp,
64 gint width,
65 gint height,
66 gint bpp,
67 gint plane,
68 guchar *buf,
69 guint16 bytes);
70 static void load_8 (FILE *fp,
71 gint width,
72 gint height,
73 guchar *buf,
74 guint16 bytes);
75 static void load_24 (FILE *fp,
76 gint width,
77 gint height,
78 guchar *buf,
79 guint16 bytes);
80 static void readline (FILE *fp,
81 guchar *buf,
82 gint bytes);
83
84 static gint save_image (const gchar *filename,
85 gint32 image,
86 gint32 layer,
87 GError **error);
88 static void save_less_than_8 (FILE *fp,
89 gint width,
90 gint height,
91 const gint bpp,
92 const guchar *buf,
93 gboolean padding);
94 static void save_8 (FILE *fp,
95 gint width,
96 gint height,
97 const guchar *buf,
98 gboolean padding);
99 static void save_24 (FILE *fp,
100 gint width,
101 gint height,
102 const guchar *buf,
103 gboolean padding);
104 static void writeline (FILE *fp,
105 const guchar *buf,
106 gint bytes);
107
108 const GimpPlugInInfo PLUG_IN_INFO =
109 {
110 NULL, /* init_proc */
111 NULL, /* quit_proc */
112 query, /* query_proc */
113 run, /* run_proc */
114 };
115
MAIN()116 MAIN ()
117
118 static void
119 query (void)
120 {
121 static const GimpParamDef load_args[] =
122 {
123 { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" },
124 { GIMP_PDB_STRING, "filename", "The name of the file to load" },
125 { GIMP_PDB_STRING, "raw-filename", "The name entered" }
126 };
127 static const GimpParamDef load_return_vals[] =
128 {
129 { GIMP_PDB_IMAGE, "image", "Output image" }
130 };
131
132 static const GimpParamDef save_args[] =
133 {
134 { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" },
135 { GIMP_PDB_IMAGE, "image", "Input image" },
136 { GIMP_PDB_DRAWABLE, "drawable", "Drawable to export" },
137 { GIMP_PDB_STRING, "filename", "The name of the file to export the image in" },
138 { GIMP_PDB_STRING, "raw-filename", "The name entered" }
139 };
140
141 gimp_install_procedure (LOAD_PROC,
142 "Loads files in Zsoft PCX file format",
143 "FIXME: write help for pcx_load",
144 "Francisco Bustamante & Nick Lamb",
145 "Nick Lamb <njl195@zepler.org.uk>",
146 "January 1997",
147 N_("ZSoft PCX image"),
148 NULL,
149 GIMP_PLUGIN,
150 G_N_ELEMENTS (load_args),
151 G_N_ELEMENTS (load_return_vals),
152 load_args, load_return_vals);
153
154 gimp_register_file_handler_mime (LOAD_PROC, "image/x-pcx");
155 gimp_register_magic_load_handler (LOAD_PROC,
156 "pcx,pcc",
157 "",
158 "0&,byte,10,2&,byte,1,3&,byte,>0,3,byte,<9");
159
160 gimp_install_procedure (SAVE_PROC,
161 "Exports files in ZSoft PCX file format",
162 "FIXME: write help for pcx_save",
163 "Francisco Bustamante & Nick Lamb",
164 "Nick Lamb <njl195@zepler.org.uk>",
165 "January 1997",
166 N_("ZSoft PCX image"),
167 "INDEXED, RGB, GRAY",
168 GIMP_PLUGIN,
169 G_N_ELEMENTS (save_args), 0,
170 save_args, NULL);
171
172 gimp_register_file_handler_mime (SAVE_PROC, "image/x-pcx");
173 gimp_register_save_handler (SAVE_PROC, "pcx,pcc", "");
174 }
175
176 static void
run(const gchar * name,gint nparams,const GimpParam * param,gint * nreturn_vals,GimpParam ** return_vals)177 run (const gchar *name,
178 gint nparams,
179 const GimpParam *param,
180 gint *nreturn_vals,
181 GimpParam **return_vals)
182 {
183 static GimpParam values[2];
184 GimpRunMode run_mode;
185 GimpPDBStatusType status = GIMP_PDB_SUCCESS;
186 gint32 image_ID;
187 gint32 drawable_ID;
188 GimpExportReturn export = GIMP_EXPORT_CANCEL;
189 GError *error = NULL;
190
191 INIT_I18N ();
192 gegl_init (NULL, NULL);
193
194 run_mode = param[0].data.d_int32;
195
196 *nreturn_vals = 1;
197 *return_vals = values;
198
199 values[0].type = GIMP_PDB_STATUS;
200 values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR;
201
202 if (strcmp (name, LOAD_PROC) == 0)
203 {
204 image_ID = load_image (param[1].data.d_string, &error);
205
206 if (image_ID != -1)
207 {
208 *nreturn_vals = 2;
209 values[1].type = GIMP_PDB_IMAGE;
210 values[1].data.d_image = image_ID;
211 }
212 else
213 {
214 status = GIMP_PDB_EXECUTION_ERROR;
215 }
216 }
217 else if (strcmp (name, SAVE_PROC) == 0)
218 {
219 image_ID = param[1].data.d_int32;
220 drawable_ID = param[2].data.d_int32;
221
222 /* eventually export the image */
223 switch (run_mode)
224 {
225 case GIMP_RUN_INTERACTIVE:
226 case GIMP_RUN_WITH_LAST_VALS:
227 gimp_ui_init (PLUG_IN_BINARY, FALSE);
228
229 export = gimp_export_image (&image_ID, &drawable_ID, "PCX",
230 GIMP_EXPORT_CAN_HANDLE_RGB |
231 GIMP_EXPORT_CAN_HANDLE_GRAY |
232 GIMP_EXPORT_CAN_HANDLE_INDEXED);
233
234 if (export == GIMP_EXPORT_CANCEL)
235 {
236 values[0].data.d_status = GIMP_PDB_CANCEL;
237 return;
238 }
239 break;
240 default:
241 break;
242 }
243
244 switch (run_mode)
245 {
246 case GIMP_RUN_INTERACTIVE:
247 break;
248
249 case GIMP_RUN_NONINTERACTIVE:
250 if (nparams != 5)
251 status = GIMP_PDB_CALLING_ERROR;
252 break;
253
254 case GIMP_RUN_WITH_LAST_VALS:
255 break;
256
257 default:
258 break;
259 }
260
261 if (status == GIMP_PDB_SUCCESS)
262 {
263 if (! save_image (param[3].data.d_string, image_ID, drawable_ID,
264 &error))
265 {
266 status = GIMP_PDB_EXECUTION_ERROR;
267 }
268 }
269
270 if (export == GIMP_EXPORT_EXPORT)
271 gimp_image_delete (image_ID);
272 }
273 else
274 {
275 status = GIMP_PDB_CALLING_ERROR;
276 }
277
278 if (status != GIMP_PDB_SUCCESS && error)
279 {
280 *nreturn_vals = 2;
281 values[1].type = GIMP_PDB_STRING;
282 values[1].data.d_string = error->message;
283 }
284
285 values[0].data.d_status = status;
286 }
287
288 static struct
289 {
290 guint8 manufacturer;
291 guint8 version;
292 guint8 compression;
293 guint8 bpp;
294 guint16 x1, y1;
295 guint16 x2, y2;
296 guint16 hdpi;
297 guint16 vdpi;
298 guint8 colormap[48];
299 guint8 reserved;
300 guint8 planes;
301 guint16 bytesperline;
302 guint16 color;
303 guint8 filler[58];
304 } pcx_header;
305
306 static struct {
307 size_t size;
308 gpointer address;
309 } const pcx_header_buf_xlate[] = {
310 { 1, &pcx_header.manufacturer },
311 { 1, &pcx_header.version },
312 { 1, &pcx_header.compression },
313 { 1, &pcx_header.bpp },
314 { 2, &pcx_header.x1 },
315 { 2, &pcx_header.y1 },
316 { 2, &pcx_header.x2 },
317 { 2, &pcx_header.y2 },
318 { 2, &pcx_header.hdpi },
319 { 2, &pcx_header.vdpi },
320 { 48, &pcx_header.colormap },
321 { 1, &pcx_header.reserved },
322 { 1, &pcx_header.planes },
323 { 2, &pcx_header.bytesperline },
324 { 2, &pcx_header.color },
325 { 58, &pcx_header.filler },
326 { 0, NULL }
327 };
328
329 static void
pcx_header_from_buffer(guint8 * buf)330 pcx_header_from_buffer (guint8 *buf)
331 {
332 gint i;
333 gint buf_offset = 0;
334
335 for (i = 0; pcx_header_buf_xlate[i].size != 0; i++)
336 {
337 memmove (pcx_header_buf_xlate[i].address, buf + buf_offset,
338 pcx_header_buf_xlate[i].size);
339 buf_offset += pcx_header_buf_xlate[i].size;
340 }
341 }
342
343 static void
pcx_header_to_buffer(guint8 * buf)344 pcx_header_to_buffer (guint8 *buf)
345 {
346 gint i;
347 gint buf_offset = 0;
348
349 for (i = 0; pcx_header_buf_xlate[i].size != 0; i++)
350 {
351 memmove (buf + buf_offset, pcx_header_buf_xlate[i].address,
352 pcx_header_buf_xlate[i].size);
353 buf_offset += pcx_header_buf_xlate[i].size;
354 }
355 }
356
357 static gint32
load_image(const gchar * filename,GError ** error)358 load_image (const gchar *filename,
359 GError **error)
360 {
361 FILE *fd;
362 GeglBuffer *buffer;
363 guint16 offset_x, offset_y, bytesperline;
364 gint32 width, height;
365 guint16 resolution_x, resolution_y;
366 gint32 image, layer;
367 guchar *dest, cmap[768];
368 guint8 header_buf[128];
369
370 gimp_progress_init_printf (_("Opening '%s'"),
371 gimp_filename_to_utf8 (filename));
372
373 fd = g_fopen (filename, "rb");
374
375 if (! fd)
376 {
377 g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
378 _("Could not open '%s' for reading: %s"),
379 gimp_filename_to_utf8 (filename), g_strerror (errno));
380 return -1;
381 }
382
383 if (fread (header_buf, 128, 1, fd) == 0)
384 {
385 g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
386 _("Could not read header from '%s'"),
387 gimp_filename_to_utf8 (filename));
388 fclose (fd);
389 return -1;
390 }
391
392 pcx_header_from_buffer (header_buf);
393
394 if (pcx_header.manufacturer != 10)
395 {
396 g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
397 _("'%s' is not a PCX file"),
398 gimp_filename_to_utf8 (filename));
399 fclose (fd);
400 return -1;
401 }
402
403 offset_x = GUINT16_FROM_LE (pcx_header.x1);
404 offset_y = GUINT16_FROM_LE (pcx_header.y1);
405 width = GUINT16_FROM_LE (pcx_header.x2) - offset_x + 1;
406 height = GUINT16_FROM_LE (pcx_header.y2) - offset_y + 1;
407 bytesperline = GUINT16_FROM_LE (pcx_header.bytesperline);
408 resolution_x = GUINT16_FROM_LE (pcx_header.hdpi);
409 resolution_y = GUINT16_FROM_LE (pcx_header.vdpi);
410
411 if ((width <= 0) || (width > GIMP_MAX_IMAGE_SIZE))
412 {
413 g_message (_("Unsupported or invalid image width: %d"), width);
414 fclose (fd);
415 return -1;
416 }
417 if ((height <= 0) || (height > GIMP_MAX_IMAGE_SIZE))
418 {
419 g_message (_("Unsupported or invalid image height: %d"), height);
420 fclose (fd);
421 return -1;
422 }
423 if (bytesperline < ((width * pcx_header.bpp + 7) / 8))
424 {
425 g_message (_("Invalid number of bytes per line in PCX header"));
426 fclose (fd);
427 return -1;
428 }
429 if ((resolution_x < 1) || (resolution_x > GIMP_MAX_RESOLUTION) ||
430 (resolution_y < 1) || (resolution_y > GIMP_MAX_RESOLUTION))
431 {
432 g_message (_("Resolution out of bounds in XCX header, using 72x72"));
433 resolution_x = 72;
434 resolution_y = 72;
435 }
436
437 /* Shield against potential buffer overflows in load_*() functions. */
438 if (G_MAXSIZE / width / height < 3)
439 {
440 g_message (_("Image dimensions too large: width %d x height %d"), width, height);
441 fclose (fd);
442 return -1;
443 }
444
445 if (pcx_header.planes == 3 && pcx_header.bpp == 8)
446 {
447 image= gimp_image_new (width, height, GIMP_RGB);
448 layer= gimp_layer_new (image, _("Background"), width, height,
449 GIMP_RGB_IMAGE,
450 100,
451 gimp_image_get_default_new_layer_mode (image));
452 }
453 else
454 {
455 image= gimp_image_new (width, height, GIMP_INDEXED);
456 layer= gimp_layer_new (image, _("Background"), width, height,
457 GIMP_INDEXED_IMAGE,
458 100,
459 gimp_image_get_default_new_layer_mode (image));
460 }
461
462 gimp_image_set_filename (image, filename);
463 gimp_image_set_resolution (image, resolution_x, resolution_y);
464
465 gimp_image_insert_layer (image, layer, -1, 0);
466 gimp_layer_set_offsets (layer, offset_x, offset_y);
467
468 buffer = gimp_drawable_get_buffer (layer);
469
470 if (pcx_header.planes == 1 && pcx_header.bpp == 1)
471 {
472 const guint8 *colormap = pcx_header.colormap;
473 dest = g_new (guchar, ((gsize) width) * height);
474 load_1 (fd, width, height, dest, bytesperline);
475 /* Monochrome does not mean necessarily B&W. Therefore we still
476 * want to check the header palette, even for just 2 colors.
477 * Hopefully the header palette will always be filled with
478 * meaningful colors and the creator software did not just assume
479 * B&W by being monochrome.
480 * Until now test samples showed that even when B&W the header
481 * palette was correctly filled with these 2 colors and we didn't
482 * find counter-examples.
483 * See bug 159947, comment 21 and 23.
484 */
485 /* ... Actually, there *are* files out there with a zeroed 1-bit palette,
486 * which are supposed to be displayed as B&W (see issue #2997.) These
487 * files *might* be in the wrong (who knows...) but the fact is that
488 * other software, including older versions of GIMP, do display them
489 * "correctly", so let's follow suit: if the two palette colors are
490 * equal, use a B&W palette instead.
491 */
492 if (! memcmp (colormap, colormap + 3, 3))
493 {
494 static const guint8 bw_colormap[6] = { 0, 0, 0,
495 255, 255, 255};
496 colormap = bw_colormap;
497 }
498 gimp_image_set_colormap (image, colormap, 2);
499 }
500 else if (pcx_header.bpp == 1 && pcx_header.planes == 2)
501 {
502 dest = g_new (guchar, ((gsize) width) * height);
503 load_sub_8 (fd, width, height, 1, 2, dest, bytesperline);
504 gimp_image_set_colormap (image, pcx_header.colormap, 4);
505 }
506 else if (pcx_header.bpp == 2 && pcx_header.planes == 1)
507 {
508 dest = g_new (guchar, ((gsize) width) * height);
509 load_sub_8 (fd, width, height, 2, 1, dest, bytesperline);
510 gimp_image_set_colormap (image, pcx_header.colormap, 4);
511 }
512 else if (pcx_header.bpp == 1 && pcx_header.planes == 3)
513 {
514 dest = g_new (guchar, ((gsize) width) * height);
515 load_sub_8 (fd, width, height, 1, 3, dest, bytesperline);
516 gimp_image_set_colormap (image, pcx_header.colormap, 8);
517 }
518 else if (pcx_header.bpp == 1 && pcx_header.planes == 4)
519 {
520 dest = g_new (guchar, ((gsize) width) * height);
521 load_4 (fd, width, height, dest, bytesperline);
522 gimp_image_set_colormap (image, pcx_header.colormap, 16);
523 }
524 else if (pcx_header.bpp == 4 && pcx_header.planes == 1)
525 {
526 dest = g_new (guchar, ((gsize) width) * height);
527 load_sub_8 (fd, width, height, 4, 1, dest, bytesperline);
528 gimp_image_set_colormap (image, pcx_header.colormap, 16);
529 }
530 else if (pcx_header.bpp == 8 && pcx_header.planes == 1)
531 {
532 dest = g_new (guchar, ((gsize) width) * height);
533 load_8 (fd, width, height, dest, bytesperline);
534 fseek (fd, -768L, SEEK_END);
535 fread (cmap, 768, 1, fd);
536 gimp_image_set_colormap (image, cmap, 256);
537 }
538 else if (pcx_header.bpp == 8 && pcx_header.planes == 3)
539 {
540 dest = g_new (guchar, ((gsize) width) * height * 3);
541 load_24 (fd, width, height, dest, bytesperline);
542 }
543 else
544 {
545 g_message (_("Unusual PCX flavour, giving up"));
546 fclose (fd);
547 return -1;
548 }
549
550 gegl_buffer_set (buffer, GEGL_RECTANGLE (0, 0, width, height), 0,
551 NULL, dest, GEGL_AUTO_ROWSTRIDE);
552
553 fclose (fd);
554 g_free (dest);
555 g_object_unref (buffer);
556
557 gimp_progress_update (1.0);
558
559 return image;
560 }
561
562 static void
load_8(FILE * fp,gint width,gint height,guchar * buf,guint16 bytes)563 load_8 (FILE *fp,
564 gint width,
565 gint height,
566 guchar *buf,
567 guint16 bytes)
568 {
569 gint row;
570 guchar *line = g_new (guchar, bytes);
571
572 for (row = 0; row < height; buf += width, ++row)
573 {
574 readline (fp, line, bytes);
575 memcpy (buf, line, width);
576 gimp_progress_update ((double) row / (double) height);
577 }
578
579 g_free (line);
580 }
581
582 static void
load_24(FILE * fp,gint width,gint height,guchar * buf,guint16 bytes)583 load_24 (FILE *fp,
584 gint width,
585 gint height,
586 guchar *buf,
587 guint16 bytes)
588 {
589 gint x, y, c;
590 guchar *line = g_new (guchar, bytes);
591
592 for (y = 0; y < height; buf += width * 3, ++y)
593 {
594 for (c = 0; c < 3; ++c)
595 {
596 readline (fp, line, bytes);
597 for (x = 0; x < width; ++x)
598 {
599 buf[x * 3 + c] = line[x];
600 }
601 }
602 gimp_progress_update ((double) y / (double) height);
603 }
604
605 g_free (line);
606 }
607
608 static void
load_1(FILE * fp,gint width,gint height,guchar * buf,guint16 bytes)609 load_1 (FILE *fp,
610 gint width,
611 gint height,
612 guchar *buf,
613 guint16 bytes)
614 {
615 gint x, y;
616 guchar *line = g_new (guchar, bytes);
617
618 for (y = 0; y < height; buf += width, ++y)
619 {
620 readline (fp, line, bytes);
621 for (x = 0; x < width; ++x)
622 {
623 if (line[x / 8] & (128 >> (x % 8)))
624 buf[x] = 1;
625 else
626 buf[x] = 0;
627 }
628 gimp_progress_update ((double) y / (double) height);
629 }
630
631 g_free (line);
632 }
633
634 static void
load_4(FILE * fp,gint width,gint height,guchar * buf,guint16 bytes)635 load_4 (FILE *fp,
636 gint width,
637 gint height,
638 guchar *buf,
639 guint16 bytes)
640 {
641 gint x, y, c;
642 guchar *line = g_new (guchar, bytes);
643
644 for (y = 0; y < height; buf += width, ++y)
645 {
646 for (x = 0; x < width; ++x)
647 buf[x] = 0;
648 for (c = 0; c < 4; ++c)
649 {
650 readline(fp, line, bytes);
651 for (x = 0; x < width; ++x)
652 {
653 if (line[x / 8] & (128 >> (x % 8)))
654 buf[x] += (1 << c);
655 }
656 }
657 gimp_progress_update ((double) y / (double) height);
658 }
659
660 g_free (line);
661 }
662
663 static void
load_sub_8(FILE * fp,gint width,gint height,gint bpp,gint plane,guchar * buf,guint16 bytes)664 load_sub_8 (FILE *fp,
665 gint width,
666 gint height,
667 gint bpp,
668 gint plane,
669 guchar *buf,
670 guint16 bytes)
671 {
672 gint x, y, c, b;
673 guchar *line = g_new (guchar, bytes);
674 gint real_bpp = bpp - 1;
675 gint current_bit = 0;
676
677 for (y = 0; y < height; buf += width, ++y)
678 {
679 for (x = 0; x < width; ++x)
680 buf[x] = 0;
681 for (c = 0; c < plane; ++c)
682 {
683 readline (fp, line, bytes);
684 for (x = 0; x < width; ++x)
685 {
686 for (b = 0; b < bpp; b++)
687 {
688 current_bit = bpp * x + b;
689 if (line[current_bit / 8] & (128 >> (current_bit % 8)))
690 buf[x] += (1 << (real_bpp - b + c));
691 }
692 }
693 }
694 gimp_progress_update ((double) y / (double) height);
695 }
696
697 g_free (line);
698 }
699
700 static void
readline(FILE * fp,guchar * buf,gint bytes)701 readline (FILE *fp,
702 guchar *buf,
703 gint bytes)
704 {
705 static guchar count = 0, value = 0;
706
707 if (pcx_header.compression)
708 {
709 while (bytes--)
710 {
711 if (count == 0)
712 {
713 value = fgetc (fp);
714 if (value < 0xc0)
715 {
716 count = 1;
717 }
718 else
719 {
720 count = value - 0xc0;
721 value = fgetc (fp);
722 }
723 }
724 count--;
725 *(buf++) = value;
726 }
727 }
728 else
729 {
730 fread (buf, bytes, 1, fp);
731 }
732 }
733
734 static gint
save_image(const gchar * filename,gint32 image,gint32 layer,GError ** error)735 save_image (const gchar *filename,
736 gint32 image,
737 gint32 layer,
738 GError **error)
739 {
740 FILE *fp;
741 GeglBuffer *buffer;
742 const Babl *format;
743 GimpImageType drawable_type;
744 guchar *cmap= NULL;
745 guchar *pixels;
746 gint offset_x, offset_y;
747 guint width, height;
748 gdouble resolution_x, resolution_y;
749 gint colors, i;
750 guint8 header_buf[128];
751 gboolean padding = FALSE;
752
753 drawable_type = gimp_drawable_type (layer);
754 gimp_drawable_offsets (layer, &offset_x, &offset_y);
755
756 buffer = gimp_drawable_get_buffer (layer);
757
758 width = gegl_buffer_get_width (buffer);
759 height = gegl_buffer_get_height (buffer);
760
761 gimp_progress_init_printf (_("Exporting '%s'"),
762 gimp_filename_to_utf8 (filename));
763
764 pcx_header.manufacturer = 0x0a;
765 pcx_header.version = 5;
766 pcx_header.compression = 1;
767
768 switch (drawable_type)
769 {
770 case GIMP_INDEXED_IMAGE:
771 cmap = gimp_image_get_colormap (image, &colors);
772 if (colors > 16)
773 {
774 pcx_header.bpp = 8;
775 pcx_header.planes = 1;
776 pcx_header.bytesperline = width;
777 }
778 else if (colors > 2)
779 {
780 pcx_header.bpp = 4;
781 pcx_header.planes = 1;
782 pcx_header.bytesperline = (width + 1) / 2;
783 }
784 else
785 {
786 pcx_header.bpp = 1;
787 pcx_header.planes = 1;
788 pcx_header.bytesperline = (width + 7) / 8;
789 }
790 pcx_header.color = GUINT16_TO_LE (1);
791 format = NULL;
792
793 /* Some references explain that 2bpp/1plane and 4bpp/1plane files
794 * would use the palette at EOF (not the one from the header) if
795 * we are in version 5 of PCX. Other sources affirm that even in
796 * version 5, EOF palette must be used only when there are more
797 * than 16 colors. We go with this second assumption.
798 * See bug 159947, comment 21 and 23.
799 */
800 if (colors <= 16)
801 {
802 for (i = 0; i < (colors * 3); i++)
803 {
804 pcx_header.colormap[i] = cmap[i];
805 }
806 }
807
808 break;
809
810 case GIMP_RGB_IMAGE:
811 pcx_header.bpp = 8;
812 pcx_header.planes = 3;
813 pcx_header.color = GUINT16_TO_LE (1);
814 pcx_header.bytesperline = width;
815 format = babl_format ("R'G'B' u8");
816 break;
817
818 case GIMP_GRAY_IMAGE:
819 pcx_header.bpp = 8;
820 pcx_header.planes = 1;
821 pcx_header.color = GUINT16_TO_LE (2);
822 pcx_header.bytesperline = width;
823 format = babl_format ("Y' u8");
824 break;
825
826 default:
827 g_message (_("Cannot export images with alpha channel."));
828 return FALSE;
829 }
830
831 /* Bytes per Line must be an even number, according to spec */
832 if (pcx_header.bytesperline % 2 != 0)
833 {
834 pcx_header.bytesperline++;
835 padding = TRUE;
836 }
837 pcx_header.bytesperline = GUINT16_TO_LE (pcx_header.bytesperline);
838
839 pixels = (guchar *) g_malloc (width * height * pcx_header.planes);
840
841 gegl_buffer_get (buffer, GEGL_RECTANGLE (0, 0, width, height), 1.0,
842 format, pixels,
843 GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
844
845 if ((offset_x < 0) || (offset_x > (1<<16)))
846 {
847 g_message (_("Invalid X offset: %d"), offset_x);
848 return FALSE;
849 }
850
851 if ((offset_y < 0) || (offset_y > (1<<16)))
852 {
853 g_message (_("Invalid Y offset: %d"), offset_y);
854 return FALSE;
855 }
856
857 if (offset_x + width - 1 > (1<<16))
858 {
859 g_message (_("Right border out of bounds (must be < %d): %d"), (1<<16),
860 offset_x + width - 1);
861 return FALSE;
862 }
863
864 if (offset_y + height - 1 > (1<<16))
865 {
866 g_message (_("Bottom border out of bounds (must be < %d): %d"), (1<<16),
867 offset_y + height - 1);
868 return FALSE;
869 }
870
871 if ((fp = g_fopen (filename, "wb")) == NULL)
872 {
873 g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
874 _("Could not open '%s' for writing: %s"),
875 gimp_filename_to_utf8 (filename), g_strerror (errno));
876 return FALSE;
877 }
878
879 pcx_header.x1 = GUINT16_TO_LE ((guint16)offset_x);
880 pcx_header.y1 = GUINT16_TO_LE ((guint16)offset_y);
881 pcx_header.x2 = GUINT16_TO_LE ((guint16)(offset_x + width - 1));
882 pcx_header.y2 = GUINT16_TO_LE ((guint16)(offset_y + height - 1));
883
884 gimp_image_get_resolution (image, &resolution_x, &resolution_y);
885
886 pcx_header.hdpi = GUINT16_TO_LE (RINT (MAX (resolution_x, 1.0)));
887 pcx_header.vdpi = GUINT16_TO_LE (RINT (MAX (resolution_y, 1.0)));
888 pcx_header.reserved = 0;
889
890 pcx_header_to_buffer (header_buf);
891
892 fwrite (header_buf, 128, 1, fp);
893
894 switch (drawable_type)
895 {
896 case GIMP_INDEXED_IMAGE:
897 if (colors > 16)
898 {
899 save_8 (fp, width, height, pixels, padding);
900 fputc (0x0c, fp);
901 fwrite (cmap, colors, 3, fp);
902 for (i = colors; i < 256; i++)
903 {
904 fputc (0, fp);
905 fputc (0, fp);
906 fputc (0, fp);
907 }
908 }
909 else /* Covers 1 and 4 bpp */
910 {
911 save_less_than_8 (fp, width, height, pcx_header.bpp, pixels, padding);
912 }
913 break;
914
915 case GIMP_RGB_IMAGE:
916 save_24 (fp, width, height, pixels, padding);
917 break;
918
919 case GIMP_GRAY_IMAGE:
920 save_8 (fp, width, height, pixels, padding);
921 fputc (0x0c, fp);
922 for (i = 0; i < 256; i++)
923 {
924 fputc ((guchar) i, fp);
925 fputc ((guchar) i, fp);
926 fputc ((guchar) i, fp);
927 }
928 break;
929
930 default:
931 return FALSE;
932 }
933
934 g_object_unref (buffer);
935 g_free (pixels);
936
937 if (fclose (fp) != 0)
938 {
939 g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
940 _("Writing to file '%s' failed: %s"),
941 gimp_filename_to_utf8 (filename), g_strerror (errno));
942 return FALSE;
943 }
944
945 return TRUE;
946 }
947
948 static void
save_less_than_8(FILE * fp,gint width,gint height,const gint bpp,const guchar * buf,gboolean padding)949 save_less_than_8 (FILE *fp,
950 gint width,
951 gint height,
952 const gint bpp,
953 const guchar *buf,
954 gboolean padding)
955 {
956 const gint bit_limit = (8 - bpp);
957 const gint buf_size = width * height;
958 const gint line_end = width - 1;
959 gint j = bit_limit;
960 gint count = 0;
961 guchar byte_to_write = 0x00;
962 guchar *line;
963 gint x;
964
965 line = (guchar *) g_malloc (((width + 7) / 8) * bpp);
966
967 for (x = 0; x < buf_size; x++)
968 {
969 byte_to_write |= (buf[x] << j);
970 j -= bpp;
971
972 if (j < 0 || (x % width == line_end))
973 {
974 line[count] = byte_to_write;
975 count++;
976 byte_to_write = 0x00;
977 j = bit_limit;
978
979 if ((x % width == line_end))
980 {
981 writeline (fp, line, count);
982 count = 0;
983 if (padding)
984 fputc ('\0', fp);
985 gimp_progress_update ((double) x / (double) buf_size);
986 }
987 }
988 }
989 g_free (line);
990 }
991
992 static void
save_8(FILE * fp,gint width,gint height,const guchar * buf,gboolean padding)993 save_8 (FILE *fp,
994 gint width,
995 gint height,
996 const guchar *buf,
997 gboolean padding)
998 {
999 int row;
1000
1001 for (row = 0; row < height; ++row)
1002 {
1003 writeline (fp, buf, width);
1004 buf += width;
1005 if (padding)
1006 fputc ('\0', fp);
1007 gimp_progress_update ((double) row / (double) height);
1008 }
1009 }
1010
1011 static void
save_24(FILE * fp,gint width,gint height,const guchar * buf,gboolean padding)1012 save_24 (FILE *fp,
1013 gint width,
1014 gint height,
1015 const guchar *buf,
1016 gboolean padding)
1017 {
1018 int x, y, c;
1019 guchar *line;
1020
1021 line = (guchar *) g_malloc (width);
1022
1023 for (y = 0; y < height; ++y)
1024 {
1025 for (c = 0; c < 3; ++c)
1026 {
1027 for (x = 0; x < width; ++x)
1028 {
1029 line[x] = buf[(3*x) + c];
1030 }
1031 writeline (fp, line, width);
1032 if (padding)
1033 fputc ('\0', fp);
1034 }
1035 buf += width * 3;
1036 gimp_progress_update ((double) y / (double) height);
1037 }
1038 g_free (line);
1039 }
1040
1041 static void
writeline(FILE * fp,const guchar * buf,gint bytes)1042 writeline (FILE *fp,
1043 const guchar *buf,
1044 gint bytes)
1045 {
1046 const guchar *finish = buf + bytes;
1047 guchar value;
1048 guchar count;
1049
1050 while (buf < finish)
1051 {
1052 value = *(buf++);
1053 count = 1;
1054
1055 while (buf < finish && count < 63 && *buf == value)
1056 {
1057 count++; buf++;
1058 }
1059
1060 if (value < 0xc0 && count == 1)
1061 {
1062 fputc (value, fp);
1063 }
1064 else
1065 {
1066 fputc (0xc0 + count, fp);
1067 fputc (value, fp);
1068 }
1069 }
1070 }
1071