- convert t/t104ppm.t to Test::More
[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/*
fdd00d54 100=item gpeek(mbuf *mb)
02d1d628
AMH
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/*
fdd00d54 156=item skip_comment(mb)
02d1d628
AMH
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 mm_log((1,"i_readpnm(ig %p, length %d)\n", ig, length));
240
02d1d628
AMH
241 io_glue_commit_types(ig);
242 init_buf(&buf, ig);
243
244 cp = gnext(&buf);
245
246 if (!cp || *cp != 'P') {
fac8664e 247 i_push_error(0, "bad header magic, not a PNM file");
02d1d628
AMH
248 mm_log((1, "i_readpnm: Could not read header of file\n"));
249 return NULL;
250 }
251
252 if ( !(cp = gnext(&buf)) ) {
253 mm_log((1, "i_readpnm: Could not read header of file\n"));
254 return NULL;
255 }
256
257 type = *cp-'0';
258
259 if (type < 1 || type > 6) {
fac8664e 260 i_push_error(0, "unknown PNM file type, not a PNM file");
02d1d628
AMH
261 mm_log((1, "i_readpnm: Not a pnm file\n"));
262 return NULL;
263 }
264
265 if ( !(cp = gnext(&buf)) ) {
266 mm_log((1, "i_readpnm: Could not read header of file\n"));
267 return NULL;
268 }
269
270 if ( !misspace(*cp) ) {
fac8664e 271 i_push_error(0, "unexpected character, not a PNM file");
02d1d628
AMH
272 mm_log((1, "i_readpnm: Not a pnm file\n"));
273 return NULL;
274 }
275
276 mm_log((1, "i_readpnm: image is a %s\n", typenames[type-1] ));
277
278
279 /* Read sizes and such */
280
281 if (!skip_comment(&buf)) {
fac8664e 282 i_push_error(0, "while skipping to width");
02d1d628
AMH
283 mm_log((1, "i_readpnm: error reading before width\n"));
284 return NULL;
285 }
286
287 if (!gnum(&buf, &width)) {
fac8664e 288 i_push_error(0, "could not read image width");
02d1d628
AMH
289 mm_log((1, "i_readpnm: error reading width\n"));
290 return NULL;
291 }
292
293 if (!skip_comment(&buf)) {
fac8664e 294 i_push_error(0, "while skipping to height");
02d1d628
AMH
295 mm_log((1, "i_readpnm: error reading before height\n"));
296 return NULL;
297 }
298
299 if (!gnum(&buf, &height)) {
fac8664e 300 i_push_error(0, "could not read image height");
02d1d628
AMH
301 mm_log((1, "i_readpnm: error reading height\n"));
302 return NULL;
303 }
304
305 if (!(type == 1 || type == 4)) {
306 if (!skip_comment(&buf)) {
fac8664e 307 i_push_error(0, "while skipping to maxval");
02d1d628
AMH
308 mm_log((1, "i_readpnm: error reading before maxval\n"));
309 return NULL;
310 }
311
312 if (!gnum(&buf, &maxval)) {
fac8664e 313 i_push_error(0, "could not read maxval");
02d1d628
AMH
314 mm_log((1, "i_readpnm: error reading maxval\n"));
315 return NULL;
316 }
8b695554
TC
317
318 if (maxval == 0) {
319 i_push_error(0, "maxval is zero - invalid pnm file");
320 mm_log((1, "i_readpnm: maxval is zero, invalid pnm file\n"));
321 return NULL;
322 }
323 else if (maxval > 65535) {
324 i_push_errorf(0, "maxval of %d is over 65535 - invalid pnm file",
325 maxval);
326 mm_log((1, "i_readpnm: maxval of %d is over 65535 - invalid pnm file\n"));
327 return NULL;
328 }
329 else if (type >= 4 && maxval > 255) {
330 i_push_errorf(0, "maxval of %d is over 255 - not currently supported by Imager for binary pnm", maxval);
331 mm_log((1, "i_readpnm: maxval of %d is over 255 - not currently supported by Imager for binary pnm\n", maxval));
332 return NULL;
333 }
02d1d628 334 } else maxval=1;
8b695554 335 rounder = maxval / 2;
02d1d628
AMH
336
337 if (!(cp = gnext(&buf)) || !misspace(*cp)) {
fac8664e 338 i_push_error(0, "garbage in header, invalid PNM file");
02d1d628
AMH
339 mm_log((1, "i_readpnm: garbage in header\n"));
340 return NULL;
341 }
342
343 channels = (type == 3 || type == 6) ? 3:1;
344 pcount = width*height*channels;
345
346 mm_log((1, "i_readpnm: (%d x %d), channels = %d, maxval = %d\n", width, height, channels, maxval));
347
348 im = i_img_empty_ch(NULL, width, height, channels);
349
642a675b
TC
350 i_tags_add(&im->tags, "i_format", 0, "pnm", -1, 0);
351
02d1d628
AMH
352 switch (type) {
353 case 1: /* Ascii types */
354 case 2:
355 case 3:
02d1d628
AMH
356 for(y=0;y<height;y++) for(x=0; x<width; x++) {
357 for(ch=0; ch<channels; ch++) {
358 int t;
8b695554 359 if (gnum(&buf, &t)) val.channel[ch] = (t * 255 + rounder) / maxval;
02d1d628
AMH
360 else {
361 mm_log((1,"i_readpnm: gnum() returned false in data\n"));
362 return im;
363 }
364 }
365 i_ppix(im, x, y, &val);
366 }
367 break;
368
369 case 4: /* binary pbm */
370 for(y=0;y<height;y++) for(x=0; x<width; x+=8) {
a5eb593a 371 if ( (uc = (unsigned char*)gnext(&buf)) ) {
02d1d628
AMH
372 int xt;
373 int pc = width-x < 8 ? width-x : 8;
374 /* mm_log((1,"i_readpnm: y=%d x=%d pc=%d\n", y, x, pc)); */
375 for(xt = 0; xt<pc; xt++) {
376 val.channel[0] = (*uc & (128>>xt)) ? 0 : 255;
377 i_ppix(im, x+xt, y, &val);
378 }
379 } else {
380 mm_log((1,"i_readpnm: gnext() returned false in data\n"));
381 return im;
382 }
383 }
384 break;
385
386 case 5: /* binary pgm */
387 case 6: /* binary ppm */
388 for(y=0;y<height;y++) for(x=0; x<width; x++) {
389 for(ch=0; ch<channels; ch++) {
8b695554
TC
390 if ( (uc = (unsigned char*)gnext(&buf)) )
391 val.channel[ch] = (*uc * 255 + rounder) / maxval;
02d1d628
AMH
392 else {
393 mm_log((1,"i_readpnm: gnext() returned false in data\n"));
394 return im;
395 }
396 }
397 i_ppix(im, x, y, &val);
398 }
399 break;
400 default:
401 mm_log((1, "type %s [P%d] unsupported\n", typenames[type-1], type));
402 return NULL;
403 }
404 return im;
405}
406
02d1d628 407
067d6bdc
AMH
408undef_int
409i_writeppm_wiol(i_img *im, io_glue *ig) {
410 char header[255];
411 int rc;
02d1d628 412
067d6bdc
AMH
413 mm_log((1,"i_writeppm(im %p, ig %p)\n", im, ig));
414 i_clear_error();
02d1d628 415
067d6bdc
AMH
416 /* Add code to get the filename info from the iolayer */
417 /* Also add code to check for mmapped code */
02d1d628 418
067d6bdc 419 io_glue_commit_types(ig);
02d1d628 420
faa9b3e7 421 if (im->channels == 3) {
067d6bdc 422 sprintf(header,"P6\n#CREATOR: Imager\n%d %d\n255\n",im->xsize,im->ysize);
faa9b3e7 423 if (ig->writecb(ig,header,strlen(header))<0) {
067d6bdc
AMH
424 i_push_error(errno, "could not write ppm header");
425 mm_log((1,"i_writeppm: unable to write ppm header.\n"));
426 return(0);
427 }
faa9b3e7
TC
428
429 if (!im->virtual && im->bits == i_8_bits && im->type == i_direct_type) {
430 rc = ig->writecb(ig,im->idata,im->bytes);
431 }
432 else {
433 unsigned char *data = mymalloc(3 * im->xsize);
434 if (data != NULL) {
435 int y = 0;
faa9b3e7
TC
436 static int rgb_chan[3] = { 0, 1, 2 };
437
438 rc = 0;
439 while (y < im->ysize && rc >= 0) {
440 i_gsamp(im, 0, im->xsize, y, data, rgb_chan, 3);
441 rc = ig->writecb(ig, data, im->xsize * 3);
43c5dacb 442 ++y;
faa9b3e7
TC
443 }
444 myfree(data);
445 }
446 else {
447 i_push_error(0, "Out of memory");
448 return 0;
449 }
450 }
067d6bdc
AMH
451 if (rc<0) {
452 i_push_error(errno, "could not write ppm data");
453 mm_log((1,"i_writeppm: unable to write ppm data.\n"));
454 return(0);
455 }
456 }
457 else if (im->channels == 1) {
458 sprintf(header, "P5\n#CREATOR: Imager\n%d %d\n255\n",
459 im->xsize, im->ysize);
faa9b3e7 460 if (ig->writecb(ig,header, strlen(header)) < 0) {
067d6bdc
AMH
461 i_push_error(errno, "could not write pgm header");
462 mm_log((1,"i_writeppm: unable to write pgm header.\n"));
463 return(0);
464 }
faa9b3e7
TC
465
466 if (!im->virtual && im->bits == i_8_bits && im->type == i_direct_type) {
467 rc=ig->writecb(ig,im->idata,im->bytes);
468 }
469 else {
470 unsigned char *data = mymalloc(im->xsize);
471 if (data != NULL) {
472 int y = 0;
faa9b3e7 473 int chan = 0;
faa9b3e7
TC
474
475 rc = 0;
476 while (y < im->ysize && rc >= 0) {
477 i_gsamp(im, 0, im->xsize, y, data, &chan, 1);
478 rc = ig->writecb(ig, data, im->xsize);
43c5dacb 479 ++y;
faa9b3e7
TC
480 }
481 myfree(data);
482 }
483 else {
484 i_push_error(0, "Out of memory");
485 return 0;
486 }
487 }
067d6bdc
AMH
488 if (rc<0) {
489 i_push_error(errno, "could not write pgm data");
490 mm_log((1,"i_writeppm: unable to write pgm data.\n"));
491 return(0);
492 }
493 }
494 else {
495 i_push_error(0, "can only save 1 or 3 channel images to pnm");
496 mm_log((1,"i_writeppm: ppm/pgm is 1 or 3 channel only (current image is %d)\n",im->channels));
497 return(0);
498 }
10461f9a 499 ig->closecb(ig);
faa9b3e7 500
067d6bdc
AMH
501 return(1);
502}
b8c2033e
AMH
503
504/*
505=back
506
507=head1 AUTHOR
508
509Arnar M. Hrafnkelsson <addi@umich.edu>
510
511=head1 SEE ALSO
512
513Imager(3)
514
515=cut
516*/