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