]> git.imager.perl.org - imager.git/blame - pnm.c
support saving to pgm file
[imager.git] / pnm.c
CommitLineData
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
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
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
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;
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
403undef_int
404i_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