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