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