move the GIF file handling code into a sub-module
authorTony Cook <tony@develop=help.com>
Mon, 23 Aug 2010 09:38:20 +0000 (09:38 +0000)
committerTony Cook <tony@develop=help.com>
Mon, 23 Aug 2010 09:38:20 +0000 (09:38 +0000)
44 files changed:
Changes
GIF/GIF.pm [new file with mode: 0644]
GIF/GIF.xs [new file with mode: 0644]
GIF/Makefile.PL [new file with mode: 0644]
GIF/imgif.c [new file with mode: 0644]
GIF/imgif.h [new file with mode: 0644]
GIF/t/t10gif.t [new file with mode: 0644]
GIF/t/t20new.t [new file with mode: 0644]
GIF/testimg/badindex.gif [new file with mode: 0644]
GIF/testimg/bandw.gif [new file with mode: 0644]
GIF/testimg/expected.gif [new file with mode: 0644]
GIF/testimg/loccmap.gif [new file with mode: 0644]
GIF/testimg/nocmap.gif [new file with mode: 0644]
GIF/testimg/scale.gif [new file with mode: 0644]
GIF/testimg/scalei.gif [new file with mode: 0644]
GIF/testimg/screen2.gif [new file with mode: 0644]
GIF/testimg/screen3.gif [new file with mode: 0644]
GIF/testimg/trimgdesc.gif [new file with mode: 0644]
GIF/testimg/trmiddesc.gif [new file with mode: 0644]
GIF/testimg/zerocomm.gif [new file with mode: 0644]
Imager.pm
Imager.xs
MANIFEST
Makefile.PL
gif.c [deleted file]
imdatatypes.h
imextpl.h [new file with mode: 0644]
imextpltypes.h [new file with mode: 0644]
t/t105gif.t [deleted file]
t/t105nogif.t
t/t50basicoo.t
t/t70newgif.t [deleted file]
testimg/badindex.gif [deleted file]
testimg/bandw.gif [deleted file]
testimg/expected.gif [deleted file]
testimg/loccmap.gif [deleted file]
testimg/nocmap.gif [deleted file]
testimg/scale.gif [deleted file]
testimg/scalei.gif [deleted file]
testimg/screen2.gif [deleted file]
testimg/screen3.gif [deleted file]
testimg/trimgdesc.gif [deleted file]
testimg/trmiddesc.gif [deleted file]
testimg/zerocomm.gif [deleted file]

diff --git a/Changes b/Changes
index 42dfbe3..025e5a0 100644 (file)
--- a/Changes
+++ b/Changes
@@ -8,6 +8,10 @@ Imager 0.78 - unreleased
    Thanks to Justin Davis.
    https://rt.cpan.org/Ticket/Display.html?id=60491
 
+ - moved the GIF file handling code into a sub-module in preparation
+   for separate distribution.
+   https://rt.cpan.org/Ticket/Display.html?id=49616 (partial)
+
 Bug fixes:
 
  - Imager::Probe was calling ExtUtils::Liblist to initialize
