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