- renamed io.h to imio.h to prevent problems building under cygwin.
[imager.git] / pnm.c
1 #include "image.h"
2 #include "log.h"
3 #include "iolayer.h"
4
5 #include <stdlib.h>
6 #include <errno.h>
7
8
9 /*
10 =head1 NAME
11
12 pnm.c - implements reading and writing ppm/pnm/pbm files, uses io layer.
13
14 =head1 SYNOPSIS
15
16    io_glue *ig = io_new_fd( fd );
17    i_img *im   = i_readpnm_wiol(ig, -1); // no limit on how much is read
18    // or 
19    io_glue *ig = io_new_fd( fd );
20    return_code = i_writepnm_wiol(im, ig); 
21
22 =head1 DESCRIPTION
23
24 pnm.c implements the basic functions to read and write portable 
25 anymap files.  It uses the iolayer and needs either a seekable source
26 or an entire memory mapped buffer.
27
28 =head1 FUNCTION REFERENCE
29
30 Some of these functions are internal.
31
32 =over
33
34 =cut
35 */
36
37
38 #define BSIZ 1024
39 #define misspace(x) (x==' ' || x=='\n' || x=='\r' || x=='\t' || x=='\f' || x=='\v')
40 #define misnumber(x) (x <= '9' && x>='0')
41
42 static char *typenames[]={"ascii pbm", "ascii pgm", "ascii ppm", "binary pbm", "binary pgm", "binary ppm"};
43
44 /*
45  * Type to encapsulate the local buffer
46  * management skipping over in a file 
47  */
48
49 typedef struct {
50   io_glue *ig;
51   int len;
52   int cp;
53   char buf[BSIZ];
54 } mbuf;
55
56
57 static
58 void init_buf(mbuf *mb, io_glue *ig) {
59   mb->len = 0;
60   mb->cp  = 0;
61   mb->ig  = ig;
62 }
63
64
65
66 /*
67 =item gnext(mbuf *mb)
68
69 Fetches a character and advances in stream by one character.  
70 Returns a pointer to the byte or NULL on failure (internal).
71
72    mb - buffer object
73
74 =cut
75 */
76
77 static
78 char *
79 gnext(mbuf *mb) {
80   io_glue *ig = mb->ig;
81   if (mb->cp == mb->len) {
82     mb->cp = 0;
83     mb->len = ig->readcb(ig, mb->buf, BSIZ);
84     if (mb->len == -1) {
85       i_push_error(errno, "file read error");
86       mm_log((1, "i_readpnm: read error\n"));
87       return NULL;
88     }
89     if (mb->len == 0) {
90       i_push_error(errno, "unexpected end of file");
91       mm_log((1, "i_readpnm: end of file\n"));
92       return NULL;
93     }
94   }
95   return &mb->buf[mb->cp++];
96 }
97
98
99 /*
100 =item gnext(mbuf *mb)
101
102 Fetches a character but does NOT advance.  Returns a pointer to
103 the byte or NULL on failure (internal).
104
105    mb - buffer object
106
107 =cut
108 */
109
110 static
111 char *
112 gpeek(mbuf *mb) {
113   io_glue *ig = mb->ig;
114   if (mb->cp == mb->len) {
115     mb->cp = 0;
116     mb->len = ig->readcb(ig, mb->buf, BSIZ);
117     if (mb->len == -1) {
118       i_push_error(errno, "read error");
119       mm_log((1, "i_readpnm: read error\n"));
120       return NULL;
121     }
122     if (mb->len == 0) {
123       i_push_error(0, "unexpected end of file");
124       mm_log((1, "i_readpnm: end of file\n"));
125       return NULL;
126     }
127   }
128   return &mb->buf[mb->cp];
129 }
130
131
132
133
134 /*
135 =item skip_spaces(mb)
136
137 Advances in stream until it is positioned at a
138 non white space character. (internal)
139
140    mb - buffer object
141
142 =cut
143 */
144
145 static
146 int
147 skip_spaces(mbuf *mb) {
148   char *cp;
149   while( (cp = gpeek(mb)) && misspace(*cp) ) if ( !gnext(mb) ) break;
150   if (!cp) return 0;
151   return 1;
152 }
153
154
155 /*
156 =item skip_spaces(mb)
157
158 Advances in stream over whitespace and a comment if one is found. (internal)
159
160    mb - buffer object
161
162 =cut
163 */
164
165 static
166 int
167 skip_comment(mbuf *mb) {
168   char *cp;
169
170   if (!skip_spaces(mb)) return 0;
171
172   if (!(cp = gpeek(mb))) return 0;
173   if (*cp == '#') {
174     while( (cp = gpeek(mb)) && (*cp != '\n' && *cp != '\r') ) {
175       if ( !gnext(mb) ) break;
176     }
177   }
178   if (!cp) return 0;
179   
180   return 1;
181 }
182
183
184 /*
185 =item gnum(mb, i)
186
187 Fetches the next number from stream and stores in i, returns true
188 on success else false.
189
190    mb - buffer object
191    i  - integer to store result in
192
193 =cut
194 */
195
196 static
197 int
198 gnum(mbuf *mb, int *i) {
199   char *cp;
200   *i = 0;
201
202   if (!skip_spaces(mb)) return 0; 
203
204   while( (cp = gpeek(mb)) && misnumber(*cp) ) {
205     *i = *i*10+(*cp-'0');
206     cp = gnext(mb);
207   }
208   return 1;
209 }
210
211
212 /*
213 =item i_readpnm_wiol(ig, length)
214
215 Retrieve an image and stores in the iolayer object. Returns NULL on fatal error.
216
217    ig     - io_glue object
218    length - maximum length to read from data source, before closing it -1 
219             signifies no limit.
220
221 =cut
222 */
223
224
225 i_img *
226 i_readpnm_wiol(io_glue *ig, int length) {
227   i_img* im;
228   int type;
229   int x, y, ch;
230   int width, height, maxval, channels, pcount;
231   int rounder;
232   char *cp;
233   unsigned char *uc;
234   mbuf buf;
235   i_color val;
236
237   i_clear_error();
238
239   /*  char *pp; */
240
241   mm_log((1,"i_readpnm(ig %p, length %d)\n", ig, length));
242
243   /*
244   pp = mymalloc(20);
245   
246   pp[-1]= 'c';
247   pp[-2]= 'c';
248   
249   bndcheck_all();
250
251   myfree(pp);
252
253   mm_log((1, "Hack is exiting\n"));
254
255 */
256   
257   io_glue_commit_types(ig);
258   init_buf(&buf, ig);
259
260   cp = gnext(&buf);
261
262   if (!cp || *cp != 'P') {
263     i_push_error(0, "bad header magic, not a PNM file");
264     mm_log((1, "i_readpnm: Could not read header of file\n"));
265     return NULL;
266   }
267
268   if ( !(cp = gnext(&buf)) ) {
269     mm_log((1, "i_readpnm: Could not read header of file\n"));
270     return NULL;
271   }
272   
273   type = *cp-'0';
274
275   if (type < 1 || type > 6) {
276     i_push_error(0, "unknown PNM file type, not a PNM file");
277     mm_log((1, "i_readpnm: Not a pnm file\n"));
278     return NULL;
279   }
280
281   if ( !(cp = gnext(&buf)) ) {
282     mm_log((1, "i_readpnm: Could not read header of file\n"));
283     return NULL;
284   }
285   
286   if ( !misspace(*cp) ) {
287     i_push_error(0, "unexpected character, not a PNM file");
288     mm_log((1, "i_readpnm: Not a pnm file\n"));
289     return NULL;
290   }
291   
292   mm_log((1, "i_readpnm: image is a %s\n", typenames[type-1] ));
293
294   
295   /* Read sizes and such */
296
297   if (!skip_comment(&buf)) {
298     i_push_error(0, "while skipping to width");
299     mm_log((1, "i_readpnm: error reading before width\n"));
300     return NULL;
301   }
302   
303   if (!gnum(&buf, &width)) {
304     i_push_error(0, "could not read image width");
305     mm_log((1, "i_readpnm: error reading width\n"));
306     return NULL;
307   }
308
309   if (!skip_comment(&buf)) {
310     i_push_error(0, "while skipping to height");
311     mm_log((1, "i_readpnm: error reading before height\n"));
312     return NULL;
313   }
314
315   if (!gnum(&buf, &height)) {
316     i_push_error(0, "could not read image height");
317     mm_log((1, "i_readpnm: error reading height\n"));
318     return NULL;
319   }
320   
321   if (!(type == 1 || type == 4)) {
322     if (!skip_comment(&buf)) {
323       i_push_error(0, "while skipping to maxval");
324       mm_log((1, "i_readpnm: error reading before maxval\n"));
325       return NULL;
326     }
327
328     if (!gnum(&buf, &maxval)) {
329       i_push_error(0, "could not read maxval");
330       mm_log((1, "i_readpnm: error reading maxval\n"));
331       return NULL;
332     }
333
334     if (maxval == 0) {
335       i_push_error(0, "maxval is zero - invalid pnm file");
336       mm_log((1, "i_readpnm: maxval is zero, invalid pnm file\n"));
337       return NULL;
338     }
339     else if (maxval > 65535) {
340       i_push_errorf(0, "maxval of %d is over 65535 - invalid pnm file", 
341                     maxval);
342       mm_log((1, "i_readpnm: maxval of %d is over 65535 - invalid pnm file\n"));
343       return NULL;
344     }
345     else if (type >= 4 && maxval > 255) {
346       i_push_errorf(0, "maxval of %d is over 255 - not currently supported by Imager for binary pnm", maxval);
347       mm_log((1, "i_readpnm: maxval of %d is over 255 - not currently supported by Imager for binary pnm\n", maxval));
348       return NULL;
349     }
350   } else maxval=1;
351   rounder = maxval / 2;
352
353   if (!(cp = gnext(&buf)) || !misspace(*cp)) {
354     i_push_error(0, "garbage in header, invalid PNM file");
355     mm_log((1, "i_readpnm: garbage in header\n"));
356     return NULL;
357   }
358
359   channels = (type == 3 || type == 6) ? 3:1;
360   pcount = width*height*channels;
361
362   mm_log((1, "i_readpnm: (%d x %d), channels = %d, maxval = %d\n", width, height, channels, maxval));
363   
364   im = i_img_empty_ch(NULL, width, height, channels);
365
366   switch (type) {
367   case 1: /* Ascii types */
368   case 2:
369   case 3:
370     for(y=0;y<height;y++) for(x=0; x<width; x++) {
371       for(ch=0; ch<channels; ch++) {
372         int t;
373         if (gnum(&buf, &t)) val.channel[ch] = (t * 255 + rounder) / maxval;
374         else {
375           mm_log((1,"i_readpnm: gnum() returned false in data\n"));
376           return im;
377         }
378       }
379       i_ppix(im, x, y, &val);
380     }
381     break;
382     
383   case 4: /* binary pbm */
384     for(y=0;y<height;y++) for(x=0; x<width; x+=8) {
385       if ( (uc = (unsigned char*)gnext(&buf)) ) {
386         int xt;
387         int pc = width-x < 8 ? width-x : 8;
388         /*      mm_log((1,"i_readpnm: y=%d x=%d pc=%d\n", y, x, pc)); */
389         for(xt = 0; xt<pc; xt++) {
390           val.channel[0] = (*uc & (128>>xt)) ? 0 : 255; 
391           i_ppix(im, x+xt, y, &val);
392         }
393       } else {
394         mm_log((1,"i_readpnm: gnext() returned false in data\n"));
395         return im;
396       }
397     }
398     break;
399
400   case 5: /* binary pgm */
401   case 6: /* binary ppm */
402     for(y=0;y<height;y++) for(x=0; x<width; x++) {
403       for(ch=0; ch<channels; ch++) {
404         if ( (uc = (unsigned char*)gnext(&buf)) ) 
405           val.channel[ch] = (*uc * 255 + rounder) / maxval;
406         else {
407           mm_log((1,"i_readpnm: gnext() returned false in data\n"));
408           return im;
409         }
410       }
411       i_ppix(im, x, y, &val);
412     }
413     break;
414   default:
415     mm_log((1, "type %s [P%d] unsupported\n", typenames[type-1], type));
416     return NULL;
417   }
418   return im;
419 }
420
421
422 undef_int
423 i_writeppm_wiol(i_img *im, io_glue *ig) {
424   char header[255];
425   int rc;
426   writep write_func;
427
428   mm_log((1,"i_writeppm(im %p, ig %p)\n", im, ig));
429   i_clear_error();
430
431   /* Add code to get the filename info from the iolayer */
432   /* Also add code to check for mmapped code */
433
434   io_glue_commit_types(ig);
435
436   if (im->channels == 3) {
437     sprintf(header,"P6\n#CREATOR: Imager\n%d %d\n255\n",im->xsize,im->ysize);
438     if (ig->writecb(ig,header,strlen(header))<0) {
439       i_push_error(errno, "could not write ppm header");
440       mm_log((1,"i_writeppm: unable to write ppm header.\n"));
441       return(0);
442     }
443
444     if (!im->virtual && im->bits == i_8_bits && im->type == i_direct_type) {
445       rc = ig->writecb(ig,im->idata,im->bytes);
446     }
447     else {
448       unsigned char *data = mymalloc(3 * im->xsize);
449       if (data != NULL) {
450         int y = 0;
451         int x, ch;
452         unsigned char *p;
453         static int rgb_chan[3] = { 0, 1, 2 };
454
455         rc = 0;
456         while (y < im->ysize && rc >= 0) {
457           i_gsamp(im, 0, im->xsize, y, data, rgb_chan, 3);
458           rc = ig->writecb(ig, data, im->xsize * 3);
459           ++y;
460         }
461         myfree(data);
462       }
463       else {
464         i_push_error(0, "Out of memory");
465         return 0;
466       }
467     }
468     if (rc<0) {
469       i_push_error(errno, "could not write ppm data");
470       mm_log((1,"i_writeppm: unable to write ppm data.\n"));
471       return(0);
472     }
473   }
474   else if (im->channels == 1) {
475     sprintf(header, "P5\n#CREATOR: Imager\n%d %d\n255\n",
476             im->xsize, im->ysize);
477     if (ig->writecb(ig,header, strlen(header)) < 0) {
478       i_push_error(errno, "could not write pgm header");
479       mm_log((1,"i_writeppm: unable to write pgm header.\n"));
480       return(0);
481     }
482
483     if (!im->virtual && im->bits == i_8_bits && im->type == i_direct_type) {
484       rc=ig->writecb(ig,im->idata,im->bytes);
485     }
486     else {
487       unsigned char *data = mymalloc(im->xsize);
488       if (data != NULL) {
489         int y = 0;
490         int x, ch;
491         int chan = 0;
492         unsigned char *p;
493
494         rc = 0;
495         while (y < im->ysize && rc >= 0) {
496           i_gsamp(im, 0, im->xsize, y, data, &chan, 1);
497           rc = ig->writecb(ig, data, im->xsize);
498           ++y;
499         }
500         myfree(data);
501       }
502       else {
503         i_push_error(0, "Out of memory");
504         return 0;
505       }
506     }
507     if (rc<0) {
508       i_push_error(errno, "could not write pgm data");
509       mm_log((1,"i_writeppm: unable to write pgm data.\n"));
510       return(0);
511     }
512   }
513   else {
514     i_push_error(0, "can only save 1 or 3 channel images to pnm");
515     mm_log((1,"i_writeppm: ppm/pgm is 1 or 3 channel only (current image is %d)\n",im->channels));
516     return(0);
517   }
518   ig->closecb(ig);
519
520   return(1);
521 }
522
523 /*
524 =back
525
526 =head1 AUTHOR
527
528 Arnar M. Hrafnkelsson <addi@umich.edu>
529
530 =head1 SEE ALSO
531
532 Imager(3)
533
534 =cut
535 */