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