]> git.imager.perl.org - imager.git/blobdiff - gif.c
note the memory leak fix in Changes
[imager.git] / gif.c
diff --git a/gif.c b/gif.c
index 3830ff621db72bb0b08fc5c2ffb3776d50744336..be4e874f19c8575b227c4b7016febb07a11086c4 100644 (file)
--- a/gif.c
+++ b/gif.c
@@ -1,6 +1,6 @@
-#include "image.h"
+#include "imageri.h"
 #include <gif_lib.h>
-#ifdef _MSCVER
+#ifdef _MSC_VER
 #include <io.h>
 #else
 #include <unistd.h>
@@ -177,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); 
   
@@ -354,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;
 }
 
@@ -396,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.
 
@@ -482,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;
  
@@ -493,16 +519,21 @@ i_img **i_readgif_multi_low(GifFileType *GifFile, int *count) {
   
   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 trans_index = 0; /* transparent index if we see a GCE */
+  int gif_delay = 0; /* delay from a GCE */
+  int user_input = 0; /* user input flag from a GCE */
+  int disposal = 0; /* disposal method from a GCE */
   int got_ns_loop = 0;
-  int ns_loop;
+  int ns_loop = 0;
   char *comment = NULL; /* a comment */
   i_img **results = NULL;
   int result_alloc = 0;
   int channels;
+  int image_colors = 0;
+  i_color black; /* used to expand the palette if needed */
+
+  for (i = 0; i < MAXCHANNELS; ++i)
+    black.channel[i] = 0;
   
   *count = 0;
 
@@ -513,7 +544,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 {
@@ -522,6 +553,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;
     }
     
@@ -532,130 +564,204 @@ 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 {
-          /* 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_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;
-      }
+      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);
+       }
+       image_colors = ColorMapSize;
+       ++*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);
+       i_tags_addn(&img->tags, "gif_colormap_size", 0, ColorMapSize);
+       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;
+             }
+
+             /* range check the scanline if needed */
+             if (image_colors != 256) {
+               int x;
+               for (x = 0; x < Width; ++x) {
+                 while (GifRow[x] >= image_colors) {
+                   /* expand the palette since a palette index is too big */
+                   i_addcolors(img, &black, 1);
+                   ++image_colors;
+                 }
+               }
+             }
+
+             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;
+           }
+           
+           /* range check the scanline if needed */
+           if (image_colors != 256) {
+             int x;
+             for (x = 0; x < Width; ++x) {
+               while (GifRow[x] >= image_colors) {
+                 /* expand the palette since a palette index is too big */
+                 i_addcolors(img, &black, 1);
+                 ++image_colors;
+               }
+             }
+           }
 
-      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));
+           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: */
@@ -673,8 +779,8 @@ i_img **i_readgif_multi_low(GifFileType *GifFile, int *count) {
         else
           trans_index = -1;
         gif_delay = Extension[2] + 256 * Extension[3];
-        user_input = (Extension[0] & 2) != 0;
-        disposal = (Extension[0] >> 2) & 3;
+        user_input = (Extension[1] & 2) != 0;
+        disposal = (Extension[1] >> 2) & 7;
       }
       if (ExtCode == 0xFF && *Extension == 11) {
         if (memcmp(Extension+1, "NETSCAPE2.0", 11) == 0) {
@@ -738,6 +844,13 @@ 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;
 }
 
@@ -774,7 +887,7 @@ i_readgif_multi_wiol(io_glue *ig, int *count) {
       return NULL;
     }
     
-    return i_readgif_multi_low(GifFile, count);
+    return i_readgif_multi_low(GifFile, count, -1);
 #else
     i_clear_error();
     i_push_error(0, "callbacks not supported with giflib3");
@@ -804,7 +917,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);
 }
 
 /*
@@ -834,7 +947,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
@@ -870,8 +983,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
@@ -1023,7 +1136,7 @@ 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
@@ -1081,6 +1194,91 @@ i_readgif_wiol(io_glue *ig, int **color_table, int *colors) {
   }
 }
 
+/*
+=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
+  }
+}
+
 /*
 =item do_write(GifFileType *gf, i_gif_opts *opts, i_img *img, i_palidx *data)
 
@@ -1202,9 +1400,11 @@ static int do_comments(GifFileType *gf, i_img *img) {
 
 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.
+Giflib/libungif prior to 4.1.1 didn't support writing application
+extension blocks, so we don't attempt to write them for older versions.
+
+Giflib/libungif prior to 4.1.3 used the wrong write mechanism when
+writing extension blocks so that they could only be written to files.
 
 =cut
 */
