1/* This code is ripped from Autotrace-0.29. Small modifications by pts. */
2
3/* input-pnm.ci:
4 * The pnm reading and writing code was written from scratch by Erik Nygren
5 * (nygren@mit.edu) based on the specifications in the man pages and
6 * does not contain any code from the netpbm or pbmplus distributions.
7 */
8
9#ifdef __GNUC__
10#ifndef __clang__
11#pragma implementation
12#endif
13#endif
14
15/* #include "types.h" */
16#include "at_bitmap.h"
17/* #include "input-pnm.h" */
18/* #include "message.h" */
19/* #include "xstd.h" */
20
21/* #include <math.h> -- ceil(); emulated */
22#include <stdlib.h> /* atoi(...) */
23
24/**** pts ****/
25/* #include <ctype.h> */
26#define isspace(c) ((c)=='\0' || (c)==' ' || ((unsigned char)((c)-011)<=(unsigned char)(015-011)))
27/* ^^^ not strictly POSIX C locale */
28#define isdigit(c) ((unsigned char)(c-'0')<=(unsigned char)('9'-'0'))
29
30#if 0
31#  define PNMFILE FILE
32#  define fread_PNMFILE(s,slen,f) fread(s, 1, slen, f)
33#else
34#  define PNMFILE /*Filter::UngetFILED*/GenBuffer::Readable
35#  define fread_PNMFILE(s,slen,f) f->vi_read(s, slen)
36#endif
37
38/* Declare local data types
39 */
40
41typedef struct _PNMScanner
42{
43  PNMFILE   *fd;		      /* The file descriptor of the file being read */
44  char   cur;		      /* The current character in the input stream */
45  int    eof;		      /* Have we reached end of file? */
46} PNMScanner;
47
48typedef struct _PNMInfo
49{
50  unsigned int       xres, yres;	/* The size of the image */
51  int       maxval;		/* For ascii image files, the max value
52				 * which we need to normalize to */
53  int       np;		/* Number of image planes (0 for pbm) */
54  int       asciibody;		/* 1 if ascii body, 0 if raw body */
55  /* Routine to use to load the pnm body */
56  void    (* loader) (PNMScanner *, struct _PNMInfo *, unsigned char *);
57} PNMInfo;
58
59/* Contains the information needed to write out PNM rows */
60typedef struct _PNMRowInfo
61{
62  PNMFILE   *fd;		/* File descriptor */
63  char  *rowbuf;	/* Buffer for writing out rows */
64  int    xres;		/* X resolution */
65  int    np;		/* Number of planes */
66  unsigned char *red;		/* Colormap red */
67  unsigned char *grn;		/* Colormap green */
68  unsigned char *blu;		/* Colormap blue */
69} PNMRowInfo;
70
71/* Save info  */
72typedef struct
73{
74  int  raw;  /*  raw or ascii  */
75} PNMSaveVals;
76
77typedef struct
78{
79  int  run;  /*  run  */
80} PNMSaveInterface;
81
82#define PNM_BUFLEN 512		/* The input buffer size for data returned
83				 * from the scanner.  Note that lines
84				 * aren't allowed to be over 256 characters
85				 * by the spec anyways so this shouldn't
86				 * be an issue. */
87
88#define SAVE_COMMENT_STRING "# CREATOR: The GIMP's PNM Filter Version 1.0\n"
89
90/* Declare some local functions.
91 */
92
93static void   pnm_load_ascii           (PNMScanner *scan,
94					PNMInfo    *info,
95					unsigned char  *pixel_rgn);
96static void   pnm_load_raw             (PNMScanner *scan,
97					PNMInfo    *info,
98					unsigned char  *pixel_rgn);
99static void   pnm_load_rawpbm          (PNMScanner *scan,
100					PNMInfo    *info,
101					unsigned char  *pixel_rgn);
102
103static void   pnmscanner_destroy       (PNMScanner *s);
104#if 0
105static void   pnmscanner_createbuffer  (PNMScanner *s,
106					unsigned int bufsize);
107static void   pnmscanner_getchar       (PNMScanner *s);
108static void   pnmscanner_getsmalltoken (PNMScanner *s, unsigned char *buf);
109#endif
110static void   pnmscanner_eatwhitespace (PNMScanner *s);
111static void   pnmscanner_gettoken      (PNMScanner *s,
112					unsigned char *buf,
113					unsigned int bufsize);
114static unsigned pnmscanner_getint(PNMScanner *s);
115
116static PNMScanner * pnmscanner_create  (PNMFILE *fd);
117
118
119#define pnmscanner_eof(s) ((s)->eof)
120#define pnmscanner_fd(s) ((s)->fd)
121
122#if 0
123/* pnmscanner_getchar ---
124 *    Reads a character from the input stream
125 */
126static void
127pnmscanner_getchar (PNMScanner *s)
128{
129  if (s->inbuf)
130    {
131      s->cur = s->inbuf[s->inbufpos++];
132      if (s->inbufpos >= s->inbufvalidsize)
133	{
134	  if (s->inbufsize > s->inbufvalidsize)
135	    s->eof = 1;
136	  else
137	    s->inbufvalidsize = fread(s->inbuf, 1, s->inbufsize, s->fd);
138	  s->inbufpos = 0;
139	}
140    }
141  else
142    s->eof = !fread(&(s->cur), 1, 1, s->fd);
143}
144#endif
145
146#define pnmscanner_getchar(s) do { s->eof = !fread_PNMFILE(&(s->cur), 1, s->fd); } while(0)
147
148/* pnmscanner_eatwhitespace ---
149 *    Eats up whitespace from the input and returns when done or eof.
150 *    Also deals with comments.
151 */
152static inline void pnmscanner_eatwhitespace(PNMScanner *s) { /**** pts ****/
153  while (1) {
154    if (s->cur=='#') {
155      do pnmscanner_getchar(s); while (s->cur!='\n');
156    } else if (!isspace(s->cur)) {
157      break;
158    }
159    pnmscanner_getchar(s);
160  }
161}
162
163
164static struct struct_pnm_types
165{
166  char   name;
167  int    np;
168  int    asciibody;
169  int    maxval;
170  void (* loader) (PNMScanner *, struct _PNMInfo *, unsigned char *pixel_rgn);
171} pnm_types[] =
172{
173  { '1', 0, 1,   1, pnm_load_ascii },  /* ASCII PBM */
174  { '2', 1, 1, 255, pnm_load_ascii },  /* ASCII PGM */
175  { '3', 3, 1, 255, pnm_load_ascii },  /* ASCII PPM */
176  { '4', 0, 0,   1, pnm_load_rawpbm }, /* RAW   PBM */
177  { '5', 1, 0, 255, pnm_load_raw },    /* RAW   PGM */
178  { '6', 3, 0, 255, pnm_load_raw },    /* RAW   PPM */
179  {  0 , 0, 0,   0, NULL}
180};
181
182#if PTS_SAM2P
183bitmap_type pnm_load_image (PNMFILE* filename)
184#else
185bitmap_type pnm_load_image (at_string filename)
186#endif
187{
188  char buf[PNM_BUFLEN];		/* buffer for random things like scanning */
189  PNMInfo *pnminfo;
190  PNMScanner * volatile scan;
191  int ctr;
192  PNMFILE* fd;
193  bitmap_type bitmap;
194
195  #if PTS_SAM2P /**** pts ****/
196    fd=filename;
197  #else
198  /* open the file */
199  fd = xfopen (filename, "rb");
200  if (fd == NULL)
201    {
202      FATAL("PNM: can't open file\n");
203      BITMAP_BITS (bitmap) = NULL;
204      BITMAP_WIDTH (bitmap) = 0;
205      BITMAP_HEIGHT (bitmap) = 0;
206      BITMAP_PLANES (bitmap) = 0;
207      return (bitmap);
208    }
209  #endif
210
211
212  /* allocate the necessary structures */
213  /* pnminfo = (PNMInfo *) malloc (sizeof (PNMInfo)); */
214  XMALLOCT(pnminfo, PNMInfo*, sizeof(PNMInfo));
215
216  scan = NULL;
217  /* set error handling */
218
219  scan = pnmscanner_create(fd);
220
221  /* Get magic number */
222  pnmscanner_gettoken (scan, (unsigned char *)buf, PNM_BUFLEN);
223  if (pnmscanner_eof(scan))
224    FATALP ("PNM: premature end of file");
225  if (buf[0] != 'P' || buf[2])
226    FATALP ("PNM: is not a valid file");
227
228  /* Look up magic number to see what type of PNM this is */
229  for (ctr=0; pnm_types[ctr].name; ctr++)
230    if (buf[1] == pnm_types[ctr].name)
231      {
232	pnminfo->np        = pnm_types[ctr].np;
233	pnminfo->asciibody = pnm_types[ctr].asciibody;
234	pnminfo->maxval    = pnm_types[ctr].maxval;
235	pnminfo->loader    = pnm_types[ctr].loader;
236      }
237  if (!pnminfo->loader)
238      FATALP ("PNM: file not in a supported format");
239
240  pnmscanner_gettoken(scan, (unsigned char *)buf, PNM_BUFLEN);
241  if (pnmscanner_eof(scan))
242    FATALP ("PNM: premature end of file");
243  pnminfo->xres = isdigit(*buf)?atoi(buf):0;
244  if (pnminfo->xres<=0)
245    FATALP ("PNM: invalid xres while loading");
246
247  pnmscanner_gettoken(scan, (unsigned char *)buf, PNM_BUFLEN);
248  if (pnmscanner_eof(scan))
249    FATALP ("PNM: premature end of file");
250  pnminfo->yres = isdigit(*buf)?atoi(buf):0;
251  if (pnminfo->yres<=0)
252    FATALP ("PNM: invalid yres while loading");
253
254  if (pnminfo->np != 0)		/* pbm's don't have a maxval field */
255    {
256      pnmscanner_gettoken(scan, (unsigned char *)buf, PNM_BUFLEN);
257      if (pnmscanner_eof(scan))
258        FATALP ("PNM: premature end of file");
259
260      pnminfo->maxval = isdigit(*buf)?atoi(buf):0;
261      if ((pnminfo->maxval<=0)
262		|| (pnminfo->maxval>255 && !pnminfo->asciibody))
263        FATALP ("PNM: invalid maxval while loading");
264    }
265
266  BITMAP_WIDTH (bitmap) = (at_dimen_t) pnminfo->xres;
267  BITMAP_HEIGHT (bitmap) = (at_dimen_t) pnminfo->yres;
268
269  BITMAP_PLANES (bitmap) = (pnminfo->np)?(pnminfo->np):1;
270  /* BITMAP_BITS (bitmap) = (unsigned char *) malloc (pnminfo->yres * pnminfo->xres * BITMAP_PLANES (bitmap)); */
271  XMALLOCT(BITMAP_BITS (bitmap), unsigned char *, pnminfo->yres * pnminfo->xres * BITMAP_PLANES (bitmap));
272  pnminfo->loader (scan, pnminfo, BITMAP_BITS (bitmap));
273  /* vvv Dat: We detect truncation late truncated files will just have garbage :-( */
274  if (pnmscanner_eof(scan))
275    FATALP ("PNM: truncated image data");
276
277  /* Destroy the scanner */
278  pnmscanner_destroy (scan);
279
280  /* free the structures */
281  /* free (pnminfo); */
282  XFREE(pnminfo);
283
284  /* close the file */
285  /* xfclose (fd, filename); */
286
287  return (bitmap);
288}
289
290static void
291pnm_load_ascii (PNMScanner *scan,
292		PNMInfo    *info,
293		unsigned char *data)
294{
295  register unsigned char *d, *dend;
296  unsigned u, s;
297  #if 0 /**** pts ****/
298    /* Buffer reads to increase performance */
299    /* !! convert(1) is faster -- maybe buffering helps? */
300    pnmscanner_createbuffer(scan, 4096);
301  #endif
302  d = data;
303  if (info->np==0) { /* PBM */
304    dend=d+info->xres*info->yres;
305    while (d!=dend) {
306      /* pnmscanner_getsmalltoken(scan, (unsigned char *)buf); */
307      pnmscanner_eatwhitespace(scan);
308      *d++=-(scan->cur=='0');
309      pnmscanner_getchar(scan);
310    }
311  } else { /* PGM or PPM */ /**** pts ****/
312    dend=d+info->xres*info->yres*info->np;
313    switch (s=info->maxval) {
314     case 255:
315      while (d!=dend) {
316        *d++=pnmscanner_getint(scan); /* Dat: removed isdigit() */
317      }
318      break;
319     case 15:
320      while (d!=dend) {
321        *d++ = pnmscanner_getint(scan)*17;
322      }
323      break;
324     case 3:
325      while (d!=dend) {
326        *d++ = pnmscanner_getint(scan)*85;
327      }
328      break;
329     case 0: /* avoid division by 0 */
330     case 1:
331      while (d!=dend) {
332        *d++ = -(0==pnmscanner_getint(scan)); /* (*buf=='0')?0xff:0x00; */
333      }
334     default:
335      while (d!=dend) {
336	u=pnmscanner_getint(scan);
337	*d++ = (0UL+u*255UL+(s>>1))/s; /* always <= 255 */
338      }
339    }
340  }
341}
342
343static void
344pnm_load_raw (PNMScanner *scan,
345	      PNMInfo    *info,
346	      unsigned char  *data)
347{
348  unsigned char *d, *dend;
349  unsigned s=info->maxval;
350  slen_t delta, scanlines;
351  PNMFILE *fd=pnmscanner_fd(scan);
352
353  scanlines = info->yres;
354  d = data;
355  delta=info->xres * info->np;
356  dend=d+delta*scanlines;
357  while (d!=dend) {
358    if (info->xres*info->np != fread_PNMFILE((char*)d, delta, fd)) return;
359    d+=delta;
360  }
361  d=data;
362  switch (s=info->maxval) { /**** pts ****/
363   case 1: case 0:
364    for (; d!=dend; d++) if (*d!=0) *d=255;
365    break;
366   case 3:
367    while (d!=dend) *d++*=85;
368    break;
369   case 15:
370    while (d!=dend) *d++*=17;
371    break;
372   case 255:
373    break;
374   default:
375    for (; d!=dend; d++) *d=(0UL+*d*255UL+(s>>1))/s; /* always <= 255 */
376    break;
377  }
378}
379
380static void
381pnm_load_rawpbm (PNMScanner *scan,
382		 PNMInfo    *info,
383		 unsigned char  *data)
384{
385  unsigned char *buf;
386  unsigned char  curbyte;
387  unsigned char *d;
388  unsigned int   x, i;
389  unsigned int   start, end, scanlines;
390  PNMFILE          *fd;
391  unsigned int            rowlen, bufpos;
392
393  fd = pnmscanner_fd(scan);
394  /****pts****/ /* rowlen = (unsigned int)ceil((double)(info->xres)/8.0);*/
395  rowlen=(info->xres+7)>>3;
396  /* buf = (unsigned char *)malloc(rowlen*sizeof(unsigned char)); */
397  XMALLOCT(buf, unsigned char*, rowlen*sizeof(unsigned char));
398
399      start = 0;
400      end = info->yres;
401      scanlines = end - start;
402      d = data;
403
404      for (i = 0; i < scanlines; i++)
405	{
406	  if (rowlen != fread_PNMFILE((char*)buf, rowlen, fd))
407            FATALP ("PNM: error reading file");
408	  bufpos = 0;
409	  curbyte = buf[0];
410
411	  for (x = 0; x < info->xres; x++)
412	    {
413	      if ((x % 8) == 0) {
414		curbyte = buf[bufpos++];
415		/* // if (curbyte!=0) printf("%d <%u>\n", x, curbyte); */
416              }
417	      /* // if (curbyte!=0) printf("[%u]\n", curbyte); */
418	      d[x] = (curbyte&0x80) ? 0x00 : 0xff;
419	      curbyte <<= 1;
420	    }
421
422	  d += info->xres;
423	}
424
425  XFREE(buf);
426}
427
428/**************** FILE SCANNER UTILITIES **************/
429
430/* pnmscanner_create ---
431 *    Creates a new scanner based on a file descriptor.  The
432 *    look ahead buffer is one character initially.
433 */
434static PNMScanner *
435pnmscanner_create (PNMFILE *fd)
436{
437  PNMScanner *s;
438
439  XMALLOCT (s, PNMScanner*, sizeof(PNMScanner));
440  s->fd = fd;
441  s->eof = !fread_PNMFILE(&(s->cur), 1, s->fd);
442  return s;
443}
444
445/* pnmscanner_destroy ---
446 *    Destroys a scanner and its resources.  Doesn't close the fd.
447 */
448static void
449pnmscanner_destroy (PNMScanner *s)
450{
451  XFREE(s);
452}
453
454#if 0 /**** pts ****/
455/* pnmscanner_createbuffer ---
456 *    Creates a buffer so we can do buffered reads.
457 */
458static void
459pnmscanner_createbuffer (PNMScanner *s,
460			 unsigned int bufsize)
461{
462  /* s->inbuf = (char *)malloc(sizeof(char)*bufsize); */
463  XMALLOCT(s->inbuf, char*, sizeof(char)*bufsize);
464  s->inbufsize = bufsize;
465  s->inbufpos = 0;
466  s->inbufvalidsize = fread(s->inbuf, 1, bufsize, s->fd);
467}
468#endif
469
470/* pnmscanner_gettoken ---
471 *    Gets the next token, eating any leading whitespace.
472 */
473static void pnmscanner_gettoken (PNMScanner *s,
474		     unsigned char *buf,
475		     unsigned int bufsize)
476{
477  unsigned char *bufend=buf+bufsize-1;
478  pnmscanner_eatwhitespace(s);
479  while (!pnmscanner_eof(s) && !isspace(s->cur) && s->cur!='#') {
480    if (buf!=bufend) *buf++=s->cur;
481    pnmscanner_getchar(s);
482  }
483  *buf='\0';
484}
485
486static unsigned pnmscanner_getint(PNMScanner *s) {
487  unsigned ret=0;
488  pnmscanner_eatwhitespace(s);
489  while (!pnmscanner_eof(s)) {
490    if (isdigit(s->cur)) ret=10*ret+s->cur-'0';
491    else if (isspace(s->cur) || s->cur=='#') break;
492    pnmscanner_getchar(s);
493  }
494  return ret;
495}
496