]> git.imager.perl.org - imager.git/blob - pnm.c
test image file limits are thread localized
[imager.git] / pnm.c
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
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 );
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
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
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
42 static char *typenames[]={"ascii pbm", "ascii pgm", "ascii ppm", "binary pbm", "binary pgm", "binary ppm"};
43
44 /*
45 =item skip_spaces(ig)
46
47 Advances in stream until it is positioned at a
48 non white space character. (internal)
49
50    ig - io_glue
51
52 =cut
53 */
54
55 static
56 int
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
66   return 1;
67 }
68
69
70 /*
71 =item skip_comment(ig)
72
73 Advances in stream over whitespace and a comment if one is found. (internal)
74
75    ig - io_glue object
76
77 =cut
78 */
79
80 static
81 int
82 skip_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
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
118 gnum(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_getc(ig);
137   }
138
139   return 1;
140 }
141
142 static
143 i_img *
144 read_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
200 static
201 i_img *
202 read_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
248 static 
249 i_img *
250 read_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 */
298 static 
299 i_img *
300 read_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
335 static
336 i_img *
337 read_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
379 static
380 i_img *
381 read_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
426 Retrieve 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
434 i_img *
435 i_readpnm_wiol( io_glue *ig, int allow_incomplete) {
436   i_img* im;
437   int type;
438   int width, height, maxval, channels;
439   int rounder;
440   int c;
441
442   i_clear_error();
443   mm_log((1,"i_readpnm(ig %p, allow_incomplete %d)\n", ig, allow_incomplete));
444
445   c = i_io_getc(ig);
446
447   if (c != 'P') {
448     i_push_error(0, "bad header magic, not a PNM file");
449     mm_log((1, "i_readpnm: Could not read header of file\n"));
450     return NULL;
451   }
452
453   if ((c = i_io_getc(ig)) == EOF ) {
454     mm_log((1, "i_readpnm: Could not read header of file\n"));
455     return NULL;
456   }
457   
458   type = c - '0';
459
460   if (type < 1 || type > 6) {
461     i_push_error(0, "unknown PNM file type, not a PNM file");
462     mm_log((1, "i_readpnm: Not a pnm file\n"));
463     return NULL;
464   }
465
466   if ( (c = i_io_getc(ig)) == EOF ) {
467     mm_log((1, "i_readpnm: Could not read header of file\n"));
468     return NULL;
469   }
470   
471   if ( !misspace(c) ) {
472     i_push_error(0, "unexpected character, not a PNM file");
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
482   if (!skip_comment(ig)) {
483     i_push_error(0, "while skipping to width");
484     mm_log((1, "i_readpnm: error reading before width\n"));
485     return NULL;
486   }
487   
488   if (!gnum(ig, &width)) {
489     i_push_error(0, "could not read image width");
490     mm_log((1, "i_readpnm: error reading width\n"));
491     return NULL;
492   }
493
494   if (!skip_comment(ig)) {
495     i_push_error(0, "while skipping to height");
496     mm_log((1, "i_readpnm: error reading before height\n"));
497     return NULL;
498   }
499
500   if (!gnum(ig, &height)) {
501     i_push_error(0, "could not read image height");
502     mm_log((1, "i_readpnm: error reading height\n"));
503     return NULL;
504   }
505   
506   if (!(type == 1 || type == 4)) {
507     if (!skip_comment(ig)) {
508       i_push_error(0, "while skipping to maxval");
509       mm_log((1, "i_readpnm: error reading before maxval\n"));
510       return NULL;
511     }
512
513     if (!gnum(ig, &maxval)) {
514       i_push_error(0, "could not read maxval");
515       mm_log((1, "i_readpnm: error reading maxval\n"));
516       return NULL;
517     }
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);
527       mm_log((1, "i_readpnm: maxval of %d is over 65535 - invalid pnm file\n", maxval));
528       return NULL;
529     }
530   } else maxval=1;
531   rounder = maxval / 2;
532
533   if ((c = i_io_getc(ig)) == EOF || !misspace(c)) {
534     i_push_error(0, "garbage in header, invalid PNM file");
535     mm_log((1, "i_readpnm: garbage in header\n"));
536     return NULL;
537   }
538
539   channels = (type == 3 || type == 6) ? 3:1;
540
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
546   mm_log((1, "i_readpnm: (%d x %d), channels = %d, maxval = %d\n", width, height, channels, maxval));
547
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   }
562
563   switch (type) {
564   case 1: /* Ascii types */
565     im = read_pbm_ascii(ig, im, width, height, allow_incomplete);
566     break;
567
568   case 2:
569   case 3:
570     if (maxval > 255)
571       im = read_pgm_ppm_ascii_16(ig, im, width, height, channels, maxval, allow_incomplete);
572     else
573       im = read_pgm_ppm_ascii(ig, im, width, height, channels, maxval, allow_incomplete);
574     break;
575     
576   case 4: /* binary pbm */
577     im = read_pbm_bin(ig, im, width, height, allow_incomplete);
578     break;
579
580   case 5: /* binary pgm */
581   case 6: /* binary ppm */
582     if (maxval > 255)
583       im = read_pgm_ppm_bin16(ig, im, width, height, channels, maxval, allow_incomplete);
584     else
585       im = read_pgm_ppm_bin8(ig, im, width, height, channels, maxval, allow_incomplete);
586     break;
587
588   default:
589     mm_log((1, "type %s [P%d] unsupported\n", typenames[type-1], type));
590     return NULL;
591   }
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
600   return im;
601 }
602
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;
616     char c = EOF;
617     int result_alloc = 0, 
618         value = 0, 
619         eof = 0;
620     *count=0;
621
622     do {
623         mm_log((1, "read image %i\n", 1+*count));
624         img = i_readpnm_wiol( ig, allow_incomplete );
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         }
647         else if( skip_spaces( ig ) && ( c=i_io_peekc( ig ) ) != EOF && c == 'P' ) {
648             eof = 0;
649         }
650         else {
651             eof = 1;
652         }
653     } while(!eof);
654     return results;
655 }
656
657
658
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;
664   i_img_dim write_size;
665   unsigned char *write_buf;
666   unsigned char *writep;
667   char header[255];
668   unsigned mask;
669
670   sprintf(header, "P4\012# CREATOR: Imager\012%" i_DF " %" i_DF "\012", 
671           i_DFc(im->xsize), i_DFc(im->ysize));
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
708 write_ppm_data_8(i_img *im, io_glue *ig, int want_channels) {
709   size_t write_size = im->xsize * want_channels;
710   size_t buf_size = im->xsize * im->channels;
711   unsigned char *data = mymalloc(buf_size);
712   i_img_dim y = 0;
713   int rc = 1;
714   i_color bg;
715
716   i_get_file_background(im, &bg);
717   while (y < im->ysize && rc >= 0) {
718     i_gsamp_bg(im, 0, im->xsize, y, data, want_channels, &bg);
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
733 write_ppm_data_16(i_img *im, io_glue *ig, int want_channels) {
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;
737   i_fsample_t *line_buf = mymalloc(line_size);
738   i_fsample_t *samplep;
739   unsigned char *write_buf = mymalloc(write_size);
740   unsigned char *writep;
741   size_t sample_num;
742   i_img_dim y = 0;
743   int rc = 1;
744   i_fcolor bg;
745
746   i_get_file_backgroundf(im, &bg);
747
748   while (y < im->ysize) {
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;
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 }
769
770 undef_int
771 i_writeppm_wiol(i_img *im, io_glue *ig) {
772   char header[255];
773   int zero_is_white;
774   int wide_data;
775
776   mm_log((1,"i_writeppm(im %p, ig %p)\n", im, ig));
777   i_clear_error();
778
779   /* Add code to get the filename info from the iolayer */
780   /* Also add code to check for mmapped code */
781
782   if (i_img_is_monochrome(im, &zero_is_white)) {
783     if (!write_pbm(im, ig, zero_is_white))
784       return 0;
785   }
786   else {
787     int type;
788     int maxval;
789     int want_channels = im->channels;
790
791     if (want_channels == 2 || want_channels == 4)
792       --want_channels;
793
794     if (!i_tags_get_int(&im->tags, "pnm_write_wide_data", 0, &wide_data))
795       wide_data = 0;
796
797     if (want_channels == 3) {
798       type = 6;
799     }
800     else if (want_channels == 1) {
801       type = 5;
802     }
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));
806       return(0);
807     }
808     if (im->bits <= 8 || !wide_data)
809       maxval = 255;
810     else
811       maxval = 65535;
812
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);
815
816     if (i_io_write(ig,header,strlen(header)) != strlen(header)) {
817       i_push_error(errno, "could not write ppm header");
818       mm_log((1,"i_writeppm: unable to write ppm header.\n"));
819       return(0);
820     }
821
822     if (!im->virtual && im->bits == i_8_bits && im->type == i_direct_type
823         && im->channels == want_channels) {
824       if (i_io_write(ig,im->idata,im->bytes) != im->bytes) {
825         i_push_error(errno, "could not write ppm data");
826         return 0;
827       }
828     }
829     else if (maxval == 255) {
830       if (!write_ppm_data_8(im, ig, want_channels))
831         return 0;
832     }
833     else {
834       if (!write_ppm_data_16(im, ig, want_channels))
835         return 0;
836     }
837   }
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   }
842
843   return(1);
844 }
845
846 /*
847 =back
848
849 =head1 AUTHOR
850
851 Arnar M. Hrafnkelsson <addi@umich.edu>, Tony Cook <tonyc@cpan.org>,
852 Philip Gwyn <gwyn@cpan.org>.
853
854 =head1 SEE ALSO
855
856 Imager(3)
857
858 =cut
859 */