]> git.imager.perl.org - imager.git/commitdiff
writing a paletted image as GIF should be a bit more efficient
authorTony Cook <tony@develop=help.com>
Tue, 18 Sep 2001 04:42:54 +0000 (04:42 +0000)
committerTony Cook <tony@develop=help.com>
Tue, 18 Sep 2001 04:42:54 +0000 (04:42 +0000)
Changes
Imager.pm
Imager.xs
TODO
gif.c
image.h
t/t105gif.t

diff --git a/Changes b/Changes
index 83568e1530d3760f5d47e5d9e51003f050dc7cbe..2db114b1f7aa22229a166f050544297249bf8181 100644 (file)
--- a/Changes
+++ b/Changes
@@ -509,6 +509,8 @@ Revision history for Perl extension Imager.
         - the tt driver produces some artifacts when characters
           overlapped
         - error handling for writing jpeg images
+        - writing paletted images to GIF now uses the image palette
+          if it's small enough
 
 =================================================================
 
index 3a85b1279ffcafddc2936dcaa2f144d128a403bb..bb9c40b1ed262ae24db3a021f3a17533ff9d9c42 100644 (file)
--- a/Imager.pm
+++ b/Imager.pm
@@ -2491,6 +2491,11 @@ 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
 
 =head2 Quantization options
index db3bc5d835766986ab15c389183bb2a8023a401d..36a4041e8bb5197fa4660baa84c91cc0c0c3ae09 100644 (file)
--- a/Imager.xs
+++ b/Imager.xs
@@ -179,6 +179,30 @@ static struct value_name orddith_names[] =
   { "custom", od_custom, },
 };
 
+static int
+hv_fetch_bool(HV *hv, char const *name, int def) {
+  SV **sv;
+
+  sv = hv_fetch(hv, name, strlen(name), 0);
+  if (sv && *sv) {
+    return SvTRUE(*sv);
+  }
+  else
+    return def;
+}
+
+static int
+hv_fetch_int(HV *hv, char const *name, int def) {
+  SV **sv;
+
+  sv = hv_fetch(hv, name, strlen(name), 0);
+  if (sv && *sv) {
+    return SvIV(*sv);
+  }
+  else
+    return def;
+}
+
 /* look through the hash for quantization options */
 static void handle_quant_opts(i_quantize *quant, HV *hv)
 {
@@ -315,12 +339,9 @@ static void handle_gif_opts(i_gif_opts *opts, HV *hv)
   SV **sv;
   int i;
   /**((char *)0) = '\0';*/
-  sv = hv_fetch(hv, "gif_each_palette", 16, 0);
-  if (sv && *sv)
-    opts->each_palette = SvIV(*sv);
-  sv = hv_fetch(hv, "interlace", 9, 0);
-  if (sv && *sv)
-    opts->interlace = SvIV(*sv);
+  opts->each_palette = hv_fetch_bool(hv, "gif_each_palette", 0);
+  opts->interlace = hv_fetch_bool(hv, "interlace", 0);
+
   sv = hv_fetch(hv, "gif_delays", 10, 0);
   if (sv && *sv && SvROK(*sv) && SvTYPE(SvRV(*sv)) == SVt_PVAV) {
     AV *av = (AV*)SvRV(*sv);
@@ -377,9 +398,9 @@ static void handle_gif_opts(i_gif_opts *opts, HV *hv)
     }
   }
   /* Netscape2.0 loop count extension */
-  sv = hv_fetch(hv, "gif_loop_count", 14, 0);
-  if (sv && *sv)
-    opts->loop_count = SvIV(*sv);
+  opts->loop_count = hv_fetch_int(hv, "gif_loop_count", 0);
+
+  opts->eliminate_unused = hv_fetch_bool(hv, "gif_eliminate_unused", 1);
 }
 
 /* copies the color map from the hv into the colors member of the HV */
