switch to using size_t and i_img_dim strictly
[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     int work = *i*10+(*cp-'0');
233     if (work < *i) {
234       /* overflow */
235       i_push_error(0, "integer overflow");
236       return 0;
237     }
238     *i = work;
239     cp = gnext(mb);
240   }
241   return 1;
242 }
243
244 static
245 i_img *
246 read_pgm_ppm_bin8(mbuf *mb, i_img *im, int width, int height, 
247                   int channels, int maxval, int allow_incomplete) {
248   i_color *line, *linep;
249   int read_size;
250   unsigned char *read_buf, *readp;
251   int x, y, ch;
252   int rounder = maxval / 2;
253
254   line = mymalloc(width * sizeof(i_color));
255   read_size = channels * width;
256   read_buf = mymalloc(read_size);
257   for(y=0;y<height;y++) {
258     linep = line;
259     readp = read_buf;
260     if (gread(mb, read_buf, read_size) != read_size) {
261       myfree(line);
262       myfree(read_buf);
263       if (allow_incomplete) {
264         i_tags_setn(&im->tags, "i_incomplete", 1);
265         i_tags_setn(&im->tags, "i_lines_read", y);
266         return im;
267       }
268       else {
269         i_push_error(0, "short read - file truncated?");
270         i_img_destroy(im);
271         return NULL;
272       }
273     }
274     if (maxval == 255) {
275       for(x=0; x<width; x++) {
276         for(ch=0; ch<channels; ch++) {
277           linep->channel[ch] = *readp++;
278         }
279         ++linep;
280       }
281     }
282     else {
283       for(x=0; x<width; x++) {
284         for(ch=0; ch<channels; ch++) {
285           /* we just clamp samples to the correct range */
286           unsigned sample = *readp++;
287           if (sample > maxval)
288             sample = maxval;
289           linep->channel[ch] = (sample * 255 + rounder) / maxval;
290         }
291         ++linep;
292       }
293     }
294     i_plin(im, 0, width, y, line);
295   }
296   myfree(read_buf);
297   myfree(line);
298
299   return im;
300 }
301
302 static
303 i_img *
304 read_pgm_ppm_bin16(mbuf *mb, i_img *im, int width, int height, 
305                   int channels, int maxval, int allow_incomplete) {
306   i_fcolor *line, *linep;
307   int read_size;
308   unsigned char *read_buf, *readp;
309   int x, y, ch;
310   double maxvalf = maxval;
311
312   line = mymalloc(width * sizeof(i_fcolor));
313   read_size = channels * width * 2;
314   read_buf = mymalloc(read_size);
315   for(y=0;y<height;y++) {
316     linep = line;
317     readp = read_buf;
318     if (gread(mb, read_buf, read_size) != read_size) {
319       myfree(line);
320       myfree(read_buf);
321       if (allow_incomplete) {
322         i_tags_setn(&im->tags, "i_incomplete", 1);
323         i_tags_setn(&im->tags, "i_lines_read", y);
324         return im;
325       }
326       else {
327         i_push_error(0, "short read - file truncated?");
328         i_img_destroy(im);
329         return NULL;
330       }
331     }
332     for(x=0; x<width; x++) {
333       for(ch=0; ch<channels; ch++) {
334         unsigned sample = (readp[0] << 8) + readp[1];
335         if (sample > maxval)
336           sample = maxval;
337         readp += 2;
338         linep->channel[ch] = sample / maxvalf;
339       }
340       ++linep;
341     }
342     i_plinf(im, 0, width, y, line);
343   }
344   myfree(read_buf);
345   myfree(line);
346
347   return im;
348 }
349
350 static 
351 i_img *
352 read_pbm_bin(mbuf *mb, i_img *im, int width, int height, int allow_incomplete) {
353   i_palidx *line, *linep;
354   int read_size;
355   unsigned char *read_buf, *readp;
356   int x, y;
357   unsigned mask;
358
359   line = mymalloc(width * sizeof(i_palidx));
360   read_size = (width + 7) / 8;
361   read_buf = mymalloc(read_size);
362   for(y = 0; y < height; y++) {
363     if (gread(mb, read_buf, read_size) != read_size) {
364       myfree(line);
365       myfree(read_buf);
366       if (allow_incomplete) {
367         i_tags_setn(&im->tags, "i_incomplete", 1);
368         i_tags_setn(&im->tags, "i_lines_read", y);
369         return im;
370       }
371       else {
372         i_push_error(0, "short read - file truncated?");
373         i_img_destroy(im);
374         return NULL;
375       }
376     }
377     linep = line;
378     readp = read_buf;
379     mask = 0x80;
380     for(x = 0; x < width; ++x) {
381       *linep++ = *readp & mask ? 1 : 0;
382       mask >>= 1;
383       if (mask == 0) {
384         ++readp;
385         mask = 0x80;
386       }
387     }
388     i_ppal(im, 0, width, y, line);
389   }
390   myfree(read_buf);
391   myfree(line);
392
393   return im;
394 }
395
396 /* unlike pgm/ppm pbm:
397   - doesn't require spaces between samples (bits)
398   - 1 (maxval) is black instead of white
399 */
400 static 
401 i_img *
402 read_pbm_ascii(mbuf *mb, i_img *im, int width, int height, int allow_incomplete) {
403   i_palidx *line, *linep;
404   int x, y;
405
406   line = mymalloc(width * sizeof(i_palidx));
407   for(y = 0; y < height; y++) {
408     linep = line;
409     for(x = 0; x < width; ++x) {
410       char *cp;
411       skip_spaces(mb);
412       if (!(cp = gnext(mb)) || (*cp != '0' && *cp != '1')) {
413         myfree(line);
414         if (allow_incomplete) {
415           i_tags_setn(&im->tags, "i_incomplete", 1);
416           i_tags_setn(&im->tags, "i_lines_read", y);
417           return im;
418         }
419         else {
420           if (cp)
421             i_push_error(0, "invalid data for ascii pnm");
422           else
423             i_push_error(0, "short read - file truncated?");
424           i_img_destroy(im);
425           return NULL;
426         }
427       }
428       *linep++ = *cp == '0' ? 0 : 1;
429     }
430     i_ppal(im, 0, width, y, line);
431   }
432   myfree(line);
433
434   return im;
435 }
436
437 static
438 i_img *
439 read_pgm_ppm_ascii(mbuf *mb, i_img *im, int width, int height, int channels, 
440                    int maxval, int allow_incomplete) {
441   i_color *line, *linep;
442   int x, y, ch;
443   int rounder = maxval / 2;
444
445   line = mymalloc(width * sizeof(i_color));
446   for(y=0;y<height;y++) {
447     linep = line;
448     for(x=0; x<width; x++) {
449       for(ch=0; ch<channels; ch++) {
450         int sample;
451         
452         if (!gnum(mb, &sample)) {
453           myfree(line);
454           if (allow_incomplete) {
455             i_tags_setn(&im->tags, "i_incomplete", 1);
456             i_tags_setn(&im->tags, "i_lines_read", 1);
457             return im;
458           }
459           else {
460             if (gpeek(mb))
461               i_push_error(0, "invalid data for ascii pnm");
462             else
463               i_push_error(0, "short read - file truncated?");
464             i_img_destroy(im);
465             return NULL;
466           }
467         }
468         if (sample > maxval)
469           sample = maxval;
470         linep->channel[ch] = (sample * 255 + rounder) / maxval;
471       }
472       ++linep;
473     }
474     i_plin(im, 0, width, y, line);
475   }
476   myfree(line);
477
478   return im;
479 }
480
481 static
482 i_img *
483 read_pgm_ppm_ascii_16(mbuf *mb, i_img *im, int width, int height, 
484                       int channels, int maxval, int allow_incomplete) {
485   i_fcolor *line, *linep;
486   int x, y, ch;
487   double maxvalf = maxval;
488
489   line = mymalloc(width * sizeof(i_fcolor));
490   for(y=0;y<height;y++) {
491     linep = line;
492     for(x=0; x<width; x++) {
493       for(ch=0; ch<channels; ch++) {
494         int sample;
495         
496         if (!gnum(mb, &sample)) {
497           myfree(line);
498           if (allow_incomplete) {
499             i_tags_setn(&im->tags, "i_incomplete", 1);
500             i_tags_setn(&im->tags, "i_lines_read", y);
501             return im;
502           }
503           else {
504             if (gpeek(mb))
505               i_push_error(0, "invalid data for ascii pnm");
506             else
507               i_push_error(0, "short read - file truncated?");
508             i_img_destroy(im);
509             return NULL;
510           }
511         }
512         if (sample > maxval)
513           sample = maxval;
514         linep->channel[ch] = sample / maxvalf;
515       }
516       ++linep;
517     }
518     i_plinf(im, 0, width, y, line);
519   }
520   myfree(line);
521
522   return im;
523 }
524
525 /*
526 =item i_readpnm_wiol(ig, allow_incomplete)
527
528 Retrieve an image and stores in the iolayer object. Returns NULL on fatal error.
529
530    ig     - io_glue object
531    allow_incomplete - allows a partial file to be read successfully
532
533 =cut
534 */
535 static i_img *i_readpnm_wiol_low( mbuf*, int);
536
537 i_img *
538 i_readpnm_wiol(io_glue *ig, int allow_incomplete) {
539   mbuf buf;
540   io_glue_commit_types(ig);
541   init_buf(&buf, ig);
542
543   return i_readpnm_wiol_low( &buf, allow_incomplete );
544 }
545
546 static i_img *
547 i_readpnm_wiol_low( mbuf *buf, int allow_incomplete) {
548   i_img* im;
549   int type;
550   int width, height, maxval, channels;
551   int rounder;
552   char *cp;
553
554   i_clear_error();
555   mm_log((1,"i_readpnm(ig %p, allow_incomplete %d)\n", buf->ig, allow_incomplete));
556
557   cp = gnext(buf);
558
559   if (!cp || *cp != 'P') {
560     i_push_error(0, "bad header magic, not a PNM file");
561     mm_log((1, "i_readpnm: Could not read header of file\n"));
562     return NULL;
563   }
564
565   if ( !(cp = gnext(buf)) ) {
566     mm_log((1, "i_readpnm: Could not read header of file\n"));
567     return NULL;
568   }
569   
570   type = *cp-'0';
571
572   if (type < 1 || type > 6) {
573     i_push_error(0, "unknown PNM file type, not a PNM file");
574     mm_log((1, "i_readpnm: Not a pnm file\n"));
575     return NULL;
576   }
577
578   if ( !(cp = gnext(buf)) ) {
579     mm_log((1, "i_readpnm: Could not read header of file\n"));
580     return NULL;
581   }
582   
583   if ( !misspace(*cp) ) {
584     i_push_error(0, "unexpected character, not a PNM file");
585     mm_log((1, "i_readpnm: Not a pnm file\n"));
586     return NULL;
587   }
588   
589   mm_log((1, "i_readpnm: image is a %s\n", typenames[type-1] ));
590
591   
592   /* Read sizes and such */
593
594   if (!skip_comment(buf)) {
595     i_push_error(0, "while skipping to width");
596     mm_log((1, "i_readpnm: error reading before width\n"));
597     return NULL;
598   }
599   
600   if (!gnum(buf, &width)) {
601     i_push_error(0, "could not read image width");
602     mm_log((1, "i_readpnm: error reading width\n"));
603     return NULL;
604   }
605
606   if (!skip_comment(buf)) {
607     i_push_error(0, "while skipping to height");
608     mm_log((1, "i_readpnm: error reading before height\n"));
609     return NULL;
610   }
611
612   if (!gnum(buf, &height)) {
613     i_push_error(0, "could not read image height");
614     mm_log((1, "i_readpnm: error reading height\n"));
615     return NULL;
616   }
617   
618   if (!(type == 1 || type == 4)) {
619     if (!skip_comment(buf)) {
620       i_push_error(0, "while skipping to maxval");
621       mm_log((1, "i_readpnm: error reading before maxval\n"));
622       return NULL;
623     }
624
625     if (!gnum(buf, &maxval)) {
626       i_push_error(0, "could not read maxval");
627       mm_log((1, "i_readpnm: error reading maxval\n"));
628       return NULL;
629     }
630
631     if (maxval == 0) {
632       i_push_error(0, "maxval is zero - invalid pnm file");
633       mm_log((1, "i_readpnm: maxval is zero, invalid pnm file\n"));
634       return NULL;
635     }
636     else if (maxval > 65535) {
637       i_push_errorf(0, "maxval of %d is over 65535 - invalid pnm file", 
638                     maxval);
639       mm_log((1, "i_readpnm: maxval of %d is over 65535 - invalid pnm file\n", maxval));
640       return NULL;
641     }
642   } else maxval=1;
643   rounder = maxval / 2;
644
645   if (!(cp = gnext(buf)) || !misspace(*cp)) {
646     i_push_error(0, "garbage in header, invalid PNM file");
647     mm_log((1, "i_readpnm: garbage in header\n"));
648     return NULL;
649   }
650
651   channels = (type == 3 || type == 6) ? 3:1;
652
653   if (!i_int_check_image_file_limits(width, height, channels, sizeof(i_sample_t))) {
654     mm_log((1, "i_readpnm: image size exceeds limits\n"));
655     return NULL;
656   }
657
658   mm_log((1, "i_readpnm: (%d x %d), channels = %d, maxval = %d\n", width, height, channels, maxval));
659
660   if (type == 1 || type == 4) {
661     i_color pbm_pal[2];
662     pbm_pal[0].channel[0] = 255;
663     pbm_pal[1].channel[0] = 0;
664     
665     im = i_img_pal_new(width, height, 1, 256);
666     i_addcolors(im, pbm_pal, 2);
667   }
668   else {
669     if (maxval > 255)
670       im = i_img_16_new(width, height, channels);
671     else
672       im = i_img_8_new(width, height, channels);
673   }
674
675   switch (type) {
676   case 1: /* Ascii types */
677     im = read_pbm_ascii(buf, im, width, height, allow_incomplete);
678     break;
679
680   case 2:
681   case 3:
682     if (maxval > 255)
683       im = read_pgm_ppm_ascii_16(buf, im, width, height, channels, maxval, allow_incomplete);
684     else
685       im = read_pgm_ppm_ascii(buf, im, width, height, channels, maxval, allow_incomplete);
686     break;
687     
688   case 4: /* binary pbm */
689     im = read_pbm_bin(buf, im, width, height, allow_incomplete);
690     break;
691
692   case 5: /* binary pgm */
693   case 6: /* binary ppm */
694     if (maxval > 255)
695       im = read_pgm_ppm_bin16(buf, im, width, height, channels, maxval, allow_incomplete);
696     else
697       im = read_pgm_ppm_bin8(buf, im, width, height, channels, maxval, allow_incomplete);
698     break;
699
700   default:
701     mm_log((1, "type %s [P%d] unsupported\n", typenames[type-1], type));
702     return NULL;
703   }
704
705   if (!im)
706     return NULL;
707
708   i_tags_add(&im->tags, "i_format", 0, "pnm", -1, 0);
709   i_tags_setn(&im->tags, "pnm_maxval", maxval);
710   i_tags_setn(&im->tags, "pnm_type", type);
711
712   return im;
713 }
714
715 static void free_images(i_img **imgs, int count) {
716   int i;
717
718   if (count) {
719     for (i = 0; i < count; ++i)
720       i_img_destroy(imgs[i]);
721     myfree(imgs);
722   }
723 }
724
725 i_img **i_readpnm_multi_wiol(io_glue *ig, int *count, int allow_incomplete) {
726     i_img **results = NULL;
727     i_img *img = NULL;
728     char *cp = NULL;
729     mbuf buf;
730     int result_alloc = 0, 
731         value = 0, 
732         eof = 0;
733     *count=0;
734     io_glue_commit_types(ig);
735     init_buf(&buf, ig);
736     do {
737         mm_log((1, "read image %i\n", 1+*count));
738         img = i_readpnm_wiol_low( &buf, allow_incomplete );
739         if( !img ) {
740             free_images( results, *count );
741             return NULL;
742         }
743         ++*count;
744         if (*count > result_alloc) {
745             if (result_alloc == 0) {
746                 result_alloc = 5;
747                 results = mymalloc(result_alloc * sizeof(i_img *));
748             }
749             else {
750                 /* myrealloc never fails (it just dies if it can't allocate) */
751                 result_alloc *= 2;
752                 results = myrealloc(results, result_alloc * sizeof(i_img *));
753             }
754         }
755         results[*count-1] = img;
756
757
758         if( i_tags_get_int(&img->tags, "i_incomplete", 0, &value ) && value) {
759             eof = 1;
760         }
761         else if( skip_spaces( &buf ) && ( cp=gpeek( &buf ) ) && *cp == 'P' ) {
762             eof = 0;
763         }
764         else {
765             eof = 1;
766         }
767     } while(!eof);
768     return results;
769 }
770
771
772
773 static
774 int
775 write_pbm(i_img *im, io_glue *ig, int zero_is_white) {
776   int x, y;
777   i_palidx *line;
778   i_img_dim write_size;
779   unsigned char *write_buf;
780   unsigned char *writep;
781   char header[255];
782   unsigned mask;
783
784   sprintf(header, "P4\012# CREATOR: Imager\012%" i_DF " %" i_DF "\012", 
785           i_DFc(im->xsize), i_DFc(im->ysize));
786   if (i_io_write(ig, header, strlen(header)) < 0) {
787     i_push_error(0, "could not write pbm header");
788     return 0;
789   }
790   write_size = (im->xsize + 7) / 8;
791   line = mymalloc(sizeof(i_palidx) * im->xsize);
792   write_buf = mymalloc(write_size);
793   for (y = 0; y < im->ysize; ++y) {
794     i_gpal(im, 0, im->xsize, y, line);
795     mask = 0x80;
796     writep = write_buf;
797     memset(write_buf, 0, write_size);
798     for (x = 0; x < im->xsize; ++x) {
799       if (zero_is_white ? line[x] : !line[x])
800         *writep |= mask;
801       mask >>= 1;
802       if (!mask) {
803         ++writep;
804         mask = 0x80;
805       }
806     }
807     if (i_io_write(ig, write_buf, write_size) != write_size) {
808       i_push_error(0, "write failure");
809       myfree(write_buf);
810       myfree(line);
811       return 0;
812     }
813   }
814   myfree(write_buf);
815   myfree(line);
816
817   return 1;
818 }
819
820 static
821 int
822 write_ppm_data_8(i_img *im, io_glue *ig, int want_channels) {
823   size_t write_size = im->xsize * want_channels;
824   size_t buf_size = im->xsize * im->channels;
825   unsigned char *data = mymalloc(buf_size);
826   i_img_dim y = 0;
827   int rc = 1;
828   i_color bg;
829
830   i_get_file_background(im, &bg);
831   while (y < im->ysize && rc >= 0) {
832     i_gsamp_bg(im, 0, im->xsize, y, data, want_channels, &bg);
833     if (i_io_write(ig, data, write_size) != write_size) {
834       i_push_error(errno, "could not write ppm data");
835       rc = 0;
836       break;
837     }
838     ++y;
839   }
840   myfree(data);
841
842   return rc;
843 }
844
845 static
846 int
847 write_ppm_data_16(i_img *im, io_glue *ig, int want_channels) {
848   size_t line_size = im->channels * im->xsize * sizeof(i_fsample_t);
849   size_t sample_count = want_channels * im->xsize;
850   size_t write_size = sample_count * 2;
851   i_fsample_t *line_buf = mymalloc(line_size);
852   i_fsample_t *samplep;
853   unsigned char *write_buf = mymalloc(write_size);
854   unsigned char *writep;
855   size_t sample_num;
856   i_img_dim y = 0;
857   int rc = 1;
858   i_fcolor bg;
859
860   i_get_file_backgroundf(im, &bg);
861
862   while (y < im->ysize) {
863     i_gsampf_bg(im, 0, im->xsize, y, line_buf, want_channels, &bg);
864     samplep = line_buf;
865     writep = write_buf;
866     for (sample_num = 0; sample_num < sample_count; ++sample_num) {
867       unsigned sample16 = SampleFTo16(*samplep++);
868       *writep++ = sample16 >> 8;
869       *writep++ = sample16 & 0xFF;
870     }
871     if (i_io_write(ig, write_buf, write_size) != write_size) {
872       i_push_error(errno, "could not write ppm data");
873       rc = 0;
874       break;
875     }
876     ++y;
877   }
878   myfree(line_buf);
879   myfree(write_buf);
880
881   return rc;
882 }
883
884 undef_int
885 i_writeppm_wiol(i_img *im, io_glue *ig) {
886   char header[255];
887   int zero_is_white;
888   int wide_data;
889
890   mm_log((1,"i_writeppm(im %p, ig %p)\n", im, ig));
891   i_clear_error();
892
893   /* Add code to get the filename info from the iolayer */
894   /* Also add code to check for mmapped code */
895
896   io_glue_commit_types(ig);
897
898   if (i_img_is_monochrome(im, &zero_is_white)) {
899     return write_pbm(im, ig, zero_is_white);
900   }
901   else {
902     int type;
903     int maxval;
904     int want_channels = im->channels;
905
906     if (want_channels == 2 || want_channels == 4)
907       --want_channels;
908
909     if (!i_tags_get_int(&im->tags, "pnm_write_wide_data", 0, &wide_data))
910       wide_data = 0;
911
912     if (want_channels == 3) {
913       type = 6;
914     }
915     else if (want_channels == 1) {
916       type = 5;
917     }
918     else {
919       i_push_error(0, "can only save 1 or 3 channel images to pnm");
920       mm_log((1,"i_writeppm: ppm/pgm is 1 or 3 channel only (current image is %d)\n",im->channels));
921       return(0);
922     }
923     if (im->bits <= 8 || !wide_data)
924       maxval = 255;
925     else
926       maxval = 65535;
927
928     sprintf(header,"P%d\n#CREATOR: Imager\n%" i_DF " %" i_DF"\n%d\n", 
929             type, i_DFc(im->xsize), i_DFc(im->ysize), maxval);
930
931     if (ig->writecb(ig,header,strlen(header)) != strlen(header)) {
932       i_push_error(errno, "could not write ppm header");
933       mm_log((1,"i_writeppm: unable to write ppm header.\n"));
934       return(0);
935     }
936
937     if (!im->virtual && im->bits == i_8_bits && im->type == i_direct_type
938         && im->channels == want_channels) {
939       if (ig->writecb(ig,im->idata,im->bytes) != im->bytes) {
940         i_push_error(errno, "could not write ppm data");
941         return 0;
942       }
943     }
944     else if (maxval == 255) {
945       if (!write_ppm_data_8(im, ig, want_channels))
946         return 0;
947     }
948     else {
949       if (!write_ppm_data_16(im, ig, want_channels))
950         return 0;
951     }
952   }
953   ig->closecb(ig);
954
955   return(1);
956 }
957
958 /*
959 =back
960
961 =head1 AUTHOR
962
963 Arnar M. Hrafnkelsson <addi@umich.edu>, Tony Cook <tonyc@cpan.org>,
964 Philip Gwyn <gwyn@cpan.org>.
965
966 =head1 SEE ALSO
967
968 Imager(3)
969
970 =cut
971 */