]> git.imager.perl.org - imager.git/commitdiff
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 42dfbe35f90cb8159bebff291f84d38738c742ac..025e5a001d6dcb8098ec9ff0528e398afaac0c85 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
 
    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
 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 fe3b0c5043a4f6bb9e495082ea2ac185e74fab0c..d817cb8a07008e01e2291a9ad761a949c291fa00 100644 (file)
--- a/Imager.pm
+++ b/Imager.pm
@@ -81,14 +81,6 @@ use Imager::Font;
                i_writetiff_wiol
                i_writetiff_wiol_faxable
 
                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
 
                i_readpnm_wiol
                i_writeppm_wiol
 
@@ -1907,21 +1899,7 @@ sub write_multi {
     ($IO, $file) = $class->_get_writer_io($opts, $type)
       or return undef;
     
     ($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)
       $class->_set_opts($opts, "tiff_", @images)
         or return;
       $class->_set_opts($opts, "exif_", @images)
index f42d22d75729be77439de4b9fb908f1cc6940339..821b1a1f1c234f6358c4e99b6f433ca529e9ffcb 100644 (file)
--- a/Imager.xs
+++ b/Imager.xs
@@ -19,6 +19,7 @@ extern "C" {
 #include "dynaload.h"
 #include "regmach.h"
 #include "imextdef.h"
 #include "dynaload.h"
 #include "regmach.h"
 #include "imextdef.h"
+#include "imextpltypes.h"
 
 #if i_int_hlines_testing()
 #include "imageri.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 */
 };
 
 /* 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;
 {
   /*** 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);
 }
 
     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 */
   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;
   SV **sv;
   AV *av;
   int i;
@@ -928,6 +932,18 @@ i_int_hlines_dump(i_int_hlines *hlines) {
 
 #endif
 
 
 #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
 #ifdef IMEXIF_ENABLE
 #define i_exif_enabled() 1
 #else
@@ -2505,499 +2521,6 @@ i_tiff_has_compression(name)
 #endif
 
 
 #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
 
 
 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));
           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;
         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) {
         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
 
       OUTPUT:
         RETVAL
 
@@ -5023,3 +4547,4 @@ i_int_hlines_CLONE_SKIP(cls)
 
 BOOT:
         PERL_SET_GLOBAL_CALLBACKS;
 
 BOOT:
         PERL_SET_GLOBAL_CALLBACKS;
+       PERL_PL_SET_GLOBAL_CALLBACKS;
\ No newline at end of file
index 9e40200845c636cbe32a511bb4766dcec9c83171..47f448f1fc03634e612799b79bc01efe817bc8ba 100644 (file)
--- a/MANIFEST
+++ b/MANIFEST
@@ -13,6 +13,23 @@ Flines/Flines.pm
 Flines/Flines.xs
 Flines/Makefile.PL
 Flines/t/t00flines.t
 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
 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
 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
 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
 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
 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/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)
 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/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
 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/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
 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/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
 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/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/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/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/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
 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/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
 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/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
 tga.c           Reading and writing Targa files
 tiff.c
 trans2.c
index fde11eba2e0f7bdfdf7df3156759b73eb2459644..3a1da3c56bc185c018ed7ea67b85edccf7455631 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 @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,
 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,
            "verbose|v" => \$VERBOSE,
            "nolog" => \$NOLOG,
           "noexif" => \$noexif,
-           "nogifsetversion" => \$no_gif_set_version,
           'coverage' => \$coverage,
           "assert|a" => \$assert);
 
           'coverage' => \$coverage,
           "assert|a" => \$assert);
 
@@ -127,9 +125,6 @@ if ($MANUAL) {
   automatic();
 }
 
   automatic();
 }
 
-# Make sure there isn't a clash between the gif libraries.
-gifcheck();
-
 my $lib_cflags = '';
 my $lib_lflags = '';
 my $F_LIBS = '';
 my $lib_cflags = '';
 my $lib_lflags = '';
 my $F_LIBS = '';
@@ -220,6 +215,7 @@ if ($MM_ver >= 6.46) {
       directory =>
       [
        "PNG",
       directory =>
       [
        "PNG",
+       "GIF",
       ],
      },
      resources =>
       ],
      },
      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 {
 
 
 sub grep_directory {
@@ -576,34 +572,34 @@ sub init {
 #                    code => \&png_probe,
 #                };
 
 #                    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',
 
   $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:
        $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)
 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 cfa10864cb543ddf66baa7ca903ad2b8337f2422..7b6e659147b0e6840481a28075b6cac484a279cc 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 {
 
 /* 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 */
   /* 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;
 
   /* 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 {
 } 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 586bd4612e02aab0a7aa286b73e2c2f3c3010931..b93ef7246a1166128f7a8705bc38da57be94b39c 100644 (file)
@@ -4,7 +4,7 @@ $|=1;
 use Test::More;
 use Imager qw(:all);
 
 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;
   and plan skip_all => "gif support available and this tests the lack of it";
 
 plan tests => 12;
index 39d907ac8a1d65ec985a3d53be92084c6bcd7403..1da09f02ba8f3378fe005862d12ceb20f942e752 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/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" }, );
                  { 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);
   }
 
     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 {
   }
   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) {
 }
 
 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");
 
      "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 {
   }
   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