Initial revision
[imager.git] / pnm.c
1 #include "image.h"
2 #include "io.h"
3 #include "log.h"
4
5 #include <stdlib.h>
6
7
8 /*
9 =head1 NAME
10
11 pnm.c - implements reading and writing ppm/pnm/pbm files, uses io layer.
12
13 =head1 SYNOPSIS
14
15    io_glue *ig = io_new_fd( fd );
16    i_img *im   = i_readpnm_wiol(ig, -1); // no limit on how much is read
17    // or 
18    io_glue *ig = io_new_fd( fd );
19    return_code = i_writepnm_wiol(im, ig); 
20
21 =head1 DESCRIPTION
22
23 pnm.c implements the basic functions to read and write portable 
24 anymap files.  It uses the iolayer and needs either a seekable source
25 or an entire memory mapped buffer.
26
27 =head1 FUNCTION REFERENCE
28
29 Some of these functions are internal.
30
31 =over 4
32
33 =cut
34 */
35
36
37 #define BSIZ 1024
38 #define misspace(x) (x==' ' || x=='\n' || x=='\r' || x=='\t' || x=='\f' || x=='\v')
39 #define misnumber(x) (x <= '9' && x>='0')
40
41 static char *typenames[]={"ascii pbm", "ascii pgm", "ascii ppm", "binary pbm", "binary pgm", "binary ppm"};
42
43 /*
44  * Type to encapsulate the local buffer
45  * management skipping over in a file 
46  */
47
48 typedef struct {
49   io_glue *ig;
50   int len;
51   int cp;
52   char buf[BSIZ];
53 } mbuf;
54
55
56 static
57 void init_buf(mbuf *mb, io_glue *ig) {
58   mb->len = 0;
59   mb->cp  = 0;
60   mb->ig  = ig;
61 }
62
63
64
65 /*
66 =item gnext(mbuf *mb)
67
68 Fetches a character and advances in stream by one character.  
69 Returns a pointer to the byte or NULL on failure (internal).
70
71    mb - buffer object
72
73 =cut
74 */
75
76 static
77 char *
78 gnext(mbuf *mb) {
79   io_glue *ig = mb->ig;
80   if (mb->cp == mb->len) {
81     mb->cp = 0;
82     mb->len = ig->readcb(ig, mb->buf, BSIZ);
83     if (mb->len == -1) {
84       mm_log((1, "i_readpnm: read error\n"));
85       return NULL;
86     }
87     if (mb->len == 0) {
88       mm_log((1, "i_readpnm: end of file\n"));
89       return NULL;
90     }
91   }
92   return &mb->buf[mb->cp++];
93 }
94
95
96 /*
97 =item gnext(mbuf *mb)
98
99 Fetches a character but does NOT advance.  Returns a pointer to
100 the byte or NULL on failure (internal).
101
102    mb - buffer object
103
104 =cut
105 */
106
107 static
108 char *
109 gpeek(mbuf *mb) {
110   io_glue *ig = mb->ig;
111   if (mb->cp == mb->len) {
112     mb->cp = 0;
113     mb->len = ig->readcb(ig, mb->buf, BSIZ);
114     if (mb->len == -1) {
115       mm_log((1, "i_readpnm: read error\n"));
116       return NULL;
117     }
118     if (mb->len == 0) {
119       mm_log((1, "i_readpnm: end of file\n"));
120       return NULL;
121     }
122   }
123   return &mb->buf[mb->cp];
124 }
125
126
127
128
129 /*
130 =item skip_spaces(mb)
131
132 Advances in stream until it is positioned at a
133 non white space character. (internal)
134
135    mb - buffer object
136
137 =cut
138 */
139
140 static
141 int
142 skip_spaces(mbuf *mb) {
143   char *cp;
144   while( (cp = gpeek(mb)) && misspace(*cp) ) if ( !gnext(mb) ) break;
145   if (!cp) return 0;
146   return 1;
147 }
148
149
150 /*
151 =item skip_spaces(mb)
152
153 Advances in stream over whitespace and a comment if one is found. (internal)
154
155    mb - buffer object
156
157 =cut
158 */
159
160 static
161 int
162 skip_comment(mbuf *mb) {
163   char *cp;
164
165   if (!skip_spaces(mb)) return 0;
166
167   if (!(cp = gpeek(mb))) return 0;
168   if (*cp == '#') {
169     while( (cp = gpeek(mb)) && (*cp != '\n' && *cp != '\r') ) {
170       if ( !gnext(mb) ) break;
171     }
172   }
173   if (!cp) return 0;
174   
175   return 1;
176 }
177
178
179 /*
180 =item gnum(mb, i)
181
182 Fetches the next number from stream and stores in i, returns true
183 on success else false.
184
185    mb - buffer object
186    i  - integer to store result in
187
188 =cut
189 */
190
191 static
192 int
193 gnum(mbuf *mb, int *i) {
194   char *cp;
195   *i = 0;
196
197   if (!skip_spaces(mb)) return 0; 
198
199   while( (cp = gpeek(mb)) && misnumber(*cp) ) {
200     *i = *i*10+(*cp-'0');
201     cp = gnext(mb);
202   }
203   return 1;
204 }
205
206
207 /*
208 =item i_readpnm_wiol(ig, length)
209
210 Retrieve an image and stores in the iolayer object. Returns NULL on fatal error.
211
212    ig     - io_glue object
213    length - maximum length to read from data source, before closing it -1 
214             signifies no limit.
215
216 =cut
217 */
218
219
220 i_img *
221 i_readpnm_wiol(io_glue *ig, int length) {
222   i_img* im;
223   int type;
224   int x, y, ch;
225   int width, height, maxval, channels, pcount;
226   char *cp;
227   unsigned char *uc;
228   mbuf buf;
229   i_color val;
230   int mult;
231
232   /*  char *pp; */
233
234   mm_log((1,"i_readpnm(ig %p, length %d)\n", ig, length));
235
236   /*
237   pp = mymalloc(20);
238   
239   pp[-1]= 'c';
240   pp[-2]= 'c';
241   
242   bndcheck_all();
243
244   myfree(pp);
245
246   mm_log((1, "Hack is exiting\n"));
247
248 */
249   
250   io_glue_commit_types(ig);
251   init_buf(&buf, ig);
252
253   cp = gnext(&buf);
254
255   if (!cp || *cp != 'P') {
256     mm_log((1, "i_readpnm: Could not read header of file\n"));
257     return NULL;
258   }
259
260   if ( !(cp = gnext(&buf)) ) {
261     mm_log((1, "i_readpnm: Could not read header of file\n"));
262     return NULL;
263   }
264   
265   type = *cp-'0';
266
267   if (type < 1 || type > 6) {
268     mm_log((1, "i_readpnm: Not a pnm file\n"));
269     return NULL;
270   }
271
272   if ( !(cp = gnext(&buf)) ) {
273     mm_log((1, "i_readpnm: Could not read header of file\n"));
274     return NULL;
275   }
276   
277   if ( !misspace(*cp) ) {
278     mm_log((1, "i_readpnm: Not a pnm file\n"));
279     return NULL;
280   }
281   
282   mm_log((1, "i_readpnm: image is a %s\n", typenames[type-1] ));
283
284   
285   /* Read sizes and such */
286
287   if (!skip_comment(&buf)) {
288     mm_log((1, "i_readpnm: error reading before width\n"));
289     return NULL;
290   }
291   
292   if (!gnum(&buf, &width)) {
293     mm_log((1, "i_readpnm: error reading width\n"));
294     return NULL;
295   }
296
297   if (!skip_comment(&buf)) {
298     mm_log((1, "i_readpnm: error reading before height\n"));
299     return NULL;
300   }
301
302   if (!gnum(&buf, &height)) {
303     mm_log((1, "i_readpnm: error reading height\n"));
304     return NULL;
305   }
306   
307   if (!(type == 1 || type == 4)) {
308     if (!skip_comment(&buf)) {
309       mm_log((1, "i_readpnm: error reading before maxval\n"));
310       return NULL;
311     }
312
313     if (!gnum(&buf, &maxval)) {
314       mm_log((1, "i_readpnm: error reading maxval\n"));
315       return NULL;
316     }
317   } else maxval=1;
318
319   if (!(cp = gnext(&buf)) || !misspace(*cp)) {
320     mm_log((1, "i_readpnm: garbage in header\n"));
321     return NULL;
322   }
323
324   channels = (type == 3 || type == 6) ? 3:1;
325   pcount = width*height*channels;
326
327   mm_log((1, "i_readpnm: (%d x %d), channels = %d, maxval = %d\n", width, height, channels, maxval));
328   
329   im = i_img_empty_ch(NULL, width, height, channels);
330
331   switch (type) {
332   case 1: /* Ascii types */
333   case 2:
334   case 3:
335     mult = type == 1 ? 255 : 1;
336     for(y=0;y<height;y++) for(x=0; x<width; x++) {
337       for(ch=0; ch<channels; ch++) {
338         int t;
339         if (gnum(&buf, &t)) val.channel[ch] = t;
340         else {
341           mm_log((1,"i_readpnm: gnum() returned false in data\n"));
342           return im;
343         }
344       }
345       i_ppix(im, x, y, &val);
346     }
347     break;
348     
349   case 4: /* binary pbm */
350     for(y=0;y<height;y++) for(x=0; x<width; x+=8) {
351       if ( (uc = gnext(&buf)) ) {
352         int xt;
353         int pc = width-x < 8 ? width-x : 8;
354         /*      mm_log((1,"i_readpnm: y=%d x=%d pc=%d\n", y, x, pc)); */
355         for(xt = 0; xt<pc; xt++) {
356           val.channel[0] = (*uc & (128>>xt)) ? 0 : 255; 
357           i_ppix(im, x+xt, y, &val);
358         }
359       } else {
360         mm_log((1,"i_readpnm: gnext() returned false in data\n"));
361         return im;
362       }
363     }
364     break;
365
366   case 5: /* binary pgm */
367   case 6: /* binary ppm */
368     for(y=0;y<height;y++) for(x=0; x<width; x++) {
369       for(ch=0; ch<channels; ch++) {
370         if ( (uc = gnext(&buf)) ) val.channel[ch] = *uc;
371         else {
372           mm_log((1,"i_readpnm: gnext() returned false in data\n"));
373           return im;
374         }
375       }
376       i_ppix(im, x, y, &val);
377     }
378     break;
379   default:
380     mm_log((1, "type %s [P%d] unsupported\n", typenames[type-1], type));
381     return NULL;
382   }
383   return im;
384 }
385
386 undef_int
387 i_writeppm(i_img *im,int fd) {
388   char header[255];
389   int rc;
390
391   mm_log((1,"i_writeppm(im* 0x%x,fd %d)\n",im,fd));
392   if (im->channels!=3) {
393     mm_log((1,"i_writeppm: ppm is 3 channel only (current image is %d)\n",im->channels));
394     return(0);
395   }
396   
397   sprintf(header,"P6\n#CREATOR: Imager\n%d %d\n255\n",im->xsize,im->ysize);
398   
399   if (mywrite(fd,header,strlen(header))<0) {
400     mm_log((1,"i_writeppm: unable to write ppm header.\n"));
401     return(0);
402   }
403   
404   rc=mywrite(fd,im->data,im->bytes);
405   if (rc<0) {
406     mm_log((1,"i_writeppm: unable to write ppm data.\n"));
407     return(0);
408   }
409   return(1);
410 }
411
412
413
414
415
416
417