merge write to gif tags updates
authorTony Cook <tony@develop=help.com>
Wed, 6 Mar 2002 12:08:23 +0000 (12:08 +0000)
committerTony Cook <tony@develop=help.com>
Wed, 6 Mar 2002 12:08:23 +0000 (12:08 +0000)
13 files changed:
Changes
Imager.pm
Imager.xs
gif.c
image.h
io.c
lib/Imager/Files.pod
lib/Imager/Matrix2d.pm
quant.c
t/t105gif.t
t/t106tiff.t
t/t50basicoo.t
tags.c

diff --git a/Changes b/Changes
index 6b5ea5d..3311fba 100644 (file)
--- a/Changes
+++ b/Changes
@@ -600,6 +600,10 @@ Revision history for Perl extension Imager.
         - added getpixel() and setpixel() methods
         - added Artur's OSX dlload() emulation, with minor changes
         - modified _color() to work around a 5.6.0 bug
+        - replaced old gif options with tags
+        - we now log which memory block is being freed before giving 
+          an error on it being re-freed
+        - fixed stupid bug in deleting tags
 
 
 =================================================================
index f485bd5..3bcee4f 100644 (file)
--- a/Imager.pm
+++ b/Imager.pm
@@ -1,7 +1,7 @@
 package Imager;
 
 use strict;
-use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS %formats $DEBUG %filters %DSOs $ERRSTR $fontstate %OPCODES $I2P $FORMATGUESS);
+use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS %formats $DEBUG %filters %DSOs $ERRSTR $fontstate %OPCODES $I2P $FORMATGUESS $warn_obsolete);
 use IO::File;
 
 use Imager::Color;
@@ -363,6 +363,8 @@ BEGIN {
     };
 
   $FORMATGUESS=\&def_guess_type;
+
+  $warn_obsolete = 1;
 }
 
 #
@@ -385,6 +387,9 @@ sub init {
   if ($parms{'log'}) {
     init_log($parms{'log'},$parms{'loglevel'});
   }
+  if (exists $parms{'warn_obsolete'}) {
+    $warn_obsolete = $parms{'warn_obsolete'};
+  }
 
 #    if ($parms{T1LIB_CONFIG}) { $ENV{T1LIB_CONFIG}=$parms{T1LIB_CONFIG}; }
 #    if ( $ENV{T1LIB_CONFIG} and ( $fontstate eq 'missing conf' )) {
@@ -859,6 +864,22 @@ sub deltag {
   }
 }
 
+sub settag {
+  my ($self, %opts) = @_;
+
+  if ($opts{name}) {
+    $self->deltag(name=>$opts{name});
+    return $self->addtag(name=>$opts{name}, value=>$opts{value});
+  }
+  elsif (defined $opts{code}) {
+    $self->deltag(code=>$opts{code});
+    return $self->addtag(code=>$opts{code}, value=>$opts{value});
+  }
+  else {
+    return undef;
+  }
+}
+
 my @needseekcb = qw/tiff/;
 my %needseekcb = map { $_, $_ } @needseekcb;
 
@@ -1167,6 +1188,94 @@ sub read {
   return $self;
 }
 
