]> git.imager.perl.org - imager.git/blob - GIF/imgif.c
make the jpegquality docs clearer
[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 && page != -1) {
909     /* there were images, but the page selected wasn't found */
910     i_push_errorf(0, "page %d not found (%d total)", page, ImageNum);
911     free_images(results, *count);
912     return NULL;
913   }
914
915   return results;
916 }
917
918 static int io_glue_read_cb(GifFileType *gft, GifByteType *buf, int length);
919
920 /*
921 =item i_readgif_multi_wiol(ig, int *count)
922
923 =cut
924 */
925
926 i_img **
927 i_readgif_multi_wiol(io_glue *ig, int *count) {
928   GifFileType *GifFile;
929   int gif_error;
930   i_img **result;
931
932   gif_mutex_lock(mutex);
933
934   i_clear_error();
935   
936   if ((GifFile = myDGifOpen((void *)ig, io_glue_read_cb, &gif_error )) == NULL) {
937     gif_push_error(gif_error);
938     i_push_error(0, "Cannot create giflib callback object");
939     mm_log((1,"i_readgif_multi_wiol: Unable to open callback datasource.\n"));
940     gif_mutex_unlock(mutex);
941     return NULL;
942   }
943     
944   result = i_readgif_multi_low(GifFile, count, -1);
945
946   gif_mutex_unlock(mutex);
947
948   return result;
949 }
950
951 static int
952 io_glue_read_cb(GifFileType *gft, GifByteType *buf, int length) {
953   io_glue *ig = (io_glue *)gft->UserData;
954
955   return i_io_read(ig, buf, length);
956 }
957
958 i_img *
959 i_readgif_wiol(io_glue *ig, int **color_table, int *colors) {
960   GifFileType *GifFile;
961   int gif_error;
962   i_img *result;
963
964   gif_mutex_lock(mutex);
965
966   i_clear_error();
967
968   if ((GifFile = myDGifOpen((void *)ig, io_glue_read_cb, &gif_error )) == NULL) {
969     gif_push_error(gif_error);
970     i_push_error(0, "Cannot create giflib callback object");
971     mm_log((1,"i_readgif_wiol: Unable to open callback datasource.\n"));
972     gif_mutex_unlock(mutex);
973     return NULL;
974   }
975     
976   result = i_readgif_low(GifFile, color_table, colors);
977
978   gif_mutex_unlock(mutex);
979
980   return result;
981 }
982
983 /*
984 =item i_readgif_single_low(GifFile, page)
985
986 Lower level function to read a single image from a GIF.
987
988 page must be non-negative.
989
990 =cut
991 */
992 static i_img *
993 i_readgif_single_low(GifFileType *GifFile, int page) {
994   int count = 0;
995   i_img **imgs;
996
997   imgs = i_readgif_multi_low(GifFile, &count, page);
998
999   if (imgs && count) {
1000     i_img *result = imgs[0];
1001
1002     myfree(imgs);
1003     return result;
1004   }
1005   else {
1006     /* i_readgif_multi_low() handles the errors appropriately */
1007     return NULL;
1008   }
1009 }
1010
1011 /*
1012 =item i_readgif_single_wiol(ig, page)
1013
1014 Read a single page from a GIF image file, where the page is indexed
1015 from 0.
1016
1017 Returns NULL if the page isn't found.
1018
1019 =cut
1020 */
1021
1022 i_img *
1023 i_readgif_single_wiol(io_glue *ig, int page) {
1024   GifFileType *GifFile;
1025   int gif_error;
1026   i_img *result;
1027
1028   i_clear_error();
1029   if (page < 0) {
1030     i_push_error(0, "page must be non-negative");
1031     return NULL;
1032   }
1033
1034   gif_mutex_lock(mutex);
1035
1036   if ((GifFile = myDGifOpen((void *)ig, io_glue_read_cb, &gif_error )) == NULL) {
1037     gif_push_error(gif_error);
1038     i_push_error(0, "Cannot create giflib callback object");
1039     mm_log((1,"i_readgif_wiol: Unable to open callback datasource.\n"));
1040     gif_mutex_unlock(mutex);
1041     return NULL;
1042   }
1043     
1044   result = i_readgif_single_low(GifFile, page);
1045
1046   gif_mutex_unlock(mutex);
1047
1048   return result;
1049 }
1050
1051 /*
1052 =item do_write(GifFileType *gf, i_gif_opts *opts, i_img *img, i_palidx *data)
1053
1054 Internal.  Low level image write function.  Writes in interlace if
1055 that was requested in the GIF options.
1056
1057 Returns non-zero on success.
1058
1059 =cut
1060 */
1061 static undef_int 
1062 do_write(GifFileType *gf, int interlace, i_img *img, i_palidx *data) {
1063   if (interlace) {
1064     int i, j;
1065     for (i = 0; i < 4; ++i) {
1066       for (j = InterlacedOffset[i]; j < img->ysize; j += InterlacedJumps[i]) {
1067         if (EGifPutLine(gf, data+j*img->xsize, img->xsize) == GIF_ERROR) {
1068           gif_push_error(myGifError(gf));
1069           i_push_error(0, "Could not save image data:");
1070           mm_log((1, "Error in EGifPutLine\n"));
1071           return 0;
1072         }
1073       }
1074     }
1075   }
1076   else {
1077     int y;
1078     for (y = 0; y < img->ysize; ++y) {
1079       if (EGifPutLine(gf, data, img->xsize) == GIF_ERROR) {
1080         gif_push_error(myGifError(gf));
1081         i_push_error(0, "Could not save image data:");
1082         mm_log((1, "Error in EGifPutLine\n"));
1083         return 0;
1084       }
1085       data += img->xsize;
1086     }
1087   }
1088
1089   return 1;
1090 }
1091
1092 /*
1093 =item do_gce(GifFileType *gf, int index, i_gif_opts *opts, int want_trans, int trans_index)
1094
1095 Internal. Writes the GIF graphics control extension, if necessary.
1096
1097 Returns non-zero on success.
1098
1099 =cut
1100 */
1101
1102 static int
1103 do_gce(GifFileType *gf, i_img *img, int want_trans, int trans_index)
1104 {
1105   unsigned char gce[4] = {0};
1106   int want_gce = 0;
1107   int delay;
1108   int user_input;
1109   int disposal_method;
1110
1111   if (want_trans) {
1112     gce[0] |= 1;
1113     gce[3] = trans_index;
1114     ++want_gce;
1115   }
1116   if (i_tags_get_int(&img->tags, "gif_delay", 0, &delay)) {
1117     gce[1] = delay % 256;
1118     gce[2] = delay / 256;
1119     ++want_gce;
1120   }
1121   if (i_tags_get_int(&img->tags, "gif_user_input", 0, &user_input) 
1122       && user_input) {
1123     gce[0] |= 2;
1124     ++want_gce;
1125   }
1126   if (i_tags_get_int(&img->tags, "gif_disposal", 0, &disposal_method)) {
1127     gce[0] |= (disposal_method & 3) << 2;
1128     ++want_gce;
1129   }
1130   if (want_gce) {
1131     if (EGifPutExtension(gf, 0xF9, sizeof(gce), gce) == GIF_ERROR) {
1132       gif_push_error(myGifError(gf));
1133       i_push_error(0, "Could not save GCE");
1134     }
1135   }
1136   return 1;
1137 }
1138
1139 /*
1140 =item do_comments(gf, img)
1141
1142 Write any comments in the image.
1143
1144 =cut
1145 */
1146
1147 static int
1148 do_comments(GifFileType *gf, i_img *img) {
1149   int pos = -1;
1150
1151   while (i_tags_find(&img->tags, "gif_comment", pos+1, &pos)) {
1152     if (img->tags.tags[pos].data) {
1153       if (EGifPutComment(gf, img->tags.tags[pos].data) == GIF_ERROR) {
1154         return 0;
1155       }
1156     }
1157     else {
1158       char buf[50];
1159 #ifdef IMAGER_SNPRINTF
1160       snprintf(buf, sizeof(buf), "%d", img->tags.tags[pos].idata);
1161 #else
1162       sprintf(buf, "%d", img->tags.tags[pos].idata);
1163 #endif
1164       if (EGifPutComment(gf, buf) == GIF_ERROR) {
1165         return 0;
1166       }
1167     }
1168   }
1169
1170   return 1;
1171 }
1172
1173 /*
1174 =item do_ns_loop(GifFileType *gf, i_gif_opts *opts)
1175
1176 Internal.  Add the Netscape2.0 loop extension block, if requested.
1177
1178 Giflib/libungif prior to 4.1.1 didn't support writing application
1179 extension blocks, so we don't attempt to write them for older versions.
1180
1181 Giflib/libungif prior to 4.1.3 used the wrong write mechanism when
1182 writing extension blocks so that they could only be written to files.
1183
1184 =cut
1185 */
1186
1187 static int
1188 do_ns_loop(GifFileType *gf, i_img *img)
1189 {
1190   /* EGifPutExtension() doesn't appear to handle application 
1191      extension blocks in any way
1192      Since giflib wraps the fd with a FILE * (and puts that in its
1193      private data), we can't do an end-run and write the data 
1194      directly to the fd.
1195      There's no open interface that takes a FILE * either, so we 
1196      can't workaround it that way either.
1197      If giflib's callback interface wasn't broken by default, I'd 
1198      force file writes to use callbacks, but it is broken by default.
1199   */
1200   /* yes this was another attempt at supporting the loop extension */
1201   int loop_count;
1202   if (i_tags_get_int(&img->tags, "gif_loop", 0, &loop_count)) {
1203     unsigned char nsle[12] = "NETSCAPE2.0";
1204     unsigned char subblock[3];
1205
1206     subblock[0] = 1;
1207     subblock[1] = loop_count % 256;
1208     subblock[2] = loop_count / 256;
1209
1210 #if IMGIFLIB_API_VERSION >= 500
1211     if (EGifPutExtensionLeader(gf, APPLICATION_EXT_FUNC_CODE) == GIF_ERROR
1212         || EGifPutExtensionBlock(gf, 11, nsle) == GIF_ERROR
1213         || EGifPutExtensionBlock(gf, 3, subblock) == GIF_ERROR
1214         || EGifPutExtensionTrailer(gf) == GIF_ERROR) {
1215       gif_push_error(myGifError(gf));
1216       i_push_error(0, "writing loop extension");
1217       return 0;
1218     }
1219         
1220 #else
1221     if (EGifPutExtensionFirst(gf, APPLICATION_EXT_FUNC_CODE, 11, nsle) == GIF_ERROR) {
1222       gif_push_error(myGifError(gf));
1223       i_push_error(0, "writing loop extension");
1224       return 0;
1225     }
1226     if (EGifPutExtensionLast(gf, APPLICATION_EXT_FUNC_CODE, 3, subblock) == GIF_ERROR) {
1227       gif_push_error(myGifError(gf));
1228       i_push_error(0, "writing loop extension sub-block");
1229       return 0;
1230     }
1231 #endif
1232   }
1233
1234   return 1;
1235 }
1236
1237 /*
1238 =item make_gif_map(i_quantize *quant, int want_trans)
1239
1240 Create a giflib color map object from an Imager color map.
1241
1242 =cut
1243 */
1244
1245 static ColorMapObject *
1246 make_gif_map(i_quantize *quant, i_img *img, int want_trans) {
1247   GifColorType colors[256];
1248   int i;
1249   int size = quant->mc_count;
1250   int map_size;
1251   ColorMapObject *map;
1252   i_color trans;
1253
1254   for (i = 0; i < quant->mc_count; ++i) {
1255     colors[i].Red = quant->mc_colors[i].rgb.r;
1256     colors[i].Green = quant->mc_colors[i].rgb.g;
1257     colors[i].Blue = quant->mc_colors[i].rgb.b;
1258   }
1259   if (want_trans) {
1260     if (!i_tags_get_color(&img->tags, "gif_trans_color", 0, &trans))
1261       trans.rgb.r = trans.rgb.g = trans.rgb.b = 0;
1262     colors[size].Red = trans.rgb.r;
1263     colors[size].Green = trans.rgb.g;
1264     colors[size].Blue = trans.rgb.b;
1265     ++size;
1266   }
1267   map_size = 1;
1268   while (map_size < size)
1269     map_size <<= 1;
1270   /* giflib spews for 1 colour maps, reasonable, I suppose */
1271   if (map_size == 1)
1272     map_size = 2;
1273   while (i < map_size) {
1274     colors[i].Red = colors[i].Green = colors[i].Blue = 0;
1275     ++i;
1276   }
1277   
1278   map = MakeMapObject(map_size, colors);
1279   mm_log((1, "XXX map is at %p and colors at %p\n", map, map->Colors));
1280   if (!map) {
1281     i_push_error(0, "Could not create color map object");
1282     return NULL;
1283   }
1284 #if IMGIFLIB_API_VERSION >= 500
1285   map->SortFlag = 0;
1286 #endif
1287   return map;
1288 }
1289
1290 /*
1291 =item need_version_89a(i_quantize *quant, i_img *imgs, int count)
1292
1293 Return true if the file we're creating on these images needs a GIF89a
1294 header.
1295
1296 =cut
1297 */
1298
1299 static int
1300 need_version_89a(i_quantize *quant, i_img **imgs, int count) {
1301   int need_89a = 0;
1302   int temp;
1303   int i;
1304
1305   for (i = 0; i < count; ++i) {
1306     if (quant->transp != tr_none &&
1307         (imgs[i]->channels == 2 || imgs[i]->channels == 4)) {
1308       need_89a = 1;
1309       break;
1310     }
1311     if (i_tags_get_int(&imgs[i]->tags, "gif_delay", 0, &temp)) {
1312       need_89a = 1;
1313       break;
1314     }
1315     if (i_tags_get_int(&imgs[i]->tags, "gif_user_input", 0, &temp) && temp) {
1316       need_89a = 1; 
1317       break;
1318     }
1319     if (i_tags_get_int(&imgs[i]->tags, "gif_disposal", 0, &temp)) {
1320       need_89a = 1;
1321       break;
1322     }
1323     if (i_tags_get_int(&imgs[i]->tags, "gif_loop", 0, &temp)) {
1324       need_89a = 1;
1325       break;
1326     }
1327   }
1328
1329   return need_89a;
1330 }
1331
1332 static int 
1333 in_palette(i_color *c, i_quantize *quant, int size) {
1334   int i;
1335
1336   for (i = 0; i < size; ++i) {
1337     if (c->channel[0] == quant->mc_colors[i].channel[0]
1338         && c->channel[1] == quant->mc_colors[i].channel[1]
1339         && c->channel[2] == quant->mc_colors[i].channel[2]) {
1340       return i;
1341     }
1342   }
1343
1344   return -1;
1345 }
1346
1347 /*
1348 =item has_common_palette(imgs, count, quant)
1349
1350 Tests if all the given images are paletted and their colors are in the
1351 palette produced.
1352
1353 Previously this would build a consolidated palette from the source,
1354 but that meant that if the caller supplied a static palette (or
1355 specified a fixed palette like "webmap") then we wouldn't be
1356 quantizing to the caller specified palette.
1357
1358 =cut
1359 */
1360
1361 static int
1362 has_common_palette(i_img **imgs, int count, i_quantize *quant) {
1363   int i;
1364   int imgn;
1365   char used[256];
1366   int col_count;
1367
1368   /* we try to build a common palette here, if we can manage that, then
1369      that's the palette we use */
1370   for (imgn = 0; imgn < count; ++imgn) {
1371     int eliminate_unused;
1372     if (imgs[imgn]->type != i_palette_type)
1373       return 0;
1374
1375     if (!i_tags_get_int(&imgs[imgn]->tags, "gif_eliminate_unused", 0, 
1376                         &eliminate_unused)) {
1377       eliminate_unused = 1;
1378     }
1379
1380     if (eliminate_unused) {
1381       i_palidx *line = mymalloc(sizeof(i_palidx) * imgs[imgn]->xsize);
1382       int x, y;
1383       memset(used, 0, sizeof(used));
1384
1385       for (y = 0; y < imgs[imgn]->ysize; ++y) {
1386         i_gpal(imgs[imgn], 0, imgs[imgn]->xsize, y, line);
1387         for (x = 0; x < imgs[imgn]->xsize; ++x)
1388           used[line[x]] = 1;
1389       }
1390
1391       myfree(line);
1392     }
1393     else {
1394       /* assume all are in use */
1395       memset(used, 1, sizeof(used));
1396     }
1397
1398     col_count = i_colorcount(imgs[imgn]);
1399     for (i = 0; i < col_count; ++i) {
1400       i_color c;
1401       
1402       i_getcolors(imgs[imgn], i, &c, 1);
1403       if (used[i]) {
1404         if (in_palette(&c, quant, quant->mc_count) < 0) {
1405           mm_log((1, "  color not found in palette, no palette shortcut\n"));
1406   
1407           return 0;
1408         }
1409       }
1410     }
1411   }
1412
1413   mm_log((1, "  all colors found in palette, palette shortcut\n"));
1414
1415   return 1;
1416 }
1417
1418 static i_palidx *
1419 quant_paletted(i_quantize *quant, i_img *img) {
1420   i_palidx *data = mymalloc(sizeof(i_palidx) * img->xsize * img->ysize);
1421   i_palidx *p = data;
1422   i_palidx trans[256];
1423   int i;
1424   i_img_dim x, y;
1425
1426   /* build a translation table */
1427   for (i = 0; i < i_colorcount(img); ++i) {
1428     i_color c;
1429     i_getcolors(img, i, &c, 1);
1430     trans[i] = in_palette(&c, quant, quant->mc_count);
1431   }
1432
1433   for (y = 0; y < img->ysize; ++y) {
1434     i_gpal(img, 0, img->xsize, y, data+img->xsize * y);
1435     for (x = 0; x < img->xsize; ++x) {
1436       *p = trans[*p];
1437       ++p;
1438     }
1439   }
1440
1441   return data;
1442 }
1443
1444 /*
1445 =item i_writegif_low(i_quantize *quant, GifFileType *gf, i_img **imgs, int count, i_gif_opts *opts)
1446
1447 Internal.  Low-level function that does the high-level GIF processing
1448 :)
1449
1450 Returns non-zero on success.
1451
1452 =cut
1453 */
1454
1455 static undef_int
1456 i_writegif_low(i_quantize *quant, GifFileType *gf, i_img **imgs, int count) {
1457   unsigned char *result = NULL;
1458   int color_bits;
1459   ColorMapObject *map;
1460   int scrw = 0, scrh = 0;
1461   int imgn, orig_count, orig_size;
1462   int posx, posy;
1463   int trans_index = -1;
1464   int *localmaps = NULL;
1465   int anylocal;
1466   i_img **glob_imgs = NULL; /* images that will use the global color map */
1467   int glob_img_count;
1468   i_color *orig_colors = quant->mc_colors;
1469   i_color *glob_colors = NULL;
1470   int glob_color_count = 0;
1471   int glob_want_trans;
1472   int glob_paletted = 0; /* the global map was made from the image palettes */
1473   int colors_paletted = 0;
1474   int want_trans = 0;
1475   int interlace;
1476   int gif_background;
1477   int error;
1478
1479   mm_log((1, "i_writegif_low(quant %p, gf  %p, imgs %p, count %d)\n", 
1480           quant, gf, imgs, count));
1481   
1482   /* *((char *)0) = 1; */ /* used to break into the debugger */
1483   
1484   if (count <= 0) {
1485     i_push_error(0, "No images provided to write");
1486     goto fail_cleanup;
1487   }
1488
1489   /* sanity is nice */
1490   if (quant->mc_size > 256) 
1491     quant->mc_size = 256;
1492   if (quant->mc_count > quant->mc_size)
1493     quant->mc_count = quant->mc_size;
1494
1495   if (!i_tags_get_int(&imgs[0]->tags, "gif_screen_width", 0, &scrw))
1496     scrw = 0;
1497   if (!i_tags_get_int(&imgs[0]->tags, "gif_screen_height", 0, &scrh))
1498     scrh = 0;
1499
1500   anylocal = 0;
1501   localmaps = mymalloc(sizeof(int) * count);
1502   glob_imgs = mymalloc(sizeof(i_img *) * count);
1503   glob_img_count = 0;
1504   glob_want_trans = 0;
1505   for (imgn = 0; imgn < count; ++imgn) {
1506     i_img *im = imgs[imgn];
1507     if (im->xsize > 0xFFFF || im->ysize > 0xFFFF) {
1508       i_push_error(0, "image too large for GIF");
1509       goto fail_cleanup;
1510     }
1511     
1512     posx = posy = 0;
1513     i_tags_get_int(&im->tags, "gif_left", 0, &posx);
1514     if (posx < 0) posx = 0;
1515     i_tags_get_int(&im->tags, "gif_top", 0, &posy);
1516     if (posy < 0) posy = 0;
1517     if (im->xsize + posx > scrw)
1518       scrw = im->xsize + posx;
1519     if (im->ysize + posy > scrh)
1520       scrh = im->ysize + posy;
1521     if (!i_tags_get_int(&im->tags, "gif_local_map", 0, localmaps+imgn))
1522       localmaps[imgn] = 0;
1523     if (localmaps[imgn])
1524       anylocal = 1;
1525     else {
1526       if (im->channels == 4) {
1527         glob_want_trans = 1;
1528       }
1529       glob_imgs[glob_img_count++] = im;
1530     }
1531   }
1532   glob_want_trans = glob_want_trans && quant->transp != tr_none ;
1533
1534   if (scrw > 0xFFFF || scrh > 0xFFFF) {
1535     i_push_error(0, "screen size too large for GIF");
1536     goto fail_cleanup;
1537   }
1538
1539   orig_count = quant->mc_count;
1540   orig_size = quant->mc_size;
1541
1542   if (glob_img_count) {
1543     /* this is ugly */
1544     glob_colors = mymalloc(sizeof(i_color) * quant->mc_size);
1545     quant->mc_colors = glob_colors;
1546     memcpy(glob_colors, orig_colors, sizeof(i_color) * quant->mc_count);
1547     /* we have some images that want to use the global map */
1548     if (glob_want_trans && quant->mc_count == 256) {
1549       mm_log((2, "  disabling transparency for global map - no space\n"));
1550       glob_want_trans = 0;
1551     }
1552     if (glob_want_trans && quant->mc_size == 256) {
1553       mm_log((2, "  reserving color for transparency\n"));
1554       --quant->mc_size;
1555     }
1556
1557     i_quant_makemap(quant, glob_imgs, glob_img_count);
1558     glob_paletted = has_common_palette(glob_imgs, glob_img_count, quant);
1559     glob_color_count = quant->mc_count;
1560     quant->mc_colors = orig_colors;
1561   }
1562
1563   /* use the global map if we have one, otherwise use the local map */
1564   gif_background = 0;
1565   if (glob_colors) {
1566     quant->mc_colors = glob_colors;
1567     quant->mc_count = glob_color_count;
1568     want_trans = glob_want_trans && imgs[0]->channels == 4;
1569
1570     if (!i_tags_get_int(&imgs[0]->tags, "gif_background", 0, &gif_background))
1571       gif_background = 0;
1572     if (gif_background < 0)
1573       gif_background = 0;
1574     if (gif_background >= glob_color_count)
1575       gif_background = 0;
1576   }
1577   else {
1578     want_trans = quant->transp != tr_none && imgs[0]->channels == 4;
1579     i_quant_makemap(quant, imgs, 1);
1580     colors_paletted = has_common_palette(imgs, 1, quant);
1581   }
1582
1583   if ((map = make_gif_map(quant, imgs[0], want_trans)) == NULL) {
1584     mm_log((1, "Error in MakeMapObject"));
1585     goto fail_cleanup;
1586   }
1587   color_bits = 1;
1588   if (anylocal) {
1589     /* since we don't know how big some the local palettes could be
1590        we need to base the bits on the maximum number of colors */
1591     while (orig_size > (1 << color_bits))
1592       ++color_bits;
1593   }
1594   else {
1595     int count = quant->mc_count;
1596     if (want_trans)
1597       ++count;
1598     while (count > (1 << color_bits))
1599       ++color_bits;
1600   }
1601   
1602   if (EGifPutScreenDesc(gf, scrw, scrh, color_bits, 
1603                         gif_background, map) == GIF_ERROR) {
1604     gif_push_error(myGifError(gf));
1605     i_push_error(0, "Could not save screen descriptor");
1606     FreeMapObject(map);
1607     mm_log((1, "Error in EGifPutScreenDesc."));
1608     goto fail_cleanup;
1609   }
1610   FreeMapObject(map);
1611
1612   if (!i_tags_get_int(&imgs[0]->tags, "gif_left", 0, &posx))
1613     posx = 0;
1614   if (!i_tags_get_int(&imgs[0]->tags, "gif_top", 0, &posy))
1615     posy = 0;
1616
1617   if (!localmaps[0]) {
1618     map = NULL;
1619     colors_paletted = glob_paletted;
1620   }
1621   else {
1622     /* if this image has a global map the colors in quant don't
1623        belong to this image, so build a palette */
1624     if (glob_colors) {
1625       /* generate the local map for this image */
1626       quant->mc_colors = orig_colors;
1627       quant->mc_size = orig_size;
1628       quant->mc_count = orig_count;
1629       want_trans = quant->transp != tr_none && imgs[0]->channels == 4;
1630
1631       /* if the caller gives us too many colours we can't do transparency */
1632       if (want_trans && quant->mc_count == 256)
1633         want_trans = 0;
1634       /* if they want transparency but give us a big size, make it smaller
1635          to give room for a transparency colour */
1636       if (want_trans && quant->mc_size == 256)
1637         --quant->mc_size;
1638       i_quant_makemap(quant, imgs, 1);
1639       colors_paletted = has_common_palette(imgs, 1, quant);
1640       if ((map = make_gif_map(quant, imgs[0], want_trans)) == NULL) {
1641         mm_log((1, "Error in MakeMapObject"));
1642         goto fail_cleanup;
1643       }
1644     }
1645     else {
1646       /* the map we wrote was the map for this image - don't set the local 
1647          map */
1648       map = NULL;
1649     }
1650   }
1651
1652   if (colors_paletted)
1653     result = quant_paletted(quant, imgs[0]);
1654   else
1655     result = i_quant_translate(quant, imgs[0]);
1656   if (!result) {
1657     goto fail_cleanup;
1658   }
1659   if (want_trans) {
1660     i_quant_transparent(quant, result, imgs[0], quant->mc_count);
1661     trans_index = quant->mc_count;
1662   }
1663
1664   if (!do_ns_loop(gf, imgs[0])) {
1665     goto fail_cleanup;
1666   }
1667
1668   if (!do_gce(gf, imgs[0], want_trans, trans_index)) {
1669     goto fail_cleanup;
1670   }
1671
1672   if (!do_comments(gf, imgs[0])) {
1673     goto fail_cleanup;
1674   }
1675
1676   if (!i_tags_get_int(&imgs[0]->tags, "gif_interlace", 0, &interlace))
1677     interlace = 0;
1678   if (EGifPutImageDesc(gf, posx, posy, imgs[0]->xsize, imgs[0]->ysize, 
1679                        interlace, map) == GIF_ERROR) {
1680     if (map)
1681       FreeMapObject(map);
1682     gif_push_error(myGifError(gf));
1683     i_push_error(0, "Could not save image descriptor");
1684     mm_log((1, "Error in EGifPutImageDesc."));
1685     goto fail_cleanup;
1686   }
1687   if (map)
1688     FreeMapObject(map);
1689
1690   if (!do_write(gf, interlace, imgs[0], result)) {
1691     goto fail_cleanup;
1692   }
1693   myfree(result);
1694   result = NULL;
1695
1696   /* that first awful image is out of the way, do the rest */
1697   for (imgn = 1; imgn < count; ++imgn) {
1698     if (localmaps[imgn]) {
1699       quant->mc_colors = orig_colors;
1700       quant->mc_count = orig_count;
1701       quant->mc_size = orig_size;
1702
1703       want_trans = quant->transp != tr_none 
1704         && imgs[imgn]->channels == 4;
1705       /* if the caller gives us too many colours we can't do transparency */
1706       if (want_trans && quant->mc_count == 256)
1707         want_trans = 0;
1708       /* if they want transparency but give us a big size, make it smaller
1709          to give room for a transparency colour */
1710       if (want_trans && quant->mc_size == 256)
1711         --quant->mc_size;
1712
1713       if (has_common_palette(imgs+imgn, 1, quant)) {
1714         result = quant_paletted(quant, imgs[imgn]);
1715       }
1716       else {
1717         i_quant_makemap(quant, imgs+imgn, 1);
1718         result = i_quant_translate(quant, imgs[imgn]);
1719       }
1720       if (!result) {
1721         mm_log((1, "error in i_quant_translate()"));
1722         goto fail_cleanup;
1723       }
1724       if (want_trans) {
1725         i_quant_transparent(quant, result, imgs[imgn], quant->mc_count);
1726         trans_index = quant->mc_count;
1727       }
1728
1729       if ((map = make_gif_map(quant, imgs[imgn], want_trans)) == NULL) {
1730         mm_log((1, "Error in MakeMapObject."));
1731         goto fail_cleanup;
1732       }
1733     }
1734     else {
1735       quant->mc_colors = glob_colors;
1736       quant->mc_count = glob_color_count;
1737       if (glob_paletted)
1738         result = quant_paletted(quant, imgs[imgn]);
1739       else
1740         result = i_quant_translate(quant, imgs[imgn]);
1741       want_trans = glob_want_trans && imgs[imgn]->channels == 4;
1742       if (want_trans) {
1743         i_quant_transparent(quant, result, imgs[imgn], quant->mc_count);
1744         trans_index = quant->mc_count;
1745       }
1746       map = NULL;
1747     }
1748
1749     if (!do_gce(gf, imgs[imgn], want_trans, trans_index)) {
1750       goto fail_cleanup;
1751     }
1752
1753     if (!do_comments(gf, imgs[imgn])) {
1754       goto fail_cleanup;
1755     }
1756
1757     if (!i_tags_get_int(&imgs[imgn]->tags, "gif_left", 0, &posx))
1758       posx = 0;
1759     if (!i_tags_get_int(&imgs[imgn]->tags, "gif_top", 0, &posy))
1760       posy = 0;
1761
1762     if (!i_tags_get_int(&imgs[imgn]->tags, "gif_interlace", 0, &interlace))
1763       interlace = 0;
1764     if (EGifPutImageDesc(gf, posx, posy, imgs[imgn]->xsize, 
1765                          imgs[imgn]->ysize, interlace, map) == GIF_ERROR) {
1766       gif_push_error(myGifError(gf));
1767       i_push_error(0, "Could not save image descriptor");
1768       if (map)
1769         FreeMapObject(map);
1770       mm_log((1, "Error in EGifPutImageDesc."));
1771       goto fail_cleanup;
1772     }
1773     if (map)
1774       FreeMapObject(map);
1775     
1776     if (!do_write(gf, interlace, imgs[imgn], result)) {
1777       goto fail_cleanup;
1778     }
1779     myfree(result);
1780     result = NULL;
1781   }
1782
1783   if (myEGifCloseFile(gf, &error) == GIF_ERROR) {
1784     gif_push_error(error);
1785     i_push_error(0, "Could not close GIF file");
1786     goto fail_cleanup;
1787   }
1788   if (glob_colors) {
1789     int i;
1790     for (i = 0; i < glob_color_count; ++i)
1791       orig_colors[i] = glob_colors[i];
1792   }
1793
1794   myfree(glob_colors);
1795   myfree(localmaps);
1796   myfree(glob_imgs);
1797   quant->mc_colors = orig_colors;
1798
1799   return 1;
1800
1801  fail_cleanup:
1802   quant->mc_colors = orig_colors;
1803   myfree(result);
1804   myfree(glob_colors);
1805   myfree(localmaps);
1806   myfree(glob_imgs);
1807   (void)myEGifCloseFile(gf, &error);
1808   return 0;
1809 }
1810
1811 static int
1812 io_glue_write_cb(GifFileType *gft, const GifByteType *data, int length) {
1813   io_glue *ig = (io_glue *)gft->UserData;
1814
1815   return i_io_write(ig, data, length);
1816 }
1817
1818
1819 /*
1820 =item i_writegif_wiol(ig, quant, opts, imgs, count)
1821
1822 =cut
1823 */
1824 undef_int
1825 i_writegif_wiol(io_glue *ig, i_quantize *quant, i_img **imgs,
1826                 int count) {
1827   GifFileType *GifFile;
1828   int gif_error;
1829   int result;
1830
1831   gif_mutex_lock(mutex);
1832
1833   i_clear_error();
1834
1835 #ifdef PRE_SET_VERSION
1836   EGifSetGifVersion(need_version_89a(quant, imgs, count) ? "89a" : "87a");
1837 #endif
1838   
1839   if ((GifFile = myEGifOpen((void *)ig, io_glue_write_cb, &gif_error )) == NULL) {
1840     gif_push_error(gif_error);
1841     i_push_error(0, "Cannot create giflib callback object");
1842     mm_log((1,"i_writegif_wiol: Unable to open callback datasource.\n"));
1843     gif_mutex_unlock(mutex);
1844     return 0;
1845   }
1846   
1847 #ifdef POST_SET_VERSION
1848   EGifSetGifVersion(GifFile, need_version_89a(quant, imgs, count));
1849 #endif
1850
1851   result = i_writegif_low(quant, GifFile, imgs, count);
1852   
1853   gif_mutex_unlock(mutex);
1854
1855   if (i_io_close(ig))
1856     return 0;
1857   
1858   return result;
1859 }
1860
1861 /*
1862 =item gif_error_msg(int code)
1863
1864 Grabs the most recent giflib error code from GifLastError() and 
1865 returns a string that describes that error.
1866
1867 Returns NULL for unknown error codes.
1868
1869 =cut
1870 */
1871
1872 static char const *
1873 gif_error_msg(int code) {
1874 #if IMGIFLIB_API_VERSION >= 500
1875   return GifErrorString(code);
1876 #else
1877   switch (code) {
1878   case E_GIF_ERR_OPEN_FAILED: /* should not see this */
1879     return "Failed to open given file";
1880     
1881   case E_GIF_ERR_WRITE_FAILED:
1882     return "Write failed";
1883
1884   case E_GIF_ERR_HAS_SCRN_DSCR: /* should not see this */
1885     return "Screen descriptor already passed to giflib";
1886
1887   case E_GIF_ERR_HAS_IMAG_DSCR: /* should not see this */
1888     return "Image descriptor already passed to giflib";
1889     
1890   case E_GIF_ERR_NO_COLOR_MAP: /* should not see this */
1891     return "Neither global nor local color map set";
1892
1893   case E_GIF_ERR_DATA_TOO_BIG: /* should not see this */
1894     return "Too much pixel data passed to giflib";
1895
1896   case E_GIF_ERR_NOT_ENOUGH_MEM:
1897     return "Out of memory";
1898     
1899   case E_GIF_ERR_DISK_IS_FULL:
1900     return "Disk is full";
1901     
1902   case E_GIF_ERR_CLOSE_FAILED: /* should not see this */
1903     return "File close failed";
1904  
1905   case E_GIF_ERR_NOT_WRITEABLE: /* should not see this */
1906     return "File not writable";
1907
1908   case D_GIF_ERR_OPEN_FAILED:
1909     return "Failed to open file";
1910     
1911   case D_GIF_ERR_READ_FAILED:
1912     return "Failed to read from file";
1913
1914   case D_GIF_ERR_NOT_GIF_FILE:
1915     return "File is not a GIF file";
1916
1917   case D_GIF_ERR_NO_SCRN_DSCR:
1918     return "No screen descriptor detected - invalid file";
1919
1920   case D_GIF_ERR_NO_IMAG_DSCR:
1921     return "No image descriptor detected - invalid file";
1922
1923   case D_GIF_ERR_NO_COLOR_MAP:
1924     return "No global or local color map found";
1925
1926   case D_GIF_ERR_WRONG_RECORD:
1927     return "Wrong record type detected - invalid file?";
1928
1929   case D_GIF_ERR_DATA_TOO_BIG:
1930     return "Data in file too big for image";
1931
1932   case D_GIF_ERR_NOT_ENOUGH_MEM:
1933     return "Out of memory";
1934
1935   case D_GIF_ERR_CLOSE_FAILED:
1936     return "Close failed";
1937
1938   case D_GIF_ERR_NOT_READABLE:
1939     return "File not opened for read";
1940
1941   case D_GIF_ERR_IMAGE_DEFECT:
1942     return "Defective image";
1943
1944   case D_GIF_ERR_EOF_TOO_SOON:
1945     return "Unexpected EOF - invalid file";
1946
1947   default:
1948     return NULL;
1949   }
1950 #endif
1951 }
1952
1953 /*
1954 =item gif_push_error(code)
1955
1956 Utility function that takes the current GIF error code, converts it to
1957 an error message and pushes it on the error stack.
1958
1959 =cut
1960 */
1961
1962 static void
1963 gif_push_error(int code) {
1964   const char *msg = gif_error_msg(code);
1965   if (msg)
1966     i_push_error(code, msg);
1967   else
1968     i_push_errorf(code, "Unknown GIF error %d", code);
1969 }
1970
1971 /*
1972 =head1 AUTHOR
1973
1974 Arnar M. Hrafnkelsson, addi@umich.edu
1975
1976 Tony Cook <tonyc@cpan.org>
1977
1978 =head1 SEE ALSO
1979
1980 perl(1), Imager(3)
1981
1982 =cut
1983
1984 */