]>
Commit | Line | Data |
---|---|---|
02d1d628 AMH |
1 | #include "image.h" |
2 | #include "io.h" | |
3 | #include "log.h" | |
4 | ||
5 | #include <stdlib.h> | |
f0e7b647 | 6 | #include <errno.h> |
02d1d628 AMH |
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 4 | |
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) { | |
fac8664e | 85 | i_push_error(errno, "file read error"); |
02d1d628 AMH |
86 | mm_log((1, "i_readpnm: read error\n")); |
87 | return NULL; | |
88 | } | |
89 | if (mb->len == 0) { | |
fac8664e | 90 | i_push_error(errno, "unexpected end of file"); |
02d1d628 AMH |
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) { | |
fac8664e | 118 | i_push_error(errno, "read error"); |
02d1d628 AMH |
119 | mm_log((1, "i_readpnm: read error\n")); |
120 | return NULL; | |
121 | } | |
122 | if (mb->len == 0) { | |
fac8664e | 123 | i_push_error(0, "unexpected end of file"); |
02d1d628 AMH |
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 | char *cp; | |
232 | unsigned char *uc; | |
233 | mbuf buf; | |
234 | i_color val; | |
235 | int mult; | |
236 | ||
fac8664e TC |
237 | i_clear_error(); |
238 | ||
02d1d628 AMH |
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') { | |
fac8664e | 263 | i_push_error(0, "bad header magic, not a PNM file"); |
02d1d628 AMH |
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) { | |
fac8664e | 276 | i_push_error(0, "unknown PNM file type, not a PNM file"); |
02d1d628 AMH |
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) ) { | |
fac8664e | 287 | i_push_error(0, "unexpected character, not a PNM file"); |
02d1d628 AMH |
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)) { | |
fac8664e | 298 | i_push_error(0, "while skipping to width"); |
02d1d628 AMH |
299 | mm_log((1, "i_readpnm: error reading before width\n")); |
300 | return NULL; | |
301 | } | |
302 | ||
303 | if (!gnum(&buf, &width)) { | |
fac8664e | 304 | i_push_error(0, "could not read image width"); |
02d1d628 AMH |
305 | mm_log((1, "i_readpnm: error reading width\n")); |
306 | return NULL; | |
307 | } | |
308 | ||
309 | if (!skip_comment(&buf)) { | |
fac8664e | 310 | i_push_error(0, "while skipping to height"); |
02d1d628 AMH |
311 | mm_log((1, "i_readpnm: error reading before height\n")); |
312 | return NULL; | |
313 | } | |
314 | ||
315 | if (!gnum(&buf, &height)) { | |
fac8664e | 316 | i_push_error(0, "could not read image height"); |
02d1d628 AMH |
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)) { | |
fac8664e | 323 | i_push_error(0, "while skipping to maxval"); |
02d1d628 AMH |
324 | mm_log((1, "i_readpnm: error reading before maxval\n")); |
325 | return NULL; | |
326 | } | |
327 | ||
328 | if (!gnum(&buf, &maxval)) { | |
fac8664e | 329 | i_push_error(0, "could not read maxval"); |
02d1d628 AMH |
330 | mm_log((1, "i_readpnm: error reading maxval\n")); |
331 | return NULL; | |
332 | } | |
333 | } else maxval=1; | |
334 | ||
335 | if (!(cp = gnext(&buf)) || !misspace(*cp)) { | |
fac8664e | 336 | i_push_error(0, "garbage in header, invalid PNM file"); |
02d1d628 AMH |
337 | mm_log((1, "i_readpnm: garbage in header\n")); |
338 | return NULL; | |
339 | } | |
340 | ||
341 | channels = (type == 3 || type == 6) ? 3:1; | |
342 | pcount = width*height*channels; | |
343 | ||
344 | mm_log((1, "i_readpnm: (%d x %d), channels = %d, maxval = %d\n", width, height, channels, maxval)); | |
345 | ||
346 | im = i_img_empty_ch(NULL, width, height, channels); | |
347 | ||
348 | switch (type) { | |
349 | case 1: /* Ascii types */ | |
350 | case 2: | |
351 | case 3: | |
352 | mult = type == 1 ? 255 : 1; | |
353 | for(y=0;y<height;y++) for(x=0; x<width; x++) { | |
354 | for(ch=0; ch<channels; ch++) { | |
355 | int t; | |
356 | if (gnum(&buf, &t)) val.channel[ch] = t; | |
357 | else { | |
358 | mm_log((1,"i_readpnm: gnum() returned false in data\n")); | |
359 | return im; | |
360 | } | |
361 | } | |
362 | i_ppix(im, x, y, &val); | |
363 | } | |
364 | break; | |
365 | ||
366 | case 4: /* binary pbm */ | |
367 | for(y=0;y<height;y++) for(x=0; x<width; x+=8) { | |
368 | if ( (uc = gnext(&buf)) ) { | |
369 | int xt; | |
370 | int pc = width-x < 8 ? width-x : 8; | |
371 | /* mm_log((1,"i_readpnm: y=%d x=%d pc=%d\n", y, x, pc)); */ | |
372 | for(xt = 0; xt<pc; xt++) { | |
373 | val.channel[0] = (*uc & (128>>xt)) ? 0 : 255; | |
374 | i_ppix(im, x+xt, y, &val); | |
375 | } | |
376 | } else { | |
377 | mm_log((1,"i_readpnm: gnext() returned false in data\n")); | |
378 | return im; | |
379 | } | |
380 | } | |
381 | break; | |
382 | ||
383 | case 5: /* binary pgm */ | |
384 | case 6: /* binary ppm */ | |
385 | for(y=0;y<height;y++) for(x=0; x<width; x++) { | |
386 | for(ch=0; ch<channels; ch++) { | |
387 | if ( (uc = gnext(&buf)) ) val.channel[ch] = *uc; | |
388 | else { | |
389 | mm_log((1,"i_readpnm: gnext() returned false in data\n")); | |
390 | return im; | |
391 | } | |
392 | } | |
393 | i_ppix(im, x, y, &val); | |
394 | } | |
395 | break; | |
396 | default: | |
397 | mm_log((1, "type %s [P%d] unsupported\n", typenames[type-1], type)); | |
398 | return NULL; | |
399 | } | |
400 | return im; | |
401 | } | |
402 | ||
403 | undef_int | |
404 | i_writeppm(i_img *im,int fd) { | |
405 | char header[255]; | |
406 | int rc; | |
407 | ||
408 | mm_log((1,"i_writeppm(im* 0x%x,fd %d)\n",im,fd)); | |
02d1d628 | 409 | |
9f56d386 TC |
410 | i_clear_error(); |
411 | ||
412 | if (im->channels==3) { | |
413 | sprintf(header,"P6\n#CREATOR: Imager\n%d %d\n255\n",im->xsize,im->ysize); | |
414 | ||
415 | if (mywrite(fd,header,strlen(header))<0) { | |
416 | i_push_error(errno, "could not write ppm header"); | |
417 | mm_log((1,"i_writeppm: unable to write ppm header.\n")); | |
418 | return(0); | |
419 | } | |
420 | ||
421 | rc=mywrite(fd,im->data,im->bytes); | |
422 | if (rc<0) { | |
423 | i_push_error(errno, "could not write ppm data"); | |
424 | mm_log((1,"i_writeppm: unable to write ppm data.\n")); | |
425 | return(0); | |
426 | } | |
02d1d628 | 427 | } |
9f56d386 TC |
428 | else if (im->channels == 1) { |
429 | sprintf(header, "P5\n#CREATOR: Imager\n%d %d\n255\n", | |
430 | im->xsize, im->ysize); | |
431 | if (mywrite(fd,header, strlen(header)) < 0) { | |
432 | i_push_error(errno, "could not write pgm header"); | |
433 | mm_log((1,"i_writeppm: unable to write pgm header.\n")); | |
434 | return(0); | |
435 | } | |
436 | ||
437 | rc=mywrite(fd,im->data,im->bytes); | |
438 | if (rc<0) { | |
439 | i_push_error(errno, "could not write pgm data"); | |
440 | mm_log((1,"i_writeppm: unable to write pgm data.\n")); | |
441 | return(0); | |
442 | } | |
443 | } | |
444 | else { | |
445 | i_push_error(0, "can only save 1 or 3 channel images to pnm"); | |
446 | mm_log((1,"i_writeppm: ppm/pgm is 1 or 3 channel only (current image is %d)\n",im->channels)); | |
02d1d628 AMH |
447 | return(0); |
448 | } | |
9f56d386 | 449 | |
02d1d628 AMH |
450 | return(1); |
451 | } | |
452 | ||
453 | ||
454 | ||
455 | ||
456 | ||
457 | ||
458 |