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