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