]> git.imager.perl.org - imager.git/blobdiff - gif.c
initial cut
[imager.git] / gif.c
diff --git a/gif.c b/gif.c
index 55f923aaf1172ed1b58865affe634961943674e9..9e139fb14e059f8caf88c7dff38e533f0cbf900b 100644 (file)
--- a/gif.c
+++ b/gif.c
@@ -1,15 +1,91 @@
 #include "image.h"
 #include <gif_lib.h>
 
+/* XXX: Reading still needs to support reading all those gif properties */
+
+/*
+=head1 NAME
+
+gif.c - read and write gif files for Imager
+
+=head1 SYNOPSIS
+
+  i_img *img;
+  i_img *imgs[count];
+  int fd;
+  int *colour_table,
+  int colours;
+  int max_colours; // number of bits per colour
+  int pixdev;  // how much noise to add 
+  i_color fixed[N]; // fixed palette entries 
+  int fixedlen; // number of fixed colours 
+  int success; // non-zero on success
+  char *data; // a GIF file in memory
+  int length; // how big data is 
+  int reader(char *, char *, int, int);
+  int writer(char *, char *, int);
+  char *userdata; // user's data, whatever it is
+  i_quantize quant;
+  i_gif_opts opts;
+
+  img = i_readgif(fd, &colour_table, &colours);
+  success = i_writegif(img, fd, max_colours, pixdev, fixedlen, fixed);
+  success = i_writegifmc(img, fd, max_colours);
+  img = i_readgif_scalar(data, length, &colour_table, &colours);
+  img = i_readgif_callback(cb, userdata, &colour_table, &colours);
+  success = i_writegif_gen(&quant, fd, imgs, count, &opts);
+  success = i_writegif_callback(&quant, writer, userdata, maxlength, 
+                                imgs, count, &opts);
+
+=head1 DESCRIPTION
+
+This source file provides the C level interface to reading and writing
+GIF files for Imager.
+
+This has been tested with giflib 3 and 4, though you lose the callback
+functionality with giflib3.
+
+=head1 REFERENCE
+
+=over
+
+=cut
+*/
+
+static char const *gif_error_msg(int code);
+static void gif_push_error(void);
 
 #if IM_GIFMAJOR >= 4
 
+static int gif_read_callback(GifFileType *gft, GifByteType *buf, int length);
+
+/*
+=item gif_scalar_info
+
+Internal.  A structure passed to the reader function used for reading
+GIFs from scalars.
+
+Used with giflib 4 and later.
+
+=cut
+*/
+
 struct gif_scalar_info {
   char *data;
   int length;
   int cpos;
 };
 
