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