Leolo's guassian2 patch
[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_nextc(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 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
601 static 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
611 i_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
657 static
658 int
659 write_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
704 static
705 int
706 write_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
729 static
730 int
731 write_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
768 undef_int
769 i_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
849 Arnar M. Hrafnkelsson <addi@umich.edu>, Tony Cook <tonyc@cpan.org>,
850 Philip Gwyn <gwyn@cpan.org>.
851
852 =head1 SEE ALSO
853
854 Imager(3)
855
856 =cut
857 */