]> git.imager.perl.org - imager.git/blobdiff - gif.c
long delayed renaming of m_fatal() to i_fatal() to match Imager's
[imager.git] / gif.c
diff --git a/gif.c b/gif.c
index 3b29d14add6514db78f8accf3e0e62eaeb4bc86a..84b51bf17bff752cf1cc83317faccc1a44d75233 100644 (file)
--- a/gif.c
+++ b/gif.c
@@ -1,6 +1,11 @@
-#include "image.h"
+#include "imageri.h"
 #include <gif_lib.h>
-
+#ifdef _MSCVER
+#include <io.h>
+#else
+#include <unistd.h>
+#endif
+#include <errno.h>
 /* XXX: Reading still needs to support reading all those gif properties */
 
 /*
@@ -172,8 +177,25 @@ i_readgif_low(GifFileType *GifFile, int **colour_table, int *colours) {
     cmapcnt++;
   }
   
+  if (!i_int_check_image_file_limits(GifFile->SWidth, GifFile->SHeight, 3, sizeof(i_sample_t))) {
+    if (colour_table && *colour_table) {
+      myfree(*colour_table);
+      *colour_table = NULL;
+    }
+    DGifCloseFile(GifFile);
+    mm_log((1, "i_readgif: image size exceeds limits\n"));
+    return NULL;
+  }
 
   im = i_img_empty_ch(NULL, GifFile->SWidth, GifFile->SHeight, 3);
+  if (!im) {
+    if (colour_table && *colour_table) {
+      myfree(*colour_table);
+      *colour_table = NULL;
+    }
+    DGifCloseFile(GifFile);
+    return NULL;
+  }
 
   Size = GifFile->SWidth * sizeof(GifPixelType); 
   
@@ -349,6 +371,9 @@ i_readgif_low(GifFileType *GifFile, int **colour_table, int *colours) {
     i_img_destroy(im);
     return NULL;
   }
+
+  i_tags_add(&im->tags, "i_format", 0, "gif", -1, 0);
+
   return im;
 }
 
@@ -391,19 +416,25 @@ Internal function called by i_readgif_multi_low() in error handling
 */
 static void free_images(i_img **imgs, int count) {
   int i;
-  for (i = 0; i < count; ++i)
-    i_img_destroy(imgs[i]);
-  myfree(imgs);
+  
+  if (count) {
+    for (i = 0; i < count; ++i)
+      i_img_destroy(imgs[i]);
+    myfree(imgs);
+  }
 }
 
 /*
-=item i_readgif_multi_low(GifFileType *gf, int *count)
+=item i_readgif_multi_low(GifFileType *gf, int *count, int page)
 
 Reads one of more gif images from the given GIF file.
 
 Returns a pointer to an array of i_img *, and puts the count into 
 *count.
 
+If page is not -1 then the given image _only_ is returned from the
+file, where the first image is 0, the second 1 and so on.
+
 Unlike the normal i_readgif*() functions the images are paletted
 images rather than a combined RGB image.
 
@@ -477,9 +508,9 @@ standard.
 =cut
 */
 