@@ -1220,13 +1420,13 @@ static int do_ns_loop(GifFileType *gf, i_img *img)
      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 IM_GIFMAJOR == 4 && IM_GIFMINOR >= 1
   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) {
+    if (EGifPutExtensionFirst(gf, APPLICATION_EXT_FUNC_CODE, 11, nsle) == GIF_ERROR) {
       gif_push_error();
       i_push_error(0, "writing loop extension");
       return 0;
@@ -1234,18 +1434,14 @@ static int do_ns_loop(GifFileType *gf, i_img *img)
     subblock[0] = 1;
     subblock[1] = loop_count % 256;
     subblock[2] = loop_count / 256;
-    if (EGifPutExtension(gf, 0, 3, subblock) == GIF_ERROR) {
+    if (EGifPutExtensionLast(gf, APPLICATION_EXT_FUNC_CODE, 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;
 }
 
@@ -1320,24 +1516,53 @@ which is very broken.
 Failing to set the correct GIF version doesn't seem to cause a problem
 with readers.
 
+Modern versions (4.1.4 anyway) of giflib/libungif handle
+EGifSetGifVersion correctly.
+
+If t/t105gif.t crashes here then run Makefile.PL with
+--nogifsetversion, eg.:
+
+  perl Makefile.PL --nogifsetversion
+
+or install a less buggy giflib.
+
 =cut
 */
 
 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
-     || opts->loop_count
-     || quant->transp != tr_none)
+#if (IM_GIFMAJOR >= 4 || IM_GIFMAJOR == 4 && IM_GIFMINOR >= 1) \
+       && !defined(IM_NO_SET_GIF_VERSION)
+  int need_89a = 0;
+  int temp;
+  int i;
+
+  if (quant->transp != tr_none)
+    need_89a = 1;
+  else {
+    for (i = 0; i < count; ++i) {
+      if (i_tags_get_int(&imgs[i]->tags, "gif_delay", 0, &temp)) { 
+        need_89a = 1; 
+        break;
+      }
+      if (i_tags_get_int(&imgs[i]->tags, "gif_user_input", 0, &temp) && temp) {
+        need_89a = 1; 
+        break;
+      }
+      if (i_tags_get_int(&imgs[i]->tags, "gif_disposal", 0, &temp)) {
+        need_89a = 1;
+        break;
+      }
+      if (i_tags_get_int(&imgs[i]->tags, "gif_loop", 0, &temp)) {
+        need_89a = 1;
+        break;
+      }
+    }
+  }
+  if (need_89a)
      EGifSetGifVersion("89a");
-     else
+  else
      EGifSetGifVersion("87a");
-  */
+#endif
 }
 
 static int 
@@ -1370,9 +1595,8 @@ static int
 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
@@ -1467,13 +1691,13 @@ Returns non-zero on success.
 
 static undef_int
 i_writegif_low(i_quantize *quant, GifFileType *gf, i_img **imgs, int count) {
-  unsigned char *result;
+  unsigned char *result = NULL;
   int color_bits;
   ColorMapObject *map;
   int scrw = 0, scrh = 0;
   int imgn, orig_count, orig_size;
   int posx, posy;
-  int trans_index;
+  int trans_index = -1;
   i_mempool mp;
   int *localmaps;
   int anylocal;
@@ -1481,12 +1705,11 @@ i_writegif_low(i_quantize *quant, GifFileType *gf, i_img **imgs, int count) {
   int glob_img_count;
   i_color *orig_colors = quant->mc_colors;
   i_color *glob_colors = NULL;
-  int glob_color_count;
-  int glob_map_size;
+  int glob_color_count = 0;
   int glob_want_trans;
-  int glob_paletted; /* the global map was made from the image palettes */
-  int colors_paletted;
-  int want_trans;
+  int glob_paletted = 0; /* the global map was made from the image palettes */
+  int colors_paletted = 0;
+  int want_trans = 0;
   int interlace;
   int gif_background;
 
@@ -1510,7 +1733,7 @@ i_writegif_low(i_quantize *quant, GifFileType *gf, i_img **imgs, int count) {
 
   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))
+  if (!i_tags_get_int(&imgs[0]->tags, "gif_screen_height", 0, &scrh))
     scrw = 0;
 
   anylocal = 0;
@@ -1561,7 +1784,7 @@ i_writegif_low(i_quantize *quant, GifFileType *gf, i_img **imgs, int count) {
     }
     else {
       glob_paletted = 0;
-      quant_makemap(quant, glob_imgs, glob_img_count);
+      i_quant_makemap(quant, glob_imgs, glob_img_count);
     }
     glob_color_count = quant->mc_count;
     quant->mc_colors = orig_colors;
@@ -1588,7 +1811,7 @@ i_writegif_low(i_quantize *quant, GifFileType *gf, i_img **imgs, int count) {
     }
     else {
       colors_paletted = 0;
-      quant_makemap(quant, imgs, 1);
+      i_quant_makemap(quant, imgs, 1);
     }
   }
   if ((map = make_gif_map(quant, imgs[0], want_trans)) == NULL) {
@@ -1658,11 +1881,12 @@ i_writegif_low(i_quantize *quant, GifFileType *gf, i_img **imgs, int count) {
       }
       else {
         colors_paletted = 0;
-        quant_makemap(quant, imgs, 1);
+        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;
       }
@@ -1677,9 +1901,15 @@ i_writegif_low(i_quantize *quant, GifFileType *gf, i_img **imgs, int count) {
   if (colors_paletted)
     result = quant_paletted(quant, imgs[0]);
   else
-    result = quant_translate(quant, imgs[0]);
+    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) {
-    quant_transparent(quant, result, imgs[0], quant->mc_count);
+    i_quant_transparent(quant, result, imgs[0], quant->mc_count);
     trans_index = quant->mc_count;
   }
 
@@ -1750,11 +1980,18 @@ i_writegif_low(i_quantize *quant, GifFileType *gf, i_img **imgs, int count) {
         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;
       }
 
@@ -1773,10 +2010,10 @@ i_writegif_low(i_quantize *quant, GifFileType *gf, i_img **imgs, int count) {
       if (glob_paletted)
         result = quant_paletted(quant, imgs[imgn]);
       else
-        result = quant_translate(quant, imgs[imgn]);
+        result = i_quant_translate(quant, imgs[imgn]);
       want_trans = glob_want_trans && imgs[imgn]->channels == 4;
       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;
       }
       map = NULL;
@@ -1838,6 +2075,12 @@ i_writegif_low(i_quantize *quant, GifFileType *gf, i_img **imgs, int count) {
     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;
 
@@ -1926,12 +2169,12 @@ i_writegif_callback(i_quantize *quant, i_write_callback_t cb, char *userdata,
     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);
-  return free_gen_write_data(gwd, result);
+  return i_free_gen_write_data(gwd, result);
 #else
   i_clear_error();
   i_push_error(0, "callbacks not supported with giflib3");