1 /* input-pnm.c: import pnm and pbm files.
2 
3    Copyright (C) 1999, 2000, 2001 Erik Nygren <nygren@mit.edu>
4 
5    This library is free software; you can redistribute it and/or
6    modify it under the terms of the GNU Lesser General Public License
7    as published by the Free Software Foundation; either version 2.1 of
8    the License, or (at your option) any later version.
9 
10    This library is distributed in the hope that it will be useful, but
11    WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    Lesser General Public License for more details.
14 
15    You should have received a copy of the GNU Lesser General Public
16    License along with this library; if not, write to the Free Software
17    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
18    USA. */
19 
20 /*
21  * The pnm reading and writing code was written from scratch by Erik Nygren
22  * (nygren@mit.edu) based on the specifications in the man pages and
23  * does not contain any code from the netpbm or pbmplus distributions.
24  */
25 
26 #ifdef HAVE_CONFIG_H
27 #include "config.h"
28 #endif /* Def: HAVE_CONFIG_H */
29 
30 #include "types.h"
31 #include "bitmap.h"
32 #include "input-pnm.h"
33 #include "message.h"
34 #include "xstd.h"
35 
36 #include <math.h>
37 #include <ctype.h>
38 
39 /* Declare local data types
40  */
41 
42 typedef struct _PNMScanner
43 {
44   FILE   *fd;		      /* The file descriptor of the file being read */
45   char   cur;		      /* The current character in the input stream */
46   int    eof;		      /* Have we reached end of file? */
47   char  *inbuf;	      /* Input buffer - initially 0 */
48   int    inbufsize;	      /* Size of input buffer */
49   int    inbufvalidsize;     /* Size of input buffer with valid data */
50   int    inbufpos;           /* Position in input buffer */
51 } PNMScanner;
52 
53 typedef struct _PNMInfo
54 {
55   unsigned int       xres, yres;	/* The size of the image */
56   int       maxval;		/* For ascii image files, the max value
57 				 * which we need to normalize to */
58   int       np;		/* Number of image planes (0 for pbm) */
59   int       asciibody;		/* 1 if ascii body, 0 if raw body */
60   /* Routine to use to load the pnm body */
61   void    (* loader) (PNMScanner *, struct _PNMInfo *, unsigned char *,
62 		      at_exception_type * excep);
63 } PNMInfo;
64 
65 #define BUFLEN 512		/* The input buffer size for data returned
66 				 * from the scanner.  Note that lines
67 				 * aren't allowed to be over 256 characters
68 				 * by the spec anyways so this shouldn't
69 				 * be an issue. */
70 
71 #define SAVE_COMMENT_STRING "# CREATOR: The GIMP's PNM Filter Version 1.0\n"
72 
73 /* Declare some local functions.
74  */
75 
76 static void   pnm_load_ascii           (PNMScanner *scan,
77 					PNMInfo    *info,
78 					unsigned char  *pixel_rgn,
79 					at_exception_type * excep);
80 static void   pnm_load_raw             (PNMScanner *scan,
81 					PNMInfo    *info,
82 					unsigned char  *pixel_rgn,
83 					at_exception_type * excep);
84 static void   pnm_load_rawpbm          (PNMScanner *scan,
85 					PNMInfo    *info,
86 					unsigned char  *pixel_rgn,
87 					at_exception_type * excep);
88 
89 static void   pnmscanner_destroy       (PNMScanner *s);
90 static void   pnmscanner_createbuffer  (PNMScanner *s,
91 					unsigned int bufsize);
92 static void   pnmscanner_getchar       (PNMScanner *s);
93 static void   pnmscanner_eatwhitespace (PNMScanner *s);
94 static void   pnmscanner_gettoken      (PNMScanner *s,
95 					unsigned char *buf,
96 					unsigned int bufsize);
97 static void   pnmscanner_getsmalltoken (PNMScanner *s,
98 					unsigned char *buf);
99 
100 static PNMScanner * pnmscanner_create  (FILE        *fd);
101 
102 
103 #define pnmscanner_eof(s) ((s)->eof)
104 #define pnmscanner_fd(s) ((s)->fd)
105 
106 static struct struct_pnm_types
107 {
108   char   name;
109   int    np;
110   int    asciibody;
111   int    maxval;
112   void (* loader) (PNMScanner *, struct _PNMInfo *, unsigned char *pixel_rgn,
113 		   at_exception_type * excep);
114 } pnm_types[] =
115 {
116   { '1', 0, 1,   1, pnm_load_ascii },  /* ASCII PBM */
117   { '2', 1, 1, 255, pnm_load_ascii },  /* ASCII PGM */
118   { '3', 3, 1, 255, pnm_load_ascii },  /* ASCII PPM */
119   { '4', 0, 0,   1, pnm_load_rawpbm }, /* RAW   PBM */
120   { '5', 1, 0, 255, pnm_load_raw },    /* RAW   PGM */
121   { '6', 3, 0, 255, pnm_load_raw },    /* RAW   PPM */
122   {  0 , 0, 0,   0, NULL}
123 };
124 
input_pnm_reader(at_string filename,at_input_opts_type * opts,at_msg_func msg_func,at_address msg_data)125 at_bitmap_type input_pnm_reader (at_string filename,
126 				 at_input_opts_type * opts,
127 				 at_msg_func msg_func,
128 				 at_address msg_data)
129 {
130   char buf[BUFLEN];		/* buffer for random things like scanning */
131   PNMInfo *pnminfo;
132   PNMScanner * volatile scan;
133   int ctr;
134   FILE* fd;
135   at_bitmap_type bitmap = at_bitmap_init(NULL, 0, 0, 0);
136   at_exception_type excep = at_exception_new(msg_func, msg_data);
137 
138   /* open the file */
139   fd = fopen (filename, "rb");
140 
141   if (fd == NULL)
142     {
143       LOG("pnm filter: can't open file\n");
144       at_exception_fatal(&excep, "pnm filter: can't open file");
145       return (bitmap);
146     }
147 
148   /* allocate the necessary structures */
149   pnminfo = (PNMInfo *) malloc (sizeof (PNMInfo));
150 
151   scan = NULL;
152   /* set error handling */
153 
154   scan = pnmscanner_create(fd);
155 
156   /* Get magic number */
157   pnmscanner_gettoken (scan, (unsigned char *)buf, BUFLEN);
158   if (pnmscanner_eof(scan))
159     {
160       LOG("pnm filter: premature end of file\n");
161       at_exception_fatal (&excep, "pnm filter: premature end of file");
162       goto cleanup;
163     }
164   if (buf[0] != 'P' || buf[2])
165     {
166       LOG1("pnm filter: %s is not a valid file\n", filename);
167       at_exception_fatal (&excep, "pnm filter: invalid file");
168       goto cleanup;
169     }
170 
171   /* Look up magic number to see what type of PNM this is */
172   for (ctr=0; pnm_types[ctr].name; ctr++)
173     if (buf[1] == pnm_types[ctr].name)
174       {
175 	pnminfo->np        = pnm_types[ctr].np;
176 	pnminfo->asciibody = pnm_types[ctr].asciibody;
177 	pnminfo->maxval    = pnm_types[ctr].maxval;
178 	pnminfo->loader    = pnm_types[ctr].loader;
179       }
180   if (!pnminfo->loader)
181     {
182       LOG ("pnm filter: file not in a supported format\n");
183       at_exception_fatal (&excep, "pnm filter: file not in a supported format");
184       goto cleanup;
185     }
186 
187 
188   pnmscanner_gettoken(scan, (unsigned char *)buf, BUFLEN);
189   if (pnmscanner_eof(scan))
190     {
191       LOG ("pnm filter: premature end of file\n");
192       at_exception_fatal (&excep, "pnm filter: premature end of file");
193       goto cleanup;
194     }
195   pnminfo->xres = isdigit(*buf)?atoi(buf):0;
196   if (pnminfo->xres<=0)
197     {
198       LOG ("pnm filter: invalid xres while loading\n");
199       at_exception_fatal (&excep, "pnm filter: premature end of file");
200       goto cleanup;
201     }
202 
203   pnmscanner_gettoken(scan, (unsigned char *)buf, BUFLEN);
204   if (pnmscanner_eof(scan))
205     {
206       LOG ("pnm filter: premature end of file\n");
207       at_exception_fatal (&excep, "pnm filter: premature end of file");
208       goto cleanup;
209     }
210   pnminfo->yres = isdigit(*buf)?atoi(buf):0;
211   if (pnminfo->yres<=0)
212     {
213       LOG ("pnm filter: invalid yres while loading\n");
214       at_exception_fatal (&excep, "pnm filter: invalid yres while loading");
215       goto cleanup;
216     }
217 
218 
219   if (pnminfo->np != 0)		/* pbm's don't have a maxval field */
220     {
221       pnmscanner_gettoken(scan, (unsigned char *)buf, BUFLEN);
222       if (pnmscanner_eof(scan))
223 	{
224 	  LOG ("pnm filter: premature end of file\n");
225 	  at_exception_fatal (&excep, "pnm filter: invalid yres while loading");
226 	  goto cleanup;
227 	}
228 
229       pnminfo->maxval = isdigit(*buf)?atoi(buf):0;
230       if ((pnminfo->maxval<=0)
231 	  || (pnminfo->maxval>255 && !pnminfo->asciibody))
232 	{
233 	  LOG ("pnm filter: invalid maxval while loading\n");
234 	  at_exception_fatal (&excep, "pnm filter: invalid maxval while loading");
235 	  goto cleanup;
236 	}
237     }
238 
239   bitmap = at_bitmap_init(NULL,
240 			  (unsigned short) pnminfo->xres,
241 			  (unsigned short) pnminfo->yres,
242 			  (pnminfo->np)?(pnminfo->np):1);
243   pnminfo->loader (scan, pnminfo, AT_BITMAP_BITS (bitmap), &excep);
244 
245  cleanup:
246   /* Destroy the scanner */
247   pnmscanner_destroy (scan);
248 
249   /* free the structures */
250   free (pnminfo);
251 
252   /* close the file */
253   fclose (fd);
254 
255   return (bitmap);
256 }
257 
258 static void
pnm_load_ascii(PNMScanner * scan,PNMInfo * info,unsigned char * data,at_exception_type * excep)259 pnm_load_ascii (PNMScanner *scan,
260 		PNMInfo    *info,
261 		unsigned char *data,
262 		at_exception_type * excep)
263 {
264   unsigned char *d;
265   unsigned int x;
266   int   i, b;
267   int   start, end, scanlines;
268   int   np;
269   char           buf[BUFLEN];
270 
271   np = (info->np)?(info->np):1;
272 
273   /* Buffer reads to increase performance */
274   pnmscanner_createbuffer(scan, 4096);
275 
276       start = 0;
277       end = info->yres;
278       scanlines = end - start;
279       d = data;
280 
281       for (i = 0; i < scanlines; i++)
282 	for (x = 0; x < info->xres; x++)
283 	  {
284 	    for (b = 0; b < np; b++)
285 	      {
286 		/* Truncated files will just have all 0's at the end of the images */
287 		if (pnmscanner_eof(scan))
288 		  {
289 		    LOG ("pnm filter: premature end of file\n");
290 		    at_exception_fatal(excep, "pnm filter: premature end of file");
291 		    return;
292 		  }
293 		if (info->np)
294 		  pnmscanner_gettoken(scan, (unsigned char *)buf, BUFLEN);
295 		else
296 		  pnmscanner_getsmalltoken(scan, (unsigned char *)buf);
297 		switch (info->maxval)
298 		  {
299 		  case 255:
300 		    d[b] = (unsigned char) (isdigit(*buf)?atoi(buf):0);
301 		    break;
302 		  case 1:
303 		    d[b] = (*buf=='0')?0xff:0x00;
304 		    break;
305 		  default:
306 		    d[b] = (unsigned char)(255.0*(((double)(isdigit(*buf)?atoi(buf):0))
307 						  / (double)(info->maxval)));
308 		  }
309 	      }
310 
311 	    d += np;
312 	  }
313 }
314 
315 static void
pnm_load_raw(PNMScanner * scan,PNMInfo * info,unsigned char * data,at_exception_type * excep)316 pnm_load_raw (PNMScanner *scan,
317 	      PNMInfo    *info,
318 	      unsigned char  *data,
319 	      at_exception_type * excep)
320 {
321   unsigned char *d;
322   unsigned int   x, i;
323   unsigned int   start, end, scanlines;
324   FILE          *fd;
325 
326   fd = pnmscanner_fd(scan);
327 
328       start = 0;
329       end = info->yres;
330       scanlines = end - start;
331       d = data;
332 
333       for (i = 0; i < scanlines; i++)
334 	{
335 	  if (info->xres*info->np
336 	      != fread(d, 1, info->xres*info->np, fd))
337 	    {
338 	      LOG ("pnm filter: premature end of file\n");
339 	      at_exception_fatal (excep, "pnm filter: premature end of file\n");
340 	      return ;
341 	    }
342 
343 	  if (info->maxval != 255)	/* Normalize if needed */
344 	    {
345 	      for (x = 0; x < info->xres * info->np; x++)
346 		d[x] = (unsigned char)(255.0*(double)(d[x]) / (double)(info->maxval));
347 	    }
348 
349 	  d += info->xres * info->np;
350 	}
351 }
352 
353 static void
pnm_load_rawpbm(PNMScanner * scan,PNMInfo * info,unsigned char * data,at_exception_type * excep)354 pnm_load_rawpbm (PNMScanner *scan,
355 		 PNMInfo    *info,
356 		 unsigned char  *data,
357 		 at_exception_type * excep)
358 {
359   unsigned char *buf;
360   unsigned char  curbyte;
361   unsigned char *d;
362   unsigned int   x, i;
363   unsigned int   start, end, scanlines;
364   FILE          *fd;
365   unsigned int            rowlen, bufpos;
366 
367   fd = pnmscanner_fd(scan);
368   rowlen = (unsigned int)ceil((double)(info->xres)/8.0);
369   buf = (unsigned char *)malloc(rowlen*sizeof(unsigned char));
370 
371       start = 0;
372       end = info->yres;
373       scanlines = end - start;
374       d = data;
375 
376       for (i = 0; i < scanlines; i++)
377 	{
378 	  if (rowlen != fread(buf, 1, rowlen, fd))
379 	    {
380 	      LOG ("pnm filter: error reading file\n");
381 	      at_exception_fatal (excep, "pnm filter: error reading file");
382 	      goto cleanup;
383 	    }
384 	  bufpos = 0;
385 	  curbyte = buf[0];
386 
387 	  for (x = 0; x < info->xres; x++)
388 	    {
389 	      if ((x % 8) == 0)
390 		curbyte = buf[bufpos++];
391 	      d[x] = (curbyte&0x80) ? 0x00 : 0xff;
392 	      curbyte <<= 1;
393 	    }
394 
395 	  d += info->xres;
396 	}
397  cleanup:
398   free(buf);
399 }
400 
401 /**************** FILE SCANNER UTILITIES **************/
402 
403 /* pnmscanner_create ---
404  *    Creates a new scanner based on a file descriptor.  The
405  *    look ahead buffer is one character initially.
406  */
407 static PNMScanner *
pnmscanner_create(FILE * fd)408 pnmscanner_create (FILE *fd)
409 {
410   PNMScanner *s;
411 
412   XMALLOC (s, sizeof(PNMScanner));
413   s->fd = fd;
414   s->inbuf = 0;
415   s->eof = !fread(&(s->cur), 1, 1, s->fd);
416   return(s);
417 }
418 
419 /* pnmscanner_destroy ---
420  *    Destroys a scanner and its resources.  Doesn't close the fd.
421  */
422 static void
pnmscanner_destroy(PNMScanner * s)423 pnmscanner_destroy (PNMScanner *s)
424 {
425   if (s->inbuf) free(s->inbuf);
426   free(s);
427 }
428 
429 /* pnmscanner_createbuffer ---
430  *    Creates a buffer so we can do buffered reads.
431  */
432 static void
pnmscanner_createbuffer(PNMScanner * s,unsigned int bufsize)433 pnmscanner_createbuffer (PNMScanner *s,
434 			 unsigned int bufsize)
435 {
436   s->inbuf = (char *)malloc(sizeof(char)*bufsize);
437   s->inbufsize = bufsize;
438   s->inbufpos = 0;
439   s->inbufvalidsize = fread(s->inbuf, 1, bufsize, s->fd);
440 }
441 
442 /* pnmscanner_gettoken ---
443  *    Gets the next token, eating any leading whitespace.
444  */
445 static void
pnmscanner_gettoken(PNMScanner * s,unsigned char * buf,unsigned int bufsize)446 pnmscanner_gettoken (PNMScanner *s,
447 		     unsigned char *buf,
448 		     unsigned int bufsize)
449 {
450   unsigned int ctr=0;
451 
452   pnmscanner_eatwhitespace(s);
453   while (!(s->eof) && !isspace(s->cur) && (s->cur != '#') && (ctr<bufsize))
454     {
455       buf[ctr++] = s->cur;
456       pnmscanner_getchar(s);
457     }
458   buf[ctr] = '\0';
459 }
460 
461 /* pnmscanner_getsmalltoken ---
462  *    Gets the next char, eating any leading whitespace.
463  */
464 static void
pnmscanner_getsmalltoken(PNMScanner * s,unsigned char * buf)465 pnmscanner_getsmalltoken (PNMScanner *s,
466 			  unsigned char *buf)
467 {
468   pnmscanner_eatwhitespace(s);
469   if (!(s->eof) && !isspace(s->cur) && (s->cur != '#'))
470     {
471       *buf = s->cur;
472       pnmscanner_getchar(s);
473     }
474 }
475 
476 /* pnmscanner_getchar ---
477  *    Reads a character from the input stream
478  */
479 static void
pnmscanner_getchar(PNMScanner * s)480 pnmscanner_getchar (PNMScanner *s)
481 {
482   if (s->inbuf)
483     {
484       s->cur = s->inbuf[s->inbufpos++];
485       if (s->inbufpos >= s->inbufvalidsize)
486 	{
487 	  if (s->inbufsize > s->inbufvalidsize)
488 	    s->eof = 1;
489 	  else
490 	    s->inbufvalidsize = fread(s->inbuf, 1, s->inbufsize, s->fd);
491 	  s->inbufpos = 0;
492 	}
493     }
494   else
495     s->eof = !fread(&(s->cur), 1, 1, s->fd);
496 }
497 
498 /* pnmscanner_eatwhitespace ---
499  *    Eats up whitespace from the input and returns when done or eof.
500  *    Also deals with comments.
501  */
502 static void
pnmscanner_eatwhitespace(PNMScanner * s)503 pnmscanner_eatwhitespace (PNMScanner *s)
504 {
505   int state = 0;
506 
507   while (!(s->eof) && (state != -1))
508     {
509       switch (state)
510 	{
511 	case 0:  /* in whitespace */
512 	  if (s->cur == '#')
513 	    {
514 	      state = 1;  /* goto comment */
515 	      pnmscanner_getchar(s);
516 	    }
517 	  else if (!isspace(s->cur))
518 	    state = -1;
519 	  else
520 	    pnmscanner_getchar(s);
521 	  break;
522 
523 	case 1:  /* in comment */
524 	  if (s->cur == '\n')
525 	    state = 0;  /* goto whitespace */
526 	  pnmscanner_getchar(s);
527 	  break;
528 	}
529     }
530 }
531