]>
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 | */ | |
529 | ||
530 | ||
531 | i_img * | |
d87dc9a4 | 532 | i_readpnm_wiol(io_glue *ig, int allow_incomplete) { |
02d1d628 AMH |
533 | i_img* im; |
534 | int type; | |
02d1d628 | 535 | int width, height, maxval, channels, pcount; |
8b695554 | 536 | int rounder; |
02d1d628 | 537 | char *cp; |
02d1d628 | 538 | mbuf buf; |
02d1d628 | 539 | |
fac8664e | 540 | i_clear_error(); |
d87dc9a4 | 541 | mm_log((1,"i_readpnm(ig %p, allow_incomplete %d)\n", ig, allow_incomplete)); |
02d1d628 | 542 | |
02d1d628 AMH |
543 | io_glue_commit_types(ig); |
544 | init_buf(&buf, ig); | |
545 | ||
546 | cp = gnext(&buf); | |
547 | ||
548 | if (!cp || *cp != 'P') { | |
fac8664e | 549 | i_push_error(0, "bad header magic, not a PNM file"); |
02d1d628 AMH |
550 | mm_log((1, "i_readpnm: Could not read header of file\n")); |
551 | return NULL; | |
552 | } | |
553 | ||
554 | if ( !(cp = gnext(&buf)) ) { | |
555 | mm_log((1, "i_readpnm: Could not read header of file\n")); | |
556 | return NULL; | |
557 | } | |
558 | ||
559 | type = *cp-'0'; | |
560 | ||
561 | if (type < 1 || type > 6) { | |
fac8664e | 562 | i_push_error(0, "unknown PNM file type, not a PNM file"); |
02d1d628 AMH |
563 | mm_log((1, "i_readpnm: Not a pnm file\n")); |
564 | return NULL; | |
565 | } | |
566 | ||
567 | if ( !(cp = gnext(&buf)) ) { | |
568 | mm_log((1, "i_readpnm: Could not read header of file\n")); | |
569 | return NULL; | |
570 | } | |
571 | ||
572 | if ( !misspace(*cp) ) { | |
fac8664e | 573 | i_push_error(0, "unexpected character, not a PNM file"); |
02d1d628 AMH |
574 | mm_log((1, "i_readpnm: Not a pnm file\n")); |
575 | return NULL; | |
576 | } | |
577 | ||
578 | mm_log((1, "i_readpnm: image is a %s\n", typenames[type-1] )); | |
579 | ||
580 | ||
581 | /* Read sizes and such */ | |
582 | ||
583 | if (!skip_comment(&buf)) { | |
fac8664e | 584 | i_push_error(0, "while skipping to width"); |
02d1d628 AMH |
585 | mm_log((1, "i_readpnm: error reading before width\n")); |
586 | return NULL; | |
587 | } | |
588 | ||
589 | if (!gnum(&buf, &width)) { | |
fac8664e | 590 | i_push_error(0, "could not read image width"); |
02d1d628 AMH |
591 | mm_log((1, "i_readpnm: error reading width\n")); |
592 | return NULL; | |
593 | } | |
594 | ||
595 | if (!skip_comment(&buf)) { | |
fac8664e | 596 | i_push_error(0, "while skipping to height"); |
02d1d628 AMH |
597 | mm_log((1, "i_readpnm: error reading before height\n")); |
598 | return NULL; | |
599 | } | |
600 | ||
601 | if (!gnum(&buf, &height)) { | |
fac8664e | 602 | i_push_error(0, "could not read image height"); |
02d1d628 AMH |
603 | mm_log((1, "i_readpnm: error reading height\n")); |
604 | return NULL; | |
605 | } | |
606 | ||
607 | if (!(type == 1 || type == 4)) { | |
608 | if (!skip_comment(&buf)) { | |
fac8664e | 609 | i_push_error(0, "while skipping to maxval"); |
02d1d628 AMH |
610 | mm_log((1, "i_readpnm: error reading before maxval\n")); |
611 | return NULL; | |
612 | } | |
613 | ||
614 | if (!gnum(&buf, &maxval)) { | |
fac8664e | 615 | i_push_error(0, "could not read maxval"); |
02d1d628 AMH |
616 | mm_log((1, "i_readpnm: error reading maxval\n")); |
617 | return NULL; | |
618 | } | |
8b695554 TC |
619 | |
620 | if (maxval == 0) { | |
621 | i_push_error(0, "maxval is zero - invalid pnm file"); | |
622 | mm_log((1, "i_readpnm: maxval is zero, invalid pnm file\n")); | |
623 | return NULL; | |
624 | } | |
625 | else if (maxval > 65535) { | |
626 | i_push_errorf(0, "maxval of %d is over 65535 - invalid pnm file", | |
627 | maxval); | |
628 | mm_log((1, "i_readpnm: maxval of %d is over 65535 - invalid pnm file\n")); | |
629 | return NULL; | |
630 | } | |
02d1d628 | 631 | } else maxval=1; |
8b695554 | 632 | rounder = maxval / 2; |
02d1d628 AMH |
633 | |
634 | if (!(cp = gnext(&buf)) || !misspace(*cp)) { | |
fac8664e | 635 | i_push_error(0, "garbage in header, invalid PNM file"); |
02d1d628 AMH |
636 | mm_log((1, "i_readpnm: garbage in header\n")); |
637 | return NULL; | |
638 | } | |
639 | ||
640 | channels = (type == 3 || type == 6) ? 3:1; | |
641 | pcount = width*height*channels; | |
642 | ||
77157728 TC |
643 | if (!i_int_check_image_file_limits(width, height, channels, sizeof(i_sample_t))) { |
644 | mm_log((1, "i_readpnm: image size exceeds limits\n")); | |
645 | return NULL; | |
646 | } | |
647 | ||
02d1d628 | 648 | mm_log((1, "i_readpnm: (%d x %d), channels = %d, maxval = %d\n", width, height, channels, maxval)); |
02d1d628 | 649 | |
9c106321 TC |
650 | if (type == 1 || type == 4) { |
651 | i_color pbm_pal[2]; | |
652 | pbm_pal[0].channel[0] = 255; | |
653 | pbm_pal[1].channel[0] = 0; | |
654 | ||
655 | im = i_img_pal_new(width, height, 1, 256); | |
656 | i_addcolors(im, pbm_pal, 2); | |
657 | } | |
658 | else { | |
659 | if (maxval > 255) | |
660 | im = i_img_16_new(width, height, channels); | |
661 | else | |
662 | im = i_img_8_new(width, height, channels); | |
663 | } | |
642a675b | 664 | |
02d1d628 AMH |
665 | switch (type) { |
666 | case 1: /* Ascii types */ | |
d87dc9a4 | 667 | im = read_pbm_ascii(&buf, im, width, height, allow_incomplete); |
9c106321 TC |
668 | break; |
669 | ||
02d1d628 AMH |
670 | case 2: |
671 | case 3: | |
9c106321 | 672 | if (maxval > 255) |
d87dc9a4 | 673 | im = read_pgm_ppm_ascii_16(&buf, im, width, height, channels, maxval, allow_incomplete); |
9c106321 | 674 | else |
d87dc9a4 | 675 | im = read_pgm_ppm_ascii(&buf, im, width, height, channels, maxval, allow_incomplete); |
02d1d628 AMH |
676 | break; |
677 | ||
678 | case 4: /* binary pbm */ | |
d87dc9a4 | 679 | im = read_pbm_bin(&buf, im, width, height, allow_incomplete); |
02d1d628 AMH |
680 | break; |
681 | ||
682 | case 5: /* binary pgm */ | |
683 | case 6: /* binary ppm */ | |
9c106321 | 684 | if (maxval > 255) |
d87dc9a4 | 685 | im = read_pgm_ppm_bin16(&buf, im, width, height, channels, maxval, allow_incomplete); |
9c106321 | 686 | else |
d87dc9a4 | 687 | im = read_pgm_ppm_bin8(&buf, im, width, height, channels, maxval, allow_incomplete); |
02d1d628 | 688 | break; |
9c106321 | 689 | |
02d1d628 AMH |
690 | default: |
691 | mm_log((1, "type %s [P%d] unsupported\n", typenames[type-1], type)); | |
692 | return NULL; | |
693 | } | |
9c106321 TC |
694 | |
695 | if (!im) | |
696 | return NULL; | |
697 | ||
698 | i_tags_add(&im->tags, "i_format", 0, "pnm", -1, 0); | |
699 | i_tags_setn(&im->tags, "pnm_maxval", maxval); | |
700 | i_tags_setn(&im->tags, "pnm_type", type); | |
701 | ||
02d1d628 AMH |
702 | return im; |
703 | } | |
704 | ||
9c106321 TC |
705 | static |
706 | int | |
707 | write_pbm(i_img *im, io_glue *ig, int zero_is_white) { | |
708 | int x, y; | |
709 | i_palidx *line; | |
710 | int write_size; | |
711 | unsigned char *write_buf; | |
712 | unsigned char *writep; | |
713 | char header[255]; | |
714 | unsigned mask; | |
715 | ||
716 | sprintf(header, "P4\012# CREATOR: Imager\012%d %d\012", | |
717 | im->xsize, im->ysize); | |
718 | if (i_io_write(ig, header, strlen(header)) < 0) { | |
719 | i_push_error(0, "could not write pbm header"); | |
720 | return 0; | |
721 | } | |
722 | write_size = (im->xsize + 7) / 8; | |
723 | line = mymalloc(sizeof(i_palidx) * im->xsize); | |
724 | write_buf = mymalloc(write_size); | |
725 | for (y = 0; y < im->ysize; ++y) { | |
726 | i_gpal(im, 0, im->xsize, y, line); | |
727 | mask = 0x80; | |
728 | writep = write_buf; | |
729 | memset(write_buf, 0, write_size); | |
730 | for (x = 0; x < im->xsize; ++x) { | |
731 | if (zero_is_white ? line[x] : !line[x]) | |
732 | *writep |= mask; | |
733 | mask >>= 1; | |
734 | if (!mask) { | |
735 | ++writep; | |
736 | mask = 0x80; | |
737 | } | |
738 | } | |
739 | if (i_io_write(ig, write_buf, write_size) != write_size) { | |
740 | i_push_error(0, "write failure"); | |
741 | myfree(write_buf); | |
742 | myfree(line); | |
743 | return 0; | |
744 | } | |
745 | } | |
746 | myfree(write_buf); | |
747 | myfree(line); | |
748 | ||
749 | return 1; | |
750 | } | |
751 | ||
752 | static | |
753 | int | |
fa90de94 TC |
754 | write_ppm_data_8(i_img *im, io_glue *ig, int want_channels) { |
755 | int write_size = im->xsize * want_channels; | |
756 | i_color *line_buf = mymalloc(sizeof(i_color) * im->xsize); | |
9c106321 TC |
757 | unsigned char *data = mymalloc(write_size); |
758 | int y = 0; | |
fa90de94 TC |
759 | int x; |
760 | int ch; | |
9c106321 | 761 | int rc = 1; |
fa90de94 | 762 | i_color bg; |
9c106321 | 763 | |
fa90de94 | 764 | i_get_file_background(im, &bg); |
9c106321 | 765 | while (y < im->ysize && rc >= 0) { |
fa90de94 TC |
766 | i_color *linep = line_buf; |
767 | unsigned char *datap = data; | |
768 | ||
769 | i_glin(im, 0, im->xsize, y, line_buf); | |
770 | i_adapt_colors_bg(want_channels, im->channels, line_buf, im->xsize, &bg); | |
771 | for (x = 0; x < im->xsize; ++x) { | |
772 | for (ch = 0; ch < want_channels; ++ch) { | |
773 | *datap++ = linep->channel[ch]; | |
774 | } | |
775 | ++linep; | |
776 | } | |
9c106321 TC |
777 | if (i_io_write(ig, data, write_size) != write_size) { |
778 | i_push_error(errno, "could not write ppm data"); | |
fa90de94 TC |
779 | myfree(data); |
780 | myfree(line_buf); | |
9c106321 TC |
781 | rc = 0; |
782 | break; | |
783 | } | |
784 | ++y; | |
785 | } | |
786 | myfree(data); | |
fa90de94 | 787 | myfree(line_buf); |
9c106321 TC |
788 | |
789 | return rc; | |
790 | } | |
791 | ||
792 | static | |
793 | int | |
fa90de94 TC |
794 | write_ppm_data_16(i_img *im, io_glue *ig, int want_channels) { |
795 | int sample_count = want_channels * im->xsize; | |
9c106321 | 796 | int write_size = sample_count * 2; |
fa90de94 | 797 | i_fcolor *line_buf = mymalloc(sizeof(i_fcolor) * im->xsize); |
9c106321 | 798 | unsigned char *write_buf = mymalloc(write_size); |
9c106321 | 799 | int y = 0; |
fa90de94 | 800 | int x, ch; |
9c106321 | 801 | int rc = 1; |
fa90de94 TC |
802 | i_fcolor bg; |
803 | ||
804 | i_get_file_backgroundf(im, &bg); | |
9c106321 TC |
805 | |
806 | while (y < im->ysize) { | |
fa90de94 TC |
807 | i_fcolor *linep = line_buf; |
808 | unsigned char *writep = write_buf; | |
809 | ||
810 | i_glinf(im, 0, im->xsize, y, line_buf); | |
811 | i_adapt_fcolors_bg(want_channels, im->channels, line_buf, im->xsize, &bg); | |
812 | for (x = 0; x < im->xsize; ++x) { | |
813 | for (ch = 0; ch < want_channels; ++ch) { | |
814 | unsigned sample16 = SampleFTo16(linep->channel[ch]); | |
815 | *writep++ = sample16 >> 8; | |
816 | *writep++ = sample16 & 0xFF; | |
817 | } | |
818 | ++linep; | |
9c106321 | 819 | } |
fa90de94 | 820 | |
9c106321 TC |
821 | if (i_io_write(ig, write_buf, write_size) != write_size) { |
822 | i_push_error(errno, "could not write ppm data"); | |
823 | rc = 0; | |
824 | break; | |
825 | } | |
826 | ++y; | |
827 | } | |
828 | myfree(line_buf); | |
829 | myfree(write_buf); | |
830 | ||
831 | return rc; | |
832 | } | |
02d1d628 | 833 | |
067d6bdc AMH |
834 | undef_int |
835 | i_writeppm_wiol(i_img *im, io_glue *ig) { | |
836 | char header[255]; | |
9c106321 TC |
837 | int zero_is_white; |
838 | int wide_data; | |
02d1d628 | 839 | |
067d6bdc AMH |
840 | mm_log((1,"i_writeppm(im %p, ig %p)\n", im, ig)); |
841 | i_clear_error(); | |
02d1d628 | 842 | |
067d6bdc AMH |
843 | /* Add code to get the filename info from the iolayer */ |
844 | /* Also add code to check for mmapped code */ | |
02d1d628 | 845 | |
067d6bdc | 846 | io_glue_commit_types(ig); |
02d1d628 | 847 | |
9c106321 TC |
848 | if (i_img_is_monochrome(im, &zero_is_white)) { |
849 | return write_pbm(im, ig, zero_is_white); | |
850 | } | |
851 | else { | |
852 | int type; | |
853 | int maxval; | |
fa90de94 TC |
854 | int want_channels = im->channels; |
855 | ||
856 | if (want_channels == 2 || want_channels == 4) | |
857 | --want_channels; | |
faa9b3e7 | 858 | |
9c106321 TC |
859 | if (!i_tags_get_int(&im->tags, "pnm_write_wide_data", 0, &wide_data)) |
860 | wide_data = 0; | |
861 | ||
fa90de94 | 862 | if (want_channels == 3) { |
9c106321 | 863 | type = 6; |
faa9b3e7 | 864 | } |
fa90de94 | 865 | else if (want_channels == 1) { |
9c106321 | 866 | type = 5; |
faa9b3e7 | 867 | } |
9c106321 TC |
868 | else { |
869 | i_push_error(0, "can only save 1 or 3 channel images to pnm"); | |
870 | mm_log((1,"i_writeppm: ppm/pgm is 1 or 3 channel only (current image is %d)\n",im->channels)); | |
067d6bdc AMH |
871 | return(0); |
872 | } | |
9c106321 TC |
873 | if (im->bits <= 8 || !wide_data) |
874 | maxval = 255; | |
875 | else | |
876 | maxval = 65535; | |
877 | ||
878 | sprintf(header,"P%d\n#CREATOR: Imager\n%d %d\n%d\n", | |
879 | type, im->xsize, im->ysize, maxval); | |
880 | ||
881 | if (ig->writecb(ig,header,strlen(header)) != strlen(header)) { | |
882 | i_push_error(errno, "could not write ppm header"); | |
883 | mm_log((1,"i_writeppm: unable to write ppm header.\n")); | |
067d6bdc AMH |
884 | return(0); |
885 | } | |
faa9b3e7 | 886 | |
fa90de94 TC |
887 | if (!im->virtual && im->bits == i_8_bits && im->type == i_direct_type |
888 | && im->channels == want_channels) { | |
9c106321 TC |
889 | if (ig->writecb(ig,im->idata,im->bytes) != im->bytes) { |
890 | i_push_error(errno, "could not write ppm data"); | |
faa9b3e7 TC |
891 | return 0; |
892 | } | |
893 | } | |
9c106321 | 894 | else if (maxval == 255) { |
fa90de94 | 895 | if (!write_ppm_data_8(im, ig, want_channels)) |
9c106321 TC |
896 | return 0; |
897 | } | |
898 | else { | |
fa90de94 | 899 | if (!write_ppm_data_16(im, ig, want_channels)) |
9c106321 | 900 | return 0; |
067d6bdc | 901 | } |
067d6bdc | 902 | } |
10461f9a | 903 | ig->closecb(ig); |
faa9b3e7 | 904 | |
067d6bdc AMH |
905 | return(1); |
906 | } | |
b8c2033e AMH |
907 | |
908 | /* | |
909 | =back | |
910 | ||
911 | =head1 AUTHOR | |
912 | ||
9c106321 | 913 | Arnar M. Hrafnkelsson <addi@umich.edu>, Tony Cook<tony@imager.perl.org> |
b8c2033e AMH |
914 | |
915 | =head1 SEE ALSO | |
916 | ||
917 | Imager(3) | |
918 | ||
919 | =cut | |
920 | */ |