summaryrefslogtreecommitdiffstats
path: root/plug-ins/common/file-xbm.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--plug-ins/common/file-xbm.c1445
1 files changed, 1445 insertions, 0 deletions
diff --git a/plug-ins/common/file-xbm.c b/plug-ins/common/file-xbm.c
new file mode 100644
index 0000000..5ab3b00
--- /dev/null
+++ b/plug-ins/common/file-xbm.c
@@ -0,0 +1,1445 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * X10 and X11 bitmap (XBM) loading and exporting file filter for GIMP.
+ * XBM code Copyright (C) 1998 Gordon Matzigkeit
+ *
+ * The XBM reading and writing code was written from scratch by Gordon
+ * Matzigkeit <gord@gnu.org> based on the XReadBitmapFile(3X11) manual
+ * page distributed with X11R6 and by staring at valid XBM files. It
+ * does not contain any code written for other XBM file loaders.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/* Release 1.0, 1998-02-04, Gordon Matzigkeit <gord@gnu.org>:
+ * - Load and save X10 and X11 bitmaps.
+ * - Allow the user to specify the C identifier prefix.
+ *
+ * TODO:
+ * - Parsing is very tolerant, and the algorithms are quite hairy, so
+ * load_image should be carefully tested to make sure there are no XBM's
+ * that fail.
+ */
+
+/* Set this for debugging. */
+/* #define VERBOSE 2 */
+
+#include "config.h"
+
+#include <errno.h>
+#include <string.h>
+
+#include <glib/gstdio.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#include "libgimp/stdplugins-intl.h"
+
+
+#define LOAD_PROC "file-xbm-load"
+#define SAVE_PROC "file-xbm-save"
+#define PLUG_IN_BINARY "file-xbm"
+#define PLUG_IN_ROLE "gimp-file-xbm"
+
+
+/* Wear your GIMP with pride! */
+#define DEFAULT_USE_COMMENT TRUE
+#define MAX_COMMENT 72
+#define MAX_MASK_EXT 32
+
+/* C identifier prefix. */
+#define DEFAULT_PREFIX "bitmap"
+#define MAX_PREFIX 64
+
+/* Whether or not to export as X10 bitmap. */
+#define DEFAULT_X10_FORMAT FALSE
+
+typedef struct _XBMSaveVals
+{
+ gchar comment[MAX_COMMENT + 1];
+ gint x10_format;
+ gint use_hot;
+ gint x_hot;
+ gint y_hot;
+ gchar prefix[MAX_PREFIX + 1];
+ gboolean write_mask;
+ gchar mask_ext[MAX_MASK_EXT + 1];
+} XBMSaveVals;
+
+static XBMSaveVals xsvals =
+{
+ "###", /* comment */
+ DEFAULT_X10_FORMAT, /* x10_format */
+ FALSE,
+ 0, /* x_hot */
+ 0, /* y_hot */
+ DEFAULT_PREFIX, /* prefix */
+ FALSE, /* write_mask */
+ "-mask"
+};
+
+
+/* Declare some local functions.
+ */
+static void query (void);
+static void run (const gchar *name,
+ gint nparams,
+ const GimpParam *param,
+ gint *nreturn_vals,
+ GimpParam **return_vals);
+
+static gint32 load_image (const gchar *filename,
+ GError **error);
+static gint save_image (GFile *file,
+ const gchar *prefix,
+ const gchar *comment,
+ gboolean save_mask,
+ gint32 image_ID,
+ gint32 drawable_ID,
+ GError **error);
+static gboolean save_dialog (gint32 drawable_ID);
+
+static gboolean print (GOutputStream *output,
+ GError **error,
+ const gchar *format,
+ ...) G_GNUC_PRINTF (3, 4);
+
+#if 0
+/* DISABLED - see http://bugzilla.gnome.org/show_bug.cgi?id=82763 */
+static void comment_entry_callback (GtkWidget *widget,
+ gpointer data);
+#endif
+static void prefix_entry_callback (GtkWidget *widget,
+ gpointer data);
+static void mask_ext_entry_callback (GtkWidget *widget,
+ gpointer data);
+
+
+const GimpPlugInInfo PLUG_IN_INFO =
+{
+ NULL, /* init_proc */
+ NULL, /* quit_proc */
+ query, /* query_proc */
+ run, /* run_proc */
+};
+
+MAIN ()
+
+#ifdef VERBOSE
+static int verbose = VERBOSE;
+#endif
+
+
+static void
+query (void)
+{
+ static const GimpParamDef load_args[] =
+ {
+ { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" },
+ { GIMP_PDB_STRING, "filename", "The name of the file to load" },
+ { GIMP_PDB_STRING, "raw-filename", "The name entered" }
+ };
+
+ static const GimpParamDef load_return_vals[] =
+ {
+ { GIMP_PDB_IMAGE, "image", "Output image" }
+ };
+
+ static const GimpParamDef save_args[] =
+ {
+ { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" },
+ { GIMP_PDB_IMAGE, "image", "Input image" },
+ { GIMP_PDB_DRAWABLE, "drawable", "Drawable to export" },
+ { GIMP_PDB_STRING, "filename", "The name of the file to export" },
+ { GIMP_PDB_STRING, "raw-filename", "The name entered" },
+ { GIMP_PDB_STRING, "comment", "Image description (maximum 72 bytes)" },
+ { GIMP_PDB_INT32, "x10", "Export in X10 format" },
+ { GIMP_PDB_INT32, "x-hot", "X coordinate of hotspot" },
+ { GIMP_PDB_INT32, "y-hot", "Y coordinate of hotspot" },
+ { GIMP_PDB_STRING, "prefix", "Identifier prefix [determined from filename]"},
+ { GIMP_PDB_INT32, "write-mask", "(0 = ignore, 1 = save as extra file)" },
+ { GIMP_PDB_STRING, "mask-extension", "Extension of the mask file" }
+ } ;
+
+ gimp_install_procedure (LOAD_PROC,
+ "Load a file in X10 or X11 bitmap (XBM) file format",
+ "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.",
+ "Gordon Matzigkeit",
+ "Gordon Matzigkeit",
+ "1998",
+ N_("X BitMap image"),
+ NULL,
+ GIMP_PLUGIN,
+ G_N_ELEMENTS (load_args),
+ G_N_ELEMENTS (load_return_vals),
+ load_args, load_return_vals);
+
+ gimp_register_file_handler_mime (LOAD_PROC, "image/x-xbitmap");
+ gimp_register_load_handler (LOAD_PROC,
+ "xbm,icon,bitmap",
+ "");
+
+ gimp_install_procedure (SAVE_PROC,
+ "Export a file in X10 or X11 bitmap (XBM) file format",
+ "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.",
+ "Gordon Matzigkeit",
+ "Gordon Matzigkeit",
+ "1998",
+ N_("X BitMap image"),
+ "INDEXED",
+ GIMP_PLUGIN,
+ G_N_ELEMENTS (save_args), 0,
+ save_args, NULL);
+
+ gimp_register_file_handler_mime (SAVE_PROC, "image/x-xbitmap");
+ gimp_register_file_handler_uri (SAVE_PROC);
+ gimp_register_save_handler (SAVE_PROC, "xbm,icon,bitmap", "");
+}
+
+static gchar *
+init_prefix (const gchar *filename)
+{
+ gchar *p, *prefix;
+ gint len;
+
+ prefix = g_path_get_basename (filename);
+
+ memset (xsvals.prefix, 0, sizeof (xsvals.prefix));
+
+ if (prefix)
+ {
+ /* Strip any extension. */
+ p = strrchr (prefix, '.');
+ if (p && p != prefix)
+ len = MIN (MAX_PREFIX, p - prefix);
+ else
+ len = MAX_PREFIX;
+
+ strncpy (xsvals.prefix, prefix, len);
+ g_free (prefix);
+ }
+
+ return xsvals.prefix;
+}
+
+static void
+run (const gchar *name,
+ gint nparams,
+ const GimpParam *param,
+ gint *nreturn_vals,
+ GimpParam **return_vals)
+{
+ static GimpParam values[2];
+ GimpRunMode run_mode;
+ GimpPDBStatusType status = GIMP_PDB_SUCCESS;
+ gint32 image_ID;
+ gint32 drawable_ID;
+ GimpParasite *parasite = NULL;
+ gchar *mask_basename = NULL;
+ GError *error = NULL;
+ GimpExportReturn export = GIMP_EXPORT_CANCEL;
+
+ INIT_I18N ();
+ gegl_init (NULL, NULL);
+
+ strncpy (xsvals.comment, "Created with GIMP", MAX_COMMENT);
+
+ run_mode = param[0].data.d_int32;
+
+ *nreturn_vals = 1;
+ *return_vals = values;
+
+ values[0].type = GIMP_PDB_STATUS;
+ values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR;
+
+#ifdef VERBOSE
+ if (verbose)
+ printf ("XBM: RUN %s\n", name);
+#endif
+
+ if (strcmp (name, LOAD_PROC) == 0)
+ {
+ image_ID = load_image (param[1].data.d_string, &error);
+
+ if (image_ID != -1)
+ {
+ *nreturn_vals = 2;
+ values[1].type = GIMP_PDB_IMAGE;
+ values[1].data.d_image = image_ID;
+ }
+ else
+ {
+ status = GIMP_PDB_EXECUTION_ERROR;
+ }
+ }
+ else if (strcmp (name, SAVE_PROC) == 0)
+ {
+ image_ID = param[1].data.d_int32;
+ drawable_ID = param[2].data.d_int32;
+
+ switch (run_mode)
+ {
+ case GIMP_RUN_INTERACTIVE:
+ case GIMP_RUN_WITH_LAST_VALS:
+ gimp_ui_init (PLUG_IN_BINARY, FALSE);
+
+ export = gimp_export_image (&image_ID, &drawable_ID, "XBM",
+ GIMP_EXPORT_CAN_HANDLE_BITMAP |
+ GIMP_EXPORT_CAN_HANDLE_ALPHA);
+
+ if (export == GIMP_EXPORT_CANCEL)
+ {
+ values[0].data.d_status = GIMP_PDB_CANCEL;
+ return;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ switch (run_mode)
+ {
+ case GIMP_RUN_INTERACTIVE:
+ case GIMP_RUN_WITH_LAST_VALS:
+ /* Possibly retrieve data */
+ gimp_get_data (SAVE_PROC, &xsvals);
+
+ /* Always override the prefix with the filename. */
+ mask_basename = g_strdup (init_prefix (param[3].data.d_string));
+ break;
+
+ case GIMP_RUN_NONINTERACTIVE:
+ /* Make sure all the required arguments are there! */
+ if (nparams < 5)
+ {
+ status = GIMP_PDB_CALLING_ERROR;
+ }
+ else
+ {
+ gint i = 5;
+
+ if (nparams > i)
+ {
+ memset (xsvals.comment, 0, sizeof (xsvals.comment));
+ strncpy (xsvals.comment, param[i].data.d_string,
+ MAX_COMMENT);
+ }
+
+ i ++;
+ if (nparams > i)
+ xsvals.x10_format = (param[i].data.d_int32) ? TRUE : FALSE;
+
+ i += 2;
+ if (nparams > i)
+ {
+ /* They've asked for a hotspot. */
+ xsvals.use_hot = TRUE;
+ xsvals.x_hot = param[i - 1].data.d_int32;
+ xsvals.y_hot = param[i].data.d_int32;
+ }
+
+ mask_basename = g_strdup (init_prefix (param[3].data.d_string));
+
+ i ++;
+ if (nparams > i)
+ {
+ memset (xsvals.prefix, 0, sizeof (xsvals.prefix));
+ strncpy (xsvals.prefix, param[i].data.d_string,
+ MAX_PREFIX);
+ }
+
+ i += 2;
+ if (nparams > i)
+ {
+ xsvals.write_mask = param[i - 1].data.d_int32;
+ memset (xsvals.mask_ext, 0, sizeof (xsvals.mask_ext));
+ strncpy (xsvals.mask_ext, param[i].data.d_string,
+ MAX_MASK_EXT);
+ }
+
+ i ++;
+ /* Too many arguments. */
+ if (nparams > i)
+ status = GIMP_PDB_CALLING_ERROR;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ if (run_mode == GIMP_RUN_INTERACTIVE)
+ {
+ /* Get the parasites */
+ parasite = gimp_image_get_parasite (image_ID, "gimp-comment");
+
+ if (parasite)
+ {
+ gint size = gimp_parasite_data_size (parasite);
+
+ strncpy (xsvals.comment,
+ gimp_parasite_data (parasite), MIN (size, MAX_COMMENT));
+ xsvals.comment[MIN (size, MAX_COMMENT) + 1] = 0;
+
+ gimp_parasite_free (parasite);
+ }
+
+ parasite = gimp_image_get_parasite (image_ID, "hot-spot");
+
+ if (parasite)
+ {
+ gint x, y;
+
+ if (sscanf (gimp_parasite_data (parasite), "%i %i", &x, &y) == 2)
+ {
+ xsvals.use_hot = TRUE;
+ xsvals.x_hot = x;
+ xsvals.y_hot = y;
+ }
+ gimp_parasite_free (parasite);
+ }
+
+ /* Acquire information with a dialog */
+ if (! save_dialog (drawable_ID))
+ status = GIMP_PDB_CANCEL;
+ }
+
+ if (status == GIMP_PDB_SUCCESS)
+ {
+ GFile *file = g_file_new_for_uri (param[3].data.d_string);
+ GFile *mask_file;
+ GFile *dir;
+ gchar *mask_prefix;
+ gchar *temp;
+
+ dir = g_file_get_parent (file);
+ temp = g_strdup_printf ("%s%s.xbm", mask_basename, xsvals.mask_ext);
+
+ mask_file = g_file_get_child (dir, temp);
+
+ g_free (temp);
+ g_object_unref (dir);
+
+ /* Change any non-alphanumeric prefix characters to underscores. */
+ for (temp = xsvals.prefix; *temp; temp++)
+ if (! g_ascii_isalnum (*temp))
+ *temp = '_';
+
+ mask_prefix = g_strdup_printf ("%s%s",
+ xsvals.prefix, xsvals.mask_ext);
+
+ for (temp = mask_prefix; *temp; temp++)
+ if (! g_ascii_isalnum (*temp))
+ *temp = '_';
+
+ if (save_image (file,
+ xsvals.prefix,
+ xsvals.comment,
+ FALSE,
+ image_ID, drawable_ID,
+ &error)
+
+ && (! xsvals.write_mask ||
+ save_image (mask_file,
+ mask_prefix,
+ xsvals.comment,
+ TRUE,
+ image_ID, drawable_ID,
+ &error)))
+ {
+ /* Store xsvals data */
+ gimp_set_data (SAVE_PROC, &xsvals, sizeof (xsvals));
+ }
+ else
+ {
+ status = GIMP_PDB_EXECUTION_ERROR;
+ }
+
+ g_free (mask_prefix);
+ g_free (mask_basename);
+
+ g_object_unref (file);
+ g_object_unref (mask_file);
+ }
+
+ if (export == GIMP_EXPORT_EXPORT)
+ gimp_image_delete (image_ID);
+ }
+ else
+ {
+ status = GIMP_PDB_CALLING_ERROR;
+ }
+
+ if (status != GIMP_PDB_SUCCESS && error)
+ {
+ *nreturn_vals = 2;
+ values[1].type = GIMP_PDB_STRING;
+ values[1].data.d_string = error->message;
+ }
+
+ values[0].data.d_status = status;
+}
+
+
+/* Return the value of a digit. */
+static gint
+getval (gint c,
+ gint base)
+{
+ const gchar *digits = "0123456789abcdefABCDEF";
+ gint val;
+
+ /* Include uppercase hex digits. */
+ if (base == 16)
+ base = 22;
+
+ /* Find a match. */
+ for (val = 0; val < base; val ++)
+ if (c == digits[val])
+ return (val < 16) ? val : (val - 6);
+ return -1;
+}
+
+
+/* Get a comment */
+static gchar *
+fgetcomment (FILE *fp)
+{
+ GString *str = NULL;
+ gint comment, c;
+
+ comment = 0;
+ do
+ {
+ c = fgetc (fp);
+ if (comment)
+ {
+ if (c == '*')
+ {
+ /* In a comment, with potential to leave. */
+ comment = 1;
+ }
+ else if (comment == 1 && c == '/')
+ {
+ gchar *retval;
+
+ /* Leaving a comment. */
+ comment = 0;
+
+ retval = g_strstrip (g_strdup (str->str));
+ g_string_free (str, TRUE);
+ return retval;
+ }
+ else
+ {
+ /* In a comment, with no potential to leave. */
+ comment = 2;
+ g_string_append_c (str, c);
+ }
+ }
+ else
+ {
+ /* Not in a comment. */
+ if (c == '/')
+ {
+ /* Potential to enter a comment. */
+ c = fgetc (fp);
+ if (c == '*')
+ {
+ /* Entered a comment, with no potential to leave. */
+ comment = 2;
+ str = g_string_new (NULL);
+ }
+ else
+ {
+ /* put everything back and return */
+ ungetc (c, fp);
+ c = '/';
+ ungetc (c, fp);
+ return NULL;
+ }
+ }
+ else if (c != EOF && g_ascii_isspace (c))
+ {
+ /* Skip leading whitespace */
+ continue;
+ }
+ }
+ }
+ while (comment && c != EOF);
+
+ if (str)
+ g_string_free (str, TRUE);
+
+ return NULL;
+}
+
+
+/* Same as fgetc, but skip C-style comments and insert whitespace. */
+static gint
+cpp_fgetc (FILE *fp)
+{
+ gint comment, c;
+
+ /* FIXME: insert whitespace as advertised. */
+ comment = 0;
+ do
+ {
+ c = fgetc (fp);
+ if (comment)
+ {
+ if (c == '*')
+ /* In a comment, with potential to leave. */
+ comment = 1;
+ else if (comment == 1 && c == '/')
+ /* Leaving a comment. */
+ comment = 0;
+ else
+ /* In a comment, with no potential to leave. */
+ comment = 2;
+ }
+ else
+ {
+ /* Not in a comment. */
+ if (c == '/')
+ {
+ /* Potential to enter a comment. */
+ c = fgetc (fp);
+ if (c == '*')
+ /* Entered a comment, with no potential to leave. */
+ comment = 2;
+ else
+ {
+ /* Just a slash in the open. */
+ ungetc (c, fp);
+ c = '/';
+ }
+ }
+ }
+ }
+ while (comment && c != EOF);
+ return c;
+}
+
+
+/* Match a string with a file. */
+static gint
+match (FILE *fp,
+ const gchar *s)
+{
+ gint c;
+
+ do
+ {
+ c = fgetc (fp);
+ if (c == *s)
+ s ++;
+ else
+ break;
+ }
+ while (c != EOF && *s);
+
+ if (!*s)
+ return TRUE;
+
+ if (c != EOF)
+ ungetc (c, fp);
+ return FALSE;
+}
+
+
+/* Read the next integer from the file, skipping all non-integers. */
+static gint
+get_int (FILE *fp)
+{
+ int digval, base, val, c;
+
+ do
+ c = cpp_fgetc (fp);
+ while (c != EOF && ! g_ascii_isdigit (c));
+
+ if (c == EOF)
+ return 0;
+
+ /* Check for the base. */
+ if (c == '0')
+ {
+ c = fgetc (fp);
+ if (c == 'x' || c == 'X')
+ {
+ c = fgetc (fp);
+ base = 16;
+ }
+ else if (g_ascii_isdigit (c))
+ base = 8;
+ else
+ {
+ ungetc (c, fp);
+ return 0;
+ }
+ }
+ else
+ base = 10;
+
+ val = 0;
+ for (;;)
+ {
+ digval = getval (c, base);
+ if (digval == -1)
+ {
+ ungetc (c, fp);
+ break;
+ }
+ val *= base;
+ val += digval;
+ c = fgetc (fp);
+ }
+
+ return val;
+}
+
+
+static gint32
+load_image (const gchar *filename,
+ GError **error)
+{
+ FILE *fp;
+ GeglBuffer *buffer;
+ gint32 image_ID;
+ gint32 layer_ID;
+ guchar *data;
+ gint intbits;
+ gint width = 0;
+ gint height = 0;
+ gint x_hot = 0;
+ gint y_hot = 0;
+ gint c, i, j, k;
+ gint tileheight, rowoffset;
+ gchar *comment;
+
+ const guchar cmap[] =
+ {
+ 0x00, 0x00, 0x00, /* black */
+ 0xff, 0xff, 0xff /* white */
+ };
+
+ gimp_progress_init_printf (_("Opening '%s'"),
+ gimp_filename_to_utf8 (filename));
+
+ fp = g_fopen (filename, "rb");
+ if (! fp)
+ {
+ g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
+ _("Could not open '%s' for reading: %s"),
+ gimp_filename_to_utf8 (filename), g_strerror (errno));
+ return -1;
+ }
+
+ comment = fgetcomment (fp);
+
+ /* Loosely parse the header */
+ intbits = height = width = 0;
+ c = ' ';
+ do
+ {
+ if (g_ascii_isspace (c))
+ {
+ if (match (fp, "char"))
+ {
+ c = fgetc (fp);
+ if (g_ascii_isspace (c))
+ {
+ intbits = 8;
+ continue;
+ }
+ }
+ else if (match (fp, "short"))
+ {
+ c = fgetc (fp);
+ if (g_ascii_isspace (c))
+ {
+ intbits = 16;
+ continue;
+ }
+ }
+ }
+
+ if (c == '_')
+ {
+ if (match (fp, "width"))
+ {
+ c = fgetc (fp);
+ if (g_ascii_isspace (c))
+ {
+ width = get_int (fp);
+ continue;
+ }
+ }
+ else if (match (fp, "height"))
+ {
+ c = fgetc (fp);
+ if (g_ascii_isspace (c))
+ {
+ height = get_int (fp);
+ continue;
+ }
+ }
+ else if (match (fp, "x_hot"))
+ {
+ c = fgetc (fp);
+ if (g_ascii_isspace (c))
+ {
+ x_hot = get_int (fp);
+ continue;
+ }
+ }
+ else if (match (fp, "y_hot"))
+ {
+ c = fgetc (fp);
+ if (g_ascii_isspace (c))
+ {
+ y_hot = get_int (fp);
+ continue;
+ }
+ }
+ }
+
+ c = cpp_fgetc (fp);
+ }
+ while (c != '{' && c != EOF);
+
+ if (c == EOF)
+ {
+ g_message (_("'%s':\nCould not read header (ftell == %ld)"),
+ gimp_filename_to_utf8 (filename), ftell (fp));
+ fclose (fp);
+ return -1;
+ }
+
+ if (width <= 0)
+ {
+ g_message (_("'%s':\nNo image width specified"),
+ gimp_filename_to_utf8 (filename));
+ fclose (fp);
+ return -1;
+ }
+
+ if (width > GIMP_MAX_IMAGE_SIZE)
+ {
+ g_message (_("'%s':\nImage width is larger than GIMP can handle"),
+ gimp_filename_to_utf8 (filename));
+ fclose (fp);
+ return -1;
+ }
+
+ if (height <= 0)
+ {
+ g_message (_("'%s':\nNo image height specified"),
+ gimp_filename_to_utf8 (filename));
+ fclose (fp);
+ return -1;
+ }
+
+ if (height > GIMP_MAX_IMAGE_SIZE)
+ {
+ g_message (_("'%s':\nImage height is larger than GIMP can handle"),
+ gimp_filename_to_utf8 (filename));
+ fclose (fp);
+ return -1;
+ }
+
+ if (intbits == 0)
+ {
+ g_message (_("'%s':\nNo image data type specified"),
+ gimp_filename_to_utf8 (filename));
+ fclose (fp);
+ return -1;
+ }
+
+ image_ID = gimp_image_new (width, height, GIMP_INDEXED);
+ gimp_image_set_filename (image_ID, filename);
+
+ if (comment)
+ {
+ GimpParasite *parasite;
+
+ parasite = gimp_parasite_new ("gimp-comment",
+ GIMP_PARASITE_PERSISTENT,
+ strlen (comment) + 1, (gpointer) comment);
+ gimp_image_attach_parasite (image_ID, parasite);
+ gimp_parasite_free (parasite);
+
+ g_free (comment);
+ }
+
+ x_hot = CLAMP (x_hot, 0, width);
+ y_hot = CLAMP (y_hot, 0, height);
+
+ if (x_hot > 0 || y_hot > 0)
+ {
+ GimpParasite *parasite;
+ gchar *str;
+
+ str = g_strdup_printf ("%d %d", x_hot, y_hot);
+ parasite = gimp_parasite_new ("hot-spot",
+ GIMP_PARASITE_PERSISTENT,
+ strlen (str) + 1, (gpointer) str);
+ g_free (str);
+ gimp_image_attach_parasite (image_ID, parasite);
+ gimp_parasite_free (parasite);
+ }
+
+ /* Set a black-and-white colormap. */
+ gimp_image_set_colormap (image_ID, cmap, 2);
+
+ layer_ID = gimp_layer_new (image_ID,
+ _("Background"),
+ width, height,
+ GIMP_INDEXED_IMAGE,
+ 100,
+ gimp_image_get_default_new_layer_mode (image_ID));
+ gimp_image_insert_layer (image_ID, layer_ID, -1, 0);
+
+ buffer = gimp_drawable_get_buffer (layer_ID);
+
+ /* Allocate the data. */
+ tileheight = gimp_tile_height ();
+ data = (guchar *) g_malloc (width * tileheight);
+
+ for (i = 0; i < height; i += tileheight)
+ {
+ tileheight = MIN (tileheight, height - i);
+
+#ifdef VERBOSE
+ if (verbose > 1)
+ printf ("XBM: reading %dx(%d+%d) pixel region\n", width, i,
+ tileheight);
+#endif
+
+ /* Parse the data from the file */
+ for (j = 0; j < tileheight; j ++)
+ {
+ /* Read each row. */
+ rowoffset = j * width;
+ for (k = 0; k < width; k ++)
+ {
+ /* Expand each integer into INTBITS pixels. */
+ if (k % intbits == 0)
+ {
+ c = get_int (fp);
+
+ /* Flip all the bits so that 1's become black and
+ 0's become white. */
+ c ^= 0xffff;
+ }
+
+ data[rowoffset + k] = c & 1;
+ c >>= 1;
+ }
+ }
+
+ /* Put the data into the image. */
+ gegl_buffer_set (buffer, GEGL_RECTANGLE (0, i, width, tileheight), 0,
+ NULL, data, GEGL_AUTO_ROWSTRIDE);
+
+ gimp_progress_update ((double) (i + tileheight) / (double) height);
+ }
+
+ g_free (data);
+ g_object_unref (buffer);
+ fclose (fp);
+
+ gimp_progress_update (1.0);
+
+ return image_ID;
+}
+
+static gboolean
+save_image (GFile *file,
+ const gchar *prefix,
+ const gchar *comment,
+ gboolean save_mask,
+ gint32 image_ID,
+ gint32 drawable_ID,
+ GError **error)
+{
+ GOutputStream *output;
+ GeglBuffer *buffer;
+ GCancellable *cancellable;
+ gint width, height, colors, dark;
+ gint intbits, lineints, need_comma, nints, rowoffset, tileheight;
+ gint c, i, j, k, thisbit;
+ gboolean has_alpha;
+ gint bpp;
+ guchar *data = NULL;
+ guchar *cmap;
+ const gchar *intfmt;
+
+#if 0
+ if (save_mask)
+ g_printerr ("%s: save_mask '%s'\n", G_STRFUNC, prefix);
+ else
+ g_printerr ("%s: save_image '%s'\n", G_STRFUNC, prefix);
+#endif
+
+ cmap = gimp_image_get_colormap (image_ID, &colors);
+
+ if (! gimp_drawable_is_indexed (drawable_ID) || colors > 2)
+ {
+ /* The image is not black-and-white. */
+ g_message (_("The image which you are trying to export as "
+ "an XBM contains more than two colors.\n\n"
+ "Please convert it to a black and white "
+ "(1-bit) indexed image and try again."));
+ g_free (cmap);
+ return FALSE;
+ }
+
+ has_alpha = gimp_drawable_has_alpha (drawable_ID);
+
+ if (! has_alpha && save_mask)
+ {
+ g_message (_("You cannot save a cursor mask for an image\n"
+ "which has no alpha channel."));
+ return FALSE;
+ }
+
+ buffer = gimp_drawable_get_buffer (drawable_ID);
+ width = gegl_buffer_get_width (buffer);
+ height = gegl_buffer_get_height (buffer);
+ bpp = gimp_drawable_bpp (drawable_ID);
+
+ /* Figure out which color is black, and which is white. */
+ dark = 0;
+ if (colors > 1)
+ {
+ gint first, second;
+
+ /* Maybe the second color is darker than the first. */
+ first = (cmap[0] * cmap[0]) + (cmap[1] * cmap[1]) + (cmap[2] * cmap[2]);
+ second = (cmap[3] * cmap[3]) + (cmap[4] * cmap[4]) + (cmap[5] * cmap[5]);
+
+ if (second < first)
+ dark = 1;
+ }
+
+ gimp_progress_init_printf (_("Exporting '%s'"),
+ gimp_file_get_utf8_name (file));
+
+ output = G_OUTPUT_STREAM (g_file_replace (file,
+ NULL, FALSE, G_FILE_CREATE_NONE,
+ NULL, error));
+ if (output)
+ {
+ GOutputStream *buffered;
+
+ buffered = g_buffered_output_stream_new (output);
+ g_object_unref (output);
+
+ output = buffered;
+ }
+ else
+ {
+ return FALSE;
+ }
+
+ /* Maybe write the image comment. */
+#if 0
+ /* DISABLED - see http://bugzilla.gnome.org/show_bug.cgi?id=82763 */
+ /* a future version should write the comment at the end of the file */
+ if (*comment)
+ {
+ if (! print (output, error, "/* %s */\n", comment))
+ goto fail;
+ }
+#endif
+
+ /* Write out the image height and width. */
+ if (! print (output, error, "#define %s_width %d\n", prefix, width) ||
+ ! print (output, error, "#define %s_height %d\n", prefix, height))
+ goto fail;
+
+ /* Write out the hotspot, if any. */
+ if (xsvals.use_hot)
+ {
+ if (! print (output, error,
+ "#define %s_x_hot %d\n", prefix, xsvals.x_hot) ||
+ ! print (output, error,
+ "#define %s_y_hot %d\n", prefix, xsvals.y_hot))
+ goto fail;
+ }
+
+ /* Now write the actual data. */
+ if (xsvals.x10_format)
+ {
+ /* We can fit 9 hex shorts on a single line. */
+ lineints = 9;
+ intbits = 16;
+ intfmt = " 0x%04x";
+ }
+ else
+ {
+ /* We can fit 12 hex chars on a single line. */
+ lineints = 12;
+ intbits = 8;
+ intfmt = " 0x%02x";
+ }
+
+ if (! print (output, error,
+ "static %s %s_bits[] = {\n ",
+ xsvals.x10_format ? "unsigned short" : "unsigned char", prefix))
+ goto fail;
+
+ /* Allocate a new set of pixels. */
+ tileheight = gimp_tile_height ();
+ data = (guchar *) g_malloc (width * tileheight * bpp);
+
+ /* Write out the integers. */
+ need_comma = 0;
+ nints = 0;
+ for (i = 0; i < height; i += tileheight)
+ {
+ /* Get a horizontal slice of the image. */
+ tileheight = MIN (tileheight, height - i);
+
+ gegl_buffer_get (buffer, GEGL_RECTANGLE (0, i, width, tileheight), 1.0,
+ NULL, data,
+ GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
+
+#ifdef VERBOSE
+ if (verbose > 1)
+ printf ("XBM: writing %dx(%d+%d) pixel region\n",
+ width, i, tileheight);
+#endif
+
+ for (j = 0; j < tileheight; j ++)
+ {
+ /* Write out a row at a time. */
+ rowoffset = j * width * bpp;
+ c = 0;
+ thisbit = 0;
+
+ for (k = 0; k < width * bpp; k += bpp)
+ {
+ if (k != 0 && thisbit == intbits)
+ {
+ /* Output a completed integer. */
+ if (need_comma)
+ {
+ if (! print (output, error, ","))
+ goto fail;
+ }
+
+ need_comma = 1;
+
+ /* Maybe start a new line. */
+ if (nints ++ >= lineints)
+ {
+ nints = 1;
+
+ if (! print (output, error, "\n "))
+ goto fail;
+ }
+
+ if (! print (output, error, intfmt, c))
+ goto fail;
+
+ /* Start a new integer. */
+ c = 0;
+ thisbit = 0;
+ }
+
+ /* Pack INTBITS pixels into an integer. */
+ if (save_mask)
+ {
+ c |= ((data[rowoffset + k + 1] < 128) ? 0 : 1) << (thisbit ++);
+ }
+ else
+ {
+ if (has_alpha && (data[rowoffset + k + 1] < 128))
+ c |= 0 << (thisbit ++);
+ else
+ c |= ((data[rowoffset + k] == dark) ? 1 : 0) << (thisbit ++);
+ }
+ }
+
+ if (thisbit != 0)
+ {
+ /* Write out the last oddball int. */
+ if (need_comma)
+ {
+ if (! print (output, error, ","))
+ goto fail;
+ }
+
+ need_comma = 1;
+
+ /* Maybe start a new line. */
+ if (nints ++ == lineints)
+ {
+ nints = 1;
+
+ if (! print (output, error, "\n "))
+ goto fail;
+ }
+
+ if (! print (output, error, intfmt, c))
+ goto fail;
+ }
+ }
+
+ gimp_progress_update ((double) (i + tileheight) / (double) height);
+ }
+
+ /* Write the trailer. */
+ if (! print (output, error, " };\n"))
+ goto fail;
+
+ if (! g_output_stream_close (output, NULL, error))
+ goto fail;
+
+ g_free (data);
+ g_object_unref (buffer);
+ g_object_unref (output);
+
+ gimp_progress_update (1.0);
+
+ return TRUE;
+
+ fail:
+
+ cancellable = g_cancellable_new ();
+ g_cancellable_cancel (cancellable);
+ g_output_stream_close (output, cancellable, NULL);
+ g_object_unref (cancellable);
+
+ g_free (data);
+ g_object_unref (buffer);
+ g_object_unref (output);
+
+ return FALSE;
+}
+
+static gboolean
+save_dialog (gint32 drawable_ID)
+{
+ GtkWidget *dialog;
+ GtkWidget *frame;
+ GtkWidget *vbox;
+ GtkWidget *toggle;
+ GtkWidget *table;
+ GtkWidget *entry;
+ GtkWidget *spinbutton;
+ GtkAdjustment *adj;
+ gboolean run;
+
+ dialog = gimp_export_dialog_new (_("XBM"), PLUG_IN_BINARY, SAVE_PROC);
+
+ /* parameter settings */
+ frame = gimp_frame_new (_("XBM Options"));
+ gtk_container_set_border_width (GTK_CONTAINER (frame), 12);
+ gtk_box_pack_start (GTK_BOX (gimp_export_dialog_get_content_area (dialog)),
+ frame, TRUE, TRUE, 0);
+ gtk_widget_show (frame);
+
+ vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
+ gtk_container_add (GTK_CONTAINER (frame), vbox);
+
+ /* X10 format */
+ toggle = gtk_check_button_new_with_mnemonic (_("_X10 format bitmap"));
+ gtk_box_pack_start (GTK_BOX (vbox), toggle, FALSE, FALSE, 0);
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), xsvals.x10_format);
+ gtk_widget_show (toggle);
+
+ g_signal_connect (toggle, "toggled",
+ G_CALLBACK (gimp_toggle_button_update),
+ &xsvals.x10_format);
+
+ table = gtk_table_new (2, 2, FALSE);
+ gtk_table_set_col_spacings (GTK_TABLE (table), 6);
+ gtk_table_set_row_spacings (GTK_TABLE (table), 6);
+ gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 0);
+ gtk_widget_show (table);
+
+ /* prefix */
+ entry = gtk_entry_new ();
+ gtk_entry_set_max_length (GTK_ENTRY (entry), MAX_PREFIX);
+ gtk_entry_set_text (GTK_ENTRY (entry), xsvals.prefix);
+ gimp_table_attach_aligned (GTK_TABLE (table), 0, 0,
+ _("_Identifier prefix:"), 0.0, 0.5,
+ entry, 1, TRUE);
+ g_signal_connect (entry, "changed",
+ G_CALLBACK (prefix_entry_callback),
+ NULL);
+
+ /* comment string. */
+#if 0
+ /* DISABLED - see http://bugzilla.gnome.org/show_bug.cgi?id=82763 */
+ entry = gtk_entry_new ();
+ gtk_entry_set_max_length (GTK_ENTRY (entry), MAX_COMMENT);
+ gtk_widget_set_size_request (entry, 240, -1);
+ gtk_entry_set_text (GTK_ENTRY (entry), xsvals.comment);
+ gimp_table_attach_aligned (GTK_TABLE (table), 0, 1,
+ _("Comment:"), 0.0, 0.5,
+ entry, 1, TRUE);
+ g_signal_connect (entry, "changed",
+ G_CALLBACK (comment_entry_callback),
+ NULL);
+#endif
+
+ /* hotspot toggle */
+ toggle = gtk_check_button_new_with_mnemonic (_("_Write hot spot values"));
+ gtk_box_pack_start (GTK_BOX (vbox), toggle, FALSE, FALSE, 0);
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), xsvals.use_hot);
+ gtk_widget_show (toggle);
+
+ g_signal_connect (toggle, "toggled",
+ G_CALLBACK (gimp_toggle_button_update),
+ &xsvals.use_hot);
+
+ table = gtk_table_new (2, 2, FALSE);
+ gtk_table_set_col_spacings (GTK_TABLE (table), 6);
+ gtk_table_set_row_spacings (GTK_TABLE (table), 6);
+ gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 0);
+ gtk_widget_show (table);
+
+ g_object_bind_property (toggle, "active",
+ table, "sensitive",
+ G_BINDING_SYNC_CREATE);
+
+ adj = (GtkAdjustment *)
+ gtk_adjustment_new (xsvals.x_hot, 0,
+ gimp_drawable_width (drawable_ID) - 1,
+ 1, 10, 0);
+ spinbutton = gimp_spin_button_new (adj, 1.0, 0);
+ gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE);
+ gimp_table_attach_aligned (GTK_TABLE (table), 0, 0,
+ _("Hot spot _X:"), 0.0, 0.5,
+ spinbutton, 1, TRUE);
+
+ g_signal_connect (adj, "value-changed",
+ G_CALLBACK (gimp_int_adjustment_update),
+ &xsvals.x_hot);
+
+ adj = (GtkAdjustment *)
+ gtk_adjustment_new (xsvals.y_hot, 0,
+ gimp_drawable_height (drawable_ID) - 1,
+ 1, 10, 0);
+ spinbutton = gimp_spin_button_new (adj, 1.0, 0);
+ gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE);
+ gimp_table_attach_aligned (GTK_TABLE (table), 0, 1,
+ _("Hot spot _Y:"), 0.0, 0.5,
+ spinbutton, 1, TRUE);
+
+ g_signal_connect (adj, "value-changed",
+ G_CALLBACK (gimp_int_adjustment_update),
+ &xsvals.y_hot);
+
+ /* mask file */
+ frame = gimp_frame_new (_("Mask File"));
+ gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
+ gtk_widget_show (frame);
+
+ table = gtk_table_new (2, 2, FALSE);
+ gtk_table_set_col_spacings (GTK_TABLE (table), 6);
+ gtk_table_set_row_spacings (GTK_TABLE (table), 6);
+ gtk_container_add (GTK_CONTAINER (frame), table);
+ gtk_widget_show (table);
+
+ toggle = gtk_check_button_new_with_mnemonic (_("W_rite extra mask file"));
+ gtk_table_attach_defaults (GTK_TABLE (table), toggle, 0, 2, 0, 1);
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), xsvals.write_mask);
+ gtk_widget_show (toggle);
+
+ g_signal_connect (toggle, "toggled",
+ G_CALLBACK (gimp_toggle_button_update),
+ &xsvals.write_mask);
+
+ entry = gtk_entry_new ();
+ gtk_entry_set_max_length (GTK_ENTRY (entry), MAX_MASK_EXT);
+ gtk_entry_set_text (GTK_ENTRY (entry), xsvals.mask_ext);
+ gimp_table_attach_aligned (GTK_TABLE (table), 0, 1,
+ _("_Mask file extension:"), 0.0, 0.5,
+ entry, 1, TRUE);
+ g_signal_connect (entry, "changed",
+ G_CALLBACK (mask_ext_entry_callback),
+ NULL);
+
+ g_object_bind_property (toggle, "active",
+ entry, "sensitive",
+ G_BINDING_SYNC_CREATE);
+
+ gtk_widget_set_sensitive (frame, gimp_drawable_has_alpha (drawable_ID));
+
+ /* Done. */
+ gtk_widget_show (vbox);
+ gtk_widget_show (dialog);
+
+ run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK);
+
+ gtk_widget_destroy (dialog);
+
+ return run;
+}
+
+static gboolean
+print (GOutputStream *output,
+ GError **error,
+ const gchar *format,
+ ...)
+{
+ va_list args;
+ gboolean success;
+
+ va_start (args, format);
+ success = g_output_stream_vprintf (output, NULL, NULL,
+ error, format, args);
+ va_end (args);
+
+ return success;
+}
+
+/* Update the comment string. */
+#if 0
+/* DISABLED - see http://bugzilla.gnome.org/show_bug.cgi?id=82763 */
+static void
+comment_entry_callback (GtkWidget *widget,
+ gpointer data)
+{
+ memset (xsvals.comment, 0, sizeof (xsvals.comment));
+ strncpy (xsvals.comment,
+ gtk_entry_get_text (GTK_ENTRY (widget)), MAX_COMMENT);
+}
+#endif
+
+static void
+prefix_entry_callback (GtkWidget *widget,
+ gpointer data)
+{
+ memset (xsvals.prefix, 0, sizeof (xsvals.prefix));
+ strncpy (xsvals.prefix,
+ gtk_entry_get_text (GTK_ENTRY (widget)), MAX_PREFIX);
+}
+
+static void
+mask_ext_entry_callback (GtkWidget *widget,
+ gpointer data)
+{
+ memset (xsvals.mask_ext, 0, sizeof (xsvals.mask_ext));
+ strncpy (xsvals.mask_ext,
+ gtk_entry_get_text (GTK_ENTRY (widget)), MAX_MASK_EXT);
+}