]> git.imager.perl.org - imager.git/blame_incremental - pnm.c
RT#128481: fix handling of GIF files with no images
[imager.git] / pnm.c
... / ...
CommitLineData
1#include "imager.h"
2#include "log.h"
3#include "iolayer.h"
4#include "imageri.h"
5
6#include <stdlib.h>
7#include <errno.h>
8
9
10/*
11=head1 NAME
12
13pnm.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 );
18 i_img *im = i_readpnm_wiol(ig, 0); // no limit on how much is read
19 // or
20 io_glue *ig = io_new_fd( fd );
21 return_code = i_writepnm_wiol(im, ig);
22
23=head1 DESCRIPTION
24
25pnm.c implements the basic functions to read and write portable
26anymap files. It uses the iolayer and needs either a seekable source
27or an entire memory mapped buffer.
28
29=head1 FUNCTION REFERENCE
30
31Some of these functions are internal.
32
33=over
34
35=cut
36*/
37
38
39#define misspace(x) (x==' ' || x=='\n' || x=='\r' || x=='\t' || x=='\f' || x=='\v')
40#define misnumber(x) (x <= '9' && x>='0')
41
42static char *typenames[]={"ascii pbm", "ascii pgm", "ascii ppm", "binary pbm", "binary pgm", "binary ppm"};
43
44/*
45=item skip_spaces(ig)
46
47Advances in stream until it is positioned at a
48non white space character. (internal)
49
50 ig - io_glue
51
52=cut
53*/
54
55static
56int
57skip_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
66 return 1;
67}
68
69
70/*
71=item skip_comment(ig)
72
73Advances in stream over whitespace and a comment if one is found. (internal)
74
75 ig - io_glue object
76
77=cut
78*/
79
80static
81int
82skip_comment(io_glue *ig) {
83 int c;
84
85 if (!skip_spaces(ig))
86 return 0;
87
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;
95 }
96 }
97 if (c == EOF)
98 return 0;
99
100 return 1;
101}
102
103
104/*
105=item gnum(mb, i)
106
107Fetches the next number from stream and stores in i, returns true
108on success else false.
109
110 mb - buffer object
111 i - integer to store result in
112
113=cut
114*/
115
116static
117int
118gnum(io_glue *ig, int *i) {
119 int c;
120 *i = 0;
121
122 if (!skip_spaces(ig)) return 0;
123
124 if ((c = i_io_peekc(ig)) == EOF)
125 return 0;
126 if (!misnumber(c))
127 return 0;
128 while( (c = i_io_peekc(ig)) != EOF && misnumber(c) ) {
129 int work = *i * 10 + (c - '0');
130 if (work < *i) {
131 /* overflow */
132 i_push_error(0, "integer overflow");
133 return 0;
134 }
135 *i = work;
136 i_io_nextc(ig);
137 }
138
139 return 1;
140}
141
142static
143i_img *
144read_pgm_ppm_bin8(io_glue *ig, i_img *im, int width, int height,
145 int channels, int maxval, int allow_incomplete) {
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;
158 if (i_io_read(ig, read_buf, read_size) != read_size) {
159 myfree(line);
160 myfree(read_buf);
161 if (allow_incomplete) {
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
200static
201i_img *
202read_pgm_ppm_bin16(io_glue *ig, i_img *im, int width, int height,
203 int channels, int maxval, int allow_incomplete) {
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;
216 if (i_io_read(ig, read_buf, read_size) != read_size) {
217 myfree(line);
218 myfree(read_buf);
219 if (allow_incomplete) {
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
248static
249i_img *
250read_pbm_bin(io_glue *ig, i_img *im, int width, int height, int allow_incomplete) {
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++) {
261 if (i_io_read(ig, read_buf, read_size) != read_size) {
262 myfree(line);
263 myfree(read_buf);
264 if (allow_incomplete) {
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*/
298static
299i_img *
300read_pbm_ascii(io_glue *ig, i_img *im, int width, int height, int allow_incomplete) {
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) {
308 int c;
309 skip_spaces(ig);
310 if ((c = i_io_getc(ig)) == EOF || (c != '0' && c != '1')) {
311 myfree(line);
312 if (allow_incomplete) {
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 {
318 if (c != EOF)
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 }
326 *linep++ = c == '0' ? 0 : 1;
327 }
328 i_ppal(im, 0, width, y, line);
329 }
330 myfree(line);
331
332 return im;
333}
334
335static
336i_img *
337read_pgm_ppm_ascii(io_glue *ig, i_img *im, int width, int height, int channels,
338 int maxval, int allow_incomplete) {
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
350 if (!gnum(ig, &sample)) {
351 myfree(line);
352 if (allow_incomplete) {
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 {
358 if (i_io_peekc(ig) != EOF)
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
379static
380i_img *
381read_pgm_ppm_ascii_16(io_glue *ig, i_img *im, int width, int height,
382 int channels, int maxval, int allow_incomplete) {
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
394 if (!gnum(ig, &sample)) {
395 myfree(line);
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;
400 }
401 else {
402 if (i_io_peekc(ig) != EOF)
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}
422
423/*
424=item i_readpnm_wiol(ig, allow_incomplete)
425
426Retrieve an image and stores in the iolayer object. Returns NULL on fatal error.
427
428 ig - io_glue object
429 allow_incomplete - allows a partial file to be read successfully
430
431=cut
432*/
433
434i_img *
435i_readpnm_wiol( io_glue *ig, int allow_incomplete) {
436 i_img* im;
437 int type;
438 int width, height, maxval, channels;
439 int c;
440
441 i_clear_error();
442 mm_log((1,"i_readpnm(ig %p, allow_incomplete %d)\n", ig, allow_incomplete));
443
444 c = i_io_getc(ig);
445
446 if (c != 'P') {
447 i_push_error(0, "bad header magic, not a PNM file");
448 mm_log((1, "i_readpnm: Could not read header of file\n"));
449 return NULL;
450 }
451
452 if ((c = i_io_getc(ig)) == EOF ) {
453 mm_log((1, "i_readpnm: Could not read header of file\n"));
454 return NULL;
455 }
456
457 type = c - '0';
458
459 if (type < 1 || type > 6) {
460 i_push_error(0, "unknown PNM file type, not a PNM file");
461 mm_log((1, "i_readpnm: Not a pnm file\n"));
462 return NULL;
463 }
464
465 if ( (c = i_io_getc(ig)) == EOF ) {
466 mm_log((1, "i_readpnm: Could not read header of file\n"));
467 return NULL;
468 }
469
470 if ( !misspace(c) ) {
471 i_push_error(0, "unexpected character, not a PNM file");
472 mm_log((1, "i_readpnm: Not a pnm file\n"));
473 return NULL;
474 }
475
476 mm_log((1, "i_readpnm: image is a %s\n", typenames[type-1] ));
477
478
479 /* Read sizes and such */
480
481 if (!skip_comment(ig)) {
482 i_push_error(0, "while skipping to width");
483 mm_log((1, "i_readpnm: error reading before width\n"));
484 return NULL;
485 }
486
487 if (!gnum(ig, &width)) {
488 i_push_error(0, "could not read image width");
489 mm_log((1, "i_readpnm: error reading width\n"));
490 return NULL;
491 }
492
493 if (!skip_comment(ig)) {
494 i_push_error(0, "while skipping to height");
495 mm_log((1, "i_readpnm: error reading before height\n"));
496 return NULL;
497 }
498
499 if (!gnum(ig, &height)) {
500 i_push_error(0, "could not read image height");
501 mm_log((1, "i_readpnm: error reading height\n"));
502 return NULL;
503 }
504
505 if (!(type == 1 || type == 4)) {
506 if (!skip_comment(ig)) {
507 i_push_error(0, "while skipping to maxval");
508 mm_log((1, "i_readpnm: error reading before maxval\n"));
509 return NULL;
510 }
511
512 if (!gnum(ig, &maxval)) {
513 i_push_error(0, "could not read maxval");
514 mm_log((1, "i_readpnm: error reading maxval\n"));
515 return NULL;
516 }
517
518 if (maxval == 0) {
519 i_push_error(0, "maxval is zero - invalid pnm file");
520 mm_log((1, "i_readpnm: maxval is zero, invalid pnm file\n"));
521 return NULL;
522 }
523 else if (maxval > 65535) {
524 i_push_errorf(0, "maxval of %d is over 65535 - invalid pnm file",
525 maxval);
526 mm_log((1, "i_readpnm: maxval of %d is over 65535 - invalid pnm file\n", maxval));
527 return NULL;
528 }
529 } else maxval=1;
530
531 if ((c = i_io_getc(ig)) == EOF || !misspace(c)) {
532 i_push_error(0, "garbage in header, invalid PNM file");
533 mm_log((1, "i_readpnm: garbage in header\n"));
534 return NULL;
535 }
536
537 channels = (type == 3 || type == 6) ? 3:1;
538
539 if (!i_int_check_image_file_limits(width, height, channels, sizeof(i_sample_t))) {
540 mm_log((1, "i_readpnm: image size exceeds limits\n"));
541 return NULL;
542 }
543
544 mm_log((1, "i_readpnm: (%d x %d), channels = %d, maxval = %d\n", width, height, channels, maxval));
545
546 if (type == 1 || type == 4) {
547 i_color pbm_pal[2];
548 pbm_pal[0].channel[0] = 255;
549 pbm_pal[1].channel[0] = 0;
550
551 im = i_img_pal_new(width, height, 1, 256);
552 i_addcolors(im, pbm_pal, 2);
553 }
554 else {
555 if (maxval > 255)
556 im = i_img_16_new(width, height, channels);
557 else
558 im = i_img_8_new(width, height, channels);
559 }
560
561 switch (type) {
562 case 1: /* Ascii types */
563 im = read_pbm_ascii(ig, im, width, height, allow_incomplete);
564 break;
565
566 case 2:
567 case 3:
568 if (maxval > 255)
569 im = read_pgm_ppm_ascii_16(ig, im, width, height, channels, maxval, allow_incomplete);
570 else
571 im = read_pgm_ppm_ascii(ig, im, width, height, channels, maxval, allow_incomplete);
572 break;
573
574 case 4: /* binary pbm */
575 im = read_pbm_bin(ig, im, width, height, allow_incomplete);
576 break;
577
578 case 5: /* binary pgm */
579 case 6: /* binary ppm */
580 if (maxval > 255)
581 im = read_pgm_ppm_bin16(ig, im, width, height, channels, maxval, allow_incomplete);
582 else
583 im = read_pgm_ppm_bin8(ig, im, width, height, channels, maxval, allow_incomplete);
584 break;
585
586 default:
587 mm_log((1, "type %s [P%d] unsupported\n", typenames[type-1], type));
588 return NULL;
589 }
590
591 if (!im)
592 return NULL;
593
594 i_tags_add(&im->tags, "i_format", 0, "pnm", -1, 0);
595 i_tags_setn(&im->tags, "pnm_maxval", maxval);
596 i_tags_setn(&im->tags, "pnm_type", type);
597
598 return im;
599}
600
601static void free_images(i_img **imgs, int count) {
602 int i;
603
604 if (count) {
605 for (i = 0; i < count; ++i)
606 i_img_destroy(imgs[i]);
607 myfree(imgs);
608 }
609}
610
611i_img **i_readpnm_multi_wiol(io_glue *ig, int *count, int allow_incomplete) {
612 i_img **results = NULL;
613 i_img *img = NULL;
614 char c = EOF;
615 int result_alloc = 0,
616 value = 0,
617 eof = 0;
618 *count=0;
619
620 do {
621 mm_log((1, "read image %i\n", 1+*count));
622 img = i_readpnm_wiol( ig, allow_incomplete );
623 if( !img ) {
624 free_images( results, *count );
625 return NULL;
626 }
627 ++*count;
628 if (*count > result_alloc) {
629 if (result_alloc == 0) {
630 result_alloc = 5;
631 results = mymalloc(result_alloc * sizeof(i_img *));
632 }
633 else {
634 /* myrealloc never fails (it just dies if it can't allocate) */
635 result_alloc *= 2;
636 results = myrealloc(results, result_alloc * sizeof(i_img *));
637 }
638 }
639 results[*count-1] = img;
640
641
642 if( i_tags_get_int(&img->tags, "i_incomplete", 0, &value ) && value) {
643 eof = 1;
644 }
645 else if( skip_spaces( ig ) && ( c=i_io_peekc( ig ) ) != EOF && c == 'P' ) {
646 eof = 0;
647 }
648 else {
649 eof = 1;
650 }
651 } while(!eof);
652 return results;
653}
654
655
656
657static
658int
659write_pbm(i_img *im, io_glue *ig, int zero_is_white) {
660 int x, y;
661 i_palidx *line;
662 i_img_dim write_size;
663 unsigned char *write_buf;
664 unsigned char *writep;
665 char header[255];
666 unsigned mask;
667
668 sprintf(header, "P4\012# CREATOR: Imager\012%" i_DF " %" i_DF "\012",
669 i_DFc(im->xsize), i_DFc(im->ysize));
670 if (i_io_write(ig, header, strlen(header)) < 0) {
671 i_push_error(0, "could not write pbm header");
672 return 0;
673 }
674 write_size = (im->xsize + 7) / 8;
675 line = mymalloc(sizeof(i_palidx) * im->xsize);
676 write_buf = mymalloc(write_size);
677 for (y = 0; y < im->ysize; ++y) {
678 i_gpal(im, 0, im->xsize, y, line);
679 mask = 0x80;
680 writep = write_buf;
681 memset(write_buf, 0, write_size);
682 for (x = 0; x < im->xsize; ++x) {
683 if (zero_is_white ? line[x] : !line[x])
684 *writep |= mask;
685 mask >>= 1;
686 if (!mask) {
687 ++writep;
688 mask = 0x80;
689 }
690 }
691 if (i_io_write(ig, write_buf, write_size) != write_size) {
692 i_push_error(0, "write failure");
693 myfree(write_buf);
694 myfree(line);
695 return 0;
696 }
697 }
698 myfree(write_buf);
699 myfree(line);
700
701 return 1;
702}
703
704static
705int
706write_ppm_data_8(i_img *im, io_glue *ig, int want_channels) {
707 size_t write_size = im->xsize * want_channels;
708 size_t buf_size = im->xsize * im->channels;
709 unsigned char *data = mymalloc(buf_size);
710 i_img_dim y = 0;
711 int rc = 1;
712 i_color bg;
713
714 i_get_file_background(im, &bg);
715 while (y < im->ysize && rc >= 0) {
716 i_gsamp_bg(im, 0, im->xsize, y, data, want_channels, &bg);
717 if (i_io_write(ig, data, write_size) != write_size) {
718 i_push_error(errno, "could not write ppm data");
719 rc = 0;
720 break;
721 }
722 ++y;
723 }
724 myfree(data);
725
726 return rc;
727}
728
729static
730int
731write_ppm_data_16(i_img *im, io_glue *ig, int want_channels) {
732 size_t line_size = im->channels * im->xsize * sizeof(i_fsample_t);
733 size_t sample_count = want_channels * im->xsize;
734 size_t write_size = sample_count * 2;
735 i_fsample_t *line_buf = mymalloc(line_size);
736 i_fsample_t *samplep;
737 unsigned char *write_buf = mymalloc(write_size);
738 unsigned char *writep;
739 size_t sample_num;
740 i_img_dim y = 0;
741 int rc = 1;
742 i_fcolor bg;
743
744 i_get_file_backgroundf(im, &bg);
745
746 while (y < im->ysize) {
747 i_gsampf_bg(im, 0, im->xsize, y, line_buf, want_channels, &bg);
748 samplep = line_buf;
749 writep = write_buf;
750 for (sample_num = 0; sample_num < sample_count; ++sample_num) {
751 unsigned sample16 = SampleFTo16(*samplep++);
752 *writep++ = sample16 >> 8;
753 *writep++ = sample16 & 0xFF;
754 }
755 if (i_io_write(ig, write_buf, write_size) != write_size) {
756 i_push_error(errno, "could not write ppm data");
757 rc = 0;
758 break;
759 }
760 ++y;
761 }
762 myfree(line_buf);
763 myfree(write_buf);
764
765 return rc;
766}
767
768undef_int
769i_writeppm_wiol(i_img *im, io_glue *ig) {
770 char header[255];
771 int zero_is_white;
772 int wide_data;
773
774 mm_log((1,"i_writeppm(im %p, ig %p)\n", im, ig));
775 i_clear_error();
776
777 /* Add code to get the filename info from the iolayer */
778 /* Also add code to check for mmapped code */
779
780 if (i_img_is_monochrome(im, &zero_is_white)) {
781 if (!write_pbm(im, ig, zero_is_white))
782 return 0;
783 }
784 else {
785 int type;
786 int maxval;
787 int want_channels = im->channels;
788
789 if (want_channels == 2 || want_channels == 4)
790 --want_channels;
791
792 if (!i_tags_get_int(&im->tags, "pnm_write_wide_data", 0, &wide_data))
793 wide_data = 0;
794
795 if (want_channels == 3) {
796 type = 6;
797 }
798 else if (want_channels == 1) {
799 type = 5;
800 }
801 else {
802 i_push_error(0, "can only save 1 or 3 channel images to pnm");
803 mm_log((1,"i_writeppm: ppm/pgm is 1 or 3 channel only (current image is %d)\n",im->channels));
804 return(0);
805 }
806 if (im->bits <= 8 || !wide_data)
807 maxval = 255;
808 else
809 maxval = 65535;
810
811 sprintf(header,"P%d\n#CREATOR: Imager\n%" i_DF " %" i_DF"\n%d\n",
812 type, i_DFc(im->xsize), i_DFc(im->ysize), maxval);
813
814 if (i_io_write(ig,header,strlen(header)) != strlen(header)) {
815 i_push_error(errno, "could not write ppm header");
816 mm_log((1,"i_writeppm: unable to write ppm header.\n"));
817 return(0);
818 }
819
820 if (!im->virtual && im->bits == i_8_bits && im->type == i_direct_type
821 && im->channels == want_channels) {
822 if (i_io_write(ig,im->idata,im->bytes) != im->bytes) {
823 i_push_error(errno, "could not write ppm data");
824 return 0;
825 }
826 }
827 else if (maxval == 255) {
828 if (!write_ppm_data_8(im, ig, want_channels))
829 return 0;
830 }
831 else {
832 if (!write_ppm_data_16(im, ig, want_channels))
833 return 0;
834 }
835 }
836 if (i_io_close(ig)) {
837 i_push_errorf(i_io_error(ig), "Error closing stream: %d", i_io_error(ig));
838 return 0;
839 }
840
841 return(1);
842}
843
844/*
845=back
846
847=head1 AUTHOR
848
849Arnar M. Hrafnkelsson <addi@umich.edu>, Tony Cook <tonyc@cpan.org>,
850Philip Gwyn <gwyn@cpan.org>.
851
852=head1 SEE ALSO
853
854Imager(3)
855
856=cut
857*/