/* PIKA - Photo and Image Kooker Application * a rebranding of The GNU Image Manipulation Program (created with heckimp) * A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio * * Original copyright, applying to most contents (license remains unchanged): * Copyright (C) 1995 Spencer Kimball and Peter Mattis * * X10 and X11 bitmap (XBM) loading and exporting file filter for PIKA. * XBM code Copyright (C) 1998 Gordon Matzigkeit * * The XBM reading and writing code was written from scratch by Gordon * Matzigkeit 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 . */ /* Release 1.0, 1998-02-04, Gordon Matzigkeit : * - 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. */ #include "config.h" #include #include #include #include #include #include "libpika/stdplugins-intl.h" #define LOAD_PROC "file-xbm-load" #define SAVE_PROC "file-xbm-save" #define PLUG_IN_BINARY "file-xbm" #define MAX_COMMENT 72 #define MAX_MASK_EXT 32 #define MAX_PREFIX 64 typedef struct _Xbm Xbm; typedef struct _XbmClass XbmClass; struct _Xbm { PikaPlugIn parent_instance; }; struct _XbmClass { PikaPlugInClass parent_class; }; #define XBM_TYPE (xbm_get_type ()) #define XBM (obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), XBM_TYPE, Xbm)) GType xbm_get_type (void) G_GNUC_CONST; static GList * xbm_query_procedures (PikaPlugIn *plug_in); static PikaProcedure * xbm_create_procedure (PikaPlugIn *plug_in, const gchar *name); static PikaValueArray * xbm_load (PikaProcedure *procedure, PikaRunMode run_mode, GFile *file, const PikaValueArray *args, gpointer run_data); static PikaValueArray * xbm_save (PikaProcedure *procedure, PikaRunMode run_mode, PikaImage *image, gint n_drawables, PikaDrawable **drawables, GFile *file, const PikaValueArray *args, gpointer run_data); static PikaImage * load_image (GFile *file, GError **error); static gboolean save_image (GFile *file, const gchar *prefix, gboolean save_mask, PikaImage *image, PikaDrawable *drawable, GObject *config, GError **error); static gboolean save_dialog (PikaImage *image, PikaDrawable *drawable, PikaProcedure *procedure, GObject *config); static gboolean print (GOutputStream *output, GError **error, const gchar *format, ...) G_GNUC_PRINTF (3, 4); G_DEFINE_TYPE (Xbm, xbm, PIKA_TYPE_PLUG_IN) PIKA_MAIN (XBM_TYPE) DEFINE_STD_SET_I18N static void xbm_class_init (XbmClass *klass) { PikaPlugInClass *plug_in_class = PIKA_PLUG_IN_CLASS (klass); plug_in_class->query_procedures = xbm_query_procedures; plug_in_class->create_procedure = xbm_create_procedure; plug_in_class->set_i18n = STD_SET_I18N; } static void xbm_init (Xbm *xbm) { } static GList * xbm_query_procedures (PikaPlugIn *plug_in) { GList *list = NULL; list = g_list_append (list, g_strdup (LOAD_PROC)); list = g_list_append (list, g_strdup (SAVE_PROC)); return list; } static PikaProcedure * xbm_create_procedure (PikaPlugIn *plug_in, const gchar *name) { PikaProcedure *procedure = NULL; if (! strcmp (name, LOAD_PROC)) { procedure = pika_load_procedure_new (plug_in, name, PIKA_PDB_PROC_TYPE_PLUGIN, xbm_load, NULL, NULL); pika_procedure_set_menu_label (procedure, _("X BitMap image")); pika_procedure_set_documentation (procedure, _("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."), name); pika_procedure_set_attribution (procedure, "Gordon Matzigkeit", "Gordon Matzigkeit", "1998"); pika_file_procedure_set_mime_types (PIKA_FILE_PROCEDURE (procedure), "image/x-xbitmap"); pika_file_procedure_set_extensions (PIKA_FILE_PROCEDURE (procedure), "xbm,icon,bitmap"); } else if (! strcmp (name, SAVE_PROC)) { procedure = pika_save_procedure_new (plug_in, name, PIKA_PDB_PROC_TYPE_PLUGIN, xbm_save, NULL, NULL); pika_procedure_set_image_types (procedure, "INDEXED"); pika_procedure_set_menu_label (procedure, _("X BitMap image")); pika_procedure_set_documentation (procedure, _("Export a file in X10 or X11 bitmap " "(XBM) file format"), _("X10 or X11 bitmap " "(XBM) file format. XBM is a lossless " "format for flat black-and-white " "(two color indexed) images."), name); pika_procedure_set_attribution (procedure, "Gordon Matzigkeit", "Gordon Matzigkeit", "1998"); pika_file_procedure_set_format_name (PIKA_FILE_PROCEDURE (procedure), _("XBM")); pika_file_procedure_set_handles_remote (PIKA_FILE_PROCEDURE (procedure), TRUE); pika_file_procedure_set_mime_types (PIKA_FILE_PROCEDURE (procedure), "image/x-xbitmap"); pika_file_procedure_set_extensions (PIKA_FILE_PROCEDURE (procedure), "xbm,icon,bitmap"); PIKA_PROC_ARG_BOOLEAN (procedure, "save-comment", _("_Write comment"), _("Write a comment at the beginning of the file."), FALSE, /* *NOT* pika_export_comment() */ G_PARAM_READWRITE); PIKA_PROC_ARG_STRING (procedure, "pika-comment", _("Co_mment"), _("Image description (maximum 72 bytes)"), pika_get_default_comment (), G_PARAM_READWRITE); pika_procedure_set_argument_sync (procedure, "pika-comment", PIKA_ARGUMENT_SYNC_PARASITE); PIKA_PROC_ARG_BOOLEAN (procedure, "x10-format", _("_X10 format bitmap"), _("Export in X10 format"), FALSE, G_PARAM_READWRITE); PIKA_PROC_ARG_BOOLEAN (procedure, "use-hot-spot", _("Write hot spot _values"), _("Write hotspot information"), FALSE, G_PARAM_READWRITE); PIKA_PROC_ARG_INT (procedure, "hot-spot-x", _("Hot s_pot X"), _("X coordinate of hotspot"), 0, PIKA_MAX_IMAGE_SIZE, 0, G_PARAM_READWRITE); PIKA_PROC_ARG_INT (procedure, "hot-spot-y", _("Hot spot _Y"), _("Y coordinate of hotspot"), 0, PIKA_MAX_IMAGE_SIZE, 0, G_PARAM_READWRITE); PIKA_PROC_ARG_STRING (procedure, "prefix", _("I_dentifier prefix"), _("Identifier prefix [determined from filename]"), "bitmap", G_PARAM_READWRITE); PIKA_PROC_ARG_BOOLEAN (procedure, "write-mask", _("Write extra mask _file"), _("Write extra mask file"), FALSE, G_PARAM_READWRITE); PIKA_PROC_ARG_STRING (procedure, "mask-suffix", _("Mas_k file extensions"), _("Suffix of the mask file"), "-mask", G_PARAM_READWRITE); } return procedure; } static PikaValueArray * xbm_load (PikaProcedure *procedure, PikaRunMode run_mode, GFile *file, const PikaValueArray *args, gpointer run_data) { PikaValueArray *return_vals; PikaPDBStatusType status = PIKA_PDB_SUCCESS; PikaImage *image; GError *error = NULL; gegl_init (NULL, NULL); image = load_image (file, &error); if (! image) return pika_procedure_new_return_values (procedure, status, error); return_vals = pika_procedure_new_return_values (procedure, PIKA_PDB_SUCCESS, NULL); PIKA_VALUES_SET_IMAGE (return_vals, 1, image); return return_vals; } static gchar * init_prefix (GFile *file, GObject *config) { gchar *p, *prefix; gint len; prefix = g_file_get_basename (file); g_object_set (config, "prefix", NULL, NULL); if (prefix) { /* Strip any extension. */ p = strrchr (prefix, '.'); if (p && p != prefix) len = MIN (MAX_PREFIX, p - prefix); else len = MAX_PREFIX; prefix[len] = '\0'; g_object_set (config, "prefix", prefix, NULL); } return prefix; } static PikaValueArray * xbm_save (PikaProcedure *procedure, PikaRunMode run_mode, PikaImage *image, gint n_drawables, PikaDrawable **drawables, GFile *file, const PikaValueArray *args, gpointer run_data) { PikaProcedureConfig *config; PikaPDBStatusType status = PIKA_PDB_SUCCESS; PikaExportReturn export = PIKA_EXPORT_CANCEL; gchar *mask_basename = NULL; GError *error = NULL; gegl_init (NULL, NULL); config = pika_procedure_create_config (procedure); pika_procedure_config_begin_export (config, image, run_mode, args, NULL); switch (run_mode) { case PIKA_RUN_INTERACTIVE: case PIKA_RUN_WITH_LAST_VALS: pika_ui_init (PLUG_IN_BINARY); export = pika_export_image (&image, &n_drawables, &drawables, "XBM", PIKA_EXPORT_CAN_HANDLE_BITMAP | PIKA_EXPORT_CAN_HANDLE_ALPHA); if (export == PIKA_EXPORT_CANCEL) return pika_procedure_new_return_values (procedure, PIKA_PDB_CANCEL, NULL); break; default: break; } if (n_drawables != 1) { g_set_error (&error, G_FILE_ERROR, 0, _("XBM format does not support multiple layers.")); return pika_procedure_new_return_values (procedure, PIKA_PDB_CALLING_ERROR, error); } if (run_mode == PIKA_RUN_INTERACTIVE || run_mode == PIKA_RUN_WITH_LAST_VALS) { /* Always override the prefix with the filename. */ mask_basename = g_strdup (init_prefix (file, G_OBJECT (config))); } if (run_mode == PIKA_RUN_INTERACTIVE) { PikaParasite *parasite; parasite = pika_image_get_parasite (image, "hot-spot"); if (parasite) { gchar *parasite_data; guint32 parasite_size; gint x, y; parasite_data = (gchar *) pika_parasite_get_data (parasite, ¶site_size); parasite_data = g_strndup (parasite_data, parasite_size); if (sscanf (parasite_data, "%i %i", &x, &y) == 2) { g_object_set (config, "use-hot-spot", TRUE, "hot-spot-x", x, "hot-spot-y", y, NULL); } pika_parasite_free (parasite); g_free (parasite_data); } if (! save_dialog (image, drawables[0], procedure, G_OBJECT (config))) status = PIKA_PDB_CANCEL; } if (status == PIKA_PDB_SUCCESS) { GFile *mask_file; GFile *dir; gchar *mask_prefix; gchar *mask_ext; gchar *prefix; gchar *temp; gboolean write_mask; g_object_get (config, "prefix", &prefix, "mask-suffix", &mask_ext, "write-mask", &write_mask, NULL); dir = g_file_get_parent (file); temp = g_strdup_printf ("%s%s.xbm", mask_basename, 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 = prefix; *temp; temp++) if (! g_ascii_isalnum (*temp)) *temp = '_'; g_object_set (config, "prefix", prefix, NULL); mask_prefix = g_strdup_printf ("%s%s", prefix, mask_ext); for (temp = mask_prefix; *temp; temp++) if (! g_ascii_isalnum (*temp)) *temp = '_'; if (! save_image (file, prefix, FALSE, image, drawables[0], G_OBJECT (config), &error) || (write_mask && ! save_image (mask_file, mask_prefix, TRUE, image, drawables[0], G_OBJECT (config), &error))) { status = PIKA_PDB_EXECUTION_ERROR; } g_free (prefix); g_free (mask_prefix); g_free (mask_ext); g_free (mask_basename); g_object_unref (mask_file); } pika_procedure_config_end_export (config, image, file, status); g_object_unref (config); if (export == PIKA_EXPORT_EXPORT) { pika_image_delete (image); g_free (drawables); } return pika_procedure_new_return_values (procedure, status, error); } /* 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 PikaImage * load_image (GFile *file, GError **error) { FILE *fp; GeglBuffer *buffer; PikaImage *image; PikaLayer *layer; 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 */ }; pika_progress_init_printf (_("Opening '%s'"), pika_file_get_utf8_name (file)); fp = g_fopen (g_file_peek_path (file), "rb"); if (! fp) { g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno), _("Could not open '%s' for reading: %s"), pika_file_get_utf8_name (file), g_strerror (errno)); return NULL; } 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)"), pika_file_get_utf8_name (file), ftell (fp)); fclose (fp); return NULL; } if (width <= 0) { g_message (_("'%s':\nNo image width specified"), pika_file_get_utf8_name (file)); fclose (fp); return NULL; } if (width > PIKA_MAX_IMAGE_SIZE) { g_message (_("'%s':\nImage width is larger than PIKA can handle"), pika_file_get_utf8_name (file)); fclose (fp); return NULL; } if (height <= 0) { g_message (_("'%s':\nNo image height specified"), pika_file_get_utf8_name (file)); fclose (fp); return NULL; } if (height > PIKA_MAX_IMAGE_SIZE) { g_message (_("'%s':\nImage height is larger than PIKA can handle"), pika_file_get_utf8_name (file)); fclose (fp); return NULL; } if (intbits == 0) { g_message (_("'%s':\nNo image data type specified"), pika_file_get_utf8_name (file)); fclose (fp); return NULL; } image = pika_image_new (width, height, PIKA_INDEXED); if (comment) { PikaParasite *parasite; parasite = pika_parasite_new ("pika-comment", PIKA_PARASITE_PERSISTENT, strlen (comment) + 1, (gpointer) comment); pika_image_attach_parasite (image, parasite); pika_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) { PikaParasite *parasite; gchar *str; str = g_strdup_printf ("%d %d", x_hot, y_hot); parasite = pika_parasite_new ("hot-spot", PIKA_PARASITE_PERSISTENT, strlen (str) + 1, (gpointer) str); g_free (str); pika_image_attach_parasite (image, parasite); pika_parasite_free (parasite); } /* Set a black-and-white colormap. */ pika_image_set_colormap (image, cmap, 2); layer = pika_layer_new (image, _("Background"), width, height, PIKA_INDEXED_IMAGE, 100, pika_image_get_default_new_layer_mode (image)); pika_image_insert_layer (image, layer, NULL, 0); buffer = pika_drawable_get_buffer (PIKA_DRAWABLE (layer)); /* Allocate the data. */ tileheight = pika_tile_height (); data = (guchar *) g_malloc (width * tileheight); for (i = 0; i < height; i += tileheight) { tileheight = MIN (tileheight, height - i); /* 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); pika_progress_update ((double) (i + tileheight) / (double) height); } g_free (data); g_object_unref (buffer); fclose (fp); pika_progress_update (1.0); return image; } static gboolean save_image (GFile *file, const gchar *prefix, gboolean save_mask, PikaImage *image, PikaDrawable *drawable, GObject *config, 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; gboolean config_save_comment; gchar *config_comment; gint config_x10_format; gint config_use_hot; gint config_x_hot; gint config_y_hot; g_object_get (config, "save-comment", &config_save_comment, "pika-comment", &config_comment, "x10-format", &config_x10_format, "use-hot-spot", &config_use_hot, "hot-spot-x", &config_x_hot, "hot-spot-y", &config_y_hot, NULL); #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 = pika_image_get_colormap (image, NULL, &colors); if (! pika_drawable_is_indexed (drawable) || 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 = pika_drawable_has_alpha (drawable); 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 = pika_drawable_get_buffer (drawable); width = gegl_buffer_get_width (buffer); height = gegl_buffer_get_height (buffer); bpp = pika_drawable_get_bpp (drawable); /* 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; } pika_progress_init_printf (_("Exporting '%s'"), pika_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 (config_save_comment && config_comment && *config_comment) { if (! print (output, error, "/* %s */\n", config_comment)) goto fail; } /* 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 (config_use_hot) { if (! print (output, error, "#define %s_x_hot %d\n", prefix, config_x_hot) || ! print (output, error, "#define %s_y_hot %d\n", prefix, config_y_hot)) goto fail; } /* Now write the actual data. */ if (config_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 ", config_x10_format ? "unsigned short" : "unsigned char", prefix)) goto fail; /* Allocate a new set of pixels. */ tileheight = pika_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); 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; } } pika_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); pika_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 (PikaImage *image, PikaDrawable *drawable, PikaProcedure *procedure, GObject *config) { GtkWidget *dialog; GtkWidget *vbox; GtkWidget *frame; GtkWidget *hint; gboolean run; dialog = pika_save_procedure_dialog_new (PIKA_SAVE_PROCEDURE (procedure), PIKA_PROCEDURE_CONFIG (config), image); /* comment string. */ hint = g_object_new (PIKA_TYPE_HINT_BOX, "icon-name", PIKA_ICON_DIALOG_WARNING, "hint", _("Writing a comment will make the XBM " "file unreadable by some applications.\n" "The comment will not affect embedding " "the XBM in C source code."), NULL); vbox = pika_procedure_dialog_fill_box (PIKA_PROCEDURE_DIALOG (dialog), "comment-vbox", "pika-comment", NULL); gtk_box_pack_start (GTK_BOX (vbox), hint, FALSE, FALSE, 0); gtk_widget_set_visible (hint, TRUE); gtk_box_reorder_child (GTK_BOX (vbox), hint, 0); gtk_widget_set_margin_end (hint, 24); frame = pika_procedure_dialog_fill_frame (PIKA_PROCEDURE_DIALOG (dialog), "comment-frame", "save-comment", FALSE, "comment-vbox"); /* hotspot toggle */ pika_procedure_dialog_fill_box (PIKA_PROCEDURE_DIALOG (dialog), "hot-spot-vbox", "hot-spot-x", "hot-spot-y", NULL); frame = pika_procedure_dialog_fill_frame (PIKA_PROCEDURE_DIALOG (dialog), "hot-spot-frame", "use-hot-spot", FALSE, "hot-spot-vbox"); /* mask file */ frame = pika_procedure_dialog_fill_frame (PIKA_PROCEDURE_DIALOG (dialog), "mask-frame", "write-mask", FALSE, "mask-suffix"); gtk_widget_set_sensitive (frame, pika_drawable_has_alpha (drawable)); pika_procedure_dialog_fill (PIKA_PROCEDURE_DIALOG (dialog), "x10-format", "prefix", "comment-frame", "hot-spot-frame", "mask-frame", NULL); gtk_widget_show (dialog); run = pika_procedure_dialog_run (PIKA_PROCEDURE_DIALOG (dialog)); 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; }