Commit | Line | Data |
---|---|---|
92bda632 | 1 | #include "imager.h" |
02d1d628 | 2 | #include "log.h" |
067d6bdc | 3 | #include "iolayer.h" |
92bda632 | 4 | #include "imageri.h" |
02d1d628 AMH |
5 | |
6 | #include <stdlib.h> | |
f0e7b647 | 7 | #include <errno.h> |
02d1d628 AMH |
8 | |
9 | ||
10 | /* | |
11 | =head1 NAME | |
12 | ||
13 | pnm.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 ); | |
9c106321 | 18 | i_img *im = i_readpnm_wiol(ig, 0); // no limit on how much is read |
02d1d628 AMH |
19 | // or |
20 | io_glue *ig = io_new_fd( fd ); | |
21 | return_code = i_writepnm_wiol(im, ig); | |
22 | ||
23 | =head1 DESCRIPTION | |
24 | ||
25 | pnm.c implements the basic functions to read and write portable | |
26 | anymap files. It uses the iolayer and needs either a seekable source | |
27 | or an entire memory mapped buffer. | |
28 | ||
29 | =head1 FUNCTION REFERENCE | |
30 | ||
31 | Some 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 | ||
43 | static 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 | ||
50 | typedef struct { | |
51 | io_glue *ig; | |
52 | int len; | |
53 | int cp; | |
54 | char buf[BSIZ]; | |
55 | } mbuf; | |
56 | ||
57 | ||
58 | static | |
59 | void 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 | ||
70 | Fetches a character and advances in stream by one character. | |
71 | Returns a pointer to the byte or NULL on failure (internal). | |
72 | ||
73 | mb - buffer object | |
74 | ||
75 | =cut | |
76 | */ | |
77 | ||
9c106321 TC |
78 | #define gnext(mb) (((mb)->cp == (mb)->len) ? gnextf(mb) : (mb)->buf + (mb)->cp++) |
79 | ||
02d1d628 AMH |
80 | static |
81 | char * | |
9c106321 | 82 | gnextf(mbuf *mb) { |
02d1d628 AMH |
83 | io_glue *ig = mb->ig; |
84 | if (mb->cp == mb->len) { | |
85 | mb->cp = 0; | |
86 | mb->len = ig->readcb(ig, mb->buf, BSIZ); | |
87 | if (mb->len == -1) { | |
fac8664e | 88 | i_push_error(errno, "file read error"); |
02d1d628 AMH |
89 | mm_log((1, "i_readpnm: read error\n")); |
90 | return NULL; | |
91 | } | |
92 | if (mb->len == 0) { | |
93 | mm_log((1, "i_readpnm: end of file\n")); | |
94 | return NULL; | |
95 | } | |
96 | } | |
97 | return &mb->buf[mb->cp++]; | |
98 | } | |
99 | ||
100 | ||
101 | /* | |
fdd00d54 | 102 | =item gpeek(mbuf *mb) |
02d1d628 AMH |
103 | |
104 | Fetches a character but does NOT advance. Returns a pointer to | |
105 | the byte or NULL on failure (internal). | |
106 | ||
107 | mb - buffer object | |
108 | ||
109 | =cut | |
110 | */ | |
111 | ||
9c106321 TC |
112 | #define gpeek(mb) ((mb)->cp == (mb)->len ? gpeekf(mb) : (mb)->buf + (mb)->cp) |
113 | ||
02d1d628 AMH |
114 | static |
115 | char * | |
9c106321 | 116 | gpeekf(mbuf *mb) { |
02d1d628 AMH |
117 | io_glue *ig = mb->ig; |
118 | if (mb->cp == mb->len) { | |
119 | mb->cp = 0; | |
120 | mb->len = ig->readcb(ig, mb->buf, BSIZ); | |
121 | if (mb->len == -1) { | |
fac8664e | 122 | i_push_error(errno, "read error"); |
02d1d628 AMH |
123 | mm_log((1, "i_readpnm: read error\n")); |
124 | return NULL; | |
125 | } | |
126 | if (mb->len == 0) { | |
127 | mm_log((1, "i_readpnm: end of file\n")); | |
128 | return NULL; | |
129 | } | |
130 | } | |
131 | return &mb->buf[mb->cp]; | |
132 | } | |
133 | ||
9c106321 TC |
134 | int |
135 | gread(mbuf *mb, unsigned char *buf, size_t read_size) { | |
136 | int total_read = 0; | |
137 | if (mb->cp != mb->len) { | |
138 | int avail_size = mb->len - mb->cp; | |
139 | int use_size = read_size > avail_size ? avail_size : read_size; | |
140 | memcpy(buf, mb->buf+mb->cp, use_size); | |
141 | mb->cp += use_size; | |
142 | total_read += use_size; | |
143 | read_size -= use_size; | |
144 | buf += use_size; | |
145 | } | |
146 | if (read_size) { | |
147 | io_glue *ig = mb->ig; | |
148 | int read_res = i_io_read(ig, buf, read_size); | |
149 | if (read_res >= 0) { | |
150 | total_read += read_res; | |
151 | } | |
152 | } | |
153 | return total_read; | |
154 | } | |
02d1d628 AMH |
155 | |
156 | ||
157 | /* | |
158 | =item skip_spaces(mb) | |
159 | ||
160 | Advances in stream until it is positioned at a | |
161 | non white space character. (internal) | |
162 | ||
163 | mb - buffer object | |
164 | ||
165 | =cut | |
166 | */ | |
167 | ||
168 | static | |
169 | int | |
170 | skip_spaces(mbuf *mb) { | |
171 | char *cp; | |
172 | while( (cp = gpeek(mb)) && misspace(*cp) ) if ( !gnext(mb) ) break; | |
173 | if (!cp) return 0; | |
174 | return 1; | |
175 | } | |
176 | ||
177 | ||
178 | /* | |
fdd00d54 | 179 | =item skip_comment(mb) |
02d1d628 AMH |
180 | |
181 | Advances in stream over whitespace and a comment if one is found. (internal) | |
182 | ||
183 | mb - buffer object | |
184 | ||
185 | =cut | |
186 | */ | |
187 | ||
188 | static | |
189 | int | |
190 | skip_comment(mbuf *mb) { | |
191 | char *cp; | |
192 | ||
193 | if (!skip_spaces(mb)) return 0; | |
194 | ||
195 | if (!(cp = gpeek(mb))) return 0; | |
196 | if (*cp == '#') { | |
197 | while( (cp = gpeek(mb)) && (*cp != '\n' && *cp != '\r') ) { | |
198 | if ( !gnext(mb) ) break; | |
199 | } | |
200 | } | |
201 | if (!cp) return 0; | |
202 | ||
203 | return 1; | |
204 | } | |
205 | ||
206 | ||
207 | /* | |
208 | =item gnum(mb, i) | |
209 | ||
210 | Fetches the next number from stream and stores in i, returns true | |
211 | on success else false. | |
212 | ||
213 | mb - buffer object | |
214 | i - integer to store result in | |
215 | ||
216 | =cut | |
217 | */ | |
218 | ||
219 | static | |
220 | int | |
221 | gnum(mbuf *mb, int *i) { | |
222 | char *cp; | |
223 | *i = 0; | |
224 | ||
225 | if (!skip_spaces(mb)) return 0; | |
226 | ||
9c106321 TC |
227 | if (!(cp = gpeek(mb))) |
228 | return 0; | |
229 | if (!misnumber(*cp)) | |
230 | return 0; | |
02d1d628 AMH |
231 | while( (cp = gpeek(mb)) && misnumber(*cp) ) { |
232 | *i = *i*10+(*cp-'0'); | |
233 | cp = gnext(mb); | |
234 | } | |
235 | return 1; | |
236 | } | |
237 | ||
9c106321 TC |
238 | static |
239 | i_img * | |
240 | read_pgm_ppm_bin8(mbuf *mb, i_img *im, int width, int height, | |
d87dc9a4 | 241 | int channels, int maxval, int allow_incomplete) { |
9c106321 TC |
242 | i_color *line, *linep; |
243 | int read_size; | |
244 | unsigned char *read_buf, *readp; | |
245 | int x, y, ch; | |
246 | int rounder = maxval / 2; | |
247 | ||
248 | line = mymalloc(width * sizeof(i_color)); | |
249 | read_size = channels * width; | |
250 | read_buf = mymalloc(read_size); | |
251 | for(y=0;y<height;y++) { | |
252 | linep = line; | |
253 | readp = read_buf; | |
254 | if (gread(mb, read_buf, read_size) != read_size) { | |
255 | myfree(line); | |
256 | myfree(read_buf); | |
d87dc9a4 | 257 | if (allow_incomplete) { |
9c106321 TC |
258 | i_tags_setn(&im->tags, "i_incomplete", 1); |
259 | i_tags_setn(&im->tags, "i_lines_read", y); | |
260 | return im; | |
261 | } | |
262 | else { | |
263 | i_push_error(0, "short read - file truncated?"); | |
264 | i_img_destroy(im); | |
265 | return NULL; | |
266 | } | |
267 | } | |
268 | if (maxval == 255) { | |
269 | for(x=0; x<width; x++) { | |
270 | for(ch=0; ch<channels; ch++) { | |
271 | linep->channel[ch] = *readp++; | |
272 | } | |
273 | ++linep; | |
274 | } | |
275 | } | |
276 | else { | |
277 | for(x=0; x<width; x++) { | |
278 | for(ch=0; ch<channels; ch++) { | |
279 | /* we just clamp samples to the correct range */ | |
280 | unsigned sample = *readp++; | |
281 | if (sample > maxval) | |
282 | sample = maxval; | |
283 | linep->channel[ch] = (sample * 255 + rounder) / maxval; | |
284 | } | |
285 | ++linep; | |
286 | } | |
287 | } | |
288 | i_plin(im, 0, width, y, line); | |
289 | } | |
290 | myfree(read_buf); | |
291 | myfree(line); | |
292 | ||
293 | return im; | |
294 | } | |
295 | ||
296 | static | |
297 | i_img * | |
298 | read_pgm_ppm_bin16(mbuf *mb, i_img *im, int width, int height, | |
d87dc9a4 | 299 | int channels, int maxval, int allow_incomplete) { |
9c106321 TC |
300 | i_fcolor *line, *linep; |
301 | int read_size; | |
302 | unsigned char *read_buf, *readp; | |
303 | int x, y, ch; | |
304 | double maxvalf = maxval; | |
305 | ||
306 | line = mymalloc(width * sizeof(i_fcolor)); | |
307 | read_size = channels * width * 2; | |
308 | read_buf = mymalloc(read_size); | |
309 | for(y=0;y<height;y++) { | |
310 | linep = line; | |
311 | readp = read_buf; | |
312 | if (gread(mb, read_buf, read_size) != read_size) { | |
313 | myfree(line); | |
314 | myfree(read_buf); | |
d87dc9a4 | 315 | if (allow_incomplete) { |
9c106321 TC |
316 | i_tags_setn(&im->tags, "i_incomplete", 1); |
317 | i_tags_setn(&im->tags, "i_lines_read", y); | |
318 | return im; | |
319 | } | |
320 | else { | |
321 | i_push_error(0, "short read - file truncated?"); | |
322 | i_img_destroy(im); | |
323 | return NULL; | |
324 | } | |
325 | } | |
326 | for(x=0; x<width; x++) { | |
327 | for(ch=0; ch<channels; ch++) { | |
328 | unsigned sample = (readp[0] << 8) + readp[1]; | |
329 | if (sample > maxval) | |
330 | sample = maxval; | |
331 | readp += 2; | |
332 | linep->channel[ch] = sample / maxvalf; | |
333 | } | |
334 | ++linep; | |
335 | } | |
336 | i_plinf(im, 0, width, y, line); | |
337 | } | |
338 | myfree(read_buf); | |
339 | myfree(line); | |
340 | ||
341 | return im; | |
342 | } | |
343 | ||
344 | static | |
345 | i_img * | |
d87dc9a4 | 346 | read_pbm_bin(mbuf *mb, i_img *im, int width, int height, int allow_incomplete) { |
9c106321 TC |
347 | i_palidx *line, *linep; |
348 | int read_size; | |
349 | unsigned char *read_buf, *readp; | |
350 | int x, y; | |
351 | unsigned mask; | |
352 | ||
353 | line = mymalloc(width * sizeof(i_palidx)); | |
354 | read_size = (width + 7) / 8; | |
355 | read_buf = mymalloc(read_size); | |
356 | for(y = 0; y < height; y++) { | |
357 | if (gread(mb, read_buf, read_size) != read_size) { | |
358 | myfree(line); | |
359 | myfree(read_buf); | |
d87dc9a4 | 360 | if (allow_incomplete) { |
9c106321 TC |
361 | i_tags_setn(&im->tags, "i_incomplete", 1); |
362 | i_tags_setn(&im->tags, "i_lines_read", y); | |
363 | return im; | |
364 | } | |
365 | else { | |
366 | i_push_error(0, "short read - file truncated?"); | |
367 | i_img_destroy(im); | |
368 | return NULL; | |
369 | } | |
370 | } | |
371 | linep = line; | |
372 | readp = read_buf; | |
373 | mask = 0x80; | |
374 | for(x = 0; x < width; ++x) { | |
375 | *linep++ = *readp & mask ? 1 : 0; | |
376 | mask >>= 1; | |
377 | if (mask == 0) { | |
378 | ++readp; | |
379 | mask = 0x80; | |
380 | } | |
381 | } | |
382 | i_ppal(im, 0, width, y, line); | |
383 | } | |
384 | myfree(read_buf); | |
385 | myfree(line); | |
386 | ||
387 | return im; | |
388 | } | |
389 | ||
390 | /* unlike pgm/ppm pbm: | |
391 | - doesn't require spaces between samples (bits) | |
392 | - 1 (maxval) is black instead of white | |
393 | */ | |
394 | static | |
395 | i_img * | |
d87dc9a4 | 396 | read_pbm_ascii(mbuf *mb, i_img *im, int width, int height, int allow_incomplete) { |
9c106321 TC |
397 | i_palidx *line, *linep; |
398 | int x, y; | |
399 | ||
400 | line = mymalloc(width * sizeof(i_palidx)); | |
401 | for(y = 0; y < height; y++) { | |
402 | linep = line; | |
403 | for(x = 0; x < width; ++x) { | |
404 | char *cp; | |
405 | skip_spaces(mb); | |
406 | if (!(cp = gnext(mb)) || (*cp != '0' && *cp != '1')) { | |
407 | myfree(line); | |
d87dc9a4 | 408 | if (allow_incomplete) { |
9c106321 TC |
409 | i_tags_setn(&im->tags, "i_incomplete", 1); |
410 | i_tags_setn(&im->tags, "i_lines_read", y); | |
411 | return im; | |
412 | } | |
413 | else { | |
414 | if (cp) | |
415 | i_push_error(0, "invalid data for ascii pnm"); | |
416 | else | |
417 | i_push_error(0, "short read - file truncated?"); | |
418 | i_img_destroy(im); | |
419 | return NULL; | |
420 | } | |
421 | } | |
422 | *linep++ = *cp == '0' ? 0 : 1; | |
423 | } | |
424 | i_ppal(im, 0, width, y, line); | |
425 | } | |
426 | myfree(line); | |
427 | ||
428 | return im; | |
429 | } | |
430 | ||
431 | static | |
432 | i_img * | |
433 | read_pgm_ppm_ascii(mbuf *mb, i_img *im, int width, int height, int channels, | |
d87dc9a4 | 434 | int maxval, int allow_incomplete) { |
9c106321 TC |
435 | i_color *line, *linep; |
436 | int x, y, ch; | |
437 | int rounder = maxval / 2; | |
438 | ||
439 | line = mymalloc(width * sizeof(i_color)); | |
440 | for(y=0;y<height;y++) { | |
441 | linep = line; | |
442 | for(x=0; x<width; x++) { | |
443 | for(ch=0; ch<channels; ch++) { | |
444 | int sample; | |
445 | ||
446 | if (!gnum(mb, &sample)) { | |
447 | myfree(line); | |
d87dc9a4 | 448 | if (allow_incomplete) { |
9c106321 TC |
449 | i_tags_setn(&im->tags, "i_incomplete", 1); |
450 | i_tags_setn(&im->tags, "i_lines_read", 1); | |
451 | return im; | |
452 | } | |
453 | else { | |
454 | if (gpeek(mb)) | |
455 | i_push_error(0, "invalid data for ascii pnm"); | |
456 | else | |
457 | i_push_error(0, "short read - file truncated?"); | |
458 | i_img_destroy(im); | |
459 | return NULL; | |
460 | } | |
461 | } | |
462 | if (sample > maxval) | |
463 | sample = maxval; | |
464 | linep->channel[ch] = (sample * 255 + rounder) / maxval; | |
465 | } | |
466 | ++linep; | |
467 | } | |
468 | i_plin(im, 0, width, y, line); | |
469 | } | |
470 | myfree(line); | |
471 | ||
472 | return im; | |
473 | } | |
474 | ||
475 | static | |
476 | i_img * | |
477 | read_pgm_ppm_ascii_16(mbuf *mb, i_img *im, int width, int height, | |
d87dc9a4 | 478 | int channels, int maxval, int allow_incomplete) { |
9c106321 TC |
479 | i_fcolor *line, *linep; |
480 | int x, y, ch; | |
481 | double maxvalf = maxval; | |
482 | ||
483 | line = mymalloc(width * sizeof(i_fcolor)); | |
484 | for(y=0;y<height;y++) { | |
485 | linep = line; | |
486 | for(x=0; x<width; x++) { | |
487 | for(ch=0; ch<channels; ch++) { | |
488 | int sample; | |
489 | ||
490 | if (!gnum(mb, &sample)) { | |
491 | myfree(line); | |
d87dc9a4 TC |
492 | if (allow_incomplete) { |
493 | i_tags_setn(&im->tags, "i_incomplete", 1); | |
494 | i_tags_setn(&im->tags, "i_lines_read", y); | |
495 | return im; | |
9c106321 TC |
496 | } |
497 | else { | |
498 | if (gpeek(mb)) | |
499 | i_push_error(0, "invalid data for ascii pnm"); | |
500 | else | |
501 | i_push_error(0, "short read - file truncated?"); | |
502 | i_img_destroy(im); | |
503 | return NULL; | |
504 | } | |
505 | } | |
506 | if (sample > maxval) | |
507 | sample = maxval; | |
508 | linep->channel[ch] = sample / maxvalf; | |
509 | } | |
510 | ++linep; | |
511 | } | |
512 | i_plinf(im, 0, width, y, line); | |
513 | } | |
514 | myfree(line); | |
515 | ||
516 | return im; | |
517 | } | |
02d1d628 AMH |
518 | |
519 | /* | |
d87dc9a4 | 520 | =item i_readpnm_wiol(ig, allow_incomplete) |
02d1d628 AMH |
521 | |
522 | Retrieve an image and stores in the iolayer object. Returns NULL on fatal error. | |
523 | ||
524 | ig - io_glue object | |
d87dc9a4 | 525 | allow_incomplete - allows a partial file to be read successfully |
02d1d628 AMH |
526 | |
527 | =cut | |
528 | */ | |
2086be61 | 529 | static i_img *i_readpnm_wiol_low( mbuf*, int); |
02d1d628 AMH |
530 | |
531 | i_img * | |
d87dc9a4 | 532 | i_readpnm_wiol(io_glue *ig, int allow_incomplete) { |
2086be61 TC |
533 | mbuf buf; |
534 | io_glue_commit_types(ig); | |
535 | init_buf(&buf, ig); | |
536 | ||
537 | return i_readpnm_wiol_low( &buf, allow_incomplete ); | |
538 | } | |
539 | ||
540 | static i_img * | |
541 | i_readpnm_wiol_low( mbuf *buf, int allow_incomplete) { | |
02d1d628 AMH |
542 | i_img* im; |
543 | int type; | |
02d1d628 | 544 | int width, height, maxval, channels, pcount; |
8b695554 | 545 | int rounder; |
02d1d628 | 546 | char *cp; |
02d1d628 | 547 | |
fac8664e | 548 | i_clear_error(); |
2086be61 | 549 | mm_log((1,"i_readpnm(ig %p, allow_incomplete %d)\n", buf->ig, allow_incomplete)); |
02d1d628 | 550 | |
2086be61 | 551 | cp = gnext(buf); |
02d1d628 AMH |
552 | |
553 | if (!cp || *cp != 'P') { | |
fac8664e | 554 | i_push_error(0, "bad header magic, not a PNM file"); |
02d1d628 AMH |
555 | mm_log((1, "i_readpnm: Could not read header of file\n")); |
556 | return NULL; | |
557 | } | |
558 | ||
2086be61 | 559 | if ( !(cp = gnext(buf)) ) { |
02d1d628 AMH |
560 | mm_log((1, "i_readpnm: Could not read header of file\n")); |
561 | return NULL; | |
562 | } | |
563 | ||
564 | type = *cp-'0'; | |
565 | ||
566 | if (type < 1 || type > 6) { | |
fac8664e | 567 | i_push_error(0, "unknown PNM file type, not a PNM file"); |
02d1d628 AMH |
568 | mm_log((1, "i_readpnm: Not a pnm file\n")); |
569 | return NULL; | |
570 | } | |
571 | ||
2086be61 | 572 | if ( !(cp = gnext(buf)) ) { |
02d1d628 AMH |
573 | mm_log((1, "i_readpnm: Could not read header of file\n")); |
574 | return NULL; | |
575 | } | |
576 | ||
577 | if ( !misspace(*cp) ) { | |
fac8664e | 578 | i_push_error(0, "unexpected character, not a PNM file"); |
02d1d628 AMH |
579 | mm_log((1, "i_readpnm: Not a pnm file\n")); |
580 | return NULL; | |
581 | } | |
582 | ||
583 | mm_log((1, "i_readpnm: image is a %s\n", typenames[type-1] )); | |
584 | ||
585 | ||
586 | /* Read sizes and such */ | |
587 | ||
2086be61 | 588 | if (!skip_comment(buf)) { |
fac8664e | 589 | i_push_error(0, "while skipping to width"); |
02d1d628 AMH |
590 | mm_log((1, "i_readpnm: error reading before width\n")); |
591 | return NULL; | |
592 | } | |
593 | ||
2086be61 | 594 | if (!gnum(buf, &width)) { |
fac8664e | 595 | i_push_error(0, "could not read image width"); |
02d1d628 AMH |
596 | mm_log((1, "i_readpnm: error reading width\n")); |
597 | return NULL; | |
598 | } | |
599 | ||
2086be61 | 600 | if (!skip_comment(buf)) { |
fac8664e | 601 | i_push_error(0, "while skipping to height"); |
02d1d628 AMH |
602 | mm_log((1, "i_readpnm: error reading before height\n")); |
603 | return NULL; | |
604 | } | |
605 | ||
2086be61 | 606 | if (!gnum(buf, &height)) { |
fac8664e | 607 | i_push_error(0, "could not read image height"); |
02d1d628 AMH |
608 | mm_log((1, "i_readpnm: error reading height\n")); |
609 | return NULL; | |
610 | } | |
611 | ||
612 | if (!(type == 1 || type == 4)) { | |
2086be61 | 613 | if (!skip_comment(buf)) { |
fac8664e | 614 | i_push_error(0, "while skipping to maxval"); |
02d1d628 AMH |
615 | mm_log((1, "i_readpnm: error reading before maxval\n")); |
616 | return NULL; | |
617 | } | |
618 | ||
2086be61 | 619 | if (!gnum(buf, &maxval)) { |
fac8664e | 620 | i_push_error(0, "could not read maxval"); |
02d1d628 AMH |
621 | mm_log((1, "i_readpnm: error reading maxval\n")); |
622 | return NULL; | |
623 | } | |
8b695554 TC |
624 | |
625 | if (maxval == 0) { | |
626 | i_push_error(0, "maxval is zero - invalid pnm file"); | |
627 | mm_log((1, "i_readpnm: maxval is zero, invalid pnm file\n")); | |
628 | return NULL; | |
629 | } | |
630 | else if (maxval > 65535) { | |
631 | i_push_errorf(0, "maxval of %d is over 65535 - invalid pnm file", | |
632 | maxval); | |
633 | mm_log((1, "i_readpnm: maxval of %d is over 65535 - invalid pnm file\n")); | |
634 | return NULL; | |
635 | } | |
02d1d628 | 636 | } else maxval=1; |
8b695554 | 637 | rounder = maxval / 2; |
02d1d628 | 638 | |
2086be61 | 639 | if (!(cp = gnext(buf)) || !misspace(*cp)) { |
fac8664e | 640 | i_push_error(0, "garbage in header, invalid PNM file"); |
02d1d628 AMH |
641 | mm_log((1, "i_readpnm: garbage in header\n")); |
642 | return NULL; | |
643 | } | |
644 | ||
645 | channels = (type == 3 || type == 6) ? 3:1; | |
646 | pcount = width*height*channels; | |
647 | ||
77157728 TC |
648 | if (!i_int_check_image_file_limits(width, height, channels, sizeof(i_sample_t))) { |
649 | mm_log((1, "i_readpnm: image size exceeds limits\n")); | |
650 | return NULL; | |
651 | } | |
652 | ||
02d1d628 | 653 | mm_log((1, "i_readpnm: (%d x %d), channels = %d, maxval = %d\n", width, height, channels, maxval)); |
02d1d628 | 654 | |
9c106321 TC |
655 | if (type == 1 || type == 4) { |
656 | i_color pbm_pal[2]; | |
657 | pbm_pal[0].channel[0] = 255; | |
658 | pbm_pal[1].channel[0] = 0; | |
659 | ||
660 | im = i_img_pal_new(width, height, 1, 256); | |
661 | i_addcolors(im, pbm_pal, 2); | |
662 | } | |
663 | else { | |
664 | if (maxval > 255) | |
665 | im = i_img_16_new(width, height, channels); | |
666 | else | |
667 | im = i_img_8_new(width, height, channels); | |
668 | } | |
642a675b | 669 | |
02d1d628 AMH |
670 | switch (type) { |
671 | case 1: /* Ascii types */ | |
2086be61 | 672 | im = read_pbm_ascii(buf, im, width, height, allow_incomplete); |
9c106321 TC |
673 | break; |
674 | ||
02d1d628 AMH |
675 | case 2: |
676 | case 3: | |
9c106321 | 677 | if (maxval > 255) |
2086be61 | 678 | im = read_pgm_ppm_ascii_16(buf, im, width, height, channels, maxval, allow_incomplete); |
9c106321 | 679 | else |
2086be61 | 680 | im = read_pgm_ppm_ascii(buf, im, width, height, channels, maxval, allow_incomplete); |
02d1d628 AMH |
681 | break; |
682 | ||
683 | case 4: /* binary pbm */ | |
2086be61 | 684 | im = read_pbm_bin(buf, im, width, height, allow_incomplete); |
02d1d628 AMH |
685 | break; |
686 | ||
687 | case 5: /* binary pgm */ | |
688 | case 6: /* binary ppm */ | |
9c106321 | 689 | if (maxval > 255) |
2086be61 | 690 | im = read_pgm_ppm_bin16(buf, im, width, height, channels, maxval, allow_incomplete); |
9c106321 | 691 | else |
2086be61 | 692 | im = read_pgm_ppm_bin8(buf, im, width, height, channels, maxval, allow_incomplete); |
02d1d628 | 693 | break; |
9c106321 | 694 | |
02d1d628 AMH |
695 | default: |
696 | mm_log((1, "type %s [P%d] unsupported\n", typenames[type-1], type)); | |
697 | return NULL; | |
698 | } | |
9c106321 TC |
699 | |
700 | if (!im) | |
701 | return NULL; | |
702 | ||
703 | i_tags_add(&im->tags, "i_format", 0, "pnm", -1, 0); | |
704 | i_tags_setn(&im->tags, "pnm_maxval", maxval); | |
705 | i_tags_setn(&im->tags, "pnm_type", type); | |
706 | ||
02d1d628 AMH |
707 | return im; |
708 | } | |
709 | ||
2086be61 TC |
710 | static void free_images(i_img **imgs, int count) { |
711 | int i; | |
712 | ||
713 | if (count) { | |
714 | for (i = 0; i < count; ++i) | |
715 | i_img_destroy(imgs[i]); | |
716 | myfree(imgs); | |
717 | } | |
718 | } | |
719 | ||
720 | i_img **i_readpnm_multi_wiol(io_glue *ig, int *count, int allow_incomplete) { | |
721 | i_img **results = NULL; | |
722 | i_img *img = NULL; | |
723 | char *cp = NULL; | |
724 | mbuf buf; | |
725 | int result_alloc = 0, | |
726 | value = 0, | |
727 | eof = 0; | |
728 | *count=0; | |
729 | io_glue_commit_types(ig); | |
730 | init_buf(&buf, ig); | |
731 | do { | |
732 | mm_log((1, "read image %i\n", 1+*count)); | |
733 | img = i_readpnm_wiol_low( &buf, allow_incomplete ); | |
734 | if( !img ) { | |
735 | free_images( results, *count ); | |
736 | return NULL; | |
737 | } | |
738 | ++*count; | |
739 | if (*count > result_alloc) { | |
740 | if (result_alloc == 0) { | |
741 | result_alloc = 5; | |
742 | results = mymalloc(result_alloc * sizeof(i_img *)); | |
743 | } | |
744 | else { | |
745 | /* myrealloc never fails (it just dies if it can't allocate) */ | |
746 | result_alloc *= 2; | |
747 | results = myrealloc(results, result_alloc * sizeof(i_img *)); | |
748 | } | |
749 | } | |
750 | results[*count-1] = img; | |
751 | ||
752 | ||
753 | if( i_tags_get_int(&img->tags, "i_incomplete", 0, &value ) && value) { | |
754 | eof = 1; | |
755 | } | |
756 | else if( skip_spaces( &buf ) && ( cp=gpeek( &buf ) ) && *cp == 'P' ) { | |
757 | eof = 0; | |
758 | } | |
759 | else { | |
760 | eof = 1; | |
761 | } | |
762 | } while(!eof); | |
763 | return results; | |
764 | } | |
765 | ||
766 | ||
767 | ||
9c106321 TC |
768 | static |
769 | int | |
770 | write_pbm(i_img *im, io_glue *ig, int zero_is_white) { | |
771 | int x, y; | |
772 | i_palidx *line; | |
773 | int write_size; | |
774 | unsigned char *write_buf; | |
775 | unsigned char *writep; | |
776 | char header[255]; | |
777 | unsigned mask; | |
778 | ||
779 | sprintf(header, "P4\012# CREATOR: Imager\012%d %d\012", | |
780 | im->xsize, im->ysize); | |
781 | if (i_io_write(ig, header, strlen(header)) < 0) { | |
782 | i_push_error(0, "could not write pbm header"); | |
783 | return 0; | |
784 | } | |
785 | write_size = (im->xsize + 7) / 8; | |
786 | line = mymalloc(sizeof(i_palidx) * im->xsize); | |
787 | write_buf = mymalloc(write_size); | |
788 | for (y = 0; y < im->ysize; ++y) { | |
789 | i_gpal(im, 0, im->xsize, y, line); | |
790 | mask = 0x80; | |
791 | writep = write_buf; | |
792 | memset(write_buf, 0, write_size); | |
793 | for (x = 0; x < im->xsize; ++x) { | |
794 | if (zero_is_white ? line[x] : !line[x]) | |
795 | *writep |= mask; | |
796 | mask >>= 1; | |
797 | if (!mask) { | |
798 | ++writep; | |
799 | mask = 0x80; | |
800 | } | |
801 | } | |
802 | if (i_io_write(ig, write_buf, write_size) != write_size) { | |
803 | i_push_error(0, "write failure"); | |
804 | myfree(write_buf); | |
805 | myfree(line); | |
806 | return 0; | |
807 | } | |
808 | } | |
809 | myfree(write_buf); | |
810 | myfree(line); | |
811 | ||
812 | return 1; | |
813 | } | |
814 | ||
815 | static | |
816 | int | |
fa90de94 TC |
817 | write_ppm_data_8(i_img *im, io_glue *ig, int want_channels) { |
818 | int write_size = im->xsize * want_channels; | |
2a31a4b4 TC |
819 | int buf_size = im->xsize * im->channels; |
820 | unsigned char *data = mymalloc(buf_size); | |
9c106321 TC |
821 | int y = 0; |
822 | int rc = 1; | |
fa90de94 | 823 | i_color bg; |
9c106321 | 824 | |
fa90de94 | 825 | i_get_file_background(im, &bg); |
9c106321 | 826 | while (y < im->ysize && rc >= 0) { |
2a31a4b4 | 827 | i_gsamp_bg(im, 0, im->xsize, y, data, want_channels, &bg); |
9c106321 TC |
828 | if (i_io_write(ig, data, write_size) != write_size) { |
829 | i_push_error(errno, "could not write ppm data"); | |
830 | rc = 0; | |
831 | break; | |
832 | } | |
833 | ++y; | |
834 | } | |
835 | myfree(data); | |
836 | ||
837 | return rc; | |
838 | } | |
839 | ||
840 | static | |
841 | int | |
fa90de94 | 842 | write_ppm_data_16(i_img *im, io_glue *ig, int want_channels) { |
2a31a4b4 | 843 | int line_size = im->channels * im->xsize * sizeof(i_fsample_t); |
fa90de94 | 844 | int sample_count = want_channels * im->xsize; |
9c106321 | 845 | int write_size = sample_count * 2; |
2a31a4b4 TC |
846 | i_fsample_t *line_buf = mymalloc(line_size); |
847 | i_fsample_t *samplep; | |
9c106321 | 848 | unsigned char *write_buf = mymalloc(write_size); |
2a31a4b4 TC |
849 | unsigned char *writep; |
850 | int sample_num; | |
9c106321 TC |
851 | int y = 0; |
852 | int rc = 1; | |
fa90de94 TC |
853 | i_fcolor bg; |
854 | ||
855 | i_get_file_backgroundf(im, &bg); | |
9c106321 TC |
856 | |
857 | while (y < im->ysize) { | |
2a31a4b4 TC |
858 | i_gsampf_bg(im, 0, im->xsize, y, line_buf, want_channels, &bg); |
859 | samplep = line_buf; | |
860 | writep = write_buf; | |
861 | for (sample_num = 0; sample_num < sample_count; ++sample_num) { | |
862 | unsigned sample16 = SampleFTo16(*samplep++); | |
863 | *writep++ = sample16 >> 8; | |
864 | *writep++ = sample16 & 0xFF; | |
9c106321 TC |
865 | } |
866 | if (i_io_write(ig, write_buf, write_size) != write_size) { | |
867 | i_push_error(errno, "could not write ppm data"); | |
868 | rc = 0; | |
869 | break; | |
870 | } | |
871 | ++y; | |
872 | } | |
873 | myfree(line_buf); | |
874 | myfree(write_buf); | |
875 | ||
876 | return rc; | |
877 | } | |
02d1d628 | 878 | |
067d6bdc AMH |
879 | undef_int |
880 | i_writeppm_wiol(i_img *im, io_glue *ig) { | |
881 | char header[255]; | |
9c106321 TC |
882 | int zero_is_white; |
883 | int wide_data; | |
02d1d628 | 884 | |
067d6bdc AMH |
885 | mm_log((1,"i_writeppm(im %p, ig %p)\n", im, ig)); |
886 | i_clear_error(); | |
02d1d628 | 887 | |
067d6bdc AMH |
888 | /* Add code to get the filename info from the iolayer */ |
889 | /* Also add code to check for mmapped code */ | |
02d1d628 | 890 | |
067d6bdc | 891 | io_glue_commit_types(ig); |
02d1d628 | 892 | |
9c106321 TC |
893 | if (i_img_is_monochrome(im, &zero_is_white)) { |
894 | return write_pbm(im, ig, zero_is_white); | |
895 | } | |
896 | else { | |
897 | int type; | |
898 | int maxval; | |
fa90de94 TC |
899 | int want_channels = im->channels; |
900 | ||
901 | if (want_channels == 2 || want_channels == 4) | |
902 | --want_channels; | |
faa9b3e7 | 903 | |
9c106321 TC |
904 | if (!i_tags_get_int(&im->tags, "pnm_write_wide_data", 0, &wide_data)) |
905 | wide_data = 0; | |
906 | ||
fa90de94 | 907 | if (want_channels == 3) { |
9c106321 | 908 | type = 6; |
faa9b3e7 | 909 | } |
fa90de94 | 910 | else if (want_channels == 1) { |
9c106321 | 911 | type = 5; |
faa9b3e7 | 912 | } |
9c106321 TC |
913 | else { |
914 | i_push_error(0, "can only save 1 or 3 channel images to pnm"); | |
915 | mm_log((1,"i_writeppm: ppm/pgm is 1 or 3 channel only (current image is %d)\n",im->channels)); | |
067d6bdc AMH |
916 | return(0); |
917 | } | |
9c106321 TC |
918 | if (im->bits <= 8 || !wide_data) |
919 | maxval = 255; | |
920 | else | |
921 | maxval = 65535; | |
922 | ||
923 | sprintf(header,"P%d\n#CREATOR: Imager\n%d %d\n%d\n", | |
924 | type, im->xsize, im->ysize, maxval); | |
925 | ||
926 | if (ig->writecb(ig,header,strlen(header)) != strlen(header)) { | |
927 | i_push_error(errno, "could not write ppm header"); | |
928 | mm_log((1,"i_writeppm: unable to write ppm header.\n")); | |
067d6bdc AMH |
929 | return(0); |
930 | } | |
faa9b3e7 | 931 | |
fa90de94 TC |
932 | if (!im->virtual && im->bits == i_8_bits && im->type == i_direct_type |
933 | && im->channels == want_channels) { | |
9c106321 TC |
934 | if (ig->writecb(ig,im->idata,im->bytes) != im->bytes) { |
935 | i_push_error(errno, "could not write ppm data"); | |
faa9b3e7 TC |
936 | return 0; |
937 | } | |
938 | } | |
9c106321 | 939 | else if (maxval == 255) { |
fa90de94 | 940 | if (!write_ppm_data_8(im, ig, want_channels)) |
9c106321 TC |
941 | return 0; |
942 | } | |
943 | else { | |
fa90de94 | 944 | if (!write_ppm_data_16(im, ig, want_channels)) |
9c106321 | 945 | return 0; |
067d6bdc | 946 | } |
067d6bdc | 947 | } |
10461f9a | 948 | ig->closecb(ig); |
faa9b3e7 | 949 | |
067d6bdc AMH |
950 | return(1); |
951 | } | |
b8c2033e AMH |
952 | |
953 | /* | |
954 | =back | |
955 | ||
956 | =head1 AUTHOR | |
957 | ||
5b480b14 | 958 | Arnar M. Hrafnkelsson <addi@umich.edu>, Tony Cook <tonyc@cpan.org>, |
2086be61 | 959 | Philip Gwyn <gwyn@cpan.org>. |
b8c2033e AMH |
960 | |
961 | =head1 SEE ALSO | |
962 | ||
963 | Imager(3) | |
964 | ||
965 | =cut | |
966 | */ |