+sub _fix_gif_positions {
+  my ($opts, $opt, $msg, @imgs) = @_;
+  
+  my $positions = $opts->{'gif_positions'};
+  my $index = 0;
+  for my $pos (@$positions) {
+    my ($x, $y) = @$pos;
+    my $img = $imgs[$index++];
+    $img->settag(gif_left=>$x);
+    $img->settag(gif_top=>$y) if defined $y;
+  }
+  $$msg .= "replaced with the gif_left and gif_top tags";
+}
+
+my %obsolete_opts =
+  (
+   gif_each_palette=>'gif_local_map',
+   interlace       => 'gif_interlace',
+   gif_delays => 'gif_delay',
+   gif_positions => \&_fix_gif_positions,
+   gif_loop_count => 'gif_loop',
+  );
+
+sub _set_opts {
+  my ($self, $opts, $prefix, @imgs) = @_;
+
+  for my $opt (keys %$opts) {
+    my $tagname = $opt;
+    if ($obsolete_opts{$opt}) {
+      my $new = $obsolete_opts{$opt};
+      my $msg = "Obsolete option $opt ";
+      if (ref $new) {
+        $new->($opts, $opt, \$msg, @imgs);
+      }
+      else {
+        $msg .= "replaced with the $new tag ";
+        $tagname = $new;
+      }
+      $msg .= "line ".(caller(2))[2]." of file ".(caller(2))[1];
+      warn $msg if $warn_obsolete && $^W;
+    }
+    next unless $tagname =~ /^\Q$prefix/;
+    my $value = $opts->{$opt};
+    if (ref $value) {
+      if (UNIVERSAL::isa($value, "Imager::Color")) {
+        my $tag = sprintf("color(%d,%d,%d,%d)", $value->rgba);
+        for my $img (@imgs) {
+          $img->settag(name=>$tagname, value=>$tag);
+        }
+      }
+      elsif (ref($value) eq 'ARRAY') {
+        for my $i (0..$#$value) {
+          my $val = $value->[$i];
+          if (ref $val) {
+            if (UNIVERSAL::isa($val, "Imager::Color")) {
+              my $tag = sprintf("color(%d,%d,%d,%d)", $value->rgba);
+              $i < @imgs and
+                $imgs[$i]->settag(name=>$tagname, value=>$tag);
+            }
+            else {
+              $self->_set_error("Unknown reference type " . ref($value) . 
+                                " supplied in array for $opt");
+              return;
+            }
+          }
+          else {
+            $i < @imgs
+              and $imgs[$i]->settag(name=>$tagname, value=>$val);
+          }
+        }
+      }
+      else {
+        $self->_set_error("Unknown reference type " . ref($value) . 
+                          " supplied for $opt");
+        return;
+      }
+    }
+    else {
+      # set it as a tag for every image
+      for my $img (@imgs) {
+        $img->settag(name=>$tagname, value=>$value);
+      }
+    }
+  }
+
+  return 1;
+}
+
 # Write an image to file
 sub write {
   my $self = shift;
@@ -1180,6 +1289,9 @@ sub write {
             fax_fine=>1, @_);
   my $rc;
 
+  $self->_set_opts(\%input, "i_", $self)
+    or return undef;
+
   my %iolready=( tiff=>1, raw=>1, png=>1, pnm=>1, bmp=>1, jpeg=>1, tga=>1, 
                  gif=>1 ); # this will be SO MUCH BETTER once they are all in there
 
@@ -1202,6 +1314,11 @@ sub write {
   if ($iolready{$input{'type'}}) {
 
     if ($input{'type'} eq 'tiff') {
+      $self->_set_opts(\%input, "tiff_", $self)
+        or return undef;
+      $self->_set_opts(\%input, "exif_", $self)
+        or return undef;
+
       if (defined $input{class} && $input{class} eq 'fax') {
        if (!i_writetiff_wiol_faxable($self->{IMG}, $IO, $input{fax_fine})) {
          $self->{ERRSTR}='Could not write to buffer';
@@ -1214,36 +1331,50 @@ sub write {
        }
       }
     } elsif ( $input{'type'} eq 'pnm' ) {
+      $self->_set_opts(\%input, "pnm_", $self)
+        or return undef;
       if ( ! i_writeppm_wiol($self->{IMG},$IO) ) {
        $self->{ERRSTR}='unable to write pnm image';
        return undef;
       }
       $self->{DEBUG} && print "writing a pnm file\n";
     } elsif ( $input{'type'} eq 'raw' ) {
+      $self->_set_opts(\%input, "raw_", $self)
+        or return undef;
       if ( !i_writeraw_wiol($self->{IMG},$IO) ) {
        $self->{ERRSTR}='unable to write raw image';
        return undef;
       }
       $self->{DEBUG} && print "writing a raw file\n";
     } elsif ( $input{'type'} eq 'png' ) {
+      $self->_set_opts(\%input, "png_", $self)
+        or return undef;
       if ( !i_writepng_wiol($self->{IMG}, $IO) ) {
        $self->{ERRSTR}='unable to write png image';
        return undef;
       }
       $self->{DEBUG} && print "writing a png file\n";
     } elsif ( $input{'type'} eq 'jpeg' ) {
+      $self->_set_opts(\%input, "jpeg_", $self)
+        or return undef;
+      $self->_set_opts(\%input, "exif_", $self)
+        or return undef;
       if ( !i_writejpeg_wiol($self->{IMG}, $IO, $input{jpegquality})) {
         $self->{ERRSTR} = $self->_error_as_msg();
        return undef;
       }
       $self->{DEBUG} && print "writing a jpeg file\n";
     } elsif ( $input{'type'} eq 'bmp' ) {
+      $self->_set_opts(\%input, "bmp_", $self)
+        or return undef;
       if ( !i_writebmp_wiol($self->{IMG}, $IO) ) {
        $self->{ERRSTR}='unable to write bmp image';
        return undef;
       }
       $self->{DEBUG} && print "writing a bmp file\n";
     } elsif ( $input{'type'} eq 'tga' ) {
+      $self->_set_opts(\%input, "tga_", $self)
+        or return undef;
 
       if ( !i_writetga_wiol($self->{IMG}, $IO, $input{wierdpack}, $input{compress}, $input{idstring}) ) {
        $self->{ERRSTR}=$self->_error_as_msg();
@@ -1251,6 +1382,8 @@ sub write {
       }
       $self->{DEBUG} && print "writing a tga file\n";
     } elsif ( $input{'type'} eq 'gif' ) {
+      $self->_set_opts(\%input, "gif_", $self)
+        or return undef;
       # compatibility with the old interfaces
       if ($input{gifquant} eq 'lm') {
         $input{make_colors} = 'addi';
@@ -1294,10 +1427,14 @@ sub write_multi {
     $class->_set_error('Usage: Imager->write_multi({ options }, @images)');
     return 0;
   }
+  $class->_set_opts($opts, "i_", @images)
+    or return;
   my @work = map $_->{IMG}, @images;
   my ($IO, $file) = $class->_get_writer_io($opts, $opts->{'type'})
     or return undef;
   if ($opts->{'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}) {
@@ -1309,6 +1446,10 @@ sub write_multi {
     return $res;
   }
   elsif ($opts->{'type'} eq 'tiff') {
+    $class->_set_opts($opts, "tiff_", @images)
+      or return;
+    $class->_set_opts($opts, "exif_", @images)
+      or return;
     my $res;
     $opts->{fax_fine} = 1 unless exists $opts->{fax_fine};
     if ($opts->{'class'} && $opts->{'class'} eq 'fax') {
index 1d8ad77..cc24d90 100644 (file)
--- a/Imager.xs
+++ b/Imager.xs
@@ -447,6 +447,7 @@ static struct value_name make_color_names[] =
   { "none", mc_none, },
   { "webmap", mc_web_map, },
   { "addi", mc_addi, },
+  { "mediancut", mc_median_cut, },
 };
 
 static struct value_name translate_names[] =
@@ -644,6 +645,7 @@ static void cleanup_quant_opts(i_quantize *quant) {
     myfree(quant->ed_map);
 }
 
+#if 0
 /* look through the hash for options to add to opts */
 static void handle_gif_opts(i_gif_opts *opts, HV *hv)
 {
@@ -725,6 +727,8 @@ static void cleanup_gif_opts(i_gif_opts *opts) {
     myfree(opts->positions);
 }
 
+#endif
+
 /* copies the color map from the hv into the colors member of the HV */
 static void copy_colors_back(HV *hv, i_quantize *quant) {
   SV **sv;
@@ -1936,7 +1940,6 @@ i_writegif_gen(fd, ...)
       PROTOTYPE: $$@
       PREINIT:
        i_quantize quant;
-       i_gif_opts opts;
        i_img **imgs = NULL;
        int img_count;
        int i;
@@ -1949,9 +1952,7 @@ i_writegif_gen(fd, ...)
        hv = (HV *)SvRV(ST(1));
        memset(&quant, 0, sizeof(quant));
        quant.mc_size = 256;
-       memset(&opts, 0, sizeof(opts));
        handle_quant_opts(&quant, hv);
-       handle_gif_opts(&opts, hv);
        img_count = items - 2;
        RETVAL = 1;
        if (img_count < 1) {
@@ -1975,7 +1976,7 @@ i_writegif_gen(fd, ...)
             }
          }
           if (RETVAL) {
-           RETVAL = i_writegif_gen(&quant, fd, imgs, img_count, &opts);
+           RETVAL = i_writegif_gen(&quant, fd, imgs, img_count);
           }
          myfree(imgs);
           if (RETVAL) {
@@ -1985,7 +1986,6 @@ i_writegif_gen(fd, ...)
         ST(0) = sv_newmortal();
         if (RETVAL == 0) ST(0)=&PL_sv_undef;
         else sv_setiv(ST(0), (IV)RETVAL);
-       cleanup_gif_opts(&opts);
        cleanup_quant_opts(&quant);
 
 
@@ -1994,7 +1994,6 @@ i_writegif_callback(cb, maxbuffer,...)
        int maxbuffer;
       PREINIT:
        i_quantize quant;
-       i_gif_opts opts;
        i_img **imgs = NULL;
        int img_count;
        int i;
@@ -2008,9 +2007,7 @@ i_writegif_callback(cb, maxbuffer,...)
        hv = (HV *)SvRV(ST(2));
        memset(&quant, 0, sizeof(quant));
        quant.mc_size = 256;
-       memset(&opts, 0, sizeof(opts));
        handle_quant_opts(&quant, hv);
-       handle_gif_opts(&opts, hv);
        img_count = items - 3;
        RETVAL = 1;
        if (img_count < 1) {
@@ -2031,7 +2028,7 @@ i_writegif_callback(cb, maxbuffer,...)
          }
           if (RETVAL) {
            wd.sv = ST(0);
-           RETVAL = i_writegif_callback(&quant, write_callback, (char *)&wd, maxbuffer, imgs, img_count, &opts);
+           RETVAL = i_writegif_callback(&quant, write_callback, (char *)&wd, maxbuffer, imgs, img_count);
           }
          myfree(imgs);
           if (RETVAL) {
@@ -2041,7 +2038,6 @@ i_writegif_callback(cb, maxbuffer,...)
        ST(0) = sv_newmortal();
        if (RETVAL == 0) ST(0)=&PL_sv_undef;
        else sv_setiv(ST(0), (IV)RETVAL);
-       cleanup_gif_opts(&opts);
        cleanup_quant_opts(&quant);
 
 undef_int
@@ -2049,7 +2045,6 @@ i_writegif_wiol(ig, opts,...)
        Imager::IO ig
       PREINIT:
        i_quantize quant;
-       i_gif_opts opts;
        i_img **imgs = NULL;
        int img_count;
        int i;
@@ -2062,9 +2057,7 @@ i_writegif_wiol(ig, opts,...)
        hv = (HV *)SvRV(ST(1));
        memset(&quant, 0, sizeof(quant));
        quant.mc_size = 256;
-       memset(&opts, 0, sizeof(opts));
        handle_quant_opts(&quant, hv);
-       handle_gif_opts(&opts, hv);
        img_count = items - 2;
        RETVAL = 1;
        if (img_count < 1) {
@@ -2084,7 +2077,7 @@ i_writegif_wiol(ig, opts,...)
             }
          }
           if (RETVAL) {
-           RETVAL = i_writegif_wiol(ig, &quant, &opts, imgs, img_count);
+           RETVAL = i_writegif_wiol(ig, &quant, imgs, img_count);
           }
          myfree(imgs);
           if (RETVAL) {
@@ -2094,7 +2087,6 @@ i_writegif_wiol(ig, opts,...)
        ST(0) = sv_newmortal();
        if (RETVAL == 0) ST(0)=&PL_sv_undef;
        else sv_setiv(ST(0), (IV)RETVAL);
-       cleanup_gif_opts(&opts);
        cleanup_quant_opts(&quant);
 
 void
diff --git a/gif.c b/gif.c
index 2dc13c7..3830ff6 100644 (file)
--- a/gif.c
+++ b/gif.c
@@ -595,8 +595,12 @@ i_img **i_readgif_multi_low(GifFileType *GifFile, int *count) {
         i_tags_addn(&img->tags, "gif_localmap", 0, 1);
       }
       if (got_gce) {
-        if (trans_index >= 0)
+        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);
@@ -891,10 +895,8 @@ undef_int
 i_writegif(i_img *im, int fd, int max_colors, int pixdev, int fixedlen, i_color fixed[]) {
   i_color colors[256];
   i_quantize quant;
-  i_gif_opts opts;
   
   memset(&quant, 0, sizeof(quant));
-  memset(&opts, 0, sizeof(opts));
   quant.make_colors = mc_addi;
   quant.mc_colors = colors;
   quant.mc_size = 1<<max_colors;
@@ -902,7 +904,7 @@ i_writegif(i_img *im, int fd, int max_colors, int pixdev, int fixedlen, i_color
   memcpy(colors, fixed, fixedlen * sizeof(i_color));
   quant.translate = pt_perturb;
   quant.perturb = pixdev;
-  return i_writegif_gen(&quant, fd, &im, 1, &opts);
+  return i_writegif_gen(&quant, fd, &im, 1);
 }
 
 /*
@@ -920,16 +922,16 @@ undef_int
 i_writegifmc(i_img *im, int fd, int max_colors) {
   i_color colors[256];
   i_quantize quant;
-  i_gif_opts opts;
+
+/*    *(char *)0 = 1; */
   
   memset(&quant, 0, sizeof(quant));
-  memset(&opts, 0, sizeof(opts));
   quant.make_colors = mc_none; /* ignored for pt_giflib */
   quant.mc_colors = colors;
   quant.mc_size = 1 << max_colors;
   quant.mc_count = 0;
   quant.translate = pt_giflib;
-  return i_writegif_gen(&quant, fd, &im, 1, &opts);
+  return i_writegif_gen(&quant, fd, &im, 1);
 }
 
 
@@ -1090,8 +1092,8 @@ Returns non-zero on success.
 =cut
 */
 static undef_int 
-do_write(GifFileType *gf, i_gif_opts *opts, i_img *img, i_palidx *data) {
-  if (opts->interlace) {
+do_write(GifFileType *gf, int interlace, i_img *img, i_palidx *data) {
+  if (interlace) {
     int i, j;
     for (i = 0; i < 4; ++i) {
       for (j = InterlacedOffset[i]; j < img->ysize; j += InterlacedJumps[i]) {
@@ -1131,27 +1133,31 @@ Returns non-zero on success.
 
 =cut
 */
-static int do_gce(GifFileType *gf, int index, i_gif_opts *opts, int want_trans, int trans_index)
+static int do_gce(GifFileType *gf, i_img *img, int want_trans, int trans_index)
 {
   unsigned char gce[4] = {0};
   int want_gce = 0;
+  int delay;
+  int user_input;
+  int disposal_method;
+
   if (want_trans) {
     gce[0] |= 1;
     gce[3] = trans_index;
     ++want_gce;
   }
-  if (index < opts->delay_count) {
-    gce[1] = opts->delays[index] % 256;
-    gce[2] = opts->delays[index] / 256;
+  if (i_tags_get_int(&img->tags, "gif_delay", 0, &delay)) {
+    gce[1] = delay % 256;
+    gce[2] = delay / 256;
     ++want_gce;
   }
-  if (index < opts->user_input_count) {
-    if (opts->user_input_flags[index])
-      gce[0] |= 2;
+  if (i_tags_get_int(&img->tags, "gif_user_input", 0, &user_input) 
+      && user_input) {
+    gce[0] |= 2;
     ++want_gce;
   }
-  if (index < opts->disposal_count) {
-    gce[0] |= (opts->disposal[index] & 3) << 2;
+  if (i_tags_get_int(&img->tags, "gif_disposal", 0, &disposal_method)) {
+    gce[0] |= (disposal_method & 3) << 2;
     ++want_gce;
   }
   if (want_gce) {
@@ -1163,6 +1169,34 @@ static int do_gce(GifFileType *gf, int index, i_gif_opts *opts, int want_trans,
   return 1;
 }
 
+/*
+=item do_comments(gf, img)
+
+Write any comments in the image.
+
+=cut
+*/
+static int do_comments(GifFileType *gf, i_img *img) {
+  int pos = -1;
+
+  while (i_tags_find(&img->tags, "gif_comment", pos+1, &pos)) {
+    if (img->tags.tags[pos].data) {
+      if (EGifPutComment(gf, img->tags.tags[pos].data) == GIF_ERROR) {
+        return 0;
+      }
+    }
+    else {
+      char buf[50];
+      sprintf(buf, "%d", img->tags.tags[pos].idata);
+      if (EGifPutComment(gf, buf) == GIF_ERROR) {
+        return 0;
+      }
+    }
+  }
+
+  return 1;
+}
+
 /*
 =item do_ns_loop(GifFileType *gf, i_gif_opts *opts)
 
@@ -1174,7 +1208,7 @@ application extension blocks.
 
 =cut
 */
-static int do_ns_loop(GifFileType *gf, i_gif_opts *opts)
+static int do_ns_loop(GifFileType *gf, i_img *img)
 {
   /* EGifPutExtension() doesn't appear to handle application 
      extension blocks in any way
@@ -1188,7 +1222,8 @@ static int do_ns_loop(GifFileType *gf, i_gif_opts *opts)
   */
 #if 0
   /* yes this was another attempt at supporting the loop extension */
-  if (opts->loop_count) {
+  int loop_count;
+  if (i_tags_get_int(&img->tags, "gif_loop", 0, &loop_count)) {
     unsigned char nsle[12] = "NETSCAPE2.0";
     unsigned char subblock[3];
     if (EGifPutExtension(gf, 0xFF, 11, nsle) == GIF_ERROR) {
@@ -1197,8 +1232,8 @@ static int do_ns_loop(GifFileType *gf, i_gif_opts *opts)
       return 0;
     }
     subblock[0] = 1;
-    subblock[1] = opts->loop_count % 256;
-    subblock[2] = opts->loop_count / 256;
+    subblock[1] = loop_count % 256;
+    subblock[2] = loop_count / 256;
     if (EGifPutExtension(gf, 0, 3, subblock) == GIF_ERROR) {
       gif_push_error();
       i_push_error(0, "writing loop extention sub-block");
@@ -1215,20 +1250,21 @@ static int do_ns_loop(GifFileType *gf, i_gif_opts *opts)
 }
 
 /*
-=item make_gif_map(i_quantize *quant, i_gif_opts *opts, int want_trans)
+=item make_gif_map(i_quantize *quant, int want_trans)
 
 Create a giflib color map object from an Imager color map.
 
 =cut
 */
 
-static ColorMapObject *make_gif_map(i_quantize *quant, i_gif_opts *opts,
-                                   int want_trans) {
+static ColorMapObject *make_gif_map(i_quantize *quant, i_img *img, 
+                                    int want_trans) {
   GifColorType colors[256];
   int i;
   int size = quant->mc_count;
   int map_size;
   ColorMapObject *map;
+  i_color trans;
 
   for (i = 0; i < quant->mc_count; ++i) {
     colors[i].Red = quant->mc_colors[i].rgb.r;
@@ -1236,9 +1272,11 @@ static ColorMapObject *make_gif_map(i_quantize *quant, i_gif_opts *opts,
     colors[i].Blue = quant->mc_colors[i].rgb.b;
   }
   if (want_trans) {
-    colors[size].Red = opts->tran_color.rgb.r;
-    colors[size].Green = opts->tran_color.rgb.g;
-    colors[size].Blue = opts->tran_color.rgb.b;
+    if (!i_tags_get_color(&img->tags, "gif_trans_color", 0, &trans))
+      trans.rgb.r = trans.rgb.g = trans.rgb.b = 0;
+    colors[size].Red = trans.rgb.r;
+    colors[size].Green = trans.rgb.g;
+    colors[size].Blue = trans.rgb.b;
     ++size;
   }
   map_size = 1;
@@ -1263,7 +1301,7 @@ static ColorMapObject *make_gif_map(i_quantize *quant, i_gif_opts *opts,
 }
 
 /*
-=item gif_set_version(i_quantize *quant, i_gif_opts *opts)
+=item gif_set_version(i_quantize *quant, i_img *imgs, int count)
 
 We need to call EGifSetGifVersion() before opening the file - put that
 common code here.
@@ -1285,11 +1323,12 @@ with readers.
 =cut
 */
 
-static void gif_set_version(i_quantize *quant, i_gif_opts *opts) {
+static void gif_set_version(i_quantize *quant, i_img **imgs, int count) {
   /* the following crashed giflib
      the EGifSetGifVersion() is seriously borked in giflib
      it's less borked in the ungiflib beta, but we don't have a mechanism
      to distinguish them
+     Needs to be updated to support tags.
      if (opts->delay_count
      || opts->user_input_count
      || opts->disposal_count
@@ -1325,10 +1364,11 @@ if they do it builds that palette.
 A possible improvement might be to eliminate unused colors in the
 images palettes.
 
-=cut */
+=cut
+*/
 static int
-has_common_palette(i_img **imgs, int count, i_quantize *quant, int want_trans,
-                   i_gif_opts *opts) {
+has_common_palette(i_img **imgs, int count, i_quantize *quant, 
+                   int want_trans) {
   int size = quant->mc_count;
   int i, j;
   int imgn;
@@ -1338,10 +1378,16 @@ has_common_palette(i_img **imgs, int count, i_quantize *quant, int want_trans,
   /* we try to build a common palette here, if we can manage that, then
      that's the palette we use */
   for (imgn = 0; imgn < count; ++imgn) {
+    int eliminate_unused;
     if (imgs[imgn]->type != i_palette_type)
       return 0;
 
-    if (opts->eliminate_unused) {
+    if (!i_tags_get_int(&imgs[imgn]->tags, "gif_eliminate_unused", 0, 
+                        &eliminate_unused)) {
+      eliminate_unused = 1;
+    }
+
+    if (eliminate_unused) {
       i_palidx *line = mymalloc(sizeof(i_palidx) * imgs[imgn]->xsize);
       int x, y;
       memset(used, 0, sizeof(used));
@@ -1420,8 +1466,7 @@ Returns non-zero on success.
 */
 
 static undef_int
-i_writegif_low(i_quantize *quant, GifFileType *gf, i_img **imgs, int count,
-              i_gif_opts *opts) {
+i_writegif_low(i_quantize *quant, GifFileType *gf, i_img **imgs, int count) {
   unsigned char *result;
   int color_bits;
   ColorMapObject *map;
@@ -1429,121 +1474,270 @@ i_writegif_low(i_quantize *quant, GifFileType *gf, i_img **imgs, int count,
   int imgn, orig_count, orig_size;
   int posx, posy;
   int trans_index;
+  i_mempool mp;
+  int *localmaps;
+  int anylocal;
+  i_img **glob_imgs; /* images that will use the global color map */
+  int glob_img_count;
+  i_color *orig_colors = quant->mc_colors;
+  i_color *glob_colors = NULL;
+  int glob_color_count;
+  int glob_map_size;
+  int glob_want_trans;
+  int glob_paletted; /* the global map was made from the image palettes */
+  int colors_paletted;
+  int want_trans;
+  int interlace;
+  int gif_background;
+
+  mm_log((1, "i_writegif_low(quant %p, gf  %p, imgs %p, count %d)\n", 
+         quant, gf, imgs, count));
+  
+  /* *((char *)0) = 1; */ /* used to break into the debugger */
+  
+  if (count <= 0) {
+    i_push_error(0, "No images provided to write");
+    return 0; /* what are you smoking? */
+  }
 
-  mm_log((1, "i_writegif_low(quant %p, gf  %p, imgs %p, count %d, opts %p)\n", 
-         quant, gf, imgs, count, opts));
+  i_mempool_init(&mp);
 
-  /**((char *)0) = 1;*/
   /* sanity is nice */
   if (quant->mc_size > 256) 
     quant->mc_size = 256;
   if (quant->mc_count > quant->mc_size)
     quant->mc_count = quant->mc_size;
 
+  if (!i_tags_get_int(&imgs[0]->tags, "gif_screen_width", 0, &scrw))
+    scrw = 0;
+  if (!i_tags_get_int(&imgs[0]->tags, "gif_screen_height", 0, &scrw))
+    scrw = 0;
+
+  anylocal = 0;
+  localmaps = i_mempool_alloc(&mp, sizeof(int) * count);
+  glob_imgs = i_mempool_alloc(&mp, sizeof(i_img *) * count);
+  glob_img_count = 0;
+  glob_want_trans = 0;
   for (imgn = 0; imgn < count; ++imgn) {
-    if (imgn < opts->position_count) {
-      if (imgs[imgn]->xsize + opts->positions[imgn].x > scrw)
-       scrw = imgs[imgn]->xsize + opts->positions[imgn].x;
-      if (imgs[imgn]->ysize + opts->positions[imgn].y > scrh)
-       scrh = imgs[imgn]->ysize + opts->positions[imgn].y;
-    }
+    posx = posy = 0;
+    i_tags_get_int(&imgs[imgn]->tags, "gif_left", 0, &posx);
+    i_tags_get_int(&imgs[imgn]->tags, "gif_top", 0, &posy);
+    if (imgs[imgn]->xsize + posx > scrw)
+      scrw = imgs[imgn]->xsize + posx;
+    if (imgs[imgn]->ysize + posy > scrh)
+      scrh = imgs[imgn]->ysize + posy;
+    if (!i_tags_get_int(&imgs[imgn]->tags, "gif_local_map", 0, localmaps+imgn))
+      localmaps[imgn] = 0;
+    if (localmaps[imgn])
+      anylocal = 1;
     else {
-      if (imgs[imgn]->xsize > scrw)
-       scrw = imgs[imgn]->xsize;
-      if (imgs[imgn]->ysize > scrh)
-       scrh = imgs[imgn]->ysize;
+      if (imgs[imgn]->channels == 4) {
+        glob_want_trans = 1;
+      }
+      glob_imgs[glob_img_count++] = imgs[imgn];
     }
   }
-
-  if (count <= 0) {
-    i_push_error(0, "No images provided to write");
-    return 0; /* what are you smoking? */
-  }
+  glob_want_trans = glob_want_trans && quant->transp != tr_none ;
 
   orig_count = quant->mc_count;
   orig_size = quant->mc_size;
 
-  if (opts->each_palette) {
-    int want_trans = quant->transp != tr_none 
-      && imgs[0]->channels == 4;
-
-    /* if the caller gives us too many colours we can't do transparency */
-    if (want_trans && quant->mc_count == 256)
-      want_trans = 0;
-    /* if they want transparency but give us a big size, make it smaller
-       to give room for a transparency colour */
-    if (want_trans && quant->mc_size == 256)
+  if (glob_img_count) {
+    /* this is ugly */
+    glob_colors = i_mempool_alloc(&mp, sizeof(i_color) * quant->mc_size);
+    quant->mc_colors = glob_colors;
+    memcpy(glob_colors, orig_colors, sizeof(i_color) * quant->mc_count);
+    /* we have some images that want to use the global map */
+    if (glob_want_trans && quant->mc_count == 256) {
+      mm_log((2, "  disabling transparency for global map - no space\n"));
+      glob_want_trans = 0;
+    }
+    if (glob_want_trans && quant->mc_size == 256) {
+      mm_log((2, "  reserving color for transparency\n"));
       --quant->mc_size;
-
-    /* we always generate a global palette - this lets systems with a 
-       broken giflib work */
-    if (has_common_palette(imgs, 1, quant, want_trans, opts)) {
-      result = quant_paletted(quant, imgs[0]);
     }
-    else {
-      quant_makemap(quant, imgs, 1);
-      result = quant_translate(quant, imgs[0]);
+    if (has_common_palette(glob_imgs, glob_img_count, quant, want_trans)) {
+      glob_paletted = 1;
     }
-    if (want_trans) {
-      trans_index = quant->mc_count;
-      quant_transparent(quant, result, imgs[0], trans_index);
+    else {
+      glob_paletted = 0;
+      quant_makemap(quant, glob_imgs, glob_img_count);
     }
+    glob_color_count = quant->mc_count;
+    quant->mc_colors = orig_colors;
+  }
 
-    if ((map = make_gif_map(quant, opts, want_trans)) == NULL) {
-      myfree(result);
-      EGifCloseFile(gf);
-      mm_log((1, "Error in MakeMapObject."));
-      return 0;
+  /* use the global map if we have one, otherwise use the local map */
+  gif_background = 0;
+  if (glob_colors) {
+    quant->mc_colors = glob_colors;
+    quant->mc_count = glob_color_count;
+    want_trans = glob_want_trans && imgs[0]->channels == 4;
+
+    if (!i_tags_get_int(&imgs[0]->tags, "gif_background", 0, &gif_background))
+      gif_background = 0;
+    if (gif_background < 0)
+      gif_background = 0;
+    if (gif_background >= glob_color_count)
+      gif_background = 0;
+  }
+  else {
+    want_trans = quant->transp != tr_none && imgs[0]->channels == 4;
+    if (has_common_palette(imgs, 1, quant, want_trans)) {
+      colors_paletted = 1;
     }
-
-    color_bits = 1;
-    while (quant->mc_size > (1 << color_bits))
+    else {
+      colors_paletted = 0;
+      quant_makemap(quant, imgs, 1);
+    }
+  }
+  if ((map = make_gif_map(quant, imgs[0], want_trans)) == NULL) {
+    i_mempool_destroy(&mp);
+    quant->mc_colors = orig_colors;
+    EGifCloseFile(gf);
+    mm_log((1, "Error in MakeMapObject"));
+    return 0;
+  }
+  color_bits = 1;
+  if (anylocal) {
+    /* since we don't know how big some the local palettes could be
+       we need to base the bits on the maximum number of colors */
+    while (orig_size > (1 << color_bits))
+      ++color_bits;
+  }
+  else {
+    int count = quant->mc_count;
+    if (want_trans)
+      ++count;
+    while (count > (1 << color_bits))
       ++color_bits;
+  }
   
-    if (EGifPutScreenDesc(gf, scrw, scrh, color_bits, 0, map) == GIF_ERROR) {
-      gif_push_error();
-      i_push_error(0, "Could not save screen descriptor");
-      FreeMapObject(map);
-      myfree(result);
-      EGifCloseFile(gf);
-      mm_log((1, "Error in EGifPutScreenDesc."));
-      return 0;
-    }
+  if (EGifPutScreenDesc(gf, scrw, scrh, color_bits, 
+                        gif_background, map) == GIF_ERROR) {
+    i_mempool_destroy(&mp);
+    quant->mc_colors = orig_colors;
+    gif_push_error();
+    i_push_error(0, "Could not save screen descriptor");
     FreeMapObject(map);
+    myfree(result);
+    EGifCloseFile(gf);
+    mm_log((1, "Error in EGifPutScreenDesc."));
+    return 0;
+  }
+  FreeMapObject(map);
 
-    if (!do_ns_loop(gf, opts))
-      return 0;
+  if (!i_tags_get_int(&imgs[0]->tags, "gif_left", 0, &posx))
+    posx = 0;
+  if (!i_tags_get_int(&imgs[0]->tags, "gif_top", 0, &posy))
+    posy = 0;
 
-    if (!do_gce(gf, 0, opts, want_trans, trans_index)) {
-      myfree(result);
-      EGifCloseFile(gf);
-      return 0;
-    }
-    if (opts->position_count) {
-      posx = opts->positions[0].x;
-      posy = opts->positions[0].y;
-    }
-    else
-      posx = posy = 0;
-    if (EGifPutImageDesc(gf, posx, posy, imgs[0]->xsize, imgs[0]->ysize, 
-                        opts->interlace, NULL) == GIF_ERROR) {
-      gif_push_error();
-      i_push_error(0, "Could not save image descriptor");
-      EGifCloseFile(gf);
-      mm_log((1, "Error in EGifPutImageDesc."));
-      return 0;
+  if (!localmaps[0]) {
+    map = NULL;
+    colors_paletted = glob_paletted;
+  }
+  else {
+    /* if this image has a global map the colors in quant don't
+       belong to this image, so build a palette */
+    if (glob_colors) {
+      /* generate the local map for this image */
+      quant->mc_colors = orig_colors;
+      quant->mc_size = orig_size;
+      quant->mc_count = orig_count;
+      want_trans = quant->transp != tr_none && imgs[0]->channels == 4;
+
+      /* if the caller gives us too many colours we can't do transparency */
+      if (want_trans && quant->mc_count == 256)
+        want_trans = 0;
+      /* if they want transparency but give us a big size, make it smaller
+         to give room for a transparency colour */
+      if (want_trans && quant->mc_size == 256)
+        --quant->mc_size;
+      if (has_common_palette(imgs, 1, quant, want_trans)) {
+        colors_paletted = 1;
+      }
+      else {
+        colors_paletted = 0;
+        quant_makemap(quant, imgs, 1);
+      }
+      if ((map = make_gif_map(quant, imgs[0], want_trans)) == NULL) {
+        i_mempool_destroy(&mp);
+        EGifCloseFile(gf);
+        mm_log((1, "Error in MakeMapObject"));
+        return 0;
+      }
     }
-    if (!do_write(gf, opts, imgs[0], result)) {
-      EGifCloseFile(gf);
-      myfree(result);
-      return 0;
+    else {
+      /* the map we wrote was the map for this image - don't set the local 
+         map */
+      map = NULL;
     }
+  }
+
+  if (colors_paletted)
+    result = quant_paletted(quant, imgs[0]);
+  else
+    result = quant_translate(quant, imgs[0]);
+  if (want_trans) {
+    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);
-    for (imgn = 1; imgn < count; ++imgn) {
+    EGifCloseFile(gf);
+    return 0;
+  }
+
+  if (!do_comments(gf, imgs[0])) {
+    i_mempool_destroy(&mp);
+    quant->mc_colors = orig_colors;
+    myfree(result);
+    EGifCloseFile(gf);
+    return 0;
+  }
+
+  if (!i_tags_get_int(&imgs[0]->tags, "gif_interlace", 0, &interlace))
+    interlace = 0;
+  if (EGifPutImageDesc(gf, posx, posy, imgs[0]->xsize, imgs[0]->ysize, 
+                       interlace, map) == GIF_ERROR) {
+    i_mempool_destroy(&mp);
+    quant->mc_colors = orig_colors;
+    gif_push_error();
+    i_push_error(0, "Could not save image descriptor");
+    EGifCloseFile(gf);
+    mm_log((1, "Error in EGifPutImageDesc."));
+    return 0;
+  }
+  if (map)
+    FreeMapObject(map);
+
+  if (!do_write(gf, interlace, imgs[0], result)) {
+    i_mempool_destroy(&mp);
+    quant->mc_colors = orig_colors;
+    EGifCloseFile(gf);
+    myfree(result);
+    return 0;
+  }
+  myfree(result);
+
+  /* that first awful image is out of the way, do the rest */
+  for (imgn = 1; imgn < count; ++imgn) {
+    if (localmaps[imgn]) {
+      quant->mc_colors = orig_colors;
       quant->mc_count = orig_count;
       quant->mc_size = orig_size;
+
       want_trans = quant->transp != tr_none 
-       && imgs[0]->channels == 4;
+       && imgs[imgn]->channels == 4;
       /* if the caller gives us too many colours we can't do transparency */
       if (want_trans && quant->mc_count == 256)
        want_trans = 0;
@@ -1552,7 +1746,7 @@ i_writegif_low(i_quantize *quant, GifFileType *gf, i_img **imgs, int count,
       if (want_trans && quant->mc_size == 256)
        --quant->mc_size;
 
-      if (has_common_palette(imgs+imgn, 1, quant, want_trans, opts)) {
+      if (has_common_palette(imgs+imgn, 1, quant, want_trans)) {
         result = quant_paletted(quant, imgs[imgn]);
       }
       else {
@@ -1564,173 +1758,88 @@ i_writegif_low(i_quantize *quant, GifFileType *gf, i_img **imgs, int count,
         trans_index = quant->mc_count;
       }
 
-      if (!do_gce(gf, imgn, opts, want_trans, quant->mc_count)) {
-       myfree(result);
-       EGifCloseFile(gf);
-       return 0;
-      }
-      if ((map = make_gif_map(quant, opts, want_trans)) == NULL) {
-       myfree(result);
-       EGifCloseFile(gf);
-       mm_log((1, "Error in MakeMapObject."));
-       return 0;
-      }
-      if (imgn < opts->position_count) {
-       posx = opts->positions[imgn].x;
-       posy = opts->positions[imgn].y;
-      }
-      else
-       posx = posy = 0;
-      if (EGifPutImageDesc(gf, posx, posy, imgs[imgn]->xsize, 
-                          imgs[imgn]->ysize, opts->interlace, 
-                          map) == GIF_ERROR) {
-       gif_push_error();
-       i_push_error(0, "Could not save image descriptor");
-       myfree(result);
-       FreeMapObject(map);
-       EGifCloseFile(gf);
-       mm_log((1, "Error in EGifPutImageDesc."));
-       return 0;
-      }
-      FreeMapObject(map);
-      
-      if (!do_write(gf, opts, imgs[imgn], result)) {
-       EGifCloseFile(gf);
-       myfree(result);
-       return 0;
+      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;
       }
-      myfree(result);
-    }
-  }
-  else {
-    int want_trans;
-    int do_quant_paletted = 0;
-
-    /* get a palette entry for the transparency iff we have an image
-       with an alpha channel */
-    want_trans = 0;
-    for (imgn = 0; imgn < count; ++imgn) {
-      if (imgs[imgn]->channels == 4) {
-       ++want_trans;
-       break;
-      }
-    }
-    want_trans = want_trans && quant->transp != tr_none 
-      && quant->mc_count < 256;
-    if (want_trans && quant->mc_size == 256)
-      --quant->mc_size;
-
-    /* handle the first image separately - since we allow giflib
-       conversion and giflib doesn't give us a separate function to build
-       the colormap. */
-     
-    /* produce a colour map */
-    if (has_common_palette(imgs, count, quant, want_trans, opts)) {
-      result = quant_paletted(quant, imgs[0]);
-      ++do_quant_paletted;
     }
     else {
-      quant_makemap(quant, imgs, count);
-      result = quant_translate(quant, imgs[0]);
+      quant->mc_colors = glob_colors;
+      quant->mc_count = glob_color_count;
+      if (glob_paletted)
+        result = quant_paletted(quant, imgs[imgn]);
+      else
+        result = quant_translate(quant, imgs[imgn]);
+      want_trans = glob_want_trans && imgs[imgn]->channels == 4;
+      if (want_trans) {
+        quant_transparent(quant, result, imgs[imgn], quant->mc_count);
+        trans_index = quant->mc_count;
+      }
+      map = NULL;
     }
 
-    if ((map = make_gif_map(quant, opts, want_trans)) == NULL) {
+    if (!do_gce(gf, imgs[imgn], want_trans, trans_index)) {
+      i_mempool_destroy(&mp);
+      quant->mc_colors = orig_colors;
       myfree(result);
       EGifCloseFile(gf);
-      mm_log((1, "Error in MakeMapObject"));
       return 0;
     }
-    color_bits = 1;
-    while (quant->mc_count > (1 << color_bits))
-      ++color_bits;
 
-    if (EGifPutScreenDesc(gf, scrw, scrh, color_bits, 0, map) == GIF_ERROR) {
-      gif_push_error();
-      i_push_error(0, "Could not save screen descriptor");
-      FreeMapObject(map);
+    if (!do_comments(gf, imgs[imgn])) {
+      i_mempool_destroy(&mp);
+      quant->mc_colors = orig_colors;
       myfree(result);
       EGifCloseFile(gf);
-      mm_log((1, "Error in EGifPutScreenDesc."));
       return 0;
     }
-    FreeMapObject(map);
-
-    if (!do_ns_loop(gf, opts))
-      return 0;
 
-    if (!do_gce(gf, 0, opts, want_trans, quant->mc_count)) {
-      myfree(result);
-      EGifCloseFile(gf);
-      return 0;
-    }
-    if (opts->position_count) {
-      posx = opts->positions[0].x;
-      posy = opts->positions[0].y;
-    }
-    else
-      posx = posy = 0;
-    if (EGifPutImageDesc(gf, posx, posy, imgs[0]->xsize, imgs[0]->ysize, 
-                        opts->interlace, NULL) == GIF_ERROR) {
+    if (!i_tags_get_int(&imgs[imgn]->tags, "gif_left", 0, &posx))
+      posx = 0;
+    if (!i_tags_get_int(&imgs[imgn]->tags, "gif_top", 0, &posy))
+      posy = 0;
+
+    if (!i_tags_get_int(&imgs[imgn]->tags, "gif_interlace", 0, &interlace))
+      interlace = 0;
+    if (EGifPutImageDesc(gf, posx, posy, imgs[imgn]->xsize, 
+                         imgs[imgn]->ysize, interlace, map) == GIF_ERROR) {
+      i_mempool_destroy(&mp);
+      quant->mc_colors = orig_colors;
       gif_push_error();
       i_push_error(0, "Could not save image descriptor");
+      myfree(result);
+      if (map)
+        FreeMapObject(map);
       EGifCloseFile(gf);
       mm_log((1, "Error in EGifPutImageDesc."));
       return 0;
     }
-    if (want_trans && imgs[0]->channels == 4) 
-      quant_transparent(quant, result, imgs[0], quant->mc_count);
-
-    if (!do_write(gf, opts, imgs[0], result)) {
+    if (map)
+      FreeMapObject(map);
+    
+    if (!do_write(gf, interlace, imgs[imgn], result)) {
+      i_mempool_destroy(&mp);
+      quant->mc_colors = orig_colors;
       EGifCloseFile(gf);
       myfree(result);
       return 0;
     }
     myfree(result);
-
-    for (imgn = 1; imgn < count; ++imgn) {
-      int local_trans;
-      if (do_quant_paletted)
-        result = quant_paletted(quant, imgs[imgn]);
-      else
-        result = quant_translate(quant, imgs[imgn]);
-      local_trans = want_trans && imgs[imgn]->channels == 4;
-      if (local_trans)
-       quant_transparent(quant, result, imgs[imgn], quant->mc_count);
-      if (!do_gce(gf, imgn, opts, local_trans, quant->mc_count)) {
-       myfree(result);
-       EGifCloseFile(gf);
-       return 0;
-      }
-      if (imgn < opts->position_count) {
-       posx = opts->positions[imgn].x;
-       posy = opts->positions[imgn].y;
-      }
-      else
-       posx = posy = 0;
-      if (EGifPutImageDesc(gf, posx, posy, 
-                          imgs[imgn]->xsize, imgs[imgn]->ysize, 
-                          opts->interlace, NULL) == GIF_ERROR) {
-       gif_push_error();
-       i_push_error(0, "Could not save image descriptor");
-       myfree(result);
-       EGifCloseFile(gf);
-       mm_log((1, "Error in EGifPutImageDesc."));
-       return 0;
-      }
-      if (!do_write(gf, opts, imgs[imgn], result)) {
-       EGifCloseFile(gf);
-       myfree(result);
-       return 0;
-      }
-      myfree(result);
-    }
   }
+
   if (EGifCloseFile(gf) == GIF_ERROR) {
+    i_mempool_destroy(&mp);
     gif_push_error();
     i_push_error(0, "Could not close GIF file");
     mm_log((1, "Error in EGifCloseFile\n"));
     return 0;
   }
+  i_mempool_destroy(&mp);
+  quant->mc_colors = orig_colors;
 
   return 1;
 }
@@ -1750,15 +1859,14 @@ Returns non-zero on success.
 */
 
 undef_int
-i_writegif_gen(i_quantize *quant, int fd, i_img **imgs, int count, 
-              i_gif_opts *opts) {
+i_writegif_gen(i_quantize *quant, int fd, i_img **imgs, int count) {
   GifFileType *gf;
 
   i_clear_error();
-  mm_log((1, "i_writegif_gen(quant %p, fd %d, imgs %p, count %d, opts %p)\n", 
-         quant, fd, imgs, count, opts));
+  mm_log((1, "i_writegif_gen(quant %p, fd %d, imgs %p, count %d)\n", 
+         quant, fd, imgs, count));
 
-  gif_set_version(quant, opts);
+  gif_set_version(quant, imgs, count);
 
   if ((gf = EGifOpenFileHandle(fd)) == NULL) {
     gif_push_error();
@@ -1767,7 +1875,7 @@ i_writegif_gen(i_quantize *quant, int fd, i_img **imgs, int count,
     return 0;
   }
 
-  return i_writegif_low(quant, gf, imgs, count, opts);
+  return i_writegif_low(quant, gf, imgs, count);
 }
 
 #if IM_GIFMAJOR >= 4
@@ -1802,7 +1910,7 @@ Returns non-zero on success.
 
 undef_int
 i_writegif_callback(i_quantize *quant, i_write_callback_t cb, char *userdata,
-                   int maxlength, i_img **imgs, int count, i_gif_opts *opts)
+                   int maxlength, i_img **imgs, int count)
 {
 #if IM_GIFMAJOR >= 4
   GifFileType *gf;
@@ -1811,8 +1919,8 @@ i_writegif_callback(i_quantize *quant, i_write_callback_t cb, char *userdata,
 
   i_clear_error();
 
-  mm_log((1, "i_writegif_callback(quant %p, i_write_callback_t %p, userdata $p, maxlength %d, imgs %p, count %d, opts %p)\n", 
-         quant, cb, userdata, maxlength, imgs, count, opts));
+  mm_log((1, "i_writegif_callback(quant %p, i_write_callback_t %p, userdata $p, maxlength %d, imgs %p, count %d)\n", 
+         quant, cb, userdata, maxlength, imgs, count));
   
   if ((gf = EGifOpen(gwd, &gif_writer_callback)) == NULL) {
     gif_push_error();
@@ -1822,7 +1930,7 @@ i_writegif_callback(i_quantize *quant, i_write_callback_t cb, char *userdata,
     return 0;
   }
 
-  result = i_writegif_low(quant, gf, imgs, count, opts);
+  result = i_writegif_low(quant, gf, imgs, count);
   return free_gen_write_data(gwd, result);
 #else
   i_clear_error();
@@ -1849,7 +1957,7 @@ io_glue_write_cb(GifFileType *gft, const GifByteType *data, int length) {
 =cut
 */
 undef_int
-i_writegif_wiol(io_glue *ig, i_quantize *quant, i_gif_opts *opts, i_img **imgs,
+i_writegif_wiol(io_glue *ig, i_quantize *quant, i_img **imgs,
                 int count) {
   io_glue_commit_types(ig);
 
@@ -1861,7 +1969,7 @@ i_writegif_wiol(io_glue *ig, i_quantize *quant, i_gif_opts *opts, i_img **imgs,
     }
     /* 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, opts);
+    return i_writegif_gen(quant, fd, imgs, count);
   }
   else {
 #if IM_GIFMAJOR >= 4
@@ -1870,7 +1978,7 @@ i_writegif_wiol(io_glue *ig, i_quantize *quant, i_gif_opts *opts, i_img **imgs,
 
     i_clear_error();
 
-    gif_set_version(quant, opts);
+    gif_set_version(quant, imgs, count);
 
     if ((GifFile = EGifOpen((void *)ig, io_glue_write_cb )) == NULL) {
       gif_push_error();
@@ -1879,7 +1987,7 @@ i_writegif_wiol(io_glue *ig, i_quantize *quant, i_gif_opts *opts, i_img **imgs,
       return 0;
     }
     
-    result = i_writegif_low(quant, GifFile, imgs, count, opts);
+    result = i_writegif_low(quant, GifFile, imgs, count);
     
     ig->closecb(ig);
 
@@ -1902,7 +2010,8 @@ returns a string that describes that error.
 The returned pointer points to a static buffer, either from a literal
 C string or a static buffer.
 
-=cut */
+=cut
+*/
 
 static char const *gif_error_msg(int code) {
   static char msg[80];
diff --git a/image.h b/image.h
index 7025b2d..83cb50c 100644 (file)
--- a/image.h
+++ b/image.h
@@ -391,6 +391,7 @@ typedef enum i_make_colors_tag {
   mc_none, /* user supplied colour map only */
   mc_web_map, /* Use the 216 colour web colour map */
   mc_addi, /* Addi's algorithm */
+  mc_median_cut, /* median cut - similar to giflib, hopefully */
   mc_mask = 0xFF /* (mask for generator) */
 } i_make_colors;
 
@@ -560,9 +561,9 @@ extern i_img **i_readgif_multi_wiol(io_glue *ig, int *count);
 undef_int i_writegif(i_img *im,int fd,int colors,int pixdev,int fixedlen,i_color fixed[]);
 undef_int i_writegifmc(i_img *im,int fd,int colors);
 undef_int i_writegifex(i_img *im,int fd);
-undef_int i_writegif_gen(i_quantize *quant, int fd, i_img **imgs, int count, i_gif_opts *opts);
-undef_int i_writegif_callback(i_quantize *quant, i_write_callback_t cb, char *userdata, int maxbuffer, i_img **imgs, int count, i_gif_opts *opts);
-undef_int i_writegif_wiol(io_glue *ig, i_quantize *quant, i_gif_opts *opts, 
+undef_int i_writegif_gen(i_quantize *quant, int fd, i_img **imgs, int count);
+undef_int i_writegif_callback(i_quantize *quant, i_write_callback_t cb, char *userdata, int maxbuffer, i_img **imgs, int count);
+undef_int i_writegif_wiol(io_glue *ig, i_quantize *quant, 
                           i_img **imgs, int count);
 void i_qdist(i_img *im);
 
@@ -724,22 +725,29 @@ extern int i_failed(int code, char const *msg);
 
 /* image tag processing */
 extern void i_tags_new(i_img_tags *tags);
-extern int i_tags_addn(i_img_tags *tags, char *name, int code, int idata);
-extern int i_tags_add(i_img_tags *tags, char *name, int code, char *data, 
-                      int size, int idata);
+extern int i_tags_addn(i_img_tags *tags, char const *name, int code, 
+                       int idata);
+extern int i_tags_add(i_img_tags *tags, char const *name, int code, 
+                      char const *data, int size, int idata);
 extern void i_tags_destroy(i_img_tags *tags);
-extern int i_tags_find(i_img_tags *tags, char *name, int start, int *entry);
+extern int i_tags_find(i_img_tags *tags, char const *name, int start, 
+                       int *entry);
 extern int i_tags_findn(i_img_tags *tags, int code, int start, int *entry);
 extern int i_tags_delete(i_img_tags *tags, int entry);
-extern int i_tags_delbyname(i_img_tags *tags, char *name);
+extern int i_tags_delbyname(i_img_tags *tags, char const *name);
 extern int i_tags_delbycode(i_img_tags *tags, int code);
-extern int i_tags_get_float(i_img_tags *tags, char *name, int code, 
+extern int i_tags_get_float(i_img_tags *tags, char const *name, int code, 
                            double *value);
-extern int i_tags_set_float(i_img_tags *tags, char *name, int code, 
+extern int i_tags_set_float(i_img_tags *tags, char const *name, int code, 
                            double value);
-extern int i_tags_get_int(i_img_tags *tags, char *name, int code, int *value);
-extern int i_tags_get_string(i_img_tags *tags, char *name, int code, 
+extern int i_tags_get_int(i_img_tags *tags, char const *name, int code, 
+                          int *value);
+extern int i_tags_get_string(i_img_tags *tags, char const *name, int code, 
                             char *value, size_t value_size);
+extern int i_tags_get_color(i_img_tags *tags, char const *name, int code, 
+                            i_color *value);
+extern int i_tags_set_color(i_img_tags *tags, char const *name, int code, 
+                            i_color const *value);
 extern void i_tags_print(i_img_tags *tags);
 
 #endif
diff --git a/io.c b/io.c
index 1936ee8..73fbd3b 100644 (file)
--- a/io.c
+++ b/io.c
@@ -203,6 +203,8 @@ myfree_file_line(void *p, char *file, int line) {
     malloc_pointers[i].ptr = NULL;
     match++;
   }
+
+  mm_log((1, "myfree_file_line: freeing address %p (real %p)\n", pp, pp-UNDRRNVAL));
   
   if (match != 1) {
     mm_log((1, "myfree_file_line: INCONSISTENT REFCOUNT %d at %s (%i)\n", match, file, line));
@@ -210,7 +212,6 @@ myfree_file_line(void *p, char *file, int line) {
                exit(255);
   }
   
-  mm_log((1, "myfree_file_line: freeing address %p (real %p)\n", pp, pp-UNDRRNVAL));
   
   free(pp-UNDRRNVAL);
 }
index 88067f3..25a84ec 100644 (file)
@@ -149,6 +149,23 @@ Return either a valid Imager file type, or undef.
 The different image formats can write different image type, and some have
 different options to control how the images are written.
 
+When you call C<write()> or C<write_multi()> with an option that has
+the same name as a tag for the image format you're writing, then the
+value supplied to that option will be used to set the corresponding
+tag in the image.  Depending on the image format, these values will be
+used when writing the image.
+
+This replaces the previous options that were used when writing GIF
+images.  Currently if you use an obsolete option, it will be converted
+to the equivalent tag and Imager will produced a warning.  You can
+suppress these warnings by calling the C<Imager::init()> function with
+the C<warn_obsolete> option set to false:
+
+  Imager::init(warn_obsolete=>0);
+
+At some point in the future these obsolete options will no longer be
+supported.
+
 =head2 PNM (Portable aNy Map)
 
 Imager can write PGM (Portable Gray Map) and PPM (Portable PixMaps)
@@ -182,75 +199,9 @@ PNM does not support the spatial resolution tags.
 
 =head2 GIF (Graphics Interchange Format)
 
-You can supply many different options when writing to a GIF file, and
-you can write a multi-image GIF file, eg. for animation, with the
-C<write_multi()> method.
-
-These options can be specified when calling write_multi() or when
-writing a single image with the C<gifquant> option set to 'gen'
-
-Note that some viewers will ignore some of these options
-(C<gif_user_input> in particular).
-
-=over
-
-=item gif_each_palette
-
-Each image in the gif file has it's own palette if this is non-zero.
-All but the first image has a local colour table (the first uses the
-global colour table.
-
-=item interlace
-
-The images are written interlaced if this is non-zero.
-
-=item gif_delays
-
-A reference to an array containing the delays between images, in 1/100
-seconds.
-
-If you want the same delay for every frame you can simply set this to
-the delay in 1/100 seconds.
-
-=item gif_user_input
-
-A reference to an array contains user input flags.  If the given flag
-is non-zero the image viewer should wait for input before displaying
-the next image.
-
-=item gif_disposal
-
-A reference to an array of image disposal methods.  These define what
-should be done to the image before displaying the next one.  These are
-integers, where 0 means unspecified, 1 means the image should be left
-in place, 2 means restore to background colour and 3 means restore to
-the previous value.
-
-=item gif_tran_color
-
-A reference to an Imager::Color object, which is the colour to use for
-the palette entry used to represent transparency in the palette.  You
-need to set the transp option (see L<Quantization options>) for this
-value to be used.
-
-=item gif_positions
-
-A reference to an array of references to arrays which represent screen
-positions for each image.
-
-=item gif_loop_count
-
-If this is non-zero the Netscape loop extension block is generated,
-which makes the animation of the images repeat.
-
-This is currently unimplemented due to some limitations in giflib.
-
-=item gif_eliminate_unused
-
-If this is true, when you write a paletted image any unused colors
-will be eliminated from its palette.  This is set by default.
-
-=back
+When writing one of more GIF images you can use the same
+L<Quantization Options|Imager::ImageTypes> as you can when converting
+an RGB image into a paletted image.
 
 When reading a GIF all of the sub-images are combined using the screen
 size and image positions into one big image, producing an RGB image.
@@ -266,8 +217,8 @@ use the C<getcolors()> method on each image.
 
 GIF does not support the spatial resolution tags.
 
-GIF will set the following tags in each image when reading, but does
-not use them when saving to GIF:
+Imager will set the following tags in each image when reading, and can
+use most of them when writing to GIF:
 
 =over
 
@@ -288,24 +239,42 @@ non-zero if the image was interlaced ("Interlace Flag")
 
 =item gif_screen_height
 
-the size of the logical screen ("Logical Screen Width", 
-"Logical Screen Height")
+the size of the logical screen. When writing this is used as the
+minimum.  If any image being written would extend beyond this the
+screen size is extended.  ("Logical Screen Width", "Logical Screen
+Height").
+
+When writing this is used as a minimum, if the combination of the
+image size and the image's C<gif_left> and C<gif_top> is beyond this
+size then the screen size will be expanded.
 
 =item gif_local_map
 
-Non-zero if this image had a local color map.
+Non-zero if this image had a local color map.  If set for an image
+when writing the image is quantized separately from the other images
+in the file.
 
 =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.
+colormap.  You can set this on write too, but for it to choose the
+color you want, you will need to supply only paletted images and set
+the C<gif_eliminate_unused> tag to 0.
 
 =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")
+the alpha set to zero in this palette entry.  This value is not used
+when writing. ("Transparent Color Index")
+
+=item gif_trans_color
+
+A reference to an Imager::Color object, which is the colour to use for
+the palette entry used to represent transparency in the palette.  You
+need to set the transp option (see L<Quantization options>) for this
+value to be used.
 
 =item gif_delay
 
@@ -329,11 +298,59 @@ the number of loops from the Netscape Loop extension.  This may be zero.
 
 the first block of the first gif comment before each image.
 
+=item gif_eliminate_unused
+
+If this is true, when you write a paletted image any unused colors
+will be eliminated from its palette.  This is set by default.
+
 =back
 
 Where applicable, the ("name") is the name of that field from the GIF89 
 standard.
 
+The following gif writing options are obsolete, you should set the
+corresponding tag in the image, either by using the tags functions, or
+by supplying the tag and value as options.
+
+=over
+
+=item gif_each_palette
+
+Each image in the gif file has it's own palette if this is non-zero.
+All but the first image has a local colour table (the first uses the
+global colour table.
+
+Use C<gif_local_map> in new code.
+
+=item interlace
+
+The images are written interlaced if this is non-zero.
+
+Use C<gif_interlace> in new code.
+
+=item gif_delays
+
+A reference to an array containing the delays between images, in 1/100
+seconds.
+
+Use C<gif_delay> in new code.
+
+=item gif_positions
+
+A reference to an array of references to arrays which represent screen
+positions for each image.
+
+New code should use the C<gif_left> and C<gif_top> tags.
+
+=item gif_loop_count
+
+If this is non-zero the Netscape loop extension block is generated,
+which makes the animation of the images repeat.
+
+This is currently unimplemented due to some limitations in giflib.
+
+=back
+
 =head2 TIFF (Tagged Image File Format)
 
 Imager can write images to either paletted or RGB TIFF images,
@@ -539,5 +556,8 @@ When saving Gif images the program does NOT try to shave of extra
 colors if it is possible.  If you specify 128 colors and there are
 only 2 colors used - it will have a 128 colortable anyway.
 
+=head1 SEE ALSO
+
+Imager(3)
 
 =cut
index 6917f18..fd68e71 100644 (file)
@@ -118,7 +118,7 @@ sub translate {
                    0, 1, $opts{'y'},
                    0, 0, 1 ], $class;
   }
-  
+
   $Imager::ERRSTR = 'x and y parameters required';
   return undef;
 }
diff --git a/quant.c b/quant.c
index b80c053..023a7d4 100644 (file)
--- a/quant.c
+++ b/quant.c
@@ -5,6 +5,7 @@
 #include "image.h"
 
 static void makemap_addi(i_quantize *, i_img **imgs, int count);
+static void makemap_mediancut(i_quantize *, i_img **imgs, int count);
 
 static
 void
@@ -27,11 +28,17 @@ setcol(i_color *cl,unsigned char r,unsigned char g,unsigned char b,unsigned char
 
 void
 quant_makemap(i_quantize *quant, i_img **imgs, int count) {
-#ifdef HAVE_LIBGIF
-  /* giflib does it's own color table generation */
-  if (quant->translate == pt_giflib) 
+
+  if (quant->translate == pt_giflib) {
+    /* giflib does it's own color table generation */
+    /* previously we used giflib's quantizer, but it didn't handle multiple
+       images, which made it hard to build a global color map
+       We've implemented our own median cut code so we can ignore 
+       the giflib version */
+    makemap_mediancut(quant, imgs, count);
     return;
-#endif
+  }
+
   switch (quant->make_colors & mc_mask) {
   case mc_none:
     /* use user's specified map */
@@ -48,6 +55,10 @@ quant_makemap(i_quantize *quant, i_img **imgs, int count) {
     }
     break;
 
+  case mc_median_cut:
+    makemap_mediancut(quant, imgs, count);
+    break;
+
   case mc_addi:
   default:
     makemap_addi(quant, imgs, count);
@@ -73,13 +84,8 @@ i_palidx *quant_translate(i_quantize *quant, i_img *img) {
   result = mymalloc(img->xsize * img->ysize);
 
   switch (quant->translate) {
-#ifdef HAVE_LIBGIF
-  case pt_giflib:
-    translate_giflib(quant, img, result);
-    break;
-#endif
-
   case pt_closest:
+  case pt_giflib:
     translate_closest(quant, img, result);
     break;
     
@@ -497,6 +503,247 @@ makemap_addi(i_quantize *quant, i_img **imgs, int count) {
   myfree(clr);
 }
 
+typedef struct {
+  i_sample_t rgb[3];
+  int count;
+} quant_color_entry;
+
+#define MEDIAN_CUT_COLORS 32768
+
+#define MED_CUT_INDEX(c) ((((c).rgb.r & 0xF8) << 7) | \
+        (((c).rgb.g & 0xF8) << 2) | (((c).rgb.b & 0xF8) >> 3))
+
+/* scale these to cover the whole range */
+#define MED_CUT_RED(index) ((((index) & 0x7C00) >> 10) * 255 / 31)
+#define MED_CUT_GREEN(index) ((((index) & 0x3E0) >> 5) * 255 / 31)
+#define MED_CUT_BLUE(index) (((index) & 0x1F) * 255 / 31)
+
+typedef struct {
+  i_sample_t min[3]; /* minimum for each channel */
+  i_sample_t max[3]; /* maximum for each channel */
+  i_sample_t width[3]; /* width for each channel */
+  int start, size;   /* beginning and size of the partition */
+  int pixels; /* number of pixels represented by this partition */
+} medcut_partition;
+
+/*
+=item calc_part(part, colors)
+
+Calculates the new color limits for the given partition.
+
+Giflib assumes that the limits for the non-split channels stay the
+same, but this strikes me as incorrect, especially if the colors tend
+to be color ramps.
+
+Of course this could be optimized by not recalculating the channel we
+just sorted on, but it's not worth the effort right now.
+
+=cut
+*/
+static void calc_part(medcut_partition *part, quant_color_entry *colors) {
+  int i, ch;
+  
+  for (ch = 0; ch < 3; ++ch) {
+    part->min[ch] = 255;
+    part->max[ch] = 0;
+  }
+  for (i = part->start; i < part->start + part->size; ++i) {
+    for (ch = 0; ch < 3; ++ch) {
+      if (part->min[ch] > colors[i].rgb[ch])
+        part->min[ch] = colors[i].rgb[ch];
+      if (part->max[ch] < colors[i].rgb[ch])
+        part->max[ch] = colors[i].rgb[ch];
+    }
+  }
+  for (ch = 0; ch < 3; ++ch) {
+    part->width[ch] = part->max[ch] - part->min[ch];
+  }
+}
+
+/* simple functions to sort by each channel - we could use a global, but 
+   that would be bad */
+
+static int
+color_sort_red(void const *left, void const *right) {
+  return ((quant_color_entry *)left)->rgb[0] - ((quant_color_entry *)right)->rgb[0];
+}
+
+static int
+color_sort_green(void const *left, void const *right) {
+  return ((quant_color_entry *)left)->rgb[1] - ((quant_color_entry *)right)->rgb[1];
+}
+
+static int
+color_sort_blue(void const *left, void const *right) {
+  return ((quant_color_entry *)left)->rgb[2] - ((quant_color_entry *)right)->rgb[2];
+}
+
+static int (*sorters[])(void const *, void const *) =
+{
+  color_sort_red,
+  color_sort_green,
+  color_sort_blue,
+};
+
+static void
+makemap_mediancut(i_quantize *quant, i_img **imgs, int count) {
+  quant_color_entry *colors;
+  i_mempool mp;
+  int imgn, x, y, i, ch;
+  int max_width;
+  i_color *line;
+  int color_count;
+  int total_pixels;
+  medcut_partition *parts;
+  int part_num;
+  int in, out;
+
+  /*printf("images %d  pal size %d\n", count, quant->mc_size);*/
+
+  i_mempool_init(&mp);
+
+  colors = i_mempool_alloc(&mp, sizeof(*colors) * MEDIAN_CUT_COLORS);
+  for (i = 0; i < MEDIAN_CUT_COLORS; ++i) {
+    colors[i].rgb[0] = MED_CUT_RED(i);
+    colors[i].rgb[1] = MED_CUT_GREEN(i);
+    colors[i].rgb[2] = MED_CUT_BLUE(i);
+    colors[i].count = 0;
+  }
+
+  max_width = -1;
+  for (imgn = 0; imgn < count; ++imgn) {
+    if (imgs[imgn]->xsize > max_width)
+      max_width = imgs[imgn]->xsize;
+  }
+  line = i_mempool_alloc(&mp, sizeof(i_color) * max_width);
+
+  /* build the stats */
+  total_pixels = 0;
+  for (imgn = 0; imgn < count; ++imgn) {
+    total_pixels += imgs[imgn]->xsize * imgs[imgn]->ysize;
+    for (y = 0; y < imgs[imgn]->ysize; ++y) {
+      i_glin(imgs[imgn], 0, imgs[imgn]->xsize, y, line);
+      for (x = 0; x < imgs[imgn]->xsize; ++x) {
+        ++colors[MED_CUT_INDEX(line[x])].count;
+      }
+    }
+  }
+
+  /* eliminate the empty colors */
+  out = 0;
+  for (in = 0; in < MEDIAN_CUT_COLORS; ++in) {
+    if (colors[in].count) {
+      colors[out++] = colors[in];
+    }
+  }
+  /*printf("out %d\n", out);
+
+  for (i = 0; i < out; ++i) {
+    if (colors[i].count) {
+      printf("%d: (%d,%d,%d) -> %d\n", i, colors[i].rgb[0], colors[i].rgb[1], 
+             colors[i].rgb[2], colors[i].count);
+    }
+    }*/
+
+  if (out < quant->mc_size) {
+    /* just copy them into the color table */
+    for (i = 0; i < out; ++i) {
+      for (ch = 0; ch < 3; ++ch) {
+        quant->mc_colors[i].channel[ch] = colors[i].rgb[ch];
+      }
+    }
+    quant->mc_count = out;
+  }
+  else {
+    /* build the starting partition */
+    parts = i_mempool_alloc(&mp, sizeof(*parts) * quant->mc_size);
+    parts[0].start = 0;
+    parts[0].size = out;
+    parts[0].pixels = total_pixels;
+    calc_part(parts, colors);
+    color_count = 1;
+    
+    while (color_count < quant->mc_size) {
+      int max_index, max_ch; /* index/channel with biggest spread */
+      int max_size;
+      medcut_partition *workpart;
+      int cum_total;
+      int half;
+      
+      /* find the partition with the most biggest span with more than 
+         one color */
+      max_size = -1;
+      for (i = 0; i < color_count; ++i) {
+        for (ch = 0; ch < 3; ++ch) {
+          if (parts[i].width[ch] > max_size 
+              && parts[i].size > 1) {
+            max_index = i;
+            max_ch = ch;
+            max_size = parts[i].width[ch];
+          }
+        }
+      }
+      
+      /* nothing else we can split */
+      if (max_size == -1)
+        break;
+      
+      workpart = parts+max_index;
+      /*printf("splitting partition %d (pixels %ld, start %d, size %d)\n", max_index, workpart->pixels, workpart->start, workpart->size);*/
+      qsort(colors + workpart->start, workpart->size, sizeof(*colors),
+            sorters[max_ch]);
+      
+      /* find the median or something like it we need to make sure both
+         sides of the split have at least one color in them, so we don't
+         test at the first or last entry */
+      i = workpart->start;
+      cum_total = colors[i].count;
+      ++i;
+      half = workpart->pixels / 2;
+      while (i < workpart->start + workpart->size - 1
+             && cum_total < half) {
+        cum_total += colors[i++].count;
+      }
+      /*printf("Split at %d to make %d (half %ld, cumtotal %ld)\n", i, color_count, half, cum_total);*/
+      
+      /* found the spot to split */
+      parts[color_count].start = i;
+      parts[color_count].size = workpart->start + workpart->size - i;
+      workpart->size = i - workpart->start;
+      parts[color_count].pixels = workpart->pixels - cum_total;
+      workpart->pixels = cum_total;
+      
+      /* recalculate the limits */
+      calc_part(workpart, colors);
+      calc_part(parts+color_count, colors);
+      ++color_count;
+    }
+    
+    /* fill in the color table - since we could still have partitions
+       that have more than one color, we need to average the colors */
+    for (part_num = 0; part_num < color_count; ++part_num) {
+      long sums[3];
+      medcut_partition *workpart;
+      
+      workpart = parts+part_num;
+      for (ch = 0; ch < 3; ++ch)
+        sums[ch] = 0;
+      
+      for (i = workpart->start; i < workpart->start + workpart->size; ++i) {
+        for (ch = 0; ch < 3; ++ch) {
+          sums[ch] += colors[i].rgb[ch] * colors[i].count;
+        }
+      }
+      for (ch = 0; ch < 3; ++ch) {
+        quant->mc_colors[part_num].channel[ch] = sums[ch] / workpart->pixels;
+      }
+    }
+    quant->mc_count = color_count;
+  }
+  /*printf("out %d colors\n", quant->mc_count);*/
+  i_mempool_destroy(&mp);
+}
+
 #define pboxjump 32
 
 /* Define one of the following 4 symbols to choose a colour search method
index 998788b..94ede0e 100644 (file)
@@ -1,7 +1,7 @@
 #!perl -w
 use strict;
 $|=1;
-print "1..40\n";
+print "1..45\n";
 use Imager qw(:all);
 
 sub ok ($$$);
@@ -25,11 +25,11 @@ i_box_filled($timg, 0, 0, 20, 20, $green);
 i_box_filled($timg, 2, 2, 18, 18, $trans);
 
 if (!i_has_format("gif")) {
-  for (1..40) { print "ok $_ # skip no gif support\n"; }
+  for (1..45) { print "ok $_ # skip no gif support\n"; }
 } else {
     open(FH,">testout/t105.gif") || die "Cannot open testout/t105.gif\n";
     binmode(FH);
-    i_writegifmc($img,fileno(FH),7) || die "Cannot write testout/t105.gif\n";
+    i_writegifmc($img,fileno(FH),6) || die "Cannot write testout/t105.gif\n";
     close(FH);
 
     print "ok 1\n";
@@ -145,6 +145,7 @@ if (!i_has_format("gif")) {
     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);
@@ -215,11 +216,13 @@ EOS
       print "ok 14 # skip giflib3 doesn't support callbacks\n";
     }
     @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) {
-         my $c = i_color_new($x * 6, $y * 6, 32*$g+$x+$y, 255);
+          $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);
        }
       }
@@ -232,11 +235,8 @@ EOS
     # output looks moderately horrible
     open FH, ">testout/t105_mult_pall.gif" or die "Cannot create file: $!";
     binmode FH;
-    if (i_writegif_gen(fileno(FH), { make_colors=>'webmap',
+    if (i_writegif_gen(fileno(FH), { #make_colors=>'webmap',
                                      translate=>'giflib',
-                                     gif_delays=>[ 50, 50, 50, 50 ],
-                                     #gif_loop_count => 50,
-                                     gif_each_palette => 1,
                                    }, @imgs)) {
       print "ok 15\n";
     }
@@ -449,6 +449,25 @@ EOS
        "re-reading saved paletted images");
     ok(39, i_img_diff($imgs[0], $imgs2[0]) == 0, "imgs[0] mismatch");
     ok(40, 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(41, $ooim->read(file=>"testout/t105.gif"), "read into object");
+      ok(42, $ooim->write(file=>"testout/t105_warn.gif", interlace=>1),
+        "save from object");
+      ok(43, grep(/Obsolete .* interlace .* gif_interlace/, @warns),
+        "check for warning");
+      init(warn_obsolete=>0);
+      @warns = ();
+      ok(44, $ooim->write(file=>"testout/t105_warn.gif", interlace=>1),
+        "save from object");
+      ok(45, !grep(/Obsolete .* interlace .* gif_interlace/, @warns),
+        "check for warning");
+    }
 }
 
 sub ok ($$$) {
@@ -489,3 +508,18 @@ sub read_failure {
   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);
+  }
+}
index 68e3cf6..a1521b7 100644 (file)
@@ -147,7 +147,7 @@ if (!i_has_format("tiff")) {
 
   # OO to data
   $ooim->write(data=>\$oodata, type=>'tiff')
-    or print 'not ';
+    or print "# ",$ooim->errstr, "\nnot ";
   print "ok 19\n";
   $oodata eq $tiffdata or print "not ";
   print "ok 20\n";
index 61373a1..66f4537 100644 (file)
@@ -50,7 +50,8 @@ my %files;
                   { file => "testout/t108_24bit.tga" }, );
 my %writeopts =
   (
-   gif=> { make_colors=>'webmap', translate=>'closest', gifquant=>'gen' },
+   gif=> { make_colors=>'webmap', translate=>'closest', gifquant=>'gen',
+         gif_delay=>20 },
   );
 
 for my $type (@types) {
diff --git a/tags.c b/tags.c
index f5a21fc..f53a18e 100644 (file)
--- a/tags.c
+++ b/tags.c
@@ -47,6 +47,8 @@ A tag is represented by an i_img_tag structure:
 #include "image.h"
 #include <string.h>
 #include <stdlib.h>
+#include <errno.h>
+#include <limits.h>
 
 /* useful for debugging */
 void i_tags_print(i_img_tags *tags);
@@ -79,7 +81,7 @@ Returns non-zero on success.
 =cut
 */
 
-int i_tags_addn(i_img_tags *tags, char *name, int code, int idata) {
+int i_tags_addn(i_img_tags *tags, char const *name, int code, int idata) {
   return i_tags_add(tags, name, code, NULL, 0, idata);
 }
 
@@ -95,9 +97,11 @@ Returns non-zero on success.
 =cut
 */
 
-int i_tags_add(i_img_tags *tags, char *name, int code, char *data, int size
-              int idata) {
+int i_tags_add(i_img_tags *tags, char const *name, int code, char const *data
+               int size, int idata) {
   i_img_tag work = {0};
+  /*printf("i_tags_add(tags %p [count %d], name %s, code %d, data %p, size %d, idata %d)\n",
+    tags, tags->count, name, code, data, size, idata);*/
   if (tags->tags == NULL) {
     int alloc = 10;
     tags->tags = mymalloc(sizeof(i_img_tag) * alloc);
@@ -134,6 +138,8 @@ int i_tags_add(i_img_tags *tags, char *name, int code, char *data, int size,
   work.idata = idata;
   tags->tags[tags->count++] = work;
 
+  /*i_tags_print(tags);*/
+
   return 1;
 }
 
@@ -150,7 +156,7 @@ void i_tags_destroy(i_img_tags *tags) {
   }
 }
 
-int i_tags_find(i_img_tags *tags, char *name, int start, int *entry) {
+int i_tags_find(i_img_tags *tags, char const *name, int start, int *entry) {
   if (tags->tags) {
     while (start < tags->count) {
       if (tags->tags[start].name && strcmp(name, tags->tags[start].name) == 0) {
@@ -177,23 +183,28 @@ int i_tags_findn(i_img_tags *tags, int code, int start, int *entry) {
 }
 
 int i_tags_delete(i_img_tags *tags, int entry) {
+  /*printf("i_tags_delete(tags %p [count %d], entry %d)\n",
+    tags, tags->count, entry);*/
   if (tags->tags && entry >= 0 && entry < tags->count) {
     i_img_tag old = tags->tags[entry];
     memmove(tags->tags+entry, tags->tags+entry+1,
-           tags->count-entry-1);
+           (tags->count-entry-1) * sizeof(i_img_tag));
     if (old.name)
       myfree(old.name);
     if (old.data)
       myfree(old.data);
     --tags->count;
+
     return 1;
   }
   return 0;
 }
 
-int i_tags_delbyname(i_img_tags *tags, char *name) {
+int i_tags_delbyname(i_img_tags *tags, char const *name) {
   int count = 0;
   int i;
+  /*printf("i_tags_delbyname(tags %p [count %d], name %s)\n",
+    tags, tags->count, name);*/
   if (tags->tags) {
     for (i = tags->count-1; i >= 0; --i) {
       if (tags->tags[i].name && strcmp(name, tags->tags[i].name) == 0) {
@@ -202,6 +213,8 @@ int i_tags_delbyname(i_img_tags *tags, char *name) {
       }
     }
   }
+  /*i_tags_print(tags);*/
+
   return count;
 }
 
@@ -219,7 +232,8 @@ int i_tags_delbycode(i_img_tags *tags, int code) {
   return count;
 }
 
-int i_tags_get_float(i_img_tags *tags, char *name, int code, double *value) {
+int i_tags_get_float(i_img_tags *tags, char const *name, int code, 
+                     double *value) {
   int index;
   i_img_tag *entry;
 
@@ -240,7 +254,8 @@ int i_tags_get_float(i_img_tags *tags, char *name, int code, double *value) {
   return 1;
 }
 
-int i_tags_set_float(i_img_tags *tags, char *name, int code, double value) {
+int i_tags_set_float(i_img_tags *tags, char const *name, int code, 
+                     double value) {
   char temp[40];
 
   sprintf(temp, "%.30g", value);
@@ -252,7 +267,7 @@ int i_tags_set_float(i_img_tags *tags, char *name, int code, double value) {
   return i_tags_add(tags, name, code, temp, strlen(temp), 0);
 }
 
-int i_tags_get_int(i_img_tags *tags, char *name, int code, int *value) {
+int i_tags_get_int(i_img_tags *tags, char const *name, int code, int *value) {
   int index;
   i_img_tag *entry;
 
@@ -273,7 +288,132 @@ int i_tags_get_int(i_img_tags *tags, char *name, int code, int *value) {
   return 1;
 }
 
-int i_tags_get_string(i_img_tags *tags, char *name, int code, 
+static int parse_long(char *data, char **end, long *out) {
+#if 0
+  /* I wrote this without thinking about strtol */
+  long x = 0;
+  int neg = *data == '-';
+
+  if (neg)
+    ++data;
+  if (!isdigit(*data))
+    return 0;
+  while (isdigit(*data)) {
+    /* this check doesn't guarantee we don't overflow, but it helps */
+    if (x > LONG_MAX / 10)
+      return 0; 
+    x = x * 10 + *data - '0';
+    ++data;
+  }
+  if (neg)
+    x = -x;
+
+  *end = data;
+
+  return 1;
+#else
+  long result;
+  int savederr = errno;
+  char *myend;
+
+  errno = 0;
+  result = strtol(data, &myend, 10);
+  if ((result == LONG_MIN || result == LONG_MAX) && errno == ERANGE
+      || myend == data) {
+    return 0;
+  }
+
+  *out = result;
+  *end = myend;
+
+  return 1;
+#endif
+}
+
+/* parse a comma-separated list of integers
+   returns when it has maxcount numbers, finds a non-comma after a number
+   or can't parse a number
+   if it can't parse a number after a comma, that's considered an error
+*/
+static int parse_long_list(char *data, char **end, int maxcount, long *out) {
+  int i;
+
+  while (i < maxcount-1) {
+    if (!parse_long(data, &data, out))
+      return 0;
+    out++;
+    i++;
+    if (*data != ',')
+      return i;
+    ++data;
+  }
+  if (!parse_long(data, &data, out))
+    return 0;
+  ++i;
+  *end = data;
+  return i;
+}
+
+/* parse "color(red,green,blue,alpha)" */
+static int parse_color(char *data, char **end, i_color *value) {
+  long n[4];
+  int count, i;
+  
+  if (memcmp(data, "color(", 6))
+    return 0; /* not a color */
+  data += 6;
+  count = parse_long_list(data, &data, 4, n);
+  if (count < 3)
+    return 0;
+  for (i = 0; i < count; ++i)
+    value->channel[i] = n[i];
+  if (count < 4)
+    value->channel[3] = 255;
+
+  return 1;
+}
+
+int i_tags_get_color(i_img_tags *tags, char const *name, int code, 
+                     i_color *value) {
+  int index;
+  i_img_tag *entry;
+  char *end;
+
+  if (name) {
+    if (!i_tags_find(tags, name, 0, &index))
+      return 0;
+  }
+  else {
+    if (!i_tags_findn(tags, code, 0, &index))
+      return 0;
+  }
+  entry = tags->tags+index;
+  if (!entry->data) 
+    return 0;
+
+  if (!parse_color(entry->data, &end, value))
+    return 0;
+  
+  /* for now we're sloppy about the end */
+
+  return 1;
+}
+
+int i_tags_set_color(i_img_tags *tags, char const *name, int code, 
+                     i_color const *value) {
+  char temp[80];
+
+  sprintf(temp, "color(%d,%d,%d,%d)", value->channel[0], value->channel[1],
+          value->channel[2], value->channel[3]);
+  if (name)
+    i_tags_delbyname(tags, name);
+  else
+    i_tags_delbycode(tags, code);
+
+  return i_tags_add(tags, name, code, temp, strlen(temp), 0);
+}
+
+int i_tags_get_string(i_img_tags *tags, char const *name, int code, 
                       char *value, size_t value_size) {
   int index;
   i_img_tag *entry;
@@ -309,11 +449,11 @@ void i_tags_print(i_img_tags *tags) {
     i_img_tag *tag = tags->tags + i;
     printf("Tag %d\n", i);
     if (tag->name)
-      printf(" Name : %s\n", tag->name);
+      printf(" Name : %s (%p)\n", tag->name, tag->name);
     printf(" Code : %d\n", tag->code);
     if (tag->data) {
       int pos;
-      printf(" Data : %d => '", tag->size);
+      printf(" Data : %d (%p) => '", tag->size, tag->data);
       for (pos = 0; pos < tag->size; ++pos) {
        if (tag->data[pos] == '\\' || tag->data[pos] == '\'') {
          putchar('\\');