1 /* GIMP - The GNU Image Manipulation Program
2  * Copyright (C) 1995 Spencer Kimball and Peter Mattis
3  *
4  * X10 and X11 bitmap (XBM) loading and exporting file filter for GIMP.
5  * XBM code Copyright (C) 1998 Gordon Matzigkeit
6  *
7  * The XBM reading and writing code was written from scratch by Gordon
8  * Matzigkeit <gord@gnu.org> based on the XReadBitmapFile(3X11) manual
9  * page distributed with X11R6 and by staring at valid XBM files.  It
10  * does not contain any code written for other XBM file loaders.
11  *
12  * This program is free software: you can redistribute it and/or modify
13  * it under the terms of the GNU General Public License as published by
14  * the Free Software Foundation; either version 3 of the License, or
15  * (at your option) any later version.
16  *
17  * This program is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  * GNU General Public License for more details.
21  *
22  * You should have received a copy of the GNU General Public License
23  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
24  */
25 
26 /* Release 1.0, 1998-02-04, Gordon Matzigkeit <gord@gnu.org>:
27  *   - Load and save X10 and X11 bitmaps.
28  *   - Allow the user to specify the C identifier prefix.
29  *
30  * TODO:
31  *   - Parsing is very tolerant, and the algorithms are quite hairy, so
32  *     load_image should be carefully tested to make sure there are no XBM's
33  *     that fail.
34  */
35 
36 /* Set this for debugging. */
37 /* #define VERBOSE 2 */
38 
39 #include "config.h"
40 
41 #include <errno.h>
42 #include <string.h>
43 
44 #include <glib/gstdio.h>
45 
46 #include <libgimp/gimp.h>
47 #include <libgimp/gimpui.h>
48 
49 #include "libgimp/stdplugins-intl.h"
50 
51 
52 #define LOAD_PROC      "file-xbm-load"
53 #define SAVE_PROC      "file-xbm-save"
54 #define PLUG_IN_BINARY "file-xbm"
55 #define PLUG_IN_ROLE   "gimp-file-xbm"
56 
57 
58 /* Wear your GIMP with pride! */
59 #define DEFAULT_USE_COMMENT TRUE
60 #define MAX_COMMENT         72
61 #define MAX_MASK_EXT        32
62 
63 /* C identifier prefix. */
64 #define DEFAULT_PREFIX "bitmap"
65 #define MAX_PREFIX     64
66 
67 /* Whether or not to export as X10 bitmap. */
68 #define DEFAULT_X10_FORMAT FALSE
69 
70 typedef struct _XBMSaveVals
71 {
72   gchar    comment[MAX_COMMENT + 1];
73   gint     x10_format;
74   gint     use_hot;
75   gint     x_hot;
76   gint     y_hot;
77   gchar    prefix[MAX_PREFIX + 1];
78   gboolean write_mask;
79   gchar    mask_ext[MAX_MASK_EXT + 1];
80 } XBMSaveVals;
81 
82 static XBMSaveVals xsvals =
83 {
84   "###",                /* comment */
85   DEFAULT_X10_FORMAT,   /* x10_format */
86   FALSE,
87   0,                    /* x_hot */
88   0,                    /* y_hot */
89   DEFAULT_PREFIX,       /* prefix */
90   FALSE,                /* write_mask */
91   "-mask"
92 };
93 
94 
95 /* Declare some local functions.
96  */
97 static void      query                   (void);
98 static void      run                     (const gchar      *name,
99                                           gint              nparams,
100                                           const GimpParam  *param,
101                                           gint             *nreturn_vals,
102                                           GimpParam       **return_vals);
103 
104 static gint32    load_image              (const gchar      *filename,
105                                           GError          **error);
106 static gint      save_image              (GFile            *file,
107                                           const gchar      *prefix,
108                                           const gchar      *comment,
109                                           gboolean          save_mask,
110                                           gint32            image_ID,
111                                           gint32            drawable_ID,
112                                           GError          **error);
113 static gboolean  save_dialog             (gint32            drawable_ID);
114 
115 static gboolean  print                   (GOutputStream    *output,
116                                           GError          **error,
117                                           const gchar      *format,
118                                           ...) G_GNUC_PRINTF (3, 4);
119 
120 #if 0
121 /* DISABLED - see http://bugzilla.gnome.org/show_bug.cgi?id=82763 */
122 static void      comment_entry_callback  (GtkWidget        *widget,
123                                           gpointer          data);
124 #endif
125 static void      prefix_entry_callback   (GtkWidget        *widget,
126                                           gpointer          data);
127 static void      mask_ext_entry_callback (GtkWidget        *widget,
128                                           gpointer          data);
129 
130 
131 const GimpPlugInInfo PLUG_IN_INFO =
132 {
133   NULL,  /* init_proc  */
134   NULL,  /* quit_proc  */
135   query, /* query_proc */
136   run,   /* run_proc   */
137 };
138 
139 MAIN ()
140 
141 #ifdef VERBOSE
142 static int verbose = VERBOSE;
143 #endif
144 
145 
146 static void
query(void)147 query (void)
148 {
149   static const GimpParamDef load_args[] =
150   {
151     { GIMP_PDB_INT32,  "run-mode",     "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" },
152     { GIMP_PDB_STRING, "filename",     "The name of the file to load" },
153     { GIMP_PDB_STRING, "raw-filename", "The name entered"             }
154   };
155 
156   static const GimpParamDef load_return_vals[] =
157   {
158     { GIMP_PDB_IMAGE,  "image",        "Output image" }
159   };
160 
161   static const GimpParamDef save_args[] =
162   {
163     { GIMP_PDB_INT32,    "run-mode",       "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" },
164     { GIMP_PDB_IMAGE,    "image",          "Input image" },
165     { GIMP_PDB_DRAWABLE, "drawable",       "Drawable to export" },
166     { GIMP_PDB_STRING,   "filename",       "The name of the file to export" },
167     { GIMP_PDB_STRING,   "raw-filename",   "The name entered" },
168     { GIMP_PDB_STRING,   "comment",        "Image description (maximum 72 bytes)" },
169     { GIMP_PDB_INT32,    "x10",            "Export in X10 format" },
170     { GIMP_PDB_INT32,    "x-hot",          "X coordinate of hotspot" },
171     { GIMP_PDB_INT32,    "y-hot",          "Y coordinate of hotspot" },
172     { GIMP_PDB_STRING,   "prefix",         "Identifier prefix [determined from filename]"},
173     { GIMP_PDB_INT32,    "write-mask",     "(0 = ignore, 1 = save as extra file)" },
174     { GIMP_PDB_STRING,   "mask-extension", "Extension of the mask file" }
175   } ;
176 
177   gimp_install_procedure (LOAD_PROC,
178                           "Load a file in X10 or X11 bitmap (XBM) file format",
179                           "Load a file in X10 or X11 bitmap (XBM) file format.  XBM is a lossless format for flat black-and-white (two color indexed) images.",
180                           "Gordon Matzigkeit",
181                           "Gordon Matzigkeit",
182                           "1998",
183                           N_("X BitMap image"),
184                           NULL,
185                           GIMP_PLUGIN,
186                           G_N_ELEMENTS (load_args),
187                           G_N_ELEMENTS (load_return_vals),
188                           load_args, load_return_vals);
189 
190   gimp_register_file_handler_mime (LOAD_PROC, "image/x-xbitmap");
191   gimp_register_load_handler (LOAD_PROC,
192                               "xbm,icon,bitmap",
193                               "");
194 
195   gimp_install_procedure (SAVE_PROC,
196                           "Export a file in X10 or X11 bitmap (XBM) file format",
197                           "Export a file in X10 or X11 bitmap (XBM) file format.  XBM is a lossless format for flat black-and-white (two color indexed) images.",
198                           "Gordon Matzigkeit",
199                           "Gordon Matzigkeit",
200                           "1998",
201                           N_("X BitMap image"),
202                           "INDEXED",
203                           GIMP_PLUGIN,
204                           G_N_ELEMENTS (save_args), 0,
205                           save_args, NULL);
206 
207   gimp_register_file_handler_mime (SAVE_PROC, "image/x-xbitmap");
208   gimp_register_file_handler_uri (SAVE_PROC);
209   gimp_register_save_handler (SAVE_PROC, "xbm,icon,bitmap", "");
210 }
211 
212 static gchar *
init_prefix(const gchar * filename)213 init_prefix (const gchar *filename)
214 {
215   gchar *p, *prefix;
216   gint len;
217 
218   prefix = g_path_get_basename (filename);
219 
220   memset (xsvals.prefix, 0, sizeof (xsvals.prefix));
221 
222   if (prefix)
223     {
224       /* Strip any extension. */
225       p = strrchr (prefix, '.');
226       if (p && p != prefix)
227         len = MIN (MAX_PREFIX, p - prefix);
228       else
229         len = MAX_PREFIX;
230 
231       strncpy (xsvals.prefix, prefix, len);
232       g_free (prefix);
233     }
234 
235   return xsvals.prefix;
236 }
237 
238 static void
run(const gchar * name,gint nparams,const GimpParam * param,gint * nreturn_vals,GimpParam ** return_vals)239 run (const gchar      *name,
240      gint              nparams,
241      const GimpParam  *param,
242      gint             *nreturn_vals,
243      GimpParam       **return_vals)
244 {
245   static GimpParam   values[2];
246   GimpRunMode        run_mode;
247   GimpPDBStatusType  status        = GIMP_PDB_SUCCESS;
248   gint32             image_ID;
249   gint32             drawable_ID;
250   GimpParasite      *parasite      = NULL;
251   gchar             *mask_basename = NULL;
252   GError            *error         = NULL;
253   GimpExportReturn   export        = GIMP_EXPORT_CANCEL;
254 
255   INIT_I18N ();
256   gegl_init (NULL, NULL);
257 
258   strncpy (xsvals.comment, "Created with GIMP", MAX_COMMENT);
259 
260   run_mode = param[0].data.d_int32;
261 
262   *nreturn_vals = 1;
263   *return_vals  = values;
264 
265   values[0].type          = GIMP_PDB_STATUS;
266   values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR;
267 
268 #ifdef VERBOSE
269   if (verbose)
270     printf ("XBM: RUN %s\n", name);
271 #endif
272 
273   if (strcmp (name, LOAD_PROC) == 0)
274     {
275       image_ID = load_image (param[1].data.d_string, &error);
276 
277       if (image_ID != -1)
278         {
279           *nreturn_vals = 2;
280           values[1].type         = GIMP_PDB_IMAGE;
281           values[1].data.d_image = image_ID;
282         }
283       else
284         {
285           status = GIMP_PDB_EXECUTION_ERROR;
286         }
287     }
288   else if (strcmp (name, SAVE_PROC) == 0)
289     {
290       image_ID    = param[1].data.d_int32;
291       drawable_ID = param[2].data.d_int32;
292 
293       switch (run_mode)
294         {
295         case GIMP_RUN_INTERACTIVE:
296         case GIMP_RUN_WITH_LAST_VALS:
297           gimp_ui_init (PLUG_IN_BINARY, FALSE);
298 
299           export = gimp_export_image (&image_ID, &drawable_ID, "XBM",
300                                       GIMP_EXPORT_CAN_HANDLE_BITMAP |
301                                       GIMP_EXPORT_CAN_HANDLE_ALPHA);
302 
303           if (export == GIMP_EXPORT_CANCEL)
304             {
305               values[0].data.d_status = GIMP_PDB_CANCEL;
306               return;
307             }
308           break;
309 
310         default:
311           break;
312         }
313 
314       switch (run_mode)
315         {
316         case GIMP_RUN_INTERACTIVE:
317         case GIMP_RUN_WITH_LAST_VALS:
318           /*  Possibly retrieve data  */
319           gimp_get_data (SAVE_PROC, &xsvals);
320 
321           /* Always override the prefix with the filename. */
322           mask_basename = g_strdup (init_prefix (param[3].data.d_string));
323           break;
324 
325         case GIMP_RUN_NONINTERACTIVE:
326           /*  Make sure all the required arguments are there!  */
327           if (nparams < 5)
328             {
329               status = GIMP_PDB_CALLING_ERROR;
330             }
331           else
332             {
333               gint i = 5;
334 
335               if (nparams > i)
336                 {
337                   memset (xsvals.comment, 0, sizeof (xsvals.comment));
338                   strncpy (xsvals.comment, param[i].data.d_string,
339                            MAX_COMMENT);
340                 }
341 
342               i ++;
343               if (nparams > i)
344                 xsvals.x10_format = (param[i].data.d_int32) ? TRUE : FALSE;
345 
346               i += 2;
347               if (nparams > i)
348                 {
349                   /* They've asked for a hotspot. */
350                   xsvals.use_hot = TRUE;
351                   xsvals.x_hot = param[i - 1].data.d_int32;
352                   xsvals.y_hot = param[i].data.d_int32;
353                 }
354 
355               mask_basename = g_strdup (init_prefix (param[3].data.d_string));
356 
357               i ++;
358               if (nparams > i)
359                 {
360                   memset (xsvals.prefix, 0, sizeof (xsvals.prefix));
361                   strncpy (xsvals.prefix, param[i].data.d_string,
362                            MAX_PREFIX);
363                 }
364 
365               i += 2;
366               if (nparams > i)
367                 {
368                   xsvals.write_mask = param[i - 1].data.d_int32;
369                   memset (xsvals.mask_ext, 0, sizeof (xsvals.mask_ext));
370                   strncpy (xsvals.mask_ext, param[i].data.d_string,
371                            MAX_MASK_EXT);
372                 }
373 
374               i ++;
375               /* Too many arguments. */
376               if (nparams > i)
377                 status = GIMP_PDB_CALLING_ERROR;
378             }
379           break;
380 
381         default:
382           break;
383         }
384 
385       if (run_mode == GIMP_RUN_INTERACTIVE)
386         {
387           /* Get the parasites */
388           parasite = gimp_image_get_parasite (image_ID, "gimp-comment");
389 
390           if (parasite)
391             {
392               gint size = gimp_parasite_data_size (parasite);
393 
394               strncpy (xsvals.comment,
395                        gimp_parasite_data (parasite), MIN (size, MAX_COMMENT));
396               xsvals.comment[MIN (size, MAX_COMMENT) + 1] = 0;
397 
398               gimp_parasite_free (parasite);
399             }
400 
401           parasite = gimp_image_get_parasite (image_ID, "hot-spot");
402 
403           if (parasite)
404             {
405               gint x, y;
406 
407               if (sscanf (gimp_parasite_data (parasite), "%i %i", &x, &y) == 2)
408                {
409                  xsvals.use_hot = TRUE;
410                  xsvals.x_hot = x;
411                  xsvals.y_hot = y;
412                }
413              gimp_parasite_free (parasite);
414            }
415 
416           /*  Acquire information with a dialog  */
417           if (! save_dialog (drawable_ID))
418             status = GIMP_PDB_CANCEL;
419         }
420 
421       if (status == GIMP_PDB_SUCCESS)
422         {
423           GFile *file = g_file_new_for_uri (param[3].data.d_string);
424           GFile *mask_file;
425           GFile *dir;
426           gchar *mask_prefix;
427           gchar *temp;
428 
429           dir = g_file_get_parent (file);
430           temp = g_strdup_printf ("%s%s.xbm", mask_basename, xsvals.mask_ext);
431 
432           mask_file = g_file_get_child (dir, temp);
433 
434           g_free (temp);
435           g_object_unref (dir);
436 
437           /* Change any non-alphanumeric prefix characters to underscores. */
438           for (temp = xsvals.prefix; *temp; temp++)
439             if (! g_ascii_isalnum (*temp))
440               *temp = '_';
441 
442           mask_prefix = g_strdup_printf ("%s%s",
443                                          xsvals.prefix, xsvals.mask_ext);
444 
445           for (temp = mask_prefix; *temp; temp++)
446             if (! g_ascii_isalnum (*temp))
447               *temp = '_';
448 
449           if (save_image (file,
450                           xsvals.prefix,
451                           xsvals.comment,
452                           FALSE,
453                           image_ID, drawable_ID,
454                           &error)
455 
456               && (! xsvals.write_mask ||
457                   save_image (mask_file,
458                               mask_prefix,
459                               xsvals.comment,
460                               TRUE,
461                               image_ID, drawable_ID,
462                               &error)))
463             {
464               /*  Store xsvals data  */
465               gimp_set_data (SAVE_PROC, &xsvals, sizeof (xsvals));
466             }
467           else
468             {
469               status = GIMP_PDB_EXECUTION_ERROR;
470             }
471 
472           g_free (mask_prefix);
473           g_free (mask_basename);
474 
475           g_object_unref (file);
476           g_object_unref (mask_file);
477         }
478 
479       if (export == GIMP_EXPORT_EXPORT)
480         gimp_image_delete (image_ID);
481     }
482   else
483     {
484       status = GIMP_PDB_CALLING_ERROR;
485     }
486 
487   if (status != GIMP_PDB_SUCCESS && error)
488     {
489       *nreturn_vals = 2;
490       values[1].type          = GIMP_PDB_STRING;
491       values[1].data.d_string = error->message;
492     }
493 
494   values[0].data.d_status = status;
495 }
496 
497 
498 /* Return the value of a digit. */
499 static gint
getval(gint c,gint base)500 getval (gint c,
501         gint base)
502 {
503   const gchar *digits = "0123456789abcdefABCDEF";
504   gint         val;
505 
506   /* Include uppercase hex digits. */
507   if (base == 16)
508     base = 22;
509 
510   /* Find a match. */
511   for (val = 0; val < base; val ++)
512     if (c == digits[val])
513       return (val < 16) ? val : (val - 6);
514   return -1;
515 }
516 
517 
518 /* Get a comment */
519 static gchar *
fgetcomment(FILE * fp)520 fgetcomment (FILE *fp)
521 {
522   GString *str = NULL;
523   gint comment, c;
524 
525   comment = 0;
526   do
527     {
528       c = fgetc (fp);
529       if (comment)
530         {
531           if (c == '*')
532             {
533               /* In a comment, with potential to leave. */
534               comment = 1;
535             }
536           else if (comment == 1 && c == '/')
537             {
538               gchar *retval;
539 
540               /* Leaving a comment. */
541               comment = 0;
542 
543               retval = g_strstrip (g_strdup (str->str));
544               g_string_free (str, TRUE);
545               return retval;
546             }
547           else
548             {
549               /* In a comment, with no potential to leave. */
550               comment = 2;
551               g_string_append_c (str, c);
552             }
553         }
554       else
555         {
556           /* Not in a comment. */
557           if (c == '/')
558             {
559               /* Potential to enter a comment. */
560               c = fgetc (fp);
561               if (c == '*')
562                 {
563                   /* Entered a comment, with no potential to leave. */
564                   comment = 2;
565                   str = g_string_new (NULL);
566                 }
567               else
568                 {
569                   /* put everything back and return */
570                   ungetc (c, fp);
571                   c = '/';
572                   ungetc (c, fp);
573                   return NULL;
574                 }
575             }
576           else if (c != EOF && g_ascii_isspace (c))
577             {
578               /* Skip leading whitespace */
579               continue;
580             }
581         }
582     }
583   while (comment && c != EOF);
584 
585   if (str)
586     g_string_free (str, TRUE);
587 
588   return NULL;
589 }
590 
591 
592 /* Same as fgetc, but skip C-style comments and insert whitespace. */
593 static gint
cpp_fgetc(FILE * fp)594 cpp_fgetc (FILE *fp)
595 {
596   gint comment, c;
597 
598   /* FIXME: insert whitespace as advertised. */
599   comment = 0;
600   do
601     {
602       c = fgetc (fp);
603       if (comment)
604         {
605           if (c == '*')
606             /* In a comment, with potential to leave. */
607             comment = 1;
608           else if (comment == 1 && c == '/')
609             /* Leaving a comment. */
610             comment = 0;
611           else
612             /* In a comment, with no potential to leave. */
613             comment = 2;
614         }
615       else
616         {
617           /* Not in a comment. */
618           if (c == '/')
619             {
620               /* Potential to enter a comment. */
621               c = fgetc (fp);
622               if (c == '*')
623                 /* Entered a comment, with no potential to leave. */
624                 comment = 2;
625               else
626                 {
627                   /* Just a slash in the open. */
628                   ungetc (c, fp);
629                   c = '/';
630                 }
631             }
632         }
633     }
634   while (comment && c != EOF);
635   return c;
636 }
637 
638 
639 /* Match a string with a file. */
640 static gint
match(FILE * fp,const gchar * s)641 match (FILE        *fp,
642        const gchar *s)
643 {
644   gint c;
645 
646   do
647     {
648       c = fgetc (fp);
649       if (c == *s)
650         s ++;
651       else
652         break;
653     }
654   while (c != EOF && *s);
655 
656   if (!*s)
657     return TRUE;
658 
659   if (c != EOF)
660     ungetc (c, fp);
661   return FALSE;
662 }
663 
664 
665 /* Read the next integer from the file, skipping all non-integers. */
666 static gint
get_int(FILE * fp)667 get_int (FILE *fp)
668 {
669   int digval, base, val, c;
670 
671   do
672     c = cpp_fgetc (fp);
673   while (c != EOF && ! g_ascii_isdigit (c));
674 
675   if (c == EOF)
676     return 0;
677 
678   /* Check for the base. */
679   if (c == '0')
680     {
681       c = fgetc (fp);
682       if (c == 'x' || c == 'X')
683         {
684           c = fgetc (fp);
685           base = 16;
686         }
687       else if (g_ascii_isdigit (c))
688         base = 8;
689       else
690         {
691           ungetc (c, fp);
692           return 0;
693         }
694     }
695   else
696     base = 10;
697 
698   val = 0;
699   for (;;)
700     {
701       digval = getval (c, base);
702       if (digval == -1)
703         {
704           ungetc (c, fp);
705           break;
706         }
707       val *= base;
708       val += digval;
709       c = fgetc (fp);
710     }
711 
712   return val;
713 }
714 
715 
716 static gint32
load_image(const gchar * filename,GError ** error)717 load_image (const gchar  *filename,
718             GError      **error)
719 {
720   FILE         *fp;
721   GeglBuffer   *buffer;
722   gint32        image_ID;
723   gint32        layer_ID;
724   guchar       *data;
725   gint          intbits;
726   gint          width  = 0;
727   gint          height = 0;
728   gint          x_hot  = 0;
729   gint          y_hot  = 0;
730   gint          c, i, j, k;
731   gint          tileheight, rowoffset;
732   gchar        *comment;
733 
734   const guchar cmap[] =
735   {
736     0x00, 0x00, 0x00,           /* black */
737     0xff, 0xff, 0xff            /* white */
738   };
739 
740   gimp_progress_init_printf (_("Opening '%s'"),
741                              gimp_filename_to_utf8 (filename));
742 
743   fp = g_fopen (filename, "rb");
744   if (! fp)
745     {
746       g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
747                    _("Could not open '%s' for reading: %s"),
748                    gimp_filename_to_utf8 (filename), g_strerror (errno));
749       return -1;
750     }
751 
752   comment = fgetcomment (fp);
753 
754   /* Loosely parse the header */
755   intbits = height = width = 0;
756   c = ' ';
757   do
758     {
759       if (g_ascii_isspace (c))
760         {
761           if (match (fp, "char"))
762             {
763               c = fgetc (fp);
764               if (g_ascii_isspace (c))
765                 {
766                   intbits = 8;
767                   continue;
768                 }
769             }
770           else if (match (fp, "short"))
771             {
772               c = fgetc (fp);
773               if (g_ascii_isspace (c))
774                 {
775                   intbits = 16;
776                   continue;
777                 }
778             }
779         }
780 
781       if (c == '_')
782         {
783           if (match (fp, "width"))
784             {
785               c = fgetc (fp);
786               if (g_ascii_isspace (c))
787                 {
788                   width = get_int (fp);
789                   continue;
790                 }
791             }
792           else if (match (fp, "height"))
793             {
794               c = fgetc (fp);
795               if (g_ascii_isspace (c))
796                 {
797                   height = get_int (fp);
798                   continue;
799                 }
800             }
801           else if (match (fp, "x_hot"))
802             {
803               c = fgetc (fp);
804               if (g_ascii_isspace (c))
805                 {
806                   x_hot = get_int (fp);
807                   continue;
808                 }
809             }
810           else if (match (fp, "y_hot"))
811             {
812               c = fgetc (fp);
813               if (g_ascii_isspace (c))
814                 {
815                   y_hot = get_int (fp);
816                   continue;
817                 }
818             }
819         }
820 
821       c = cpp_fgetc (fp);
822     }
823   while (c != '{' && c != EOF);
824 
825   if (c == EOF)
826     {
827       g_message (_("'%s':\nCould not read header (ftell == %ld)"),
828                  gimp_filename_to_utf8 (filename), ftell (fp));
829       fclose (fp);
830       return -1;
831     }
832 
833   if (width <= 0)
834     {
835       g_message (_("'%s':\nNo image width specified"),
836                  gimp_filename_to_utf8 (filename));
837       fclose (fp);
838       return -1;
839     }
840 
841   if (width > GIMP_MAX_IMAGE_SIZE)
842     {
843       g_message (_("'%s':\nImage width is larger than GIMP can handle"),
844                  gimp_filename_to_utf8 (filename));
845       fclose (fp);
846       return -1;
847     }
848 
849   if (height <= 0)
850     {
851       g_message (_("'%s':\nNo image height specified"),
852                  gimp_filename_to_utf8 (filename));
853       fclose (fp);
854       return -1;
855     }
856 
857   if (height > GIMP_MAX_IMAGE_SIZE)
858     {
859       g_message (_("'%s':\nImage height is larger than GIMP can handle"),
860                  gimp_filename_to_utf8 (filename));
861       fclose (fp);
862       return -1;
863     }
864 
865   if (intbits == 0)
866     {
867       g_message (_("'%s':\nNo image data type specified"),
868                  gimp_filename_to_utf8 (filename));
869       fclose (fp);
870       return -1;
871     }
872 
873   image_ID = gimp_image_new (width, height, GIMP_INDEXED);
874   gimp_image_set_filename (image_ID, filename);
875 
876   if (comment)
877     {
878       GimpParasite *parasite;
879 
880       parasite = gimp_parasite_new ("gimp-comment",
881                                     GIMP_PARASITE_PERSISTENT,
882                                     strlen (comment) + 1, (gpointer) comment);
883       gimp_image_attach_parasite (image_ID, parasite);
884       gimp_parasite_free (parasite);
885 
886       g_free (comment);
887     }
888 
889   x_hot = CLAMP (x_hot, 0, width);
890   y_hot = CLAMP (y_hot, 0, height);
891 
892   if (x_hot > 0 || y_hot > 0)
893     {
894       GimpParasite *parasite;
895       gchar        *str;
896 
897       str = g_strdup_printf ("%d %d", x_hot, y_hot);
898       parasite = gimp_parasite_new ("hot-spot",
899                                     GIMP_PARASITE_PERSISTENT,
900                                     strlen (str) + 1, (gpointer) str);
901       g_free (str);
902       gimp_image_attach_parasite (image_ID, parasite);
903       gimp_parasite_free (parasite);
904     }
905 
906   /* Set a black-and-white colormap. */
907   gimp_image_set_colormap (image_ID, cmap, 2);
908 
909   layer_ID = gimp_layer_new (image_ID,
910                              _("Background"),
911                              width, height,
912                              GIMP_INDEXED_IMAGE,
913                              100,
914                              gimp_image_get_default_new_layer_mode (image_ID));
915   gimp_image_insert_layer (image_ID, layer_ID, -1, 0);
916 
917   buffer = gimp_drawable_get_buffer (layer_ID);
918 
919   /* Allocate the data. */
920   tileheight = gimp_tile_height ();
921   data = (guchar *) g_malloc (width * tileheight);
922 
923   for (i = 0; i < height; i += tileheight)
924     {
925       tileheight = MIN (tileheight, height - i);
926 
927 #ifdef VERBOSE
928       if (verbose > 1)
929         printf ("XBM: reading %dx(%d+%d) pixel region\n", width, i,
930                 tileheight);
931 #endif
932 
933       /* Parse the data from the file */
934       for (j = 0; j < tileheight; j ++)
935         {
936           /* Read each row. */
937           rowoffset = j * width;
938           for (k = 0; k < width; k ++)
939             {
940               /* Expand each integer into INTBITS pixels. */
941               if (k % intbits == 0)
942                 {
943                   c = get_int (fp);
944 
945                   /* Flip all the bits so that 1's become black and
946                      0's become white. */
947                   c ^= 0xffff;
948                 }
949 
950               data[rowoffset + k] = c & 1;
951               c >>= 1;
952             }
953         }
954 
955       /* Put the data into the image. */
956       gegl_buffer_set (buffer, GEGL_RECTANGLE (0, i, width, tileheight), 0,
957                        NULL, data, GEGL_AUTO_ROWSTRIDE);
958 
959       gimp_progress_update ((double) (i + tileheight) / (double) height);
960     }
961 
962   g_free (data);
963   g_object_unref (buffer);
964   fclose (fp);
965 
966   gimp_progress_update (1.0);
967 
968   return image_ID;
969 }
970 
971 static gboolean
save_image(GFile * file,const gchar * prefix,const gchar * comment,gboolean save_mask,gint32 image_ID,gint32 drawable_ID,GError ** error)972 save_image (GFile        *file,
973             const gchar  *prefix,
974             const gchar  *comment,
975             gboolean      save_mask,
976             gint32        image_ID,
977             gint32        drawable_ID,
978             GError      **error)
979 {
980   GOutputStream *output;
981   GeglBuffer    *buffer;
982   GCancellable  *cancellable;
983   gint           width, height, colors, dark;
984   gint           intbits, lineints, need_comma, nints, rowoffset, tileheight;
985   gint           c, i, j, k, thisbit;
986   gboolean       has_alpha;
987   gint           bpp;
988   guchar        *data = NULL;
989   guchar        *cmap;
990   const gchar   *intfmt;
991 
992 #if 0
993   if (save_mask)
994     g_printerr ("%s: save_mask '%s'\n", G_STRFUNC, prefix);
995   else
996     g_printerr ("%s: save_image '%s'\n", G_STRFUNC, prefix);
997 #endif
998 
999   cmap = gimp_image_get_colormap (image_ID, &colors);
1000 
1001   if (! gimp_drawable_is_indexed (drawable_ID) || colors > 2)
1002     {
1003       /* The image is not black-and-white. */
1004       g_message (_("The image which you are trying to export as "
1005                    "an XBM contains more than two colors.\n\n"
1006                    "Please convert it to a black and white "
1007                    "(1-bit) indexed image and try again."));
1008       g_free (cmap);
1009       return FALSE;
1010     }
1011 
1012   has_alpha = gimp_drawable_has_alpha (drawable_ID);
1013 
1014   if (! has_alpha && save_mask)
1015     {
1016       g_message (_("You cannot save a cursor mask for an image\n"
1017                    "which has no alpha channel."));
1018       return FALSE;
1019     }
1020 
1021   buffer = gimp_drawable_get_buffer (drawable_ID);
1022   width  = gegl_buffer_get_width  (buffer);
1023   height = gegl_buffer_get_height (buffer);
1024   bpp    = gimp_drawable_bpp (drawable_ID);
1025 
1026   /* Figure out which color is black, and which is white. */
1027   dark = 0;
1028   if (colors > 1)
1029     {
1030       gint first, second;
1031 
1032       /* Maybe the second color is darker than the first. */
1033       first  = (cmap[0] * cmap[0]) + (cmap[1] * cmap[1]) + (cmap[2] * cmap[2]);
1034       second = (cmap[3] * cmap[3]) + (cmap[4] * cmap[4]) + (cmap[5] * cmap[5]);
1035 
1036       if (second < first)
1037         dark = 1;
1038     }
1039 
1040   gimp_progress_init_printf (_("Exporting '%s'"),
1041                              gimp_file_get_utf8_name (file));
1042 
1043   output = G_OUTPUT_STREAM (g_file_replace (file,
1044                                             NULL, FALSE, G_FILE_CREATE_NONE,
1045                                             NULL, error));
1046   if (output)
1047     {
1048       GOutputStream *buffered;
1049 
1050       buffered = g_buffered_output_stream_new (output);
1051       g_object_unref (output);
1052 
1053       output = buffered;
1054     }
1055   else
1056     {
1057       return FALSE;
1058     }
1059 
1060   /* Maybe write the image comment. */
1061 #if 0
1062   /* DISABLED - see http://bugzilla.gnome.org/show_bug.cgi?id=82763 */
1063   /* a future version should write the comment at the end of the file */
1064   if (*comment)
1065     {
1066       if (! print (output, error, "/* %s */\n", comment))
1067         goto fail;
1068     }
1069 #endif
1070 
1071   /* Write out the image height and width. */
1072   if (! print (output, error, "#define %s_width %d\n",  prefix, width) ||
1073       ! print (output, error, "#define %s_height %d\n", prefix, height))
1074     goto fail;
1075 
1076   /* Write out the hotspot, if any. */
1077   if (xsvals.use_hot)
1078     {
1079       if (! print (output, error,
1080                    "#define %s_x_hot %d\n", prefix, xsvals.x_hot) ||
1081           ! print (output, error,
1082                    "#define %s_y_hot %d\n", prefix, xsvals.y_hot))
1083         goto fail;
1084     }
1085 
1086   /* Now write the actual data. */
1087   if (xsvals.x10_format)
1088     {
1089       /* We can fit 9 hex shorts on a single line. */
1090       lineints = 9;
1091       intbits = 16;
1092       intfmt = " 0x%04x";
1093     }
1094   else
1095     {
1096       /* We can fit 12 hex chars on a single line. */
1097       lineints = 12;
1098       intbits = 8;
1099       intfmt = " 0x%02x";
1100     }
1101 
1102   if (! print (output, error,
1103                "static %s %s_bits[] = {\n  ",
1104                xsvals.x10_format ? "unsigned short" : "unsigned char", prefix))
1105     goto fail;
1106 
1107   /* Allocate a new set of pixels. */
1108   tileheight = gimp_tile_height ();
1109   data = (guchar *) g_malloc (width * tileheight * bpp);
1110 
1111   /* Write out the integers. */
1112   need_comma = 0;
1113   nints = 0;
1114   for (i = 0; i < height; i += tileheight)
1115     {
1116       /* Get a horizontal slice of the image. */
1117       tileheight = MIN (tileheight, height - i);
1118 
1119       gegl_buffer_get (buffer, GEGL_RECTANGLE (0, i, width, tileheight), 1.0,
1120                        NULL, data,
1121                        GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
1122 
1123 #ifdef VERBOSE
1124       if (verbose > 1)
1125         printf ("XBM: writing %dx(%d+%d) pixel region\n",
1126                 width, i, tileheight);
1127 #endif
1128 
1129       for (j = 0; j < tileheight; j ++)
1130         {
1131           /* Write out a row at a time. */
1132           rowoffset = j * width * bpp;
1133           c = 0;
1134           thisbit = 0;
1135 
1136           for (k = 0; k < width * bpp; k += bpp)
1137             {
1138               if (k != 0 && thisbit == intbits)
1139                 {
1140                   /* Output a completed integer. */
1141                   if (need_comma)
1142                     {
1143                       if (! print (output, error, ","))
1144                         goto fail;
1145                     }
1146 
1147                   need_comma = 1;
1148 
1149                   /* Maybe start a new line. */
1150                   if (nints ++ >= lineints)
1151                     {
1152                       nints = 1;
1153 
1154                       if (! print (output, error, "\n  "))
1155                         goto fail;
1156                     }
1157 
1158                   if (! print (output, error, intfmt, c))
1159                     goto fail;
1160 
1161                   /* Start a new integer. */
1162                   c = 0;
1163                   thisbit = 0;
1164                 }
1165 
1166               /* Pack INTBITS pixels into an integer. */
1167               if (save_mask)
1168                 {
1169                   c |= ((data[rowoffset + k + 1] < 128) ? 0 : 1) << (thisbit ++);
1170                 }
1171               else
1172                 {
1173                   if (has_alpha && (data[rowoffset + k + 1] < 128))
1174                     c |= 0 << (thisbit ++);
1175                   else
1176                     c |= ((data[rowoffset + k] == dark) ? 1 : 0) << (thisbit ++);
1177                 }
1178             }
1179 
1180           if (thisbit != 0)
1181             {
1182               /* Write out the last oddball int. */
1183               if (need_comma)
1184                 {
1185                   if (! print (output, error, ","))
1186                     goto fail;
1187                 }
1188 
1189               need_comma = 1;
1190 
1191               /* Maybe start a new line. */
1192               if (nints ++ == lineints)
1193                 {
1194                   nints = 1;
1195 
1196                   if (! print (output, error, "\n  "))
1197                     goto fail;
1198                 }
1199 
1200               if (! print (output, error, intfmt, c))
1201                 goto fail;
1202             }
1203         }
1204 
1205       gimp_progress_update ((double) (i + tileheight) / (double) height);
1206     }
1207 
1208   /* Write the trailer. */
1209   if (! print (output, error, " };\n"))
1210     goto fail;
1211 
1212   if (! g_output_stream_close (output, NULL, error))
1213     goto fail;
1214 
1215   g_free (data);
1216   g_object_unref (buffer);
1217   g_object_unref (output);
1218 
1219   gimp_progress_update (1.0);
1220 
1221   return TRUE;
1222 
1223  fail:
1224 
1225   cancellable = g_cancellable_new ();
1226   g_cancellable_cancel (cancellable);
1227   g_output_stream_close (output, cancellable, NULL);
1228   g_object_unref (cancellable);
1229 
1230   g_free (data);
1231   g_object_unref (buffer);
1232   g_object_unref (output);
1233 
1234   return FALSE;
1235 }
1236 
1237 static gboolean
save_dialog(gint32 drawable_ID)1238 save_dialog (gint32 drawable_ID)
1239 {
1240   GtkWidget     *dialog;
1241   GtkWidget     *frame;
1242   GtkWidget     *vbox;
1243   GtkWidget     *toggle;
1244   GtkWidget     *table;
1245   GtkWidget     *entry;
1246   GtkWidget     *spinbutton;
1247   GtkAdjustment *adj;
1248   gboolean       run;
1249 
1250   dialog = gimp_export_dialog_new (_("XBM"), PLUG_IN_BINARY, SAVE_PROC);
1251 
1252   /* parameter settings */
1253   frame = gimp_frame_new (_("XBM Options"));
1254   gtk_container_set_border_width (GTK_CONTAINER (frame), 12);
1255   gtk_box_pack_start (GTK_BOX (gimp_export_dialog_get_content_area (dialog)),
1256                       frame, TRUE, TRUE, 0);
1257   gtk_widget_show (frame);
1258 
1259   vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
1260   gtk_container_add (GTK_CONTAINER (frame), vbox);
1261 
1262   /*  X10 format  */
1263   toggle = gtk_check_button_new_with_mnemonic (_("_X10 format bitmap"));
1264   gtk_box_pack_start (GTK_BOX (vbox), toggle, FALSE, FALSE, 0);
1265   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), xsvals.x10_format);
1266   gtk_widget_show (toggle);
1267 
1268   g_signal_connect (toggle, "toggled",
1269                     G_CALLBACK (gimp_toggle_button_update),
1270                     &xsvals.x10_format);
1271 
1272   table = gtk_table_new (2, 2, FALSE);
1273   gtk_table_set_col_spacings (GTK_TABLE (table), 6);
1274   gtk_table_set_row_spacings (GTK_TABLE (table), 6);
1275   gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 0);
1276   gtk_widget_show (table);
1277 
1278   /* prefix */
1279   entry = gtk_entry_new ();
1280   gtk_entry_set_max_length (GTK_ENTRY (entry), MAX_PREFIX);
1281   gtk_entry_set_text (GTK_ENTRY (entry), xsvals.prefix);
1282   gimp_table_attach_aligned (GTK_TABLE (table), 0, 0,
1283                              _("_Identifier prefix:"), 0.0, 0.5,
1284                              entry, 1, TRUE);
1285   g_signal_connect (entry, "changed",
1286                     G_CALLBACK (prefix_entry_callback),
1287                     NULL);
1288 
1289   /* comment string. */
1290 #if 0
1291   /* DISABLED - see http://bugzilla.gnome.org/show_bug.cgi?id=82763 */
1292   entry = gtk_entry_new ();
1293   gtk_entry_set_max_length (GTK_ENTRY (entry), MAX_COMMENT);
1294   gtk_widget_set_size_request (entry, 240, -1);
1295   gtk_entry_set_text (GTK_ENTRY (entry), xsvals.comment);
1296   gimp_table_attach_aligned (GTK_TABLE (table), 0, 1,
1297                              _("Comment:"), 0.0, 0.5,
1298                              entry, 1, TRUE);
1299   g_signal_connect (entry, "changed",
1300                     G_CALLBACK (comment_entry_callback),
1301                     NULL);
1302 #endif
1303 
1304   /* hotspot toggle */
1305   toggle = gtk_check_button_new_with_mnemonic (_("_Write hot spot values"));
1306   gtk_box_pack_start (GTK_BOX (vbox), toggle, FALSE, FALSE, 0);
1307   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), xsvals.use_hot);
1308   gtk_widget_show (toggle);
1309 
1310   g_signal_connect (toggle, "toggled",
1311                     G_CALLBACK (gimp_toggle_button_update),
1312                     &xsvals.use_hot);
1313 
1314   table = gtk_table_new (2, 2, FALSE);
1315   gtk_table_set_col_spacings (GTK_TABLE (table), 6);
1316   gtk_table_set_row_spacings (GTK_TABLE (table), 6);
1317   gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 0);
1318   gtk_widget_show (table);
1319 
1320   g_object_bind_property (toggle, "active",
1321                           table,  "sensitive",
1322                           G_BINDING_SYNC_CREATE);
1323 
1324   adj = (GtkAdjustment *)
1325     gtk_adjustment_new (xsvals.x_hot, 0,
1326                         gimp_drawable_width (drawable_ID) - 1,
1327                         1, 10, 0);
1328   spinbutton = gimp_spin_button_new (adj, 1.0, 0);
1329   gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE);
1330   gimp_table_attach_aligned (GTK_TABLE (table), 0, 0,
1331                              _("Hot spot _X:"), 0.0, 0.5,
1332                              spinbutton, 1, TRUE);
1333 
1334   g_signal_connect (adj, "value-changed",
1335                     G_CALLBACK (gimp_int_adjustment_update),
1336                     &xsvals.x_hot);
1337 
1338   adj = (GtkAdjustment *)
1339     gtk_adjustment_new (xsvals.y_hot, 0,
1340                         gimp_drawable_height (drawable_ID) - 1,
1341                         1, 10, 0);
1342   spinbutton = gimp_spin_button_new (adj, 1.0, 0);
1343   gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE);
1344   gimp_table_attach_aligned (GTK_TABLE (table), 0, 1,
1345                              _("Hot spot _Y:"), 0.0, 0.5,
1346                              spinbutton, 1, TRUE);
1347 
1348   g_signal_connect (adj, "value-changed",
1349                     G_CALLBACK (gimp_int_adjustment_update),
1350                     &xsvals.y_hot);
1351 
1352   /* mask file */
1353   frame = gimp_frame_new (_("Mask File"));
1354   gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
1355   gtk_widget_show (frame);
1356 
1357   table = gtk_table_new (2, 2, FALSE);
1358   gtk_table_set_col_spacings (GTK_TABLE (table), 6);
1359   gtk_table_set_row_spacings (GTK_TABLE (table), 6);
1360   gtk_container_add (GTK_CONTAINER (frame), table);
1361   gtk_widget_show (table);
1362 
1363   toggle = gtk_check_button_new_with_mnemonic (_("W_rite extra mask file"));
1364   gtk_table_attach_defaults (GTK_TABLE (table), toggle, 0, 2, 0, 1);
1365   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), xsvals.write_mask);
1366   gtk_widget_show (toggle);
1367 
1368   g_signal_connect (toggle, "toggled",
1369                     G_CALLBACK (gimp_toggle_button_update),
1370                     &xsvals.write_mask);
1371 
1372   entry = gtk_entry_new ();
1373   gtk_entry_set_max_length (GTK_ENTRY (entry), MAX_MASK_EXT);
1374   gtk_entry_set_text (GTK_ENTRY (entry), xsvals.mask_ext);
1375   gimp_table_attach_aligned (GTK_TABLE (table), 0, 1,
1376                              _("_Mask file extension:"), 0.0, 0.5,
1377                              entry, 1, TRUE);
1378   g_signal_connect (entry, "changed",
1379                     G_CALLBACK (mask_ext_entry_callback),
1380                     NULL);
1381 
1382   g_object_bind_property (toggle, "active",
1383                           entry,  "sensitive",
1384                           G_BINDING_SYNC_CREATE);
1385 
1386   gtk_widget_set_sensitive (frame, gimp_drawable_has_alpha (drawable_ID));
1387 
1388   /* Done. */
1389   gtk_widget_show (vbox);
1390   gtk_widget_show (dialog);
1391 
1392   run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK);
1393 
1394   gtk_widget_destroy (dialog);
1395 
1396   return run;
1397 }
1398 
1399 static gboolean
print(GOutputStream * output,GError ** error,const gchar * format,...)1400 print (GOutputStream  *output,
1401        GError        **error,
1402        const gchar    *format,
1403        ...)
1404 {
1405   va_list  args;
1406   gboolean success;
1407 
1408   va_start (args, format);
1409   success = g_output_stream_vprintf (output, NULL, NULL,
1410                                      error, format, args);
1411   va_end (args);
1412 
1413   return success;
1414 }
1415 
1416 /* Update the comment string. */
1417 #if 0
1418 /* DISABLED - see http://bugzilla.gnome.org/show_bug.cgi?id=82763 */
1419 static void
1420 comment_entry_callback (GtkWidget *widget,
1421                         gpointer   data)
1422 {
1423   memset (xsvals.comment, 0, sizeof (xsvals.comment));
1424   strncpy (xsvals.comment,
1425            gtk_entry_get_text (GTK_ENTRY (widget)), MAX_COMMENT);
1426 }
1427 #endif
1428 
1429 static void
prefix_entry_callback(GtkWidget * widget,gpointer data)1430 prefix_entry_callback (GtkWidget *widget,
1431                        gpointer   data)
1432 {
1433   memset (xsvals.prefix, 0, sizeof (xsvals.prefix));
1434   strncpy (xsvals.prefix,
1435            gtk_entry_get_text (GTK_ENTRY (widget)), MAX_PREFIX);
1436 }
1437 
1438 static void
mask_ext_entry_callback(GtkWidget * widget,gpointer data)1439 mask_ext_entry_callback (GtkWidget *widget,
1440                        gpointer   data)
1441 {
1442   memset (xsvals.mask_ext, 0, sizeof (xsvals.mask_ext));
1443   strncpy (xsvals.mask_ext,
1444            gtk_entry_get_text (GTK_ENTRY (widget)), MAX_MASK_EXT);
1445 }
1446