-i_img **i_readgif_multi_low(GifFileType *GifFile, int *count) {
+i_img **i_readgif_multi_low(GifFileType *GifFile, int *count, int page) {
   i_img *img;
-  int i, j, Size, Width, Height, ExtCode, Count, x;
+  int i, j, Size, Width, Height, ExtCode, Count;
   int ImageNum = 0, BackGround = 0, ColorMapSize = 0;
   ColorMapObject *ColorMap;
  
@@ -508,7 +539,7 @@ i_img **i_readgif_multi_low(GifFileType *GifFile, int *count) {
   Size = GifFile->SWidth * sizeof(GifPixelType);
   
   if ((GifRow = (GifRowType) mymalloc(Size)) == NULL)
-    m_fatal(0,"Failed to allocate memory required, aborted."); /* First row. */
+    i_fatal(0,"Failed to allocate memory required, aborted."); /* First row. */
 
   /* Scan the content of the GIF file and load the image(s) in: */
   do {
@@ -517,6 +548,7 @@ i_img **i_readgif_multi_low(GifFileType *GifFile, int *count) {
       i_push_error(0, "Unable to get record type");
       free_images(results, *count);
       DGifCloseFile(GifFile);
+      myfree(GifRow);
       return NULL;
     }
     
@@ -527,126 +559,178 @@ i_img **i_readgif_multi_low(GifFileType *GifFile, int *count) {
        i_push_error(0, "Unable to get image descriptor");
         free_images(results, *count);
        DGifCloseFile(GifFile);
+       myfree(GifRow);
        return NULL;
       }
 
-      if (( ColorMap = (GifFile->Image.ColorMap ? GifFile->Image.ColorMap : GifFile->SColorMap) )) {
-       mm_log((1, "Adding local colormap\n"));
-       ColorMapSize = ColorMap->ColorCount;
-      } else {
-       /* No colormap and we are about to read in the image - 
-           abandon for now */
-       mm_log((1, "Going in with no colormap\n"));
-       i_push_error(0, "Image does not have a local or a global color map");
-        free_images(results, *count);
-       DGifCloseFile(GifFile);
-       return NULL;
-      }
-      
       Width = GifFile->Image.Width;
       Height = GifFile->Image.Height;
-      channels = 3;
-      if (got_gce && trans_index >= 0)
-        channels = 4;
-      img = i_img_pal_new(Width, Height, channels, 256);
-      /* populate the palette of the new image */
-      mm_log((1, "ColorMapSize %d\n", ColorMapSize));
-      for (i = 0; i < ColorMapSize; ++i) {
-        i_color col;
-        col.rgba.r = ColorMap->Colors[i].Red;
-        col.rgba.g = ColorMap->Colors[i].Green;
-        col.rgba.b = ColorMap->Colors[i].Blue;
-        if (channels == 4 && trans_index == i)
-          col.rgba.a = 0;
-        else
-          col.rgba.a = 255;
-       
-        i_addcolors(img, &col, 1);
-      }
-      ++*count;
-      if (*count > result_alloc) {
-        if (result_alloc == 0) {
-          result_alloc = 5;
-          results = mymalloc(result_alloc * sizeof(i_img *));
-        }
-        else {
-          i_img **newresults;
-          result_alloc *= 2;
-          newresults = myrealloc(results, result_alloc * sizeof(i_img *));
-        }
-      }
-      results[*count-1] = img;
-      i_tags_addn(&img->tags, "gif_left", 0, GifFile->Image.Left);
-      /**(char *)0 = 1;*/
-      i_tags_addn(&img->tags, "gif_top",  0, GifFile->Image.Top);
-      i_tags_addn(&img->tags, "gif_interlace", 0, GifFile->Image.Interlace);
-      i_tags_addn(&img->tags, "gif_screen_width", 0, GifFile->SWidth);
-      i_tags_addn(&img->tags, "gif_screen_height", 0, GifFile->SHeight);
-      if (GifFile->SColorMap && !GifFile->Image.ColorMap) {
-        i_tags_addn(&img->tags, "gif_background", 0, 
-                    GifFile->SBackGroundColor);
-      }
-      if (GifFile->Image.ColorMap) {
-        i_tags_addn(&img->tags, "gif_localmap", 0, 1);
-      }
-      if (got_gce) {
-        if (trans_index >= 0)
-          i_tags_addn(&img->tags, "gif_trans_index", 0, trans_index);
-        i_tags_addn(&img->tags, "gif_delay", 0, gif_delay);
-        i_tags_addn(&img->tags, "gif_user_input", 0, user_input);
-        i_tags_addn(&img->tags, "gif_disposal", 0, disposal);
-      }
-      got_gce = 0;
-      if (got_ns_loop)
-        i_tags_addn(&img->tags, "gif_loop", 0, ns_loop);
-      if (comment) {
-        i_tags_add(&img->tags, "gif_comment", 0, comment, strlen(comment), 0);
-        myfree(comment);
-        comment = NULL;
-      }
-
-      ImageNum++;
-      mm_log((1,"i_readgif_multi_low: Image %d at (%d, %d) [%dx%d]: \n",
-             ImageNum, GifFile->Image.Left, GifFile->Image.Top, Width, Height));
+      if (page == -1 || page == ImageNum) {
+       if (( ColorMap = (GifFile->Image.ColorMap ? GifFile->Image.ColorMap : GifFile->SColorMap) )) {
+         mm_log((1, "Adding local colormap\n"));
+         ColorMapSize = ColorMap->ColorCount;
+       } else {
+         /* No colormap and we are about to read in the image - 
+            abandon for now */
+         mm_log((1, "Going in with no colormap\n"));
+         i_push_error(0, "Image does not have a local or a global color map");
+         free_images(results, *count);
+         DGifCloseFile(GifFile);
+         myfree(GifRow);
+         return NULL;
+       }
+       
+       channels = 3;
+       if (got_gce && trans_index >= 0)
+         channels = 4;
+       if (!i_int_check_image_file_limits(Width, Height, channels, sizeof(i_sample_t))) {
+         free_images(results, *count);
+         mm_log((1, "i_readgif: image size exceeds limits\n"));
+         DGifCloseFile(GifFile);
+         myfree(GifRow);
+         return NULL;
+       }
+       img = i_img_pal_new(Width, Height, channels, 256);
+       if (!img) {
+         free_images(results, *count);
+         DGifCloseFile(GifFile);
+         return NULL;
+       }
+       /* populate the palette of the new image */
+       mm_log((1, "ColorMapSize %d\n", ColorMapSize));
+       for (i = 0; i < ColorMapSize; ++i) {
+         i_color col;
+         col.rgba.r = ColorMap->Colors[i].Red;
+         col.rgba.g = ColorMap->Colors[i].Green;
+         col.rgba.b = ColorMap->Colors[i].Blue;
+         if (channels == 4 && trans_index == i)
+           col.rgba.a = 0;
+         else
+           col.rgba.a = 255;
+         
+         i_addcolors(img, &col, 1);
+       }
+       ++*count;
+       if (*count > result_alloc) {
+         if (result_alloc == 0) {
+           result_alloc = 5;
+           results = mymalloc(result_alloc * sizeof(i_img *));
+         }
+         else {
+           /* myrealloc never fails (it just dies if it can't allocate) */
+           result_alloc *= 2;
+           results = myrealloc(results, result_alloc * sizeof(i_img *));
+         }
+       }
+       results[*count-1] = img;
+       i_tags_add(&img->tags, "i_format", 0, "gif", -1, 0);
+       i_tags_addn(&img->tags, "gif_left", 0, GifFile->Image.Left);
+       /**(char *)0 = 1;*/
+       i_tags_addn(&img->tags, "gif_top",  0, GifFile->Image.Top);
+       i_tags_addn(&img->tags, "gif_interlace", 0, GifFile->Image.Interlace);
+       i_tags_addn(&img->tags, "gif_screen_width", 0, GifFile->SWidth);
+       i_tags_addn(&img->tags, "gif_screen_height", 0, GifFile->SHeight);
+       if (GifFile->SColorMap && !GifFile->Image.ColorMap) {
+         i_tags_addn(&img->tags, "gif_background", 0, 
+                     GifFile->SBackGroundColor);
+       }
+       if (GifFile->Image.ColorMap) {
+         i_tags_addn(&img->tags, "gif_localmap", 0, 1);
+       }
+       if (got_gce) {
+         if (trans_index >= 0) {
+           i_color trans;
+           i_tags_addn(&img->tags, "gif_trans_index", 0, trans_index);
+           i_getcolors(img, trans_index, &trans, 1);
+           i_tags_set_color(&img->tags, "gif_trans_color", 0, &trans);
+         }
+         i_tags_addn(&img->tags, "gif_delay", 0, gif_delay);
+         i_tags_addn(&img->tags, "gif_user_input", 0, user_input);
+         i_tags_addn(&img->tags, "gif_disposal", 0, disposal);
+       }
+       got_gce = 0;
+       if (got_ns_loop)
+         i_tags_addn(&img->tags, "gif_loop", 0, ns_loop);
+       if (comment) {
+         i_tags_add(&img->tags, "gif_comment", 0, comment, strlen(comment), 0);
+         myfree(comment);
+         comment = NULL;
+       }
+       
+       mm_log((1,"i_readgif_multi_low: Image %d at (%d, %d) [%dx%d]: \n",
+               ImageNum, GifFile->Image.Left, GifFile->Image.Top, Width, Height));
+       
+       if (GifFile->Image.Left + GifFile->Image.Width > GifFile->SWidth ||
+           GifFile->Image.Top + GifFile->Image.Height > GifFile->SHeight) {
+         i_push_errorf(0, "Image %d is not confined to screen dimension, aborted.\n",ImageNum);
+         free_images(results, *count);        
+         DGifCloseFile(GifFile);
+         myfree(GifRow);
+         return(0);
+       }
+       
+       if (GifFile->Image.Interlace) {
+         for (Count = i = 0; i < 4; i++) {
+           for (j = InterlacedOffset[i]; j < Height; 
+                j += InterlacedJumps[i]) {
+             Count++;
+             if (DGifGetLine(GifFile, GifRow, Width) == GIF_ERROR) {
+               gif_push_error();
+               i_push_error(0, "Reading GIF line");
+               free_images(results, *count);
+               DGifCloseFile(GifFile);
+               myfree(GifRow);
+               return NULL;
+             }
+             
+             i_ppal(img, 0, Width, j, GifRow);
+           }
+         }
+       }
+       else {
+         for (i = 0; i < Height; i++) {
+           if (DGifGetLine(GifFile, GifRow, Width) == GIF_ERROR) {
+             gif_push_error();
+             i_push_error(0, "Reading GIF line");
+             free_images(results, *count);
+             DGifCloseFile(GifFile);
+             myfree(GifRow);
+             return NULL;
+           }
+           
+           i_ppal(img, 0, Width, i, GifRow);
+         }
+       }
 
-      if (GifFile->Image.Left + GifFile->Image.Width > GifFile->SWidth ||
-         GifFile->Image.Top + GifFile->Image.Height > GifFile->SHeight) {
-       i_push_errorf(0, "Image %d is not confined to screen dimension, aborted.\n",ImageNum);
-       free_images(results, *count);        
-       DGifCloseFile(GifFile);
-       return(0);
-      }
-            
-      if (GifFile->Image.Interlace) {
-       for (Count = i = 0; i < 4; i++) {
-          for (j = InterlacedOffset[i]; j < Height; 
-               j += InterlacedJumps[i]) {
-            Count++;
-            if (DGifGetLine(GifFile, GifRow, Width) == GIF_ERROR) {
-              gif_push_error();
-              i_push_error(0, "Reading GIF line");
-              free_images(results, *count);
-              DGifCloseFile(GifFile);
-              return NULL;
-            }
-            
-            i_ppal(img, 0, Width, j, GifRow);
-          }
+       /* must be only one image wanted and that was it */
+       if (page != -1) {
+         myfree(GifRow);
+         DGifCloseFile(GifFile);
+         return results;
        }
       }
       else {
+       /* skip the image */
+       /* whether interlaced or not, it has the same number of lines */
+       /* giflib does't have an interface to skip the image data */
        for (i = 0; i < Height; i++) {
          if (DGifGetLine(GifFile, GifRow, Width) == GIF_ERROR) {
            gif_push_error();
            i_push_error(0, "Reading GIF line");
-            free_images(results, *count);
+           free_images(results, *count);
+           myfree(GifRow);
            DGifCloseFile(GifFile);
            return NULL;
          }
+       }
 
-          i_ppal(img, 0, Width, i, GifRow);
+       /* kill the comment so we get the right comment for the page */
+       if (comment) {
+         myfree(comment);
+         comment = NULL;
        }
       }
+      ImageNum++;
       break;
     case EXTENSION_RECORD_TYPE:
       /* Skip any extension blocks in file: */
@@ -729,9 +813,59 @@ i_img **i_readgif_multi_low(GifFileType *GifFile, int *count) {
     return NULL;
   }
 
+  if (ImageNum && page != -1) {
+    /* there were images, but the page selected wasn't found */
+    i_push_errorf(0, "page %d not found (%d total)", page, ImageNum);
+    free_images(results, *count);
+    return NULL;
+  }
+
   return results;
 }
 
+#if IM_GIFMAJOR >= 4
+/* giflib declares this incorrectly as EgifOpen */
+extern GifFileType *EGifOpen(void *userData, OutputFunc writeFunc);
+
+static int io_glue_read_cb(GifFileType *gft, GifByteType *buf, int length);
+#endif
+
+/*
+=item i_readgif_multi_wiol(ig, int *count)
+
+=cut
+*/
+
+i_img **
+i_readgif_multi_wiol(io_glue *ig, int *count) {
+  io_glue_commit_types(ig);
+
+  if (ig->source.type == FDSEEK || ig->source.type == FDNOSEEK) {
+    return i_readgif_multi(ig->source.fdseek.fd, count);
+  }
+  else {
+#if IM_GIFMAJOR >= 4
+    GifFileType *GifFile;
+
+    i_clear_error();
+
+    if ((GifFile = DGifOpen((void *)ig, io_glue_read_cb )) == NULL) {
+      gif_push_error();
+      i_push_error(0, "Cannot create giflib callback object");
+      mm_log((1,"i_readgif_multi_wiol: Unable to open callback datasource.\n"));
+      return NULL;
+    }
+    
+    return i_readgif_multi_low(GifFile, count, -1);
+#else
+    i_clear_error();
+    i_push_error(0, "callbacks not supported with giflib3");
+    
+    return NULL;
+#endif
+  }
+}
+
 /*
 =item i_readgif_multi(int fd, int *count)
 
@@ -752,7 +886,7 @@ i_readgif_multi(int fd, int *count) {
     return NULL;
   }
 
-  return i_readgif_multi_low(GifFile, count);
+  return i_readgif_multi_low(GifFile, count, -1);
 }
 
 /*
@@ -782,7 +916,7 @@ i_readgif_multi_scalar(char *data, int length, int *count) {
     return NULL;
   }
 
-  return i_readgif_multi_low(GifFile, count);
+  return i_readgif_multi_low(GifFile, count, -1);
 #else
   return NULL;
 #endif
@@ -818,8 +952,8 @@ i_readgif_multi_callback(i_read_callback_t cb, char *userdata, int *count) {
     return NULL;
   }
 
-  result = i_readgif_multi_low(GifFile, count);
-  free_gen_read_data(gci);
+  result = i_readgif_multi_low(GifFile, count, -1);
+  i_free_gen_read_data(gci);
 
   return result;
 #else
@@ -843,10 +977,8 @@ undef_int
 i_writegif(i_img *im, int fd, int max_colors, int pixdev, int fixedlen, i_color fixed[]) {
   i_color colors[256];
   i_quantize quant;
-  i_gif_opts opts;
   
   memset(&quant, 0, sizeof(quant));
-  memset(&opts, 0, sizeof(opts));
   quant.make_colors = mc_addi;
   quant.mc_colors = colors;
   quant.mc_size = 1<<max_colors;
@@ -854,7 +986,7 @@ i_writegif(i_img *im, int fd, int max_colors, int pixdev, int fixedlen, i_color
   memcpy(colors, fixed, fixedlen * sizeof(i_color));
   quant.translate = pt_perturb;
   quant.perturb = pixdev;
-  return i_writegif_gen(&quant, fd, &im, 1, &opts);
+  return i_writegif_gen(&quant, fd, &im, 1);
 }
 
 /*
@@ -872,16 +1004,16 @@ undef_int
 i_writegifmc(i_img *im, int fd, int max_colors) {
   i_color colors[256];
   i_quantize quant;
-  i_gif_opts opts;
+
+/*    *(char *)0 = 1; */
   
   memset(&quant, 0, sizeof(quant));
-  memset(&opts, 0, sizeof(opts));
   quant.make_colors = mc_none; /* ignored for pt_giflib */
   quant.mc_colors = colors;
   quant.mc_size = 1 << max_colors;
   quant.mc_count = 0;
   quant.translate = pt_giflib;
-  return i_writegif_gen(&quant, fd, &im, 1, &opts);
+  return i_writegif_gen(&quant, fd, &im, 1);
 }
 
 
@@ -973,12 +1105,147 @@ i_readgif_callback(i_read_callback_t cb, char *userdata, int **colour_table, int
   }
 
   result = i_readgif_low(GifFile, colour_table, colours);
-  free_gen_read_data(gci);
+  i_free_gen_read_data(gci);
 
   return result;
 #else
+  i_clear_error();
+  i_push_error(0, "callbacks not supported with giflib3");
+
+  return NULL;
+#endif
+}
+
+#if IM_GIFMAJOR >= 4
+
+static int
+io_glue_read_cb(GifFileType *gft, GifByteType *buf, int length) {
+  io_glue *ig = (io_glue *)gft->UserData;
+
+  return ig->readcb(ig, buf, length);
+}
+
+#endif
+
+i_img *
+i_readgif_wiol(io_glue *ig, int **color_table, int *colors) {
+  io_glue_commit_types(ig);
+
+  if (ig->source.type == FDSEEK || ig->source.type == FDNOSEEK) {
+    int fd = dup(ig->source.fdseek.fd);
+    if (fd < 0) {
+      i_push_error(errno, "dup() failed");
+      return 0;
+    }
+    return i_readgif(fd, color_table, colors);
+  }
+  else {
+#if IM_GIFMAJOR >= 4
+    GifFileType *GifFile;
+
+    i_clear_error();
+
+    if ((GifFile = DGifOpen((void *)ig, io_glue_read_cb )) == NULL) {
+      gif_push_error();
+      i_push_error(0, "Cannot create giflib callback object");
+      mm_log((1,"i_readgif_wiol: Unable to open callback datasource.\n"));
+      return NULL;
+    }
+    
+    return i_readgif_low(GifFile, color_table, colors);
+  
+#else
+  i_clear_error();
+  i_push_error(0, "callbacks not supported with giflib3");
+
   return NULL;
 #endif
+  }
+}
+
+/*
+=item i_readgif_single_low(GifFile, page)
+
+Lower level function to read a single image from a GIF.
+
+page must be non-negative.
+
+=cut
+*/
+static i_img *
+i_readgif_single_low(GifFileType *GifFile, int page) {
+  int count = 0;
+  i_img **imgs;
+
+  imgs = i_readgif_multi_low(GifFile, &count, page);
+
+  if (imgs && count) {
+    i_img *result = imgs[0];
+
+    myfree(imgs);
+    return result;
+  }
+  else {
+    /* i_readgif_multi_low() handles the errors appropriately */
+    return NULL;
+  }
+}
+
+/*
+=item i_readgif_single_wiol(ig, page)
+
+Read a single page from a GIF image file, where the page is indexed
+from 0.
+
+Returns NULL if the page isn't found.
+
+=cut
+*/
+
+i_img *
+i_readgif_single_wiol(io_glue *ig, int page) {
+  io_glue_commit_types(ig);
+
+  i_clear_error();
+
+  if (page < 0) {
+    i_push_error(0, "page must be non-negative");
+    return NULL;
+  }
+
+  if (ig->source.type == FDSEEK || ig->source.type == FDNOSEEK) {
+    GifFileType *GifFile;
+    int fd = dup(ig->source.fdseek.fd);
+    if (fd < 0) {
+      i_push_error(errno, "dup() failed");
+      return NULL;
+    }
+    if ((GifFile = DGifOpenFileHandle(fd)) == NULL) {
+      gif_push_error();
+      i_push_error(0, "Cannot create giflib file object");
+      mm_log((1,"i_readgif: Unable to open file\n"));
+      return NULL;
+    }
+    return i_readgif_single_low(GifFile, page);
+  }
+  else {
+#if IM_GIFMAJOR >= 4
+    GifFileType *GifFile;
+
+    if ((GifFile = DGifOpen((void *)ig, io_glue_read_cb )) == NULL) {
+      gif_push_error();
+      i_push_error(0, "Cannot create giflib callback object");
+      mm_log((1,"i_readgif_wiol: Unable to open callback datasource.\n"));
+      return NULL;
+    }
+    
+    return i_readgif_single_low(GifFile, page);
+#else
+    i_push_error(0, "callbacks not supported with giflib3");
+
+    return NULL;
+#endif
+  }
 }
 
 /*
@@ -992,8 +1259,8 @@ Returns non-zero on success.
 =cut
 */
 static undef_int 
-do_write(GifFileType *gf, i_gif_opts *opts, i_img *img, i_palidx *data) {
-  if (opts->interlace) {
+do_write(GifFileType *gf, int interlace, i_img *img, i_palidx *data) {
+  if (interlace) {
     int i, j;
     for (i = 0; i < 4; ++i) {
       for (j = InterlacedOffset[i]; j < img->ysize; j += InterlacedJumps[i]) {
@@ -1033,27 +1300,31 @@ Returns non-zero on success.
 
 =cut
 */
-static int do_gce(GifFileType *gf, int index, i_gif_opts *opts, int want_trans, int trans_index)
+static int do_gce(GifFileType *gf, i_img *img, int want_trans, int trans_index)
 {
   unsigned char gce[4] = {0};
   int want_gce = 0;
+  int delay;
+  int user_input;
+  int disposal_method;
+
   if (want_trans) {
     gce[0] |= 1;
     gce[3] = trans_index;
     ++want_gce;
   }
-  if (index < opts->delay_count) {
-    gce[1] = opts->delays[index] % 256;
-    gce[2] = opts->delays[index] / 256;
+  if (i_tags_get_int(&img->tags, "gif_delay", 0, &delay)) {
+    gce[1] = delay % 256;
+    gce[2] = delay / 256;
     ++want_gce;
   }
-  if (index < opts->user_input_count) {
-    if (opts->user_input_flags[index])
-      gce[0] |= 2;
+  if (i_tags_get_int(&img->tags, "gif_user_input", 0, &user_input) 
+      && user_input) {
+    gce[0] |= 2;
     ++want_gce;
   }
-  if (index < opts->disposal_count) {
-    gce[0] |= (opts->disposal[index] & 3) << 2;
+  if (i_tags_get_int(&img->tags, "gif_disposal", 0, &disposal_method)) {
+    gce[0] |= (disposal_method & 3) << 2;
     ++want_gce;
   }
   if (want_gce) {
@@ -1065,6 +1336,34 @@ static int do_gce(GifFileType *gf, int index, i_gif_opts *opts, int want_trans,
   return 1;
 }
 
+/*
+=item do_comments(gf, img)
+
+Write any comments in the image.
+
+=cut
+*/
+static int do_comments(GifFileType *gf, i_img *img) {
+  int pos = -1;
+
+  while (i_tags_find(&img->tags, "gif_comment", pos+1, &pos)) {
+    if (img->tags.tags[pos].data) {
+      if (EGifPutComment(gf, img->tags.tags[pos].data) == GIF_ERROR) {
+        return 0;
+      }
+    }
+    else {
+      char buf[50];
+      sprintf(buf, "%d", img->tags.tags[pos].idata);
+      if (EGifPutComment(gf, buf) == GIF_ERROR) {
+        return 0;
+      }
+    }
+  }
+
+  return 1;
+}
+
 /*
 =item do_ns_loop(GifFileType *gf, i_gif_opts *opts)
 
@@ -1076,7 +1375,7 @@ application extension blocks.
 
 =cut
 */
-static int do_ns_loop(GifFileType *gf, i_gif_opts *opts)
+static int do_ns_loop(GifFileType *gf, i_img *img)
 {
   /* EGifPutExtension() doesn't appear to handle application 
      extension blocks in any way
@@ -1090,7 +1389,8 @@ static int do_ns_loop(GifFileType *gf, i_gif_opts *opts)
   */
 #if 0
   /* yes this was another attempt at supporting the loop extension */
-  if (opts->loop_count) {
+  int loop_count;
+  if (i_tags_get_int(&img->tags, "gif_loop", 0, &loop_count)) {
     unsigned char nsle[12] = "NETSCAPE2.0";
     unsigned char subblock[3];
     if (EGifPutExtension(gf, 0xFF, 11, nsle) == GIF_ERROR) {
@@ -1099,8 +1399,8 @@ static int do_ns_loop(GifFileType *gf, i_gif_opts *opts)
       return 0;
     }
     subblock[0] = 1;
-    subblock[1] = opts->loop_count % 256;
-    subblock[2] = opts->loop_count / 256;
+    subblock[1] = loop_count % 256;
+    subblock[2] = loop_count / 256;
     if (EGifPutExtension(gf, 0, 3, subblock) == GIF_ERROR) {
       gif_push_error();
       i_push_error(0, "writing loop extention sub-block");
@@ -1117,20 +1417,21 @@ static int do_ns_loop(GifFileType *gf, i_gif_opts *opts)
 }
 
 /*
-=item make_gif_map(i_quantize *quant, i_gif_opts *opts, int want_trans)
+=item make_gif_map(i_quantize *quant, int want_trans)
 
 Create a giflib color map object from an Imager color map.
 
 =cut
 */
 
-static ColorMapObject *make_gif_map(i_quantize *quant, i_gif_opts *opts,
-                                   int want_trans) {
+static ColorMapObject *make_gif_map(i_quantize *quant, i_img *img, 
+                                    int want_trans) {
   GifColorType colors[256];
   int i;
   int size = quant->mc_count;
   int map_size;
   ColorMapObject *map;
+  i_color trans;
 
   for (i = 0; i < quant->mc_count; ++i) {
     colors[i].Red = quant->mc_colors[i].rgb.r;
@@ -1138,9 +1439,11 @@ static ColorMapObject *make_gif_map(i_quantize *quant, i_gif_opts *opts,
     colors[i].Blue = quant->mc_colors[i].rgb.b;
   }
   if (want_trans) {
-    colors[size].Red = opts->tran_color.rgb.r;
-    colors[size].Green = opts->tran_color.rgb.g;
-    colors[size].Blue = opts->tran_color.rgb.b;
+    if (!i_tags_get_color(&img->tags, "gif_trans_color", 0, &trans))
+      trans.rgb.r = trans.rgb.g = trans.rgb.b = 0;
+    colors[size].Red = trans.rgb.r;
+    colors[size].Green = trans.rgb.g;
+    colors[size].Blue = trans.rgb.b;
     ++size;
   }
   map_size = 1;
@@ -1149,6 +1452,10 @@ static ColorMapObject *make_gif_map(i_quantize *quant, i_gif_opts *opts,
   /* giflib spews for 1 colour maps, reasonable, I suppose */
   if (map_size == 1)
     map_size = 2;
+  while (i < map_size) {
+    colors[i].Red = colors[i].Green = colors[i].Blue = 0;
+    ++i;
+  }
   
   map = MakeMapObject(map_size, colors);
   mm_log((1, "XXX map is at %p and colors at %p\n", map, map->Colors));
@@ -1161,7 +1468,7 @@ static ColorMapObject *make_gif_map(i_quantize *quant, i_gif_opts *opts,
 }
 
 /*
-=item gif_set_version(i_quantize *quant, i_gif_opts *opts)
+=item gif_set_version(i_quantize *quant, i_img *imgs, int count)
 
 We need to call EGifSetGifVersion() before opening the file - put that
 common code here.
@@ -1183,11 +1490,12 @@ with readers.
 =cut
 */
 
-static void gif_set_version(i_quantize *quant, i_gif_opts *opts) {
+static void gif_set_version(i_quantize *quant, i_img **imgs, int count) {
   /* the following crashed giflib
      the EGifSetGifVersion() is seriously borked in giflib
      it's less borked in the ungiflib beta, but we don't have a mechanism
      to distinguish them
+     Needs to be updated to support tags.
      if (opts->delay_count
      || opts->user_input_count
      || opts->disposal_count
@@ -1223,23 +1531,29 @@ if they do it builds that palette.
 A possible improvement might be to eliminate unused colors in the
 images palettes.
 
-=cut */
+=cut
+*/
 static int
-has_common_palette(i_img **imgs, int count, i_quantize *quant, int want_trans,
-                   i_gif_opts *opts) {
+has_common_palette(i_img **imgs, int count, i_quantize *quant, 
+                   int want_trans) {
   int size = quant->mc_count;
-  int i, j;
+  int i;
   int imgn;
-  int x, y;
   char used[256];
 
   /* we try to build a common palette here, if we can manage that, then
      that's the palette we use */
   for (imgn = 0; imgn < count; ++imgn) {
+    int eliminate_unused;
     if (imgs[imgn]->type != i_palette_type)
       return 0;
 
-    if (opts->eliminate_unused) {
+    if (!i_tags_get_int(&imgs[imgn]->tags, "gif_eliminate_unused", 0, 
+                        &eliminate_unused)) {
+      eliminate_unused = 1;
+    }
+
+    if (eliminate_unused) {
       i_palidx *line = mymalloc(sizeof(i_palidx) * imgs[imgn]->xsize);
       int x, y;
       memset(used, 0, sizeof(used));
@@ -1318,8 +1632,7 @@ Returns non-zero on success.
 */
 
 static undef_int
-i_writegif_low(i_quantize *quant, GifFileType *gf, i_img **imgs, int count,
-              i_gif_opts *opts) {
+i_writegif_low(i_quantize *quant, GifFileType *gf, i_img **imgs, int count) {
   unsigned char *result;
   int color_bits;
   ColorMapObject *map;
@@ -1327,120 +1640,276 @@ i_writegif_low(i_quantize *quant, GifFileType *gf, i_img **imgs, int count,
   int imgn, orig_count, orig_size;
   int posx, posy;
   int trans_index;
+  i_mempool mp;
+  int *localmaps;
+  int anylocal;
+  i_img **glob_imgs; /* images that will use the global color map */
+  int glob_img_count;
+  i_color *orig_colors = quant->mc_colors;
+  i_color *glob_colors = NULL;
+  int glob_color_count;
+  int glob_want_trans;
+  int glob_paletted; /* the global map was made from the image palettes */
+  int colors_paletted;
+  int want_trans;
+  int interlace;
+  int gif_background;
+
+  mm_log((1, "i_writegif_low(quant %p, gf  %p, imgs %p, count %d)\n", 
+         quant, gf, imgs, count));
+  
+  /* *((char *)0) = 1; */ /* used to break into the debugger */
+  
+  if (count <= 0) {
+    i_push_error(0, "No images provided to write");
+    return 0; /* what are you smoking? */
+  }
 
-  mm_log((1, "i_writegif_low(quant %p, gf  %p, imgs %p, count %d, opts %p)\n", 
-         quant, gf, imgs, count, opts));
+  i_mempool_init(&mp);
 
-  /**((char *)0) = 1;*/
   /* sanity is nice */
   if (quant->mc_size > 256) 
     quant->mc_size = 256;
   if (quant->mc_count > quant->mc_size)
     quant->mc_count = quant->mc_size;
 
+  if (!i_tags_get_int(&imgs[0]->tags, "gif_screen_width", 0, &scrw))
+    scrw = 0;
+  if (!i_tags_get_int(&imgs[0]->tags, "gif_screen_height", 0, &scrw))
+    scrw = 0;
+
+  anylocal = 0;
+  localmaps = i_mempool_alloc(&mp, sizeof(int) * count);
+  glob_imgs = i_mempool_alloc(&mp, sizeof(i_img *) * count);
+  glob_img_count = 0;
+  glob_want_trans = 0;
   for (imgn = 0; imgn < count; ++imgn) {
-    if (imgn < opts->position_count) {
-      if (imgs[imgn]->xsize + opts->positions[imgn].x > scrw)
-       scrw = imgs[imgn]->xsize + opts->positions[imgn].x;
-      if (imgs[imgn]->ysize + opts->positions[imgn].y > scrw)
-       scrh = imgs[imgn]->ysize + opts->positions[imgn].y;
-    }
+    posx = posy = 0;
+    i_tags_get_int(&imgs[imgn]->tags, "gif_left", 0, &posx);
+    i_tags_get_int(&imgs[imgn]->tags, "gif_top", 0, &posy);
+    if (imgs[imgn]->xsize + posx > scrw)
+      scrw = imgs[imgn]->xsize + posx;
+    if (imgs[imgn]->ysize + posy > scrh)
+      scrh = imgs[imgn]->ysize + posy;
+    if (!i_tags_get_int(&imgs[imgn]->tags, "gif_local_map", 0, localmaps+imgn))
+      localmaps[imgn] = 0;
+    if (localmaps[imgn])
+      anylocal = 1;
     else {
-      if (imgs[imgn]->xsize > scrw)
-       scrw = imgs[imgn]->xsize;
-      if (imgs[imgn]->ysize > scrh)
-       scrh = imgs[imgn]->ysize;
+      if (imgs[imgn]->channels == 4) {
+        glob_want_trans = 1;
+      }
+      glob_imgs[glob_img_count++] = imgs[imgn];
     }
   }
-
-  if (count <= 0) {
-    i_push_error(0, "No images provided to write");
-    return 0; /* what are you smoking? */
-  }
+  glob_want_trans = glob_want_trans && quant->transp != tr_none ;
 
   orig_count = quant->mc_count;
   orig_size = quant->mc_size;
 
-  if (opts->each_palette) {
-    int want_trans = quant->transp != tr_none 
-      && imgs[0]->channels == 4;
-
-    /* if the caller gives us too many colours we can't do transparency */
-    if (want_trans && quant->mc_count == 256)
-      want_trans = 0;
-    /* if they want transparency but give us a big size, make it smaller
-       to give room for a transparency colour */
-    if (want_trans && quant->mc_size == 256)
+  if (glob_img_count) {
+    /* this is ugly */
+    glob_colors = i_mempool_alloc(&mp, sizeof(i_color) * quant->mc_size);
+    quant->mc_colors = glob_colors;
+    memcpy(glob_colors, orig_colors, sizeof(i_color) * quant->mc_count);
+    /* we have some images that want to use the global map */
+    if (glob_want_trans && quant->mc_count == 256) {
+      mm_log((2, "  disabling transparency for global map - no space\n"));
+      glob_want_trans = 0;
+    }
+    if (glob_want_trans && quant->mc_size == 256) {
+      mm_log((2, "  reserving color for transparency\n"));
       --quant->mc_size;
-
-    /* we always generate a global palette - this lets systems with a 
-       broken giflib work */
-    if (has_common_palette(imgs, 1, quant, want_trans, opts)) {
-      result = quant_paletted(quant, imgs[0]);
     }
-    else {
-      quant_makemap(quant, imgs, 1);
-      result = quant_translate(quant, imgs[0]);
+    if (has_common_palette(glob_imgs, glob_img_count, quant, want_trans)) {
+      glob_paletted = 1;
     }
-    if (want_trans) {
-      trans_index = quant->mc_count;
-      quant_transparent(quant, result, imgs[0], trans_index);
+    else {
+      glob_paletted = 0;
+      i_quant_makemap(quant, glob_imgs, glob_img_count);
     }
+    glob_color_count = quant->mc_count;
+    quant->mc_colors = orig_colors;
+  }
 
-    if ((map = make_gif_map(quant, opts, want_trans)) == NULL) {
-      myfree(result);
-      EGifCloseFile(gf);
-      mm_log((1, "Error in MakeMapObject."));
-      return 0;
+  /* use the global map if we have one, otherwise use the local map */
+  gif_background = 0;
+  if (glob_colors) {
+    quant->mc_colors = glob_colors;
+    quant->mc_count = glob_color_count;
+    want_trans = glob_want_trans && imgs[0]->channels == 4;
+
+    if (!i_tags_get_int(&imgs[0]->tags, "gif_background", 0, &gif_background))
+      gif_background = 0;
+    if (gif_background < 0)
+      gif_background = 0;
+    if (gif_background >= glob_color_count)
+      gif_background = 0;
+  }
+  else {
+    want_trans = quant->transp != tr_none && imgs[0]->channels == 4;
+    if (has_common_palette(imgs, 1, quant, want_trans)) {
+      colors_paletted = 1;
     }
-
-    color_bits = 1;
-    while (quant->mc_size > (1 << color_bits))
+    else {
+      colors_paletted = 0;
+      i_quant_makemap(quant, imgs, 1);
+    }
+  }
+  if ((map = make_gif_map(quant, imgs[0], want_trans)) == NULL) {
+    i_mempool_destroy(&mp);
+    quant->mc_colors = orig_colors;
+    EGifCloseFile(gf);
+    mm_log((1, "Error in MakeMapObject"));
+    return 0;
+  }
+  color_bits = 1;
+  if (anylocal) {
+    /* since we don't know how big some the local palettes could be
+       we need to base the bits on the maximum number of colors */
+    while (orig_size > (1 << color_bits))
       ++color_bits;
+  }
+  else {
+    int count = quant->mc_count;
+    if (want_trans)
+      ++count;
+    while (count > (1 << color_bits))
+      ++color_bits;
+  }
   
-    if (EGifPutScreenDesc(gf, scrw, scrh, color_bits, 0, map) == GIF_ERROR) {
-      gif_push_error();
-      i_push_error(0, "Could not save screen descriptor");
-      FreeMapObject(map);
-      myfree(result);
-      EGifCloseFile(gf);
-      mm_log((1, "Error in EGifPutScreenDesc."));
-      return 0;
-    }
+  if (EGifPutScreenDesc(gf, scrw, scrh, color_bits, 
+                        gif_background, map) == GIF_ERROR) {
+    i_mempool_destroy(&mp);
+    quant->mc_colors = orig_colors;
+    gif_push_error();
+    i_push_error(0, "Could not save screen descriptor");
     FreeMapObject(map);
+    myfree(result);
+    EGifCloseFile(gf);
+    mm_log((1, "Error in EGifPutScreenDesc."));
+    return 0;
+  }
+  FreeMapObject(map);
 
-    if (!do_ns_loop(gf, opts))
-      return 0;
+  if (!i_tags_get_int(&imgs[0]->tags, "gif_left", 0, &posx))
+    posx = 0;
+  if (!i_tags_get_int(&imgs[0]->tags, "gif_top", 0, &posy))
+    posy = 0;
 
-    if (!do_gce(gf, 0, opts, want_trans, trans_index)) {
-      myfree(result);
-      EGifCloseFile(gf);
-      return 0;
-    }
-    if (opts->position_count) {
-      posx = opts->positions[0].x;
-      posy = opts->positions[0].y;
-    }
-    else
-      posx = posy = 0;
-    if (EGifPutImageDesc(gf, posx, posy, imgs[0]->xsize, imgs[0]->ysize, 
-                        opts->interlace, NULL) == GIF_ERROR) {
-      gif_push_error();
-      i_push_error(0, "Could not save image descriptor");
-      EGifCloseFile(gf);
-      mm_log((1, "Error in EGifPutImageDesc."));
-      return 0;
+  if (!localmaps[0]) {
+    map = NULL;
+    colors_paletted = glob_paletted;
+  }
+  else {
+    /* if this image has a global map the colors in quant don't
+       belong to this image, so build a palette */
+    if (glob_colors) {
+      /* generate the local map for this image */
+      quant->mc_colors = orig_colors;
+      quant->mc_size = orig_size;
+      quant->mc_count = orig_count;
+      want_trans = quant->transp != tr_none && imgs[0]->channels == 4;
+
+      /* if the caller gives us too many colours we can't do transparency */
+      if (want_trans && quant->mc_count == 256)
+        want_trans = 0;
+      /* if they want transparency but give us a big size, make it smaller
+         to give room for a transparency colour */
+      if (want_trans && quant->mc_size == 256)
+        --quant->mc_size;
+      if (has_common_palette(imgs, 1, quant, want_trans)) {
+        colors_paletted = 1;
+      }
+      else {
+        colors_paletted = 0;
+        i_quant_makemap(quant, imgs, 1);
+      }
+      if ((map = make_gif_map(quant, imgs[0], want_trans)) == NULL) {
+        i_mempool_destroy(&mp);
+        EGifCloseFile(gf);
+        quant->mc_colors = orig_colors;
+        mm_log((1, "Error in MakeMapObject"));
+        return 0;
+      }
     }
-    if (!do_write(gf, opts, imgs[0], result)) {
-      EGifCloseFile(gf);
-      myfree(result);
-      return 0;
+    else {
+      /* the map we wrote was the map for this image - don't set the local 
+         map */
+      map = NULL;
     }
-    for (imgn = 1; imgn < count; ++imgn) {
+  }
+
+  if (colors_paletted)
+    result = quant_paletted(quant, imgs[0]);
+  else
+    result = i_quant_translate(quant, imgs[0]);
+  if (!result) {
+    i_mempool_destroy(&mp);
+    quant->mc_colors = orig_colors;
+    EGifCloseFile(gf);
+    return 0;
+  }
+  if (want_trans) {
+    i_quant_transparent(quant, result, imgs[0], quant->mc_count);
+    trans_index = quant->mc_count;
+  }
+
+  if (!do_ns_loop(gf, imgs[0])) {
+    i_mempool_destroy(&mp);
+    quant->mc_colors = orig_colors;
+    return 0;
+  }
+
+  if (!do_gce(gf, imgs[0], want_trans, trans_index)) {
+    i_mempool_destroy(&mp);
+    quant->mc_colors = orig_colors;
+    myfree(result);
+    EGifCloseFile(gf);
+    return 0;
+  }
+
+  if (!do_comments(gf, imgs[0])) {
+    i_mempool_destroy(&mp);
+    quant->mc_colors = orig_colors;
+    myfree(result);
+    EGifCloseFile(gf);
+    return 0;
+  }
+
+  if (!i_tags_get_int(&imgs[0]->tags, "gif_interlace", 0, &interlace))
+    interlace = 0;
+  if (EGifPutImageDesc(gf, posx, posy, imgs[0]->xsize, imgs[0]->ysize, 
+                       interlace, map) == GIF_ERROR) {
+    i_mempool_destroy(&mp);
+    quant->mc_colors = orig_colors;
+    gif_push_error();
+    i_push_error(0, "Could not save image descriptor");
+    EGifCloseFile(gf);
+    mm_log((1, "Error in EGifPutImageDesc."));
+    return 0;
+  }
+  if (map)
+    FreeMapObject(map);
+
+  if (!do_write(gf, interlace, imgs[0], result)) {
+    i_mempool_destroy(&mp);
+    quant->mc_colors = orig_colors;
+    EGifCloseFile(gf);
+    myfree(result);
+    return 0;
+  }
+  myfree(result);
+
+  /* that first awful image is out of the way, do the rest */
+  for (imgn = 1; imgn < count; ++imgn) {
+    if (localmaps[imgn]) {
+      quant->mc_colors = orig_colors;
       quant->mc_count = orig_count;
       quant->mc_size = orig_size;
+
       want_trans = quant->transp != tr_none 
-       && imgs[0]->channels == 4;
+       && imgs[imgn]->channels == 4;
       /* if the caller gives us too many colours we can't do transparency */
       if (want_trans && quant->mc_count == 256)
        want_trans = 0;
@@ -1449,185 +1918,113 @@ i_writegif_low(i_quantize *quant, GifFileType *gf, i_img **imgs, int count,
       if (want_trans && quant->mc_size == 256)
        --quant->mc_size;
 
-      if (has_common_palette(imgs+imgn, 1, quant, want_trans, opts)) {
+      if (has_common_palette(imgs+imgn, 1, quant, want_trans)) {
         result = quant_paletted(quant, imgs[imgn]);
       }
       else {
-        quant_makemap(quant, imgs+imgn, 1);
-        result = quant_translate(quant, imgs[imgn]);
+        i_quant_makemap(quant, imgs+imgn, 1);
+        result = i_quant_translate(quant, imgs[imgn]);
+      }
+      if (!result) {
+        i_mempool_destroy(&mp);
+        quant->mc_colors = orig_colors;
+        EGifCloseFile(gf);
+        mm_log((1, "error in i_quant_translate()"));
+        return 0;
       }
       if (want_trans) {
-        quant_transparent(quant, result, imgs[imgn], quant->mc_count);
+        i_quant_transparent(quant, result, imgs[imgn], quant->mc_count);
         trans_index = quant->mc_count;
       }
 
-      if (!do_gce(gf, imgn, opts, want_trans, quant->mc_count)) {
-       myfree(result);
-       EGifCloseFile(gf);
-       return 0;
-      }
-      if ((map = make_gif_map(quant, opts, want_trans)) == NULL) {
-       myfree(result);
-       EGifCloseFile(gf);
-       mm_log((1, "Error in MakeMapObject."));
-       return 0;
-      }
-      if (imgn < opts->position_count) {
-       posx = opts->positions[imgn].x;
-       posy = opts->positions[imgn].y;
-      }
-      else
-       posx = posy = 0;
-      if (EGifPutImageDesc(gf, posx, posy, imgs[imgn]->xsize, 
-                          imgs[imgn]->ysize, opts->interlace, 
-                          map) == GIF_ERROR) {
-       gif_push_error();
-       i_push_error(0, "Could not save image descriptor");
-       myfree(result);
-       FreeMapObject(map);
-       EGifCloseFile(gf);
-       mm_log((1, "Error in EGifPutImageDesc."));
-       return 0;
-      }
-      FreeMapObject(map);
-      
-      if (!do_write(gf, opts, imgs[imgn], result)) {
-       EGifCloseFile(gf);
-       myfree(result);
-       return 0;
-      }
-      myfree(result);
-    }
-  }
-  else {
-    int want_trans;
-    int do_quant_paletted = 0;
-
-    /* get a palette entry for the transparency iff we have an image
-       with an alpha channel */
-    want_trans = 0;
-    for (imgn = 0; imgn < count; ++imgn) {
-      if (imgs[imgn]->channels == 4) {
-       ++want_trans;
-       break;
+      if ((map = make_gif_map(quant, imgs[imgn], want_trans)) == NULL) {
+        i_mempool_destroy(&mp);
+        quant->mc_colors = orig_colors;
+        myfree(result);
+        EGifCloseFile(gf);
+        mm_log((1, "Error in MakeMapObject."));
+        return 0;
       }
     }
-    want_trans = want_trans && quant->transp != tr_none 
-      && quant->mc_count < 256;
-    if (want_trans && quant->mc_size == 256)
-      --quant->mc_size;
-
-    /* handle the first image separately - since we allow giflib
-       conversion and giflib doesn't give us a separate function to build
-       the colormap. */
-     
-    /* produce a colour map */
-    if (has_common_palette(imgs, count, quant, want_trans, opts)) {
-      result = quant_paletted(quant, imgs[0]);
-      ++do_quant_paletted;
-    }
     else {
-      quant_makemap(quant, imgs, count);
-      result = quant_translate(quant, imgs[0]);
+      quant->mc_colors = glob_colors;
+      quant->mc_count = glob_color_count;
+      if (glob_paletted)
+        result = quant_paletted(quant, imgs[imgn]);
+      else
+        result = i_quant_translate(quant, imgs[imgn]);
+      want_trans = glob_want_trans && imgs[imgn]->channels == 4;
+      if (want_trans) {
+        i_quant_transparent(quant, result, imgs[imgn], quant->mc_count);
+        trans_index = quant->mc_count;
+      }
+      map = NULL;
     }
 
-    if ((map = make_gif_map(quant, opts, want_trans)) == NULL) {
+    if (!do_gce(gf, imgs[imgn], want_trans, trans_index)) {
+      i_mempool_destroy(&mp);
+      quant->mc_colors = orig_colors;
       myfree(result);
       EGifCloseFile(gf);
-      mm_log((1, "Error in MakeMapObject"));
       return 0;
     }
-    color_bits = 1;
-    while (quant->mc_count > (1 << color_bits))
-      ++color_bits;
 
-    if (EGifPutScreenDesc(gf, scrw, scrh, color_bits, 0, map) == GIF_ERROR) {
-      gif_push_error();
-      i_push_error(0, "Could not save screen descriptor");
-      FreeMapObject(map);
+    if (!do_comments(gf, imgs[imgn])) {
+      i_mempool_destroy(&mp);
+      quant->mc_colors = orig_colors;
       myfree(result);
       EGifCloseFile(gf);
-      mm_log((1, "Error in EGifPutScreenDesc."));
       return 0;
     }
-    FreeMapObject(map);
-
-    if (!do_ns_loop(gf, opts))
-      return 0;
 
-    if (!do_gce(gf, 0, opts, want_trans, quant->mc_count)) {
-      myfree(result);
-      EGifCloseFile(gf);
-      return 0;
-    }
-    if (opts->position_count) {
-      posx = opts->positions[0].x;
-      posy = opts->positions[0].y;
-    }
-    else
-      posx = posy = 0;
-    if (EGifPutImageDesc(gf, posx, posy, imgs[0]->xsize, imgs[0]->ysize, 
-                        opts->interlace, NULL) == GIF_ERROR) {
+    if (!i_tags_get_int(&imgs[imgn]->tags, "gif_left", 0, &posx))
+      posx = 0;
+    if (!i_tags_get_int(&imgs[imgn]->tags, "gif_top", 0, &posy))
+      posy = 0;
+
+    if (!i_tags_get_int(&imgs[imgn]->tags, "gif_interlace", 0, &interlace))
+      interlace = 0;
+    if (EGifPutImageDesc(gf, posx, posy, imgs[imgn]->xsize, 
+                         imgs[imgn]->ysize, interlace, map) == GIF_ERROR) {
+      i_mempool_destroy(&mp);
+      quant->mc_colors = orig_colors;
       gif_push_error();
       i_push_error(0, "Could not save image descriptor");
+      myfree(result);
+      if (map)
+        FreeMapObject(map);
       EGifCloseFile(gf);
       mm_log((1, "Error in EGifPutImageDesc."));
       return 0;
     }
-    if (want_trans && imgs[0]->channels == 4) 
-      quant_transparent(quant, result, imgs[0], quant->mc_count);
-
-    if (!do_write(gf, opts, imgs[0], result)) {
+    if (map)
+      FreeMapObject(map);
+    
+    if (!do_write(gf, interlace, imgs[imgn], result)) {
+      i_mempool_destroy(&mp);
+      quant->mc_colors = orig_colors;
       EGifCloseFile(gf);
       myfree(result);
       return 0;
     }
     myfree(result);
-
-    for (imgn = 1; imgn < count; ++imgn) {
-      int local_trans;
-      if (do_quant_paletted)
-        result = quant_paletted(quant, imgs[imgn]);
-      else
-        result = quant_translate(quant, imgs[imgn]);
-      local_trans = want_trans && imgs[imgn]->channels == 4;
-      if (local_trans)
-       quant_transparent(quant, result, imgs[imgn], quant->mc_count);
-      if (!do_gce(gf, imgn, opts, local_trans, quant->mc_count)) {
-       myfree(result);
-       EGifCloseFile(gf);
-       return 0;
-      }
-      if (imgn < opts->position_count) {
-       posx = opts->positions[imgn].x;
-       posy = opts->positions[imgn].y;
-      }
-      else
-       posx = posy = 0;
-      if (EGifPutImageDesc(gf, posx, posy, 
-                          imgs[imgn]->xsize, imgs[imgn]->ysize, 
-                          opts->interlace, NULL) == GIF_ERROR) {
-       gif_push_error();
-       i_push_error(0, "Could not save image descriptor");
-       myfree(result);
-       EGifCloseFile(gf);
-       mm_log((1, "Error in EGifPutImageDesc."));
-       return 0;
-      }
-      if (!do_write(gf, opts, imgs[imgn], result)) {
-       EGifCloseFile(gf);
-       myfree(result);
-       return 0;
-      }
-      myfree(result);
-    }
   }
+
   if (EGifCloseFile(gf) == GIF_ERROR) {
+    i_mempool_destroy(&mp);
     gif_push_error();
     i_push_error(0, "Could not close GIF file");
     mm_log((1, "Error in EGifCloseFile\n"));
     return 0;
   }
+  if (glob_colors) {
+    int i;
+    for (i = 0; i < glob_color_count; ++i)
+      orig_colors[i] = glob_colors[i];
+  }
+
+  i_mempool_destroy(&mp);
+  quant->mc_colors = orig_colors;
 
   return 1;
 }
@@ -1647,15 +2044,14 @@ Returns non-zero on success.
 */
 
 undef_int
-i_writegif_gen(i_quantize *quant, int fd, i_img **imgs, int count, 
-              i_gif_opts *opts) {
+i_writegif_gen(i_quantize *quant, int fd, i_img **imgs, int count) {
   GifFileType *gf;
 
   i_clear_error();
-  mm_log((1, "i_writegif_gen(quant %p, fd %d, imgs %p, count %d, opts %p)\n", 
-         quant, fd, imgs, count, opts));
+  mm_log((1, "i_writegif_gen(quant %p, fd %d, imgs %p, count %d)\n", 
+         quant, fd, imgs, count));
 
-  gif_set_version(quant, opts);
+  gif_set_version(quant, imgs, count);
 
   if ((gf = EGifOpenFileHandle(fd)) == NULL) {
     gif_push_error();
@@ -1664,7 +2060,7 @@ i_writegif_gen(i_quantize *quant, int fd, i_img **imgs, int count,
     return 0;
   }
 
-  return i_writegif_low(quant, gf, imgs, count, opts);
+  return i_writegif_low(quant, gf, imgs, count);
 }
 
 #if IM_GIFMAJOR >= 4
@@ -1699,35 +2095,97 @@ Returns non-zero on success.
 
 undef_int
 i_writegif_callback(i_quantize *quant, i_write_callback_t cb, char *userdata,
-                   int maxlength, i_img **imgs, int count, i_gif_opts *opts)
+                   int maxlength, i_img **imgs, int count)
 {
 #if IM_GIFMAJOR >= 4
   GifFileType *gf;
   i_gen_write_data *gwd = i_gen_write_data_new(cb, userdata, maxlength);
-  /* giflib declares this incorrectly as EgifOpen */
-  extern GifFileType *EGifOpen(void *userData, OutputFunc writeFunc);
   int result;
 
   i_clear_error();
 
-  mm_log((1, "i_writegif_callback(quant %p, i_write_callback_t %p, userdata $p, maxlength %d, imgs %p, count %d, opts %p)\n", 
-         quant, cb, userdata, maxlength, imgs, count, opts));
+  mm_log((1, "i_writegif_callback(quant %p, i_write_callback_t %p, userdata $p, maxlength %d, imgs %p, count %d)\n", 
+         quant, cb, userdata, maxlength, imgs, count));
   
   if ((gf = EGifOpen(gwd, &gif_writer_callback)) == NULL) {
     gif_push_error();
     i_push_error(0, "Cannot create GIF file object");
     mm_log((1, "Error in EGifOpenFileHandle, unable to write image.\n"));
-    free_gen_write_data(gwd, 0);
+    i_free_gen_write_data(gwd, 0);
     return 0;
   }
 
-  result = i_writegif_low(quant, gf, imgs, count, opts);
-  return free_gen_write_data(gwd, result);
+  result = i_writegif_low(quant, gf, imgs, count);
+  return i_free_gen_write_data(gwd, result);
 #else
+  i_clear_error();
+  i_push_error(0, "callbacks not supported with giflib3");
+
   return 0;
 #endif
 }
 
+#if IM_GIFMAJOR >= 4
+
+static int
+io_glue_write_cb(GifFileType *gft, const GifByteType *data, int length) {
+  io_glue *ig = (io_glue *)gft->UserData;
+
+  return ig->writecb(ig, data, length);
+}
+
+#endif
+
+/*
+=item i_writegif_wiol(ig, quant, opts, imgs, count)
+
+=cut
+*/
+undef_int
+i_writegif_wiol(io_glue *ig, i_quantize *quant, i_img **imgs,
+                int count) {
+  io_glue_commit_types(ig);
+
+  if (ig->source.type == FDSEEK || ig->source.type == FDNOSEEK) {
+    int fd = dup(ig->source.fdseek.fd);
+    if (fd < 0) {
+      i_push_error(errno, "dup() failed");
+      return 0;
+    }
+    /* giflib opens the fd with fdopen(), which is then closed when fclose()
+       is called - dup it so the caller's fd isn't closed */
+    return i_writegif_gen(quant, fd, imgs, count);
+  }
+  else {
+#if IM_GIFMAJOR >= 4
+    GifFileType *GifFile;
+    int result;
+
+    i_clear_error();
+
+    gif_set_version(quant, imgs, count);
+
+    if ((GifFile = EGifOpen((void *)ig, io_glue_write_cb )) == NULL) {
+      gif_push_error();
+      i_push_error(0, "Cannot create giflib callback object");
+      mm_log((1,"i_writegif_wiol: Unable to open callback datasource.\n"));
+      return 0;
+    }
+    
+    result = i_writegif_low(quant, GifFile, imgs, count);
+    
+    ig->closecb(ig);
+
+    return result;
+#else
+    i_clear_error();
+    i_push_error(0, "callbacks not supported with giflib3");
+    
+    return 0;
+#endif
+  }
+}
+
 /*
 =item gif_error_msg(int code)
 
@@ -1737,7 +2195,8 @@ returns a string that describes that error.
 The returned pointer points to a static buffer, either from a literal
 C string or a static buffer.
 
-=cut */
+=cut
+*/
 
 static char const *gif_error_msg(int code) {
   static char msg[80];