- set i_format to pnm when reading pnm files and test for it
[imager.git] / pnm.c
CommitLineData
02d1d628 1#include "image.h"
02d1d628 2#include "log.h"
067d6bdc 3#include "iolayer.h"
02d1d628
AMH
4
5#include <stdlib.h>
f0e7b647 6#include <errno.h>
02d1d628
AMH
7
8
9/*
10=head1 NAME
11
12pnm.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
24pnm.c implements the basic functions to read and write portable
25anymap files. It uses the iolayer and needs either a seekable source
26or an entire memory mapped buffer.
27
28=head1 FUNCTION REFERENCE
29
30Some of these functions are internal.
31
b8c2033e 32=over
02d1d628
AMH
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
42static 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
49typedef struct {
50 io_glue *ig;
51 int len;
52 int cp;
53 char buf[BSIZ];
54} mbuf;
55
56
57static
58void 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
69Fetches a character and advances in stream by one character.
70Returns a pointer to the byte or NULL on failure (internal).
71
72 mb - buffer object
73
74=cut
75*/
76
77static
78char *
79gnext(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
102Fetches a character but does NOT advance. Returns a pointer to
103the byte or NULL on failure (internal).
104
105 mb - buffer object
106
107=cut
108*/
109
110static
111char *
112gpeek(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
137Advances in stream until it is positioned at a
138non white space character. (internal)
139
140 mb - buffer object
141
142=cut
143*/
144
145static
146int
147skip_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
158Advances in stream over whitespace and a comment if one is found. (internal)
159
160 mb - buffer object
161
162=cut
163*/
164
165static
166int
167skip_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
187Fetches the next number from stream and stores in i, returns true
188on success else false.
189
190 mb - buffer object
191 i - integer to store result in
192
193=cut
194*/
195
196static
197int
198gnum(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
215Retrieve 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
225i_img *
226i_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;
8b695554 231 int rounder;
02d1d628
AMH
232 char *cp;
233 unsigned char *uc;
234 mbuf buf;
235 i_color val;
02d1d628 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 }
8b695554
TC
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 }
02d1d628 350 } else maxval=1;
8b695554 351 rounder = maxval / 2;
02d1d628
AMH
352
353 if (!(cp = gnext(&buf)) || !misspace(*cp)) {
fac8664e 354 i_push_error(0, "garbage in header, invalid PNM file");
02d1d628
AMH
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
642a675b
TC
366 i_tags_add(&im->tags, "i_format", 0, "pnm", -1, 0);
367
02d1d628
AMH
368 switch (type) {
369 case 1: /* Ascii types */
370 case 2:
371 case 3:
02d1d628
AMH
372 for(y=0;y<height;y++) for(x=0; x<width; x++) {
373 for(ch=0; ch<channels; ch++) {
374 int t;
8b695554 375 if (gnum(&buf, &t)) val.channel[ch] = (t * 255 + rounder) / maxval;
02d1d628
AMH
376 else {
377 mm_log((1,"i_readpnm: gnum() returned false in data\n"));
378 return im;
379 }
380 }
381 i_ppix(im, x, y, &val);
382 }
383 break;
384
385 case 4: /* binary pbm */
386 for(y=0;y<height;y++) for(x=0; x<width; x+=8) {
a5eb593a 387 if ( (uc = (unsigned char*)gnext(&buf)) ) {
02d1d628
AMH
388 int xt;
389 int pc = width-x < 8 ? width-x : 8;
390 /* mm_log((1,"i_readpnm: y=%d x=%d pc=%d\n", y, x, pc)); */
391 for(xt = 0; xt<pc; xt++) {
392 val.channel[0] = (*uc & (128>>xt)) ? 0 : 255;
393 i_ppix(im, x+xt, y, &val);
394 }
395 } else {
396 mm_log((1,"i_readpnm: gnext() returned false in data\n"));
397 return im;
398 }
399 }
400 break;
401
402 case 5: /* binary pgm */
403 case 6: /* binary ppm */
404 for(y=0;y<height;y++) for(x=0; x<width; x++) {
405 for(ch=0; ch<channels; ch++) {
8b695554
TC
406 if ( (uc = (unsigned char*)gnext(&buf)) )
407 val.channel[ch] = (*uc * 255 + rounder) / maxval;
02d1d628
AMH
408 else {
409 mm_log((1,"i_readpnm: gnext() returned false in data\n"));
410 return im;
411 }
412 }
413 i_ppix(im, x, y, &val);
414 }
415 break;
416 default:
417 mm_log((1, "type %s [P%d] unsupported\n", typenames[type-1], type));
418 return NULL;
419 }
420 return im;
421}
422
02d1d628 423
067d6bdc
AMH
424undef_int
425i_writeppm_wiol(i_img *im, io_glue *ig) {
426 char header[255];
427 int rc;
428 writep write_func;
02d1d628 429
067d6bdc
AMH
430 mm_log((1,"i_writeppm(im %p, ig %p)\n", im, ig));
431 i_clear_error();
02d1d628 432
067d6bdc
AMH
433 /* Add code to get the filename info from the iolayer */
434 /* Also add code to check for mmapped code */
02d1d628 435
067d6bdc 436 io_glue_commit_types(ig);
02d1d628 437
faa9b3e7 438 if (im->channels == 3) {
067d6bdc 439 sprintf(header,"P6\n#CREATOR: Imager\n%d %d\n255\n",im->xsize,im->ysize);
faa9b3e7 440 if (ig->writecb(ig,header,strlen(header))<0) {
067d6bdc
AMH
441 i_push_error(errno, "could not write ppm header");
442 mm_log((1,"i_writeppm: unable to write ppm header.\n"));
443 return(0);
444 }
faa9b3e7
TC
445
446 if (!im->virtual && im->bits == i_8_bits && im->type == i_direct_type) {
447 rc = ig->writecb(ig,im->idata,im->bytes);
448 }
449 else {
450 unsigned char *data = mymalloc(3 * im->xsize);
451 if (data != NULL) {
452 int y = 0;
453 int x, ch;
454 unsigned char *p;
455 static int rgb_chan[3] = { 0, 1, 2 };
456
457 rc = 0;
458 while (y < im->ysize && rc >= 0) {
459 i_gsamp(im, 0, im->xsize, y, data, rgb_chan, 3);
460 rc = ig->writecb(ig, data, im->xsize * 3);
43c5dacb 461 ++y;
faa9b3e7
TC
462 }
463 myfree(data);
464 }
465 else {
466 i_push_error(0, "Out of memory");
467 return 0;
468 }
469 }
067d6bdc
AMH
470 if (rc<0) {
471 i_push_error(errno, "could not write ppm data");
472 mm_log((1,"i_writeppm: unable to write ppm data.\n"));
473 return(0);
474 }
475 }
476 else if (im->channels == 1) {
477 sprintf(header, "P5\n#CREATOR: Imager\n%d %d\n255\n",
478 im->xsize, im->ysize);
faa9b3e7 479 if (ig->writecb(ig,header, strlen(header)) < 0) {
067d6bdc
AMH
480 i_push_error(errno, "could not write pgm header");
481 mm_log((1,"i_writeppm: unable to write pgm header.\n"));
482 return(0);
483 }
faa9b3e7
TC
484
485 if (!im->virtual && im->bits == i_8_bits && im->type == i_direct_type) {
486 rc=ig->writecb(ig,im->idata,im->bytes);
487 }
488 else {
489 unsigned char *data = mymalloc(im->xsize);
490 if (data != NULL) {
491 int y = 0;
492 int x, ch;
493 int chan = 0;
494 unsigned char *p;
495
496 rc = 0;
497 while (y < im->ysize && rc >= 0) {
498 i_gsamp(im, 0, im->xsize, y, data, &chan, 1);
499 rc = ig->writecb(ig, data, im->xsize);
43c5dacb 500 ++y;
faa9b3e7
TC
501 }
502 myfree(data);
503 }
504 else {
505 i_push_error(0, "Out of memory");
506 return 0;
507 }
508 }
067d6bdc
AMH
509 if (rc<0) {
510 i_push_error(errno, "could not write pgm data");
511 mm_log((1,"i_writeppm: unable to write pgm data.\n"));
512 return(0);
513 }
514 }
515 else {
516 i_push_error(0, "can only save 1 or 3 channel images to pnm");
517 mm_log((1,"i_writeppm: ppm/pgm is 1 or 3 channel only (current image is %d)\n",im->channels));
518 return(0);
519 }
10461f9a 520 ig->closecb(ig);
faa9b3e7 521
067d6bdc
AMH
522 return(1);
523}
b8c2033e
AMH
524
525/*
526=back
527
528=head1 AUTHOR
529
530Arnar M. Hrafnkelsson <addi@umich.edu>
531
532=head1 SEE ALSO
533
534Imager(3)
535
536=cut
537*/