diff --git a/TODO b/TODO
index 82091c61cd2a0632f0834d3ca8f635b3d10bc8d6..dc23be98cce794615291186331f8a25fb2d9c14d 100644 (file)
--- a/TODO
+++ b/TODO
@@ -13,10 +13,6 @@ Iolayer:
 - Implment the maxread threshold (Indicates how far 
   a library can read before it indicates that it's done).
 
-BUGS:
-
-- jpeg writer doesn't handle write errors
-
 Enhanched internal structure:
 
 MultiImage & metadata support:
@@ -42,9 +38,14 @@ New Features:
 
 - advanced font layout (spacing, kerning, alignment) (sky)
 
+- ways to check if characters are present in a font, eg. checking if
+  ligatures are present
+
 - font synthesis - synthesize a bold or slanted font from a normal font
   (or even from an existing bold or slanted font)
+
 - utf8 support for text output
+  (available for freetype2)
 
 - image rotation, 3 ways of doing rotation:
   - rotation by shearing, which produces makes lengths in the image larger,
@@ -91,8 +92,6 @@ Format specific issues:
 
 - aalib support
 
-- when saving gifs handle the case where paletted images are being saved
-
 - save other formats as paletted for paletted where that's supported
 
 - read other format paletted images as paletted images
diff --git a/gif.c b/gif.c
index 5d82b41f28ad14dbf6c83c7b2a3b7d784da1724b..9e139fb14e059f8caf88c7dff38e533f0cbf900b 100644 (file)
--- a/gif.c
+++ b/gif.c
@@ -1189,6 +1189,113 @@ static void gif_set_version(i_quantize *quant, i_gif_opts *opts) {
   */
 }
 
+static int 
+in_palette(i_color *c, i_quantize *quant, int size) {
+  int i;
+
+  for (i = 0; i < size; ++i) {
+    if (c->channel[0] == quant->mc_colors[i].channel[0]
+        && c->channel[1] == quant->mc_colors[i].channel[1]
+        && c->channel[2] == quant->mc_colors[i].channel[2]) {
+      return i;
+    }
+  }
+
+  return -1;
+}
+
+/*
+=item has_common_palette(imgs, count, quant, want_trans)
+
+Tests if all the given images are paletted and have a common palette,
+if they do it builds that palette.
+
+A possible improvement might be to eliminate unused colors in the
+images palettes.
+
+=cut */
+static int
+has_common_palette(i_img **imgs, int count, i_quantize *quant, int want_trans,
+                   i_gif_opts *opts) {
+  int size = quant->mc_count;
+  int i, j;
+  int imgn;
+  int x, y;
+  char used[256];
+
+  /* we try to build a common palette here, if we can manage that, then
+     that's the palette we use */
+  for (imgn = 0; imgn < count; ++imgn) {
+    if (imgs[imgn]->type != i_palette_type)
+      return 0;
+
+    if (opts->eliminate_unused) {
+      i_palidx *line = mymalloc(sizeof(i_palidx) * imgs[imgn]->xsize);
+      int x, y;
+      memset(used, 0, sizeof(used));
+
+      for (y = 0; y < imgs[imgn]->ysize; ++y) {
+        i_gpal(imgs[imgn], 0, imgs[imgn]->xsize, y, line);
+        for (x = 0; x < imgs[imgn]->xsize; ++x)
+          used[line[x]] = 1;
+      }
+
+      myfree(line);
+    }
+    else {
+      /* assume all are in use */
+      memset(used, 1, sizeof(used));
+    }
+
+    for (i = 0; i < i_colorcount(imgs[imgn]); ++i) {
+      i_color c;
+      
+      i_getcolors(imgs[imgn], i, &c, 1);
+      if (used[i]) {
+        if (in_palette(&c, quant, size) < 0) {
+          if (size < quant->mc_size) {
+            quant->mc_colors[size++] = c;
+          }
+          else {
+            /* oops, too many colors */
+            return 0;
+          }
+        }
+      }
+    }
+  }
+
+  quant->mc_count = size;
+
+  return 1;
+}
+
+static i_palidx *
+quant_paletted(i_quantize *quant, i_img *img) {
+  i_palidx *data = mymalloc(sizeof(i_palidx) * img->xsize * img->ysize);
+  i_palidx *p = data;
+  i_palidx trans[256];
+  int i;
+  int x, y;
+
+  /* build a translation table */
+  for (i = 0; i < i_colorcount(img); ++i) {
+    i_color c;
+    i_getcolors(img, i, &c, 1);
+    trans[i] = in_palette(&c, quant, quant->mc_count);
+  }
+
+  for (y = 0; y < img->ysize; ++y) {
+    i_gpal(img, 0, img->xsize, y, data+img->xsize * y);
+    for (x = 0; x < img->xsize; ++x) {
+      *p = trans[*p];
+      ++p;
+    }
+  }
+
+  return data;
+}
+
 /*
 =item i_writegif_low(i_quantize *quant, GifFileType *gf, i_img **imgs, int count, i_gif_opts *opts)
 
@@ -1209,6 +1316,7 @@ i_writegif_low(i_quantize *quant, GifFileType *gf, i_img **imgs, int count,
   int scrw = 0, scrh = 0;
   int imgn, orig_count, orig_size;
   int posx, posy;
+  int trans_index;
 
   mm_log((1, "i_writegif_low(quant %p, gf  %p, imgs %p, count %d, opts %p)\n", 
          quant, gf, imgs, count, opts));
@@ -1257,12 +1365,18 @@ i_writegif_low(i_quantize *quant, GifFileType *gf, i_img **imgs, int count,
 
     /* we always generate a global palette - this lets systems with a 
        broken giflib work */
-    quant_makemap(quant, imgs, 1);
-    result = quant_translate(quant, imgs[0]);
+    if (has_common_palette(imgs, 1, quant, want_trans, opts)) {
+      result = quant_paletted(quant, imgs[0]);
+    }
+    else {
+      quant_makemap(quant, imgs, 1);
+      result = quant_translate(quant, imgs[0]);
+    }
+    if (want_trans) {
+      trans_index = quant->mc_count;
+      quant_transparent(quant, result, imgs[0], trans_index);
+    }
 
-    if (want_trans)
-      quant_transparent(quant, result, imgs[0], quant->mc_count);
-    
     if ((map = make_gif_map(quant, opts, want_trans)) == NULL) {
       myfree(result);
       EGifCloseFile(gf);
@@ -1288,7 +1402,7 @@ i_writegif_low(i_quantize *quant, GifFileType *gf, i_img **imgs, int count,
     if (!do_ns_loop(gf, opts))
       return 0;
 
-    if (!do_gce(gf, 0, opts, want_trans, quant->mc_count)) {
+    if (!do_gce(gf, 0, opts, want_trans, trans_index)) {
       myfree(result);
       EGifCloseFile(gf);
       return 0;
@@ -1325,11 +1439,18 @@ i_writegif_low(i_quantize *quant, GifFileType *gf, i_img **imgs, int count,
       if (want_trans && quant->mc_size == 256)
        --quant->mc_size;
 
-      quant_makemap(quant, imgs+imgn, 1);
-      result = quant_translate(quant, imgs[imgn]);
-      if (want_trans)
-       quant_transparent(quant, result, imgs[imgn], quant->mc_count);
-      
+      if (has_common_palette(imgs+imgn, 1, quant, want_trans, opts)) {
+        result = quant_paletted(quant, imgs[imgn]);
+      }
+      else {
+        quant_makemap(quant, imgs+imgn, 1);
+        result = quant_translate(quant, imgs[imgn]);
+      }
+      if (want_trans) {
+        quant_transparent(quant, result, imgs[imgn], quant->mc_count);
+        trans_index = quant->mc_count;
+      }
+
       if (!do_gce(gf, imgn, opts, want_trans, quant->mc_count)) {
        myfree(result);
        EGifCloseFile(gf);
@@ -1370,6 +1491,7 @@ i_writegif_low(i_quantize *quant, GifFileType *gf, i_img **imgs, int count,
   }
   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 */
@@ -1390,8 +1512,14 @@ i_writegif_low(i_quantize *quant, GifFileType *gf, i_img **imgs, int count,
        the colormap. */
      
     /* produce a colour map */
-    quant_makemap(quant, imgs, count);
-    result = quant_translate(quant, imgs[0]);
+    if (has_common_palette(imgs, count, quant, want_trans, opts)) {
+      result = quant_paletted(quant, imgs[0]);
+      ++do_quant_paletted;
+    }
+    else {
+      quant_makemap(quant, imgs, count);
+      result = quant_translate(quant, imgs[0]);
+    }
 
     if ((map = make_gif_map(quant, opts, want_trans)) == NULL) {
       myfree(result);
@@ -1448,7 +1576,10 @@ i_writegif_low(i_quantize *quant, GifFileType *gf, i_img **imgs, int count,
 
     for (imgn = 1; imgn < count; ++imgn) {
       int local_trans;
-      result = quant_translate(quant, imgs[imgn]);
+      if (do_quant_paletted)
+        result = quant_paletted(quant, imgs[imgn]);
+      else
+        result = quant_translate(quant, imgs[imgn]);
       local_trans = want_trans && imgs[imgn]->channels == 4;
       if (local_trans)
        quant_transparent(quant, result, imgs[imgn], quant->mc_count);
diff --git a/image.h b/image.h
index c62369465938da17ea834a5e31702af3e99e1859..3e74e40bfceb7319cdb7ec334d16826378fb40e9 100644 (file)
--- a/image.h
+++ b/image.h
@@ -491,6 +491,9 @@ typedef struct i_gif_opts {
 
   /* Netscape loop extension - number of loops */
   int loop_count;
+
+  /* should be eliminate unused colors? */
+  int eliminate_unused;
 } i_gif_opts;
 
 extern void quant_makemap(i_quantize *quant, i_img **imgs, int count);
index 4dec916ef40030bd76114fdd67058abc0bb6ed72..f206eac59abc06ff39c92b82034c9360bfca2263 100644 (file)
@@ -1,14 +1,18 @@
+#!perl -w
+use strict;
 $|=1;
-print "1..34\n";
+print "1..40\n";
 use Imager qw(:all);
 
+sub ok ($$$);
+
 init_log("testout/t105gif.log",1);
 
-$green=i_color_new(0,255,0,255);
-$blue=i_color_new(0,0,255,255);
-$red=i_color_new(255,0,0,255);
+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);
 
-$img=Imager::ImgRaw::new(150,150,3);
+my $img=Imager::ImgRaw::new(150,150,3);
 
 i_box_filled($img,70,25,130,125,$green);
 i_box_filled($img,20,25,80,125,$blue);
@@ -21,7 +25,7 @@ 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..34) { print "ok $_ # skip no gif support\n"; }
+  for (1..40) { print "ok $_ # skip no gif support\n"; }
 } else {
     open(FH,">testout/t105.gif") || die "Cannot open testout/t105.gif\n";
     binmode(FH);
@@ -39,7 +43,7 @@ if (!i_has_format("gif")) {
 
     open(FH,"testout/t105.gif") || die "Cannot open testout/t105.gif\n";
     binmode(FH);
-    ($img, $palette)=i_readgif(fileno(FH));
+    ($img, my $palette)=i_readgif(fileno(FH));
     $img || die "Cannot read testout/t105.gif\n";
     close(FH);
 
@@ -53,20 +57,20 @@ if (!i_has_format("gif")) {
     # image comparison code, but I know this code revealed the error
     open(FH, "<testimg/scalei.gif") || die "Cannot open testimg/scalei.gif";
     binmode FH;
-    ($imgi) = i_readgif(fileno(FH));
+    my ($imgi) = i_readgif(fileno(FH));
     $imgi || die "Cannot read testimg/scalei.gif";
     close FH;
     print "ok 4\n";
     open FH, "<testimg/scale.gif" or die "Cannot open testimg/scale.gif";
     binmode FH;
-    ($imgni) = i_readgif(fileno(FH));
+    my ($imgni) = i_readgif(fileno(FH));
     $imgni or die "Cannot read testimg/scale.gif";
     close FH;
     print "ok 5\n";
 
     open FH, ">testout/t105i.ppm" or die "Cannot create testout/t105i.ppm";
     binmode FH;
-    $IO = Imager::io_new_fd( fileno(FH) );
+    my $IO = Imager::io_new_fd( fileno(FH) );
     i_writeppm_wiol($imgi, $IO) or die "Cannot write testout/t105i.ppm";
     close FH;
 
@@ -79,11 +83,11 @@ if (!i_has_format("gif")) {
 
     # compare them
     open FH, "<testout/t105i.ppm" or die "Cannot open testout/t105i.ppm";
-    $datai = do { local $/; <FH> };
+    my $datai = do { local $/; <FH> };
     close FH;
 
     open FH, "<testout/t105ni.ppm" or die "Cannot open testout/t105ni.ppm";
-    $datani = do { local $/; <FH> };
+    my $datani = do { local $/; <FH> };
     close FH;
     if ($datai eq $datani) {
       print "ok 6\n";
@@ -206,7 +210,7 @@ EOS
       print "ok 14 # skip giflib3 doesn't support callbacks\n";
     }
     @imgs = ();
-    for $g (0..3) {
+    for my $g (0..3) {
       my $im = Imager::ImgRaw::new(200, 200, 3);
       for my $x (0 .. 39) {
        for my $y (0 .. 39) {
@@ -392,6 +396,7 @@ EOS
     my ($left) = grep $_->[0] eq 'gif_left', @tags;
     $left && $left->[1] == 3 or print "not ";
     print "ok 33\n";
+
     # screen3.gif was saved with 
     open FH, "< testimg/screen3.gif"
       or die "Cannot open testimg/screen3.gif: $!";
@@ -399,6 +404,7 @@ EOS
     @imgs = Imager::i_readgif_multi(fileno(FH))
       or print "not ";
     print "ok 34\n";
+    close FH;
     use Data::Dumper;
     # build a big map of all tags for all images
     @tags = 
@@ -412,6 +418,38 @@ EOS
     my $dump = Dumper(\@tags);
     $dump =~ s/^/# /mg;
     print "# tags from gif\n", $dump;
+
+    # at this point @imgs should contain only paletted images
+    ok(35, Imager::i_img_type($imgs[0]) == 1, "imgs[0] not paletted");
+    ok(36, Imager::i_img_type($imgs[1]) == 1, "imgs[1] not paletted");
+
+    # see how we go saving it
+    open FH, ">testout/t105_pal.gif" or die $!;
+    binmode FH;
+    ok(37, 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(38, (my @imgs2 = Imager::i_readgif_multi(fileno(FH))) == 2,
+       "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");
+}
+
+sub ok ($$$) {
+  my ($num, $ok, $comment) = @_;
+
+  if ($ok) {
+    print "ok $num\n";
+  }
+  else {
+    print "not ok $num # line ",(caller)[2],": $comment \n";
+  }
 }
 
 sub test_readgif_cb {