+/*
+=item my_gif_inputfunc(GifFileType *gft, GifByteType *buf, int length)
+
+Internal.  The reader callback passed to giflib.
+
+Used with giflib 4 and later.
+
+=cut
+*/
+
 int
 my_gif_inputfunc(GifFileType* gft, GifByteType *buf,int length) {
   struct gif_scalar_info *gsi=(struct gif_scalar_info *)gft->UserData;
@@ -24,29 +100,54 @@ my_gif_inputfunc(GifFileType* gft, GifByteType *buf,int length) {
 
 #endif
 
-/*
-  This file needs a complete rewrite 
-
-  This file needs a complete rewrite 
 
-  Maybe not anymore, though reading still needs to support reading
-  all those gif properties.
-*/
 
 /* Make some variables global, so we could access them faster: */
 
 static int
-    ImageNum = 0,
-    BackGround = 0,
-    ColorMapSize = 0,
-    InterlacedOffset[] = { 0, 4, 2, 1 }, /* The way Interlaced image should. */
-    InterlacedJumps[] = { 8, 8, 4, 2 };    /* be read - offsets and jumps... */
-static ColorMapObject *ColorMap;
+  InterlacedOffset[] = { 0, 4, 2, 1 }, /* The way Interlaced image should. */
+  InterlacedJumps[] = { 8, 8, 4, 2 };    /* be read - offsets and jumps... */
+
+
+
+static
+void
+i_colortable_copy(int **colour_table, int *colours, ColorMapObject *colourmap) {
+  GifColorType *mapentry;
+  int q;
+  int colourmapsize = colourmap->ColorCount;
+
+  if(colours) *colours = colourmapsize;
+  if(!colour_table) return;
+  
+  *colour_table = mymalloc(sizeof(int) * colourmapsize * 3);
+  memset(*colour_table, 0, sizeof(int) * colourmapsize * 3);
+
+  for(q=0; q<colourmapsize; q++) {
+    mapentry = &colourmap->Colors[q];
+    (*colour_table)[q*3 + 0] = mapentry->Red;
+    (*colour_table)[q*3 + 1] = mapentry->Green;
+    (*colour_table)[q*3 + 2] = mapentry->Blue;
+  }
+}
+
+
+/*
+=item i_readgif_low(GifFileType *GifFile, int **colour_table, int *colours)
+
+Internal.  Low-level function for reading a GIF file.  The caller must
+create the appropriate GifFileType object and pass it in.
+
+=cut
+*/
 
 i_img *
 i_readgif_low(GifFileType *GifFile, int **colour_table, int *colours) {
   i_img *im;
   int i, j, Size, Row, Col, Width, Height, ExtCode, Count, x;
+  int cmapcnt = 0, ImageNum = 0, BackGround = 0, ColorMapSize = 0;
+  ColorMapObject *ColorMap;
   GifRecordType RecordType;
   GifByteType *Extension;
   
@@ -54,102 +155,141 @@ i_readgif_low(GifFileType *GifFile, int **colour_table, int *colours) {
   static GifColorType *ColorMapEntry;
   i_color col;
 
-  /*  unsigned char *Buffer, *BufferP; */
-
   mm_log((1,"i_readgif_low(GifFile %p, colour_table %p, colours %p)\n", GifFile, colour_table, colours));
 
+  /* it's possible that the caller has called us with *colour_table being
+     non-NULL, but we check that to see if we need to free an allocated
+     colour table on error.
+  */
+  if (colour_table) *colour_table = NULL;
+
   BackGround = GifFile->SBackGroundColor;
   ColorMap = (GifFile->Image.ColorMap ? GifFile->Image.ColorMap : GifFile->SColorMap);
-  ColorMapSize = ColorMap->ColorCount;
-
-  /* **************************************** */
-  if(colour_table != NULL) {
-    int q;
-    *colour_table=mymalloc(sizeof(int *) * ColorMapSize * 3);
-    if(*colour_table == NULL) 
-      m_fatal(0,"Failed to allocate memory for GIF colour table, aborted."); 
-    
-    memset(*colour_table, 0, sizeof(int *) * ColorMapSize * 3);
 
-    for(q=0; q<ColorMapSize; q++) {
-      ColorMapEntry = &ColorMap->Colors[q];
-      (*colour_table)[q*3 + 0]=ColorMapEntry->Red;
-      (*colour_table)[q*3 + 1]=ColorMapEntry->Green;
-      (*colour_table)[q*3 + 2]=ColorMapEntry->Blue;
-    }
+  if (ColorMap) {
+    ColorMapSize = ColorMap->ColorCount;
+    i_colortable_copy(colour_table, colours, ColorMap);
+    cmapcnt++;
   }
-
-  if(colours != NULL) {
-    *colours = ColorMapSize;
-  }
-  
-  /* **************************************** */
-  im=i_img_empty_ch(NULL,GifFile->SWidth,GifFile->SHeight,3);
   
+
+  im = i_img_empty_ch(NULL, GifFile->SWidth, GifFile->SHeight, 3);
+
   Size = GifFile->SWidth * sizeof(GifPixelType); 
   
-  if ((GifRow = (GifRowType) mymalloc(Size)) == NULL)
-    m_fatal(0,"Failed to allocate memory required, aborted."); /* First row. */
+  GifRow = (GifRowType) mymalloc(Size);
 
   for (i = 0; i < GifFile->SWidth; i++) GifRow[i] = GifFile->SBackGroundColor;
   
   /* Scan the content of the GIF file and load the image(s) in: */
   do {
     if (DGifGetRecordType(GifFile, &RecordType) == GIF_ERROR) {
-      PrintGifError();
-      exit(-1);
+      gif_push_error();
+      i_push_error(0, "Unable to get record type");
+      if (colour_table && *colour_table) {
+       myfree(*colour_table);
+       *colour_table = NULL;
+      }
+      i_img_destroy(im);
+      DGifCloseFile(GifFile);
+      return NULL;
     }
     
     switch (RecordType) {
     case IMAGE_DESC_RECORD_TYPE:
       if (DGifGetImageDesc(GifFile) == GIF_ERROR) {
-       PrintGifError();
-       exit(-1);
+       gif_push_error();
+       i_push_error(0, "Unable to get image descriptor");
+       if (colour_table && *colour_table) {
+         myfree(*colour_table);
+         *colour_table = NULL;
+       }
+       i_img_destroy(im);
+       DGifCloseFile(GifFile);
+       return NULL;
       }
+
+      if (( ColorMap = (GifFile->Image.ColorMap ? GifFile->Image.ColorMap : GifFile->SColorMap) )) {
+       mm_log((1, "Adding local colormap\n"));
+       ColorMapSize = ColorMap->ColorCount;
+       if ( cmapcnt == 0) {
+         i_colortable_copy(colour_table, colours, ColorMap);
+         cmapcnt++;
+       }
+      } 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");
+       /* we can't have allocated a colour table here */
+       i_img_destroy(im);
+       DGifCloseFile(GifFile);
+       return NULL;
+      }
+      
       Row = GifFile->Image.Top; /* Image Position relative to Screen. */
       Col = GifFile->Image.Left;
       Width = GifFile->Image.Width;
       Height = GifFile->Image.Height;
       ImageNum++;
-      mm_log((1,"i_readgif: Image %d at (%d, %d) [%dx%d]: \n",ImageNum, Col, Row, Width, Height));
+      mm_log((1,"i_readgif_low: Image %d at (%d, %d) [%dx%d]: \n",ImageNum, Col, Row, Width, Height));
 
       if (GifFile->Image.Left + GifFile->Image.Width > GifFile->SWidth ||
          GifFile->Image.Top + GifFile->Image.Height > GifFile->SHeight) {
-       fprintf(stderr, "Image %d is not confined to screen dimension, aborted.\n",ImageNum);
+       i_push_errorf(0, "Image %d is not confined to screen dimension, aborted.\n",ImageNum);
+       if (colour_table && *colour_table) {
+         myfree(*colour_table);
+         *colour_table = NULL;
+       }
+       i_img_destroy(im);
+       DGifCloseFile(GifFile);
        return(0);
       }
       if (GifFile->Image.Interlace) {
 
        for (Count = i = 0; i < 4; i++) for (j = Row + InterlacedOffset[i]; j < Row + Height; j += InterlacedJumps[i]) {
          Count++;
-         if (DGifGetLine(GifFile, &GifRow[Col], Width) == GIF_ERROR) {
-           mm_log((1,"fatal"));
-           exit(-1);
+         if (DGifGetLine(GifFile, GifRow, Width) == GIF_ERROR) {
+           gif_push_error();
+           i_push_error(0, "Reading GIF line");
+           if (colour_table && *colour_table) {
+             myfree(*colour_table);
+             *colour_table = NULL;
+           }
+           i_img_destroy(im);
+           DGifCloseFile(GifFile);
+           return NULL;
          }
          
-         for (x = 0; x < GifFile->SWidth; x++) {
+         for (x = 0; x < Width; x++) {
            ColorMapEntry = &ColorMap->Colors[GifRow[x]];
            col.rgb.r = ColorMapEntry->Red;
            col.rgb.g = ColorMapEntry->Green;
            col.rgb.b = ColorMapEntry->Blue;
-           i_ppix(im,x,j,&col);
+           i_ppix(im,Col+x,j,&col);
          }
          
        }
       }
       else {
        for (i = 0; i < Height; i++) {
-         if (DGifGetLine(GifFile, &GifRow[Col], Width) == GIF_ERROR) {
-           mm_log((1,"fatal\n"));
-           exit(-1);
+         if (DGifGetLine(GifFile, GifRow, Width) == GIF_ERROR) {
+           gif_push_error();
+           i_push_error(0, "Reading GIF line");
+           if (colour_table && *colour_table) {
+             myfree(*colour_table);
+             *colour_table = NULL;
+           }
+           i_img_destroy(im);
+           DGifCloseFile(GifFile);
+           return NULL;
          }
 
-         for (x = 0; x < GifFile->SWidth; x++) {
+         for (x = 0; x < Width; x++) {
            ColorMapEntry = &ColorMap->Colors[GifRow[x]];
            col.rgb.r = ColorMapEntry->Red;
            col.rgb.g = ColorMapEntry->Green;
            col.rgb.b = ColorMapEntry->Blue;
-           i_ppix(im,x,Row,&col);
+           i_ppix(im, Col+x, Row, &col);
          }
          Row++;
        }
@@ -158,13 +298,27 @@ i_readgif_low(GifFileType *GifFile, int **colour_table, int *colours) {
     case EXTENSION_RECORD_TYPE:
       /* Skip any extension blocks in file: */
       if (DGifGetExtension(GifFile, &ExtCode, &Extension) == GIF_ERROR) {
-       mm_log((1,"fatal\n"));
-       exit(-1);
+       gif_push_error();
+       i_push_error(0, "Reading extension record");
+       if (colour_table && *colour_table) {
+         myfree(*colour_table);
+         *colour_table = NULL;
+       }
+       i_img_destroy(im);
+       DGifCloseFile(GifFile);
+       return NULL;
       }
       while (Extension != NULL) {
        if (DGifGetExtensionNext(GifFile, &Extension) == GIF_ERROR) {
-         mm_log((1,"fatal\n"));
-         exit(-1);
+         gif_push_error();
+         i_push_error(0, "reading next block of extension");
+         if (colour_table && *colour_table) {
+           myfree(*colour_table);
+           *colour_table = NULL;
+         }
+         i_img_destroy(im);
+         DGifCloseFile(GifFile);
+         return NULL;
        }
       }
       break;
@@ -178,20 +332,43 @@ i_readgif_low(GifFileType *GifFile, int **colour_table, int *colours) {
   myfree(GifRow);
   
   if (DGifCloseFile(GifFile) == GIF_ERROR) {
-    PrintGifError();
-    exit(-1);
+    gif_push_error();
+    i_push_error(0, "Closing GIF file object");
+    if (colour_table && *colour_table) {
+      myfree(*colour_table);
+      *colour_table = NULL;
+    }
+    i_img_destroy(im);
+    return NULL;
   }
   return im;
 }
 
+/*
+=item i_readgif(int fd, int **colour_table, int *colours)
+
+Reads in a GIF file from a file handle and converts it to an Imager
+RGB image object.
+
+Returns the palette for the object in colour_table for colours
+colours.
+
+Returns NULL on failure.
+
+=cut
+*/
 
 i_img *
 i_readgif(int fd, int **colour_table, int *colours) {
   GifFileType *GifFile;
+
+  i_clear_error();
   
   mm_log((1,"i_readgif(fd %d, colour_table %p, colours %p)\n", fd, colour_table, colours));
 
   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;
   }
@@ -199,154 +376,532 @@ i_readgif(int fd, int **colour_table, int *colours) {
   return i_readgif_low(GifFile, colour_table, colours);
 }
 
+/*
 
-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;
-  quant.mc_count = fixedlen;
-  memcpy(colors, fixed, fixedlen * sizeof(i_color));
-  quant.translate = pt_perturb;
-  quant.perturb = pixdev;
-  return i_writegif_gen(&quant, fd, &im, 1, &opts);
+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);
 }
 
-undef_int
-i_writegifmc(i_img *im, int fd, int max_colors) {
-  i_color colors[256];
-  i_quantize quant;
-  i_gif_opts opts;
+/*
+=item i_readgif_multi_low(GifFileType *gf, int *count)
+
+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.
+
+Unlike the normal i_readgif*() functions the images are paletted
+images rather than a combined RGB image.
+
+This functions sets tags on the images returned:
+
+=over
+
+=item gif_left
+
+the offset of the image from the left of the "screen" ("Image Left
+Position")
+
+=item gif_top
+
+the offset of the image from the top of the "screen" ("Image Top Position")
+
+=item gif_interlace
+
+non-zero if the image was interlaced ("Interlace Flag")
+
+=item gif_screen_width
+
+=item gif_screen_height
+
+the size of the logical screen ("Logical Screen Width", 
+"Logical Screen Height")
+
+=item gif_local_map
+
+Non-zero if this image had a local color map.
+
+=item gif_background
+
+The index in the global colormap of the logical screen's background
+color.  This is only set if the current image uses the global
+colormap.
+
+=item gif_trans_index
+
+The index of the color in the colormap used for transparency.  If the
+image has a transparency then it is returned as a 4 channel image with
+the alpha set to zero in this palette entry. ("Transparent Color Index")
+
+=item gif_delay
+
+The delay until the next frame is displayed, in 1/100 of a second. 
+("Delay Time").
+
+=item gif_user_input
+
+whether or not a user input is expected before continuing (view dependent) 
+("User Input Flag").
+
+=item gif_disposal
+
+how the next frame is displayed ("Disposal Method")
+
+=item gif_loop
+
+the number of loops from the Netscape Loop extension.  This may be zero.
+
+=item gif_comment
+
+the first block of the first gif comment before each image.
+
+=back
+
+Where applicable, the ("name") is the name of that field from the GIF89 
+standard.
+
+=cut
+*/
+
+i_img **i_readgif_multi_low(GifFileType *GifFile, int *count) {
+  i_img *img;
+  int i, j, Size, Row, Col, Width, Height, ExtCode, Count, x;
+  int ImageNum = 0, BackGround = 0, ColorMapSize = 0;
+  ColorMapObject *ColorMap;
+  GifRecordType RecordType;
+  GifByteType *Extension;
   
-  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);
+  GifRowType GifRow;
+  int got_gce = 0;
+  int trans_index; /* transparent index if we see a GCE */
+  int gif_delay; /* delay from a GCE */
+  int user_input; /* user input flag from a GCE */
+  int disposal; /* disposal method from a GCE */
+  int got_ns_loop = 0;
+  int ns_loop;
+  char *comment = NULL; /* a comment */
+  i_img **results = NULL;
+  int result_alloc = 0;
+  int channels;
+  
+  *count = 0;
+
+  mm_log((1,"i_readgif_multi_low(GifFile %p, , count %p)\n", GifFile, count));
+
+  BackGround = GifFile->SBackGroundColor;
+
+  Size = GifFile->SWidth * sizeof(GifPixelType);
+  
+  if ((GifRow = (GifRowType) mymalloc(Size)) == NULL)
+    m_fatal(0,"Failed to allocate memory required, aborted."); /* First row. */
+
+  /* Scan the content of the GIF file and load the image(s) in: */
+  do {
+    if (DGifGetRecordType(GifFile, &RecordType) == GIF_ERROR) {
+      gif_push_error();
+      i_push_error(0, "Unable to get record type");
+      free_images(results, *count);
+      DGifCloseFile(GifFile);
+      return NULL;
+    }
+    
+    switch (RecordType) {
+    case IMAGE_DESC_RECORD_TYPE:
+      if (DGifGetImageDesc(GifFile) == GIF_ERROR) {
+       gif_push_error();
+       i_push_error(0, "Unable to get image descriptor");
+        free_images(results, *count);
+       DGifCloseFile(GifFile);
+       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 */
+      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: Image %d at (%d, %d) [%dx%d]: \n",ImageNum, Col, Row, 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);
+       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);
+          }
+       }
+      }
+      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);
+           return NULL;
+         }
+
+          i_ppal(img, 0, Width, i, GifRow);
+       }
+      }
+      break;
+    case EXTENSION_RECORD_TYPE:
+      /* Skip any extension blocks in file: */
+      if (DGifGetExtension(GifFile, &ExtCode, &Extension) == GIF_ERROR) {
+       gif_push_error();
+       i_push_error(0, "Reading extension record");
+        free_images(results, *count);
+       DGifCloseFile(GifFile);
+       return NULL;
+      }
+      if (ExtCode == 0xF9) {
+        got_gce = 1;
+        if (Extension[1] & 1)
+          trans_index = Extension[4];
+        else
+          trans_index = -1;
+        gif_delay = Extension[2] + 256 * Extension[3];
+        user_input = (Extension[0] & 2) != 0;
+        disposal = (Extension[0] >> 2) & 3;
+      }
+      if (ExtCode == 0xFF && *Extension == 11) {
+        if (memcmp(Extension+1, "NETSCAPE2.0", 11) == 0) {
+          if (DGifGetExtensionNext(GifFile, &Extension) == GIF_ERROR) {
+            gif_push_error();
+            i_push_error(0, "reading loop extension");
+            free_images(results, *count);
+            DGifCloseFile(GifFile);
+            return NULL;
+          }
+          if (Extension && *Extension == 3) {
+            got_ns_loop = 1;
+            ns_loop = Extension[2] + 256 * Extension[3];
+          }
+        }
+      }
+      else if (ExtCode == 0xFE) {
+        /* while it's possible for a GIF file to contain more than one
+           comment, I'm only implementing a single comment per image, 
+           with the comment saved into the following image.
+           If someone wants more than that they can implement it.
+           I also don't handle comments that take more than one block.
+        */
+        if (!comment) {
+          comment = mymalloc(*Extension+1);
+          memcpy(comment, Extension+1, *Extension);
+          comment[*Extension] = '\0';
+        }
+      }
+      while (Extension != NULL) {
+       if (DGifGetExtensionNext(GifFile, &Extension) == GIF_ERROR) {
+         gif_push_error();
+         i_push_error(0, "reading next block of extension");
+          free_images(results, *count);
+         DGifCloseFile(GifFile);
+         return NULL;
+       }
+      }
+      break;
+    case TERMINATE_RECORD_TYPE:
+      break;
+    default:               /* Should be trapped by DGifGetRecordType. */
+      break;
+    }
+  } while (RecordType != TERMINATE_RECORD_TYPE);
+
+  if (comment) {
+    if (*count) {
+      i_tags_add(&(results[*count-1]->tags), "gif_comment", 0, comment, 
+                 strlen(comment), 0);
+    }
+    myfree(comment);
+  }
+  
+  myfree(GifRow);
+  
+  if (DGifCloseFile(GifFile) == GIF_ERROR) {
+    gif_push_error();
+    i_push_error(0, "Closing GIF file object");
+    free_images(results, *count);
+    return NULL;
+  }
+
+  return results;
 }
 
-#if 1
+/*
+=item i_readgif_multi(int fd, int *count)
+
+=cut
+*/
+i_img **
+i_readgif_multi(int fd, int *count) {
+  GifFileType *GifFile;
 
-undef_int
-i_writegifex(i_img *im, int fd) {
-  return 0;
+  i_clear_error();
+  
+  mm_log((1,"i_readgif_multi(fd %d, &count %p)\n", fd, count));
+
+  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_multi_low(GifFile, count);
 }
 
-#else
+/*
+=item i_readgif_multi_scalar(char *data, int length, int *count)
 
-/* I don't think this works
-   note that RedBuffer is never allocated - TC
+=cut
 */
+i_img **
+i_readgif_multi_scalar(char *data, int length, int *count) {
+#if IM_GIFMAJOR >= 4
+  GifFileType *GifFile;
+  struct gif_scalar_info gsi;
 
-undef_int
-i_writegifex(i_img *im,int fd) {
-  int colors, xsize, ysize, channels;
-  int x,y,ColorMapSize;
-  unsigned long Size;
+  i_clear_error();
+  
+  gsi.cpos=0;
+  gsi.length=length;
+  gsi.data=data;
+
+  mm_log((1,"i_readgif_multi_scalar(data %p, length %d, &count %p)\n", 
+          data, length, count));
+
+  if ((GifFile = DGifOpen( (void*) &gsi, my_gif_inputfunc )) == NULL) {
+    gif_push_error();
+    i_push_error(0, "Cannot create giflib callback object");
+    mm_log((1,"i_readgif_multi_scalar: Unable to open scalar datasource.\n"));
+    return NULL;
+  }
+
+  return i_readgif_multi_low(GifFile, count);
+#else
+  return NULL;
+#endif
+}
 
-  struct octt *ct;
+/*
+=item i_readgif_callback(i_read_callback_t cb, char *userdata, int **colour_table, int *colours)
+
+Read a GIF file into an Imager RGB file, the data of the GIF file is
+retreived by callin the user supplied callback function.
+
+This function is only used with giflib 4 and higher.
+
+=cut
+*/
 
-  GifByteType *RedBuffer = NULL, *GreenBuffer = NULL, *BlueBuffer = NULL,*OutputBuffer = NULL;
-  ColorMapObject *OutputColorMap = NULL;
+i_img**
+i_readgif_multi_callback(i_read_callback_t cb, char *userdata, int *count) {
+#if IM_GIFMAJOR >= 4
   GifFileType *GifFile;
-  GifByteType *Ptr;
+  i_img **result;
+
+  i_gen_read_data *gci = i_gen_read_data_new(cb, userdata);
+
+  i_clear_error();
+  
+  mm_log((1,"i_readgif_multi_callback(callback %p, userdata %p, count %p)\n", cb, userdata, count));
+  if ((GifFile = DGifOpen( (void*) gci, gif_read_callback )) == NULL) {
+    gif_push_error();
+    i_push_error(0, "Cannot create giflib callback object");
+    mm_log((1,"i_readgif_callback: Unable to open callback datasource.\n"));
+    myfree(gci);
+    return NULL;
+  }
+
+  result = i_readgif_multi_low(GifFile, count);
+  free_gen_read_data(gci);
+
+  return result;
+#else
+  return NULL;
+#endif
+}
+
+/*
+=item i_writegif(i_img *im, int fd, int max_colors, int pixdev, int fixedlen, i_color fixed[])
+
+Write I<img> to the file handle I<fd>.  The resulting GIF will use a
+maximum of 1<<I<max_colours> colours, with the first I<fixedlen>
+colours taken from I<fixed>.
+
+Returns non-zero on success.
+
+=cut
+*/
+
+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;
   
-  i_color val;
+  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;
+  quant.mc_count = fixedlen;
+  memcpy(colors, fixed, fixedlen * sizeof(i_color));
+  quant.translate = pt_perturb;
+  quant.perturb = pixdev;
+  return i_writegif_gen(&quant, fd, &im, 1, &opts);
+}
 
-  mm_log((1,"i_writegif(0x%x,fd %d)\n",im,fd));
-  
-  if (!(im->channels==1 || im->channels==3)) { fprintf(stderr,"Unable to write gif, improper colorspace.\n"); exit(3); }
+/*
+=item i_writegifmc(i_img *im, int fd, int max_colors)
 
-  xsize=im->xsize;
-  ysize=im->ysize;
-  channels=im->channels;
+Write I<img> to the file handle I<fd>.  The resulting GIF will use a
+maximum of 1<<I<max_colours> colours.
 
-  colors=0;
-  ct=octt_new();
+Returns non-zero on success.
 
-  colors=0;
-  for(x=0;x<xsize;x++) for(y=0;y<ysize;y++) {
-    i_gpix(im,x,y,&val);
-    colors+=octt_add(ct,val.rgb.r,val.rgb.g,val.rgb.b);
-    /*  if (colors > maxc) { octt_delete(ct); } 
-       We'll just bite the bullet */
-  }
+=cut
+*/
 
-  ColorMapSize = (colors > 256) ? 256 : colors;
-  
-  Size = ((long) im->xsize) * im->ysize * sizeof(GifByteType);
-  
-  if ((OutputColorMap = MakeMapObject(ColorMapSize, NULL)) == NULL)
-    m_fatal(0,"Failed to allocate memory for Output colormap.");
-  if ((OutputBuffer = (GifByteType *) mymalloc(im->xsize * im->ysize * sizeof(GifByteType))) == NULL)
-    m_fatal(0,"Failed to allocate memory for output buffer.");
+undef_int
+i_writegifmc(i_img *im, int fd, int max_colors) {
+  i_color colors[256];
+  i_quantize quant;
+  i_gif_opts opts;
   
-  if (QuantizeBuffer(im->xsize, im->ysize, &ColorMapSize, RedBuffer, GreenBuffer, BlueBuffer,
-                    OutputBuffer, OutputColorMap->Colors) == GIF_ERROR) {
-    mm_log((1,"Error in QuantizeBuffer, unable to write image.\n"));
-    return(0);
-  }
-
+  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);
+}
 
-  myfree(RedBuffer);
-  if (im->channels == 3) { myfree(GreenBuffer); myfree(BlueBuffer); }
-  
-  if ((GifFile = EGifOpenFileHandle(fd)) == NULL) {
-    mm_log((1,"Error in EGifOpenFileHandle, unable to write image.\n"));
-    return(0);
-  }
-  
-  if (EGifPutScreenDesc(GifFile,im->xsize, im->ysize, colors, 0,OutputColorMap) == GIF_ERROR ||
-      EGifPutImageDesc(GifFile,0, 0, im->xsize, im->ysize, FALSE, NULL) == GIF_ERROR) {
-    mm_log((1,"Error in EGifOpenFileHandle, unable to write image.\n"));
-    if (GifFile != NULL) EGifCloseFile(GifFile);
-    return(0);
-  }
 
-  Ptr = OutputBuffer;
+/*
+=item i_readgif_scalar(char *data, int length, int **colour_table, int *colours)
 
-  for (y = 0; y < im->ysize; y++) {
-    if (EGifPutLine(GifFile, Ptr, im->xsize) == GIF_ERROR) {
-      mm_log((1,"Error in EGifOpenFileHandle, unable to write image.\n"));
-      if (GifFile != NULL) EGifCloseFile(GifFile);
-      return(0);
-    }
-    
-    Ptr += im->xsize;
-  }
-  
-  if (EGifCloseFile(GifFile) == GIF_ERROR) {
-    mm_log((1,"Error in EGifCloseFile, unable to write image.\n"));
-    return(0);
-  }
-  return(1);
-}
+Reads a GIF file from an in memory copy of the file.  This can be used
+if you get the 'file' from some source other than an actual file (or
+some other file handle).
 
-#endif
+This function is only available with giflib 4 and higher.
 
+=cut
+*/
 i_img*
 i_readgif_scalar(char *data, int length, int **colour_table, int *colours) {
 #if IM_GIFMAJOR >= 4
   GifFileType *GifFile;
-  
   struct gif_scalar_info gsi;
 
+  i_clear_error();
+
   gsi.cpos=0;
   gsi.length=length;
   gsi.data=data;
 
   mm_log((1,"i_readgif_scalar(char* data, int length, colour_table %p, colours %p)\n", data, length, colour_table, colours));
   if ((GifFile = DGifOpen( (void*) &gsi, my_gif_inputfunc )) == NULL) {
+    gif_push_error();
+    i_push_error(0, "Cannot create giflib callback object");
     mm_log((1,"i_readgif_scalar: Unable to open scalar datasource.\n"));
     return NULL;
   }
@@ -359,6 +914,16 @@ i_readgif_scalar(char *data, int length, int **colour_table, int *colours) {
 
 #if IM_GIFMAJOR >= 4
 
+/*
+=item gif_read_callback(GifFileType *gft, GifByteType *buf, int length)
+
+Internal.  The reader callback wrapper passed to giflib.
+
+This function is only used with giflib 4 and higher.
+
+=cut
+*/
+
 static int
 gif_read_callback(GifFileType *gft, GifByteType *buf, int length) {
   return i_gen_reader((i_gen_read_data *)gft->UserData, buf, length);
@@ -366,16 +931,32 @@ gif_read_callback(GifFileType *gft, GifByteType *buf, int length) {
 
 #endif
 
+
+/*
+=item i_readgif_callback(i_read_callback_t cb, char *userdata, int **colour_table, int *colours)
+
+Read a GIF file into an Imager RGB file, the data of the GIF file is
+retreived by callin the user supplied callback function.
+
+This function is only used with giflib 4 and higher.
+
+=cut
+*/
+
 i_img*
 i_readgif_callback(i_read_callback_t cb, char *userdata, int **colour_table, int *colours) {
 #if IM_GIFMAJOR >= 4
   GifFileType *GifFile;
   i_img *result;
-  
+
   i_gen_read_data *gci = i_gen_read_data_new(cb, userdata);
 
+  i_clear_error();
+  
   mm_log((1,"i_readgif_callback(callback %p, userdata %p, colour_table %p, colours %p)\n", cb, userdata, colour_table, colours));
   if ((GifFile = DGifOpen( (void*) gci, gif_read_callback )) == NULL) {
+    gif_push_error();
+    i_push_error(0, "Cannot create giflib callback object");
     mm_log((1,"i_readgif_callback: Unable to open callback datasource.\n"));
     myfree(gci);
     return NULL;
@@ -390,8 +971,16 @@ i_readgif_callback(i_read_callback_t cb, char *userdata, int **colour_table, int
 #endif
 }
 
-/* low level image write 
-   writes in interlace if that's what was requested */
+/*
+=item do_write(GifFileType *gf, i_gif_opts *opts, i_img *img, i_palidx *data)
+
+Internal.  Low level image write function.  Writes in interlace if
+that was requested in the GIF options.
+
+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) {
@@ -399,6 +988,8 @@ do_write(GifFileType *gf, i_gif_opts *opts, i_img *img, i_palidx *data) {
     for (i = 0; i < 4; ++i) {
       for (j = InterlacedOffset[i]; j < img->ysize; j += InterlacedJumps[i]) {
        if (EGifPutLine(gf, data+j*img->xsize, img->xsize) == GIF_ERROR) {
+         gif_push_error();
+         i_push_error(0, "Could not save image data:");
          mm_log((1, "Error in EGifPutLine\n"));
          EGifCloseFile(gf);
          return 0;
@@ -410,6 +1001,8 @@ do_write(GifFileType *gf, i_gif_opts *opts, i_img *img, i_palidx *data) {
     int y;
     for (y = 0; y < img->ysize; ++y) {
       if (EGifPutLine(gf, data, img->xsize) == GIF_ERROR) {
+       gif_push_error();
+       i_push_error(0, "Could not save image data:");
        mm_log((1, "Error in EGifPutLine\n"));
        EGifCloseFile(gf);
        return 0;
@@ -421,6 +1014,15 @@ do_write(GifFileType *gf, i_gif_opts *opts, i_img *img, i_palidx *data) {
   return 1;
 }
 
+/*
+=item do_gce(GifFileType *gf, int index, i_gif_opts *opts, int want_trans, int trans_index)
+
+Internal. Writes the GIF graphics control extension, if necessary.
+
+Returns non-zero on success.
+
+=cut
+*/
 static int do_gce(GifFileType *gf, int index, i_gif_opts *opts, int want_trans, int trans_index)
 {
   unsigned char gce[4] = {0};
@@ -445,15 +1047,27 @@ static int do_gce(GifFileType *gf, int index, i_gif_opts *opts, int want_trans,
     ++want_gce;
   }
   if (want_gce) {
-    return EGifPutExtension(gf, 0xF9, sizeof(gce), gce) != GIF_ERROR;
+    if (EGifPutExtension(gf, 0xF9, sizeof(gce), gce) == GIF_ERROR) {
+      gif_push_error();
+      i_push_error(0, "Could not save GCE");
+    }
   }
   return 1;
 }
 
-/* add the Netscape2.0 loop extension block, if requested */
+/*
+=item do_ns_loop(GifFileType *gf, i_gif_opts *opts)
+
+Internal.  Add the Netscape2.0 loop extension block, if requested.
+
+The code for this function is currently "#if 0"ed out since the giflib
+extension writing code currently doesn't seem to support writing
+application extension blocks.
+
+=cut
+*/
 static int do_ns_loop(GifFileType *gf, i_gif_opts *opts)
 {
-#if 0
   /* EGifPutExtension() doesn't appear to handle application 
      extension blocks in any way
      Since giflib wraps the fd with a FILE * (and puts that in its
@@ -464,24 +1078,49 @@ static int do_ns_loop(GifFileType *gf, i_gif_opts *opts)
      If giflib's callback interface wasn't broken by default, I'd 
      force file writes to use callbacks, but it is broken by default.
   */
+#if 0
+  /* yes this was another attempt at supporting the loop extension */
   if (opts->loop_count) {
-    unsigned char nsle[15] = "NETSCAPE2.0";
-    nsle[11] = 3;
-    nsle[12] = 1;
-    nsle[13] = opts->loop_count % 256;
-    nsle[14] = opts->loop_count / 256;
-    return EGifPutExtension(gf, 0xFF, sizeof(nsle), nsle) != GIF_ERROR;
+    unsigned char nsle[12] = "NETSCAPE2.0";
+    unsigned char subblock[3];
+    if (EGifPutExtension(gf, 0xFF, 11, nsle) == GIF_ERROR) {
+      gif_push_error();
+      i_push_error(0, "writing loop extension");
+      return 0;
+    }
+    subblock[0] = 1;
+    subblock[1] = opts->loop_count % 256;
+    subblock[2] = opts->loop_count / 256;
+    if (EGifPutExtension(gf, 0, 3, subblock) == GIF_ERROR) {
+      gif_push_error();
+      i_push_error(0, "writing loop extention sub-block");
+      return 0;
+    }
+    if (EGifPutExtension(gf, 0, 0, subblock) == GIF_ERROR) {
+      gif_push_error();
+      i_push_error(0, "writing loop extension terminator");
+      return 0;
+    }
   }
 #endif
   return 1;
 }
 
+/*
+=item make_gif_map(i_quantize *quant, i_gif_opts *opts, 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) {
   GifColorType colors[256];
   int i;
   int size = quant->mc_count;
   int map_size;
+  ColorMapObject *map;
 
   for (i = 0; i < quant->mc_count; ++i) {
     colors[i].Red = quant->mc_colors[i].rgb.r;
@@ -497,12 +1136,43 @@ static ColorMapObject *make_gif_map(i_quantize *quant, i_gif_opts *opts,
   map_size = 1;
   while (map_size < size)
     map_size <<= 1;
-  return MakeMapObject(map_size, colors);
+  /* giflib spews for 1 colour maps, reasonable, I suppose */
+  if (map_size == 1)
+    map_size = 2;
+  
+  map = MakeMapObject(map_size, colors);
+  mm_log((1, "XXX map is at %p and colors at %p\n", map, map->Colors));
+  if (!map) {
+    gif_push_error();
+    i_push_error(0, "Could not create color map object");
+    return NULL;
+  }
+  return map;
 }
 
-/* we need to call EGifSetGifVersion() before opening the file - put that
-   common code here
+/*
+=item gif_set_version(i_quantize *quant, i_gif_opts *opts)
+
+We need to call EGifSetGifVersion() before opening the file - put that
+common code here.
+
+Unfortunately giflib 4.1.0 crashes when we use this.  Internally
+giflib 4.1.0 has code:
+
+  static char *GifVersionPrefix = GIF87_STAMP;
+
+and the code that sets the version internally does:
+
+  strncpy(&GifVersionPrefix[3], Version, 3);
+
+which is very broken.
+
+Failing to set the correct GIF version doesn't seem to cause a problem
+with readers.
+
+=cut
 */
+
 static void gif_set_version(i_quantize *quant, i_gif_opts *opts) {
   /* the following crashed giflib
      the EGifSetGifVersion() is seriously borked in giflib
@@ -519,6 +1189,124 @@ static void gif_set_version(i_quantize *quant, i_gif_opts *opts) {
   */
 }
 
+static int 
+in_palette(i_color *c, i_quantize *quant, int size) {
+  int i;
+
+  for (i = 0; i < size; ++i) {
+    if (c->channel[0] == quant->mc_colors[i].channel[0]
+        && c->channel[1] == quant->mc_colors[i].channel[1]
+        && c->channel[2] == quant->mc_colors[i].channel[2]) {
+      return i;
+    }
+  }
+
+  return -1;
+}
+
+/*
+=item has_common_palette(imgs, count, quant, want_trans)
+
+Tests if all the given images are paletted and have a common palette,
+if they do it builds that palette.
+
+A possible improvement might be to eliminate unused colors in the
+images palettes.
+
+=cut */
+static int
+has_common_palette(i_img **imgs, int count, i_quantize *quant, int want_trans,
+                   i_gif_opts *opts) {
+  int size = quant->mc_count;
+  int i, j;
+  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) {
+    if (imgs[imgn]->type != i_palette_type)
+      return 0;
+
+    if (opts->eliminate_unused) {
+      i_palidx *line = mymalloc(sizeof(i_palidx) * imgs[imgn]->xsize);
+      int x, y;
+      memset(used, 0, sizeof(used));
+
+      for (y = 0; y < imgs[imgn]->ysize; ++y) {
+        i_gpal(imgs[imgn], 0, imgs[imgn]->xsize, y, line);
+        for (x = 0; x < imgs[imgn]->xsize; ++x)
+          used[line[x]] = 1;
+      }
+
+      myfree(line);
+    }
+    else {
+      /* assume all are in use */
+      memset(used, 1, sizeof(used));
+    }
+
+    for (i = 0; i < i_colorcount(imgs[imgn]); ++i) {
+      i_color c;
+      
+      i_getcolors(imgs[imgn], i, &c, 1);
+      if (used[i]) {
+        if (in_palette(&c, quant, size) < 0) {
+          if (size < quant->mc_size) {
+            quant->mc_colors[size++] = c;
+          }
+          else {
+            /* oops, too many colors */
+            return 0;
+          }
+        }
+      }
+    }
+  }
+
+  quant->mc_count = size;
+
+  return 1;
+}
+
+static i_palidx *
+quant_paletted(i_quantize *quant, i_img *img) {
+  i_palidx *data = mymalloc(sizeof(i_palidx) * img->xsize * img->ysize);
+  i_palidx *p = data;
+  i_palidx trans[256];
+  int i;
+  int x, y;
+
+  /* build a translation table */
+  for (i = 0; i < i_colorcount(img); ++i) {
+    i_color c;
+    i_getcolors(img, i, &c, 1);
+    trans[i] = in_palette(&c, quant, quant->mc_count);
+  }
+
+  for (y = 0; y < img->ysize; ++y) {
+    i_gpal(img, 0, img->xsize, y, data+img->xsize * y);
+    for (x = 0; x < img->xsize; ++x) {
+      *p = trans[*p];
+      ++p;
+    }
+  }
+
+  return data;
+}
+
+/*
+=item i_writegif_low(i_quantize *quant, GifFileType *gf, i_img **imgs, int count, i_gif_opts *opts)
+
+Internal.  Low-level function that does the high-level GIF processing
+:)
+
+Returns non-zero on success.
+
+=cut
+*/
+
 static undef_int
 i_writegif_low(i_quantize *quant, GifFileType *gf, i_img **imgs, int count,
               i_gif_opts *opts) {
@@ -528,6 +1316,7 @@ i_writegif_low(i_quantize *quant, GifFileType *gf, i_img **imgs, int count,
   int scrw = 0, scrh = 0;
   int imgn, orig_count, orig_size;
   int posx, posy;
+  int trans_index;
 
   mm_log((1, "i_writegif_low(quant %p, gf  %p, imgs %p, count %d, opts %p)\n", 
          quant, gf, imgs, count, opts));
@@ -554,27 +1343,40 @@ i_writegif_low(i_quantize *quant, GifFileType *gf, i_img **imgs, int count,
     }
   }
 
-
-  if (count <= 0)
+  if (count <= 0) {
+    i_push_error(0, "No images provided to write");
     return 0; /* what are you smoking? */
+  }
 
   orig_count = quant->mc_count;
   orig_size = quant->mc_size;
 
   if (opts->each_palette) {
-    int want_trans;
+    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)
+      --quant->mc_size;
 
     /* we always generate a global palette - this lets systems with a 
        broken giflib work */
-    quant_makemap(quant, imgs, 1);
-    result = quant_translate(quant, imgs[0]);
+    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 (want_trans) {
+      trans_index = quant->mc_count;
+      quant_transparent(quant, result, imgs[0], trans_index);
+    }
 
-    want_trans = quant->transp != tr_none 
-      && imgs[0]->channels == 4 
-      && quant->mc_count < 256;
-    if (want_trans)
-      quant_transparent(quant, result, imgs[0], quant->mc_count);
-    
     if ((map = make_gif_map(quant, opts, want_trans)) == NULL) {
       myfree(result);
       EGifCloseFile(gf);
@@ -587,6 +1389,8 @@ i_writegif_low(i_quantize *quant, GifFileType *gf, i_img **imgs, int count,
       ++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);
@@ -598,7 +1402,7 @@ i_writegif_low(i_quantize *quant, GifFileType *gf, i_img **imgs, int count,
     if (!do_ns_loop(gf, opts))
       return 0;
 
-    if (!do_gce(gf, 0, opts, want_trans, quant->mc_count)) {
+    if (!do_gce(gf, 0, opts, want_trans, trans_index)) {
       myfree(result);
       EGifCloseFile(gf);
       return 0;
@@ -611,6 +1415,8 @@ i_writegif_low(i_quantize *quant, GifFileType *gf, i_img **imgs, int count,
       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;
@@ -623,14 +1429,28 @@ i_writegif_low(i_quantize *quant, GifFileType *gf, i_img **imgs, int count,
     for (imgn = 1; imgn < count; ++imgn) {
       quant->mc_count = orig_count;
       quant->mc_size = orig_size;
-      quant_makemap(quant, imgs+imgn, 1);
-      result = quant_translate(quant, imgs[imgn]);
       want_trans = quant->transp != tr_none 
-       && imgs[imgn]->channels == 4 
-       && quant->mc_count < 256;
-      if (want_trans)
-       quant_transparent(quant, result, imgs[imgn], quant->mc_count);
-      
+       && 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+imgn, 1, quant, want_trans, opts)) {
+        result = quant_paletted(quant, imgs[imgn]);
+      }
+      else {
+        quant_makemap(quant, imgs+imgn, 1);
+        result = quant_translate(quant, imgs[imgn]);
+      }
+      if (want_trans) {
+        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);
@@ -651,6 +1471,8 @@ i_writegif_low(i_quantize *quant, GifFileType *gf, i_img **imgs, int count,
       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);
@@ -669,16 +1491,7 @@ i_writegif_low(i_quantize *quant, GifFileType *gf, i_img **imgs, int count,
   }
   else {
     int want_trans;
-
-    mm_log((1, "i_writegif_low: GOT HERE\n"));
-
-    /* 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 */
-    quant_makemap(quant, imgs, count);
-    result = quant_translate(quant, imgs[0]);
+    int do_quant_paletted = 0;
 
     /* get a palette entry for the transparency iff we have an image
        with an alpha channel */
@@ -689,7 +1502,25 @@ i_writegif_low(i_quantize *quant, GifFileType *gf, i_img **imgs, int count,
        break;
       }
     }
-    want_trans = want_trans && quant->transp != tr_none && quant->mc_count < 256;
+    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]);
+    }
+
     if ((map = make_gif_map(quant, opts, want_trans)) == NULL) {
       myfree(result);
       EGifCloseFile(gf);
@@ -701,6 +1532,8 @@ i_writegif_low(i_quantize *quant, GifFileType *gf, i_img **imgs, int count,
       ++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);
@@ -725,6 +1558,8 @@ i_writegif_low(i_quantize *quant, GifFileType *gf, i_img **imgs, int count,
       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;
@@ -741,7 +1576,10 @@ i_writegif_low(i_quantize *quant, GifFileType *gf, i_img **imgs, int count,
 
     for (imgn = 1; imgn < count; ++imgn) {
       int local_trans;
-      result = quant_translate(quant, imgs[imgn]);
+      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);
@@ -759,6 +1597,8 @@ i_writegif_low(i_quantize *quant, GifFileType *gf, i_img **imgs, int count,
       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."));
@@ -773,6 +1613,8 @@ i_writegif_low(i_quantize *quant, GifFileType *gf, i_img **imgs, int count,
     }
   }
   if (EGifCloseFile(gf) == GIF_ERROR) {
+    gif_push_error();
+    i_push_error(0, "Could not close GIF file");
     mm_log((1, "Error in EGifCloseFile\n"));
     return 0;
   }
@@ -780,19 +1622,34 @@ i_writegif_low(i_quantize *quant, GifFileType *gf, i_img **imgs, int count,
   return 1;
 }
 
+/*
+=item i_writegif_gen(i_quantize *quant, int fd, i_img **imgs, int count, i_gif_opts *opts)
+
+General high-level function to write a GIF to a file.
+
+Writes the GIF images to the specified file handle using the options
+in quant and opts.  See L<image.h/i_quantize> and
+L<image.h/i_gif_opts>.
+
+Returns non-zero on success.
+
+=cut
+*/
+
 undef_int
 i_writegif_gen(i_quantize *quant, int fd, i_img **imgs, int count, 
               i_gif_opts *opts) {
   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));
 
   gif_set_version(quant, opts);
 
-  mm_log((1, "i_writegif_gen: set ops\n"));
-
   if ((gf = EGifOpenFileHandle(fd)) == NULL) {
+    gif_push_error();
+    i_push_error(0, "Cannot create GIF file object");
     mm_log((1, "Error in EGifOpenFileHandle, unable to write image.\n"));
     return 0;
   }
@@ -802,6 +1659,14 @@ i_writegif_gen(i_quantize *quant, int fd, i_img **imgs, int count,
 
 #if IM_GIFMAJOR >= 4
 
+/*
+=item gif_writer_callback(GifFileType *gf, const GifByteType *data, int size)
+
+Internal.  Wrapper for the user write callback function.
+
+=cut
+*/
+
 static int gif_writer_callback(GifFileType *gf, const GifByteType *data, int size)
 {
   i_gen_write_data *gwd = (i_gen_write_data *)gf->UserData;
@@ -811,6 +1676,17 @@ static int gif_writer_callback(GifFileType *gf, const GifByteType *data, int siz
 
 #endif
 
+/*
+=item i_writegif_callback(i_quantize *quant, i_write_callback_t cb, char *userdata, int maxlength, i_img **imgs, int count, i_gif_opts *opts)
+
+General high-level function to write a GIF using callbacks to send
+back the data.
+
+Returns non-zero on success.
+
+=cut
+*/
+
 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)
@@ -822,10 +1698,14 @@ i_writegif_callback(i_quantize *quant, i_write_callback_t cb, char *userdata,
   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));
   
   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);
     return 0;
@@ -837,3 +1717,135 @@ i_writegif_callback(i_quantize *quant, i_write_callback_t cb, char *userdata,
   return 0;
 #endif
 }
+
+/*
+=item gif_error_msg(int code)
+
+Grabs the most recent giflib error code from GifLastError() and 
+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 */
+
+static char const *gif_error_msg(int code) {
+  static char msg[80];
+
+  switch (code) {
+  case E_GIF_ERR_OPEN_FAILED: /* should not see this */
+    return "Failed to open given file";
+    
+  case E_GIF_ERR_WRITE_FAILED:
+    return "Write failed";
+
+  case E_GIF_ERR_HAS_SCRN_DSCR: /* should not see this */
+    return "Screen descriptor already passed to giflib";
+
+  case E_GIF_ERR_HAS_IMAG_DSCR: /* should not see this */
+    return "Image descriptor already passed to giflib";
+    
+  case E_GIF_ERR_NO_COLOR_MAP: /* should not see this */
+    return "Neither global nor local color map set";
+
+  case E_GIF_ERR_DATA_TOO_BIG: /* should not see this */
+    return "Too much pixel data passed to giflib";
+
+  case E_GIF_ERR_NOT_ENOUGH_MEM:
+    return "Out of memory";
+    
+  case E_GIF_ERR_DISK_IS_FULL:
+    return "Disk is full";
+    
+  case E_GIF_ERR_CLOSE_FAILED: /* should not see this */
+    return "File close failed";
+  case E_GIF_ERR_NOT_WRITEABLE: /* should not see this */
+    return "File not writable";
+
+  case D_GIF_ERR_OPEN_FAILED:
+    return "Failed to open file";
+    
+  case D_GIF_ERR_READ_FAILED:
+    return "Failed to read from file";
+
+  case D_GIF_ERR_NOT_GIF_FILE:
+    return "File is not a GIF file";
+
+  case D_GIF_ERR_NO_SCRN_DSCR:
+    return "No screen descriptor detected - invalid file";
+
+  case D_GIF_ERR_NO_IMAG_DSCR:
+    return "No image descriptor detected - invalid file";
+
+  case D_GIF_ERR_NO_COLOR_MAP:
+    return "No global or local color map found";
+
+  case D_GIF_ERR_WRONG_RECORD:
+    return "Wrong record type detected - invalid file?";
+
+  case D_GIF_ERR_DATA_TOO_BIG:
+    return "Data in file too big for image";
+
+  case D_GIF_ERR_NOT_ENOUGH_MEM:
+    return "Out of memory";
+
+  case D_GIF_ERR_CLOSE_FAILED:
+    return "Close failed";
+
+  case D_GIF_ERR_NOT_READABLE:
+    return "File not opened for read";
+
+  case D_GIF_ERR_IMAGE_DEFECT:
+    return "Defective image";
+
+  case D_GIF_ERR_EOF_TOO_SOON:
+    return "Unexpected EOF - invalid file";
+
+  default:
+    sprintf(msg, "Unknown giflib error code %d", code);
+    return msg;
+  }
+}
+
+/*
+=item gif_push_error()
+
+Utility function that takes the current GIF error code, converts it to
+an error message and pushes it on the error stack.
+
+=cut
+*/
+
+static void gif_push_error(void) {
+  int code = GifLastError(); /* clears saved error */
+
+  i_push_error(code, gif_error_msg(code));
+}
+
+/*
+=head1 BUGS
+
+The Netscape loop extension isn't implemented.  Giflib's extension
+writing code doesn't seem to support writing named extensions in this 
+form.
+
+A bug in giflib is tickled by the i_writegif_callback().  This isn't a
+problem on ungiflib, but causes a SEGV on giflib.  A patch is provided
+in t/t10formats.t
+
+The GIF file tag (GIF87a vs GIF89a) currently isn't set.  Using the
+supplied interface in giflib 4.1.0 causes a SEGV in
+EGifSetGifVersion().  See L<gif_set_version> for an explanation.
+
+=head1 AUTHOR
+
+Arnar M. Hrafnkelsson, addi@umich.edu
+
+=head1 SEE ALSO
+
+perl(1), Imager(3)
+
+=cut
+
+*/