diff --git a/GIF/GIF.pm b/GIF/GIF.pm
new file mode 100644 (file)
index 0000000..de067cd
--- /dev/null
@@ -0,0 +1,130 @@
+package Imager::File::GIF;
+use strict;
+use Imager;
+use vars qw($VERSION @ISA);
+
+BEGIN {
+  $VERSION = "0.77";
+
+  eval {
+    require XSLoader;
+    XSLoader::load('Imager::File::GIF', $VERSION);
+    1;
+  } or do {
+print STDERR "Falling back to DynaLoader ($@)\n";
+    require DynaLoader;
+    push @ISA, 'DynaLoader';
+    bootstrap Imager::File::GIF $VERSION;
+  };
+}
+
+Imager->register_reader
+  (
+   type=>'gif',
+   single => 
+   sub { 
+     my ($im, $io, %hsh) = @_;
+
+     if ($hsh{gif_consolidate}) {
+       if ($hsh{colors}) {
+        my $colors;
+        ($im->{IMG}, $colors) =i_readgif_wiol( $io );
+        if ($colors) {
+          ${ $hsh{colors} } = [ map { NC(@$_) } @$colors ];
+        }
+       }
+       else {
+        $im->{IMG} =i_readgif_wiol( $io );
+       }
+     }
+     else {
+       my $page = $hsh{page};
+       defined $page or $page = 0;
+       $im->{IMG} = i_readgif_single_wiol($io, $page);
+
+       unless ($im->{IMG}) {
+        $im->_set_error(Imager->_error_as_msg);
+        return;
+       }
+       if ($hsh{colors}) {
+        ${ $hsh{colors} } = [ $im->getcolors ];
+       }
+       return $im;
+     }
+   },
+   multiple =>
+   sub {
+     my ($io, %hsh) = @_;
+
+     my @imgs = i_readgif_multi_wiol($io);
+     unless (@imgs) {
+       Imager->_set_error(Imager->_error_as_msg);
+       return;
+     }
+
+     return map bless({ IMG => $_, ERRSTR => undef }, "Imager"), @imgs;
+   },
+  );
+
+Imager->register_writer
+  (
+   type=>'gif',
+   single => 
+   sub { 
+     my ($im, $io, %hsh) = @_;
+
+     $im->_set_opts(\%hsh, "i_", $im);
+     $im->_set_opts(\%hsh, "gif_", $im);
+
+     unless (i_writegif_wiol($io, \%hsh, $im->{IMG})) {
+       $im->_set_error(Imager->_error_as_msg);
+       return;
+     }
+     return $im;
+   },
+   multiple =>
+   sub {
+     my ($class, $io, $opts, @ims) = @_;
+
+     Imager->_set_opts($opts, "gif_", @ims);
+
+     my @work = map $_->{IMG}, @ims;
+     unless (i_writegif_wiol($io, $opts, @work)) {
+       Imager->_set_error(Imager->_error_as_msg);
+       return;
+     }
+
+     return 1;
+   },
+  );
+
+__END__
+
+=head1 NAME
+
+Imager::File::GIF - read and write GIF files
+
+=head1 SYNOPSIS
+
+  use Imager;
+
+  my $img = Imager->new;
+  $img->read(file=>"foo.gif")
+    or die $img->errstr;
+
+  $img->write(file => "foo.gif")
+    or die $img->errstr;
+
+=head1 DESCRIPTION
+
+Imager's GIF support is documented in L<Imager::Files>.
+
+=head1 AUTHOR
+
+Tony Cook <tony@imager.perl.org>
+
+=head1 SEE ALSO
+
+Imager, Imager::Files.
+
+=cut
diff --git a/GIF/GIF.xs b/GIF/GIF.xs
new file mode 100644 (file)
index 0000000..312c181
--- /dev/null
@@ -0,0 +1,149 @@
+#define PERL_NO_GET_CONTEXT
+#ifdef __cplusplus
+extern "C" {
+#endif
+#include "EXTERN.h"
+#include "perl.h"
+#include "XSUB.h"
+#include "imext.h"
+#include "imperl.h"
+#include "imgif.h"
+#include "imextpl.h"
+
+DEFINE_IMAGER_CALLBACKS;
+DEFINE_IMAGER_PERL_CALLBACKS;
+
+MODULE = Imager::File::GIF  PACKAGE = Imager::File::GIF
+
+long
+i_giflib_version()
+
+undef_int
+i_writegif_wiol(ig, opts,...)
+       Imager::IO ig
+      PREINIT:
+       i_quantize quant;
+       i_img **imgs = NULL;
+       int img_count;
+       int i;
+       HV *hv;
+      CODE:
+       if (items < 3)
+           croak("Usage: i_writegif_wiol(IO,hashref, images...)");
+       if (!SvROK(ST(1)) || ! SvTYPE(SvRV(ST(1))))
+           croak("i_writegif_callback: Second argument must be a hash ref");
+       hv = (HV *)SvRV(ST(1));
+       memset(&quant, 0, sizeof(quant));
+       quant.version = 1;
+       quant.mc_size = 256;
+       quant.transp = tr_threshold;
+       quant.tr_threshold = 127;
+       ip_handle_quant_opts(aTHX_ &quant, hv);
+       img_count = items - 2;
+       RETVAL = 1;
+       if (img_count < 1) {
+         RETVAL = 0;
+       }
+       else {
+          imgs = mymalloc(sizeof(i_img *) * img_count);
+          for (i = 0; i < img_count; ++i) {
+           SV *sv = ST(2+i);
+           imgs[i] = NULL;
+           if (SvROK(sv) && sv_derived_from(sv, "Imager::ImgRaw")) {
+             imgs[i] = INT2PTR(i_img *, SvIV((SV*)SvRV(sv)));
+           }
+           else {
+             RETVAL = 0;
+             break;
+            }
+         }
+          if (RETVAL) {
+           RETVAL = i_writegif_wiol(ig, &quant, imgs, img_count);
+          }
+         myfree(imgs);
+          if (RETVAL) {
+           ip_copy_colors_back(aTHX_ hv, &quant);
+          }
+       }
+       ST(0) = sv_newmortal();
+       if (RETVAL == 0) ST(0)=&PL_sv_undef;
+       else sv_setiv(ST(0), (IV)RETVAL);
+       ip_cleanup_quant_opts(aTHX_ &quant);
+
+
+void
+i_readgif_wiol(ig)
+     Imager::IO         ig
+             PREINIT:
+               int*    colour_table;
+               int     colours, q, w;
+             i_img*    rimg;
+                 SV*    temp[3];
+                 AV*    ct; 
+                 SV*    r;
+              PPCODE:
+              colour_table = NULL;
+               colours = 0;
+
+       if(GIMME_V == G_ARRAY) {
+            rimg = i_readgif_wiol(ig,&colour_table,&colours);
+        } else {
+            /* don't waste time with colours if they aren't wanted */
+            rimg = i_readgif_wiol(ig,NULL,NULL);
+        }
+       
+       if (colour_table == NULL) {
+            EXTEND(SP,1);
+            r=sv_newmortal();
+            sv_setref_pv(r, "Imager::ImgRaw", (void*)rimg);
+            PUSHs(r);
+       } else {
+            /* the following creates an [[r,g,b], [r, g, b], [r, g, b]...] */
+            /* I don't know if I have the reference counts right or not :( */
+            /* Neither do I :-) */
+            /* No Idea here either */
+
+            ct=newAV();
+            av_extend(ct, colours);
+            for(q=0; q<colours; q++) {
+                for(w=0; w<3; w++)
+                    temp[w]=sv_2mortal(newSViv(colour_table[q*3 + w]));
+                av_store(ct, q, (SV*)newRV_noinc((SV*)av_make(3, temp)));
+            }
+            myfree(colour_table);
+
+            EXTEND(SP,2);
+            r = sv_newmortal();
+            sv_setref_pv(r, "Imager::ImgRaw", (void*)rimg);
+            PUSHs(r);
+            PUSHs(newRV_noinc((SV*)ct));
+        }
+
+Imager::ImgRaw
+i_readgif_single_wiol(ig, page=0)
+       Imager::IO      ig
+        int            page
+
+void
+i_readgif_multi_wiol(ig)
+        Imager::IO ig
+      PREINIT:
+        i_img **imgs;
+        int count;
+        int i;
+      PPCODE:
+        imgs = i_readgif_multi_wiol(ig, &count);
+        if (imgs) {
+          EXTEND(SP, count);
+          for (i = 0; i < count; ++i) {
+            SV *sv = sv_newmortal();
+            sv_setref_pv(sv, "Imager::ImgRaw", (void *)imgs[i]);
+            PUSHs(sv);
+          }
+          myfree(imgs);
+        }
+
+
+BOOT:
+       PERL_INITIALIZE_IMAGER_CALLBACKS;
+       PERL_INITIALIZE_IMAGER_PERL_CALLBACKS;
diff --git a/GIF/Makefile.PL b/GIF/Makefile.PL
new file mode 100644 (file)
index 0000000..bdf8620
--- /dev/null
@@ -0,0 +1,162 @@
+#!perl -w
+use strict;
+use ExtUtils::MakeMaker qw(WriteMakefile WriteEmptyMakefile);
+use Getopt::Long;
+use Config;
+
+my $verbose = $ENV{IM_VERBOSE};
+my @libpaths;
+my @incpaths;
+
+GetOptions("incpath=s", \@incpaths,
+           "libpath=s" => \@libpaths,
+           "verbose|v" => \$verbose);
+
+our $BUILDING_IMAGER;
+
+my $MM_ver = eval $ExtUtils::MakeMaker::VERSION;
+
+my %opts = 
+  (
+   NAME => 'Imager::File::GIF',
+   VERSION_FROM => 'GIF.pm',
+   OBJECT => 'GIF.o imgif.o',
+  );
+
+my @inc;
+if ($BUILDING_IMAGER) {
+  unshift @inc, "-I..";
+  unshift @INC, "../lib";
+}
+else {
+  unshift @INC, "inc";
+  print "GIF: building independently\n";
+  require Imager::ExtUtils;
+  push @inc, Imager::ExtUtils->includes;
+  $opts{TYPEMAPS} = [ Imager::ExtUtils->typemap ];
+
+  # Imager required configure through use
+  my @Imager_req = ( Imager => "0.78" );
+  if ($MM_ver >= 6.46) {
+    $opts{META_MERGE} =
+      {
+       configure_requires => 
+       {
+       @Imager_req,
+       },
+       build_requires => 
+       {
+       @Imager_req,
+       "Test::More" => "0.47",
+       },
+       resources =>
+       {
+       homepage => "http://imager.perl.org/",
+       repository =>
+       {
+        url => "http://imager.perl.org/svn/trunk/Imager-File-GIF",
+        web => "http://imager.perl.org/svnweb/public/browse/trunk/Imager-File-GIF",
+        type => "svn",
+       },
+       },
+      };
+    $opts{PREREQ_PM} =
+      {
+       @Imager_req,
+      };
+  }
+}
+
+require Imager::Probe;
+
+my %probe =
+  (
+   name => "GIF",
+   inccheck => sub { -e File::Spec->catfile($_[0], "gif_lib.h") },
+   libbase => "gif",
+   testcode => _gif_test_code(),
+   testcodeheaders => [ "gif_lib.h", "stdio.h" ],
+   incpath => join($Config{path_sep}, @incpaths),
+   libpath => join($Config{path_sep}, @libpaths),
+  );
+
+my $probe_res = Imager::Probe->probe(\%probe);
+if ($probe_res) {
+  push @inc, $probe_res->{INC};
+  $opts{LIBS} = $probe_res->{LIBS};
+  
+  $opts{INC} = "@inc";
+  
+  if ($MM_ver > 6.06) {
+    $opts{AUTHOR} = 'Tony Cook <tony@imager.perl.org>';
+    $opts{ABSTRACT} = 'GIF Image file support';
+  }
+  
+  WriteMakefile(%opts);
+}
+else {
+  if ($BUILDING_IMAGER) {
+    WriteEmptyMakefile(%opts);
+  }
+  else {
+    # fail in good way
+    die "OS unsupported: GIF libraries or headers not found\n";
+  }
+}
+
+sub _gif_test_code {
+  return <<'CODE';
+/* TODO: test DGifOpen() and processing with callbacks */
+GifFileType *gf;
+const char vers[] = GIF_LIB_VERSION;
+const char *versp = vers;
+int ver_maj;
+int ver_min;
+fprintf(stderr, "GIF: header version '%s'\n", GIF_LIB_VERSION);
+gf=DGifOpenFileName("testimg/expected.gif");
+if (!gf) {
+  fprintf(stderr, "GIF: Cannot open testimg/expected.gif\n");
+  return 1;
+}
+if (gf->SWidth != 16 || gf->SHeight != 16) {
+  fprintf(stderr, "GIF: bad screen description (%d x %d)\n", gf->SWidth, gf->SHeight);
+  return 1;
+}
+/* crashes in older versions of giflib */
+EGifSetGifVersion("89a");
+EGifSetGifVersion("87a");
+
+/* skip the " Version " */
+while (*versp && (*versp < '0' || *versp > '9'))
+  ++versp;
+if (!*versp) {
+  fprintf(stderr, "GIF: Cannot find version number in '%s'\n", vers);
+  return 1;
+}
+ver_maj = 0;
+while (*versp && *versp >= '0' && *versp <= '9') {
+  ver_maj *= 10;
+  ver_maj += *versp++ - '0';
+}
+if (*versp != '.' || versp[1] < '0' || versp[1] > '9') {
+  fprintf(stderr, "Cannot parse major version number in '%s'\n", vers);
+  return 1;
+}
+++versp; /* skip '.' */
+ver_min = 0;
+while (*versp && *versp >= '0' && *versp <= '9') {
+  ver_min *= 10;
+  ver_min += *versp++ - '0';
+}
+if (ver_maj < 4) {
+  fprintf(stderr, "GIF: gif lib version 3 is no longer supported\n");
+  return 1;
+}
+if (ver_maj == 4 && ver_min < 1) {
+  fprintf(stderr, "GIF: you need at least giflib 4.1\n");
+  return 1;
+}
+fprintf(stderr, "GIF: Major %d, Minor %d\n", ver_maj, ver_min);
+return 0;
+CODE
+}
diff --git a/GIF/imgif.c b/GIF/imgif.c
new file mode 100644 (file)
index 0000000..0561419
--- /dev/null
@@ -0,0 +1,1925 @@
+#include "imgif.h"
+#include <gif_lib.h>
+#ifdef _MSC_VER
+#include <io.h>
+#else
+#include <unistd.h>
+#endif
+#include <errno.h>
+#include <string.h>
+
+/*
+=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);
+
+static int gif_read_callback(GifFileType *gft, GifByteType *buf, int length);
+
+/* Make some variables global, so we could access them faster: */
+
+static int
+  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;
+  }
+}
+
+long
+i_giflib_version(void) {
+  return 10;
+}
+
+/*
+=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;
+  
+  GifRowType GifRow;
+  static GifColorType *ColorMapEntry;
+  i_color col;
+
+  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);
+
+  if (ColorMap) {
+    ColorMapSize = ColorMap->ColorCount;
+    i_colortable_copy(colour_table, colours, ColorMap);
+    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_8_new(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); 
+  
+  GifRow = 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) {
+      gif_push_error();
+      i_push_error(0, "Unable to get record type");
+      if (colour_table && *colour_table) {
+       myfree(*colour_table);
+       *colour_table = NULL;
+      }
+      myfree(GifRow);
+      i_img_destroy(im);
+      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");
+       if (colour_table && *colour_table) {
+         myfree(*colour_table);
+         *colour_table = NULL;
+       }
+       myfree(GifRow);
+       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 */
+       myfree(GifRow);
+       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_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) {
+       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;
+       }
+       myfree(GifRow);
+       i_img_destroy(im);
+       DGifCloseFile(GifFile);
+       return NULL;
+      }
+      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, Width) == GIF_ERROR) {
+           gif_push_error();
+           i_push_error(0, "Reading GIF line");
+           if (colour_table && *colour_table) {
+             myfree(*colour_table);
+             *colour_table = NULL;
+           }
+           myfree(GifRow);
+           i_img_destroy(im);
+           DGifCloseFile(GifFile);
+           return NULL;
+         }
+         
+         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,Col+x,j,&col);
+         }
+         
+       }
+      }
+      else {
+       for (i = 0; i < Height; i++) {
+         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;
+           }
+           myfree(GifRow);
+           i_img_destroy(im);
+           DGifCloseFile(GifFile);
+           return NULL;
+         }
+
+         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, Col+x, Row, &col);
+         }
+         Row++;
+       }
+      }
+      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");
+       if (colour_table && *colour_table) {
+         myfree(*colour_table);
+         *colour_table = NULL;
+       }
+       myfree(GifRow);
+       i_img_destroy(im);
+       DGifCloseFile(GifFile);
+       return NULL;
+      }
+      while (Extension != NULL) {
+       if (DGifGetExtensionNext(GifFile, &Extension) == GIF_ERROR) {
+         gif_push_error();
+         i_push_error(0, "reading next block of extension");
+         if (colour_table && *colour_table) {
+           myfree(*colour_table);
+           *colour_table = NULL;
+         }
+         myfree(GifRow);
+         i_img_destroy(im);
+         DGifCloseFile(GifFile);
+         return NULL;
+       }
+      }
+      break;
+    case TERMINATE_RECORD_TYPE:
+      break;
+    default:               /* Should be traps by DGifGetRecordType. */
+      break;
+    }
+  } while (RecordType != TERMINATE_RECORD_TYPE);
+  
+  myfree(GifRow);
+  
+  if (DGifCloseFile(GifFile) == GIF_ERROR) {
+    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;
+  }
+
+  i_tags_set(&im->tags, "i_format", "gif", -1);
+
+  return im;
+}
+
+/*
+
+Internal function called by i_readgif_multi_low() in error handling
+
+*/
+static void free_images(i_img **imgs, int count) {
+  int i;
+  
+  if (count) {
+    for (i = 0; i < count; ++i)
+      i_img_destroy(imgs[i]);
+    myfree(imgs);
+  }
+}
+
+/*
+=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.
+
+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, int page) {
+  i_img *img;
+  int i, j, Size, Width, Height, ExtCode, Count;
+  int ImageNum = 0, BackGround = 0, ColorMapSize = 0;
+  ColorMapObject *ColorMap;
+  GifRecordType RecordType;
+  GifByteType *Extension;
+  
+  GifRowType GifRow;
+  int got_gce = 0;
+  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 = 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;
+
+  mm_log((1,"i_readgif_multi_low(GifFile %p, , count %p)\n", GifFile, count));
+
+  BackGround = GifFile->SBackGroundColor;
+
+  Size = GifFile->SWidth * sizeof(GifPixelType);
+  
+  GifRow = (GifRowType) mymalloc(Size);
+
+  /* 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);
+      myfree(GifRow);
+      if (comment)
+       myfree(comment);
+      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);
+       myfree(GifRow);
+       if (comment)
+         myfree(comment);
+       return NULL;
+      }
+
+      Width = GifFile->Image.Width;
+      Height = GifFile->Image.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);
+         if (comment)
+           myfree(comment);
+         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);
+         if (comment)
+           myfree(comment);
+         return NULL;
+       }
+       img = i_img_pal_new(Width, Height, channels, 256);
+       if (!img) {
+         free_images(results, *count);
+         DGifCloseFile(GifFile);
+         if (comment)
+           myfree(comment);
+         myfree(GifRow);
+         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_set(&img->tags, "i_format", "gif", -1);
+       i_tags_setn(&img->tags, "gif_left", GifFile->Image.Left);
+       /**(char *)0 = 1;*/
+       i_tags_setn(&img->tags, "gif_top",  GifFile->Image.Top);
+       i_tags_setn(&img->tags, "gif_interlace", GifFile->Image.Interlace);
+       i_tags_setn(&img->tags, "gif_screen_width", GifFile->SWidth);
+       i_tags_setn(&img->tags, "gif_screen_height", GifFile->SHeight);
+       i_tags_setn(&img->tags, "gif_colormap_size", ColorMapSize);
+       if (GifFile->SColorMap && !GifFile->Image.ColorMap) {
+         i_tags_setn(&img->tags, "gif_background",
+                     GifFile->SBackGroundColor);
+       }
+       if (GifFile->Image.ColorMap) {
+         i_tags_setn(&img->tags, "gif_localmap", 1);
+       }
+       if (got_gce) {
+         if (trans_index >= 0) {
+           i_color trans;
+           i_tags_setn(&img->tags, "gif_trans_index", trans_index);
+           i_getcolors(img, trans_index, &trans, 1);
+           i_tags_set_color(&img->tags, "gif_trans_color", 0, &trans);
+         }
+         i_tags_setn(&img->tags, "gif_delay", gif_delay);
+         i_tags_setn(&img->tags, "gif_user_input", user_input);
+         i_tags_setn(&img->tags, "gif_disposal", disposal);
+       }
+       got_gce = 0;
+       if (got_ns_loop)
+         i_tags_setn(&img->tags, "gif_loop", ns_loop);
+       if (comment) {
+         i_tags_set(&img->tags, "gif_comment", comment, strlen(comment));
+         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);
+         if (comment)
+           myfree(comment);
+         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);
+               if (comment)
+                 myfree(comment);
+               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);
+             if (comment)
+               myfree(comment);
+             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, i, GifRow);
+         }
+       }
+
+       /* must be only one image wanted and that was it */
+       if (page != -1) {
+         myfree(GifRow);
+         DGifCloseFile(GifFile);
+         if (comment)
+           myfree(comment);
+         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);
+           myfree(GifRow);
+           DGifCloseFile(GifFile);
+           if (comment) 
+             myfree(comment);
+           return NULL;
+         }
+       }
+
+       /* 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: */
+      if (DGifGetExtension(GifFile, &ExtCode, &Extension) == GIF_ERROR) {
+       gif_push_error();
+       i_push_error(0, "Reading extension record");
+        free_images(results, *count);
+       myfree(GifRow);
+       DGifCloseFile(GifFile);
+       if (comment)
+         myfree(comment);
+       return NULL;
+      }
+      /* possibly this should be an error, but "be liberal in what you accept" */
+      if (!Extension)
+       break;
+      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[1] & 2) != 0;
+        disposal = (Extension[1] >> 2) & 7;
+      }
+      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);
+           myfree(GifRow);
+           DGifCloseFile(GifFile);
+           if (comment)
+             myfree(comment);
+            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);
+         myfree(GifRow);
+         DGifCloseFile(GifFile);
+         if (comment)
+           myfree(comment);
+         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_set(&(results[*count-1]->tags), "gif_comment", comment, 
+                 strlen(comment));
+    }
+    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;
+  }
+
+  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;
+}
+
+static int io_glue_read_cb(GifFileType *gft, GifByteType *buf, int length);
+
+/*
+=item i_readgif_multi_wiol(ig, int *count)
+
+=cut
+*/
+
+i_img **
+i_readgif_multi_wiol(io_glue *ig, int *count) {
+  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);
+}
+
+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);
+}
+
+i_img *
+i_readgif_wiol(io_glue *ig, int **color_table, int *colors) {
+  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);
+}
+
+/*
+=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) {
+  i_clear_error();
+
+  if (page < 0) {
+    i_push_error(0, "page must be non-negative");
+    return NULL;
+  }
+
+  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);
+}
+
+/*
+=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, 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]) {
+       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;
+       }
+      }
+    }
+  }
+  else {
+    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;
+      }
+      data += img->xsize;
+    }
+  }
+
+  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, 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 (i_tags_get_int(&img->tags, "gif_delay", 0, &delay)) {
+    gce[1] = delay % 256;
+    gce[2] = delay / 256;
+    ++want_gce;
+  }
+  if (i_tags_get_int(&img->tags, "gif_user_input", 0, &user_input) 
+      && user_input) {
+    gce[0] |= 2;
+    ++want_gce;
+  }
+  if (i_tags_get_int(&img->tags, "gif_disposal", 0, &disposal_method)) {
+    gce[0] |= (disposal_method & 3) << 2;
+    ++want_gce;
+  }
+  if (want_gce) {
+    if (EGifPutExtension(gf, 0xF9, sizeof(gce), gce) == GIF_ERROR) {
+      gif_push_error();
+      i_push_error(0, "Could not save GCE");
+    }
+  }
+  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)
+
+Internal.  Add the Netscape2.0 loop extension block, if requested.
+
+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
+*/
+static int do_ns_loop(GifFileType *gf, i_img *img)
+{
+  /* 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
+     private data), we can't do an end-run and write the data 
+     directly to the fd.
+     There's no open interface that takes a FILE * either, so we 
+     can't workaround it that way either.
+     If giflib's callback interface wasn't broken by default, I'd 
+     force file writes to use callbacks, but it is broken by default.
+  */
+  /* yes this was another attempt at supporting the loop extension */
+  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 (EGifPutExtensionFirst(gf, APPLICATION_EXT_FUNC_CODE, 11, nsle) == GIF_ERROR) {
+      gif_push_error();
+      i_push_error(0, "writing loop extension");
+      return 0;
+    }
+    subblock[0] = 1;
+    subblock[1] = loop_count % 256;
+    subblock[2] = loop_count / 256;
+    if (EGifPutExtensionLast(gf, APPLICATION_EXT_FUNC_CODE, 3, subblock) == GIF_ERROR) {
+      gif_push_error();
+      i_push_error(0, "writing loop extension sub-block");
+      return 0;
+    }
+  }
+
+  return 1;
+}
+
+/*
+=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_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;
+    colors[i].Green = quant->mc_colors[i].rgb.g;
+    colors[i].Blue = quant->mc_colors[i].rgb.b;
+  }
+  if (want_trans) {
+    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;
+  while (map_size < size)
+    map_size <<= 1;
+  /* 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));
+  if (!map) {
+    gif_push_error();
+    i_push_error(0, "Could not create color map object");
+    return NULL;
+  }
+  return map;
+}
+
+/*
+=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.
+
+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.
+
+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) {
+  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
+     EGifSetGifVersion("87a");
+}
+
+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) {
+  int size = quant->mc_count;
+  int i;
+  int imgn;
+  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 (!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));
+
+      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) {
+  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 = -1;
+  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 = 0;
+  int glob_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;
+
+  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? */
+  }
+
+  /* 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, &scrh))
+    scrw = 0;
+
+  anylocal = 0;
+  localmaps = mymalloc(sizeof(int) * count);
+  glob_imgs = mymalloc(sizeof(i_img *) * count);
+  glob_img_count = 0;
+  glob_want_trans = 0;
+  for (imgn = 0; imgn < count; ++imgn) {
+    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]->channels == 4) {
+        glob_want_trans = 1;
+      }
+      glob_imgs[glob_img_count++] = imgs[imgn];
+    }
+  }
+  glob_want_trans = glob_want_trans && quant->transp != tr_none ;
+
+  orig_count = quant->mc_count;
+  orig_size = quant->mc_size;
+
+  if (glob_img_count) {
+    /* this is ugly */
+    glob_colors = mymalloc(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;
+    }
+    if (has_common_palette(glob_imgs, glob_img_count, quant, want_trans)) {
+      glob_paletted = 1;
+    }
+    else {
+      glob_paletted = 0;
+      i_quant_makemap(quant, glob_imgs, glob_img_count);
+    }
+    glob_color_count = quant->mc_count;
+    quant->mc_colors = orig_colors;
+  }
+
+  /* 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;
+    }
+    else {
+      colors_paletted = 0;
+      i_quant_makemap(quant, imgs, 1);
+    }
+  }
+  if ((map = make_gif_map(quant, imgs[0], want_trans)) == NULL) {
+    myfree(glob_colors);
+    myfree(localmaps);
+    myfree(glob_imgs);
+    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, 
+                        gif_background, map) == GIF_ERROR) {
+    myfree(glob_colors);
+    myfree(localmaps);
+    myfree(glob_imgs);
+    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 (!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 (!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) {
+       myfree(glob_colors);
+       myfree(localmaps);
+       myfree(glob_imgs);
+        EGifCloseFile(gf);
+        quant->mc_colors = orig_colors;
+        mm_log((1, "Error in MakeMapObject"));
+        return 0;
+      }
+    }
+    else {
+      /* the map we wrote was the map for this image - don't set the local 
+         map */
+      map = NULL;
+    }
+  }
+
+  if (colors_paletted)
+    result = quant_paletted(quant, imgs[0]);
+  else
+    result = i_quant_translate(quant, imgs[0]);
+  if (!result) {
+    myfree(glob_colors);
+    myfree(localmaps);
+    myfree(glob_imgs);
+    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])) {
+    myfree(glob_colors);
+    myfree(localmaps);
+    myfree(glob_imgs);
+    quant->mc_colors = orig_colors;
+    return 0;
+  }
+
+  if (!do_gce(gf, imgs[0], want_trans, trans_index)) {
+    myfree(glob_colors);
+    myfree(localmaps);
+    myfree(glob_imgs);
+    quant->mc_colors = orig_colors;
+    myfree(result);
+    EGifCloseFile(gf);
+    return 0;
+  }
+
+  if (!do_comments(gf, imgs[0])) {
+    myfree(glob_colors);
+    myfree(localmaps);
+    myfree(glob_imgs);
+    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) {
+    myfree(glob_colors);
+    myfree(localmaps);
+    myfree(glob_imgs);
+    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)) {
+    myfree(glob_colors);
+    myfree(localmaps);
+    myfree(glob_imgs);
+    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[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;
+      /* 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)) {
+        result = quant_paletted(quant, imgs[imgn]);
+      }
+      else {
+        i_quant_makemap(quant, imgs+imgn, 1);
+        result = i_quant_translate(quant, imgs[imgn]);
+      }
+      if (!result) {
+       myfree(glob_colors);
+       myfree(localmaps);
+       myfree(glob_imgs);
+        quant->mc_colors = orig_colors;
+        EGifCloseFile(gf);
+        mm_log((1, "error in i_quant_translate()"));
+        return 0;
+      }
+      if (want_trans) {
+        i_quant_transparent(quant, result, imgs[imgn], quant->mc_count);
+        trans_index = quant->mc_count;
+      }
+
+      if ((map = make_gif_map(quant, imgs[imgn], want_trans)) == NULL) {
+       myfree(glob_colors);
+       myfree(localmaps);
+       myfree(glob_imgs);
+        quant->mc_colors = orig_colors;
+        myfree(result);
+        EGifCloseFile(gf);
+        mm_log((1, "Error in MakeMapObject."));
+        return 0;
+      }
+    }
+    else {
+      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 (!do_gce(gf, imgs[imgn], want_trans, trans_index)) {
+      myfree(glob_colors);
+      myfree(localmaps);
+      myfree(glob_imgs);
+      quant->mc_colors = orig_colors;
+      myfree(result);
+      EGifCloseFile(gf);
+      return 0;
+    }
+
+    if (!do_comments(gf, imgs[imgn])) {
+      myfree(glob_colors);
+      myfree(localmaps);
+      myfree(glob_imgs);
+      quant->mc_colors = orig_colors;
+      myfree(result);
+      EGifCloseFile(gf);
+      return 0;
+    }
+
+    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) {
+      myfree(glob_colors);
+      myfree(localmaps);
+      myfree(glob_imgs);
+      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 (map)
+      FreeMapObject(map);
+    
+    if (!do_write(gf, interlace, imgs[imgn], result)) {
+      myfree(glob_colors);
+      myfree(localmaps);
+      myfree(glob_imgs);
+      quant->mc_colors = orig_colors;
+      EGifCloseFile(gf);
+      myfree(result);
+      return 0;
+    }
+    myfree(result);
+  }
+
+  if (EGifCloseFile(gf) == GIF_ERROR) {
+    myfree(glob_colors);
+    myfree(localmaps);
+    myfree(glob_imgs);
+    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];
+  }
+
+  myfree(glob_colors);
+  myfree(localmaps);
+  myfree(glob_imgs);
+  quant->mc_colors = orig_colors;
+
+  return 1;
+}
+
+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);
+}
+
+
+/*
+=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) {
+  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;
+}
+
+/*
+=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
+
+*/
diff --git a/GIF/imgif.h b/GIF/imgif.h
new file mode 100644 (file)
index 0000000..e58d013
--- /dev/null
@@ -0,0 +1,13 @@
+#ifndef IMAGER_IMGIF_H
+#define IMAGER_IMGIF_H
+
+#include "imext.h"
+
+long i_giflib_version(void);
+i_img *i_readgif_wiol(io_glue *ig, int **colour_table, int *colours);
+i_img *i_readgif_single_wiol(io_glue *ig, int page);
+extern i_img **i_readgif_multi_wiol(io_glue *ig, int *count);
+undef_int i_writegif_wiol(io_glue *ig, i_quantize *quant, 
+                          i_img **imgs, int count);
+
+#endif
diff --git a/GIF/t/t10gif.t b/GIF/t/t10gif.t
new file mode 100644 (file)
index 0000000..40a8860
--- /dev/null
@@ -0,0 +1,845 @@
+#!perl -w
+
+=pod
+
+IF THIS TEST CRASHES
+
+Giflib/libungif have a long history of bugs, so if this script crashes
+and you aren't running version 4.1.4 of giflib or libungif then
+UPGRADE.
+
+=cut
+
+use strict;
+$|=1;
+use Test::More;
+use Imager qw(:all);
+use Imager::Test qw(is_color3 test_image test_image_raw);
+use Imager::File::GIF;
+
+use Carp 'confess';
+$SIG{__DIE__} = sub { confess @_ };
+
+-d "testout" or mkdir "testout";
+
+init_log("testout/t105gif.log",1);
+
+plan tests => 144;
+
+my $green=i_color_new(0,255,0,255);
+my $blue=i_color_new(0,0,255,255);
+my $red=i_color_new(255,0,0,255);
+
+my $img=test_image_raw;
+
+my $gifver = Imager::File::GIF::i_giflib_version();
+diag("giflib version (from header) $gifver");
+
+{
+  open(FH,">testout/t105.gif") || die "Cannot open testout/t105.gif\n";
+  binmode(FH);
+  my $io = Imager::io_new_fd(fileno(FH));
+  ok(Imager::File::GIF::i_writegif_wiol($io, {}, $img), "write low") or
+    die "Cannot write testout/t105.gif\n";
+  $io->close;
+  close(FH);
+}
+
+{
+  open(FH,"testout/t105.gif") || die "Cannot open testout/t105.gif\n";
+  binmode(FH);
+  my $io = Imager::io_new_fd(fileno(FH));
+  ok($img=Imager::File::GIF::i_readgif_wiol($io), "read low")
+    or die "Cannot read testout/t105.gif\n";
+  close(FH);
+}
+
+{
+  open(FH,"testout/t105.gif") || die "Cannot open testout/t105.gif\n";
+  binmode(FH);
+  my $io = Imager::io_new_fd(fileno(FH));
+  ($img, my $palette)=Imager::File::GIF::i_readgif_wiol($io);
+  ok($img, "read palette") or die "Cannot read testout/t105.gif\n";
+  close(FH);
+  $palette=''; # just to skip a warning.
+}
+
+
+# check that reading interlaced/non-interlaced versions of 
+# the same GIF produce the same image
+# I could replace this with code that used Imager's built-in
+# image comparison code, but I know this code revealed the error
+{
+  open(FH, "<testimg/scalei.gif") || die "Cannot open testimg/scalei.gif";
+  binmode FH;
+  my $io = Imager::io_new_fd(fileno(FH));
+  my ($imgi) = Imager::File::GIF::i_readgif_wiol($io);
+  ok($imgi, "read interlaced") or die "Cannot read testimg/scalei.gif";
+  close FH;
+
+  open FH, "<testimg/scale.gif" or die "Cannot open testimg/scale.gif";
+  binmode FH;
+  $io = Imager::io_new_fd(fileno(FH));
+  my ($imgni) = Imager::File::GIF::i_readgif_wiol($io);
+  ok($imgni, "read normal") or die "Cannot read testimg/scale.gif";
+  close FH;
+
+  open FH, ">testout/t105i.ppm" or die "Cannot create testout/t105i.ppm";
+  binmode FH;
+  my $IO = Imager::io_new_fd( fileno(FH) );
+  i_writeppm_wiol($imgi, $IO)
+    or die "Cannot write testout/t105i.ppm";
+  close FH;
+
+  open FH, ">testout/t105ni.ppm" or die "Cannot create testout/t105ni.ppm";
+  binmode FH;
+  $IO = Imager::io_new_fd( fileno(FH) );
+  i_writeppm_wiol($imgni, $IO)
+    or die "Cannot write testout/t105ni.ppm";
+  close FH;
+
+  # compare them
+  open FH, "<testout/t105i.ppm" or die "Cannot open testout/t105i.ppm";
+  my $datai = do { local $/; <FH> };
+  close FH;
+
+  open FH, "<testout/t105ni.ppm" or die "Cannot open testout/t105ni.ppm";
+  my $datani = do { local $/; <FH> };
+  close FH;
+  is($datai, $datani, "images match");
+}
+
+{
+  # reading with a callback
+  # various sizes to make sure the buffering works
+  # requested size
+  open FH, "<testimg/scale.gif" or die "Cannot open testimg/scale.gif";
+  binmode FH;
+  my $io = Imager::io_new_cb
+    (
+     undef,
+     sub { my $tmp; read(FH, $tmp, $_[0]) and $tmp },
+     undef,
+     undef
+     );
+  # no callback version in giflib3, so don't overwrite a good image
+  my $img2 = Imager::File::GIF::i_readgif_wiol($io);
+  close FH; 
+  ok($img, "reading with a callback");
+  
+  ok(test_readgif_cb(1), "read callback 1 char buffer");
+  ok(test_readgif_cb(512), "read callback 512 char buffer");
+  ok(test_readgif_cb(1024), "read callback 1024 char buffer");
+}
+# new writegif_gen
+# test webmap, custom errdiff map
+# (looks fairly awful)
+{
+  open FH, ">testout/t105_gen.gif" or die $!;
+  binmode FH;
+  my $io = Imager::io_new_fd(fileno(FH));
+  ok(Imager::File::GIF::i_writegif_wiol
+     ($io, { make_colors=>'webmap',
+            translate=>'errdiff',
+            errdiff=>'custom',
+            errdiff_width=>2,
+            errdiff_height=>2,
+            errdiff_map=>[0, 1, 1, 0]}, $img),
+     "webmap, custom errdif map");
+  close FH;
+}
+
+print "# the following tests are fairly slow\n";
+
+# test animation, mc_addi, error diffusion, ordered transparency
+my @imgs;
+my $sortagreen = i_color_new(0, 255, 0, 63);
+for my $i (0..4) {
+  my $im = Imager::ImgRaw::new(200, 200, 4);
+  _add_tags($im, gif_delay=>50, gif_disposal=>2);
+  for my $j (0..$i-1) {
+    my $fill = i_color_new(0, 128, 0, 255 * ($i-$j)/$i);
+    i_box_filled($im, 0, $j*40, 199, $j*40+40, $fill);
+  }
+  i_box_filled($im, 0, $i*40, 199, 199, $blue);
+  push(@imgs, $im);
+}
+{
+  my @gif_delays = (50) x 5;
+  my @gif_disposal = (2) x 5;
+  open FH, ">testout/t105_anim.gif" or die $!;
+  binmode FH;
+  my $io = Imager::io_new_fd(fileno(FH));
+  ok(Imager::File::GIF::i_writegif_wiol
+     ($io, { make_colors=>'addi',
+            translate=>'closest',
+            gif_delays=>\@gif_delays,
+            gif_disposal=>\@gif_disposal,
+            gif_positions=> [ map [ $_*10, $_*10 ], 0..4 ],
+            gif_user_input=>[ 1, 0, 1, 0, 1 ],
+            transp=>'ordered',
+            'tr_orddith'=>'dot8'}, @imgs),
+     "write anim gif");
+  close FH;
+}
+
+@imgs = ();
+my $c = i_color_new(0,0,0,0);
+for my $g (0..3) {
+  my $im = Imager::ImgRaw::new(200, 200, 3);
+  _add_tags($im, gif_local_map=>1, gif_delay=>150, gif_loop=>10);
+  for my $x (0 .. 39) {
+    for my $y (0 .. 39) {
+      $c->set($x * 6, $y * 6, 32*$g+$x+$y, 255);
+      i_box_filled($im, $x*5, $y*5, $x*5+4, $y*5+4, $c);
+    }
+  }
+  push(@imgs, $im);
+}
+# test giflib with multiple palettes
+# (it was meant to test the NS loop extension too, but that's broken)
+# this looks better with make_colors=>'addi', translate=>'errdiff'
+# this test aims to overload the palette for each image, so the
+# output looks moderately horrible
+{
+  open FH, ">testout/t105_mult_pall.gif" or die "Cannot create file: $!";
+  binmode FH;
+  my $io = Imager::io_new_fd(fileno(FH));
+  ok(Imager::File::GIF::i_writegif_wiol
+     ($io, { #make_colors=>'webmap',
+           translate=>'giflib',
+          }, @imgs), "write multiple palettes")
+    or print "# ", join(":", map $_->[1], Imager::i_errors()),"\n";
+  close FH;
+}
+
+{
+  # regression test: giflib doesn't like 1 colour images
+  my $img1 = Imager::ImgRaw::new(100, 100, 3);
+  i_box_filled($img1, 0, 0, 100, 100, $red);
+  open FH, ">testout/t105_onecol.gif" or die $!;
+  binmode FH;
+  my $io = Imager::io_new_fd(fileno(FH));
+  ok(Imager::File::GIF::i_writegif_wiol($io, { translate=>'giflib'}, $img1),
+     "single colour write regression");
+  close FH;
+}
+
+{
+  # transparency test
+  # previously it was harder do write transparent images
+  # tests the improvements
+  my $timg = Imager::ImgRaw::new(20, 20, 4);
+  my $trans = i_color_new(255, 0, 0, 127);
+  i_box_filled($timg, 0, 0, 20, 20, $green);
+  i_box_filled($timg, 2, 2, 18, 18, $trans);
+  open FH, ">testout/t105_trans.gif" or die $!;
+  binmode FH;
+  my $io = Imager::io_new_fd(fileno(FH));
+  ok(Imager::File::GIF::i_writegif_wiol
+     ($io, { make_colors=>'addi',
+            translate=>'closest',
+            transp=>'ordered',
+          }, $timg), "write transparent");
+  close FH;
+}
+
+# some error handling tests
+# open a file handle for read and try to save to it
+# is this idea portable?
+# whether or not it is, giflib segfaults on this <sigh>
+#open FH, "<testout/t105_trans.gif" or die $!;
+#binmode FH; # habit, I suppose
+#if (i_writegif_gen(fileno(FH), {}, $timg)) {
+#  # this is meant to _fail_
+#  print "not ok 18 # writing to read-only should fail";
+#}
+#else {
+#  print "ok 18 # ",Imager::_error_as_msg(),"\n";
+#}
+#close FH;
+
+{
+  # try to read a file of the wrong format - the script will do
+  open FH, "<t/t10gif.t"
+    or die "Cannot open this script!: $!";
+  binmode FH;
+  my $io = Imager::io_new_fd(fileno(FH));
+  ok(!Imager::File::GIF::i_readgif_wiol($io), 
+     "read test script as gif should fail ". Imager::_error_as_msg());
+  close FH;
+}
+
+{
+  # try to save no images :)
+  open FH, ">testout/t105_none.gif"
+    or die "Cannot open testout/t105_none.gif: $!";
+  binmode FH;
+  my $io = Imager::io_new_fd(fileno(FH));
+  if (ok(!Imager::File::GIF::i_writegif_wiol($io, {}, "hello"), "shouldn't be able to write a string as a gif")) {
+    print "# ",Imager::_error_as_msg(),"\n";
+  }
+}
+
+# try to read a truncated gif (no image descriptors)
+read_failure('testimg/trimgdesc.gif');
+# file truncated just after the image descriptor tag
+read_failure('testimg/trmiddesc.gif');
+# image has no colour map
+read_failure('testimg/nocmap.gif');
+
+{
+  # image has a local colour map
+  open FH, "< testimg/loccmap.gif"
+    or die "Cannot open testimg/loccmap.gif: $!";
+  binmode FH;
+  my $io = Imager::io_new_fd(fileno(FH));
+  ok(Imager::File::GIF::i_readgif_wiol($io),
+     "read an image with only a local colour map");
+  close FH;
+}
+
+{
+  # image has global and local colour maps
+  open FH, "< testimg/screen2.gif"
+    or die "Cannot open testimg/screen2.gif: $!";
+  binmode FH;
+  my $io = Imager::io_new_fd(fileno(FH));
+  my $ims = Imager::File::GIF::i_readgif_wiol($io);
+  unless (ok($ims, "read an image with global and local colour map")) {
+    print "# ",Imager::_error_as_msg(),"\n";
+  }
+  close FH;
+  
+  open FH, "< testimg/expected.gif"
+    or die "Cannot open testimg/expected.gif: $!";
+  binmode FH;
+  $io = Imager::io_new_fd(fileno(FH));
+  my $ime = Imager::File::GIF::i_readgif_wiol($io);
+  close FH;
+  ok($ime, "reading testimg/expected.gif");
+ SKIP:
+  {
+    skip("could not read one or both of expected.gif or loccamp.gif", 1)
+      unless $ims and $ime;
+    unless (is(i_img_diff($ime, $ims), 0, 
+              "compare loccmap and expected")) {
+      # save the bad one
+      open FH, "> testout/t105_screen2.gif"
+       or die "Cannot create testout/t105_screen.gif: $!";
+      binmode FH;
+      my $io = Imager::io_new_fd(fileno(FH));
+      Imager::File::GIF::i_writegif_wiol($io, {}, $ims)
+       or print "# could not save t105_screen.gif\n";
+      close FH;
+    }
+  }
+}
+
+{
+  # test reading a multi-image file into multiple images
+  open FH, "< testimg/screen2.gif"
+    or die "Cannot open testimg/screen2.gif: $!";
+  binmode FH;
+  my $io = Imager::io_new_fd(fileno(FH));
+  @imgs = Imager::File::GIF::i_readgif_multi_wiol($io);
+  ok(@imgs, "read multi-image file into multiple images");
+  close FH;
+  is(@imgs, 2, "should be 2 images");
+  my $paletted = 1;
+  for my $img (@imgs) {
+    unless (Imager::i_img_type($img) == 1) {
+      $paletted = 0;
+      last;
+    }
+  }
+  ok($paletted, "both images should be paletted");
+  is(Imager::i_colorcount($imgs[0]), 4, "4 colours in first image");
+  is(Imager::i_colorcount($imgs[1]), 2, "2 colours in second image");
+  ok(Imager::i_tags_find($imgs[0], "gif_left", 0), 
+     "gif_left tag should be there");
+  my @tags = map {[ Imager::i_tags_get($imgs[1], $_) ]} 0..Imager::i_tags_count($imgs[1])-1;
+  my ($left) = grep $_->[0] eq 'gif_left', @tags;
+  ok($left && $left->[1] == 3, "check gif_left value");
+}
+
+{
+  # screen3.gif was saved with 
+  open FH, "< testimg/screen3.gif"
+    or die "Cannot open testimg/screen3.gif: $!";
+  binmode FH;
+  my $io = Imager::io_new_fd(fileno(FH));
+  @imgs = Imager::File::GIF::i_readgif_multi_wiol($io);
+  ok(@imgs, "read screen3.gif");
+  close FH;
+  eval {
+    require 'Data/Dumper.pm';
+    Data::Dumper->import();
+  };
+
+  unless ($@) {
+    # build a big map of all tags for all images
+    my @tags = 
+      map { 
+       my $im = $_; 
+       [ 
+        map { join ",", map { defined() ? $_ : "undef" } Imager::i_tags_get($im, $_) } 
+        0..Imager::i_tags_count($_)-1 
+       ] 
+      } @imgs;
+    my $dump = Dumper(\@tags);
+    $dump =~ s/^/# /mg;
+    print "# tags from gif\n", $dump;
+  }
+
+  # at this point @imgs should contain only paletted images
+  ok(Imager::i_img_type($imgs[0]) == 1, "imgs[0] paletted");
+  ok(Imager::i_img_type($imgs[1]) == 1, "imgs[1] paletted");
+
+  # see how we go saving it
+  open FH, ">testout/t105_pal.gif" or die $!;
+  binmode FH;
+  $io = Imager::io_new_fd(fileno(FH));
+  ok(Imager::File::GIF::i_writegif_wiol
+     ($io, { make_colors=>'addi',
+            translate=>'closest',
+            transp=>'ordered',
+          }, @imgs), "write from paletted");
+  close FH;
+  
+  # make sure nothing bad happened
+  open FH, "< testout/t105_pal.gif" or die $!;
+  binmode FH;
+  $io = Imager::io_new_fd(fileno(FH));
+  ok((my @imgs2 = Imager::File::GIF::i_readgif_multi_wiol($io)) == 2,
+     "re-reading saved paletted images");
+  ok(i_img_diff($imgs[0], $imgs2[0]) == 0, "imgs[0] mismatch");
+  ok(i_img_diff($imgs[1], $imgs2[1]) == 0, "imgs[1] mismatch");
+}
+
+# test that the OO interface warns when we supply old options
+{
+  my @warns;
+  local $SIG{__WARN__} = sub { push(@warns, "@_") };
+  
+  my $ooim = Imager->new;
+  ok($ooim->read(file=>"testout/t105.gif"), "read into object");
+  ok($ooim->write(file=>"testout/t105_warn.gif", interlace=>1),
+     "save from object")
+    or print "# ", $ooim->errstr, "\n";
+  ok(grep(/Obsolete .* interlace .* gif_interlace/, @warns),
+     "check for warning");
+  init(warn_obsolete=>0);
+  @warns = ();
+  ok($ooim->write(file=>"testout/t105_warn.gif", interlace=>1),
+     "save from object");
+  ok(!grep(/Obsolete .* interlace .* gif_interlace/, @warns),
+     "check for warning");
+}
+
+# test that we get greyscale from 1 channel images
+# we check for each makemap, and for each translate
+print "# test writes of grayscale images - ticket #365\n"; 
+my $ooim = Imager->new(xsize=>50, ysize=>50, channels=>1);
+for (my $y = 0; $y < 50; $y += 10) {
+  $ooim->box(box=>[ 0, $y, 49, $y+9], color=>NC($y*5,0,0), filled=>1);
+}
+my $ooim3 = $ooim->convert(preset=>'rgb');
+#$ooim3->write(file=>'testout/t105gray.ppm');
+my %maxerror = ( mediancut => 51000, 
+                addi => 0,
+                closest => 0,
+                perturb => 0,
+                errdiff => 0 );
+for my $makemap (qw(mediancut addi)) {
+  print "# make_colors => $makemap\n";
+  ok( $ooim->write(file=>"testout/t105gray-$makemap.gif",
+                  make_colors=>$makemap,
+                  gifquant=>'gen'),
+      "writing gif with makemap $makemap");
+  my $im2 = Imager->new;
+  if (ok($im2->read(file=>"testout/t105gray-$makemap.gif"),
+        "reading written grayscale gif")) {
+    my $diff = i_img_diff($ooim3->{IMG}, $im2->{IMG});
+    ok($diff <= $maxerror{$makemap}, "comparing images $diff");
+    #$im2->write(file=>"testout/t105gray-$makemap.ppm");
+  }
+  else {
+  SKIP: { skip("could not get test image", 1); }
+  }
+}
+for my $translate (qw(closest perturb errdiff)) {
+  print "# translate => $translate\n";
+  my @colors = map NC($_*50, $_*50, $_*50), 0..4;
+  ok($ooim->write(file=>"testout/t105gray-$translate.gif",
+                 translate=>$translate,
+                 make_colors=>'none',
+                 colors=>\@colors,
+                 gifquant=>'gen'),
+     "writing gif with translate $translate");
+  my $im2 = Imager->new;
+  if (ok($im2->read(file=>"testout/t105gray-$translate.gif"),
+        "reading written grayscale gif")) {
+    my $diff = i_img_diff($ooim3->{IMG}, $im2->{IMG});
+    ok($diff <= $maxerror{$translate}, "comparing images $diff");
+    #$im2->write(file=>"testout/t105gray-$translate.ppm");
+  }
+  else {
+  SKIP: { skip("could not load test image", 1) }
+  }
+    }
+
+# try to write an image with no colors - should error
+ok(!$ooim->write(file=>"testout/t105nocolors.gif",
+                make_colors=>'none',
+                colors=>[], gifquant=>'gen'),
+   "write with no colors");
+
+# try to write multiple with no colors, with separate maps
+# I don't see a way to test this, since we don't have a mechanism
+# to give the second image different quant options, we can't trigger
+# a failure just for the second image
+
+# check that the i_format tag is set for both multiple and single
+# image reads
+{
+  my @anim = Imager->read_multi(file=>"testout/t105_anim.gif");
+  ok(@anim == 5, "check we got all the images");
+  for my $frame (@anim) {
+    my ($type) = $frame->tags(name=>'i_format');
+    is($type, 'gif', "check i_format for animation frame");
+  }
+  
+  my $im = Imager->new;
+  ok($im->read(file=>"testout/t105.gif"), "read some gif");
+  my ($type) = $im->tags(name=>'i_format');
+  is($type, 'gif', 'check i_format for single image read');
+}
+
+{ # check file limits are checked
+  my $limit_file = "testout/t105.gif";
+  ok(Imager->set_file_limits(reset=>1, width=>149), "set width limit 149");
+  my $im = Imager->new;
+  ok(!$im->read(file=>$limit_file),
+     "should fail read due to size limits");
+  print "# ",$im->errstr,"\n";
+  like($im->errstr, qr/image width/, "check message");
+  
+  ok(Imager->set_file_limits(reset=>1, height=>149), "set height limit 149");
+  ok(!$im->read(file=>$limit_file),
+     "should fail read due to size limits");
+  print "# ",$im->errstr,"\n";
+  like($im->errstr, qr/image height/, "check message");
+  
+  ok(Imager->set_file_limits(reset=>1, width=>150), "set width limit 150");
+  ok($im->read(file=>$limit_file),
+     "should succeed - just inside width limit");
+  ok(Imager->set_file_limits(reset=>1, height=>150), "set height limit 150");
+  ok($im->read(file=>$limit_file),
+     "should succeed - just inside height limit");
+  
+  # 150 x 150 x 3 channel image uses 67500 bytes
+  ok(Imager->set_file_limits(reset=>1, bytes=>67499),
+     "set bytes limit 67499");
+  ok(!$im->read(file=>$limit_file),
+     "should fail - too many bytes");
+  print "# ",$im->errstr,"\n";
+  like($im->errstr, qr/storage size/, "check error message");
+  ok(Imager->set_file_limits(reset=>1, bytes=>67500),
+     "set bytes limit 67500");
+  ok($im->read(file=>$limit_file),
+     "should succeed - just inside bytes limit");
+  Imager->set_file_limits(reset=>1);
+}
+
+{
+  print "# test OO interface reading of consolidated images\n";
+  my $im = Imager->new;
+  ok($im->read(file=>'testimg/screen2.gif', gif_consolidate=>1),
+     "read image to consolidate");
+  my $expected = Imager->new;
+  ok($expected->read(file=>'testimg/expected.gif'),
+     "read expected via OO");
+  is(i_img_diff($im->{IMG}, $expected->{IMG}), 0,
+     "compare them");
+  
+  # check the default read doesn't match
+  ok($im->read(file=>'testimg/screen2.gif'),
+     "read same image without consolidate");
+  isnt(i_img_diff($im->{IMG}, $expected->{IMG}), 0,
+       "compare them - shouldn't include the overlayed second image");
+}
+{
+  print "# test the reading of single pages\n";
+  # build a test file
+  my $test_file = 'testout/t105_multi_sing.gif';
+  my $im1 = Imager->new(xsize=>100, ysize=>100);
+  $im1->box(filled=>1, color=>$blue);
+  $im1->addtag(name=>'gif_left', value=>10);
+  $im1->addtag(name=>'gif_top', value=>15);
+  $im1->addtag(name=>'gif_comment', value=>'First page');
+  my $im2 = Imager->new(xsize=>50, ysize=>50);
+  $im2->box(filled=>1, color=>$red);
+  $im2->addtag(name=>'gif_left', value=>30);
+  $im2->addtag(name=>'gif_top', value=>25);
+  $im2->addtag(name=>'gif_comment', value=>'Second page');
+  my $im3 = Imager->new(xsize=>25, ysize=>25);
+  $im3->box(filled=>1, color=>$green);
+  $im3->addtag(name=>'gif_left', value=>35);
+  $im3->addtag(name=>'gif_top', value=>45);
+  # don't set comment for $im3
+  ok(Imager->write_multi({ file=> $test_file}, $im1, $im2, $im3),
+     "write test file for single page reads");
+  
+  my $res = Imager->new;
+  # check we get the first image
+  ok($res->read(file=>$test_file), "read default (first) page");
+  is(i_img_diff($im1->{IMG}, $res->{IMG}), 0, "compare against first");
+  # check tags
+  is($res->tags(name=>'gif_left'), 10, "gif_left");
+  is($res->tags(name=>'gif_top'), 15, "gif_top");
+  is($res->tags(name=>'gif_comment'), 'First page', "gif_comment");
+  
+  # get the second image
+  ok($res->read(file=>$test_file, page=>1), "read second page")
+    or print "# ",$res->errstr, "\n";
+  is(i_img_diff($im2->{IMG}, $res->{IMG}), 0, "compare against second");
+  # check tags
+  is($res->tags(name=>'gif_left'), 30, "gif_left");
+  is($res->tags(name=>'gif_top'), 25, "gif_top");
+  is($res->tags(name=>'gif_comment'), 'Second page', "gif_comment");
+  
+  # get the third image
+  ok($res->read(file=>$test_file, page=>2), "read third page")
+    or print "# ",$res->errstr, "\n";
+  is(i_img_diff($im3->{IMG}, $res->{IMG}), 0, "compare against third");
+  is($res->tags(name=>'gif_left'), 35, "gif_left");
+  is($res->tags(name=>'gif_top'), 45, "gif_top");
+  is($res->tags(name=>'gif_comment'), undef, 'gif_comment undef');
+  
+  # try to read a fourth page
+    ok(!$res->read(file=>$test_file, page=>3), "fail reading fourth page");
+  cmp_ok($res->errstr, "=~", 'page 3 not found',
+        "check error message");
+}
+SKIP:
+{
+  skip("gif_loop not supported on giflib before 4.1", 6) 
+    unless $gifver >= 4.1;
+  # testing writing the loop extension
+  my $im1 = Imager->new(xsize => 100, ysize => 100);
+  $im1->box(filled => 1, color => '#FF0000');
+  my $im2 = Imager->new(xsize => 100, ysize => 100);
+  $im2->box(filled => 1, color => '#00FF00');
+  ok(Imager->write_multi({
+                         gif_loop => 5, 
+                         gif_delay => 50, 
+                         file => 'testout/t105loop.gif'
+                        }, $im1, $im2),
+     "write with loop extension");
+  
+  my @im = Imager->read_multi(file => 'testout/t105loop.gif');
+  is(@im, 2, "read loop images back");
+  is($im[0]->tags(name => 'gif_loop'), 5, "first loop read back");
+  is($im[1]->tags(name => 'gif_loop'), 5, "second loop read back");
+  is($im[0]->tags(name => 'gif_delay'), 50, "first delay read back");
+  is($im[1]->tags(name => 'gif_delay'), 50, "second delay read back");
+}
+SKIP:
+{ # check graphic control extension and ns loop tags are read correctly
+  print "# check GCE and netscape loop extension tag values\n";
+  my @im = Imager->read_multi(file => 'testimg/screen3.gif');
+  is(@im, 2, "read 2 images from screen3.gif")
+    or skip("Could not load testimg/screen3.gif:".Imager->errstr, 11);
+  is($im[0]->tags(name => 'gif_delay'),          50, "0 - gif_delay");
+  is($im[0]->tags(name => 'gif_disposal'),        2, "0 - gif_disposal");
+  is($im[0]->tags(name => 'gif_trans_index'), undef, "0 - gif_trans_index");
+  is($im[0]->tags(name => 'gif_user_input'),      0, "0 - gif_user_input");
+  is($im[0]->tags(name => 'gif_loop'),            0, "0 - gif_loop");
+  is($im[1]->tags(name => 'gif_delay'),          50, "1 - gif_delay");
+  is($im[1]->tags(name => 'gif_disposal'),        2, "1 - gif_disposal");
+  is($im[1]->tags(name => 'gif_trans_index'),     7, "1 - gif_trans_index");
+  is($im[1]->tags(name => 'gif_trans_color'), 'color(255,255,255,0)',
+     "1 - gif_trans_index");
+  is($im[1]->tags(name => 'gif_user_input'),      0, "1 - gif_user_input");
+  is($im[1]->tags(name => 'gif_loop'),            0, "1 - gif_loop");
+}
+
+{
+  # manually modified from a small gif, this had the palette
+  # size changed to half the size, leaving an index out of range
+  my $im = Imager->new;
+  ok($im->read(file => 'testimg/badindex.gif', type => 'gif'), 
+     "read bad index gif")
+    or print "# ", $im->errstr, "\n";
+  my @indexes = $im->getscanline('y' => 0, type => 'index');
+  is_deeply(\@indexes, [ 0..4 ], "check for correct indexes");
+  is($im->colorcount, 5, "check the palette was adjusted");
+  is_color3($im->getpixel('y' => 0, x => 4), 0, 0, 0, 
+           "check it was black added");
+  is($im->tags(name => 'gif_colormap_size'), 4, 'color map size tag');
+}
+
+{
+  ok(grep($_ eq 'gif', Imager->read_types), "check gif in read types");
+  ok(grep($_ eq 'gif', Imager->write_types), "check gif in write types");
+}
+
+{
+  # check screen tags handled correctly note the screen size
+  # supplied is larger than the box covered by the images
+  my $im1 = Imager->new(xsize => 10, ysize => 8);
+  $im1->settag(name => 'gif_top', value => 4);
+  $im1->settag(name => 'gif_screen_width', value => 18);
+  $im1->settag(name => 'gif_screen_height', value => 16);
+  my $im2 = Imager->new(xsize => 7, ysize => 10);
+  $im2->settag(name => 'gif_left', value => 3);
+  my @im = ( $im1, $im2 );
+  
+  my $data;
+  ok(Imager->write_multi({ data => \$data, type => 'gif' }, @im),
+     "write with screen settings")
+    or print "# ", Imager->errstr, "\n";
+  my @result = Imager->read_multi(data => $data);
+  is(@result, 2, "got 2 images back");
+  is($result[0]->tags(name => 'gif_screen_width'), 18,
+     "check result screen width");
+  is($result[0]->tags(name => 'gif_screen_height'), 16,
+     "check result screen height");
+  is($result[0]->tags(name => 'gif_left'), 0,
+     "check first gif_left");
+  is($result[0]->tags(name => 'gif_top'), 4,
+     "check first gif_top");
+  is($result[1]->tags(name => 'gif_left'), 3,
+     "check second gif_left");
+  is($result[1]->tags(name => 'gif_top'), 0,
+     "check second gif_top");
+}
+
+{ # test colors array returns colors
+  my $data;
+  my $im = test_image();
+  my @colors;
+  ok($im->write(data => \$data, 
+               colors => \@colors, 
+               make_colors => 'webmap', 
+               translate => 'closest',
+               gifquant => 'gen',
+               type => 'gif'),
+     "write using webmap to check color table");
+  is(@colors, 216, "should be 216 colors in the webmap");
+  is_color3($colors[0], 0, 0, 0, "first should be 000000");
+  is_color3($colors[1], 0, 0, 0x33, "second should be 000033");
+  is_color3($colors[8], 0, 0x33, 0x66, "9th should be 003366");
+}
+
+{ # a zero length extension could make read_/read_multi crash
+  my ($im) = Imager->read_multi(file => "testimg/zerocomm.gif");
+  ok($im, "read image with zero-length extension");
+}
+
+sub test_readgif_cb {
+  my ($size) = @_;
+
+  open FH, "<testimg/scale.gif" or die "Cannot open testimg/scale.gif";
+  binmode FH;
+  my $io = Imager::io_new_cb
+    (
+     undef,
+     sub { my $tmp; read(FH, $tmp, $size) and $tmp },
+     undef,
+     undef
+     );
+  my $img = Imager::File::GIF::i_readgif_wiol($io);
+  close FH; 
+  return $img;
+}
+
+# tests for reading bad gif files
+sub read_failure {
+  my ($filename) = @_;
+
+  open FH, "< $filename"
+    or die "Cannot open $filename: $!";
+  binmode FH;
+  my $io = Imager::io_new_fd(fileno(FH));
+  my ($result, $map) = Imager::File::GIF::i_readgif_wiol($io);
+  ok(!$result, "attempt to read invalid image $filename ".Imager::_error_as_msg());
+  close FH;
+}
+
+sub _clear_tags {
+  my (@imgs) = @_;
+
+  for my $img (@imgs) {
+    $img->deltag(code=>0);
+  }
+}
+
+sub _add_tags {
+  my ($img, %tags) = @_;
+
+  for my $key (keys %tags) {
+    Imager::i_tags_add($img, $key, 0, $tags{$key}, 0);
+  }
+}
+
+sub ext_test {
+  my ($testnum, $code, $count, $name) = @_;
+
+  $count ||= 1;
+  $name ||= "gif$testnum";
+
+  # build our code
+  my $script = "testout/$name.pl";
+  if (open SCRIPT, "> $script") {
+    print SCRIPT <<'PROLOG';
+#!perl -w
+if (lc $^O eq 'mswin32') {
+  # avoid the dialog box that window's pops up on a GPF
+  # if you want to debug this stuff, I suggest you comment out the 
+  # following
+  eval {
+    require Win32API::File;
+    Win32API::File::SetErrorMode( Win32API::File::SEM_NOGPFAULTERRORBOX());
+  };
+}
+PROLOG
+
+    print SCRIPT $code;
+    close SCRIPT;
+
+    my $perl = $^X;
+    $perl = qq/"$perl"/ if $perl =~ / /;
+
+    print "# script: $script\n";
+    my $cmd = "$perl -Mblib $script";
+    print "# command: $cmd\n";
+
+    my $ok = 1;
+    my @out = `$cmd`; # should work on DOS and Win32
+    my $found = 0;
+    for (@out) {
+      if (/^not ok\s+(?:\d+\s*)?#(.*)/ || /^not ok/) {
+        my $msg = $1 || '';
+        ok(0, $msg);
+       $ok = 0;
+       ++$found;
+      }
+      elsif (/^ok\s+(?:\d+\s*)?#(.*)/ || /^ok/) {
+        my $msg = $1 || '';
+        ok(1, $msg);
+       ++$found;
+      }
+    }
+    unless ($count == $found) {
+      print "# didn't see enough ok/not ok\n";
+      $ok = 0;
+    }
+    return $ok;
+  }
+  else {
+    return skip("could not create test script $script: $!");
+    return 0;
+  }
+}
diff --git a/GIF/t/t20new.t b/GIF/t/t20new.t
new file mode 100644 (file)
index 0000000..f68d9a2
--- /dev/null
@@ -0,0 +1,108 @@
+#!perl -w
+# Before `make install' is performed this script should be runnable with
+# `make test'. After `make install' it should work as `perl test.pl'
+
+######################### We start with some black magic to print on failure.
+
+# Change 1..1 below to 1..last_test_to_print .
+# (It may become useful if the test is moved to ./t subdirectory.)
+
+
+use strict;
+use Test::More tests => 21;
+
+use Imager qw(:all :handy);
+use Imager::Test qw(test_image is_color3);
+
+Imager::init('log'=>'testout/t70newgif.log');
+
+my $green=i_color_new(0,255,0,0);
+my $blue=i_color_new(0,0,255,0);
+
+{
+  my $img = test_image();
+  
+  ok($img->write(file=>'testout/t70newgif.gif',type=>'gif',gifplanes=>1,gifquant=>'lm',lmfixed=>[$green,$blue]))
+    or print "# failed: ",$img->{ERRSTR}, "\n";
+}
+
+SKIP:
+{
+  # make sure the palette is loaded properly (minimal test)
+  my $im2 = Imager->new();
+  my $map;
+  ok($im2->read(file=>'testimg/bandw.gif', colors=>\$map))
+    or skip("Can't load bandw.gif", 5);
+  # check the palette
+  ok($map)
+    or skip("No palette", 4);
+  is(@$map, 2)
+    or skip("Bad map count", 3);
+  my @sorted = sort { comp_entry($a,$b) } @$map;
+  # first entry must be #000000 and second #FFFFFF
+  is_color3($sorted[0], 0,0,0, "check first palette entry");
+  is_color3($sorted[1], 255,255,255, "check second palette entry");
+}
+
+{
+  # test the read_multi interface
+  my @imgs = Imager->read_multi();
+  ok(!@imgs, "read with no sources should fail");
+  like(Imager->errstr, qr/callback parameter missing/, "check error");
+  print "# ",Imager->errstr,"\n";
+
+  @imgs = Imager->read_multi(type=>'gif');
+  ok(!@imgs, "read multi no source but type should fail");
+  like(Imager->errstr, qr/file/, "check error");
+
+  # kill warning
+  *NONESUCH = \20;
+  @imgs = Imager->read_multi(type=>'gif', fh=>*NONESUCH);
+  ok(!@imgs, "read from bad fh");
+  like(Imager->errstr, qr/fh option not open/, "check message");
+  print "# ",Imager->errstr,"\n";
+  {
+    @imgs = Imager->read_multi(type=>'gif', file=>'testimg/screen2.gif');
+    is(@imgs, 2, "should read 2 images");
+    isa_ok($imgs[0], "Imager");
+    isa_ok($imgs[1], "Imager");
+    is($imgs[0]->type, "paletted");
+    is($imgs[1]->type, "paletted");
+    my @left = $imgs[0]->tags(name=>'gif_left');
+    is(@left, 1);
+    my $left = $imgs[1]->tags(name=>'gif_left');
+    is($left, 3);
+  }
+  {
+    open FH, "< testimg/screen2.gif" 
+      or die "Cannot open testimg/screen2.gif: $!";
+    binmode FH;
+    my $cb = 
+      sub {
+       my $tmp;
+       read(FH, $tmp, $_[0]) and $tmp
+      };
+    @imgs = Imager->read_multi(type=>'gif',
+                              callback => $cb);
+    close FH;
+    is(@imgs, 2, "read multi from callback");
+    
+    open FH, "< testimg/screen2.gif" 
+      or die "Cannot open testimg/screen2.gif: $!";
+    binmode FH;
+    my $data = do { local $/; <FH>; };
+    close FH;
+    @imgs = Imager->read_multi(type=>'gif',
+                              data=>$data);
+    is(@imgs, 2, "read multi from data");
+  }
+}
+
+sub comp_entry {
+  my ($l, $r) = @_;
+  my @l = $l->rgba;
+  my @r = $r->rgba;
+  return $l[0] <=> $r[0]
+    || $l[1] <=> $r[1]
+      || $l[2] <=> $r[2];
+}
diff --git a/GIF/testimg/badindex.gif b/GIF/testimg/badindex.gif
new file mode 100644 (file)
index 0000000..3591f48
Binary files /dev/null and b/GIF/testimg/badindex.gif differ
diff --git a/GIF/testimg/bandw.gif b/GIF/testimg/bandw.gif
new file mode 100644 (file)
index 0000000..93fb1d7
Binary files /dev/null and b/GIF/testimg/bandw.gif differ
diff --git a/GIF/testimg/expected.gif b/GIF/testimg/expected.gif
new file mode 100644 (file)
index 0000000..3409d38
Binary files /dev/null and b/GIF/testimg/expected.gif differ
diff --git a/GIF/testimg/loccmap.gif b/GIF/testimg/loccmap.gif
new file mode 100644 (file)
index 0000000..9dd264e
Binary files /dev/null and b/GIF/testimg/loccmap.gif differ
diff --git a/GIF/testimg/nocmap.gif b/GIF/testimg/nocmap.gif
new file mode 100644 (file)
index 0000000..8394110
Binary files /dev/null and b/GIF/testimg/nocmap.gif differ
diff --git a/GIF/testimg/scale.gif b/GIF/testimg/scale.gif
new file mode 100644 (file)
index 0000000..265ed7f
Binary files /dev/null and b/GIF/testimg/scale.gif differ
diff --git a/GIF/testimg/scalei.gif b/GIF/testimg/scalei.gif
new file mode 100644 (file)
index 0000000..3b3234a
Binary files /dev/null and b/GIF/testimg/scalei.gif differ
diff --git a/GIF/testimg/screen2.gif b/GIF/testimg/screen2.gif
new file mode 100644 (file)
index 0000000..4dddf69
Binary files /dev/null and b/GIF/testimg/screen2.gif differ
diff --git a/GIF/testimg/screen3.gif b/GIF/testimg/screen3.gif
new file mode 100644 (file)
index 0000000..77f808e
Binary files /dev/null and b/GIF/testimg/screen3.gif differ
diff --git a/GIF/testimg/trimgdesc.gif b/GIF/testimg/trimgdesc.gif
new file mode 100644 (file)
index 0000000..f352b0e
Binary files /dev/null and b/GIF/testimg/trimgdesc.gif differ
diff --git a/GIF/testimg/trmiddesc.gif b/GIF/testimg/trmiddesc.gif
new file mode 100644 (file)
index 0000000..386e3c1
Binary files /dev/null and b/GIF/testimg/trmiddesc.gif differ
diff --git a/GIF/testimg/zerocomm.gif b/GIF/testimg/zerocomm.gif
new file mode 100644 (file)
index 0000000..ede9f6a
Binary files /dev/null and b/GIF/testimg/zerocomm.gif differ
index fe3b0c5..d817cb8 100644 (file)
--- a/Imager.pm
+++ b/Imager.pm
@@ -81,14 +81,6 @@ use Imager::Font;
                i_writetiff_wiol
                i_writetiff_wiol_faxable
 
-               i_readgif
-               i_readgif_wiol
-               i_readgif_callback
-               i_writegif
-               i_writegifmc
-               i_writegif_gen
-               i_writegif_callback
-
                i_readpnm_wiol
                i_writeppm_wiol
 
@@ -1907,21 +1899,7 @@ sub write_multi {
     ($IO, $file) = $class->_get_writer_io($opts, $type)
       or return undef;
     
-    if ($type eq 'gif') {
-      $class->_set_opts($opts, "gif_", @images)
-        or return;
-      my $gif_delays = $opts->{gif_delays};
-      local $opts->{gif_delays} = $gif_delays;
-      if ($opts->{gif_delays} && !ref $opts->{gif_delays}) {
-        # assume the caller wants the same delay for each frame
-        $opts->{gif_delays} = [ ($gif_delays) x @images ];
-      }
-      unless (i_writegif_wiol($IO, $opts, @work)) {
-        $class->_set_error($class->_error_as_msg());
-        return undef;
-      }
-    }
-    elsif ($type eq 'tiff') {
+    if ($type eq 'tiff') {
       $class->_set_opts($opts, "tiff_", @images)
         or return;
       $class->_set_opts($opts, "exif_", @images)
index f42d22d..821b1a1 100644 (file)
--- a/Imager.xs
+++ b/Imager.xs
@@ -19,6 +19,7 @@ extern "C" {
 #include "dynaload.h"
 #include "regmach.h"
 #include "imextdef.h"
+#include "imextpltypes.h"
 
 #if i_int_hlines_testing()
 #include "imageri.h"
@@ -590,7 +591,8 @@ static struct value_name orddith_names[] =
 };
 
 /* look through the hash for quantization options */
-static void handle_quant_opts(pTHX_ i_quantize *quant, HV *hv)
+static void
+ip_handle_quant_opts(pTHX_ i_quantize *quant, HV *hv)
 {
   /*** POSSIBLY BROKEN: do I need to unref the SV from hv_fetch ***/
   SV **sv;
@@ -721,14 +723,16 @@ static void handle_quant_opts(pTHX_ i_quantize *quant, HV *hv)
     quant->perturb = SvIV(*sv);
 }
 
-static void cleanup_quant_opts(i_quantize *quant) {
+static void
+ip_cleanup_quant_opts(pTHX_ i_quantize *quant) {
   myfree(quant->mc_colors);
   if (quant->ed_map)
     myfree(quant->ed_map);
 }
 
 /* copies the color map from the hv into the colors member of the HV */
-static void copy_colors_back(pTHX_ HV *hv, i_quantize *quant) {
+static void
+ip_copy_colors_back(pTHX_ HV *hv, i_quantize *quant) {
   SV **sv;
   AV *av;
   int i;
@@ -928,6 +932,18 @@ i_int_hlines_dump(i_int_hlines *hlines) {
 
 #endif
 
+static im_pl_ext_funcs im_perl_funcs =
+{
+  IMAGER_PL_API_VERSION,
+  IMAGER_PL_API_LEVEL,
+  ip_handle_quant_opts,
+  ip_cleanup_quant_opts,
+  ip_copy_colors_back
+};
+
+#define PERL_PL_SET_GLOBAL_CALLBACKS \
+  sv_setiv(get_sv(PERL_PL_FUNCTION_TABLE_NAME, 1), PTR2IV(&im_perl_funcs));
+
 #ifdef IMEXIF_ENABLE
 #define i_exif_enabled() 1
 #else
@@ -2505,499 +2521,6 @@ i_tiff_has_compression(name)
 #endif
 
 
-#ifdef HAVE_LIBGIF
-
-void
-i_giflib_version()
-       PPCODE:
-         PUSHs(sv_2mortal(newSVnv(IM_GIFMAJOR+IM_GIFMINOR*0.1)));
-
-undef_int
-i_writegif(im,fd,colors,pixdev,fixed)
-    Imager::ImgRaw     im
-              int     fd
-              int     colors
-               int     pixdev
-            PREINIT:
-             int     fixedlen;
-            Imager__Color  fixed;
-            Imager__Color  tmp;
-            AV* av;
-            SV* sv1;
-             IV  Itmp;
-            int i;
-            CODE:
-            if (!SvROK(ST(4))) croak("Imager: Parameter 4 must be a reference to an array\n");
-            if (SvTYPE(SvRV(ST(4))) != SVt_PVAV) croak("Imager: Parameter 4 must be a reference to an array\n");
-            av=(AV*)SvRV(ST(4));
-            fixedlen=av_len(av)+1;
-            fixed=mymalloc( fixedlen*sizeof(i_color) );
-            for(i=0;i<fixedlen;i++) {
-              sv1=(*(av_fetch(av,i,0)));
-               if (sv_derived_from(sv1, "Imager::Color")) {
-                 Itmp = SvIV((SV*)SvRV(sv1));
-                 tmp = INT2PTR(i_color*, Itmp);
-               } else croak("Imager: one of the elements of array ref is not of Imager::Color type\n");
-               fixed[i]=*tmp;
-            }
-            RETVAL=i_writegif(im,fd,colors,pixdev,fixedlen,fixed);
-             myfree(fixed);
-             ST(0) = sv_newmortal();
-             if (RETVAL == 0) ST(0)=&PL_sv_undef;
-             else sv_setiv(ST(0), (IV)RETVAL);
-
-
-
-
-undef_int
-i_writegifmc(im,fd,colors)
-    Imager::ImgRaw    im
-              int     fd
-              int     colors
-
-
-undef_int
-i_writegif_gen(fd, ...)
-              int     fd
-      PROTOTYPE: $$@
-      PREINIT:
-       i_quantize quant;
-       i_img **imgs = NULL;
-       int img_count;
-       int i;
-       HV *hv;
-      CODE:
-       if (items < 3)
-           croak("Usage: i_writegif_gen(fd,hashref, images...)");
-       if (!SvROK(ST(1)) || ! SvTYPE(SvRV(ST(1))))
-           croak("i_writegif_gen: Second argument must be a hash ref");
-       hv = (HV *)SvRV(ST(1));
-       memset(&quant, 0, sizeof(quant));
-       quant.mc_size = 256;
-       quant.transp = tr_threshold;
-       quant.tr_threshold = 127;
-       handle_quant_opts(aTHX_ &quant, hv);
-       img_count = items - 2;
-       RETVAL = 1;
-       if (img_count < 1) {
-         RETVAL = 0;
-         i_clear_error();
-         i_push_error(0, "You need to specify images to save");
-       }
-       else {
-          imgs = mymalloc(sizeof(i_img *) * img_count);
-          for (i = 0; i < img_count; ++i) {
-           SV *sv = ST(2+i);
-           imgs[i] = NULL;
-           if (SvROK(sv) && sv_derived_from(sv, "Imager::ImgRaw")) {
-             imgs[i] = INT2PTR(i_img *, SvIV((SV*)SvRV(sv)));
-           }
-           else {
-             i_clear_error();
-             i_push_error(0, "Only images can be saved");
-             RETVAL = 0;
-             break;
-            }
-         }
-          if (RETVAL) {
-           RETVAL = i_writegif_gen(&quant, fd, imgs, img_count);
-          }
-         myfree(imgs);
-          if (RETVAL) {
-           copy_colors_back(aTHX_ hv, &quant);
-          }
-       }
-        ST(0) = sv_newmortal();
-        if (RETVAL == 0) ST(0)=&PL_sv_undef;
-        else sv_setiv(ST(0), (IV)RETVAL);
-       cleanup_quant_opts(&quant);
-
-
-undef_int
-i_writegif_callback(cb, maxbuffer,...)
-       int maxbuffer;
-      PREINIT:
-       i_quantize quant;
-       i_img **imgs = NULL;
-       int img_count;
-       int i;
-       HV *hv;
-        i_writer_data wd;
-      CODE:
-       if (items < 4)
-           croak("Usage: i_writegif_callback(\\&callback,maxbuffer,hashref, images...)");
-       if (!SvROK(ST(2)) || ! SvTYPE(SvRV(ST(2))))
-           croak("i_writegif_callback: Second argument must be a hash ref");
-       hv = (HV *)SvRV(ST(2));
-       memset(&quant, 0, sizeof(quant));
-       quant.mc_size = 256;
-       quant.transp = tr_threshold;
-       quant.tr_threshold = 127;
-       handle_quant_opts(aTHX_ &quant, hv);
-       img_count = items - 3;
-       RETVAL = 1;
-       if (img_count < 1) {
-         RETVAL = 0;
-       }
-       else {
-          imgs = mymalloc(sizeof(i_img *) * img_count);
-          for (i = 0; i < img_count; ++i) {
-           SV *sv = ST(3+i);
-           imgs[i] = NULL;
-           if (SvROK(sv) && sv_derived_from(sv, "Imager::ImgRaw")) {
-             imgs[i] = INT2PTR(i_img *, SvIV((SV*)SvRV(sv)));
-           }
-           else {
-             RETVAL = 0;
-             break;
-            }
-         }
-          if (RETVAL) {
-           wd.sv = ST(0);
-           RETVAL = i_writegif_callback(&quant, write_callback, (char *)&wd, maxbuffer, imgs, img_count);
-          }
-         myfree(imgs);
-          if (RETVAL) {
-           copy_colors_back(aTHX_ hv, &quant);
-          }
-       }
-       ST(0) = sv_newmortal();
-       if (RETVAL == 0) ST(0)=&PL_sv_undef;
-       else sv_setiv(ST(0), (IV)RETVAL);
-       cleanup_quant_opts(&quant);
-
-undef_int
-i_writegif_wiol(ig, opts,...)
-       Imager::IO ig
-      PREINIT:
-       i_quantize quant;
-       i_img **imgs = NULL;
-       int img_count;
-       int i;
-       HV *hv;
-      CODE:
-       if (items < 3)
-           croak("Usage: i_writegif_wiol(IO,hashref, images...)");
-       if (!SvROK(ST(1)) || ! SvTYPE(SvRV(ST(1))))
-           croak("i_writegif_callback: Second argument must be a hash ref");
-       hv = (HV *)SvRV(ST(1));
-       memset(&quant, 0, sizeof(quant));
-       quant.mc_size = 256;
-       quant.transp = tr_threshold;
-       quant.tr_threshold = 127;
-       handle_quant_opts(aTHX_ &quant, hv);
-       img_count = items - 2;
-       RETVAL = 1;
-       if (img_count < 1) {
-         RETVAL = 0;
-       }
-       else {
-          imgs = mymalloc(sizeof(i_img *) * img_count);
-          for (i = 0; i < img_count; ++i) {
-           SV *sv = ST(2+i);
-           imgs[i] = NULL;
-           if (SvROK(sv) && sv_derived_from(sv, "Imager::ImgRaw")) {
-             imgs[i] = INT2PTR(i_img *, SvIV((SV*)SvRV(sv)));
-           }
-           else {
-             RETVAL = 0;
-             break;
-            }
-         }
-          if (RETVAL) {
-           RETVAL = i_writegif_wiol(ig, &quant, imgs, img_count);
-          }
-         myfree(imgs);
-          if (RETVAL) {
-           copy_colors_back(aTHX_ hv, &quant);
-          }
-       }
-       ST(0) = sv_newmortal();
-       if (RETVAL == 0) ST(0)=&PL_sv_undef;
-       else sv_setiv(ST(0), (IV)RETVAL);
-       cleanup_quant_opts(&quant);
-
-void
-i_readgif(fd)
-              int     fd
-             PREINIT:
-               int*    colour_table;
-               int     colours, q, w;
-             i_img*    rimg;
-                 SV*    temp[3];
-                 AV*    ct; 
-                 SV*    r;
-              PPCODE:
-              colour_table = NULL;
-               colours = 0;
-
-       if(GIMME_V == G_ARRAY) {
-            rimg = i_readgif(fd,&colour_table,&colours);
-        } else {
-            /* don't waste time with colours if they aren't wanted */
-            rimg = i_readgif(fd,NULL,NULL);
-        }
-       
-       if (colour_table == NULL) {
-            EXTEND(SP,1);
-            r=sv_newmortal();
-            sv_setref_pv(r, "Imager::ImgRaw", (void*)rimg);
-            PUSHs(r);
-       } else {
-            /* the following creates an [[r,g,b], [r, g, b], [r, g, b]...] */
-            /* I don't know if I have the reference counts right or not :( */
-            /* Neither do I :-) */
-            /* No Idea here either */
-
-            ct=newAV();
-            av_extend(ct, colours);
-            for(q=0; q<colours; q++) {
-                for(w=0; w<3; w++)
-                    temp[w]=sv_2mortal(newSViv(colour_table[q*3 + w]));
-                av_store(ct, q, (SV*)newRV_noinc((SV*)av_make(3, temp)));
-            }
-            myfree(colour_table);
-
-            EXTEND(SP,2);
-            r = sv_newmortal();
-            sv_setref_pv(r, "Imager::ImgRaw", (void*)rimg);
-            PUSHs(r);
-            PUSHs(newRV_noinc((SV*)ct));
-        }
-
-void
-i_readgif_wiol(ig)
-     Imager::IO         ig
-             PREINIT:
-               int*    colour_table;
-               int     colours, q, w;
-             i_img*    rimg;
-                 SV*    temp[3];
-                 AV*    ct; 
-                 SV*    r;
-              PPCODE:
-              colour_table = NULL;
-               colours = 0;
-
-       if(GIMME_V == G_ARRAY) {
-            rimg = i_readgif_wiol(ig,&colour_table,&colours);
-        } else {
-            /* don't waste time with colours if they aren't wanted */
-            rimg = i_readgif_wiol(ig,NULL,NULL);
-        }
-       
-       if (colour_table == NULL) {
-            EXTEND(SP,1);
-            r=sv_newmortal();
-            sv_setref_pv(r, "Imager::ImgRaw", (void*)rimg);
-            PUSHs(r);
-       } else {
-            /* the following creates an [[r,g,b], [r, g, b], [r, g, b]...] */
-            /* I don't know if I have the reference counts right or not :( */
-            /* Neither do I :-) */
-            /* No Idea here either */
-
-            ct=newAV();
-            av_extend(ct, colours);
-            for(q=0; q<colours; q++) {
-                for(w=0; w<3; w++)
-                    temp[w]=sv_2mortal(newSViv(colour_table[q*3 + w]));
-                av_store(ct, q, (SV*)newRV_noinc((SV*)av_make(3, temp)));
-            }
-            myfree(colour_table);
-
-            EXTEND(SP,2);
-            r = sv_newmortal();
-            sv_setref_pv(r, "Imager::ImgRaw", (void*)rimg);
-            PUSHs(r);
-            PUSHs(newRV_noinc((SV*)ct));
-        }
-
-Imager::ImgRaw
-i_readgif_single_wiol(ig, page=0)
-       Imager::IO      ig
-        int            page
-
-void
-i_readgif_scalar(...)
-          PROTOTYPE: $
-            PREINIT:
-               char*    data;
-             STRLEN     length;
-               int*    colour_table;
-               int     colours, q, w;
-             i_img*    rimg;
-                 SV*    temp[3];
-                 AV*    ct; 
-                 SV*    r;
-              PPCODE:
-        data = (char *)SvPV(ST(0), length);
-        colour_table=NULL;
-        colours=0;
-
-       if(GIMME_V == G_ARRAY) {  
-            rimg=i_readgif_scalar(data,length,&colour_table,&colours);
-        } else {
-            /* don't waste time with colours if they aren't wanted */
-            rimg=i_readgif_scalar(data,length,NULL,NULL);
-        }
-
-       if (colour_table == NULL) {
-            EXTEND(SP,1);
-            r=sv_newmortal();
-            sv_setref_pv(r, "Imager::ImgRaw", (void*)rimg);
-            PUSHs(r);
-       } else {
-            /* the following creates an [[r,g,b], [r, g, b], [r, g, b]...] */
-            /* I don't know if I have the reference counts right or not :( */
-            /* Neither do I :-) */
-            ct=newAV();
-            av_extend(ct, colours);
-            for(q=0; q<colours; q++) {
-                for(w=0; w<3; w++)
-                    temp[w]=sv_2mortal(newSViv(colour_table[q*3 + w]));
-                av_store(ct, q, (SV*)newRV_noinc((SV*)av_make(3, temp)));
-            }
-            myfree(colour_table);
-            
-            EXTEND(SP,2);
-            r=sv_newmortal();
-            sv_setref_pv(r, "Imager::ImgRaw", (void*)rimg);
-            PUSHs(r);
-            PUSHs(newRV_noinc((SV*)ct));
-        }
-
-void
-i_readgif_callback(...)
-          PROTOTYPE: &
-            PREINIT:
-               int*    colour_table;
-               int     colours, q, w;
-             i_img*    rimg;
-                 SV*    temp[3];
-                 AV*    ct; 
-                 SV*    r;
-       i_reader_data    rd;
-              PPCODE:
-       rd.sv = ST(0);
-        colour_table=NULL;
-        colours=0;
-
-       if(GIMME_V == G_ARRAY) {  
-            rimg=i_readgif_callback(read_callback, (char *)&rd,&colour_table,&colours);
-        } else {
-            /* don't waste time with colours if they aren't wanted */
-            rimg=i_readgif_callback(read_callback, (char *)&rd,NULL,NULL);
-        }
-
-       if (colour_table == NULL) {
-            EXTEND(SP,1);
-            r=sv_newmortal();
-            sv_setref_pv(r, "Imager::ImgRaw", (void*)rimg);
-            PUSHs(r);
-       } else {
-            /* the following creates an [[r,g,b], [r, g, b], [r, g, b]...] */
-            /* I don't know if I have the reference counts right or not :( */
-            /* Neither do I :-) */
-            /* Neither do I - maybe I'll move this somewhere */
-            ct=newAV();
-            av_extend(ct, colours);
-            for(q=0; q<colours; q++) {
-                for(w=0; w<3; w++)
-                    temp[w]=sv_2mortal(newSViv(colour_table[q*3 + w]));
-                av_store(ct, q, (SV*)newRV_noinc((SV*)av_make(3, temp)));
-            }
-            myfree(colour_table);
-            
-            EXTEND(SP,2);
-            r=sv_newmortal();
-            sv_setref_pv(r, "Imager::ImgRaw", (void*)rimg);
-            PUSHs(r);
-            PUSHs(newRV_noinc((SV*)ct));
-        }
-
-void
-i_readgif_multi(fd)
-        int     fd
-      PREINIT:
-        i_img **imgs;
-        int count;
-        int i;
-      PPCODE:
-        imgs = i_readgif_multi(fd, &count);
-        if (imgs) {
-          EXTEND(SP, count);
-          for (i = 0; i < count; ++i) {
-            SV *sv = sv_newmortal();
-            sv_setref_pv(sv, "Imager::ImgRaw", (void *)imgs[i]);
-            PUSHs(sv);
-          }
-          myfree(imgs);
-        }
-
-void
-i_readgif_multi_scalar(data)
-      PREINIT:
-        i_img **imgs;
-        int count;
-        char *data;
-        STRLEN length;
-        int i;
-      PPCODE:
-        data = (char *)SvPV(ST(0), length);
-        imgs = i_readgif_multi_scalar(data, length, &count);
-        if (imgs) {
-          EXTEND(SP, count);
-          for (i = 0; i < count; ++i) {
-            SV *sv = sv_newmortal();
-            sv_setref_pv(sv, "Imager::ImgRaw", (void *)imgs[i]);
-            PUSHs(sv);
-          }
-          myfree(imgs);
-        }
-
-void
-i_readgif_multi_callback(cb)
-      PREINIT:
-        i_reader_data rd;
-        i_img **imgs;
-        int count;
-        int i;
-      PPCODE:
-        rd.sv = ST(0);
-        imgs = i_readgif_multi_callback(read_callback, (char *)&rd, &count);
-        if (imgs) {
-          EXTEND(SP, count);
-          for (i = 0; i < count; ++i) {
-            SV *sv = sv_newmortal();
-            sv_setref_pv(sv, "Imager::ImgRaw", (void *)imgs[i]);
-            PUSHs(sv);
-          }
-          myfree(imgs);
-        }
-
-void
-i_readgif_multi_wiol(ig)
-        Imager::IO ig
-      PREINIT:
-        i_img **imgs;
-        int count;
-        int i;
-      PPCODE:
-        imgs = i_readgif_multi_wiol(ig, &count);
-        if (imgs) {
-          EXTEND(SP, count);
-          for (i = 0; i < count; ++i) {
-            SV *sv = sv_newmortal();
-            sv_setref_pv(sv, "Imager::ImgRaw", (void *)imgs[i]);
-            PUSHs(sv);
-          }
-          myfree(imgs);
-        }
-
-
-#endif
-
 
 
 Imager::ImgRaw
@@ -3659,13 +3182,14 @@ i_img_to_pal(src, quant)
           croak("i_img_to_pal: second argument must be a hash ref");
         hv = (HV *)SvRV(ST(1));
         memset(&quant, 0, sizeof(quant));
+       quant.version = 1;
         quant.mc_size = 256;
-       handle_quant_opts(aTHX_ &quant, hv);
+       ip_handle_quant_opts(aTHX_ &quant, hv);
         RETVAL = i_img_to_pal(src, &quant);
         if (RETVAL) {
-          copy_colors_back(aTHX_ hv, &quant);
+          ip_copy_colors_back(aTHX_ hv, &quant);
         }
-       cleanup_quant_opts(&quant);
+       ip_cleanup_quant_opts(aTHX_ &quant);
       OUTPUT:
         RETVAL
 
@@ -5023,3 +4547,4 @@ i_int_hlines_CLONE_SKIP(cls)
 
 BOOT:
         PERL_SET_GLOBAL_CALLBACKS;
+       PERL_PL_SET_GLOBAL_CALLBACKS;
\ No newline at end of file
index 9e40200..47f448f 100644 (file)
--- a/MANIFEST
+++ b/MANIFEST
@@ -13,6 +13,23 @@ Flines/Flines.pm
 Flines/Flines.xs
 Flines/Makefile.PL
 Flines/t/t00flines.t
+GIF/GIF.pm
+GIF/GIF.xs
+GIF/Makefile.PL
+GIF/imgif.c
+GIF/imgif.h
+GIF/testimg/badindex.gif       GIF with a bad color index
+GIF/testimg/bandw.gif
+GIF/testimg/expected.gif
+GIF/testimg/loccmap.gif
+GIF/testimg/nocmap.gif
+GIF/testimg/scale.gif
+GIF/testimg/scalei.gif
+GIF/testimg/screen2.gif
+GIF/testimg/screen3.gif
+GIF/testimg/trimgdesc.gif
+GIF/testimg/trmiddesc.gif
+GIF/testimg/zerocomm.gif       Image with a zero-length comment extension
 ICO/ICO.pm     Windows Icon file support
 ICO/ICO.xs
 ICO/Makefile.PL
@@ -129,7 +146,6 @@ fontfiles/dcr10.pfb
 fontfiles/dodge.ttf
 freetyp2.c     Implements freetype2 font support
 gaussian.im
-gif.c
 hlines.c       Manage sets of horizontal line segments
 image.c
 imager.h
@@ -139,6 +155,8 @@ imerror.h   Error handling functions
 imext.c         Defines the function table
 imext.h         Included by external modules for API access
 imextdef.h
+imextpl.h      Included by external modules for Perl API access
+imextpltypes.h Define Perl API function table type
 imexttypes.h    Define API function table type
 imexif.c       Experimental JPEG EXIF decoding
 imexif.h
@@ -312,7 +330,6 @@ testimg/bad8wid0.bmp    8-bit/pixel, width 0
 testimg/badbits.bmp     bad bits value in header
 testimg/badcomp1.bmp    bad compression for 1-bit/pixel
 testimg/badcomp4.bmp    bad compression for 4-bit/pixel
-testimg/badindex.gif   GIF with a bad color index
 testimg/badplanes.bmp   bad planes value in header
 testimg/badused1.bmp    1-bit/pixel, out of range colors used value
 testimg/badused4a.bmp   4-bit/pixel, badly out of range used value (SEGV test)
@@ -320,7 +337,6 @@ testimg/badused4b.bmp   4-bit/pixel, just out of range used value (SEGV test)
 testimg/bad_asc.pbm    ASCII PBM with invalid image data
 testimg/bad_asc.pgm    ASCII PGM with invalid image data
 testimg/bad_asc.ppm    ASCII PPM with invalid image data
-testimg/bandw.gif
 testimg/base.jpg       Base JPEG test image
 testimg/comp4.bmp       Compressed 4-bit/pixel BMP
 testimg/comp4.tif       4-bit/pixel paletted TIFF
@@ -329,7 +345,6 @@ testimg/comp4t.tif      4-bit/pixel paletted TIFF (tiled)
 testimg/comp8.bmp       Compressed 8-bit/pixel BMP
 testimg/comp8.tif       8-bit/pixel paletted TIFF
 testimg/exiftest.jpg   Test image for EXIF parsing
-testimg/expected.gif
 testimg/gimpgrad        A GIMP gradient file
 testimg/gralpha.tif    Grey alpha test image
 testimg/grey16.tif     16-bit/sample greyscale TIFF
@@ -337,7 +352,6 @@ testimg/grey32.tif  32-bit/sample greyscale+alpha TIFF
 testimg/imager.pbm     Test bi-level
 testimg/imager.tif     Test bi-level
 testimg/junk.ppm
-testimg/loccmap.gif
 testimg/longid.tga      Test TGA with a long id string
 testimg/maxval.ppm     For ppm file maxval handling
 testimg/maxval_0.ppm
@@ -347,23 +361,18 @@ testimg/maxval_65536.ppm
 testimg/maxval_asc.ppm
 testimg/multiple.ppm   Test multiple PPM reading
 testimg/newgimpgrad.ggr Test GIMP Gradient file (newer type)
-testimg/nocmap.gif
 testimg/penguin-base.ppm
 testimg/pengtile.tif   Tiled tiff image, same as penguin-base.ppm
 testimg/pgm.pgm                Simple pgm for testing the right sample is in the right place
 testimg/rgb16.tif      16-bit/sample RGB image - strips
 testimg/rgb16t.tif     16-bit/sample RGB image - tiled
 testimg/rgbatsep.tif   Tiled/separated for testing RGBA codepath
-testimg/scale.gif
 testimg/scale.ppm
-testimg/scalei.gif
 testimg/scmyk.tif      Simple CMYK TIFF image
 testimg/scmyk.jpg      Simple CMYK JPEG image
 testimg/scmyka.tif     CMYK with one alpha channel
 testimg/scmyka16.tif   CMYK with one alpha channel (16-bit)
 testimg/scmykaa.tif    CMYK with 2 alpha channels
-testimg/screen2.gif
-testimg/screen3.gif
 testimg/short1.bmp      1-bit/pixel, data missing from EOF
 testimg/short24.bmp     24-bit/pixel, data missing from EOF
 testimg/short4.bmp      truncated 4bit/pixel uncompressed BMP
@@ -390,8 +399,6 @@ testimg/test.raw    Standard test image as RAW
 testimg/test.tga       Standard test image as TGA
 testimg/test_gimp_pal   A simple GIMP palette file
 testimg/tiffwarn.tif   Generates a warning while being read
-testimg/trimgdesc.gif
-testimg/trmiddesc.gif
 testimg/winrgb2.bmp  1-bit bmp base
 testimg/winrgb24.bmp 24-bit bmp base
 testimg/winrgb24off.bmp 24-bit bmp with image data offset from header
@@ -401,7 +408,6 @@ testimg/winrgb4off.bmp  4-bit bmp with image data offset from header
 testimg/winrgb8.bmp  8-bit bmp base
 testimg/winrgb8off.bmp  8-bit bmp with image data offset from header
 testimg/zerotype.jpg   Image with a zero type entry in the EXIF data
-testimg/zerocomm.gif   Image with a zero-length comment extension
 tga.c           Reading and writing Targa files
 tiff.c
 trans2.c
index fde11eb..3a1da3c 100644 (file)
@@ -44,7 +44,6 @@ my @disable; # or list of drivers to disable
 my @incpaths; # places to look for headers
 my @libpaths; # places to look for libraries
 my $noexif; # non-zero to disable EXIF parsing of JPEGs
-my $no_gif_set_version; # disable calling EGifSetGifVersion
 my $coverage; # build for coverage testing
 my $assert; # build with assertions
 GetOptions("help" => \$help,
@@ -55,7 +54,6 @@ GetOptions("help" => \$help,
            "verbose|v" => \$VERBOSE,
            "nolog" => \$NOLOG,
           "noexif" => \$noexif,
-           "nogifsetversion" => \$no_gif_set_version,
           'coverage' => \$coverage,
           "assert|a" => \$assert);
 
@@ -127,9 +125,6 @@ if ($MANUAL) {
   automatic();
 }
 
-# Make sure there isn't a clash between the gif libraries.
-gifcheck();
-
 my $lib_cflags = '';
 my $lib_lflags = '';
 my $F_LIBS = '';
@@ -220,6 +215,7 @@ if ($MM_ver >= 6.46) {
       directory =>
       [
        "PNG",
+       "GIF",
       ],
      },
      resources =>
@@ -329,43 +325,43 @@ sub automatic {
 }
 
 
-sub gifcheck {
-  if ($formats{'gif'} and $formats{'ungif'}) {
-    print "ungif and gif can not coexist - removing ungif support\n";
-    delete $formats{'ungif'};
-  }
-
-  for my $frm (qw(gif ungif)) {
-    checkformat($frm) if ($MANUAL and $formats{$frm});
-  }
-
-  my @dirs;
-  for my $frm (grep $formats{$_}, qw(gif ungif)) {
-    push(@dirs, @{$formats{$frm}{incdir}}) if $formats{$frm}{incdir};
-  }
-  my $minor = 0;
-  my $major = 0;
-  FILES: for my $dir (@dirs) {
-    my $h = "$dir/gif_lib.h";
-    open H, "< $h" or next;
-    while (<H>) {
-      if (/GIF_LIB_VERSION\s+"\s*version\s*(\d+)\.(\d+)/i) {
-       $major = $1;
-       $minor = $2;
-       close H;
-       last FILES;
-      }
-    }
-    close H;
-  }
-
-  # we need the version in a #ifdefable form
-
-  push @defines, [ IM_GIFMAJOR => $major, "Parsed giflib version" ];
-  push @defines, [ IM_GIFMINOR => $minor ];
-  push @defines, [ IM_NO_SET_GIF_VERSION => 1, "Disable EGifSetGifVersion" ]
-    if $no_gif_set_version;
-}
+sub gifcheck {
+  if ($formats{'gif'} and $formats{'ungif'}) {
+    print "ungif and gif can not coexist - removing ungif support\n";
+    delete $formats{'ungif'};
+  }
+
+  for my $frm (qw(gif ungif)) {
+    checkformat($frm) if ($MANUAL and $formats{$frm});
+  }
+
+  my @dirs;
+  for my $frm (grep $formats{$_}, qw(gif ungif)) {
+    push(@dirs, @{$formats{$frm}{incdir}}) if $formats{$frm}{incdir};
+  }
+  my $minor = 0;
+  my $major = 0;
+  FILES: for my $dir (@dirs) {
+    my $h = "$dir/gif_lib.h";
+    open H, "< $h" or next;
+    while (<H>) {
+      if (/GIF_LIB_VERSION\s+"\s*version\s*(\d+)\.(\d+)/i) {
+#      $major = $1;
+#      $minor = $2;
+#      close H;
+#      last FILES;
+      }
+    }
+    close H;
+  }
+
+  # we need the version in a #ifdefable form
+
+  push @defines, [ IM_GIFMAJOR => $major, "Parsed giflib version" ];
+  push @defines, [ IM_GIFMINOR => $minor ];
+  push @defines, [ IM_NO_SET_GIF_VERSION => 1, "Disable EGifSetGifVersion" ]
+    if $no_gif_set_version;
+}
 
 
 sub grep_directory {
@@ -576,34 +572,34 @@ sub init {
 #                    code => \&png_probe,
 #                };
 
-  $formats{'gif'}={
-                  order=>'20',
-                  def=>'HAVE_LIBGIF',
-                  inccheck=>sub { -e catfile($_[0], 'gif_lib.h') },
-                  libcheck=>sub { $_[0] eq "libgif$aext" or $_[0] eq "libgif.$lext" },
-                  libfiles=>'-lgif',
-                  objfiles=>'gif.o',
-                  docs=>q{
-                          gif is the de facto standard for webgraphics at the moment,
-                          it does have some patent problems. If you have giflib and
-                          are not in violation with the unisys patent you should use
-                          this instead of the 'ungif' option.  Note that they cannot
-                          be in use at the same time}
-                 };
-
-  $formats{'ungif'}={
-                    order=>'21',
-                    def=>'HAVE_LIBGIF',
-                    inccheck=>sub { -e catfile($_[0], 'gif_lib.h') },
-                    libcheck=>sub { $_[0] eq "libungif$aext" or $_[0] eq "libungif.$lext" },
-                    libfiles=>'-lungif',
-                    objfiles=>'gif.o',
-                    docs=>q{
-                            gif is the de facto standard for webgraphics at the moment,
-                            it does have some patent problems. If you have libungif and
-                            want to create images free from LZW patented compression you
-                            should use this option instead of the 'gif' option}
-                   };
+  $formats{'gif'}={
+#                 order=>'20',
+#                 def=>'HAVE_LIBGIF',
+#                 inccheck=>sub { -e catfile($_[0], 'gif_lib.h') },
+#                 libcheck=>sub { $_[0] eq "libgif$aext" or $_[0] eq "libgif.$lext" },
+#                 libfiles=>'-lgif',
+#                 objfiles=>'gif.o',
+#                 docs=>q{
+#                         gif is the de facto standard for webgraphics at the moment,
+#                         it does have some patent problems. If you have giflib and
+#                         are not in violation with the unisys patent you should use
+#                         this instead of the 'ungif' option.  Note that they cannot
+#                         be in use at the same time}
+#                };
+
+  $formats{'ungif'}={
+#                   order=>'21',
+#                   def=>'HAVE_LIBGIF',
+#                   inccheck=>sub { -e catfile($_[0], 'gif_lib.h') },
+#                   libcheck=>sub { $_[0] eq "libungif$aext" or $_[0] eq "libungif.$lext" },
+#                   libfiles=>'-lungif',
+#                   objfiles=>'gif.o',
+#                   docs=>q{
+#                           gif is the de facto standard for webgraphics at the moment,
+#                           it does have some patent problems. If you have libungif and
+#                           want to create images free from LZW patented compression you
+#                           should use this option instead of the 'gif' option}
+#                  };
 
   $formats{'T1-fonts'}={
                        order=>'30',
@@ -970,7 +966,7 @@ Usage: $0 [--enable feature1,feature2,...] [other options]
        $0 [--disable feature1,feature2,...]  [other options]
        $0 --help
 Possible feature names are:
-  png gif ungif jpeg tiff T1-fonts TT-fonts freetype2
+  jpeg tiff T1-fonts TT-fonts freetype2
 Other options:
   --verbose | -v
     Verbose library probing (or set IM_VERBOSE in the environment)
diff --git a/gif.c b/gif.c
deleted file mode 100644 (file)
index ae36537..0000000
--- a/gif.c
+++ /dev/null
@@ -1,2411 +0,0 @@
-#include "imageri.h"
-#include <gif_lib.h>
-#ifdef _MSC_VER
-#include <io.h>
-#else
-#include <unistd.h>
-#endif
-#include <errno.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;
-  /*   fprintf(stderr,"my_gif_inputfunc: length=%d cpos=%d tlength=%d\n",length,gsi->cpos,gsi->length); */
-
-  if (gsi->cpos == gsi->length) return 0;
-  if (gsi->cpos+length > gsi->length) length=gsi->length-gsi->cpos; /* Don't read too much */
-  memcpy(buf,gsi->data+gsi->cpos,length);
-  gsi->cpos+=length;
-  return length;
-}
-
-#endif
-
-
-
-/* Make some variables global, so we could access them faster: */
-
-static int
-  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;
-  
-  GifRowType GifRow;
-  static GifColorType *ColorMapEntry;
-  i_color col;
-
-  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);
-
-  if (ColorMap) {
-    ColorMapSize = ColorMap->ColorCount;
-    i_colortable_copy(colour_table, colours, ColorMap);
-    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); 
-  
-  GifRow = 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) {
-      gif_push_error();
-      i_push_error(0, "Unable to get record type");
-      if (colour_table && *colour_table) {
-       myfree(*colour_table);
-       *colour_table = NULL;
-      }
-      myfree(GifRow);
-      i_img_destroy(im);
-      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");
-       if (colour_table && *colour_table) {
-         myfree(*colour_table);
-         *colour_table = NULL;
-       }
-       myfree(GifRow);
-       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 */
-       myfree(GifRow);
-       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_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) {
-       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;
-       }
-       myfree(GifRow);
-       i_img_destroy(im);
-       DGifCloseFile(GifFile);
-       return NULL;
-      }
-      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, Width) == GIF_ERROR) {
-           gif_push_error();
-           i_push_error(0, "Reading GIF line");
-           if (colour_table && *colour_table) {
-             myfree(*colour_table);
-             *colour_table = NULL;
-           }
-           myfree(GifRow);
-           i_img_destroy(im);
-           DGifCloseFile(GifFile);
-           return NULL;
-         }
-         
-         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,Col+x,j,&col);
-         }
-         
-       }
-      }
-      else {
-       for (i = 0; i < Height; i++) {
-         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;
-           }
-           myfree(GifRow);
-           i_img_destroy(im);
-           DGifCloseFile(GifFile);
-           return NULL;
-         }
-
-         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, Col+x, Row, &col);
-         }
-         Row++;
-       }
-      }
-      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");
-       if (colour_table && *colour_table) {
-         myfree(*colour_table);
-         *colour_table = NULL;
-       }
-       myfree(GifRow);
-       i_img_destroy(im);
-       DGifCloseFile(GifFile);
-       return NULL;
-      }
-      while (Extension != NULL) {
-       if (DGifGetExtensionNext(GifFile, &Extension) == GIF_ERROR) {
-         gif_push_error();
-         i_push_error(0, "reading next block of extension");
-         if (colour_table && *colour_table) {
-           myfree(*colour_table);
-           *colour_table = NULL;
-         }
-         myfree(GifRow);
-         i_img_destroy(im);
-         DGifCloseFile(GifFile);
-         return NULL;
-       }
-      }
-      break;
-    case TERMINATE_RECORD_TYPE:
-      break;
-    default:               /* Should be traps by DGifGetRecordType. */
-      break;
-    }
-  } while (RecordType != TERMINATE_RECORD_TYPE);
-  
-  myfree(GifRow);
-  
-  if (DGifCloseFile(GifFile) == GIF_ERROR) {
-    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;
-  }
-
-  i_tags_add(&im->tags, "i_format", 0, "gif", -1, 0);
-
-  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;
-  }
-
-  return i_readgif_low(GifFile, colour_table, colours);
-}
-
-/*
-
-Internal function called by i_readgif_multi_low() in error handling
-
-*/
-static void free_images(i_img **imgs, int count) {
-  int i;
-  
-  if (count) {
-    for (i = 0; i < count; ++i)
-      i_img_destroy(imgs[i]);
-    myfree(imgs);
-  }
-}
-
-/*
-=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.
-
-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, int page) {
-  i_img *img;
-  int i, j, Size, Width, Height, ExtCode, Count;
-  int ImageNum = 0, BackGround = 0, ColorMapSize = 0;
-  ColorMapObject *ColorMap;
-  GifRecordType RecordType;
-  GifByteType *Extension;
-  
-  GifRowType GifRow;
-  int got_gce = 0;
-  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 = 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;
-
-  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)
-    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 {
-    if (DGifGetRecordType(GifFile, &RecordType) == GIF_ERROR) {
-      gif_push_error();
-      i_push_error(0, "Unable to get record type");
-      free_images(results, *count);
-      DGifCloseFile(GifFile);
-      myfree(GifRow);
-      if (comment)
-       myfree(comment);
-      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);
-       myfree(GifRow);
-       if (comment)
-         myfree(comment);
-       return NULL;
-      }
-
-      Width = GifFile->Image.Width;
-      Height = GifFile->Image.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);
-         if (comment)
-           myfree(comment);
-         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);
-         if (comment)
-           myfree(comment);
-         return NULL;
-       }
-       img = i_img_pal_new(Width, Height, channels, 256);
-       if (!img) {
-         free_images(results, *count);
-         DGifCloseFile(GifFile);
-         if (comment)
-           myfree(comment);
-         myfree(GifRow);
-         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);
-         if (comment)
-           myfree(comment);
-         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);
-               if (comment)
-                 myfree(comment);
-               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);
-             if (comment)
-               myfree(comment);
-             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, i, GifRow);
-         }
-       }
-
-       /* must be only one image wanted and that was it */
-       if (page != -1) {
-         myfree(GifRow);
-         DGifCloseFile(GifFile);
-         if (comment)
-           myfree(comment);
-         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);
-           myfree(GifRow);
-           DGifCloseFile(GifFile);
-           if (comment) 
-             myfree(comment);
-           return NULL;
-         }
-       }
-
-       /* 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: */
-      if (DGifGetExtension(GifFile, &ExtCode, &Extension) == GIF_ERROR) {
-       gif_push_error();
-       i_push_error(0, "Reading extension record");
-        free_images(results, *count);
-       myfree(GifRow);
-       DGifCloseFile(GifFile);
-       if (comment)
-         myfree(comment);
-       return NULL;
-      }
-      /* possibly this should be an error, but "be liberal in what you accept" */
-      if (!Extension)
-       break;
-      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[1] & 2) != 0;
-        disposal = (Extension[1] >> 2) & 7;
-      }
-      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);
-           myfree(GifRow);
-           DGifCloseFile(GifFile);
-           if (comment)
-             myfree(comment);
-            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);
-         myfree(GifRow);
-         DGifCloseFile(GifFile);
-         if (comment)
-           myfree(comment);
-         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;
-  }
-
-  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)
-
-=cut
-*/
-i_img **
-i_readgif_multi(int fd, int *count) {
-  GifFileType *GifFile;
-
-  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, -1);
-}
-
-/*
-=item i_readgif_multi_scalar(char *data, int length, int *count)
-
-=cut
-*/
-i_img **
-i_readgif_multi_scalar(char *data, int length, int *count) {
-#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_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, -1);
-#else
-  return NULL;
-#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_multi_callback(i_read_callback_t cb, char *userdata, int *count) {
-#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_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, -1);
-  i_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;
-  
-  memset(&quant, 0, sizeof(quant));
-  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);
-}
-
-/*
-=item i_writegifmc(i_img *im, int fd, int max_colors)
-
-Write I<img> to the file handle I<fd>.  The resulting GIF will use a
-maximum of 1<<I<max_colours> colours.
-
-Returns non-zero on success.
-
-=cut
-*/
-
-undef_int
-i_writegifmc(i_img *im, int fd, int max_colors) {
-  i_color colors[256];
-  i_quantize quant;
-
-/*    *(char *)0 = 1; */
-  
-  memset(&quant, 0, sizeof(quant));
-  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);
-}
-
-
-/*
-=item i_readgif_scalar(char *data, int length, int **colour_table, int *colours)
-
-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).
-
-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;
-  }
-
-  return i_readgif_low(GifFile, colour_table, colours);
-#else
-  return NULL;
-#endif
-}
-
-#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, (char*)buf, 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;
-  }
-
-  result = i_readgif_low(GifFile, colour_table, colours);
-  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
-  }
-}
-
-/*
-=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, 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]) {
-       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;
-       }
-      }
-    }
-  }
-  else {
-    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;
-      }
-      data += img->xsize;
-    }
-  }
-
-  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, 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 (i_tags_get_int(&img->tags, "gif_delay", 0, &delay)) {
-    gce[1] = delay % 256;
-    gce[2] = delay / 256;
-    ++want_gce;
-  }
-  if (i_tags_get_int(&img->tags, "gif_user_input", 0, &user_input) 
-      && user_input) {
-    gce[0] |= 2;
-    ++want_gce;
-  }
-  if (i_tags_get_int(&img->tags, "gif_disposal", 0, &disposal_method)) {
-    gce[0] |= (disposal_method & 3) << 2;
-    ++want_gce;
-  }
-  if (want_gce) {
-    if (EGifPutExtension(gf, 0xF9, sizeof(gce), gce) == GIF_ERROR) {
-      gif_push_error();
-      i_push_error(0, "Could not save GCE");
-    }
-  }
-  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)
-
-Internal.  Add the Netscape2.0 loop extension block, if requested.
-
-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
-*/
-static int do_ns_loop(GifFileType *gf, i_img *img)
-{
-  /* 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
-     private data), we can't do an end-run and write the data 
-     directly to the fd.
-     There's no open interface that takes a FILE * either, so we 
-     can't workaround it that way either.
-     If giflib's callback interface wasn't broken by default, I'd 
-     force file writes to use callbacks, but it is broken by default.
-  */
-  /* 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 (EGifPutExtensionFirst(gf, APPLICATION_EXT_FUNC_CODE, 11, nsle) == GIF_ERROR) {
-      gif_push_error();
-      i_push_error(0, "writing loop extension");
-      return 0;
-    }
-    subblock[0] = 1;
-    subblock[1] = loop_count % 256;
-    subblock[2] = loop_count / 256;
-    if (EGifPutExtensionLast(gf, APPLICATION_EXT_FUNC_CODE, 3, subblock) == GIF_ERROR) {
-      gif_push_error();
-      i_push_error(0, "writing loop extension sub-block");
-      return 0;
-    }
-  }
-#endif
-
-  return 1;
-}
-
-/*
-=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_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;
-    colors[i].Green = quant->mc_colors[i].rgb.g;
-    colors[i].Blue = quant->mc_colors[i].rgb.b;
-  }
-  if (want_trans) {
-    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;
-  while (map_size < size)
-    map_size <<= 1;
-  /* 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));
-  if (!map) {
-    gif_push_error();
-    i_push_error(0, "Could not create color map object");
-    return NULL;
-  }
-  return map;
-}
-
-/*
-=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.
-
-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.
-
-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) {
-#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
-     EGifSetGifVersion("87a");
-#endif
-}
-
-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) {
-  int size = quant->mc_count;
-  int i;
-  int imgn;
-  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 (!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));
-
-      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) {
-  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 = -1;
-  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 = 0;
-  int glob_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;
-
-  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? */
-  }
-
-  i_mempool_init(&mp);
-
-  /* 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, &scrh))
-    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) {
-    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]->channels == 4) {
-        glob_want_trans = 1;
-      }
-      glob_imgs[glob_img_count++] = imgs[imgn];
-    }
-  }
-  glob_want_trans = glob_want_trans && quant->transp != tr_none ;
-
-  orig_count = quant->mc_count;
-  orig_size = quant->mc_size;
-
-  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;
-    }
-    if (has_common_palette(glob_imgs, glob_img_count, quant, want_trans)) {
-      glob_paletted = 1;
-    }
-    else {
-      glob_paletted = 0;
-      i_quant_makemap(quant, glob_imgs, glob_img_count);
-    }
-    glob_color_count = quant->mc_count;
-    quant->mc_colors = orig_colors;
-  }
-
-  /* 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;
-    }
-    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, 
-                        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 (!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 (!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;
-      }
-    }
-    else {
-      /* the map we wrote was the map for this image - don't set the local 
-         map */
-      map = NULL;
-    }
-  }
-
-  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[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;
-      /* 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)) {
-        result = quant_paletted(quant, imgs[imgn]);
-      }
-      else {
-        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) {
-        i_quant_transparent(quant, result, imgs[imgn], quant->mc_count);
-        trans_index = quant->mc_count;
-      }
-
-      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;
-      }
-    }
-    else {
-      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 (!do_gce(gf, imgs[imgn], want_trans, trans_index)) {
-      i_mempool_destroy(&mp);
-      quant->mc_colors = orig_colors;
-      myfree(result);
-      EGifCloseFile(gf);
-      return 0;
-    }
-
-    if (!do_comments(gf, imgs[imgn])) {
-      i_mempool_destroy(&mp);
-      quant->mc_colors = orig_colors;
-      myfree(result);
-      EGifCloseFile(gf);
-      return 0;
-    }
-
-    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 (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);
-  }
-
-  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;
-}
-
-/*
-=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) {
-  GifFileType *gf;
-
-  i_clear_error();
-  mm_log((1, "i_writegif_gen(quant %p, fd %d, imgs %p, count %d)\n", 
-         quant, fd, imgs, count));
-
-  gif_set_version(quant, imgs, count);
-
-  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;
-  }
-
-  return i_writegif_low(quant, gf, imgs, 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;
-
-  return i_gen_writer(gwd, (char*)data, size) ? size : 0;
-}
-
-#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)
-{
-#if IM_GIFMAJOR >= 4
-  GifFileType *gf;
-  i_gen_write_data *gwd = i_gen_write_data_new(cb, userdata, maxlength);
-  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)\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"));
-    i_free_gen_write_data(gwd, 0);
-    return 0;
-  }
-
-  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)
-
-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
-
-*/
index cfa1086..7b6e659 100644 (file)
@@ -624,6 +624,8 @@ typedef struct i_gif_pos_tag {
 
 /* passed into i_writegif_gen() to control quantization */
 typedef struct i_quantize_tag {
+  int version;
+
   /* how to handle transparency */
   i_transp transp;
   /* the threshold at which to make pixels opaque */
@@ -659,6 +661,7 @@ typedef struct i_quantize_tag {
 
   /* the amount of perturbation to use for translate is mc_perturb */
   int perturb;
+  /* version 2 members after here */
 } i_quantize;
 
 typedef struct i_gif_opts {
diff --git a/imextpl.h b/imextpl.h
new file mode 100644 (file)
index 0000000..0d98f8e
--- /dev/null
+++ b/imextpl.h
@@ -0,0 +1,33 @@
+#ifndef IMAGER_IMEXTPL_H_
+#define IMAGER_IMEXTPL_H_
+
+#include "imextpltypes.h"
+#include "immacros.h"
+
+extern im_pl_ext_funcs *imager_perl_function_ext_table;
+
+#define DEFINE_IMAGER_PERL_CALLBACKS im_pl_ext_funcs *imager_perl_function_ext_table
+
+#ifndef IMAGER_MIN_PL_API_LEVEL
+#define IMAGER_MIN_PL_API_LEVEL IMAGER_PL_API_LEVEL
+#endif
+
+#define PERL_INITIALIZE_IMAGER_PERL_CALLBACKS \
+  do {  \
+    imager_perl_function_ext_table = INT2PTR(im_pl_ext_funcs *, SvIV(get_sv(PERL_PL_FUNCTION_TABLE_NAME, 1))); \
+    if (!imager_perl_function_ext_table) \
+      croak("Imager Perl API function table not found!"); \
+    if (imager_perl_function_ext_table->version != IMAGER_API_VERSION) \
+      croak("Imager Perl API version incorrect"); \
+    if (imager_perl_function_ext_table->level < IMAGER_MIN_PL_API_LEVEL) \
+      croak("perl API level %d below minimum of %d", imager_perl_function_ext_table->level, IMAGER_MIN_PL_API_LEVEL); \
+  } while (0)
+
+/* just for use here */
+#define im_exttpl imager_perl_function_ext_table
+
+#define ip_handle_quant_opts  (im_exttpl->f_ip_handle_quant_opts)
+#define ip_cleanup_quant_opts  (im_exttpl->f_ip_cleanup_quant_opts)
+#define ip_copy_colors_back (im_exttpl->f_ip_copy_colors_back)
+
+#endif
diff --git a/imextpltypes.h b/imextpltypes.h
new file mode 100644 (file)
index 0000000..e7722a2
--- /dev/null
@@ -0,0 +1,30 @@
+#ifndef IMAGER_IMEXTPLTYPES_H_
+#define IMAGER_IMEXTPLTYPES_H_
+
+#ifndef PERL_NO_GET_CONTEXT
+#error Sorry, you need to build with PERL_NO_GET_CONTEXT
+#endif
+
+#define IMAGER_PL_API_VERSION 1
+
+/* This file provides functions useful for external code in
+   interfacing with perl - these functions aren't part of the core
+   Imager API. */
+
+#define IMAGER_PL_API_LEVEL 1
+
+typedef struct {
+  int version;
+  int level;
+
+  /* IMAGER_PL_API_LEVEL 1 functions */
+  void (*f_ip_handle_quant_opts)(pTHX_ i_quantize *quant, HV *hv);
+  void (*f_ip_cleanup_quant_opts)(pTHX_ i_quantize *quant);
+  void (*f_ip_copy_colors_back)(pTHX_ HV *hv, i_quantize *quant);
+
+  /* IMAGER_PL_API_LEVEL 2 functions will go here */
+} im_pl_ext_funcs;
+
+#define PERL_PL_FUNCTION_TABLE_NAME "Imager::__ext_pl_func_table"
+
+#endif
diff --git a/t/t105gif.t b/t/t105gif.t
deleted file mode 100644 (file)
index 7514ac2..0000000
+++ /dev/null
@@ -1,847 +0,0 @@
-#!perl -w
-
-=pod
-
-IF THIS TEST CRASHES
-
-Giflib/libungif have a long history of bugs, so if this script crashes
-and you aren't running version 4.1.4 of giflib or libungif then
-UPGRADE.
-
-=cut
-
-use strict;
-$|=1;
-use Test::More;
-use Imager qw(:all);
-use Imager::Test qw(is_color3 test_image test_image_raw);
-
-use Carp 'confess';
-$SIG{__DIE__} = sub { confess @_ };
-
-my $buggy_giflib_file = "buggy_giflib.txt";
-
-init_log("testout/t105gif.log",1);
-
-i_has_format("gif")
-  or plan skip_all => "no gif support";
-
-plan tests => 146;
-
-my $green=i_color_new(0,255,0,255);
-my $blue=i_color_new(0,0,255,255);
-my $red=i_color_new(255,0,0,255);
-
-my $img=test_image_raw;
-
-my $gifver = Imager::i_giflib_version();
-diag("giflib version (from header) $gifver");
-
-open(FH,">testout/t105.gif") || die "Cannot open testout/t105.gif\n";
-binmode(FH);
-ok(i_writegifmc($img,fileno(FH),6), "write low") or
-  die "Cannot write testout/t105.gif\n";
-close(FH);
-
-open(FH,"testout/t105.gif") || die "Cannot open testout/t105.gif\n";
-binmode(FH);
-ok($img=i_readgif(fileno(FH)), "read low")
-  or die "Cannot read testout/t105.gif\n";
-close(FH);
-
-open(FH,"testout/t105.gif") || die "Cannot open testout/t105.gif\n";
-binmode(FH);
-($img, my $palette)=i_readgif(fileno(FH));
-ok($img, "read palette") or die "Cannot read testout/t105.gif\n";
-close(FH);
-
-$palette=''; # just to skip a warning.
-
-# check that reading interlaced/non-interlaced versions of 
-# the same GIF produce the same image
-# I could replace this with code that used Imager's built-in
-# image comparison code, but I know this code revealed the error
-open(FH, "<testimg/scalei.gif") || die "Cannot open testimg/scalei.gif";
-binmode FH;
-my ($imgi) = i_readgif(fileno(FH));
-ok($imgi, "read interlaced") or die "Cannot read testimg/scalei.gif";
-close FH;
-open FH, "<testimg/scale.gif" or die "Cannot open testimg/scale.gif";
-binmode FH;
-my ($imgni) = i_readgif(fileno(FH));
-ok($imgni, "read normal") or die "Cannot read testimg/scale.gif";
-close FH;
-
-open FH, ">testout/t105i.ppm" or die "Cannot create testout/t105i.ppm";
-binmode FH;
-my $IO = Imager::io_new_fd( fileno(FH) );
-i_writeppm_wiol($imgi, $IO)
-  or die "Cannot write testout/t105i.ppm";
-close FH;
-
-open FH, ">testout/t105ni.ppm" or die "Cannot create testout/t105ni.ppm";
-binmode FH;
-$IO = Imager::io_new_fd( fileno(FH) );
-i_writeppm_wiol($imgni, $IO)
-  or die "Cannot write testout/t105ni.ppm";
-close FH;
-
-# compare them
-open FH, "<testout/t105i.ppm" or die "Cannot open testout/t105i.ppm";
-my $datai = do { local $/; <FH> };
-close FH;
-
-open FH, "<testout/t105ni.ppm" or die "Cannot open testout/t105ni.ppm";
-my $datani = do { local $/; <FH> };
-close FH;
-is($datai, $datani, "images match");
-
-SKIP:
-{
-  skip("giflib3 doesn't support callbacks", 4) unless $gifver >= 4.0;
-  # reading with a callback
-  # various sizes to make sure the buffering works
-  # requested size
-  open FH, "<testimg/scale.gif" or die "Cannot open testimg/scale.gif";
-  binmode FH;
-  # no callback version in giflib3, so don't overwrite a good image
-  my $img2 = i_readgif_callback(sub { my $tmp; read(FH, $tmp, $_[0]) and $tmp });
-  close FH; 
-  ok($img, "reading with a callback");
-  
-  ok(test_readgif_cb(1), "read callback 1 char buffer");
-  ok(test_readgif_cb(512), "read callback 512 char buffer");
-  ok(test_readgif_cb(1024), "read callback 1024 char buffer");
-}
-open FH, ">testout/t105_mc.gif" or die "Cannot open testout/t105_mc.gif";
-binmode FH;
-ok(i_writegifmc($img, fileno(FH), 7), "writegifmc");
-close(FH);
-
-# new writegif_gen
-# test webmap, custom errdiff map
-# (looks fairly awful)
-open FH, ">testout/t105_gen.gif" or die $!;
-binmode FH;
-ok(i_writegif_gen(fileno(FH), { make_colors=>'webmap',
-                               translate=>'errdiff',
-                               errdiff=>'custom',
-                               errdiff_width=>2,
-                               errdiff_height=>2,
-                               errdiff_map=>[0, 1, 1, 0]}, $img),
-       "webmap, custom errdif map");
-close FH;
-
-print "# the following tests are fairly slow\n";
-
-# test animation, mc_addi, error diffusion, ordered transparency
-my @imgs;
-my $sortagreen = i_color_new(0, 255, 0, 63);
-for my $i (0..4) {
-  my $im = Imager::ImgRaw::new(200, 200, 4);
-  _add_tags($im, gif_delay=>50, gif_disposal=>2);
-  for my $j (0..$i-1) {
-    my $fill = i_color_new(0, 128, 0, 255 * ($i-$j)/$i);
-    i_box_filled($im, 0, $j*40, 199, $j*40+40, $fill);
-  }
-  i_box_filled($im, 0, $i*40, 199, 199, $blue);
-  push(@imgs, $im);
-}
-my @gif_delays = (50) x 5;
-my @gif_disposal = (2) x 5;
-open FH, ">testout/t105_anim.gif" or die $!;
-binmode FH;
-ok(i_writegif_gen(fileno(FH), { make_colors=>'addi',
-                               translate=>'closest',
-                               gif_delays=>\@gif_delays,
-                               gif_disposal=>\@gif_disposal,
-                               gif_positions=> [ map [ $_*10, $_*10 ], 0..4 ],
-                               gif_user_input=>[ 1, 0, 1, 0, 1 ],
-                               transp=>'ordered',
-                               'tr_orddith'=>'dot8'}, @imgs),
-   "write anim gif");
-close FH;
-
-my $can_write_callback = 0;
-unlink $buggy_giflib_file;
-SKIP:
-{
-  skip("giflib3 doesn't support callbacks", 1) unless $gifver >= 4.0;
-  ++$can_write_callback;
-  my $good = ext_test(14, <<'ENDOFCODE');
-use Imager qw(:all);
-use Imager::Test qw(test_image_raw);
-my $timg = test_image_raw();
-my @gif_delays = (50) x 5;
-my @gif_disposal = (2) x 5;
-my @imgs = ($timg) x 5;
-open FH, "> testout/t105_anim_cb.gif" or die $!;
-binmode FH;
-i_writegif_callback(sub { 
-                     print FH $_[0] 
-                   },
-                   -1, # max buffering
-                   { make_colors=>'webmap',    
-                     translate=>'closest',
-                     gif_delays=>\@gif_delays,
-                     gif_disposal=>\@gif_disposal,
-                     #transp=>'ordered',
-                     tr_orddith=>'dot8'}, @imgs)
-  or die "Cannot write anim gif";
-close FH;
-print "ok 14\n";
-exit;
-ENDOFCODE
-  unless ($good) {
-    $can_write_callback = 0;
-    fail("see $buggy_giflib_file");
-    print STDERR "\nprobable buggy giflib - skipping tests that depend on a good giflib\n";
-    print STDERR "see $buggy_giflib_file for more information\n";
-    open FLAG, "> $buggy_giflib_file" or die;
-    print FLAG <<EOS;
-This file is created by t105gif.t when test 14 fails.
-
-This failure usually indicates you\'re using the original versions
-of giflib 4.1.0 - 4.1.3, which have a few bugs that Imager tickles.
-
-You can apply the patch from:
-
-http://www.develop-help.com/imager/giflib.patch
-
-or you can just install Imager as is, if you only need to write GIFs to 
-files or file descriptors (such as sockets).
-
-One hunk of this patch is rejected (correctly) with giflib 4.1.3,
-since one bug that the patch fixes is fixed in 4.1.3.
-
-If you don't feel comfortable with that apply the patch file that
-belongs to the following patch entry on sourceforge:
-
-https://sourceforge.net/tracker/index.php?func=detail&aid=981255&group_id=102202&atid=631306
-
-In previous versions of Imager only this test was careful about catching 
-the error, we now skip any tests that crashed or failed when the buggy 
-giflib was present.
-EOS
-  }
-}
-@imgs = ();
-my $c = i_color_new(0,0,0,0);
-for my $g (0..3) {
-  my $im = Imager::ImgRaw::new(200, 200, 3);
-  _add_tags($im, gif_local_map=>1, gif_delay=>150, gif_loop=>10);
-  for my $x (0 .. 39) {
-    for my $y (0 .. 39) {
-      $c->set($x * 6, $y * 6, 32*$g+$x+$y, 255);
-      i_box_filled($im, $x*5, $y*5, $x*5+4, $y*5+4, $c);
-    }
-  }
-  push(@imgs, $im);
-}
-# test giflib with multiple palettes
-# (it was meant to test the NS loop extension too, but that's broken)
-# this looks better with make_colors=>'addi', translate=>'errdiff'
-# this test aims to overload the palette for each image, so the
-# output looks moderately horrible
-open FH, ">testout/t105_mult_pall.gif" or die "Cannot create file: $!";
-binmode FH;
-ok(i_writegif_gen(fileno(FH), { #make_colors=>'webmap',
-                              translate=>'giflib',
-                             }, @imgs), "write multiple palettes")
-  or print "# ", join(":", map $_->[1], Imager::i_errors()),"\n";
-close FH;
-
-# regression test: giflib doesn't like 1 colour images
-my $img1 = Imager::ImgRaw::new(100, 100, 3);
-i_box_filled($img1, 0, 0, 100, 100, $red);
-open FH, ">testout/t105_onecol.gif" or die $!;
-binmode FH;
-ok(i_writegif_gen(fileno(FH), { translate=>'giflib'}, $img1), 
-   "single colour write regression");
-close FH;
-
-# transparency test
-# previously it was harder do write transparent images
-# tests the improvements
-my $timg = Imager::ImgRaw::new(20, 20, 4);
-my $trans = i_color_new(255, 0, 0, 127);
-i_box_filled($timg, 0, 0, 20, 20, $green);
-i_box_filled($timg, 2, 2, 18, 18, $trans);
-open FH, ">testout/t105_trans.gif" or die $!;
-binmode FH;
-ok(i_writegif_gen(fileno(FH), { make_colors=>'addi',
-                               translate=>'closest',
-                               transp=>'ordered',
-                             }, $timg), "write transparent");
-close FH;
-
-# some error handling tests
-# open a file handle for read and try to save to it
-# is this idea portable?
-# whether or not it is, giflib segfaults on this <sigh>
-#open FH, "<testout/t105_trans.gif" or die $!;
-#binmode FH; # habit, I suppose
-#if (i_writegif_gen(fileno(FH), {}, $timg)) {
-#  # this is meant to _fail_
-#  print "not ok 18 # writing to read-only should fail";
-#}
-#else {
-#  print "ok 18 # ",Imager::_error_as_msg(),"\n";
-#}
-#close FH;
-
-# try to read a file of the wrong format - the script will do
-open FH, "<t/t105gif.t"
-  or die "Cannot open this script!: $!";
-binmode FH;
-ok(!i_readgif(fileno(FH)), 
-   "read test script as gif should fail ". Imager::_error_as_msg());
-close FH;
-
-# try to save no images :)
-open FH, ">testout/t105_none.gif"
-  or die "Cannot open testout/t105_none.gif: $!";
-binmode FH;
-if (ok(!i_writegif_gen(fileno(FH), {}, "hello"), "shouldn't be able to write a string as a gif")) {
-  print "# ",Imager::_error_as_msg(),"\n";
-}
-
-# try to read a truncated gif (no image descriptors)
-read_failure('testimg/trimgdesc.gif');
-# file truncated just after the image descriptor tag
-read_failure('testimg/trmiddesc.gif');
-# image has no colour map
-read_failure('testimg/nocmap.gif');
-
-SKIP:
-{
-  skip("see $buggy_giflib_file", 18) if -e $buggy_giflib_file;
-  # image has a local colour map
-  open FH, "< testimg/loccmap.gif"
-    or die "Cannot open testimg/loccmap.gif: $!";
-  binmode FH;
-  ok(i_readgif(fileno(FH)), "read an image with only a local colour map");
-  close FH;
-  
-  # image has global and local colour maps
-  open FH, "< testimg/screen2.gif"
-    or die "Cannot open testimg/screen2.gif: $!";
-  binmode FH;
-  my $ims = i_readgif(fileno(FH));
-  unless (ok($ims, "read an image with global and local colour map")) {
-    print "# ",Imager::_error_as_msg(),"\n";
-  }
-  close FH;
-  
-  open FH, "< testimg/expected.gif"
-    or die "Cannot open testimg/expected.gif: $!";
-  binmode FH;
-  my $ime = i_readgif(fileno(FH));
-  close FH;
-  ok($ime, "reading testimg/expected.gif");
- SKIP:
-  {
-    skip("could not read one or both of expected.gif or loccamp.gif", 1)
-          unless $ims and $ime;
-    unless (is(i_img_diff($ime, $ims), 0, 
-              "compare loccmap and expected")) {
-      # save the bad one
-      open FH, "> testout/t105_screen2.gif"
-       or die "Cannot create testout/t105_screen.gif: $!";
-      binmode FH;
-      i_writegifmc($ims, fileno(FH), 7)
-       or print "# could not save t105_screen.gif\n";
-      close FH;
-    }
-  }
-  
-  # test reading a multi-image file into multiple images
-  open FH, "< testimg/screen2.gif"
-    or die "Cannot open testimg/screen2.gif: $!";
-  binmode FH;
-  @imgs = Imager::i_readgif_multi(fileno(FH));
-  ok(@imgs, "read multi-image file into multiple images");
-  close FH;
-  is(@imgs, 2, "should be 2 images");
-  my $paletted = 1;
-  for my $img (@imgs) {
-    unless (Imager::i_img_type($img) == 1) {
-      $paletted = 0;
-      last;
-    }
-  }
-  ok($paletted, "both images should be paletted");
-  is(Imager::i_colorcount($imgs[0]), 4, "4 colours in first image");
-  is(Imager::i_colorcount($imgs[1]), 2, "2 colours in second image");
-  ok(Imager::i_tags_find($imgs[0], "gif_left", 0), 
-     "gif_left tag should be there");
-  my @tags = map {[ Imager::i_tags_get($imgs[1], $_) ]} 0..Imager::i_tags_count($imgs[1])-1;
-  my ($left) = grep $_->[0] eq 'gif_left', @tags;
-  ok($left && $left->[1] == 3, "check gif_left value");
-  
-  # screen3.gif was saved with 
-  open FH, "< testimg/screen3.gif"
-    or die "Cannot open testimg/screen3.gif: $!";
-  binmode FH;
-  @imgs = Imager::i_readgif_multi(fileno(FH));
-  ok(@imgs, "read screen3.gif");
-  close FH;
-  eval {
-    require 'Data/Dumper.pm';
-    Data::Dumper->import();
-  };
-  unless ($@) {
-    # build a big map of all tags for all images
-    @tags = 
-      map { 
-       my $im = $_; 
-       [ 
-        map { join ",", map { defined() ? $_ : "undef" } Imager::i_tags_get($im, $_) } 
-        0..Imager::i_tags_count($_)-1 
-       ] 
-      } @imgs;
-    my $dump = Dumper(\@tags);
-    $dump =~ s/^/# /mg;
-    print "# tags from gif\n", $dump;
-  }
-  
-  # at this point @imgs should contain only paletted images
-  ok(Imager::i_img_type($imgs[0]) == 1, "imgs[0] paletted");
-  ok(Imager::i_img_type($imgs[1]) == 1, "imgs[1] paletted");
-  
-  # see how we go saving it
-  open FH, ">testout/t105_pal.gif" or die $!;
-  binmode FH;
-  ok(i_writegif_gen(fileno(FH), { make_colors=>'addi',
-                                 translate=>'closest',
-                                 transp=>'ordered',
-                               }, @imgs), "write from paletted");
-  close FH;
-  
-  # make sure nothing bad happened
-  open FH, "< testout/t105_pal.gif" or die $!;
-  binmode FH;
-  ok((my @imgs2 = Imager::i_readgif_multi(fileno(FH))) == 2,
-     "re-reading saved paletted images");
-  ok(i_img_diff($imgs[0], $imgs2[0]) == 0, "imgs[0] mismatch");
-  ok(i_img_diff($imgs[1], $imgs2[1]) == 0, "imgs[1] mismatch");
-}
-
-# test that the OO interface warns when we supply old options
-{
-  my @warns;
-  local $SIG{__WARN__} = sub { push(@warns, "@_") };
-  
-  my $ooim = Imager->new;
-  ok($ooim->read(file=>"testout/t105.gif"), "read into object");
-  ok($ooim->write(file=>"testout/t105_warn.gif", interlace=>1),
-     "save from object")
-    or print "# ", $ooim->errstr, "\n";
-  ok(grep(/Obsolete .* interlace .* gif_interlace/, @warns),
-     "check for warning");
-  init(warn_obsolete=>0);
-  @warns = ();
-  ok($ooim->write(file=>"testout/t105_warn.gif", interlace=>1),
-     "save from object");
-  ok(!grep(/Obsolete .* interlace .* gif_interlace/, @warns),
-     "check for warning");
-}
-
-# test that we get greyscale from 1 channel images
-# we check for each makemap, and for each translate
-print "# test writes of grayscale images - ticket #365\n"; 
-my $ooim = Imager->new(xsize=>50, ysize=>50, channels=>1);
-for (my $y = 0; $y < 50; $y += 10) {
-  $ooim->box(box=>[ 0, $y, 49, $y+9], color=>NC($y*5,0,0), filled=>1);
-}
-my $ooim3 = $ooim->convert(preset=>'rgb');
-#$ooim3->write(file=>'testout/t105gray.ppm');
-my %maxerror = ( mediancut => 51000, 
-                addi => 0,
-                closest => 0,
-                perturb => 0,
-                errdiff => 0 );
-for my $makemap (qw(mediancut addi)) {
-  print "# make_colors => $makemap\n";
-  ok( $ooim->write(file=>"testout/t105gray-$makemap.gif",
-                  make_colors=>$makemap,
-                  gifquant=>'gen'),
-      "writing gif with makemap $makemap");
-  my $im2 = Imager->new;
-  if (ok($im2->read(file=>"testout/t105gray-$makemap.gif"),
-        "reading written grayscale gif")) {
-    my $diff = i_img_diff($ooim3->{IMG}, $im2->{IMG});
-    ok($diff <= $maxerror{$makemap}, "comparing images $diff");
-    #$im2->write(file=>"testout/t105gray-$makemap.ppm");
-  }
-  else {
-  SKIP: { skip("could not get test image", 1); }
-  }
-}
-for my $translate (qw(closest perturb errdiff)) {
-  print "# translate => $translate\n";
-  my @colors = map NC($_*50, $_*50, $_*50), 0..4;
-  ok($ooim->write(file=>"testout/t105gray-$translate.gif",
-                 translate=>$translate,
-                 make_colors=>'none',
-                 colors=>\@colors,
-                 gifquant=>'gen'),
-     "writing gif with translate $translate");
-  my $im2 = Imager->new;
-  if (ok($im2->read(file=>"testout/t105gray-$translate.gif"),
-        "reading written grayscale gif")) {
-    my $diff = i_img_diff($ooim3->{IMG}, $im2->{IMG});
-    ok($diff <= $maxerror{$translate}, "comparing images $diff");
-    #$im2->write(file=>"testout/t105gray-$translate.ppm");
-  }
-  else {
-  SKIP: { skip("could not load test image", 1) }
-  }
-    }
-
-# try to write an image with no colors - should error
-ok(!$ooim->write(file=>"testout/t105nocolors.gif",
-                make_colors=>'none',
-                colors=>[], gifquant=>'gen'),
-   "write with no colors");
-
-# try to write multiple with no colors, with separate maps
-# I don't see a way to test this, since we don't have a mechanism
-# to give the second image different quant options, we can't trigger
-# a failure just for the second image
-
-# check that the i_format tag is set for both multiple and single
-# image reads
-{
-  my @anim = Imager->read_multi(file=>"testout/t105_anim.gif");
-  ok(@anim == 5, "check we got all the images");
-  for my $frame (@anim) {
-    my ($type) = $frame->tags(name=>'i_format');
-    is($type, 'gif', "check i_format for animation frame");
-  }
-  
-  my $im = Imager->new;
-  ok($im->read(file=>"testout/t105.gif"), "read some gif");
-  my ($type) = $im->tags(name=>'i_format');
-  is($type, 'gif', 'check i_format for single image read');
-}
-
-{ # check file limits are checked
-  my $limit_file = "testout/t105.gif";
-  ok(Imager->set_file_limits(reset=>1, width=>149), "set width limit 149");
-  my $im = Imager->new;
-  ok(!$im->read(file=>$limit_file),
-     "should fail read due to size limits");
-  print "# ",$im->errstr,"\n";
-  like($im->errstr, qr/image width/, "check message");
-  
-  ok(Imager->set_file_limits(reset=>1, height=>149), "set height limit 149");
-  ok(!$im->read(file=>$limit_file),
-     "should fail read due to size limits");
-  print "# ",$im->errstr,"\n";
-  like($im->errstr, qr/image height/, "check message");
-  
-  ok(Imager->set_file_limits(reset=>1, width=>150), "set width limit 150");
-  ok($im->read(file=>$limit_file),
-     "should succeed - just inside width limit");
-  ok(Imager->set_file_limits(reset=>1, height=>150), "set height limit 150");
-  ok($im->read(file=>$limit_file),
-     "should succeed - just inside height limit");
-  
-  # 150 x 150 x 3 channel image uses 67500 bytes
-  ok(Imager->set_file_limits(reset=>1, bytes=>67499),
-     "set bytes limit 67499");
-  ok(!$im->read(file=>$limit_file),
-     "should fail - too many bytes");
-  print "# ",$im->errstr,"\n";
-  like($im->errstr, qr/storage size/, "check error message");
-  ok(Imager->set_file_limits(reset=>1, bytes=>67500),
-     "set bytes limit 67500");
-  ok($im->read(file=>$limit_file),
-     "should succeed - just inside bytes limit");
-  Imager->set_file_limits(reset=>1);
-}
-
-{
-  print "# test OO interface reading of consolidated images\n";
-  my $im = Imager->new;
-  ok($im->read(file=>'testimg/screen2.gif', gif_consolidate=>1),
-     "read image to consolidate");
-  my $expected = Imager->new;
-  ok($expected->read(file=>'testimg/expected.gif'),
-     "read expected via OO");
-  is(i_img_diff($im->{IMG}, $expected->{IMG}), 0,
-     "compare them");
-  
-  # check the default read doesn't match
-  ok($im->read(file=>'testimg/screen2.gif'),
-     "read same image without consolidate");
-  isnt(i_img_diff($im->{IMG}, $expected->{IMG}), 0,
-       "compare them - shouldn't include the overlayed second image");
-}
-{
-  print "# test the reading of single pages\n";
-  # build a test file
-  my $test_file = 'testout/t105_multi_sing.gif';
-  my $im1 = Imager->new(xsize=>100, ysize=>100);
-  $im1->box(filled=>1, color=>$blue);
-  $im1->addtag(name=>'gif_left', value=>10);
-  $im1->addtag(name=>'gif_top', value=>15);
-  $im1->addtag(name=>'gif_comment', value=>'First page');
-  my $im2 = Imager->new(xsize=>50, ysize=>50);
-  $im2->box(filled=>1, color=>$red);
-  $im2->addtag(name=>'gif_left', value=>30);
-  $im2->addtag(name=>'gif_top', value=>25);
-  $im2->addtag(name=>'gif_comment', value=>'Second page');
-  my $im3 = Imager->new(xsize=>25, ysize=>25);
-  $im3->box(filled=>1, color=>$green);
-  $im3->addtag(name=>'gif_left', value=>35);
-  $im3->addtag(name=>'gif_top', value=>45);
-  # don't set comment for $im3
-  ok(Imager->write_multi({ file=> $test_file}, $im1, $im2, $im3),
-     "write test file for single page reads");
-  
-  my $res = Imager->new;
-  # check we get the first image
-  ok($res->read(file=>$test_file), "read default (first) page");
-  is(i_img_diff($im1->{IMG}, $res->{IMG}), 0, "compare against first");
-  # check tags
-  is($res->tags(name=>'gif_left'), 10, "gif_left");
-  is($res->tags(name=>'gif_top'), 15, "gif_top");
-  is($res->tags(name=>'gif_comment'), 'First page', "gif_comment");
-  
-  # get the second image
-  ok($res->read(file=>$test_file, page=>1), "read second page")
-    or print "# ",$res->errstr, "\n";
-  is(i_img_diff($im2->{IMG}, $res->{IMG}), 0, "compare against second");
-  # check tags
-  is($res->tags(name=>'gif_left'), 30, "gif_left");
-  is($res->tags(name=>'gif_top'), 25, "gif_top");
-  is($res->tags(name=>'gif_comment'), 'Second page', "gif_comment");
-  
-  # get the third image
-  ok($res->read(file=>$test_file, page=>2), "read third page")
-    or print "# ",$res->errstr, "\n";
-  is(i_img_diff($im3->{IMG}, $res->{IMG}), 0, "compare against third");
-  is($res->tags(name=>'gif_left'), 35, "gif_left");
-  is($res->tags(name=>'gif_top'), 45, "gif_top");
-  is($res->tags(name=>'gif_comment'), undef, 'gif_comment undef');
-  
-  # try to read a fourth page
-    ok(!$res->read(file=>$test_file, page=>3), "fail reading fourth page");
-  cmp_ok($res->errstr, "=~", 'page 3 not found',
-        "check error message");
-}
-SKIP:
-{
-  skip("gif_loop not supported on giflib before 4.1", 6) 
-    unless $gifver >= 4.1;
-  # testing writing the loop extension
-  my $im1 = Imager->new(xsize => 100, ysize => 100);
-  $im1->box(filled => 1, color => '#FF0000');
-  my $im2 = Imager->new(xsize => 100, ysize => 100);
-  $im2->box(filled => 1, color => '#00FF00');
-  ok(Imager->write_multi({
-                         gif_loop => 5, 
-                         gif_delay => 50, 
-                         file => 'testout/t105loop.gif'
-                        }, $im1, $im2),
-     "write with loop extension");
-  
-  my @im = Imager->read_multi(file => 'testout/t105loop.gif');
-  is(@im, 2, "read loop images back");
-  is($im[0]->tags(name => 'gif_loop'), 5, "first loop read back");
-  is($im[1]->tags(name => 'gif_loop'), 5, "second loop read back");
-  is($im[0]->tags(name => 'gif_delay'), 50, "first delay read back");
-  is($im[1]->tags(name => 'gif_delay'), 50, "second delay read back");
-}
-SKIP:
-{ # check graphic control extension and ns loop tags are read correctly
-  print "# check GCE and netscape loop extension tag values\n";
-  my @im = Imager->read_multi(file => 'testimg/screen3.gif');
-  is(@im, 2, "read 2 images from screen3.gif")
-    or skip("Could not load testimg/screen3.gif:".Imager->errstr, 11);
-  is($im[0]->tags(name => 'gif_delay'),          50, "0 - gif_delay");
-  is($im[0]->tags(name => 'gif_disposal'),        2, "0 - gif_disposal");
-  is($im[0]->tags(name => 'gif_trans_index'), undef, "0 - gif_trans_index");
-  is($im[0]->tags(name => 'gif_user_input'),      0, "0 - gif_user_input");
-  is($im[0]->tags(name => 'gif_loop'),            0, "0 - gif_loop");
-  is($im[1]->tags(name => 'gif_delay'),          50, "1 - gif_delay");
-  is($im[1]->tags(name => 'gif_disposal'),        2, "1 - gif_disposal");
-  is($im[1]->tags(name => 'gif_trans_index'),     7, "1 - gif_trans_index");
-  is($im[1]->tags(name => 'gif_trans_color'), 'color(255,255,255,0)',
-     "1 - gif_trans_index");
-  is($im[1]->tags(name => 'gif_user_input'),      0, "1 - gif_user_input");
-  is($im[1]->tags(name => 'gif_loop'),            0, "1 - gif_loop");
-}
-
-{
-  # manually modified from a small gif, this had the palette
-  # size changed to half the size, leaving an index out of range
-  my $im = Imager->new;
-  ok($im->read(file => 'testimg/badindex.gif', type => 'gif'), 
-     "read bad index gif")
-    or print "# ", $im->errstr, "\n";
-  my @indexes = $im->getscanline('y' => 0, type => 'index');
-  is_deeply(\@indexes, [ 0..4 ], "check for correct indexes");
-  is($im->colorcount, 5, "check the palette was adjusted");
-  is_color3($im->getpixel('y' => 0, x => 4), 0, 0, 0, 
-           "check it was black added");
-  is($im->tags(name => 'gif_colormap_size'), 4, 'color map size tag');
-}
-
-{
-  ok(grep($_ eq 'gif', Imager->read_types), "check gif in read types");
-  ok(grep($_ eq 'gif', Imager->write_types), "check gif in write types");
-}
-
-{
-  # check screen tags handled correctly note the screen size
-  # supplied is larger than the box covered by the images
-  my $im1 = Imager->new(xsize => 10, ysize => 8);
-  $im1->settag(name => 'gif_top', value => 4);
-  $im1->settag(name => 'gif_screen_width', value => 18);
-  $im1->settag(name => 'gif_screen_height', value => 16);
-  my $im2 = Imager->new(xsize => 7, ysize => 10);
-  $im2->settag(name => 'gif_left', value => 3);
-  my @im = ( $im1, $im2 );
-  
-  my $data;
-  ok(Imager->write_multi({ data => \$data, type => 'gif' }, @im),
-     "write with screen settings")
-    or print "# ", Imager->errstr, "\n";
-  my @result = Imager->read_multi(data => $data);
-  is(@result, 2, "got 2 images back");
-  is($result[0]->tags(name => 'gif_screen_width'), 18,
-     "check result screen width");
-  is($result[0]->tags(name => 'gif_screen_height'), 16,
-     "check result screen height");
-  is($result[0]->tags(name => 'gif_left'), 0,
-     "check first gif_left");
-  is($result[0]->tags(name => 'gif_top'), 4,
-     "check first gif_top");
-  is($result[1]->tags(name => 'gif_left'), 3,
-     "check second gif_left");
-  is($result[1]->tags(name => 'gif_top'), 0,
-     "check second gif_top");
-}
-
-{ # test colors array returns colors
-  my $data;
-  my $im = test_image();
-  my @colors;
-  ok($im->write(data => \$data, 
-               colors => \@colors, 
-               make_colors => 'webmap', 
-               translate => 'closest',
-               gifquant => 'gen',
-               type => 'gif'),
-     "write using webmap to check color table");
-  is(@colors, 216, "should be 216 colors in the webmap");
-  is_color3($colors[0], 0, 0, 0, "first should be 000000");
-  is_color3($colors[1], 0, 0, 0x33, "second should be 000033");
-  is_color3($colors[8], 0, 0x33, 0x66, "9th should be 003366");
-}
-
-{ # a zero length extension could make read_/read_multi crash
-  my ($im) = Imager->read_multi(file => "testimg/zerocomm.gif");
-  ok($im, "read image with zero-length extension");
-}
-
-sub test_readgif_cb {
-  my ($size) = @_;
-
-  open FH, "<testimg/scale.gif" or die "Cannot open testimg/scale.gif";
-  binmode FH;
-  my $img = i_readgif_callback(sub { my $tmp; read(FH, $tmp, $size) and $tmp });
-  close FH; 
-  return $img;
-}
-
-# tests for reading bad gif files
-sub read_failure {
-  my ($filename) = @_;
-
-  open FH, "< $filename"
-    or die "Cannot open $filename: $!";
-  binmode FH;
-  my ($result, $map) = i_readgif(fileno(FH));
-  ok(!$result, "attempt to read invalid image $filename ".Imager::_error_as_msg());
-  close FH;
-}
-
-sub _clear_tags {
-  my (@imgs) = @_;
-
-  for my $img (@imgs) {
-    $img->deltag(code=>0);
-  }
-}
-
-sub _add_tags {
-  my ($img, %tags) = @_;
-
-  for my $key (keys %tags) {
-    Imager::i_tags_add($img, $key, 0, $tags{$key}, 0);
-  }
-}
-
-sub ext_test {
-  my ($testnum, $code, $count, $name) = @_;
-
-  $count ||= 1;
-  $name ||= "gif$testnum";
-
-  # build our code
-  my $script = "testout/$name.pl";
-  if (open SCRIPT, "> $script") {
-    print SCRIPT <<'PROLOG';
-#!perl -w
-if (lc $^O eq 'mswin32') {
-  # avoid the dialog box that window's pops up on a GPF
-  # if you want to debug this stuff, I suggest you comment out the 
-  # following
-  eval {
-    require Win32API::File;
-    Win32API::File::SetErrorMode( Win32API::File::SEM_NOGPFAULTERRORBOX());
-  };
-}
-PROLOG
-
-    print SCRIPT $code;
-    close SCRIPT;
-
-    my $perl = $^X;
-    $perl = qq/"$perl"/ if $perl =~ / /;
-
-    print "# script: $script\n";
-    my $cmd = "$perl -Mblib $script";
-    print "# command: $cmd\n";
-
-    my $ok = 1;
-    my @out = `$cmd`; # should work on DOS and Win32
-    my $found = 0;
-    for (@out) {
-      if (/^not ok\s+(?:\d+\s*)?#(.*)/ || /^not ok/) {
-        my $msg = $1 || '';
-        ok(0, $msg);
-       $ok = 0;
-       ++$found;
-      }
-      elsif (/^ok\s+(?:\d+\s*)?#(.*)/ || /^ok/) {
-        my $msg = $1 || '';
-        ok(1, $msg);
-       ++$found;
-      }
-    }
-    unless ($count == $found) {
-      print "# didn't see enough ok/not ok\n";
-      $ok = 0;
-    }
-    return $ok;
-  }
-  else {
-    return skip("could not create test script $script: $!");
-    return 0;
-  }
-}
index 586bd46..b93ef72 100644 (file)
@@ -4,7 +4,7 @@ $|=1;
 use Test::More;
 use Imager qw(:all);
 
-i_has_format("gif")
+$Imager::formats{"gif"}
   and plan skip_all => "gif support available and this tests the lack of it";
 
 plan tests => 12;
index 39d907a..1da09f0 100644 (file)
@@ -44,7 +44,7 @@ my %files;
                  { file => "testimg/test.png"  },
                  { file => "testimg/test.raw", xsize=>150, ysize=>150, type=>'raw', interleave => 0},
                  { file => "testimg/penguin-base.ppm"  },
-                 { file => "testimg/expected.gif"  },
+                 { file => "GIF/testimg/expected.gif"  },
                  { file => "testimg/comp8.tif" },
                   { file => "testimg/winrgb24.bmp" },
                   { file => "testimg/test.tga" }, );
@@ -99,71 +99,66 @@ for my $type (@types) {
     skip("couldn't open the damn file: $!", 7);
   }
 
-  if ($type ne 'gif' || Imager::i_giflib_version() >= 4) {
-    # read from a memory buffer
-    open DATA, "< $opts{file}"
-      or die "Cannot open $opts{file}: $!";
-    binmode DATA;
-    my $data = do { local $/; <DATA> };
-    close DATA;
-    my $bimg = Imager->new;
-    
-    if (ok($bimg->read(data=>$data, %mopts, type=>$type), "read from buffer", 
-           $img)) {
-      ok(Imager::i_img_diff($img->{IMG}, $bimg->{IMG}) == 0,
-         "comparing buffer read image");
-    }
-    else {
-      skip("nothing to compare");
-    }
-    
-    # read from callbacks, both with minimum and maximum reads
-    my $buf = $data;
-    my $seekpos = 0;
-    my $reader_min = 
-      sub { 
-        my ($size, $maxread) = @_;
-        my $out = substr($buf, $seekpos, $size);
-        $seekpos += length $out;
-        $out;
-      };
-    my $reader_max = 
-      sub { 
-        my ($size, $maxread) = @_;
-        my $out = substr($buf, $seekpos, $maxread);
-        $seekpos += length $out;
-        $out;
-      };
-    my $seeker =
-      sub {
-        my ($offset, $whence) = @_;
-        #print "io_seeker($offset, $whence)\n";
-        if ($whence == SEEK_SET) {
-          $seekpos = $offset;
-        }
-        elsif ($whence == SEEK_CUR) {
-          $seekpos += $offset;
-        }
-        else { # SEEK_END
-          $seekpos = length($buf) + $offset;
-        }
-        #print "-> $seekpos\n";
-        $seekpos;
-      };
-    my $cbimg = Imager->new;
-    ok($cbimg->read(callback=>$reader_min, seekcb=>$seeker, type=>$type, %mopts),
-       "read from callback min", $cbimg);
-    ok(Imager::i_img_diff($cbimg->{IMG}, $img->{IMG}) == 0,
-       "comparing mincb image");
-    $seekpos = 0;
-    ok($cbimg->read(callback=>$reader_max, seekcb=>$seeker, type=>$type, %mopts),
-       "read from callback max", $cbimg);
-    ok(Imager::i_img_diff($cbimg->{IMG}, $img->{IMG}) == 0,
-       "comparing maxcb image");
+  # read from a memory buffer
+  open DATA, "< $opts{file}"
+    or die "Cannot open $opts{file}: $!";
+  binmode DATA;
+  my $data = do { local $/; <DATA> };
+  close DATA;
+  my $bimg = Imager->new;
+  
+  if (ok($bimg->read(data=>$data, %mopts, type=>$type), "read from buffer", 
+        $img)) {
+    ok(Imager::i_img_diff($img->{IMG}, $bimg->{IMG}) == 0,
+       "comparing buffer read image");
   }
   else {
-    skip("giflib < 4 doesn't support callbacks", 6);
+    skip("nothing to compare");
   }
+  
+  # read from callbacks, both with minimum and maximum reads
+  my $buf = $data;
+  my $seekpos = 0;
+  my $reader_min = 
+    sub { 
+      my ($size, $maxread) = @_;
+      my $out = substr($buf, $seekpos, $size);
+      $seekpos += length $out;
+      $out;
+    };
+  my $reader_max = 
+    sub { 
+      my ($size, $maxread) = @_;
+      my $out = substr($buf, $seekpos, $maxread);
+      $seekpos += length $out;
+      $out;
+    };
+  my $seeker =
+    sub {
+      my ($offset, $whence) = @_;
+      #print "io_seeker($offset, $whence)\n";
+      if ($whence == SEEK_SET) {
+       $seekpos = $offset;
+      }
+      elsif ($whence == SEEK_CUR) {
+       $seekpos += $offset;
+      }
+      else { # SEEK_END
+       $seekpos = length($buf) + $offset;
+      }
+      #print "-> $seekpos\n";
+      $seekpos;
+    };
+  my $cbimg = Imager->new;
+  ok($cbimg->read(callback=>$reader_min, seekcb=>$seeker, type=>$type, %mopts),
+     "read from callback min", $cbimg);
+  ok(Imager::i_img_diff($cbimg->{IMG}, $img->{IMG}) == 0,
+     "comparing mincb image");
+  $seekpos = 0;
+  ok($cbimg->read(callback=>$reader_max, seekcb=>$seeker, type=>$type, %mopts),
+     "read from callback max", $cbimg);
+  ok(Imager::i_img_diff($cbimg->{IMG}, $img->{IMG}) == 0,
+     "comparing maxcb image");
 }
 
 for my $type (@types) {
@@ -199,106 +194,95 @@ for my $type (@types) {
      "write to FH after writing $type");
   ok($fh->close, "closing FH after writing $type");
 
-  if ($type ne 'gif' || 
-      (Imager::i_giflib_version() >= 4 && !-e $buggy_giflib_file)) {
-    if (ok(open(DATA, "< $file"), "opening data source")) {
-      binmode DATA;
-      my $data = do { local $/; <DATA> };
-      close DATA;
-
-      # writing to a buffer
-      print "# writing $type to a buffer\n";
-      my $buf = '';
-      ok($wimg->write(data=>\$buf, %extraopts, type=>$type),
-         "writing $type to a buffer", $wimg);
-      $buf .= "SUFFIX\n";
-      open DATA, "> testout/t50_buf.$type"
-        or die "Cannot create $type buffer file: $!";
-      binmode DATA;
-      print DATA $buf;
-      close DATA;
-      ok($data eq $buf, "comparing file data to buffer");
-
-      $buf = '';
-      my $seekpos = 0;
-      my $did_close;
-      my $writer = 
-        sub {
-          my ($what) = @_;
-          if ($seekpos > length $buf) {
-            $buf .= "\0" x ($seekpos - length $buf);
-          }
-          substr($buf, $seekpos, length $what) = $what;
-          $seekpos += length $what;
-          $did_close = 0; # the close must be last
-          1;
-        };
-      my $reader_min = 
-        sub { 
-          my ($size, $maxread) = @_;
-          my $out = substr($buf, $seekpos, $size);
-          $seekpos += length $out;
-          $out;
-        };
-      my $reader_max = 
-        sub { 
-          my ($size, $maxread) = @_;
-          my $out = substr($buf, $seekpos, $maxread);
-          $seekpos += length $out;
-          $out;
-        };
-      use IO::Seekable;
-      my $seeker =
-        sub {
-          my ($offset, $whence) = @_;
-          #print "io_seeker($offset, $whence)\n";
-          if ($whence == SEEK_SET) {
-            $seekpos = $offset;
-          }
-          elsif ($whence == SEEK_CUR) {
-            $seekpos += $offset;
-          }
-          else { # SEEK_END
-            $seekpos = length($buf) + $offset;
-          }
-          #print "-> $seekpos\n";
-          $seekpos;
-        };
-      
-      my $closer = sub { ++$did_close; };
-      
-      print "# writing $type via callbacks (mb=1)\n";
-      ok($wimg->write(writecb=>$writer, seekcb=>$seeker, closecb=>$closer,
-                   readcb=>$reader_min,
-                   %extraopts, type=>$type, maxbuffer=>1),
-         "writing $type to callback (mb=1)", $wimg);
-
-      ok($did_close, "checking closecb called");
-      $buf .= "SUFFIX\n";
-      ok($data eq $buf, "comparing callback output to file data");
-      print "# writing $type via callbacks (no mb)\n";
-      $buf = '';
-      $did_close = 0;
-      $seekpos = 0;
-      # we don't use the closecb here - used to make sure we don't get 
-      # a warning/error on an attempt to call an undef close sub
-      ok($wimg->write(writecb=>$writer, seekcb=>$seeker, readcb=>$reader_min,
-                   %extraopts, type=>$type),
-         "writing $type to callback (no mb)", $wimg);
-      $buf .= "SUFFIX\n";
-      ok($data eq $buf, "comparing callback output to file data");
-    }
-    else {
-      skip("couldn't open data source", 7);
-    }
+  if (ok(open(DATA, "< $file"), "opening data source")) {
+    binmode DATA;
+    my $data = do { local $/; <DATA> };
+    close DATA;
+
+    # writing to a buffer
+    print "# writing $type to a buffer\n";
+    my $buf = '';
+    ok($wimg->write(data=>\$buf, %extraopts, type=>$type),
+       "writing $type to a buffer", $wimg);
+    $buf .= "SUFFIX\n";
+    open DATA, "> testout/t50_buf.$type"
+      or die "Cannot create $type buffer file: $!";
+    binmode DATA;
+    print DATA $buf;
+    close DATA;
+    ok($data eq $buf, "comparing file data to buffer");
+
+    $buf = '';
+    my $seekpos = 0;
+    my $did_close;
+    my $writer = 
+      sub {
+       my ($what) = @_;
+       if ($seekpos > length $buf) {
+         $buf .= "\0" x ($seekpos - length $buf);
+       }
+       substr($buf, $seekpos, length $what) = $what;
+       $seekpos += length $what;
+       $did_close = 0; # the close must be last
+       1;
+      };
+    my $reader_min = 
+      sub { 
+       my ($size, $maxread) = @_;
+       my $out = substr($buf, $seekpos, $size);
+       $seekpos += length $out;
+       $out;
+      };
+    my $reader_max = 
+      sub { 
+       my ($size, $maxread) = @_;
+       my $out = substr($buf, $seekpos, $maxread);
+       $seekpos += length $out;
+       $out;
+      };
+    use IO::Seekable;
+    my $seeker =
+      sub {
+       my ($offset, $whence) = @_;
+       #print "io_seeker($offset, $whence)\n";
+       if ($whence == SEEK_SET) {
+         $seekpos = $offset;
+       }
+       elsif ($whence == SEEK_CUR) {
+         $seekpos += $offset;
+       }
+       else { # SEEK_END
+         $seekpos = length($buf) + $offset;
+       }
+       #print "-> $seekpos\n";
+       $seekpos;
+      };
+
+    my $closer = sub { ++$did_close; };
+
+    print "# writing $type via callbacks (mb=1)\n";
+    ok($wimg->write(writecb=>$writer, seekcb=>$seeker, closecb=>$closer,
+                   readcb=>$reader_min,
+                   %extraopts, type=>$type, maxbuffer=>1),
+       "writing $type to callback (mb=1)", $wimg);
+
+    ok($did_close, "checking closecb called");
+    $buf .= "SUFFIX\n";
+    ok($data eq $buf, "comparing callback output to file data");
+    print "# writing $type via callbacks (no mb)\n";
+    $buf = '';
+    $did_close = 0;
+    $seekpos = 0;
+    # we don't use the closecb here - used to make sure we don't get 
+    # a warning/error on an attempt to call an undef close sub
+    ok($wimg->write(writecb=>$writer, seekcb=>$seeker, readcb=>$reader_min,
+                   %extraopts, type=>$type),
+       "writing $type to callback (no mb)", $wimg);
+    $buf .= "SUFFIX\n";
+    ok($data eq $buf, "comparing callback output to file data");
   }
   else {
-    if (-e $buggy_giflib_file) {
-      skip("see $buggy_giflib_file", 8);
-    }
-    else {
-      skip("giflib < 4 doesn't support callbacks", 8);
-    }
+    skip("couldn't open data source", 7);
   }
 }
 
diff --git a/t/t70newgif.t b/t/t70newgif.t
deleted file mode 100644 (file)
index 6b989da..0000000
+++ /dev/null
@@ -1,170 +0,0 @@
-#!perl -w
-# Before `make install' is performed this script should be runnable with
-# `make test'. After `make install' it should work as `perl test.pl'
-
-######################### We start with some black magic to print on failure.
-
-# Change 1..1 below to 1..last_test_to_print .
-# (It may become useful if the test is moved to ./t subdirectory.)
-
-
-BEGIN { $| = 1; print "1..24\n"; }
-END {print "not ok 1\n" unless $loaded;}
-
-my $buggy_giflib_file = "buggy_giflib.txt";
-
-use Imager qw(:all :handy);
-$loaded=1;
-
-print "ok 1\n";
-
-Imager::init('log'=>'testout/t70newgif.log');
-
-$green=i_color_new(0,255,0,0);
-$blue=i_color_new(0,0,255,0);
-
-$img=Imager->new();
-$img->open(file=>'testimg/scale.ppm',type=>'pnm') || print "failed: ",$img->{ERRSTR},"\n";
-print "ok 2\n";
-
-
-if (i_has_format("gif")) {
-  $img->write(file=>'testout/t70newgif.gif',type=>'gif',gifplanes=>1,gifquant=>'lm',lmfixed=>[$green,$blue]) || print "failed: ",$img->{ERRSTR},"\nnot ";
-  print "ok 3\n";
-
-  # make sure the palette is loaded properly (minimal test)
-  my $im2 = Imager->new();
-  my $map;
-  if ($im2->read(file=>'testimg/bandw.gif', colors=>\$map)) {
-    print "ok 4\n";
-    # check the palette
-    if ($map) {
-      print "ok 5\n";
-      if (@$map == 2) {
-       print "ok 6\n";
-       my @sorted = sort { comp_entry($a,$b) } @$map;
-       # first entry must be #000000 and second #FFFFFF
-       if (comp_entry($sorted[0], NC(0,0,0)) == 0) {
-         print "ok 7\n";
-       }
-       else {
-         print "not ok 7 # entry should be black\n";
-       }
-       if (comp_entry($sorted[1], NC(255,255,255)) == 0) {
-         print "ok 8\n";
-       }
-       else {
-         print "not ok 8 # entry should be white\n";
-       }
-      } 
-      else {
-       print "not ok 6 # bad map size\n";
-       print "ok 7 # skipped bad map size\n";
-       print "ok 8 # skipped bad map size\n";
-      }
-    } 
-    else {
-      print "not ok 5 # no map returned\n";
-      for (6..8) {
-       print "ok $_ # skipped no map returned\n";
-      }
-    }
-  }
-  else {
-    print "not ok 4 # ",$im2->errstr,"\n";
-    print "ok 5 # skipped - couldn't load image\n";
-  }
-
-  # test the read_multi interface
-  my @imgs = Imager->read_multi();
-  @imgs and print "not ";
-  print "ok 9\n";
-  Imager->errstr =~ /callback parameter missing/ or print "not ";
-  print "ok 10 # ",Imager->errstr,"\n";
-
-  @imgs = Imager->read_multi(type=>'gif');
-  @imgs and print "not ";
-  print "ok 11\n";
-  Imager->errstr =~ /file/ or print "not ";
-  print "ok 12 # ",Imager->errstr,"\n";
-  # kill warning
-  *NONESUCH = \20;
-  @imgs = Imager->read_multi(type=>'gif', fh=>*NONESUCH);
-  @imgs and print "not ";
-  print "ok 13\n";
-  Imager->errstr =~ /fh option not open/ or print "not ";
-  print "ok 14 # ",Imager->errstr,"\n";
-  unless (-e $buggy_giflib_file) {
-    @imgs = Imager->read_multi(type=>'gif', file=>'testimg/screen2.gif');
-    @imgs == 2 or print "not ";
-    print "ok 15\n";
-    grep(!UNIVERSAL::isa($_, 'Imager'), @imgs) and print "not ";
-    print "ok 16\n";
-    grep($_->type eq 'direct', @imgs) and print "not ";
-    print "ok 17\n";
-    (my @left = $imgs[0]->tags(name=>'gif_left')) == 1 or print "not ";
-    print "ok 18\n";
-    my $left = $imgs[1]->tags(name=>'gif_left') or print "not ";
-    print "ok 19\n";
-    $left == 3 or print "not ";
-    print "ok 20\n";
-  }
-  else {
-    for (15 .. 20) {
-      print "ok $_ # skip see $buggy_giflib_file\n";
-    }
-  }
-  if (Imager::i_giflib_version() >= 4.0) {
-    unless (-e $buggy_giflib_file) {
-      open FH, "< testimg/screen2.gif" 
-       or die "Cannot open testimg/screen2.gif: $!";
-      binmode FH;
-      my $cb = 
-       sub {
-         my $tmp;
-         read(FH, $tmp, $_[0]) and $tmp
-       };
-      @imgs = Imager->read_multi(type=>'gif',
-                                callback => $cb) or print "not ";
-      print "ok 21\n";
-      close FH;
-      @imgs == 2 or print "not ";
-      print "ok 22\n";
-      
-      open FH, "< testimg/screen2.gif" 
-       or die "Cannot open testimg/screen2.gif: $!";
-      binmode FH;
-      my $data = do { local $/; <FH>; };
-      close FH;
-      @imgs = Imager->read_multi(type=>'gif',
-                                data=>$data) or print "not ";
-      print "ok 23\n";
-      @imgs = 2 or print "not ";
-      print "ok 24\n";
-    }
-    else {
-      for (21..24) {
-       print "ok $_ # skip see $buggy_giflib_file\n";
-      }
-    }
-  }
-  else {
-    for (21..24) {
-      print "ok $_ # skipped - giflib3 doesn't support callbacks\n";
-    }
-  }
-}
-else {
-  for (3..24) {
-    print "ok $_ # skipped: no gif support\n";
-  }
-}
-
-sub comp_entry {
-  my ($l, $r) = @_;
-  my @l = $l->rgba;
-  my @r = $r->rgba;
-  return $l[0] <=> $r[0]
-    || $l[1] <=> $r[1]
-      || $l[2] <=> $r[2];
-}
diff --git a/testimg/badindex.gif b/testimg/badindex.gif
deleted file mode 100644 (file)
index 3591f48..0000000
Binary files a/testimg/badindex.gif and /dev/null differ
diff --git a/testimg/bandw.gif b/testimg/bandw.gif
deleted file mode 100644 (file)
index 93fb1d7..0000000
Binary files a/testimg/bandw.gif and /dev/null differ
diff --git a/testimg/expected.gif b/testimg/expected.gif
deleted file mode 100644 (file)
index 3409d38..0000000
Binary files a/testimg/expected.gif and /dev/null differ
diff --git a/testimg/loccmap.gif b/testimg/loccmap.gif
deleted file mode 100644 (file)
index 9dd264e..0000000
Binary files a/testimg/loccmap.gif and /dev/null differ
diff --git a/testimg/nocmap.gif b/testimg/nocmap.gif
deleted file mode 100644 (file)
index 8394110..0000000
Binary files a/testimg/nocmap.gif and /dev/null differ
diff --git a/testimg/scale.gif b/testimg/scale.gif
deleted file mode 100644 (file)
index 265ed7f..0000000
Binary files a/testimg/scale.gif and /dev/null differ
diff --git a/testimg/scalei.gif b/testimg/scalei.gif
deleted file mode 100644 (file)
index 3b3234a..0000000
Binary files a/testimg/scalei.gif and /dev/null differ
diff --git a/testimg/screen2.gif b/testimg/screen2.gif
deleted file mode 100644 (file)
index 4dddf69..0000000
Binary files a/testimg/screen2.gif and /dev/null differ
diff --git a/testimg/screen3.gif b/testimg/screen3.gif
deleted file mode 100644 (file)
index 77f808e..0000000
Binary files a/testimg/screen3.gif and /dev/null differ
diff --git a/testimg/trimgdesc.gif b/testimg/trimgdesc.gif
deleted file mode 100644 (file)
index f352b0e..0000000
Binary files a/testimg/trimgdesc.gif and /dev/null differ
diff --git a/testimg/trmiddesc.gif b/testimg/trmiddesc.gif
deleted file mode 100644 (file)
index 386e3c1..0000000
Binary files a/testimg/trmiddesc.gif and /dev/null differ
diff --git a/testimg/zerocomm.gif b/testimg/zerocomm.gif
deleted file mode 100644 (file)
index ede9f6a..0000000
Binary files a/testimg/zerocomm.gif and /dev/null differ