]> git.imager.perl.org - imager.git/blob - gif.c
new samples
[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;
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;
1390   int imgn;
1391   char used[256];
1392
1393   /* we try to build a common palette here, if we can manage that, then
1394      that's the palette we use */
1395   for (imgn = 0; imgn < count; ++imgn) {
1396     int eliminate_unused;
1397     if (imgs[imgn]->type != i_palette_type)
1398       return 0;
1399
1400     if (!i_tags_get_int(&imgs[imgn]->tags, "gif_eliminate_unused", 0, 
1401                         &eliminate_unused)) {
1402       eliminate_unused = 1;
1403     }
1404
1405     if (eliminate_unused) {
1406       i_palidx *line = mymalloc(sizeof(i_palidx) * imgs[imgn]->xsize);
1407       int x, y;
1408       memset(used, 0, sizeof(used));
1409
1410       for (y = 0; y < imgs[imgn]->ysize; ++y) {
1411         i_gpal(imgs[imgn], 0, imgs[imgn]->xsize, y, line);
1412         for (x = 0; x < imgs[imgn]->xsize; ++x)
1413           used[line[x]] = 1;
1414       }
1415
1416       myfree(line);
1417     }
1418     else {
1419       /* assume all are in use */
1420       memset(used, 1, sizeof(used));
1421     }
1422
1423     for (i = 0; i < i_colorcount(imgs[imgn]); ++i) {
1424       i_color c;
1425       
1426       i_getcolors(imgs[imgn], i, &c, 1);
1427       if (used[i]) {
1428         if (in_palette(&c, quant, size) < 0) {
1429           if (size < quant->mc_size) {
1430             quant->mc_colors[size++] = c;
1431           }
1432           else {
1433             /* oops, too many colors */
1434             return 0;
1435           }
1436         }
1437       }
1438     }
1439   }
1440
1441   quant->mc_count = size;
1442
1443   return 1;
1444 }
1445
1446 static i_palidx *
1447 quant_paletted(i_quantize *quant, i_img *img) {
1448   i_palidx *data = mymalloc(sizeof(i_palidx) * img->xsize * img->ysize);
1449   i_palidx *p = data;
1450   i_palidx trans[256];
1451   int i;
1452   int x, y;
1453
1454   /* build a translation table */
1455   for (i = 0; i < i_colorcount(img); ++i) {
1456     i_color c;
1457     i_getcolors(img, i, &c, 1);
1458     trans[i] = in_palette(&c, quant, quant->mc_count);
1459   }
1460
1461   for (y = 0; y < img->ysize; ++y) {
1462     i_gpal(img, 0, img->xsize, y, data+img->xsize * y);
1463     for (x = 0; x < img->xsize; ++x) {
1464       *p = trans[*p];
1465       ++p;
1466     }
1467   }
1468
1469   return data;
1470 }
1471
1472 /*
1473 =item i_writegif_low(i_quantize *quant, GifFileType *gf, i_img **imgs, int count, i_gif_opts *opts)
1474
1475 Internal.  Low-level function that does the high-level GIF processing
1476 :)
1477
1478 Returns non-zero on success.
1479
1480 =cut
1481 */
1482
1483 static undef_int
1484 i_writegif_low(i_quantize *quant, GifFileType *gf, i_img **imgs, int count) {
1485   unsigned char *result;
1486   int color_bits;
1487   ColorMapObject *map;
1488   int scrw = 0, scrh = 0;
1489   int imgn, orig_count, orig_size;
1490   int posx, posy;
1491   int trans_index;
1492   i_mempool mp;
1493   int *localmaps;
1494   int anylocal;
1495   i_img **glob_imgs; /* images that will use the global color map */
1496   int glob_img_count;
1497   i_color *orig_colors = quant->mc_colors;
1498   i_color *glob_colors = NULL;
1499   int glob_color_count;
1500   int glob_want_trans;
1501   int glob_paletted; /* the global map was made from the image palettes */
1502   int colors_paletted;
1503   int want_trans;
1504   int interlace;
1505   int gif_background;
1506
1507   mm_log((1, "i_writegif_low(quant %p, gf  %p, imgs %p, count %d)\n", 
1508           quant, gf, imgs, count));
1509   
1510   /* *((char *)0) = 1; */ /* used to break into the debugger */
1511   
1512   if (count <= 0) {
1513     i_push_error(0, "No images provided to write");
1514     return 0; /* what are you smoking? */
1515   }
1516
1517   i_mempool_init(&mp);
1518
1519   /* sanity is nice */
1520   if (quant->mc_size > 256) 
1521     quant->mc_size = 256;
1522   if (quant->mc_count > quant->mc_size)
1523     quant->mc_count = quant->mc_size;
1524
1525   if (!i_tags_get_int(&imgs[0]->tags, "gif_screen_width", 0, &scrw))
1526     scrw = 0;
1527   if (!i_tags_get_int(&imgs[0]->tags, "gif_screen_height", 0, &scrw))
1528     scrw = 0;
1529
1530   anylocal = 0;
1531   localmaps = i_mempool_alloc(&mp, sizeof(int) * count);
1532   glob_imgs = i_mempool_alloc(&mp, sizeof(i_img *) * count);
1533   glob_img_count = 0;
1534   glob_want_trans = 0;
1535   for (imgn = 0; imgn < count; ++imgn) {
1536     posx = posy = 0;
1537     i_tags_get_int(&imgs[imgn]->tags, "gif_left", 0, &posx);
1538     i_tags_get_int(&imgs[imgn]->tags, "gif_top", 0, &posy);
1539     if (imgs[imgn]->xsize + posx > scrw)
1540       scrw = imgs[imgn]->xsize + posx;
1541     if (imgs[imgn]->ysize + posy > scrh)
1542       scrh = imgs[imgn]->ysize + posy;
1543     if (!i_tags_get_int(&imgs[imgn]->tags, "gif_local_map", 0, localmaps+imgn))
1544       localmaps[imgn] = 0;
1545     if (localmaps[imgn])
1546       anylocal = 1;
1547     else {
1548       if (imgs[imgn]->channels == 4) {
1549         glob_want_trans = 1;
1550       }
1551       glob_imgs[glob_img_count++] = imgs[imgn];
1552     }
1553   }
1554   glob_want_trans = glob_want_trans && quant->transp != tr_none ;
1555
1556   orig_count = quant->mc_count;
1557   orig_size = quant->mc_size;
1558
1559   if (glob_img_count) {
1560     /* this is ugly */
1561     glob_colors = i_mempool_alloc(&mp, sizeof(i_color) * quant->mc_size);
1562     quant->mc_colors = glob_colors;
1563     memcpy(glob_colors, orig_colors, sizeof(i_color) * quant->mc_count);
1564     /* we have some images that want to use the global map */
1565     if (glob_want_trans && quant->mc_count == 256) {
1566       mm_log((2, "  disabling transparency for global map - no space\n"));
1567       glob_want_trans = 0;
1568     }
1569     if (glob_want_trans && quant->mc_size == 256) {
1570       mm_log((2, "  reserving color for transparency\n"));
1571       --quant->mc_size;
1572     }
1573     if (has_common_palette(glob_imgs, glob_img_count, quant, want_trans)) {
1574       glob_paletted = 1;
1575     }
1576     else {
1577       glob_paletted = 0;
1578       quant_makemap(quant, glob_imgs, glob_img_count);
1579     }
1580     glob_color_count = quant->mc_count;
1581     quant->mc_colors = orig_colors;
1582   }
1583
1584   /* use the global map if we have one, otherwise use the local map */
1585   gif_background = 0;
1586   if (glob_colors) {
1587     quant->mc_colors = glob_colors;
1588     quant->mc_count = glob_color_count;
1589     want_trans = glob_want_trans && imgs[0]->channels == 4;
1590
1591     if (!i_tags_get_int(&imgs[0]->tags, "gif_background", 0, &gif_background))
1592       gif_background = 0;
1593     if (gif_background < 0)
1594       gif_background = 0;
1595     if (gif_background >= glob_color_count)
1596       gif_background = 0;
1597   }
1598   else {
1599     want_trans = quant->transp != tr_none && imgs[0]->channels == 4;
1600     if (has_common_palette(imgs, 1, quant, want_trans)) {
1601       colors_paletted = 1;
1602     }
1603     else {
1604       colors_paletted = 0;
1605       quant_makemap(quant, imgs, 1);
1606     }
1607   }
1608   if ((map = make_gif_map(quant, imgs[0], want_trans)) == NULL) {
1609     i_mempool_destroy(&mp);
1610     quant->mc_colors = orig_colors;
1611     EGifCloseFile(gf);
1612     mm_log((1, "Error in MakeMapObject"));
1613     return 0;
1614   }
1615   color_bits = 1;
1616   if (anylocal) {
1617     /* since we don't know how big some the local palettes could be
1618        we need to base the bits on the maximum number of colors */
1619     while (orig_size > (1 << color_bits))
1620       ++color_bits;
1621   }
1622   else {
1623     int count = quant->mc_count;
1624     if (want_trans)
1625       ++count;
1626     while (count > (1 << color_bits))
1627       ++color_bits;
1628   }
1629   
1630   if (EGifPutScreenDesc(gf, scrw, scrh, color_bits, 
1631                         gif_background, map) == GIF_ERROR) {
1632     i_mempool_destroy(&mp);
1633     quant->mc_colors = orig_colors;
1634     gif_push_error();
1635     i_push_error(0, "Could not save screen descriptor");
1636     FreeMapObject(map);
1637     myfree(result);
1638     EGifCloseFile(gf);
1639     mm_log((1, "Error in EGifPutScreenDesc."));
1640     return 0;
1641   }
1642   FreeMapObject(map);
1643
1644   if (!i_tags_get_int(&imgs[0]->tags, "gif_left", 0, &posx))
1645     posx = 0;
1646   if (!i_tags_get_int(&imgs[0]->tags, "gif_top", 0, &posy))
1647     posy = 0;
1648
1649   if (!localmaps[0]) {
1650     map = NULL;
1651     colors_paletted = glob_paletted;
1652   }
1653   else {
1654     /* if this image has a global map the colors in quant don't
1655        belong to this image, so build a palette */
1656     if (glob_colors) {
1657       /* generate the local map for this image */
1658       quant->mc_colors = orig_colors;
1659       quant->mc_size = orig_size;
1660       quant->mc_count = orig_count;
1661       want_trans = quant->transp != tr_none && imgs[0]->channels == 4;
1662
1663       /* if the caller gives us too many colours we can't do transparency */
1664       if (want_trans && quant->mc_count == 256)
1665         want_trans = 0;
1666       /* if they want transparency but give us a big size, make it smaller
1667          to give room for a transparency colour */
1668       if (want_trans && quant->mc_size == 256)
1669         --quant->mc_size;
1670       if (has_common_palette(imgs, 1, quant, want_trans)) {
1671         colors_paletted = 1;
1672       }
1673       else {
1674         colors_paletted = 0;
1675         quant_makemap(quant, imgs, 1);
1676       }
1677       if ((map = make_gif_map(quant, imgs[0], want_trans)) == NULL) {
1678         i_mempool_destroy(&mp);
1679         EGifCloseFile(gf);
1680         quant->mc_colors = orig_colors;
1681         mm_log((1, "Error in MakeMapObject"));
1682         return 0;
1683       }
1684     }
1685     else {
1686       /* the map we wrote was the map for this image - don't set the local 
1687          map */
1688       map = NULL;
1689     }
1690   }
1691
1692   if (colors_paletted)
1693     result = quant_paletted(quant, imgs[0]);
1694   else
1695     result = quant_translate(quant, imgs[0]);
1696   if (!result) {
1697     i_mempool_destroy(&mp);
1698     quant->mc_colors = orig_colors;
1699     EGifCloseFile(gf);
1700     return 0;
1701   }
1702   if (want_trans) {
1703     quant_transparent(quant, result, imgs[0], quant->mc_count);
1704     trans_index = quant->mc_count;
1705   }
1706
1707   if (!do_ns_loop(gf, imgs[0])) {
1708     i_mempool_destroy(&mp);
1709     quant->mc_colors = orig_colors;
1710     return 0;
1711   }
1712
1713   if (!do_gce(gf, imgs[0], want_trans, trans_index)) {
1714     i_mempool_destroy(&mp);
1715     quant->mc_colors = orig_colors;
1716     myfree(result);
1717     EGifCloseFile(gf);
1718     return 0;
1719   }
1720
1721   if (!do_comments(gf, imgs[0])) {
1722     i_mempool_destroy(&mp);
1723     quant->mc_colors = orig_colors;
1724     myfree(result);
1725     EGifCloseFile(gf);
1726     return 0;
1727   }
1728
1729   if (!i_tags_get_int(&imgs[0]->tags, "gif_interlace", 0, &interlace))
1730     interlace = 0;
1731   if (EGifPutImageDesc(gf, posx, posy, imgs[0]->xsize, imgs[0]->ysize, 
1732                        interlace, map) == GIF_ERROR) {
1733     i_mempool_destroy(&mp);
1734     quant->mc_colors = orig_colors;
1735     gif_push_error();
1736     i_push_error(0, "Could not save image descriptor");
1737     EGifCloseFile(gf);
1738     mm_log((1, "Error in EGifPutImageDesc."));
1739     return 0;
1740   }
1741   if (map)
1742     FreeMapObject(map);
1743
1744   if (!do_write(gf, interlace, imgs[0], result)) {
1745     i_mempool_destroy(&mp);
1746     quant->mc_colors = orig_colors;
1747     EGifCloseFile(gf);
1748     myfree(result);
1749     return 0;
1750   }
1751   myfree(result);
1752
1753   /* that first awful image is out of the way, do the rest */
1754   for (imgn = 1; imgn < count; ++imgn) {
1755     if (localmaps[imgn]) {
1756       quant->mc_colors = orig_colors;
1757       quant->mc_count = orig_count;
1758       quant->mc_size = orig_size;
1759
1760       want_trans = quant->transp != tr_none 
1761         && imgs[imgn]->channels == 4;
1762       /* if the caller gives us too many colours we can't do transparency */
1763       if (want_trans && quant->mc_count == 256)
1764         want_trans = 0;
1765       /* if they want transparency but give us a big size, make it smaller
1766          to give room for a transparency colour */
1767       if (want_trans && quant->mc_size == 256)
1768         --quant->mc_size;
1769
1770       if (has_common_palette(imgs+imgn, 1, quant, want_trans)) {
1771         result = quant_paletted(quant, imgs[imgn]);
1772       }
1773       else {
1774         quant_makemap(quant, imgs+imgn, 1);
1775         result = quant_translate(quant, imgs[imgn]);
1776       }
1777       if (!result) {
1778         i_mempool_destroy(&mp);
1779         quant->mc_colors = orig_colors;
1780         EGifCloseFile(gf);
1781         mm_log((1, "error in quant_translate()"));
1782         return 0;
1783       }
1784       if (want_trans) {
1785         quant_transparent(quant, result, imgs[imgn], quant->mc_count);
1786         trans_index = quant->mc_count;
1787       }
1788
1789       if ((map = make_gif_map(quant, imgs[imgn], want_trans)) == NULL) {
1790         i_mempool_destroy(&mp);
1791         quant->mc_colors = orig_colors;
1792         myfree(result);
1793         EGifCloseFile(gf);
1794         mm_log((1, "Error in MakeMapObject."));
1795         return 0;
1796       }
1797     }
1798     else {
1799       quant->mc_colors = glob_colors;
1800       quant->mc_count = glob_color_count;
1801       if (glob_paletted)
1802         result = quant_paletted(quant, imgs[imgn]);
1803       else
1804         result = quant_translate(quant, imgs[imgn]);
1805       want_trans = glob_want_trans && imgs[imgn]->channels == 4;
1806       if (want_trans) {
1807         quant_transparent(quant, result, imgs[imgn], quant->mc_count);
1808         trans_index = quant->mc_count;
1809       }
1810       map = NULL;
1811     }
1812
1813     if (!do_gce(gf, imgs[imgn], want_trans, trans_index)) {
1814       i_mempool_destroy(&mp);
1815       quant->mc_colors = orig_colors;
1816       myfree(result);
1817       EGifCloseFile(gf);
1818       return 0;
1819     }
1820
1821     if (!do_comments(gf, imgs[imgn])) {
1822       i_mempool_destroy(&mp);
1823       quant->mc_colors = orig_colors;
1824       myfree(result);
1825       EGifCloseFile(gf);
1826       return 0;
1827     }
1828
1829     if (!i_tags_get_int(&imgs[imgn]->tags, "gif_left", 0, &posx))
1830       posx = 0;
1831     if (!i_tags_get_int(&imgs[imgn]->tags, "gif_top", 0, &posy))
1832       posy = 0;
1833
1834     if (!i_tags_get_int(&imgs[imgn]->tags, "gif_interlace", 0, &interlace))
1835       interlace = 0;
1836     if (EGifPutImageDesc(gf, posx, posy, imgs[imgn]->xsize, 
1837                          imgs[imgn]->ysize, interlace, map) == GIF_ERROR) {
1838       i_mempool_destroy(&mp);
1839       quant->mc_colors = orig_colors;
1840       gif_push_error();
1841       i_push_error(0, "Could not save image descriptor");
1842       myfree(result);
1843       if (map)
1844         FreeMapObject(map);
1845       EGifCloseFile(gf);
1846       mm_log((1, "Error in EGifPutImageDesc."));
1847       return 0;
1848     }
1849     if (map)
1850       FreeMapObject(map);
1851     
1852     if (!do_write(gf, interlace, imgs[imgn], result)) {
1853       i_mempool_destroy(&mp);
1854       quant->mc_colors = orig_colors;
1855       EGifCloseFile(gf);
1856       myfree(result);
1857       return 0;
1858     }
1859     myfree(result);
1860   }
1861
1862   if (EGifCloseFile(gf) == GIF_ERROR) {
1863     i_mempool_destroy(&mp);
1864     gif_push_error();
1865     i_push_error(0, "Could not close GIF file");
1866     mm_log((1, "Error in EGifCloseFile\n"));
1867     return 0;
1868   }
1869   if (glob_colors) {
1870     int i;
1871     for (i = 0; i < glob_color_count; ++i)
1872       orig_colors[i] = glob_colors[i];
1873   }
1874
1875   i_mempool_destroy(&mp);
1876   quant->mc_colors = orig_colors;
1877
1878   return 1;
1879 }
1880
1881 /*
1882 =item i_writegif_gen(i_quantize *quant, int fd, i_img **imgs, int count, i_gif_opts *opts)
1883
1884 General high-level function to write a GIF to a file.
1885
1886 Writes the GIF images to the specified file handle using the options
1887 in quant and opts.  See L<image.h/i_quantize> and
1888 L<image.h/i_gif_opts>.
1889
1890 Returns non-zero on success.
1891
1892 =cut
1893 */
1894
1895 undef_int
1896 i_writegif_gen(i_quantize *quant, int fd, i_img **imgs, int count) {
1897   GifFileType *gf;
1898
1899   i_clear_error();
1900   mm_log((1, "i_writegif_gen(quant %p, fd %d, imgs %p, count %d)\n", 
1901           quant, fd, imgs, count));
1902
1903   gif_set_version(quant, imgs, count);
1904
1905   if ((gf = EGifOpenFileHandle(fd)) == NULL) {
1906     gif_push_error();
1907     i_push_error(0, "Cannot create GIF file object");
1908     mm_log((1, "Error in EGifOpenFileHandle, unable to write image.\n"));
1909     return 0;
1910   }
1911
1912   return i_writegif_low(quant, gf, imgs, count);
1913 }
1914
1915 #if IM_GIFMAJOR >= 4
1916
1917 /*
1918 =item gif_writer_callback(GifFileType *gf, const GifByteType *data, int size)
1919
1920 Internal.  Wrapper for the user write callback function.
1921
1922 =cut
1923 */
1924
1925 static int gif_writer_callback(GifFileType *gf, const GifByteType *data, int size)
1926 {
1927   i_gen_write_data *gwd = (i_gen_write_data *)gf->UserData;
1928
1929   return i_gen_writer(gwd, (char*)data, size) ? size : 0;
1930 }
1931
1932 #endif
1933
1934 /*
1935 =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)
1936
1937 General high-level function to write a GIF using callbacks to send
1938 back the data.
1939
1940 Returns non-zero on success.
1941
1942 =cut
1943 */
1944
1945 undef_int
1946 i_writegif_callback(i_quantize *quant, i_write_callback_t cb, char *userdata,
1947                     int maxlength, i_img **imgs, int count)
1948 {
1949 #if IM_GIFMAJOR >= 4
1950   GifFileType *gf;
1951   i_gen_write_data *gwd = i_gen_write_data_new(cb, userdata, maxlength);
1952   int result;
1953
1954   i_clear_error();
1955
1956   mm_log((1, "i_writegif_callback(quant %p, i_write_callback_t %p, userdata $p, maxlength %d, imgs %p, count %d)\n", 
1957           quant, cb, userdata, maxlength, imgs, count));
1958   
1959   if ((gf = EGifOpen(gwd, &gif_writer_callback)) == NULL) {
1960     gif_push_error();
1961     i_push_error(0, "Cannot create GIF file object");
1962     mm_log((1, "Error in EGifOpenFileHandle, unable to write image.\n"));
1963     i_free_gen_write_data(gwd, 0);
1964     return 0;
1965   }
1966
1967   result = i_writegif_low(quant, gf, imgs, count);
1968   return i_free_gen_write_data(gwd, result);
1969 #else
1970   i_clear_error();
1971   i_push_error(0, "callbacks not supported with giflib3");
1972
1973   return 0;
1974 #endif
1975 }
1976
1977 #if IM_GIFMAJOR >= 4
1978
1979 static int
1980 io_glue_write_cb(GifFileType *gft, const GifByteType *data, int length) {
1981   io_glue *ig = (io_glue *)gft->UserData;
1982
1983   return ig->writecb(ig, data, length);
1984 }
1985
1986 #endif
1987
1988 /*
1989 =item i_writegif_wiol(ig, quant, opts, imgs, count)
1990
1991 =cut
1992 */
1993 undef_int
1994 i_writegif_wiol(io_glue *ig, i_quantize *quant, i_img **imgs,
1995                 int count) {
1996   io_glue_commit_types(ig);
1997
1998   if (ig->source.type == FDSEEK || ig->source.type == FDNOSEEK) {
1999     int fd = dup(ig->source.fdseek.fd);
2000     if (fd < 0) {
2001       i_push_error(errno, "dup() failed");
2002       return 0;
2003     }
2004     /* giflib opens the fd with fdopen(), which is then closed when fclose()
2005        is called - dup it so the caller's fd isn't closed */
2006     return i_writegif_gen(quant, fd, imgs, count);
2007   }
2008   else {
2009 #if IM_GIFMAJOR >= 4
2010     GifFileType *GifFile;
2011     int result;
2012
2013     i_clear_error();
2014
2015     gif_set_version(quant, imgs, count);
2016
2017     if ((GifFile = EGifOpen((void *)ig, io_glue_write_cb )) == NULL) {
2018       gif_push_error();
2019       i_push_error(0, "Cannot create giflib callback object");
2020       mm_log((1,"i_writegif_wiol: Unable to open callback datasource.\n"));
2021       return 0;
2022     }
2023     
2024     result = i_writegif_low(quant, GifFile, imgs, count);
2025     
2026     ig->closecb(ig);
2027
2028     return result;
2029 #else
2030     i_clear_error();
2031     i_push_error(0, "callbacks not supported with giflib3");
2032     
2033     return 0;
2034 #endif
2035   }
2036 }
2037
2038 /*
2039 =item gif_error_msg(int code)
2040
2041 Grabs the most recent giflib error code from GifLastError() and 
2042 returns a string that describes that error.
2043
2044 The returned pointer points to a static buffer, either from a literal
2045 C string or a static buffer.
2046
2047 =cut
2048 */
2049
2050 static char const *gif_error_msg(int code) {
2051   static char msg[80];
2052
2053   switch (code) {
2054   case E_GIF_ERR_OPEN_FAILED: /* should not see this */
2055     return "Failed to open given file";
2056     
2057   case E_GIF_ERR_WRITE_FAILED:
2058     return "Write failed";
2059
2060   case E_GIF_ERR_HAS_SCRN_DSCR: /* should not see this */
2061     return "Screen descriptor already passed to giflib";
2062
2063   case E_GIF_ERR_HAS_IMAG_DSCR: /* should not see this */
2064     return "Image descriptor already passed to giflib";
2065     
2066   case E_GIF_ERR_NO_COLOR_MAP: /* should not see this */
2067     return "Neither global nor local color map set";
2068
2069   case E_GIF_ERR_DATA_TOO_BIG: /* should not see this */
2070     return "Too much pixel data passed to giflib";
2071
2072   case E_GIF_ERR_NOT_ENOUGH_MEM:
2073     return "Out of memory";
2074     
2075   case E_GIF_ERR_DISK_IS_FULL:
2076     return "Disk is full";
2077     
2078   case E_GIF_ERR_CLOSE_FAILED: /* should not see this */
2079     return "File close failed";
2080  
2081   case E_GIF_ERR_NOT_WRITEABLE: /* should not see this */
2082     return "File not writable";
2083
2084   case D_GIF_ERR_OPEN_FAILED:
2085     return "Failed to open file";
2086     
2087   case D_GIF_ERR_READ_FAILED:
2088     return "Failed to read from file";
2089
2090   case D_GIF_ERR_NOT_GIF_FILE:
2091     return "File is not a GIF file";
2092
2093   case D_GIF_ERR_NO_SCRN_DSCR:
2094     return "No screen descriptor detected - invalid file";
2095
2096   case D_GIF_ERR_NO_IMAG_DSCR:
2097     return "No image descriptor detected - invalid file";
2098
2099   case D_GIF_ERR_NO_COLOR_MAP:
2100     return "No global or local color map found";
2101
2102   case D_GIF_ERR_WRONG_RECORD:
2103     return "Wrong record type detected - invalid file?";
2104
2105   case D_GIF_ERR_DATA_TOO_BIG:
2106     return "Data in file too big for image";
2107
2108   case D_GIF_ERR_NOT_ENOUGH_MEM:
2109     return "Out of memory";
2110
2111   case D_GIF_ERR_CLOSE_FAILED:
2112     return "Close failed";
2113
2114   case D_GIF_ERR_NOT_READABLE:
2115     return "File not opened for read";
2116
2117   case D_GIF_ERR_IMAGE_DEFECT:
2118     return "Defective image";
2119
2120   case D_GIF_ERR_EOF_TOO_SOON:
2121     return "Unexpected EOF - invalid file";
2122
2123   default:
2124     sprintf(msg, "Unknown giflib error code %d", code);
2125     return msg;
2126   }
2127 }
2128
2129 /*
2130 =item gif_push_error()
2131
2132 Utility function that takes the current GIF error code, converts it to
2133 an error message and pushes it on the error stack.
2134
2135 =cut
2136 */
2137
2138 static void gif_push_error(void) {
2139   int code = GifLastError(); /* clears saved error */
2140
2141   i_push_error(code, gif_error_msg(code));
2142 }
2143
2144 /*
2145 =head1 BUGS
2146
2147 The Netscape loop extension isn't implemented.  Giflib's extension
2148 writing code doesn't seem to support writing named extensions in this 
2149 form.
2150
2151 A bug in giflib is tickled by the i_writegif_callback().  This isn't a
2152 problem on ungiflib, but causes a SEGV on giflib.  A patch is provided
2153 in t/t10formats.t
2154
2155 The GIF file tag (GIF87a vs GIF89a) currently isn't set.  Using the
2156 supplied interface in giflib 4.1.0 causes a SEGV in
2157 EGifSetGifVersion().  See L<gif_set_version> for an explanation.
2158
2159 =head1 AUTHOR
2160
2161 Arnar M. Hrafnkelsson, addi@umich.edu
2162
2163 =head1 SEE ALSO
2164
2165 perl(1), Imager(3)
2166
2167 =cut
2168
2169 */