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 | 231 | while( (cp = gpeek(mb)) && misnumber(*cp) ) { |
8d14daab TC |
232 | int work = *i*10+(*cp-'0'); |
233 | if (work < *i) { | |
234 | /* overflow */ | |
235 | i_push_error(0, "integer overflow"); | |
236 | return 0; | |
237 | } | |
238 | *i = work; | |
02d1d628 AMH |
239 | cp = gnext(mb); |
240 | } | |
241 | return 1; | |
242 | } | |
243 | ||
9c106321 TC |
244 | static |
245 | i_img * | |
246 | read_pgm_ppm_bin8(mbuf *mb, i_img *im, int width, int height, | |
d87dc9a4 | 247 | int channels, int maxval, int allow_incomplete) { |
9c106321 TC |
248 | i_color *line, *linep; |
249 | int read_size; | |
250 | unsigned char *read_buf, *readp; | |
251 | int x, y, ch; | |
252 | int rounder = maxval / 2; | |
253 | ||
254 | line = mymalloc(width * sizeof(i_color)); | |
255 | read_size = channels * width; | |
256 | read_buf = mymalloc(read_size); | |
257 | for(y=0;y<height;y++) { | |
258 | linep = line; | |
259 | readp = read_buf; | |
260 | if (gread(mb, read_buf, read_size) != read_size) { | |
261 | myfree(line); | |
262 | myfree(read_buf); | |
d87dc9a4 | 263 | if (allow_incomplete) { |
9c106321 TC |
264 | i_tags_setn(&im->tags, "i_incomplete", 1); |
265 | i_tags_setn(&im->tags, "i_lines_read", y); | |
266 | return im; | |
267 | } | |
268 | else { | |
269 | i_push_error(0, "short read - file truncated?"); | |
270 | i_img_destroy(im); | |
271 | return NULL; | |
272 | } | |
273 | } | |
274 | if (maxval == 255) { | |
275 | for(x=0; x<width; x++) { | |
276 | for(ch=0; ch<channels; ch++) { | |
277 | linep->channel[ch] = *readp++; | |
278 | } | |
279 | ++linep; | |
280 | } | |
281 | } | |
282 | else { | |
283 | for(x=0; x<width; x++) { | |
284 | for(ch=0; ch<channels; ch++) { | |
285 | /* we just clamp samples to the correct range */ | |
286 | unsigned sample = *readp++; | |
287 | if (sample > maxval) | |
288 | sample = maxval; | |
289 | linep->channel[ch] = (sample * 255 + rounder) / maxval; | |
290 | } | |
291 | ++linep; | |
292 | } | |
293 | } | |
294 | i_plin(im, 0, width, y, line); | |
295 | } | |
296 | myfree(read_buf); | |
297 | myfree(line); | |
298 | ||
299 | return im; | |
300 | } | |
301 | ||
302 | static | |
303 | i_img * | |
304 | read_pgm_ppm_bin16(mbuf *mb, i_img *im, int width, int height, | |
d87dc9a4 | 305 | int channels, int maxval, int allow_incomplete) { |
9c106321 TC |
306 | i_fcolor *line, *linep; |
307 | int read_size; | |
308 | unsigned char *read_buf, *readp; | |
309 | int x, y, ch; | |
310 | double maxvalf = maxval; | |
311 | ||
312 | line = mymalloc(width * sizeof(i_fcolor)); | |
313 | read_size = channels * width * 2; | |
314 | read_buf = mymalloc(read_size); | |
315 | for(y=0;y<height;y++) { | |
316 | linep = line; | |
317 | readp = read_buf; | |
318 | if (gread(mb, read_buf, read_size) != read_size) { | |
319 | myfree(line); | |
320 | myfree(read_buf); | |
d87dc9a4 | 321 | if (allow_incomplete) { |
9c106321 TC |
322 | i_tags_setn(&im->tags, "i_incomplete", 1); |
323 | i_tags_setn(&im->tags, "i_lines_read", y); | |
324 | return im; | |
325 | } | |
326 | else { | |
327 | i_push_error(0, "short read - file truncated?"); | |
328 | i_img_destroy(im); | |
329 | return NULL; | |
330 | } | |
331 | } | |
332 | for(x=0; x<width; x++) { | |
333 | for(ch=0; ch<channels; ch++) { | |
334 | unsigned sample = (readp[0] << 8) + readp[1]; | |
335 | if (sample > maxval) | |
336 | sample = maxval; | |
337 | readp += 2; | |
338 | linep->channel[ch] = sample / maxvalf; | |
339 | } | |
340 | ++linep; | |
341 | } | |
342 | i_plinf(im, 0, width, y, line); | |
343 | } | |
344 | myfree(read_buf); | |
345 | myfree(line); | |
346 | ||
347 | return im; | |
348 | } | |
349 | ||
350 | static | |
351 | i_img * | |
d87dc9a4 | 352 | read_pbm_bin(mbuf *mb, i_img *im, int width, int height, int allow_incomplete) { |
9c106321 TC |
353 | i_palidx *line, *linep; |
354 | int read_size; | |
355 | unsigned char *read_buf, *readp; | |
356 | int x, y; | |
357 | unsigned mask; | |
358 | ||
359 | line = mymalloc(width * sizeof(i_palidx)); | |
360 | read_size = (width + 7) / 8; | |
361 | read_buf = mymalloc(read_size); | |
362 | for(y = 0; y < height; y++) { | |
363 | if (gread(mb, read_buf, read_size) != read_size) { | |
364 | myfree(line); | |
365 | myfree(read_buf); | |
d87dc9a4 | 366 | if (allow_incomplete) { |
9c106321 TC |
367 | i_tags_setn(&im->tags, "i_incomplete", 1); |
368 | i_tags_setn(&im->tags, "i_lines_read", y); | |
369 | return im; | |
370 | } | |
371 | else { | |
372 | i_push_error(0, "short read - file truncated?"); | |
373 | i_img_destroy(im); | |
374 | return NULL; | |
375 | } | |
376 | } | |
377 | linep = line; | |
378 | readp = read_buf; | |
379 | mask = 0x80; | |
380 | for(x = 0; x < width; ++x) { | |
381 | *linep++ = *readp & mask ? 1 : 0; | |
382 | mask >>= 1; | |
383 | if (mask == 0) { | |
384 | ++readp; | |
385 | mask = 0x80; | |
386 | } | |
387 | } | |
388 | i_ppal(im, 0, width, y, line); | |
389 | } | |
390 | myfree(read_buf); | |
391 | myfree(line); | |
392 | ||
393 | return im; | |
394 | } | |
395 | ||
396 | /* unlike pgm/ppm pbm: | |
397 | - doesn't require spaces between samples (bits) | |
398 | - 1 (maxval) is black instead of white | |
399 | */ | |
400 | static | |
401 | i_img * | |
d87dc9a4 | 402 | read_pbm_ascii(mbuf *mb, i_img *im, int width, int height, int allow_incomplete) { |
9c106321 TC |
403 | i_palidx *line, *linep; |
404 | int x, y; | |
405 | ||
406 | line = mymalloc(width * sizeof(i_palidx)); | |
407 | for(y = 0; y < height; y++) { | |
408 | linep = line; | |
409 | for(x = 0; x < width; ++x) { | |
410 | char *cp; | |
411 | skip_spaces(mb); | |
412 | if (!(cp = gnext(mb)) || (*cp != '0' && *cp != '1')) { | |
413 | myfree(line); | |
d87dc9a4 | 414 | if (allow_incomplete) { |
9c106321 TC |
415 | i_tags_setn(&im->tags, "i_incomplete", 1); |
416 | i_tags_setn(&im->tags, "i_lines_read", y); | |
417 | return im; | |
418 | } | |
419 | else { | |
420 | if (cp) | |
421 | i_push_error(0, "invalid data for ascii pnm"); | |
422 | else | |
423 | i_push_error(0, "short read - file truncated?"); | |
424 | i_img_destroy(im); | |
425 | return NULL; | |
426 | } | |
427 | } | |
428 | *linep++ = *cp == '0' ? 0 : 1; | |
429 | } | |
430 | i_ppal(im, 0, width, y, line); | |
431 | } | |
432 | myfree(line); | |
433 | ||
434 | return im; | |
435 | } | |
436 | ||
437 | static | |
438 | i_img * | |
439 | read_pgm_ppm_ascii(mbuf *mb, i_img *im, int width, int height, int channels, | |
d87dc9a4 | 440 | int maxval, int allow_incomplete) { |
9c106321 TC |
441 | i_color *line, *linep; |
442 | int x, y, ch; | |
443 | int rounder = maxval / 2; | |
444 | ||
445 | line = mymalloc(width * sizeof(i_color)); | |
446 | for(y=0;y<height;y++) { | |
447 | linep = line; | |
448 | for(x=0; x<width; x++) { | |
449 | for(ch=0; ch<channels; ch++) { | |
450 | int sample; | |
451 | ||
452 | if (!gnum(mb, &sample)) { | |
453 | myfree(line); | |
d87dc9a4 | 454 | if (allow_incomplete) { |
9c106321 TC |
455 | i_tags_setn(&im->tags, "i_incomplete", 1); |
456 | i_tags_setn(&im->tags, "i_lines_read", 1); | |
457 | return im; | |
458 | } | |
459 | else { | |
460 | if (gpeek(mb)) | |
461 | i_push_error(0, "invalid data for ascii pnm"); | |
462 | else | |
463 | i_push_error(0, "short read - file truncated?"); | |
464 | i_img_destroy(im); | |
465 | return NULL; | |
466 | } | |
467 | } | |
468 | if (sample > maxval) | |
469 | sample = maxval; | |
470 | linep->channel[ch] = (sample * 255 + rounder) / maxval; | |
471 | } | |
472 | ++linep; | |
473 | } | |
474 | i_plin(im, 0, width, y, line); | |
475 | } | |
476 | myfree(line); | |
477 | ||
478 | return im; | |
479 | } | |
480 | ||
481 | static | |
482 | i_img * | |
483 | read_pgm_ppm_ascii_16(mbuf *mb, i_img *im, int width, int height, | |
d87dc9a4 | 484 | int channels, int maxval, int allow_incomplete) { |
9c106321 TC |
485 | i_fcolor *line, *linep; |
486 | int x, y, ch; | |
487 | double maxvalf = maxval; | |
488 | ||
489 | line = mymalloc(width * sizeof(i_fcolor)); | |
490 | for(y=0;y<height;y++) { | |
491 | linep = line; | |
492 | for(x=0; x<width; x++) { | |
493 | for(ch=0; ch<channels; ch++) { | |
494 | int sample; | |
495 | ||
496 | if (!gnum(mb, &sample)) { | |
497 | myfree(line); | |
d87dc9a4 TC |
498 | if (allow_incomplete) { |
499 | i_tags_setn(&im->tags, "i_incomplete", 1); | |
500 | i_tags_setn(&im->tags, "i_lines_read", y); | |
501 | return im; | |
9c106321 TC |
502 | } |
503 | else { | |
504 | if (gpeek(mb)) | |
505 | i_push_error(0, "invalid data for ascii pnm"); | |
506 | else | |
507 | i_push_error(0, "short read - file truncated?"); | |
508 | i_img_destroy(im); | |
509 | return NULL; | |
510 | } | |
511 | } | |
512 | if (sample > maxval) | |
513 | sample = maxval; | |
514 | linep->channel[ch] = sample / maxvalf; | |
515 | } | |
516 | ++linep; | |
517 | } | |
518 | i_plinf(im, 0, width, y, line); | |
519 | } | |
520 | myfree(line); | |
521 | ||
522 | return im; | |
523 | } | |
02d1d628 AMH |
524 | |
525 | /* | |
d87dc9a4 | 526 | =item i_readpnm_wiol(ig, allow_incomplete) |
02d1d628 AMH |
527 | |
528 | Retrieve an image and stores in the iolayer object. Returns NULL on fatal error. | |
529 | ||
530 | ig - io_glue object | |
d87dc9a4 | 531 | allow_incomplete - allows a partial file to be read successfully |
02d1d628 AMH |
532 | |
533 | =cut | |
534 | */ | |
2086be61 | 535 | static i_img *i_readpnm_wiol_low( mbuf*, int); |
02d1d628 AMH |
536 | |
537 | i_img * | |
d87dc9a4 | 538 | i_readpnm_wiol(io_glue *ig, int allow_incomplete) { |
2086be61 TC |
539 | mbuf buf; |
540 | io_glue_commit_types(ig); | |
541 | init_buf(&buf, ig); | |
542 | ||
543 | return i_readpnm_wiol_low( &buf, allow_incomplete ); | |
544 | } | |
545 | ||
546 | static i_img * | |
547 | i_readpnm_wiol_low( mbuf *buf, int allow_incomplete) { | |
02d1d628 AMH |
548 | i_img* im; |
549 | int type; | |
8d14daab | 550 | int width, height, maxval, channels; |
8b695554 | 551 | int rounder; |
02d1d628 | 552 | char *cp; |
02d1d628 | 553 | |
fac8664e | 554 | i_clear_error(); |
2086be61 | 555 | mm_log((1,"i_readpnm(ig %p, allow_incomplete %d)\n", buf->ig, allow_incomplete)); |
02d1d628 | 556 | |
2086be61 | 557 | cp = gnext(buf); |
02d1d628 AMH |
558 | |
559 | if (!cp || *cp != 'P') { | |
fac8664e | 560 | i_push_error(0, "bad header magic, not a PNM file"); |
02d1d628 AMH |
561 | mm_log((1, "i_readpnm: Could not read header of file\n")); |
562 | return NULL; | |
563 | } | |
564 | ||
2086be61 | 565 | if ( !(cp = gnext(buf)) ) { |
02d1d628 AMH |
566 | mm_log((1, "i_readpnm: Could not read header of file\n")); |
567 | return NULL; | |
568 | } | |
569 | ||
570 | type = *cp-'0'; | |
571 | ||
572 | if (type < 1 || type > 6) { | |
fac8664e | 573 | i_push_error(0, "unknown PNM file type, not a PNM file"); |
02d1d628 AMH |
574 | mm_log((1, "i_readpnm: Not a pnm file\n")); |
575 | return NULL; | |
576 | } | |
577 | ||
2086be61 | 578 | if ( !(cp = gnext(buf)) ) { |
02d1d628 AMH |
579 | mm_log((1, "i_readpnm: Could not read header of file\n")); |
580 | return NULL; | |
581 | } | |
582 | ||
583 | if ( !misspace(*cp) ) { | |
fac8664e | 584 | i_push_error(0, "unexpected character, not a PNM file"); |
02d1d628 AMH |
585 | mm_log((1, "i_readpnm: Not a pnm file\n")); |
586 | return NULL; | |
587 | } | |
588 | ||
589 | mm_log((1, "i_readpnm: image is a %s\n", typenames[type-1] )); | |
590 | ||
591 | ||
592 | /* Read sizes and such */ | |
593 | ||
2086be61 | 594 | if (!skip_comment(buf)) { |
fac8664e | 595 | i_push_error(0, "while skipping to width"); |
02d1d628 AMH |
596 | mm_log((1, "i_readpnm: error reading before width\n")); |
597 | return NULL; | |
598 | } | |
599 | ||
2086be61 | 600 | if (!gnum(buf, &width)) { |
fac8664e | 601 | i_push_error(0, "could not read image width"); |
02d1d628 AMH |
602 | mm_log((1, "i_readpnm: error reading width\n")); |
603 | return NULL; | |
604 | } | |
605 | ||
2086be61 | 606 | if (!skip_comment(buf)) { |
fac8664e | 607 | i_push_error(0, "while skipping to height"); |
02d1d628 AMH |
608 | mm_log((1, "i_readpnm: error reading before height\n")); |
609 | return NULL; | |
610 | } | |
611 | ||
2086be61 | 612 | if (!gnum(buf, &height)) { |
fac8664e | 613 | i_push_error(0, "could not read image height"); |
02d1d628 AMH |
614 | mm_log((1, "i_readpnm: error reading height\n")); |
615 | return NULL; | |
616 | } | |
617 | ||
618 | if (!(type == 1 || type == 4)) { | |
2086be61 | 619 | if (!skip_comment(buf)) { |
fac8664e | 620 | i_push_error(0, "while skipping to maxval"); |
02d1d628 AMH |
621 | mm_log((1, "i_readpnm: error reading before maxval\n")); |
622 | return NULL; | |
623 | } | |
624 | ||
2086be61 | 625 | if (!gnum(buf, &maxval)) { |
fac8664e | 626 | i_push_error(0, "could not read maxval"); |
02d1d628 AMH |
627 | mm_log((1, "i_readpnm: error reading maxval\n")); |
628 | return NULL; | |
629 | } | |
8b695554 TC |
630 | |
631 | if (maxval == 0) { | |
632 | i_push_error(0, "maxval is zero - invalid pnm file"); | |
633 | mm_log((1, "i_readpnm: maxval is zero, invalid pnm file\n")); | |
634 | return NULL; | |
635 | } | |
636 | else if (maxval > 65535) { | |
637 | i_push_errorf(0, "maxval of %d is over 65535 - invalid pnm file", | |
638 | maxval); | |
8d14daab | 639 | mm_log((1, "i_readpnm: maxval of %d is over 65535 - invalid pnm file\n", maxval)); |
8b695554 TC |
640 | return NULL; |
641 | } | |
02d1d628 | 642 | } else maxval=1; |
8b695554 | 643 | rounder = maxval / 2; |
02d1d628 | 644 | |
2086be61 | 645 | if (!(cp = gnext(buf)) || !misspace(*cp)) { |
fac8664e | 646 | i_push_error(0, "garbage in header, invalid PNM file"); |
02d1d628 AMH |
647 | mm_log((1, "i_readpnm: garbage in header\n")); |
648 | return NULL; | |
649 | } | |
650 | ||
651 | channels = (type == 3 || type == 6) ? 3:1; | |
02d1d628 | 652 | |
77157728 TC |
653 | if (!i_int_check_image_file_limits(width, height, channels, sizeof(i_sample_t))) { |
654 | mm_log((1, "i_readpnm: image size exceeds limits\n")); | |
655 | return NULL; | |
656 | } | |
657 | ||
02d1d628 | 658 | mm_log((1, "i_readpnm: (%d x %d), channels = %d, maxval = %d\n", width, height, channels, maxval)); |
02d1d628 | 659 | |
9c106321 TC |
660 | if (type == 1 || type == 4) { |
661 | i_color pbm_pal[2]; | |
662 | pbm_pal[0].channel[0] = 255; | |
663 | pbm_pal[1].channel[0] = 0; | |
664 | ||
665 | im = i_img_pal_new(width, height, 1, 256); | |
666 | i_addcolors(im, pbm_pal, 2); | |
667 | } | |
668 | else { | |
669 | if (maxval > 255) | |
670 | im = i_img_16_new(width, height, channels); | |
671 | else | |
672 | im = i_img_8_new(width, height, channels); | |
673 | } | |
642a675b | 674 | |
02d1d628 AMH |
675 | switch (type) { |
676 | case 1: /* Ascii types */ | |
2086be61 | 677 | im = read_pbm_ascii(buf, im, width, height, allow_incomplete); |
9c106321 TC |
678 | break; |
679 | ||
02d1d628 AMH |
680 | case 2: |
681 | case 3: | |
9c106321 | 682 | if (maxval > 255) |
2086be61 | 683 | im = read_pgm_ppm_ascii_16(buf, im, width, height, channels, maxval, allow_incomplete); |
9c106321 | 684 | else |
2086be61 | 685 | im = read_pgm_ppm_ascii(buf, im, width, height, channels, maxval, allow_incomplete); |
02d1d628 AMH |
686 | break; |
687 | ||
688 | case 4: /* binary pbm */ | |
2086be61 | 689 | im = read_pbm_bin(buf, im, width, height, allow_incomplete); |
02d1d628 AMH |
690 | break; |
691 | ||
692 | case 5: /* binary pgm */ | |
693 | case 6: /* binary ppm */ | |
9c106321 | 694 | if (maxval > 255) |
2086be61 | 695 | im = read_pgm_ppm_bin16(buf, im, width, height, channels, maxval, allow_incomplete); |
9c106321 | 696 | else |
2086be61 | 697 | im = read_pgm_ppm_bin8(buf, im, width, height, channels, maxval, allow_incomplete); |
02d1d628 | 698 | break; |
9c106321 | 699 | |
02d1d628 AMH |
700 | default: |
701 | mm_log((1, "type %s [P%d] unsupported\n", typenames[type-1], type)); | |
702 | return NULL; | |
703 | } | |
9c106321 TC |
704 | |
705 | if (!im) | |
706 | return NULL; | |
707 | ||
708 | i_tags_add(&im->tags, "i_format", 0, "pnm", -1, 0); | |
709 | i_tags_setn(&im->tags, "pnm_maxval", maxval); | |
710 | i_tags_setn(&im->tags, "pnm_type", type); | |
711 | ||
02d1d628 AMH |
712 | return im; |
713 | } | |
714 | ||
2086be61 TC |
715 | static void free_images(i_img **imgs, int count) { |
716 | int i; | |
717 | ||
718 | if (count) { | |
719 | for (i = 0; i < count; ++i) | |
720 | i_img_destroy(imgs[i]); | |
721 | myfree(imgs); | |
722 | } | |
723 | } | |
724 | ||
725 | i_img **i_readpnm_multi_wiol(io_glue *ig, int *count, int allow_incomplete) { | |
726 | i_img **results = NULL; | |
727 | i_img *img = NULL; | |
728 | char *cp = NULL; | |
729 | mbuf buf; | |
730 | int result_alloc = 0, | |
731 | value = 0, | |
732 | eof = 0; | |
733 | *count=0; | |
734 | io_glue_commit_types(ig); | |
735 | init_buf(&buf, ig); | |
736 | do { | |
737 | mm_log((1, "read image %i\n", 1+*count)); | |
738 | img = i_readpnm_wiol_low( &buf, allow_incomplete ); | |
739 | if( !img ) { | |
740 | free_images( results, *count ); | |
741 | return NULL; | |
742 | } | |
743 | ++*count; | |
744 | if (*count > result_alloc) { | |
745 | if (result_alloc == 0) { | |
746 | result_alloc = 5; | |
747 | results = mymalloc(result_alloc * sizeof(i_img *)); | |
748 | } | |
749 | else { | |
750 | /* myrealloc never fails (it just dies if it can't allocate) */ | |
751 | result_alloc *= 2; | |
752 | results = myrealloc(results, result_alloc * sizeof(i_img *)); | |
753 | } | |
754 | } | |
755 | results[*count-1] = img; | |
756 | ||
757 | ||
758 | if( i_tags_get_int(&img->tags, "i_incomplete", 0, &value ) && value) { | |
759 | eof = 1; | |
760 | } | |
761 | else if( skip_spaces( &buf ) && ( cp=gpeek( &buf ) ) && *cp == 'P' ) { | |
762 | eof = 0; | |
763 | } | |
764 | else { | |
765 | eof = 1; | |
766 | } | |
767 | } while(!eof); | |
768 | return results; | |
769 | } | |
770 | ||
771 | ||
772 | ||
9c106321 TC |
773 | static |
774 | int | |
775 | write_pbm(i_img *im, io_glue *ig, int zero_is_white) { | |
776 | int x, y; | |
777 | i_palidx *line; | |
8d14daab | 778 | i_img_dim write_size; |
9c106321 TC |
779 | unsigned char *write_buf; |
780 | unsigned char *writep; | |
781 | char header[255]; | |
782 | unsigned mask; | |
783 | ||
8d14daab TC |
784 | sprintf(header, "P4\012# CREATOR: Imager\012%" i_DF " %" i_DF "\012", |
785 | i_DFc(im->xsize), i_DFc(im->ysize)); | |
9c106321 TC |
786 | if (i_io_write(ig, header, strlen(header)) < 0) { |
787 | i_push_error(0, "could not write pbm header"); | |
788 | return 0; | |
789 | } | |
790 | write_size = (im->xsize + 7) / 8; | |
791 | line = mymalloc(sizeof(i_palidx) * im->xsize); | |
792 | write_buf = mymalloc(write_size); | |
793 | for (y = 0; y < im->ysize; ++y) { | |
794 | i_gpal(im, 0, im->xsize, y, line); | |
795 | mask = 0x80; | |
796 | writep = write_buf; | |
797 | memset(write_buf, 0, write_size); | |
798 | for (x = 0; x < im->xsize; ++x) { | |
799 | if (zero_is_white ? line[x] : !line[x]) | |
800 | *writep |= mask; | |
801 | mask >>= 1; | |
802 | if (!mask) { | |
803 | ++writep; | |
804 | mask = 0x80; | |
805 | } | |
806 | } | |
807 | if (i_io_write(ig, write_buf, write_size) != write_size) { | |
808 | i_push_error(0, "write failure"); | |
809 | myfree(write_buf); | |
810 | myfree(line); | |
811 | return 0; | |
812 | } | |
813 | } | |
814 | myfree(write_buf); | |
815 | myfree(line); | |
816 | ||
817 | return 1; | |
818 | } | |
819 | ||
820 | static | |
821 | int | |
fa90de94 | 822 | write_ppm_data_8(i_img *im, io_glue *ig, int want_channels) { |
8d14daab TC |
823 | size_t write_size = im->xsize * want_channels; |
824 | size_t buf_size = im->xsize * im->channels; | |
2a31a4b4 | 825 | unsigned char *data = mymalloc(buf_size); |
8d14daab | 826 | i_img_dim y = 0; |
9c106321 | 827 | int rc = 1; |
fa90de94 | 828 | i_color bg; |
9c106321 | 829 | |
fa90de94 | 830 | i_get_file_background(im, &bg); |
9c106321 | 831 | while (y < im->ysize && rc >= 0) { |
2a31a4b4 | 832 | i_gsamp_bg(im, 0, im->xsize, y, data, want_channels, &bg); |
9c106321 TC |
833 | if (i_io_write(ig, data, write_size) != write_size) { |
834 | i_push_error(errno, "could not write ppm data"); | |
835 | rc = 0; | |
836 | break; | |
837 | } | |
838 | ++y; | |
839 | } | |
840 | myfree(data); | |
841 | ||
842 | return rc; | |
843 | } | |
844 | ||
845 | static | |
846 | int | |
fa90de94 | 847 | write_ppm_data_16(i_img *im, io_glue *ig, int want_channels) { |
8d14daab TC |
848 | size_t line_size = im->channels * im->xsize * sizeof(i_fsample_t); |
849 | size_t sample_count = want_channels * im->xsize; | |
850 | size_t write_size = sample_count * 2; | |
2a31a4b4 TC |
851 | i_fsample_t *line_buf = mymalloc(line_size); |
852 | i_fsample_t *samplep; | |
9c106321 | 853 | unsigned char *write_buf = mymalloc(write_size); |
2a31a4b4 | 854 | unsigned char *writep; |
8d14daab TC |
855 | size_t sample_num; |
856 | i_img_dim y = 0; | |
9c106321 | 857 | int rc = 1; |
fa90de94 TC |
858 | i_fcolor bg; |
859 | ||
860 | i_get_file_backgroundf(im, &bg); | |
9c106321 TC |
861 | |
862 | while (y < im->ysize) { | |
2a31a4b4 TC |
863 | i_gsampf_bg(im, 0, im->xsize, y, line_buf, want_channels, &bg); |
864 | samplep = line_buf; | |
865 | writep = write_buf; | |
866 | for (sample_num = 0; sample_num < sample_count; ++sample_num) { | |
867 | unsigned sample16 = SampleFTo16(*samplep++); | |
868 | *writep++ = sample16 >> 8; | |
869 | *writep++ = sample16 & 0xFF; | |
9c106321 TC |
870 | } |
871 | if (i_io_write(ig, write_buf, write_size) != write_size) { | |
872 | i_push_error(errno, "could not write ppm data"); | |
873 | rc = 0; | |
874 | break; | |
875 | } | |
876 | ++y; | |
877 | } | |
878 | myfree(line_buf); | |
879 | myfree(write_buf); | |
880 | ||
881 | return rc; | |
882 | } | |
02d1d628 | 883 | |
067d6bdc AMH |
884 | undef_int |
885 | i_writeppm_wiol(i_img *im, io_glue *ig) { | |
886 | char header[255]; | |
9c106321 TC |
887 | int zero_is_white; |
888 | int wide_data; | |
02d1d628 | 889 | |
067d6bdc AMH |
890 | mm_log((1,"i_writeppm(im %p, ig %p)\n", im, ig)); |
891 | i_clear_error(); | |
02d1d628 | 892 | |
067d6bdc AMH |
893 | /* Add code to get the filename info from the iolayer */ |
894 | /* Also add code to check for mmapped code */ | |
02d1d628 | 895 | |
067d6bdc | 896 | io_glue_commit_types(ig); |
02d1d628 | 897 | |
9c106321 TC |
898 | if (i_img_is_monochrome(im, &zero_is_white)) { |
899 | return write_pbm(im, ig, zero_is_white); | |
900 | } | |
901 | else { | |
902 | int type; | |
903 | int maxval; | |
fa90de94 TC |
904 | int want_channels = im->channels; |
905 | ||
906 | if (want_channels == 2 || want_channels == 4) | |
907 | --want_channels; | |
faa9b3e7 | 908 | |
9c106321 TC |
909 | if (!i_tags_get_int(&im->tags, "pnm_write_wide_data", 0, &wide_data)) |
910 | wide_data = 0; | |
911 | ||
fa90de94 | 912 | if (want_channels == 3) { |
9c106321 | 913 | type = 6; |
faa9b3e7 | 914 | } |
fa90de94 | 915 | else if (want_channels == 1) { |
9c106321 | 916 | type = 5; |
faa9b3e7 | 917 | } |
9c106321 TC |
918 | else { |
919 | i_push_error(0, "can only save 1 or 3 channel images to pnm"); | |
920 | mm_log((1,"i_writeppm: ppm/pgm is 1 or 3 channel only (current image is %d)\n",im->channels)); | |
067d6bdc AMH |
921 | return(0); |
922 | } | |
9c106321 TC |
923 | if (im->bits <= 8 || !wide_data) |
924 | maxval = 255; | |
925 | else | |
926 | maxval = 65535; | |
927 | ||
8d14daab TC |
928 | sprintf(header,"P%d\n#CREATOR: Imager\n%" i_DF " %" i_DF"\n%d\n", |
929 | type, i_DFc(im->xsize), i_DFc(im->ysize), maxval); | |
9c106321 TC |
930 | |
931 | if (ig->writecb(ig,header,strlen(header)) != strlen(header)) { | |
932 | i_push_error(errno, "could not write ppm header"); | |
933 | mm_log((1,"i_writeppm: unable to write ppm header.\n")); | |
067d6bdc AMH |
934 | return(0); |
935 | } | |
faa9b3e7 | 936 | |
fa90de94 TC |
937 | if (!im->virtual && im->bits == i_8_bits && im->type == i_direct_type |
938 | && im->channels == want_channels) { | |
9c106321 TC |
939 | if (ig->writecb(ig,im->idata,im->bytes) != im->bytes) { |
940 | i_push_error(errno, "could not write ppm data"); | |
faa9b3e7 TC |
941 | return 0; |
942 | } | |
943 | } | |
9c106321 | 944 | else if (maxval == 255) { |
fa90de94 | 945 | if (!write_ppm_data_8(im, ig, want_channels)) |
9c106321 TC |
946 | return 0; |
947 | } | |
948 | else { | |
fa90de94 | 949 | if (!write_ppm_data_16(im, ig, want_channels)) |
9c106321 | 950 | return 0; |
067d6bdc | 951 | } |
067d6bdc | 952 | } |
10461f9a | 953 | ig->closecb(ig); |
faa9b3e7 | 954 | |
067d6bdc AMH |
955 | return(1); |
956 | } | |
b8c2033e AMH |
957 | |
958 | /* | |
959 | =back | |
960 | ||
961 | =head1 AUTHOR | |
962 | ||
5b480b14 | 963 | Arnar M. Hrafnkelsson <addi@umich.edu>, Tony Cook <tonyc@cpan.org>, |
2086be61 | 964 | Philip Gwyn <gwyn@cpan.org>. |
b8c2033e AMH |
965 | |
966 | =head1 SEE ALSO | |
967 | ||
968 | Imager(3) | |
969 | ||
970 | =cut | |
971 | */ |