diff options
Diffstat (limited to 'plug-ins/common/file-pnm.c')
-rw-r--r-- | plug-ins/common/file-pnm.c | 1820 |
1 files changed, 1820 insertions, 0 deletions
diff --git a/plug-ins/common/file-pnm.c b/plug-ins/common/file-pnm.c new file mode 100644 index 0000000..2132eec --- /dev/null +++ b/plug-ins/common/file-pnm.c @@ -0,0 +1,1820 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * PNM reading and writing code Copyright (C) 1996 Erik Nygren + * + * 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/>. + */ + +/* + * The pnm reading and writing code was written from scratch by Erik Nygren + * (nygren@mit.edu) based on the specifications in the man pages and + * does not contain any code from the netpbm or pbmplus distributions. + * + * 2006: pbm saving written by Martin K Collins (martin@mkcollins.org) + * 2015: pfm reading written by Tobias Ellinghaus (me@houz.org) + */ + +#include "config.h" + +#include <setjmp.h> +#include <stdlib.h> +#include <math.h> +#include <errno.h> + +#include <libgimp/gimp.h> +#include <libgimp/gimpui.h> + +#include "libgimp/stdplugins-intl.h" + + +#define LOAD_PROC "file-pnm-load" +#define PNM_SAVE_PROC "file-pnm-save" +#define PBM_SAVE_PROC "file-pbm-save" +#define PGM_SAVE_PROC "file-pgm-save" +#define PPM_SAVE_PROC "file-ppm-save" +#define PFM_SAVE_PROC "file-pfm-save" +#define PLUG_IN_BINARY "file-pnm" +#define PLUG_IN_ROLE "gimp-file-pnm" + + +/* Declare local data types + */ + +typedef struct _PNMScanner PNMScanner; +typedef struct _PNMInfo PNMInfo; +typedef struct _PNMRowInfo PNMRowInfo; + +typedef void (* PNMLoaderFunc) (PNMScanner *scanner, + PNMInfo *info, + GeglBuffer *buffer); +typedef gboolean (* PNMSaverowFunc) (PNMRowInfo *info, + guchar *data, + GError **error); + +struct _PNMScanner +{ + GInputStream *input; /* The input stream of the file being read */ + gchar cur; /* The current character in the input stream */ + gint eof; /* Have we reached end of file? */ + gchar *inbuf; /* Input buffer - initially 0 */ + gint inbufsize; /* Size of input buffer */ + gint inbufvalidsize; /* Size of input buffer with valid data */ + gint inbufpos; /* Position in input buffer */ +}; + +struct _PNMInfo +{ + gint xres, yres; /* The size of the image */ + gboolean float_format; /* Whether it is a floating point format */ + gint maxval; /* For integer format image files, the max value + * which we need to normalize to */ + gfloat scale_factor; /* PFM files have a scale factor */ + gint np; /* Number of image planes (0 for pbm) */ + gboolean asciibody; /* 1 if ascii body, 0 if raw body */ + jmp_buf jmpbuf; /* Where to jump to on an error loading */ + + PNMLoaderFunc loader; /* Routine to use to load the pnm body */ +}; + +/* Contains the information needed to write out PNM rows */ +struct _PNMRowInfo +{ + GOutputStream *output; /* Output stream */ + gchar *rowbuf; /* Buffer for writing out rows */ + gint xres; /* X resolution */ + gint np; /* Number of planes */ + gint bpc; /* Bytes per color */ + guchar *red; /* Colormap red */ + guchar *grn; /* Colormap green */ + guchar *blu; /* Colormap blue */ + gboolean zero_is_black; /* index zero is black (PBM only) */ +}; + +/* Export info */ +typedef struct +{ + gint raw; /* raw or ascii */ +} PNMSaveVals; + +#define BUFLEN 512 /* The input buffer size for data returned + * from the scanner. Note that lines + * aren't allowed to be over 256 characters + * by the spec anyways so this shouldn't + * be an issue. */ + +/* 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 (GFile *file, + GError **error); +static gint save_image (GFile *file, + gint32 image_ID, + gint32 drawable_ID, + gboolean pbm, + gboolean float_format, + GError **error); + +static gboolean save_dialog (void); + +static void pnm_load_ascii (PNMScanner *scan, + PNMInfo *info, + GeglBuffer *buffer); +static void pnm_load_raw (PNMScanner *scan, + PNMInfo *info, + GeglBuffer *buffer); +static void pnm_load_rawpbm (PNMScanner *scan, + PNMInfo *info, + GeglBuffer *buffer); +static void pnm_load_rawpfm (PNMScanner *scan, + PNMInfo *info, + GeglBuffer *buffer); + +static gboolean pnmsaverow_ascii (PNMRowInfo *ri, + guchar *data, + GError **error); +static gboolean pnmsaverow_raw (PNMRowInfo *ri, + guchar *data, + GError **error); +static gboolean pnmsaverow_raw_pbm (PNMRowInfo *ri, + guchar *data, + GError **error); +static gboolean pnmsaverow_ascii_pbm (PNMRowInfo *ri, + guchar *data, + GError **error); +static gboolean pnmsaverow_ascii_indexed (PNMRowInfo *ri, + guchar *data, + GError **error); +static gboolean pnmsaverow_raw_indexed (PNMRowInfo *ri, + guchar *data, + GError **error); + +static void pnmscanner_destroy (PNMScanner *s); +static void pnmscanner_createbuffer (PNMScanner *s, + gint bufsize); +static void pnmscanner_getchar (PNMScanner *s); +static void pnmscanner_eatwhitespace (PNMScanner *s); +static void pnmscanner_gettoken (PNMScanner *s, + gchar *buf, + gint bufsize); +static void pnmscanner_getsmalltoken (PNMScanner *s, + gchar *buf); + +static PNMScanner * pnmscanner_create (GInputStream *input); + + +#define pnmscanner_eof(s) ((s)->eof) +#define pnmscanner_input(s) ((s)->input) + +/* Checks for a fatal error */ +#define CHECK_FOR_ERROR(predicate, jmpbuf, ...) \ + if ((predicate)) \ + { g_message (__VA_ARGS__); longjmp ((jmpbuf), 1); } + +static const struct +{ + gchar name; + gint np; + gint asciibody; + gint maxval; + PNMLoaderFunc loader; +} pnm_types[] = +{ + { '1', 0, 1, 1, pnm_load_ascii }, /* ASCII PBM */ + { '2', 1, 1, 255, pnm_load_ascii }, /* ASCII PGM */ + { '3', 3, 1, 255, pnm_load_ascii }, /* ASCII PPM */ + { '4', 0, 0, 1, pnm_load_rawpbm }, /* RAW PBM */ + { '5', 1, 0, 255, pnm_load_raw }, /* RAW PGM */ + { '6', 3, 0, 255, pnm_load_raw }, /* RAW PPM */ + { 'F', 3, 0, 0, pnm_load_rawpfm }, /* RAW PFM (color) */ + { 'f', 1, 0, 0, pnm_load_rawpfm }, /* RAW PFM (grayscale) */ + { 0 , 0, 0, 0, NULL} +}; + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + +static PNMSaveVals psvals = +{ + TRUE /* raw? or ascii */ +}; + + +MAIN () + +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 of the file to load" } + }; + 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 the image in" }, + { GIMP_PDB_STRING, "raw-filename", "The name of the file to export the image in" }, + { GIMP_PDB_INT32, "raw", "TRUE for raw output, FALSE for ascii output" } + }; + + static const GimpParamDef pfm_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 the image in" }, + { GIMP_PDB_STRING, "raw-filename", "The name of the file to export the image in" } + }; + + gimp_install_procedure (LOAD_PROC, + "Loads files in the PNM file format", + "This plug-in loads files in the various Netpbm portable file formats.", + "Erik Nygren", + "Erik Nygren", + "1996", + N_("PNM 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-portable-anymap"); + gimp_register_file_handler_uri (LOAD_PROC); + gimp_register_magic_load_handler (LOAD_PROC, + "pnm,ppm,pgm,pbm,pfm", + "", + "0,string,P1,0,string,P2,0,string,P3," + "0,string,P4,0,string,P5,0,string,P6," + "0,string,PF,0,string,Pf"); + + gimp_install_procedure (PNM_SAVE_PROC, + "Exports files in the PNM file format", + "PNM exporting handles all image types without transparency.", + "Erik Nygren", + "Erik Nygren", + "1996", + N_("PNM image"), + "RGB, GRAY, INDEXED", + GIMP_PLUGIN, + G_N_ELEMENTS (save_args), 0, + save_args, NULL); + + gimp_install_procedure (PBM_SAVE_PROC, + "Exports files in the PBM file format", + "PBM exporting produces mono images without transparency.", + "Martin K Collins", + "Erik Nygren", + "2006", + N_("PBM image"), + "RGB, GRAY, INDEXED", + GIMP_PLUGIN, + G_N_ELEMENTS (save_args), 0, + save_args, NULL); + + gimp_install_procedure (PGM_SAVE_PROC, + "Exports files in the PGM file format", + "PGM exporting produces grayscale images without transparency.", + "Erik Nygren", + "Erik Nygren", + "1996", + N_("PGM image"), + "RGB, GRAY, INDEXED", + GIMP_PLUGIN, + G_N_ELEMENTS (save_args), 0, + save_args, NULL); + + gimp_install_procedure (PPM_SAVE_PROC, + "Exports files in the PPM file format", + "PPM exporting handles RGB images without transparency.", + "Erik Nygren", + "Erik Nygren", + "1996", + N_("PPM image"), + "RGB, GRAY, INDEXED", + GIMP_PLUGIN, + G_N_ELEMENTS (save_args), 0, + save_args, NULL); + + gimp_install_procedure (PFM_SAVE_PROC, + "Exports files in the PFM file format", + "PFM exporting handles all images without transparency.", + "Mukund Sivaraman", + "Mukund Sivaraman", + "2015", + N_("PFM image"), + "RGB, GRAY, INDEXED", + GIMP_PLUGIN, + G_N_ELEMENTS (pfm_save_args), 0, + pfm_save_args, NULL); + + gimp_register_file_handler_mime (PNM_SAVE_PROC, "image/x-portable-anymap"); + gimp_register_file_handler_mime (PBM_SAVE_PROC, "image/x-portable-bitmap"); + gimp_register_file_handler_mime (PGM_SAVE_PROC, "image/x-portable-graymap"); + gimp_register_file_handler_mime (PPM_SAVE_PROC, "image/x-portable-pixmap"); + gimp_register_file_handler_mime (PPM_SAVE_PROC, "image/x-portable-floatmap"); + + gimp_register_file_handler_uri (PNM_SAVE_PROC); + gimp_register_file_handler_uri (PBM_SAVE_PROC); + gimp_register_file_handler_uri (PGM_SAVE_PROC); + gimp_register_file_handler_uri (PPM_SAVE_PROC); + gimp_register_file_handler_uri (PFM_SAVE_PROC); + + gimp_register_save_handler (PNM_SAVE_PROC, "pnm", ""); + gimp_register_save_handler (PBM_SAVE_PROC, "pbm", ""); + gimp_register_save_handler (PGM_SAVE_PROC, "pgm", ""); + gimp_register_save_handler (PPM_SAVE_PROC, "ppm", ""); + gimp_register_save_handler (PFM_SAVE_PROC, "pfm", ""); +} + +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; + GimpExportReturn export = GIMP_EXPORT_CANCEL; + GError *error = NULL; + gboolean pbm = FALSE; /* flag for PBM output */ + gboolean float_format = FALSE; /* flag for PFM output */ + + INIT_I18N (); + gegl_init (NULL, NULL); + + 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; + + if (strcmp (name, LOAD_PROC) == 0) + { + image_ID = load_image (g_file_new_for_uri (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, PNM_SAVE_PROC) == 0 || + strcmp (name, PBM_SAVE_PROC) == 0 || + strcmp (name, PGM_SAVE_PROC) == 0 || + strcmp (name, PPM_SAVE_PROC) == 0 || + strcmp (name, PFM_SAVE_PROC) == 0) + { + image_ID = param[1].data.d_int32; + drawable_ID = param[2].data.d_int32; + + /* eventually export the image */ + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + case GIMP_RUN_WITH_LAST_VALS: + gimp_ui_init (PLUG_IN_BINARY, FALSE); + + if (strcmp (name, PNM_SAVE_PROC) == 0) + { + export = gimp_export_image (&image_ID, &drawable_ID, "PNM", + GIMP_EXPORT_CAN_HANDLE_RGB | + GIMP_EXPORT_CAN_HANDLE_GRAY | + GIMP_EXPORT_CAN_HANDLE_INDEXED); + } + else if (strcmp (name, PBM_SAVE_PROC) == 0) + { + export = gimp_export_image (&image_ID, &drawable_ID, "PBM", + GIMP_EXPORT_CAN_HANDLE_BITMAP); + pbm = TRUE; /* gimp has no mono image type so hack it */ + } + else if (strcmp (name, PGM_SAVE_PROC) == 0) + { + export = gimp_export_image (&image_ID, &drawable_ID, "PGM", + GIMP_EXPORT_CAN_HANDLE_GRAY); + } + else if (strcmp (name, PPM_SAVE_PROC) == 0) + { + export = gimp_export_image (&image_ID, &drawable_ID, "PPM", + GIMP_EXPORT_CAN_HANDLE_RGB | + GIMP_EXPORT_CAN_HANDLE_INDEXED); + } + else + { + export = gimp_export_image (&image_ID, &drawable_ID, "PFM", + GIMP_EXPORT_CAN_HANDLE_RGB | + GIMP_EXPORT_CAN_HANDLE_GRAY); + float_format = TRUE; + } + + if (export == GIMP_EXPORT_CANCEL) + { + values[0].data.d_status = GIMP_PDB_CANCEL; + return; + } + break; + + default: + break; + } + + if (strcmp (name, PFM_SAVE_PROC) != 0) + { + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + /* Possibly retrieve data */ + gimp_get_data (name, &psvals); + + /* First acquire information with a dialog */ + if (! save_dialog ()) + status = GIMP_PDB_CANCEL; + break; + + case GIMP_RUN_NONINTERACTIVE: + /* Make sure all the arguments are there! */ + if (nparams != 6) + { + status = GIMP_PDB_CALLING_ERROR; + } + else + { + psvals.raw = (param[5].data.d_int32) ? TRUE : FALSE; + pbm = (strcmp (name, PBM_SAVE_PROC) == 0); + } + break; + + case GIMP_RUN_WITH_LAST_VALS: + /* Possibly retrieve data */ + gimp_get_data (name, &psvals); + break; + + default: + break; + } + } + else + { + switch (run_mode) + { + case GIMP_RUN_NONINTERACTIVE: + /* Make sure all the arguments are there! */ + if (nparams != 5) + { + status = GIMP_PDB_CALLING_ERROR; + } + break; + + default: + break; + } + } + + if (status == GIMP_PDB_SUCCESS) + { + if (save_image (g_file_new_for_uri (param[3].data.d_string), + image_ID, drawable_ID, pbm, float_format, + &error)) + { + if (strcmp (name, PFM_SAVE_PROC) != 0) + { + /* Store psvals data */ + gimp_set_data (name, &psvals, sizeof (PNMSaveVals)); + } + } + else + { + status = GIMP_PDB_EXECUTION_ERROR; + } + } + + 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; + + gegl_exit (); +} + +static gint32 +load_image (GFile *file, + GError **error) +{ + GInputStream *input; + GeglBuffer *buffer; + gint32 volatile image_ID = -1; + gint32 layer_ID; + char buf[BUFLEN + 4]; /* buffer for random things like scanning */ + PNMInfo *pnminfo; + PNMScanner *volatile scan; + int ctr; + GimpPrecision precision; + + gimp_progress_init_printf (_("Opening '%s'"), + g_file_get_parse_name (file)); + + input = G_INPUT_STREAM (g_file_read (file, NULL, error)); + if (! input) + return -1; + + /* allocate the necessary structures */ + pnminfo = g_new (PNMInfo, 1); + + scan = NULL; + /* set error handling */ + if (setjmp (pnminfo->jmpbuf)) + { + /* If we get here, we had a problem reading the file */ + if (scan) + pnmscanner_destroy (scan); + + g_object_unref (input); + g_free (pnminfo); + + if (image_ID != -1) + gimp_image_delete (image_ID); + + return -1; + } + + if (! (scan = pnmscanner_create (input))) + longjmp (pnminfo->jmpbuf, 1); + + /* Get magic number */ + pnmscanner_gettoken (scan, buf, BUFLEN); + CHECK_FOR_ERROR (pnmscanner_eof (scan), pnminfo->jmpbuf, + _("Premature end of file.")); + CHECK_FOR_ERROR ((buf[0] != 'P' || buf[2]), pnminfo->jmpbuf, + _("Invalid file.")); + + /* Look up magic number to see what type of PNM this is */ + for (ctr = 0; pnm_types[ctr].name; ctr++) + if (buf[1] == pnm_types[ctr].name) + { + pnminfo->np = pnm_types[ctr].np; + pnminfo->asciibody = pnm_types[ctr].asciibody; + pnminfo->float_format = g_ascii_tolower (pnm_types[ctr].name) == 'f'; + pnminfo->maxval = pnm_types[ctr].maxval; + pnminfo->loader = pnm_types[ctr].loader; + } + + if (!pnminfo->loader) + { + g_message (_("File not in a supported format.")); + longjmp (pnminfo->jmpbuf, 1); + } + + pnmscanner_gettoken (scan, buf, BUFLEN); + CHECK_FOR_ERROR (pnmscanner_eof (scan), pnminfo->jmpbuf, + _("Premature end of file.")); + pnminfo->xres = g_ascii_isdigit(*buf) ? atoi (buf) : 0; + CHECK_FOR_ERROR (pnminfo->xres <= 0, pnminfo->jmpbuf, + _("Invalid X resolution.")); + CHECK_FOR_ERROR (pnminfo->xres > GIMP_MAX_IMAGE_SIZE, pnminfo->jmpbuf, + _("Image width is larger than GIMP can handle.")); + + pnmscanner_gettoken (scan, buf, BUFLEN); + CHECK_FOR_ERROR (pnmscanner_eof (scan), pnminfo->jmpbuf, + _("Premature end of file.")); + pnminfo->yres = g_ascii_isdigit (*buf) ? atoi (buf) : 0; + CHECK_FOR_ERROR (pnminfo->yres <= 0, pnminfo->jmpbuf, + _("Invalid Y resolution.")); + CHECK_FOR_ERROR (pnminfo->yres > GIMP_MAX_IMAGE_SIZE, pnminfo->jmpbuf, + _("Image height is larger than GIMP can handle.")); + + if (pnminfo->float_format) + { + gchar *endptr = NULL; + + pnmscanner_gettoken (scan, buf, BUFLEN); + CHECK_FOR_ERROR (pnmscanner_eof (scan), pnminfo->jmpbuf, + _("Premature end of file.")); + + pnminfo->scale_factor = g_ascii_strtod (buf, &endptr); + CHECK_FOR_ERROR (endptr == NULL || *endptr != 0 || errno == ERANGE, + pnminfo->jmpbuf, _("Bogus scale factor.")); + CHECK_FOR_ERROR (!isnormal (pnminfo->scale_factor), + pnminfo->jmpbuf, _("Unsupported scale factor.")); + precision = GIMP_PRECISION_FLOAT_LINEAR; + } + else if (pnminfo->np != 0) /* pbm's don't have a maxval field */ + { + pnmscanner_gettoken (scan, buf, BUFLEN); + CHECK_FOR_ERROR (pnmscanner_eof (scan), pnminfo->jmpbuf, + _("Premature end of file.")); + + pnminfo->maxval = g_ascii_isdigit (*buf) ? atoi (buf) : 0; + CHECK_FOR_ERROR (((pnminfo->maxval<=0) || (pnminfo->maxval>65535)), + pnminfo->jmpbuf, _("Unsupported maximum value.")); + if (pnminfo->maxval < 256) + { + precision = GIMP_PRECISION_U8_GAMMA; + } + else + { + precision = GIMP_PRECISION_U16_GAMMA; + } + } + else + { + precision = GIMP_PRECISION_U8_GAMMA; + } + + /* Create a new image of the proper size and associate the filename + with it. */ + image_ID = gimp_image_new_with_precision + (pnminfo->xres, pnminfo->yres, + (pnminfo->np >= 3) ? GIMP_RGB : GIMP_GRAY, + precision); + + gimp_image_set_filename (image_ID, g_file_get_uri (file)); + + layer_ID = gimp_layer_new (image_ID, _("Background"), + pnminfo->xres, pnminfo->yres, + (pnminfo->np >= 3 ? + GIMP_RGB_IMAGE : GIMP_GRAY_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); + + pnminfo->loader (scan, pnminfo, buffer); + + /* Destroy the scanner */ + pnmscanner_destroy (scan); + + g_object_unref (buffer); + g_free (pnminfo); + g_object_unref (input); + + return image_ID; +} + +static void +pnm_load_ascii (PNMScanner *scan, + PNMInfo *info, + GeglBuffer *buffer) +{ + gint bpc; + guchar *data, *d; + gushort *s; + gint x, y, i, b; + gint start, end, scanlines; + gint np; + gchar buf[BUFLEN]; + gboolean aborted = FALSE; + + np = (info->np) ? (info->np) : 1; + + if (info->maxval > 255) + bpc = 2; + else + bpc = 1; + + /* No overflow as long as gimp_tile_height() < 1365 = 2^(31 - 18) / 6 */ + data = g_new (guchar, gimp_tile_height () * info->xres * np * bpc); + + /* Buffer reads to increase performance */ + pnmscanner_createbuffer (scan, 4096); + + for (y = 0; y < info->yres; y += scanlines) + { + start = y; + end = y + gimp_tile_height (); + end = MIN (end, info->yres); + + scanlines = end - start; + + d = data; + s = (gushort *)d; + + for (i = 0; i < scanlines; i++) + for (x = 0; x < info->xres; x++) + { + for (b = 0; b < np; b++) + { + if (aborted) + { + d[b] = 0; + continue; + } + + /* Truncated files will just have all 0's + at the end of the images */ + if (pnmscanner_eof (scan)) + { + g_message (_("Premature end of file.")); + aborted = TRUE; + + d[b] = 0; + continue; + } + + if (info->np) + pnmscanner_gettoken (scan, buf, BUFLEN); + else + pnmscanner_getsmalltoken (scan, buf); + + if (info->maxval == 1) + { + if (info->np) + d[b] = (*buf == '0') ? 0x00 : 0xff; + else + d[b] = (*buf == '0') ? 0xff : 0x00; /* invert for PBM */ + } + else if (bpc > 1) + { + s[b] = (65535.0 * (((gdouble) (g_ascii_isdigit (*buf) ? + atoi (buf) : 0)) + / (gdouble) (info->maxval))); + } + else + { + d[b] = (255.0 * (((gdouble) (g_ascii_isdigit (*buf) ? + atoi (buf) : 0)) + / (gdouble) (info->maxval))); + } + } + + if (bpc > 1) + { + s += np; + } + else + { + d += np; + } + } + + gegl_buffer_set (buffer, GEGL_RECTANGLE (0, y, info->xres, scanlines), 0, + NULL, data, GEGL_AUTO_ROWSTRIDE); + + gimp_progress_update ((double) y / (double) info->yres); + } + + g_free (data); + + gimp_progress_update (1.0); +} + +static void +pnm_load_raw (PNMScanner *scan, + PNMInfo *info, + GeglBuffer *buffer) +{ + GInputStream *input; + gint bpc; + guchar *data, *d; + gushort *s; + gint x, y, i; + gint start, end, scanlines; + + if (info->maxval > 255) + bpc = 2; + else + bpc = 1; + + /* No overflow as long as gimp_tile_height() < 1365 = 2^(31 - 18) / 6 */ + data = g_new (guchar, gimp_tile_height () * info->xres * info->np * bpc); + + input = pnmscanner_input (scan); + + for (y = 0; y < info->yres; y += scanlines) + { + start = y; + end = y + gimp_tile_height (); + end = MIN (end, info->yres); + scanlines = end - start; + d = data; + s = (gushort *)data; + + for (i = 0; i < scanlines; i++) + { + gsize bytes_read; + GError *error = NULL; + + if (g_input_stream_read_all (input, d, info->xres * info->np * bpc, + &bytes_read, NULL, &error)) + { + CHECK_FOR_ERROR (info->xres * info->np * bpc != bytes_read, + info->jmpbuf, + _("Premature end of file.")); + } + else + { + CHECK_FOR_ERROR (FALSE, info->jmpbuf, "%s", error->message); + } + + if (bpc > 1) + { + for (x = 0; x < info->xres * info->np; x++) + { + int v; + + v = *d++ << 8; + v += *d++; + + s[x] = MIN (v, info->maxval); /* guard against overflow */ + s[x] = 65535.0 * (gdouble) v / (gdouble) info->maxval; + } + s += info->xres * info->np; + } + else + { + if (info->maxval != 255) /* Normalize if needed */ + { + for (x = 0; x < info->xres * info->np; x++) + { + d[x] = MIN (d[x], info->maxval); /* guard against overflow */ + d[x] = 255.0 * (gdouble) d[x] / (gdouble) info->maxval; + } + } + + d += info->xres * info->np; + } + } + + gegl_buffer_set (buffer, + GEGL_RECTANGLE (0, y, info->xres, scanlines), 0, + NULL, data, GEGL_AUTO_ROWSTRIDE); + + gimp_progress_update ((double) y / (double) info->yres); + } + + g_free (data); + + gimp_progress_update (1.0); +} + +static void +pnm_load_rawpbm (PNMScanner *scan, + PNMInfo *info, + GeglBuffer *buffer) +{ + GInputStream *input; + guchar *buf; + guchar curbyte; + guchar *data, *d; + gint x, y, i; + gint start, end, scanlines; + gint rowlen, bufpos; + + input = pnmscanner_input (scan); + + rowlen = (int)ceil ((double)(info->xres)/8.0); + data = g_new (guchar, gimp_tile_height () * info->xres); + buf = g_new (guchar, rowlen); + + for (y = 0; y < info->yres; y += scanlines) + { + start = y; + end = y + gimp_tile_height (); + end = MIN (end, info->yres); + scanlines = end - start; + d = data; + + for (i = 0; i < scanlines; i++) + { + gsize bytes_read; + GError *error = NULL; + + if (g_input_stream_read_all (input, buf, rowlen, + &bytes_read, NULL, &error)) + { + CHECK_FOR_ERROR (rowlen != bytes_read, + info->jmpbuf, + _("Premature end of file.")); + } + else + { + CHECK_FOR_ERROR (FALSE, info->jmpbuf, "%s", error->message); + } + + bufpos = 0; + curbyte = buf[0]; + + for (x = 0; x < info->xres; x++) + { + if ((x % 8) == 0) + curbyte = buf[bufpos++]; + d[x] = (curbyte & 0x80) ? 0x00 : 0xff; + curbyte <<= 1; + } + + d += info->xres; + } + + gegl_buffer_set (buffer, GEGL_RECTANGLE (0, y, info->xres, scanlines), 0, + NULL, data, GEGL_AUTO_ROWSTRIDE); + + gimp_progress_update ((double) y / (double) info->yres); + } + + g_free (buf); + g_free (data); + + gimp_progress_update (1.0); +} + +static void +pnm_load_rawpfm (PNMScanner *scan, + PNMInfo *info, + GeglBuffer *buffer) +{ + GInputStream *input; + gfloat *data; + gint x, y; + gboolean swap_byte_order; + + swap_byte_order = + (info->scale_factor >= 0.0) ^ (G_BYTE_ORDER == G_BIG_ENDIAN); + + data = g_new (gfloat, info->xres * info->np); + + input = pnmscanner_input (scan); + + for (y = info->yres - 1; y >= 0; y--) + { + gsize bytes_read; + GError *error = NULL; + + if (g_input_stream_read_all (input, data, + info->xres * info->np * sizeof (float), + &bytes_read, NULL, &error)) + { + CHECK_FOR_ERROR + (info->xres * info->np * sizeof (float) != bytes_read, + info->jmpbuf, _("Premature end of file.")); + } + else + { + CHECK_FOR_ERROR (FALSE, info->jmpbuf, "%s", error->message); + } + + for (x = 0; x < info->xres * info->np; x++) + { + if (swap_byte_order) + { + union { gfloat f; guint32 i; } v; + + v.f = data[x]; + v.i = GUINT32_SWAP_LE_BE (v.i); + data[x] = v.f; + } + + /* let's see if this is what people want, the PFM specs are a + * little vague about what the scale factor should be used + * for */ + data[x] *= fabsf (info->scale_factor); + /* Keep values smaller than zero. That is in line with what the + * TIFF loader does. If the user doesn't want the negative numbers + * he has to get rid of them afterwards */ + /* data[x] = fmaxf (0.0f, fminf (FLT_MAX, data[x])); */ + } + + gegl_buffer_set (buffer, + GEGL_RECTANGLE (0, y, info->xres, 1), 0, + NULL, data, GEGL_AUTO_ROWSTRIDE); + + if (y % 32 == 0) + gimp_progress_update ((double) (info->yres - y) / (double) info->yres); + } + + g_free (data); + + gimp_progress_update (1.0); +} + +static gboolean +output_write (GOutputStream *output, + gconstpointer buffer, + gsize count, + GError **error) +{ + return g_output_stream_write_all (output, buffer, count, NULL, NULL, error); +} + +/* Writes out mono raw rows */ +static gboolean +pnmsaverow_raw_pbm (PNMRowInfo *ri, + guchar *data, + GError **error) +{ + gint b, p = 0; + gchar *rbcur = ri->rowbuf; + gint32 len = (gint) ceil ((gdouble) (ri->xres) / 8.0); + + for (b = 0; b < len; b++) /* each output byte */ + { + gint i; + + rbcur[b] = 0; + + for (i = 0; i < 8; i++) /* each bit in this byte */ + { + if (p >= ri->xres) + break; + + if (data[p] != ri->zero_is_black) + rbcur[b] |= (char) (1 << (7 - i)); + + p++; + } + } + + return output_write (ri->output, ri->rowbuf, len, error); +} + +/* Writes out mono ascii rows */ +static gboolean +pnmsaverow_ascii_pbm (PNMRowInfo *ri, + guchar *data, + GError **error) +{ + static gint line_len = 0; /* ascii pbm lines must be <= 70 chars long */ + gint32 len = 0; + gint i; + gchar *rbcur = ri->rowbuf; + + for (i = 0; i < ri->xres; i++) + { + if (line_len > 69) + { + rbcur[i] = '\n'; + line_len = 0; + len++; + rbcur++; + } + + if (data[i] == ri->zero_is_black) + rbcur[i] = '0'; + else + rbcur[i] = '1'; + + line_len++; + len++; + } + + *(rbcur+i) = '\n'; + + return output_write (ri->output, ri->rowbuf, len, error); +} + +/* Writes out RGB and grayscale raw rows */ +static gboolean +pnmsaverow_raw (PNMRowInfo *ri, + guchar *data, + GError **error) +{ + gint i; + if (ri->bpc == 2) + { + gushort *d = (gushort *)data; + for (i = 0; i < ri->xres * ri->np; i++) + { + *d = g_htons(*d); + d++; + } + } + return output_write (ri->output, data, ri->xres * ri->np * ri->bpc, error); +} + +/* Writes out RGB and grayscale float rows */ +static gboolean +pnmsaverow_float (PNMRowInfo *ri, + const float *data, + GError **error) +{ + return output_write (ri->output, data, + ri->xres * ri->np * sizeof (float), + error); +} + +/* Writes out indexed raw rows */ +static gboolean +pnmsaverow_raw_indexed (PNMRowInfo *ri, + guchar *data, + GError **error) +{ + gint i; + gchar *rbcur = ri->rowbuf; + + for (i = 0; i < ri->xres; i++) + { + *(rbcur++) = ri->red[*data]; + *(rbcur++) = ri->grn[*data]; + *(rbcur++) = ri->blu[*(data++)]; + } + + return output_write (ri->output, ri->rowbuf, ri->xres * 3, error); +} + +/* Writes out RGB and grayscale ascii rows */ +static gboolean +pnmsaverow_ascii (PNMRowInfo *ri, + guchar *data, + GError **error) +{ + gint i; + gchar *rbcur = ri->rowbuf; + gushort *sdata = (gushort *)data; + + for (i = 0; i < ri->xres * ri->np; i++) + { + if (ri->bpc == 2) + { + sprintf ((gchar *) rbcur,"%d\n", 0xffff & *(sdata++)); + } + else + { + sprintf ((gchar *) rbcur,"%d\n", 0xff & *(data++)); + } + rbcur += strlen (rbcur); + } + + return output_write (ri->output, ri->rowbuf, rbcur - ri->rowbuf, + error); +} + +/* Writes out RGB and grayscale ascii rows */ +static gboolean +pnmsaverow_ascii_indexed (PNMRowInfo *ri, + guchar *data, + GError **error) +{ + gint i; + gchar *rbcur = ri->rowbuf; + + for (i = 0; i < ri->xres; i++) + { + sprintf (rbcur, "%d\n", 0xff & ri->red[*(data)]); + rbcur += strlen (rbcur); + sprintf (rbcur, "%d\n", 0xff & ri->grn[*(data)]); + rbcur += strlen (rbcur); + sprintf (rbcur, "%d\n", 0xff & ri->blu[*(data++)]); + rbcur += strlen (rbcur); + } + + return output_write (ri->output, ri->rowbuf, strlen ((char *) ri->rowbuf), + error); +} + +static gboolean +save_image (GFile *file, + gint32 image_ID, + gint32 drawable_ID, + gboolean pbm, + gboolean float_format, + GError **error) +{ + gboolean status = FALSE; + GOutputStream *output = NULL; + GeglBuffer *buffer = NULL; + const Babl *format; + const gchar *header_string = NULL; + GimpImageType drawable_type; + PNMRowInfo rowinfo; + PNMSaverowFunc saverow = NULL; + guchar red[256]; + guchar grn[256]; + guchar blu[256]; + gchar buf[BUFLEN]; + gint np = 0; + gint xres, yres; + gint ypos, yend; + gint rowbufsize = 0; + gchar *comment = NULL; + + /* Make sure we're not saving an image with an alpha channel */ + if (gimp_drawable_has_alpha (drawable_ID)) + { + g_message (_("Cannot export images with alpha channel.")); + goto out; + } + + gimp_progress_init_printf (_("Exporting '%s'"), + g_file_get_parse_name (file)); + + /* open the file */ + output = G_OUTPUT_STREAM (g_file_replace (file, + NULL, FALSE, G_FILE_CREATE_NONE, + NULL, error)); + if (! output) + goto out; + + buffer = gimp_drawable_get_buffer (drawable_ID); + + xres = gegl_buffer_get_width (buffer); + yres = gegl_buffer_get_height (buffer); + + drawable_type = gimp_drawable_type (drawable_ID); + + switch (gimp_image_get_precision (image_ID)) + { + case GIMP_PRECISION_U8_LINEAR: + case GIMP_PRECISION_U8_GAMMA: + rowinfo.bpc = 1; + break; + default: + rowinfo.bpc = 2; + break; + } + + /* write out magic number */ + if (!float_format && !psvals.raw) + { + if (pbm) + { + header_string = "P1\n"; + format = gegl_buffer_get_format (buffer); + np = 0; + rowbufsize = xres + (int) (xres / 70) + 1; + saverow = pnmsaverow_ascii_pbm; + } + else + { + switch (drawable_type) + { + case GIMP_GRAY_IMAGE: + header_string = "P2\n"; + if (rowinfo.bpc == 1) + { + format = babl_format ("Y' u8"); + rowbufsize = xres * 4; + } + else + { + format = babl_format ("Y' u16"); + rowbufsize = xres * 6; + } + np = 1; + saverow = pnmsaverow_ascii; + break; + + case GIMP_RGB_IMAGE: + header_string = "P3\n"; + if (rowinfo.bpc == 1) + { + format = babl_format ("R'G'B' u8"); + rowbufsize = xres * 12; + } + else + { + format = babl_format ("R'G'B' u16"); + rowbufsize = xres * 18; + } + np = 3; + saverow = pnmsaverow_ascii; + break; + + case GIMP_INDEXED_IMAGE: + header_string = "P3\n"; + format = gegl_buffer_get_format (buffer); + np = 1; + rowbufsize = xres * 12; + saverow = pnmsaverow_ascii_indexed; + break; + + default: + g_warning ("PNM: Unknown drawable_type\n"); + goto out; + } + } + } + else if (!float_format) + { + if (pbm) + { + header_string = "P4\n"; + format = gegl_buffer_get_format (buffer); + np = 0; + rowbufsize = (gint) ceil ((gdouble) xres / 8.0); + saverow = pnmsaverow_raw_pbm; + } + else + { + switch (drawable_type) + { + case GIMP_GRAY_IMAGE: + header_string = "P5\n"; + if (rowinfo.bpc == 1) + { + format = babl_format ("Y' u8"); + rowbufsize = xres; + } + else + { + format = babl_format ("Y' u16"); + rowbufsize = xres * 2; + } + np = 1; + saverow = pnmsaverow_raw; + break; + + case GIMP_RGB_IMAGE: + header_string = "P6\n"; + if (rowinfo.bpc == 1) + { + format = babl_format ("R'G'B' u8"); + rowbufsize = xres * 3; + } + else + { + format = babl_format ("R'G'B' u16"); + rowbufsize = xres * 6; + } + np = 3; + saverow = pnmsaverow_raw; + break; + + case GIMP_INDEXED_IMAGE: + header_string = "P6\n"; + format = gegl_buffer_get_format (buffer); + np = 1; + rowbufsize = xres * 3; + saverow = pnmsaverow_raw_indexed; + break; + + default: + g_warning ("PNM: Unknown drawable_type\n"); + goto out; + } + } + } + else + { + switch (drawable_type) + { + case GIMP_GRAY_IMAGE: + header_string = "Pf\n"; + format = babl_format ("Y float"); + np = 1; + break; + + case GIMP_RGB_IMAGE: + header_string = "PF\n"; + format = babl_format ("RGB float"); + np = 3; + break; + + default: + g_warning ("PFM: Unknown drawable_type\n"); + goto out; + } + } + + if (! output_write (output, header_string, strlen (header_string), error)) + goto out; + + rowinfo.zero_is_black = FALSE; + + if (drawable_type == GIMP_INDEXED_IMAGE) + { + guchar *cmap; + gint num_colors; + + cmap = gimp_image_get_colormap (image_ID, &num_colors); + + if (pbm) + { + /* Test which of the two colors is white and which is black */ + switch (num_colors) + { + case 1: + rowinfo.zero_is_black = (GIMP_RGB_LUMINANCE (cmap[0], + cmap[1], + cmap[2]) < 128); + break; + + case 2: + rowinfo.zero_is_black = (GIMP_RGB_LUMINANCE (cmap[0], + cmap[1], + cmap[2]) < + GIMP_RGB_LUMINANCE (cmap[3], + cmap[4], + cmap[5])); + break; + + default: + g_warning ("Images exported as PBM should be black/white"); + break; + } + } + else + { + const guchar *c = cmap; + gint i; + + for (i = 0; i < num_colors; i++) + { + red[i] = *c++; + grn[i] = *c++; + blu[i] = *c++; + } + + rowinfo.red = red; + rowinfo.grn = grn; + rowinfo.blu = blu; + } + + g_free (cmap); + } + + if (!float_format) + { + /* write out comment string */ + comment = g_strdup_printf("# Created by GIMP version %s PNM plug-in\n", + GIMP_VERSION); + + if (! output_write (output, comment, strlen (comment), error)) + goto out; + } + + /* write out resolution and maxval */ + if (pbm) + g_snprintf (buf, sizeof (buf), "%d %d\n", xres, yres); + else if (!float_format) + g_snprintf (buf, sizeof (buf), "%d %d\n%d\n", xres, yres, + rowinfo.bpc == 1 ? 255 : 65535); + else + g_snprintf (buf, sizeof (buf), "%d %d\n%s\n", xres, yres, + G_BYTE_ORDER == G_BIG_ENDIAN ? "1.0" : "-1.0"); + + if (! output_write (output, buf, strlen (buf), error)) + goto out; + + if (!float_format) + { + guchar *data; + guchar *d; + gchar *rowbuf = NULL; + + /* allocate a buffer for retrieving information from the pixel region */ + data = g_new (guchar, + gimp_tile_height () * xres * + babl_format_get_bytes_per_pixel (format)); + + rowbuf = g_new (gchar, rowbufsize + 1); + + rowinfo.output = output; + rowinfo.rowbuf = rowbuf; + rowinfo.xres = xres; + rowinfo.np = np; + + d = NULL; /* only to please the compiler */ + + /* Write the body out */ + for (ypos = 0; ypos < yres; ypos++) + { + if ((ypos % gimp_tile_height ()) == 0) + { + yend = ypos + gimp_tile_height (); + yend = MIN (yend, yres); + + gegl_buffer_get (buffer, + GEGL_RECTANGLE (0, ypos, xres, yend - ypos), 1.0, + format, data, + GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); + + d = data; + } + + if (! saverow (&rowinfo, d, error)) + { + g_free (rowbuf); + g_free (data); + goto out; + } + + d += xres * (np ? np : 1) * rowinfo.bpc; + + if (ypos % 32 == 0) + gimp_progress_update ((double) ypos / (double) yres); + } + + g_free (rowbuf); + g_free (data); + } + else + { + /* allocate a buffer for retrieving information from the pixel + region */ + gfloat *data = g_new (gfloat, xres * np); + + rowinfo.output = output; + rowinfo.rowbuf = NULL; + rowinfo.xres = xres; + rowinfo.np = np; + + /* Write the body out in reverse row order */ + for (ypos = yres - 1; ypos >= 0; ypos--) + { + gegl_buffer_get (buffer, + GEGL_RECTANGLE (0, ypos, xres, 1), 1.0, + format, data, + GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); + + if (! pnmsaverow_float (&rowinfo, data, error)) + { + g_free (data); + goto out; + } + + if (ypos % 32 == 0) + gimp_progress_update ((double) (yres - ypos) / (double) yres); + } + + g_free (data); + } + + gimp_progress_update (1.0); + status = TRUE; + + out: + if (! status) + { + GCancellable *cancellable = g_cancellable_new (); + + g_cancellable_cancel (cancellable); + g_output_stream_close (output, cancellable, NULL); + g_object_unref (cancellable); + } + + if (comment) + g_free (comment); + if (buffer) + g_object_unref (buffer); + if (output) + g_object_unref (output); + + return status; +} + +static gboolean +save_dialog (void) +{ + GtkWidget *dialog; + GtkWidget *frame; + gboolean run; + + dialog = gimp_export_dialog_new (_("PNM"), PLUG_IN_BINARY, PNM_SAVE_PROC); + + /* file save type */ + frame = gimp_int_radio_group_new (TRUE, _("Data formatting"), + G_CALLBACK (gimp_radio_button_update), + &psvals.raw, psvals.raw, + + _("_Raw"), TRUE, NULL, + _("_ASCII"), FALSE, NULL, + + NULL); + gtk_container_set_border_width (GTK_CONTAINER (frame), 6); + gtk_box_pack_start (GTK_BOX (gimp_export_dialog_get_content_area (dialog)), + frame, FALSE, TRUE, 0); + gtk_widget_show (frame); + + gtk_widget_show (dialog); + + run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK); + + gtk_widget_destroy (dialog); + + return run; +} + + +/**************** FILE SCANNER UTILITIES **************/ + +/* pnmscanner_create --- + * Creates a new scanner based on a file descriptor. The + * look ahead buffer is one character initially. + */ +static PNMScanner * +pnmscanner_create (GInputStream *input) +{ + PNMScanner *s; + gsize bytes_read; + + s = g_new (PNMScanner, 1); + + s->input = input; + s->inbuf = NULL; + s->eof = FALSE; + + if (! g_input_stream_read_all (input, &s->cur, 1, + &bytes_read, NULL, NULL) || + bytes_read != 1) + { + s->eof = TRUE; + } + + return s; +} + +/* pnmscanner_destroy --- + * Destroys a scanner and its resources. Doesn't close the fd. + */ +static void +pnmscanner_destroy (PNMScanner *s) +{ + if (s->inbuf) + g_free (s->inbuf); + + g_free (s); +} + +/* pnmscanner_createbuffer --- + * Creates a buffer so we can do buffered reads. + */ +static void +pnmscanner_createbuffer (PNMScanner *s, + gint bufsize) +{ + gsize bytes_read; + + s->inbuf = g_new (gchar, bufsize); + s->inbufsize = bufsize; + s->inbufpos = 0; + s->inbufvalidsize = 0; + + g_input_stream_read_all (s->input, s->inbuf, bufsize, + &bytes_read, NULL, NULL); + + s->inbufvalidsize = bytes_read; +} + +/* pnmscanner_gettoken --- + * Gets the next token, eating any leading whitespace. + */ +static void +pnmscanner_gettoken (PNMScanner *s, + gchar *buf, + gint bufsize) +{ + gint ctr = 0; + + pnmscanner_eatwhitespace (s); + + while (! s->eof && + ! g_ascii_isspace (s->cur) && + (s->cur != '#') && + (ctr < bufsize)) + { + buf[ctr++] = s->cur; + pnmscanner_getchar (s); + } + + buf[ctr] = '\0'; +} + +/* pnmscanner_getsmalltoken --- + * Gets the next char, eating any leading whitespace. + */ +static void +pnmscanner_getsmalltoken (PNMScanner *s, + gchar *buf) +{ + pnmscanner_eatwhitespace (s); + + if (! s->eof && ! g_ascii_isspace (s->cur) && (s->cur != '#')) + { + *buf = s->cur; + pnmscanner_getchar (s); + } +} + +/* pnmscanner_getchar --- + * Reads a character from the input stream + */ +static void +pnmscanner_getchar (PNMScanner *s) +{ + if (s->inbuf) + { + s->cur = s->inbuf[s->inbufpos++]; + + if (s->inbufpos >= s->inbufvalidsize) + { + if (s->inbufpos > s->inbufvalidsize) + { + s->eof = 1; + } + else + { + gsize bytes_read; + + g_input_stream_read_all (s->input, s->inbuf, s->inbufsize, + &bytes_read, NULL, NULL); + + s->inbufvalidsize = bytes_read; + } + + s->inbufpos = 0; + } + } + else + { + gsize bytes_read; + + s->eof = FALSE; + + if (! g_input_stream_read_all (s->input, &s->cur, 1, + &bytes_read, NULL, NULL) || + bytes_read != 1) + { + s->eof = TRUE; + } + } +} + +/* pnmscanner_eatwhitespace --- + * Eats up whitespace from the input and returns when done or eof. + * Also deals with comments. + */ +static void +pnmscanner_eatwhitespace (PNMScanner *s) +{ + gint state = 0; + + while (!(s->eof) && (state != -1)) + { + switch (state) + { + case 0: /* in whitespace */ + if (s->cur == '#') + { + state = 1; /* goto comment */ + pnmscanner_getchar (s); + } + else if (! g_ascii_isspace (s->cur)) + { + state = -1; + } + else + { + pnmscanner_getchar (s); + } + break; + + case 1: /* in comment */ + if (s->cur == '\n') + state = 0; /* goto whitespace */ + pnmscanner_getchar (s); + break; + } + } +} |