]> git.imager.perl.org - imager.git/blob - gif.c
-document that you don't want the FT2 freetype.h in the include path
[imager.git] / gif.c
1 #include "image.h"
2 #include <gif_lib.h>
3 #ifdef _MSCVER
4 #include <io.h>
5 #else
6 #include <unistd.h>
7 #endif
8 #include <errno.h>
9 /* XXX: Reading still needs to support reading all those gif properties */
10
11 /*
12 =head1 NAME
13
14 gif.c - read and write gif files for Imager
15
16 =head1 SYNOPSIS
17
18   i_img *img;
19   i_img *imgs[count];
20   int fd;
21   int *colour_table,
22   int colours;
23   int max_colours; // number of bits per colour
24   int pixdev;  // how much noise to add 
25   i_color fixed[N]; // fixed palette entries 
26   int fixedlen; // number of fixed colours 
27   int success; // non-zero on success
28   char *data; // a GIF file in memory
29   int length; // how big data is 
30   int reader(char *, char *, int, int);
31   int writer(char *, char *, int);
32   char *userdata; // user's data, whatever it is
33   i_quantize quant;
34   i_gif_opts opts;
35
36   img = i_readgif(fd, &colour_table, &colours);
37   success = i_writegif(img, fd, max_colours, pixdev, fixedlen, fixed);
38   success = i_writegifmc(img, fd, max_colours);
39   img = i_readgif_scalar(data, length, &colour_table, &colours);
40   img = i_readgif_callback(cb, userdata, &colour_table, &colours);
41   success = i_writegif_gen(&quant, fd, imgs, count, &opts);
42   success = i_writegif_callback(&quant, writer, userdata, maxlength, 
43                                 imgs, count, &opts);
44
45 =head1 DESCRIPTION
46
47 This source file provides the C level interface to reading and writing
48 GIF files for Imager.
49
50 This has been tested with giflib 3 and 4, though you lose the callback
51 functionality with giflib3.
52
53 =head1 REFERENCE
54
55 =over
56
57 =cut
58 */
59
60 static char const *gif_error_msg(int code);
61 static void gif_push_error(void);
62
63 #if IM_GIFMAJOR >= 4
64
65 static int gif_read_callback(GifFileType *gft, GifByteType *buf, int length);
66
67 /*
68 =item gif_scalar_info
69
70 Internal.  A structure passed to the reader function used for reading
71 GIFs from scalars.
72
73 Used with giflib 4 and later.
74
75 =cut
76 */
77
78 struct gif_scalar_info {
79   char *data;
80   int length;
81   int cpos;
82 };
83
84 /*
85 =item my_gif_inputfunc(GifFileType *gft, GifByteType *buf, int length)
86
87 Internal.  The reader callback passed to giflib.
88
89 Used with giflib 4 and later.
90
91 =cut
92 */
93
94 int
95 my_gif_inputfunc(GifFileType* gft, GifByteType *buf,int length) {
96   struct gif_scalar_info *gsi=(struct gif_scalar_info *)gft->UserData;
97   /*   fprintf(stderr,"my_gif_inputfunc: length=%d cpos=%d tlength=%d\n",length,gsi->cpos,gsi->length); */
98
99   if (gsi->cpos == gsi->length) return 0;
100   if (gsi->cpos+length > gsi->length) length=gsi->length-gsi->cpos; /* Don't read too much */
101   memcpy(buf,gsi->data+gsi->cpos,length);
102   gsi->cpos+=length;
103   return length;
104 }
105
106 #endif
107
108
109
110 /* Make some variables global, so we could access them faster: */
111
112 static int
113   InterlacedOffset[] = { 0, 4, 2, 1 }, /* The way Interlaced image should. */
114   InterlacedJumps[] = { 8, 8, 4, 2 };    /* be read - offsets and jumps... */
115
116
117
118 static
119 void
120 i_colortable_copy(int **colour_table, int *colours, ColorMapObject *colourmap) {
121   GifColorType *mapentry;
122   int q;
123   int colourmapsize = colourmap->ColorCount;
124
125   if(colours) *colours = colourmapsize;
126   if(!colour_table) return;
127   
128   *colour_table = mymalloc(sizeof(int) * colourmapsize * 3);
129   memset(*colour_table, 0, sizeof(int) * colourmapsize * 3);
130
131   for(q=0; q<colourmapsize; q++) {
132     mapentry = &colourmap->Colors[q];
133     (*colour_table)[q*3 + 0] = mapentry->Red;
134     (*colour_table)[q*3 + 1] = mapentry->Green;
135     (*colour_table)[q*3 + 2] = mapentry->Blue;
136   }
137 }
138
139
140 /*
141 =item i_readgif_low(GifFileType *GifFile, int **colour_table, int *colours)
142
143 Internal.  Low-level function for reading a GIF file.  The caller must
144 create the appropriate GifFileType object and pass it in.
145
146 =cut
147 */
148
149 i_img *
150 i_readgif_low(GifFileType *GifFile, int **colour_table, int *colours) {
151   i_img *im;
152   int i, j, Size, Row, Col, Width, Height, ExtCode, Count, x;
153   int cmapcnt = 0, ImageNum = 0, BackGround = 0, ColorMapSize = 0;
154   ColorMapObject *ColorMap;
155  
156   GifRecordType RecordType;
157   GifByteType *Extension;
158   
159   GifRowType GifRow;
160   static GifColorType *ColorMapEntry;
161   i_color col;
162
163   mm_log((1,"i_readgif_low(GifFile %p, colour_table %p, colours %p)\n", GifFile, colour_table, colours));
164
165   /* it's possible that the caller has called us with *colour_table being
166      non-NULL, but we check that to see if we need to free an allocated
167      colour table on error.
168   */
169   if (colour_table) *colour_table = NULL;
170
171   BackGround = GifFile->SBackGroundColor;
172   ColorMap = (GifFile->Image.ColorMap ? GifFile->Image.ColorMap : GifFile->SColorMap);
173
174   if (ColorMap) {
175     ColorMapSize = ColorMap->ColorCount;
176     i_colortable_copy(colour_table, colours, ColorMap);
177     cmapcnt++;
178   }
179   
180
181   im = i_img_empty_ch(NULL, GifFile->SWidth, GifFile->SHeight, 3);
182   if (!im) {
183     if (colour_table && *colour_table) {
184       myfree(*colour_table);
185       *colour_table = NULL;
186     }
187     DGifCloseFile(GifFile);
188     return NULL;
189   }
190
191   Size = GifFile->SWidth * sizeof(GifPixelType); 
192   
193   GifRow = mymalloc(Size);
194
195   for (i = 0; i < GifFile->SWidth; i++) GifRow[i] = GifFile->SBackGroundColor;
196   
197   /* Scan the content of the GIF file and load the image(s) in: */
198   do {
199     if (DGifGetRecordType(GifFile, &RecordType) == GIF_ERROR) {
200       gif_push_error();
201       i_push_error(0, "Unable to get record type");
202       if (colour_table && *colour_table) {
203         myfree(*colour_table);
204         *colour_table = NULL;
205       }
206       myfree(GifRow);
207       i_img_destroy(im);
208       DGifCloseFile(GifFile);
209       return NULL;
210     }
211     
212     switch (RecordType) {
213     case IMAGE_DESC_RECORD_TYPE:
214       if (DGifGetImageDesc(GifFile) == GIF_ERROR) {
215         gif_push_error();
216         i_push_error(0, "Unable to get image descriptor");
217         if (colour_table && *colour_table) {
218           myfree(*colour_table);
219           *colour_table = NULL;
220         }
221         myfree(GifRow);
222         i_img_destroy(im);
223         DGifCloseFile(GifFile);
224         return NULL;
225       }
226
227       if (( ColorMap = (GifFile->Image.ColorMap ? GifFile->Image.ColorMap : GifFile->SColorMap) )) {
228         mm_log((1, "Adding local colormap\n"));
229         ColorMapSize = ColorMap->ColorCount;
230         if ( cmapcnt == 0) {
231           i_colortable_copy(colour_table, colours, ColorMap);
232           cmapcnt++;
233         }
234       } else {
235         /* No colormap and we are about to read in the image - abandon for now */
236         mm_log((1, "Going in with no colormap\n"));
237         i_push_error(0, "Image does not have a local or a global color map");
238         /* we can't have allocated a colour table here */
239         myfree(GifRow);
240         i_img_destroy(im);
241         DGifCloseFile(GifFile);
242         return NULL;
243       }
244       
245       Row = GifFile->Image.Top; /* Image Position relative to Screen. */
246       Col = GifFile->Image.Left;
247       Width = GifFile->Image.Width;
248       Height = GifFile->Image.Height;
249       ImageNum++;
250       mm_log((1,"i_readgif_low: Image %d at (%d, %d) [%dx%d]: \n",ImageNum, Col, Row, Width, Height));
251
252       if (GifFile->Image.Left + GifFile->Image.Width > GifFile->SWidth ||
253           GifFile->Image.Top + GifFile->Image.Height > GifFile->SHeight) {
254         i_push_errorf(0, "Image %d is not confined to screen dimension, aborted.\n",ImageNum);
255         if (colour_table && *colour_table) {
256           myfree(*colour_table);
257           *colour_table = NULL;
258         }
259         myfree(GifRow);
260         i_img_destroy(im);
261         DGifCloseFile(GifFile);
262         return NULL;
263       }
264       if (GifFile->Image.Interlace) {
265
266         for (Count = i = 0; i < 4; i++) for (j = Row + InterlacedOffset[i]; j < Row + Height; j += InterlacedJumps[i]) {
267           Count++;
268           if (DGifGetLine(GifFile, GifRow, Width) == GIF_ERROR) {
269             gif_push_error();
270             i_push_error(0, "Reading GIF line");
271             if (colour_table && *colour_table) {
272               myfree(*colour_table);
273               *colour_table = NULL;
274             }
275             myfree(GifRow);
276             i_img_destroy(im);
277             DGifCloseFile(GifFile);
278             return NULL;
279           }
280           
281           for (x = 0; x < Width; x++) {
282             ColorMapEntry = &ColorMap->Colors[GifRow[x]];
283             col.rgb.r = ColorMapEntry->Red;
284             col.rgb.g = ColorMapEntry->Green;
285             col.rgb.b = ColorMapEntry->Blue;
286             i_ppix(im,Col+x,j,&col);
287           }
288           
289         }
290       }
291       else {
292         for (i = 0; i < Height; i++) {
293           if (DGifGetLine(GifFile, GifRow, Width) == GIF_ERROR) {
294             gif_push_error();
295             i_push_error(0, "Reading GIF line");
296             if (colour_table && *colour_table) {
297               myfree(*colour_table);
298               *colour_table = NULL;
299             }
300             myfree(GifRow);
301             i_img_destroy(im);
302             DGifCloseFile(GifFile);
303             return NULL;
304           }
305
306           for (x = 0; x < Width; x++) {
307             ColorMapEntry = &ColorMap->Colors[GifRow[x]];
308             col.rgb.r = ColorMapEntry->Red;
309             col.rgb.g = ColorMapEntry->Green;
310             col.rgb.b = ColorMapEntry->Blue;
311             i_ppix(im, Col+x, Row, &col);
312           }
313           Row++;
314         }
315       }
316       break;
317     case EXTENSION_RECORD_TYPE:
318       /* Skip any extension blocks in file: */
319       if (DGifGetExtension(GifFile, &ExtCode, &Extension) == GIF_ERROR) {
320         gif_push_error();
321         i_push_error(0, "Reading extension record");
322         if (colour_table && *colour_table) {
323           myfree(*colour_table);
324           *colour_table = NULL;
325         }
326         myfree(GifRow);
327         i_img_destroy(im);
328         DGifCloseFile(GifFile);
329         return NULL;
330       }
331       while (Extension != NULL) {
332         if (DGifGetExtensionNext(GifFile, &Extension) == GIF_ERROR) {
333           gif_push_error();
334           i_push_error(0, "reading next block of extension");
335           if (colour_table && *colour_table) {
336             myfree(*colour_table);
337             *colour_table = NULL;
338           }
339           myfree(GifRow);
340           i_img_destroy(im);
341           DGifCloseFile(GifFile);
342           return NULL;
343         }
344       }
345       break;
346     case TERMINATE_RECORD_TYPE:
347       break;
348     default:                /* Should be traps by DGifGetRecordType. */
349       break;
350     }
351   } while (RecordType != TERMINATE_RECORD_TYPE);
352   
353   myfree(GifRow);
354   
355   if (DGifCloseFile(GifFile) == GIF_ERROR) {
356     gif_push_error();
357     i_push_error(0, "Closing GIF file object");
358     if (colour_table && *colour_table) {
359       myfree(*colour_table);
360       *colour_table = NULL;
361     }
362     i_img_destroy(im);
363     return NULL;
364   }
365
366   i_tags_add(&im->tags, "i_format", 0, "gif", -1, 0);
367
368   return im;
369 }
370
371 /*
372 =item i_readgif(int fd, int **colour_table, int *colours)
373
374 Reads in a GIF file from a file handle and converts it to an Imager
375 RGB image object.
376
377 Returns the palette for the object in colour_table for colours
378 colours.
379
380 Returns NULL on failure.
381
382 =cut
383 */
384
385 i_img *
386 i_readgif(int fd, int **colour_table, int *colours) {
387   GifFileType *GifFile;
388
389   i_clear_error();
390   
391   mm_log((1,"i_readgif(fd %d, colour_table %p, colours %p)\n", fd, colour_table, colours));
392
393   if ((GifFile = DGifOpenFileHandle(fd)) == NULL) {
394     gif_push_error();
395     i_push_error(0, "Cannot create giflib file object");
396     mm_log((1,"i_readgif: Unable to open file\n"));
397     return NULL;
398   }
399
400   return i_readgif_low(GifFile, colour_table, colours);
401 }
402
403 /*
404
405 Internal function called by i_readgif_multi_low() in error handling
406
407 */
408 static void free_images(i_img **imgs, int count) {
409   int i;
410   for (i = 0; i < count; ++i)
411     i_img_destroy(imgs[i]);
412   myfree(imgs);
413 }
414
415 /*
416 =item i_readgif_multi_low(GifFileType *gf, int *count)
417
418 Reads one of more gif images from the given GIF file.
419
420 Returns a pointer to an array of i_img *, and puts the count into 
421 *count.
422
423 Unlike the normal i_readgif*() functions the images are paletted
424 images rather than a combined RGB image.
425
426 This functions sets tags on the images returned:
427
428 =over
429
430 =item gif_left
431
432 the offset of the image from the left of the "screen" ("Image Left
433 Position")
434
435 =item gif_top
436
437 the offset of the image from the top of the "screen" ("Image Top Position")
438
439 =item gif_interlace
440
441 non-zero if the image was interlaced ("Interlace Flag")
442
443 =item gif_screen_width
444
445 =item gif_screen_height
446
447 the size of the logical screen ("Logical Screen Width", 
448 "Logical Screen Height")
449
450 =item gif_local_map
451
452 Non-zero if this image had a local color map.
453
454 =item gif_background
455
456 The index in the global colormap of the logical screen's background
457 color.  This is only set if the current image uses the global
458 colormap.
459
460 =item gif_trans_index
461
462 The index of the color in the colormap used for transparency.  If the
463 image has a transparency then it is returned as a 4 channel image with
464 the alpha set to zero in this palette entry. ("Transparent Color Index")
465
466 =item gif_delay
467
468 The delay until the next frame is displayed, in 1/100 of a second. 
469 ("Delay Time").
470
471 =item gif_user_input
472
473 whether or not a user input is expected before continuing (view dependent) 
474 ("User Input Flag").
475
476 =item gif_disposal
477
478 how the next frame is displayed ("Disposal Method")
479
480 =item gif_loop
481
482 the number of loops from the Netscape Loop extension.  This may be zero.
483
484 =item gif_comment
485
486 the first block of the first gif comment before each image.
487
488 =back
489
490 Where applicable, the ("name") is the name of that field from the GIF89 
491 standard.
492
493 =cut
494 */
495
496 i_img **i_readgif_multi_low(GifFileType *GifFile, int *count) {
497   i_img *img;
498   int i, j, Size, Width, Height, ExtCode, Count, x;
499   int ImageNum = 0, BackGround = 0, ColorMapSize = 0;
500   ColorMapObject *ColorMap;
501  
502   GifRecordType RecordType;
503   GifByteType *Extension;
504   
505   GifRowType GifRow;
506   int got_gce = 0;
507   int trans_index; /* transparent index if we see a GCE */
508   int gif_delay; /* delay from a GCE */
509   int user_input; /* user input flag from a GCE */
510   int disposal; /* disposal method from a GCE */
511   int got_ns_loop = 0;
512   int ns_loop;
513   char *comment = NULL; /* a comment */
514   i_img **results = NULL;
515   int result_alloc = 0;
516   int channels;
517   
518   *count = 0;
519
520   mm_log((1,"i_readgif_multi_low(GifFile %p, , count %p)\n", GifFile, count));
521
522   BackGround = GifFile->SBackGroundColor;
523
524   Size = GifFile->SWidth * sizeof(GifPixelType);
525   
526   if ((GifRow = (GifRowType) mymalloc(Size)) == NULL)
527     m_fatal(0,"Failed to allocate memory required, aborted."); /* First row. */
528
529   /* Scan the content of the GIF file and load the image(s) in: */
530   do {
531     if (DGifGetRecordType(GifFile, &RecordType) == GIF_ERROR) {
532       gif_push_error();
533       i_push_error(0, "Unable to get record type");
534       free_images(results, *count);
535       DGifCloseFile(GifFile);
536       return NULL;
537     }
538     
539     switch (RecordType) {
540     case IMAGE_DESC_RECORD_TYPE:
541       if (DGifGetImageDesc(GifFile) == GIF_ERROR) {
542         gif_push_error();
543         i_push_error(0, "Unable to get image descriptor");
544         free_images(results, *count);
545         DGifCloseFile(GifFile);
546         return NULL;
547       }
548
549       if (( ColorMap = (GifFile->Image.ColorMap ? GifFile->Image.ColorMap : GifFile->SColorMap) )) {
550         mm_log((1, "Adding local colormap\n"));
551         ColorMapSize = ColorMap->ColorCount;
552       } else {
553         /* No colormap and we are about to read in the image - 
554            abandon for now */
555         mm_log((1, "Going in with no colormap\n"));
556         i_push_error(0, "Image does not have a local or a global color map");
557         free_images(results, *count);
558         DGifCloseFile(GifFile);
559         return NULL;
560       }
561       
562       Width = GifFile->Image.Width;
563       Height = GifFile->Image.Height;
564       channels = 3;
565       if (got_gce && trans_index >= 0)
566         channels = 4;
567       img = i_img_pal_new(Width, Height, channels, 256);
568       if (!img) {
569         free_images(results, *count);
570         return NULL;
571       }
572       /* populate the palette of the new image */
573       mm_log((1, "ColorMapSize %d\n", ColorMapSize));
574       for (i = 0; i < ColorMapSize; ++i) {
575         i_color col;
576         col.rgba.r = ColorMap->Colors[i].Red;
577         col.rgba.g = ColorMap->Colors[i].Green;
578         col.rgba.b = ColorMap->Colors[i].Blue;
579         if (channels == 4 && trans_index == i)
580           col.rgba.a = 0;
581         else
582           col.rgba.a = 255;
583        
584         i_addcolors(img, &col, 1);
585       }
586       ++*count;
587       if (*count > result_alloc) {
588         if (result_alloc == 0) {
589           result_alloc = 5;
590           results = mymalloc(result_alloc * sizeof(i_img *));
591         }
592         else {
593           /* myrealloc never fails (it just dies if it can't allocate) */
594           result_alloc *= 2;
595           results = myrealloc(results, result_alloc * sizeof(i_img *));
596         }
597       }
598       results[*count-1] = img;
599       i_tags_add(&img->tags, "i_format", 0, "gif", -1, 0);
600       i_tags_addn(&img->tags, "gif_left", 0, GifFile->Image.Left);
601       /**(char *)0 = 1;*/
602       i_tags_addn(&img->tags, "gif_top",  0, GifFile->Image.Top);
603       i_tags_addn(&img->tags, "gif_interlace", 0, GifFile->Image.Interlace);
604       i_tags_addn(&img->tags, "gif_screen_width", 0, GifFile->SWidth);
605       i_tags_addn(&img->tags, "gif_screen_height", 0, GifFile->SHeight);
606       if (GifFile->SColorMap && !GifFile->Image.ColorMap) {
607         i_tags_addn(&img->tags, "gif_background", 0, 
608                     GifFile->SBackGroundColor);
609       }
610       if (GifFile->Image.ColorMap) {
611         i_tags_addn(&img->tags, "gif_localmap", 0, 1);
612       }
613       if (got_gce) {
614         if (trans_index >= 0) {
615           i_color trans;
616           i_tags_addn(&img->tags, "gif_trans_index", 0, trans_index);
617           i_getcolors(img, trans_index, &trans, 1);
618           i_tags_set_color(&img->tags, "gif_trans_color", 0, &trans);
619         }
620         i_tags_addn(&img->tags, "gif_delay", 0, gif_delay);
621         i_tags_addn(&img->tags, "gif_user_input", 0, user_input);
622         i_tags_addn(&img->tags, "gif_disposal", 0, disposal);
623       }
624       got_gce = 0;
625       if (got_ns_loop)
626         i_tags_addn(&img->tags, "gif_loop", 0, ns_loop);
627       if (comment) {
628         i_tags_add(&img->tags, "gif_comment", 0, comment, strlen(comment), 0);
629         myfree(comment);
630         comment = NULL;
631       }
632
633       ImageNum++;
634       mm_log((1,"i_readgif_multi_low: Image %d at (%d, %d) [%dx%d]: \n",
635               ImageNum, GifFile->Image.Left, GifFile->Image.Top, Width, Height));
636
637       if (GifFile->Image.Left + GifFile->Image.Width > GifFile->SWidth ||
638           GifFile->Image.Top + GifFile->Image.Height > GifFile->SHeight) {
639         i_push_errorf(0, "Image %d is not confined to screen dimension, aborted.\n",ImageNum);
640         free_images(results, *count);        
641         DGifCloseFile(GifFile);
642         return(0);
643       }
644              
645       if (GifFile->Image.Interlace) {
646         for (Count = i = 0; i < 4; i++) {
647           for (j = InterlacedOffset[i]; j < Height; 
648                j += InterlacedJumps[i]) {
649             Count++;
650             if (DGifGetLine(GifFile, GifRow, Width) == GIF_ERROR) {
651               gif_push_error();
652               i_push_error(0, "Reading GIF line");
653               free_images(results, *count);
654               DGifCloseFile(GifFile);
655               return NULL;
656             }
657             
658             i_ppal(img, 0, Width, j, GifRow);
659           }
660         }
661       }
662       else {
663         for (i = 0; i < Height; i++) {
664           if (DGifGetLine(GifFile, GifRow, Width) == GIF_ERROR) {
665             gif_push_error();
666             i_push_error(0, "Reading GIF line");
667             free_images(results, *count);
668             DGifCloseFile(GifFile);
669             return NULL;
670           }
671
672           i_ppal(img, 0, Width, i, GifRow);
673         }
674       }
675       break;
676     case EXTENSION_RECORD_TYPE:
677       /* Skip any extension blocks in file: */
678       if (DGifGetExtension(GifFile, &ExtCode, &Extension) == GIF_ERROR) {
679         gif_push_error();
680         i_push_error(0, "Reading extension record");
681         free_images(results, *count);
682         DGifCloseFile(GifFile);
683         return NULL;
684       }
685       if (ExtCode == 0xF9) {
686         got_gce = 1;
687         if (Extension[1] & 1)
688           trans_index = Extension[4];
689         else
690           trans_index = -1;
691         gif_delay = Extension[2] + 256 * Extension[3];
692         user_input = (Extension[0] & 2) != 0;
693         disposal = (Extension[0] >> 2) & 3;
694       }
695       if (ExtCode == 0xFF && *Extension == 11) {
696         if (memcmp(Extension+1, "NETSCAPE2.0", 11) == 0) {
697           if (DGifGetExtensionNext(GifFile, &Extension) == GIF_ERROR) {
698             gif_push_error();
699             i_push_error(0, "reading loop extension");
700             free_images(results, *count);
701             DGifCloseFile(GifFile);
702             return NULL;
703           }
704           if (Extension && *Extension == 3) {
705             got_ns_loop = 1;
706             ns_loop = Extension[2] + 256 * Extension[3];
707           }
708         }
709       }
710       else if (ExtCode == 0xFE) {
711         /* while it's possible for a GIF file to contain more than one
712            comment, I'm only implementing a single comment per image, 
713            with the comment saved into the following image.
714            If someone wants more than that they can implement it.
715            I also don't handle comments that take more than one block.
716         */
717         if (!comment) {
718           comment = mymalloc(*Extension+1);
719           memcpy(comment, Extension+1, *Extension);
720           comment[*Extension] = '\0';
721         }
722       }
723       while (Extension != NULL) {
724         if (DGifGetExtensionNext(GifFile, &Extension) == GIF_ERROR) {
725           gif_push_error();
726           i_push_error(0, "reading next block of extension");
727           free_images(results, *count);
728           DGifCloseFile(GifFile);
729           return NULL;
730         }
731       }
732       break;
733     case TERMINATE_RECORD_TYPE:
734       break;
735     default:                /* Should be trapped by DGifGetRecordType. */
736       break;
737     }
738   } while (RecordType != TERMINATE_RECORD_TYPE);
739
740   if (comment) {
741     if (*count) {
742       i_tags_add(&(results[*count-1]->tags), "gif_comment", 0, comment, 
743                  strlen(comment), 0);
744     }
745     myfree(comment);
746   }
747   
748   myfree(GifRow);
749   
750   if (DGifCloseFile(GifFile) == GIF_ERROR) {
751     gif_push_error();
752     i_push_error(0, "Closing GIF file object");
753     free_images(results, *count);
754     return NULL;
755   }
756
757   return results;
758 }
759
760 #if IM_GIFMAJOR >= 4
761 /* giflib declares this incorrectly as EgifOpen */
762 extern GifFileType *EGifOpen(void *userData, OutputFunc writeFunc);
763
764 static int io_glue_read_cb(GifFileType *gft, GifByteType *buf, int length);
765 #endif
766
767 /*
768 =item i_readgif_multi_wiol(ig, int *count)
769
770 =cut
771 */
772
773 i_img **
774 i_readgif_multi_wiol(io_glue *ig, int *count) {
775   io_glue_commit_types(ig);
776
777   if (ig->source.type == FDSEEK || ig->source.type == FDNOSEEK) {
778     return i_readgif_multi(ig->source.fdseek.fd, count);
779   }
780   else {
781 #if IM_GIFMAJOR >= 4
782     GifFileType *GifFile;
783
784     i_clear_error();
785
786     if ((GifFile = DGifOpen((void *)ig, io_glue_read_cb )) == NULL) {
787       gif_push_error();
788       i_push_error(0, "Cannot create giflib callback object");
789       mm_log((1,"i_readgif_multi_wiol: Unable to open callback datasource.\n"));
790       return NULL;
791     }
792     
793     return i_readgif_multi_low(GifFile, count);
794 #else
795     i_clear_error();
796     i_push_error(0, "callbacks not supported with giflib3");
797     
798     return NULL;
799 #endif
800   }
801 }
802
803 /*
804 =item i_readgif_multi(int fd, int *count)
805
806 =cut
807 */
808 i_img **
809 i_readgif_multi(int fd, int *count) {
810   GifFileType *GifFile;
811
812   i_clear_error();
813   
814   mm_log((1,"i_readgif_multi(fd %d, &count %p)\n", fd, count));
815
816   if ((GifFile = DGifOpenFileHandle(fd)) == NULL) {
817     gif_push_error();
818     i_push_error(0, "Cannot create giflib file object");
819     mm_log((1,"i_readgif: Unable to open file\n"));
820     return NULL;
821   }
822
823   return i_readgif_multi_low(GifFile, count);
824 }
825
826 /*
827 =item i_readgif_multi_scalar(char *data, int length, int *count)
828
829 =cut
830 */
831 i_img **
832 i_readgif_multi_scalar(char *data, int length, int *count) {
833 #if IM_GIFMAJOR >= 4
834   GifFileType *GifFile;
835   struct gif_scalar_info gsi;
836
837   i_clear_error();
838   
839   gsi.cpos=0;
840   gsi.length=length;
841   gsi.data=data;
842
843   mm_log((1,"i_readgif_multi_scalar(data %p, length %d, &count %p)\n", 
844           data, length, count));
845
846   if ((GifFile = DGifOpen( (void*) &gsi, my_gif_inputfunc )) == NULL) {
847     gif_push_error();
848     i_push_error(0, "Cannot create giflib callback object");
849     mm_log((1,"i_readgif_multi_scalar: Unable to open scalar datasource.\n"));
850     return NULL;
851   }
852
853   return i_readgif_multi_low(GifFile, count);
854 #else
855   return NULL;
856 #endif
857 }
858
859 /*
860 =item i_readgif_callback(i_read_callback_t cb, char *userdata, int **colour_table, int *colours)
861
862 Read a GIF file into an Imager RGB file, the data of the GIF file is
863 retreived by callin the user supplied callback function.
864
865 This function is only used with giflib 4 and higher.
866
867 =cut
868 */
869
870 i_img**
871 i_readgif_multi_callback(i_read_callback_t cb, char *userdata, int *count) {
872 #if IM_GIFMAJOR >= 4
873   GifFileType *GifFile;
874   i_img **result;
875
876   i_gen_read_data *gci = i_gen_read_data_new(cb, userdata);
877
878   i_clear_error();
879   
880   mm_log((1,"i_readgif_multi_callback(callback %p, userdata %p, count %p)\n", cb, userdata, count));
881   if ((GifFile = DGifOpen( (void*) gci, gif_read_callback )) == NULL) {
882     gif_push_error();
883     i_push_error(0, "Cannot create giflib callback object");
884     mm_log((1,"i_readgif_callback: Unable to open callback datasource.\n"));
885     myfree(gci);
886     return NULL;
887   }
888
889   result = i_readgif_multi_low(GifFile, count);
890   i_free_gen_read_data(gci);
891
892   return result;
893 #else
894   return NULL;
895 #endif
896 }
897
898 /*
899 =item i_writegif(i_img *im, int fd, int max_colors, int pixdev, int fixedlen, i_color fixed[])
900
901 Write I<img> to the file handle I<fd>.  The resulting GIF will use a
902 maximum of 1<<I<max_colours> colours, with the first I<fixedlen>
903 colours taken from I<fixed>.
904
905 Returns non-zero on success.
906
907 =cut
908 */
909
910 undef_int
911 i_writegif(i_img *im, int fd, int max_colors, int pixdev, int fixedlen, i_color fixed[]) {
912   i_color colors[256];
913   i_quantize quant;
914   
915   memset(&quant, 0, sizeof(quant));
916   quant.make_colors = mc_addi;
917   quant.mc_colors = colors;
918   quant.mc_size = 1<<max_colors;
919   quant.mc_count = fixedlen;
920   memcpy(colors, fixed, fixedlen * sizeof(i_color));
921   quant.translate = pt_perturb;
922   quant.perturb = pixdev;
923   return i_writegif_gen(&quant, fd, &im, 1);
924 }
925
926 /*
927 =item i_writegifmc(i_img *im, int fd, int max_colors)
928
929 Write I<img> to the file handle I<fd>.  The resulting GIF will use a
930 maximum of 1<<I<max_colours> colours.
931
932 Returns non-zero on success.
933
934 =cut
935 */
936
937 undef_int
938 i_writegifmc(i_img *im, int fd, int max_colors) {
939   i_color colors[256];
940   i_quantize quant;
941
942 /*    *(char *)0 = 1; */
943   
944   memset(&quant, 0, sizeof(quant));
945   quant.make_colors = mc_none; /* ignored for pt_giflib */
946   quant.mc_colors = colors;
947   quant.mc_size = 1 << max_colors;
948   quant.mc_count = 0;
949   quant.translate = pt_giflib;
950   return i_writegif_gen(&quant, fd, &im, 1);
951 }
952
953
954 /*
955 =item i_readgif_scalar(char *data, int length, int **colour_table, int *colours)
956
957 Reads a GIF file from an in memory copy of the file.  This can be used
958 if you get the 'file' from some source other than an actual file (or
959 some other file handle).
960
961 This function is only available with giflib 4 and higher.
962
963 =cut
964 */
965 i_img*
966 i_readgif_scalar(char *data, int length, int **colour_table, int *colours) {
967 #if IM_GIFMAJOR >= 4
968   GifFileType *GifFile;
969   struct gif_scalar_info gsi;
970
971   i_clear_error();
972
973   gsi.cpos=0;
974   gsi.length=length;
975   gsi.data=data;
976
977   mm_log((1,"i_readgif_scalar(char* data, int length, colour_table %p, colours %p)\n", data, length, colour_table, colours));
978   if ((GifFile = DGifOpen( (void*) &gsi, my_gif_inputfunc )) == NULL) {
979     gif_push_error();
980     i_push_error(0, "Cannot create giflib callback object");
981     mm_log((1,"i_readgif_scalar: Unable to open scalar datasource.\n"));
982     return NULL;
983   }
984
985   return i_readgif_low(GifFile, colour_table, colours);
986 #else
987   return NULL;
988 #endif
989 }
990
991 #if IM_GIFMAJOR >= 4
992
993 /*
994 =item gif_read_callback(GifFileType *gft, GifByteType *buf, int length)
995
996 Internal.  The reader callback wrapper passed to giflib.
997
998 This function is only used with giflib 4 and higher.
999
1000 =cut
1001 */
1002
1003 static int
1004 gif_read_callback(GifFileType *gft, GifByteType *buf, int length) {
1005   return i_gen_reader((i_gen_read_data *)gft->UserData, (char*)buf, length);
1006 }
1007
1008 #endif
1009
1010
1011 /*
1012 =item i_readgif_callback(i_read_callback_t cb, char *userdata, int **colour_table, int *colours)
1013
1014 Read a GIF file into an Imager RGB file, the data of the GIF file is
1015 retreived by callin the user supplied callback function.
1016
1017 This function is only used with giflib 4 and higher.
1018
1019 =cut
1020 */
1021
1022 i_img*
1023 i_readgif_callback(i_read_callback_t cb, char *userdata, int **colour_table, int *colours) {
1024 #if IM_GIFMAJOR >= 4
1025   GifFileType *GifFile;
1026   i_img *result;
1027
1028   i_gen_read_data *gci = i_gen_read_data_new(cb, userdata);
1029
1030   i_clear_error();
1031   
1032   mm_log((1,"i_readgif_callback(callback %p, userdata %p, colour_table %p, colours %p)\n", cb, userdata, colour_table, colours));
1033   if ((GifFile = DGifOpen( (void*) gci, gif_read_callback )) == NULL) {
1034     gif_push_error();
1035     i_push_error(0, "Cannot create giflib callback object");
1036     mm_log((1,"i_readgif_callback: Unable to open callback datasource.\n"));
1037     myfree(gci);
1038     return NULL;
1039   }
1040
1041   result = i_readgif_low(GifFile, colour_table, colours);
1042   i_free_gen_read_data(gci);
1043
1044   return result;
1045 #else
1046   i_clear_error();
1047   i_push_error(0, "callbacks not supported with giflib3");
1048
1049   return NULL;
1050 #endif
1051 }
1052
1053 #if IM_GIFMAJOR >= 4
1054
1055 static int
1056 io_glue_read_cb(GifFileType *gft, GifByteType *buf, int length) {
1057   io_glue *ig = (io_glue *)gft->UserData;
1058
1059   return ig->readcb(ig, buf, length);
1060 }
1061
1062 #endif
1063
1064 i_img *
1065 i_readgif_wiol(io_glue *ig, int **color_table, int *colors) {
1066   io_glue_commit_types(ig);
1067
1068   if (ig->source.type == FDSEEK || ig->source.type == FDNOSEEK) {
1069     int fd = dup(ig->source.fdseek.fd);
1070     if (fd < 0) {
1071       i_push_error(errno, "dup() failed");
1072       return 0;
1073     }
1074     return i_readgif(fd, color_table, colors);
1075   }
1076   else {
1077 #if IM_GIFMAJOR >= 4
1078     GifFileType *GifFile;
1079
1080     i_clear_error();
1081
1082     if ((GifFile = DGifOpen((void *)ig, io_glue_read_cb )) == NULL) {
1083       gif_push_error();
1084       i_push_error(0, "Cannot create giflib callback object");
1085       mm_log((1,"i_readgif_wiol: Unable to open callback datasource.\n"));
1086       return NULL;
1087     }
1088     
1089     return i_readgif_low(GifFile, color_table, colors);
1090   
1091 #else
1092   i_clear_error();
1093   i_push_error(0, "callbacks not supported with giflib3");
1094
1095   return NULL;
1096 #endif
1097   }
1098 }
1099
1100 /*
1101 =item do_write(GifFileType *gf, i_gif_opts *opts, i_img *img, i_palidx *data)
1102
1103 Internal.  Low level image write function.  Writes in interlace if
1104 that was requested in the GIF options.
1105
1106 Returns non-zero on success.
1107
1108 =cut
1109 */
1110 static undef_int 
1111 do_write(GifFileType *gf, int interlace, i_img *img, i_palidx *data) {
1112   if (interlace) {
1113     int i, j;
1114     for (i = 0; i < 4; ++i) {
1115       for (j = InterlacedOffset[i]; j < img->ysize; j += InterlacedJumps[i]) {
1116         if (EGifPutLine(gf, data+j*img->xsize, img->xsize) == GIF_ERROR) {
1117           gif_push_error();
1118           i_push_error(0, "Could not save image data:");
1119           mm_log((1, "Error in EGifPutLine\n"));
1120           EGifCloseFile(gf);
1121           return 0;
1122         }
1123       }
1124     }
1125   }
1126   else {
1127     int y;
1128     for (y = 0; y < img->ysize; ++y) {
1129       if (EGifPutLine(gf, data, img->xsize) == GIF_ERROR) {
1130         gif_push_error();
1131         i_push_error(0, "Could not save image data:");
1132         mm_log((1, "Error in EGifPutLine\n"));
1133         EGifCloseFile(gf);
1134         return 0;
1135       }
1136       data += img->xsize;
1137     }
1138   }
1139
1140   return 1;
1141 }
1142
1143 /*
1144 =item do_gce(GifFileType *gf, int index, i_gif_opts *opts, int want_trans, int trans_index)
1145
1146 Internal. Writes the GIF graphics control extension, if necessary.
1147
1148 Returns non-zero on success.
1149
1150 =cut
1151 */
1152 static int do_gce(GifFileType *gf, i_img *img, int want_trans, int trans_index)
1153 {
1154   unsigned char gce[4] = {0};
1155   int want_gce = 0;
1156   int delay;
1157   int user_input;
1158   int disposal_method;
1159
1160   if (want_trans) {
1161     gce[0] |= 1;
1162     gce[3] = trans_index;
1163     ++want_gce;
1164   }
1165   if (i_tags_get_int(&img->tags, "gif_delay", 0, &delay)) {
1166     gce[1] = delay % 256;
1167     gce[2] = delay / 256;
1168     ++want_gce;
1169   }
1170   if (i_tags_get_int(&img->tags, "gif_user_input", 0, &user_input) 
1171       && user_input) {
1172     gce[0] |= 2;
1173     ++want_gce;
1174   }
1175   if (i_tags_get_int(&img->tags, "gif_disposal", 0, &disposal_method)) {
1176     gce[0] |= (disposal_method & 3) << 2;
1177     ++want_gce;
1178   }
1179   if (want_gce) {
1180     if (EGifPutExtension(gf, 0xF9, sizeof(gce), gce) == GIF_ERROR) {
1181       gif_push_error();
1182       i_push_error(0, "Could not save GCE");
1183     }
1184   }
1185   return 1;
1186 }
1187
1188 /*
1189 =item do_comments(gf, img)
1190
1191 Write any comments in the image.
1192
1193 =cut
1194 */
1195 static int do_comments(GifFileType *gf, i_img *img) {
1196   int pos = -1;
1197
1198   while (i_tags_find(&img->tags, "gif_comment", pos+1, &pos)) {
1199     if (img->tags.tags[pos].data) {
1200       if (EGifPutComment(gf, img->tags.tags[pos].data) == GIF_ERROR) {
1201         return 0;
1202       }
1203     }
1204     else {
1205       char buf[50];
1206       sprintf(buf, "%d", img->tags.tags[pos].idata);
1207       if (EGifPutComment(gf, buf) == GIF_ERROR) {
1208         return 0;
1209       }
1210     }
1211   }
1212
1213   return 1;
1214 }
1215
1216 /*
1217 =item do_ns_loop(GifFileType *gf, i_gif_opts *opts)
1218
1219 Internal.  Add the Netscape2.0 loop extension block, if requested.
1220
1221 The code for this function is currently "#if 0"ed out since the giflib
1222 extension writing code currently doesn't seem to support writing
1223 application extension blocks.
1224
1225 =cut
1226 */
1227 static int do_ns_loop(GifFileType *gf, i_img *img)
1228 {
1229   /* EGifPutExtension() doesn't appear to handle application 
1230      extension blocks in any way
1231      Since giflib wraps the fd with a FILE * (and puts that in its
1232      private data), we can't do an end-run and write the data 
1233      directly to the fd.
1234      There's no open interface that takes a FILE * either, so we 
1235      can't workaround it that way either.
1236      If giflib's callback interface wasn't broken by default, I'd 
1237      force file writes to use callbacks, but it is broken by default.
1238   */
1239 #if 0
1240   /* yes this was another attempt at supporting the loop extension */
1241   int loop_count;
1242   if (i_tags_get_int(&img->tags, "gif_loop", 0, &loop_count)) {
1243     unsigned char nsle[12] = "NETSCAPE2.0";
1244     unsigned char subblock[3];
1245     if (EGifPutExtension(gf, 0xFF, 11, nsle) == GIF_ERROR) {
1246       gif_push_error();
1247       i_push_error(0, "writing loop extension");
1248       return 0;
1249     }
1250     subblock[0] = 1;
1251     subblock[1] = loop_count % 256;
1252     subblock[2] = loop_count / 256;
1253     if (EGifPutExtension(gf, 0, 3, subblock) == GIF_ERROR) {
1254       gif_push_error();
1255       i_push_error(0, "writing loop extention sub-block");
1256       return 0;
1257     }
1258     if (EGifPutExtension(gf, 0, 0, subblock) == GIF_ERROR) {
1259       gif_push_error();
1260       i_push_error(0, "writing loop extension terminator");
1261       return 0;
1262     }
1263   }
1264 #endif
1265   return 1;
1266 }
1267
1268 /*
1269 =item make_gif_map(i_quantize *quant, int want_trans)
1270
1271 Create a giflib color map object from an Imager color map.
1272
1273 =cut
1274 */
1275
1276 static ColorMapObject *make_gif_map(i_quantize *quant, i_img *img, 
1277                                     int want_trans) {
1278   GifColorType colors[256];
1279   int i;
1280   int size = quant->mc_count;
1281   int map_size;
1282   ColorMapObject *map;
1283   i_color trans;
1284
1285   for (i = 0; i < quant->mc_count; ++i) {
1286     colors[i].Red = quant->mc_colors[i].rgb.r;
1287     colors[i].Green = quant->mc_colors[i].rgb.g;
1288     colors[i].Blue = quant->mc_colors[i].rgb.b;
1289   }
1290   if (want_trans) {
1291     if (!i_tags_get_color(&img->tags, "gif_trans_color", 0, &trans))
1292       trans.rgb.r = trans.rgb.g = trans.rgb.b = 0;
1293     colors[size].Red = trans.rgb.r;
1294     colors[size].Green = trans.rgb.g;
1295     colors[size].Blue = trans.rgb.b;
1296     ++size;
1297   }
1298   map_size = 1;
1299   while (map_size < size)
1300     map_size <<= 1;
1301   /* giflib spews for 1 colour maps, reasonable, I suppose */
1302   if (map_size == 1)
1303     map_size = 2;
1304   while (i < map_size) {
1305     colors[i].Red = colors[i].Green = colors[i].Blue = 0;
1306     ++i;
1307   }
1308   
1309   map = MakeMapObject(map_size, colors);
1310   mm_log((1, "XXX map is at %p and colors at %p\n", map, map->Colors));
1311   if (!map) {
1312     gif_push_error();
1313     i_push_error(0, "Could not create color map object");
1314     return NULL;
1315   }
1316   return map;
1317 }
1318
1319 /*
1320 =item gif_set_version(i_quantize *quant, i_img *imgs, int count)
1321
1322 We need to call EGifSetGifVersion() before opening the file - put that
1323 common code here.
1324
1325 Unfortunately giflib 4.1.0 crashes when we use this.  Internally
1326 giflib 4.1.0 has code:
1327
1328   static char *GifVersionPrefix = GIF87_STAMP;
1329
1330 and the code that sets the version internally does:
1331
1332   strncpy(&GifVersionPrefix[3], Version, 3);
1333
1334 which is very broken.
1335
1336 Failing to set the correct GIF version doesn't seem to cause a problem
1337 with readers.
1338
1339 =cut
1340 */
1341
1342 static void gif_set_version(i_quantize *quant, i_img **imgs, int count) {
1343   /* the following crashed giflib
1344      the EGifSetGifVersion() is seriously borked in giflib
1345      it's less borked in the ungiflib beta, but we don't have a mechanism
1346      to distinguish them
1347      Needs to be updated to support tags.
1348      if (opts->delay_count
1349      || opts->user_input_count
1350      || opts->disposal_count
1351      || opts->loop_count
1352      || quant->transp != tr_none)
1353      EGifSetGifVersion("89a");
1354      else
1355      EGifSetGifVersion("87a");
1356   */
1357 }
1358
1359 static int 
1360 in_palette(i_color *c, i_quantize *quant, int size) {
1361   int i;
1362
1363   for (i = 0; i < size; ++i) {
1364     if (c->channel[0] == quant->mc_colors[i].channel[0]
1365         && c->channel[1] == quant->mc_colors[i].channel[1]
1366         && c->channel[2] == quant->mc_colors[i].channel[2]) {
1367       return i;
1368     }
1369   }
1370
1371   return -1;
1372 }
1373
1374 /*
1375 =item has_common_palette(imgs, count, quant, want_trans)
1376
1377 Tests if all the given images are paletted and have a common palette,
1378 if they do it builds that palette.
1379
1380 A possible improvement might be to eliminate unused colors in the
1381 images palettes.
1382
1383 =cut
1384 */
1385 static int
1386 has_common_palette(i_img **imgs, int count, i_quantize *quant, 
1387                    int want_trans) {
1388   int size = quant->mc_count;
1389   int i, j;
1390   int imgn;
1391   int x, y;
1392   char used[256];
1393
1394   /* we try to build a common palette here, if we can manage that, then
1395      that's the palette we use */
1396   for (imgn = 0; imgn < count; ++imgn) {
1397     int eliminate_unused;
1398     if (imgs[imgn]->type != i_palette_type)
1399       return 0;
1400
1401     if (!i_tags_get_int(&imgs[imgn]->tags, "gif_eliminate_unused", 0, 
1402                         &eliminate_unused)) {
1403       eliminate_unused = 1;
1404     }
1405
1406     if (eliminate_unused) {
1407       i_palidx *line = mymalloc(sizeof(i_palidx) * imgs[imgn]->xsize);
1408       int x, y;
1409       memset(used, 0, sizeof(used));
1410
1411       for (y = 0; y < imgs[imgn]->ysize; ++y) {
1412         i_gpal(imgs[imgn], 0, imgs[imgn]->xsize, y, line);
1413         for (x = 0; x < imgs[imgn]->xsize; ++x)
1414           used[line[x]] = 1;
1415       }
1416
1417       myfree(line);
1418     }
1419     else {
1420       /* assume all are in use */
1421       memset(used, 1, sizeof(used));
1422     }
1423
1424     for (i = 0; i < i_colorcount(imgs[imgn]); ++i) {
1425       i_color c;
1426       
1427       i_getcolors(imgs[imgn], i, &c, 1);
1428       if (used[i]) {
1429         if (in_palette(&c, quant, size) < 0) {
1430           if (size < quant->mc_size) {
1431             quant->mc_colors[size++] = c;
1432           }
1433           else {
1434             /* oops, too many colors */
1435             return 0;
1436           }
1437         }
1438       }
1439     }
1440   }
1441
1442   quant->mc_count = size;
1443
1444   return 1;
1445 }
1446
1447 static i_palidx *
1448 quant_paletted(i_quantize *quant, i_img *img) {
1449   i_palidx *data = mymalloc(sizeof(i_palidx) * img->xsize * img->ysize);
1450   i_palidx *p = data;
1451   i_palidx trans[256];
1452   int i;
1453   int x, y;
1454
1455   /* build a translation table */
1456   for (i = 0; i < i_colorcount(img); ++i) {
1457     i_color c;
1458     i_getcolors(img, i, &c, 1);
1459     trans[i] = in_palette(&c, quant, quant->mc_count);
1460   }
1461
1462   for (y = 0; y < img->ysize; ++y) {
1463     i_gpal(img, 0, img->xsize, y, data+img->xsize * y);
1464     for (x = 0; x < img->xsize; ++x) {
1465       *p = trans[*p];
1466       ++p;
1467     }
1468   }
1469
1470   return data;
1471 }
1472
1473 /*
1474 =item i_writegif_low(i_quantize *quant, GifFileType *gf, i_img **imgs, int count, i_gif_opts *opts)
1475
1476 Internal.  Low-level function that does the high-level GIF processing
1477 :)
1478
1479 Returns non-zero on success.
1480
1481 =cut
1482 */
1483
1484 static undef_int
1485 i_writegif_low(i_quantize *quant, GifFileType *gf, i_img **imgs, int count) {
1486   unsigned char *result;
1487   int color_bits;
1488   ColorMapObject *map;
1489   int scrw = 0, scrh = 0;
1490   int imgn, orig_count, orig_size;
1491   int posx, posy;
1492   int trans_index;
1493   i_mempool mp;
1494   int *localmaps;
1495   int anylocal;
1496   i_img **glob_imgs; /* images that will use the global color map */
1497   int glob_img_count;
1498   i_color *orig_colors = quant->mc_colors;
1499   i_color *glob_colors = NULL;
1500   int glob_color_count;
1501   int glob_map_size;
1502   int glob_want_trans;
1503   int glob_paletted; /* the global map was made from the image palettes */
1504   int colors_paletted;
1505   int want_trans;
1506   int interlace;
1507   int gif_background;
1508
1509   mm_log((1, "i_writegif_low(quant %p, gf  %p, imgs %p, count %d)\n", 
1510           quant, gf, imgs, count));
1511   
1512   /* *((char *)0) = 1; */ /* used to break into the debugger */
1513   
1514   if (count <= 0) {
1515     i_push_error(0, "No images provided to write");
1516     return 0; /* what are you smoking? */
1517   }
1518
1519   i_mempool_init(&mp);
1520
1521   /* sanity is nice */
1522   if (quant->mc_size > 256) 
1523     quant->mc_size = 256;
1524   if (quant->mc_count > quant->mc_size)
1525     quant->mc_count = quant->mc_size;
1526
1527   if (!i_tags_get_int(&imgs[0]->tags, "gif_screen_width", 0, &scrw))
1528     scrw = 0;
1529   if (!i_tags_get_int(&imgs[0]->tags, "gif_screen_height", 0, &scrw))
1530     scrw = 0;
1531
1532   anylocal = 0;
1533   localmaps = i_mempool_alloc(&mp, sizeof(int) * count);
1534   glob_imgs = i_mempool_alloc(&mp, sizeof(i_img *) * count);
1535   glob_img_count = 0;
1536   glob_want_trans = 0;
1537   for (imgn = 0; imgn < count; ++imgn) {
1538     posx = posy = 0;
1539     i_tags_get_int(&imgs[imgn]->tags, "gif_left", 0, &posx);
1540     i_tags_get_int(&imgs[imgn]->tags, "gif_top", 0, &posy);
1541     if (imgs[imgn]->xsize + posx > scrw)
1542       scrw = imgs[imgn]->xsize + posx;
1543     if (imgs[imgn]->ysize + posy > scrh)
1544       scrh = imgs[imgn]->ysize + posy;
1545     if (!i_tags_get_int(&imgs[imgn]->tags, "gif_local_map", 0, localmaps+imgn))
1546       localmaps[imgn] = 0;
1547     if (localmaps[imgn])
1548       anylocal = 1;
1549     else {
1550       if (imgs[imgn]->channels == 4) {
1551         glob_want_trans = 1;
1552       }
1553       glob_imgs[glob_img_count++] = imgs[imgn];
1554     }
1555   }
1556   glob_want_trans = glob_want_trans && quant->transp != tr_none ;
1557
1558   orig_count = quant->mc_count;
1559   orig_size = quant->mc_size;
1560
1561   if (glob_img_count) {
1562     /* this is ugly */
1563     glob_colors = i_mempool_alloc(&mp, sizeof(i_color) * quant->mc_size);
1564     quant->mc_colors = glob_colors;
1565     memcpy(glob_colors, orig_colors, sizeof(i_color) * quant->mc_count);
1566     /* we have some images that want to use the global map */
1567     if (glob_want_trans && quant->mc_count == 256) {
1568       mm_log((2, "  disabling transparency for global map - no space\n"));
1569       glob_want_trans = 0;
1570     }
1571     if (glob_want_trans && quant->mc_size == 256) {
1572       mm_log((2, "  reserving color for transparency\n"));
1573       --quant->mc_size;
1574     }
1575     if (has_common_palette(glob_imgs, glob_img_count, quant, want_trans)) {
1576       glob_paletted = 1;
1577     }
1578     else {
1579       glob_paletted = 0;
1580       quant_makemap(quant, glob_imgs, glob_img_count);
1581     }
1582     glob_color_count = quant->mc_count;
1583     quant->mc_colors = orig_colors;
1584   }
1585
1586   /* use the global map if we have one, otherwise use the local map */
1587   gif_background = 0;
1588   if (glob_colors) {
1589     quant->mc_colors = glob_colors;
1590     quant->mc_count = glob_color_count;
1591     want_trans = glob_want_trans && imgs[0]->channels == 4;
1592
1593     if (!i_tags_get_int(&imgs[0]->tags, "gif_background", 0, &gif_background))
1594       gif_background = 0;
1595     if (gif_background < 0)
1596       gif_background = 0;
1597     if (gif_background >= glob_color_count)
1598       gif_background = 0;
1599   }
1600   else {
1601     want_trans = quant->transp != tr_none && imgs[0]->channels == 4;
1602     if (has_common_palette(imgs, 1, quant, want_trans)) {
1603       colors_paletted = 1;
1604     }
1605     else {
1606       colors_paletted = 0;
1607       quant_makemap(quant, imgs, 1);
1608     }
1609   }
1610   if ((map = make_gif_map(quant, imgs[0], want_trans)) == NULL) {
1611     i_mempool_destroy(&mp);
1612     quant->mc_colors = orig_colors;
1613     EGifCloseFile(gf);
1614     mm_log((1, "Error in MakeMapObject"));
1615     return 0;
1616   }
1617   color_bits = 1;
1618   if (anylocal) {
1619     /* since we don't know how big some the local palettes could be
1620        we need to base the bits on the maximum number of colors */
1621     while (orig_size > (1 << color_bits))
1622       ++color_bits;
1623   }
1624   else {
1625     int count = quant->mc_count;
1626     if (want_trans)
1627       ++count;
1628     while (count > (1 << color_bits))
1629       ++color_bits;
1630   }
1631   
1632   if (EGifPutScreenDesc(gf, scrw, scrh, color_bits, 
1633                         gif_background, map) == GIF_ERROR) {
1634     i_mempool_destroy(&mp);
1635     quant->mc_colors = orig_colors;
1636     gif_push_error();
1637     i_push_error(0, "Could not save screen descriptor");
1638     FreeMapObject(map);
1639     myfree(result);
1640     EGifCloseFile(gf);
1641     mm_log((1, "Error in EGifPutScreenDesc."));
1642     return 0;
1643   }
1644   FreeMapObject(map);
1645
1646   if (!i_tags_get_int(&imgs[0]->tags, "gif_left", 0, &posx))
1647     posx = 0;
1648   if (!i_tags_get_int(&imgs[0]->tags, "gif_top", 0, &posy))
1649     posy = 0;
1650
1651   if (!localmaps[0]) {
1652     map = NULL;
1653     colors_paletted = glob_paletted;
1654   }
1655   else {
1656     /* if this image has a global map the colors in quant don't
1657        belong to this image, so build a palette */
1658     if (glob_colors) {
1659       /* generate the local map for this image */
1660       quant->mc_colors = orig_colors;
1661       quant->mc_size = orig_size;
1662       quant->mc_count = orig_count;
1663       want_trans = quant->transp != tr_none && imgs[0]->channels == 4;
1664
1665       /* if the caller gives us too many colours we can't do transparency */
1666       if (want_trans && quant->mc_count == 256)
1667         want_trans = 0;
1668       /* if they want transparency but give us a big size, make it smaller
1669          to give room for a transparency colour */
1670       if (want_trans && quant->mc_size == 256)
1671         --quant->mc_size;
1672       if (has_common_palette(imgs, 1, quant, want_trans)) {
1673         colors_paletted = 1;
1674       }
1675       else {
1676         colors_paletted = 0;
1677         quant_makemap(quant, imgs, 1);
1678       }
1679       if ((map = make_gif_map(quant, imgs[0], want_trans)) == NULL) {
1680         i_mempool_destroy(&mp);
1681         EGifCloseFile(gf);
1682         quant->mc_colors = orig_colors;
1683         mm_log((1, "Error in MakeMapObject"));
1684         return 0;
1685       }
1686     }
1687     else {
1688       /* the map we wrote was the map for this image - don't set the local 
1689          map */
1690       map = NULL;
1691     }
1692   }
1693
1694   if (colors_paletted)
1695     result = quant_paletted(quant, imgs[0]);
1696   else
1697     result = quant_translate(quant, imgs[0]);
1698   if (!result) {
1699     i_mempool_destroy(&mp);
1700     quant->mc_colors = orig_colors;
1701     EGifCloseFile(gf);
1702     return 0;
1703   }
1704   if (want_trans) {
1705     quant_transparent(quant, result, imgs[0], quant->mc_count);
1706     trans_index = quant->mc_count;
1707   }
1708
1709   if (!do_ns_loop(gf, imgs[0])) {
1710     i_mempool_destroy(&mp);
1711     quant->mc_colors = orig_colors;
1712     return 0;
1713   }
1714
1715   if (!do_gce(gf, imgs[0], want_trans, trans_index)) {
1716     i_mempool_destroy(&mp);
1717     quant->mc_colors = orig_colors;
1718     myfree(result);
1719     EGifCloseFile(gf);
1720     return 0;
1721   }
1722
1723   if (!do_comments(gf, imgs[0])) {
1724     i_mempool_destroy(&mp);
1725     quant->mc_colors = orig_colors;
1726     myfree(result);
1727     EGifCloseFile(gf);
1728     return 0;
1729   }
1730
1731   if (!i_tags_get_int(&imgs[0]->tags, "gif_interlace", 0, &interlace))
1732     interlace = 0;
1733   if (EGifPutImageDesc(gf, posx, posy, imgs[0]->xsize, imgs[0]->ysize, 
1734                        interlace, map) == GIF_ERROR) {
1735     i_mempool_destroy(&mp);
1736     quant->mc_colors = orig_colors;
1737     gif_push_error();
1738     i_push_error(0, "Could not save image descriptor");
1739     EGifCloseFile(gf);
1740     mm_log((1, "Error in EGifPutImageDesc."));
1741     return 0;
1742   }
1743   if (map)
1744     FreeMapObject(map);
1745
1746   if (!do_write(gf, interlace, imgs[0], result)) {
1747     i_mempool_destroy(&mp);
1748     quant->mc_colors = orig_colors;
1749     EGifCloseFile(gf);
1750     myfree(result);
1751     return 0;
1752   }
1753   myfree(result);
1754
1755   /* that first awful image is out of the way, do the rest */
1756   for (imgn = 1; imgn < count; ++imgn) {
1757     if (localmaps[imgn]) {
1758       quant->mc_colors = orig_colors;
1759       quant->mc_count = orig_count;
1760       quant->mc_size = orig_size;
1761
1762       want_trans = quant->transp != tr_none 
1763         && imgs[imgn]->channels == 4;
1764       /* if the caller gives us too many colours we can't do transparency */
1765       if (want_trans && quant->mc_count == 256)
1766         want_trans = 0;
1767       /* if they want transparency but give us a big size, make it smaller
1768          to give room for a transparency colour */
1769       if (want_trans && quant->mc_size == 256)
1770         --quant->mc_size;
1771
1772       if (has_common_palette(imgs+imgn, 1, quant, want_trans)) {
1773         result = quant_paletted(quant, imgs[imgn]);
1774       }
1775       else {
1776         quant_makemap(quant, imgs+imgn, 1);
1777         result = quant_translate(quant, imgs[imgn]);
1778       }
1779       if (!result) {
1780         i_mempool_destroy(&mp);
1781         quant->mc_colors = orig_colors;
1782         EGifCloseFile(gf);
1783         mm_log((1, "error in quant_translate()"));
1784         return 0;
1785       }
1786       if (want_trans) {
1787         quant_transparent(quant, result, imgs[imgn], quant->mc_count);
1788         trans_index = quant->mc_count;
1789       }
1790
1791       if ((map = make_gif_map(quant, imgs[imgn], want_trans)) == NULL) {
1792         i_mempool_destroy(&mp);
1793         quant->mc_colors = orig_colors;
1794         myfree(result);
1795         EGifCloseFile(gf);
1796         mm_log((1, "Error in MakeMapObject."));
1797         return 0;
1798       }
1799     }
1800     else {
1801       quant->mc_colors = glob_colors;
1802       quant->mc_count = glob_color_count;
1803       if (glob_paletted)
1804         result = quant_paletted(quant, imgs[imgn]);
1805       else
1806         result = quant_translate(quant, imgs[imgn]);
1807       want_trans = glob_want_trans && imgs[imgn]->channels == 4;
1808       if (want_trans) {
1809         quant_transparent(quant, result, imgs[imgn], quant->mc_count);
1810         trans_index = quant->mc_count;
1811       }
1812       map = NULL;
1813     }
1814
1815     if (!do_gce(gf, imgs[imgn], want_trans, trans_index)) {
1816       i_mempool_destroy(&mp);
1817       quant->mc_colors = orig_colors;
1818       myfree(result);
1819       EGifCloseFile(gf);
1820       return 0;
1821     }
1822
1823     if (!do_comments(gf, imgs[imgn])) {
1824       i_mempool_destroy(&mp);
1825       quant->mc_colors = orig_colors;
1826       myfree(result);
1827       EGifCloseFile(gf);
1828       return 0;
1829     }
1830
1831     if (!i_tags_get_int(&imgs[imgn]->tags, "gif_left", 0, &posx))
1832       posx = 0;
1833     if (!i_tags_get_int(&imgs[imgn]->tags, "gif_top", 0, &posy))
1834       posy = 0;
1835
1836     if (!i_tags_get_int(&imgs[imgn]->tags, "gif_interlace", 0, &interlace))
1837       interlace = 0;
1838     if (EGifPutImageDesc(gf, posx, posy, imgs[imgn]->xsize, 
1839                          imgs[imgn]->ysize, interlace, map) == GIF_ERROR) {
1840       i_mempool_destroy(&mp);
1841       quant->mc_colors = orig_colors;
1842       gif_push_error();
1843       i_push_error(0, "Could not save image descriptor");
1844       myfree(result);
1845       if (map)
1846         FreeMapObject(map);
1847       EGifCloseFile(gf);
1848       mm_log((1, "Error in EGifPutImageDesc."));
1849       return 0;
1850     }
1851     if (map)
1852       FreeMapObject(map);
1853     
1854     if (!do_write(gf, interlace, imgs[imgn], result)) {
1855       i_mempool_destroy(&mp);
1856       quant->mc_colors = orig_colors;
1857       EGifCloseFile(gf);
1858       myfree(result);
1859       return 0;
1860     }
1861     myfree(result);
1862   }
1863
1864   if (EGifCloseFile(gf) == GIF_ERROR) {
1865     i_mempool_destroy(&mp);
1866     gif_push_error();
1867     i_push_error(0, "Could not close GIF file");
1868     mm_log((1, "Error in EGifCloseFile\n"));
1869     return 0;
1870   }
1871   if (glob_colors) {
1872     int i;
1873     for (i = 0; i < glob_color_count; ++i)
1874       orig_colors[i] = glob_colors[i];
1875   }
1876
1877   i_mempool_destroy(&mp);
1878   quant->mc_colors = orig_colors;
1879
1880   return 1;
1881 }
1882
1883 /*
1884 =item i_writegif_gen(i_quantize *quant, int fd, i_img **imgs, int count, i_gif_opts *opts)
1885
1886 General high-level function to write a GIF to a file.
1887
1888 Writes the GIF images to the specified file handle using the options
1889 in quant and opts.  See L<image.h/i_quantize> and
1890 L<image.h/i_gif_opts>.
1891
1892 Returns non-zero on success.
1893
1894 =cut
1895 */
1896
1897 undef_int
1898 i_writegif_gen(i_quantize *quant, int fd, i_img **imgs, int count) {
1899   GifFileType *gf;
1900
1901   i_clear_error();
1902   mm_log((1, "i_writegif_gen(quant %p, fd %d, imgs %p, count %d)\n", 
1903           quant, fd, imgs, count));
1904
1905   gif_set_version(quant, imgs, count);
1906
1907   if ((gf = EGifOpenFileHandle(fd)) == NULL) {
1908     gif_push_error();
1909     i_push_error(0, "Cannot create GIF file object");
1910     mm_log((1, "Error in EGifOpenFileHandle, unable to write image.\n"));
1911     return 0;
1912   }
1913
1914   return i_writegif_low(quant, gf, imgs, count);
1915 }
1916
1917 #if IM_GIFMAJOR >= 4
1918
1919 /*
1920 =item gif_writer_callback(GifFileType *gf, const GifByteType *data, int size)
1921
1922 Internal.  Wrapper for the user write callback function.
1923
1924 =cut
1925 */
1926
1927 static int gif_writer_callback(GifFileType *gf, const GifByteType *data, int size)
1928 {
1929   i_gen_write_data *gwd = (i_gen_write_data *)gf->UserData;
1930
1931   return i_gen_writer(gwd, (char*)data, size) ? size : 0;
1932 }
1933
1934 #endif
1935
1936 /*
1937 =item i_writegif_callback(i_quantize *quant, i_write_callback_t cb, char *userdata, int maxlength, i_img **imgs, int count, i_gif_opts *opts)
1938
1939 General high-level function to write a GIF using callbacks to send
1940 back the data.
1941
1942 Returns non-zero on success.
1943
1944 =cut
1945 */
1946
1947 undef_int
1948 i_writegif_callback(i_quantize *quant, i_write_callback_t cb, char *userdata,
1949                     int maxlength, i_img **imgs, int count)
1950 {
1951 #if IM_GIFMAJOR >= 4
1952   GifFileType *gf;
1953   i_gen_write_data *gwd = i_gen_write_data_new(cb, userdata, maxlength);
1954   int result;
1955
1956   i_clear_error();
1957
1958   mm_log((1, "i_writegif_callback(quant %p, i_write_callback_t %p, userdata $p, maxlength %d, imgs %p, count %d)\n", 
1959           quant, cb, userdata, maxlength, imgs, count));
1960   
1961   if ((gf = EGifOpen(gwd, &gif_writer_callback)) == NULL) {
1962     gif_push_error();
1963     i_push_error(0, "Cannot create GIF file object");
1964     mm_log((1, "Error in EGifOpenFileHandle, unable to write image.\n"));
1965     i_free_gen_write_data(gwd, 0);
1966     return 0;
1967   }
1968
1969   result = i_writegif_low(quant, gf, imgs, count);
1970   return i_free_gen_write_data(gwd, result);
1971 #else
1972   i_clear_error();
1973   i_push_error(0, "callbacks not supported with giflib3");
1974
1975   return 0;
1976 #endif
1977 }
1978
1979 #if IM_GIFMAJOR >= 4
1980
1981 static int
1982 io_glue_write_cb(GifFileType *gft, const GifByteType *data, int length) {
1983   io_glue *ig = (io_glue *)gft->UserData;
1984
1985   return ig->writecb(ig, data, length);
1986 }
1987
1988 #endif
1989
1990 /*
1991 =item i_writegif_wiol(ig, quant, opts, imgs, count)
1992
1993 =cut
1994 */
1995 undef_int
1996 i_writegif_wiol(io_glue *ig, i_quantize *quant, i_img **imgs,
1997                 int count) {
1998   io_glue_commit_types(ig);
1999
2000   if (ig->source.type == FDSEEK || ig->source.type == FDNOSEEK) {
2001     int fd = dup(ig->source.fdseek.fd);
2002     if (fd < 0) {
2003       i_push_error(errno, "dup() failed");
2004       return 0;
2005     }
2006     /* giflib opens the fd with fdopen(), which is then closed when fclose()
2007        is called - dup it so the caller's fd isn't closed */
2008     return i_writegif_gen(quant, fd, imgs, count);
2009   }
2010   else {
2011 #if IM_GIFMAJOR >= 4
2012     GifFileType *GifFile;
2013     int result;
2014
2015     i_clear_error();
2016
2017     gif_set_version(quant, imgs, count);
2018
2019     if ((GifFile = EGifOpen((void *)ig, io_glue_write_cb )) == NULL) {
2020       gif_push_error();
2021       i_push_error(0, "Cannot create giflib callback object");
2022       mm_log((1,"i_writegif_wiol: Unable to open callback datasource.\n"));
2023       return 0;
2024     }
2025     
2026     result = i_writegif_low(quant, GifFile, imgs, count);
2027     
2028     ig->closecb(ig);
2029
2030     return result;
2031 #else
2032     i_clear_error();
2033     i_push_error(0, "callbacks not supported with giflib3");
2034     
2035     return 0;
2036 #endif
2037   }
2038 }
2039
2040 /*
2041 =item gif_error_msg(int code)
2042
2043 Grabs the most recent giflib error code from GifLastError() and 
2044 returns a string that describes that error.
2045
2046 The returned pointer points to a static buffer, either from a literal
2047 C string or a static buffer.
2048
2049 =cut
2050 */
2051
2052 static char const *gif_error_msg(int code) {
2053   static char msg[80];
2054
2055   switch (code) {
2056   case E_GIF_ERR_OPEN_FAILED: /* should not see this */
2057     return "Failed to open given file";
2058     
2059   case E_GIF_ERR_WRITE_FAILED:
2060     return "Write failed";
2061
2062   case E_GIF_ERR_HAS_SCRN_DSCR: /* should not see this */
2063     return "Screen descriptor already passed to giflib";
2064
2065   case E_GIF_ERR_HAS_IMAG_DSCR: /* should not see this */
2066     return "Image descriptor already passed to giflib";
2067     
2068   case E_GIF_ERR_NO_COLOR_MAP: /* should not see this */
2069     return "Neither global nor local color map set";
2070
2071   case E_GIF_ERR_DATA_TOO_BIG: /* should not see this */
2072     return "Too much pixel data passed to giflib";
2073
2074   case E_GIF_ERR_NOT_ENOUGH_MEM:
2075     return "Out of memory";
2076     
2077   case E_GIF_ERR_DISK_IS_FULL:
2078     return "Disk is full";
2079     
2080   case E_GIF_ERR_CLOSE_FAILED: /* should not see this */
2081     return "File close failed";
2082  
2083   case E_GIF_ERR_NOT_WRITEABLE: /* should not see this */
2084     return "File not writable";
2085
2086   case D_GIF_ERR_OPEN_FAILED:
2087     return "Failed to open file";
2088     
2089   case D_GIF_ERR_READ_FAILED:
2090     return "Failed to read from file";
2091
2092   case D_GIF_ERR_NOT_GIF_FILE:
2093     return "File is not a GIF file";
2094
2095   case D_GIF_ERR_NO_SCRN_DSCR:
2096     return "No screen descriptor detected - invalid file";
2097
2098   case D_GIF_ERR_NO_IMAG_DSCR:
2099     return "No image descriptor detected - invalid file";
2100
2101   case D_GIF_ERR_NO_COLOR_MAP:
2102     return "No global or local color map found";
2103
2104   case D_GIF_ERR_WRONG_RECORD:
2105     return "Wrong record type detected - invalid file?";
2106
2107   case D_GIF_ERR_DATA_TOO_BIG:
2108     return "Data in file too big for image";
2109
2110   case D_GIF_ERR_NOT_ENOUGH_MEM:
2111     return "Out of memory";
2112
2113   case D_GIF_ERR_CLOSE_FAILED:
2114     return "Close failed";
2115
2116   case D_GIF_ERR_NOT_READABLE:
2117     return "File not opened for read";
2118
2119   case D_GIF_ERR_IMAGE_DEFECT:
2120     return "Defective image";
2121
2122   case D_GIF_ERR_EOF_TOO_SOON:
2123     return "Unexpected EOF - invalid file";
2124
2125   default:
2126     sprintf(msg, "Unknown giflib error code %d", code);
2127     return msg;
2128   }
2129 }
2130
2131 /*
2132 =item gif_push_error()
2133
2134 Utility function that takes the current GIF error code, converts it to
2135 an error message and pushes it on the error stack.
2136
2137 =cut
2138 */
2139
2140 static void gif_push_error(void) {
2141   int code = GifLastError(); /* clears saved error */
2142
2143   i_push_error(code, gif_error_msg(code));
2144 }
2145
2146 /*
2147 =head1 BUGS
2148
2149 The Netscape loop extension isn't implemented.  Giflib's extension
2150 writing code doesn't seem to support writing named extensions in this 
2151 form.
2152
2153 A bug in giflib is tickled by the i_writegif_callback().  This isn't a
2154 problem on ungiflib, but causes a SEGV on giflib.  A patch is provided
2155 in t/t10formats.t
2156
2157 The GIF file tag (GIF87a vs GIF89a) currently isn't set.  Using the
2158 supplied interface in giflib 4.1.0 causes a SEGV in
2159 EGifSetGifVersion().  See L<gif_set_version> for an explanation.
2160
2161 =head1 AUTHOR
2162
2163 Arnar M. Hrafnkelsson, addi@umich.edu
2164
2165 =head1 SEE ALSO
2166
2167 perl(1), Imager(3)
2168
2169 =cut
2170
2171 */