From: Tony Cook Date: Tue, 18 Sep 2001 04:42:54 +0000 (+0000) Subject: writing a paletted image as GIF should be a bit more efficient X-Git-Tag: Imager-0.48^2~566 X-Git-Url: http://git.imager.perl.org/imager.git/commitdiff_plain/bf9dd17cc8fd304ad74fc258d67704c5683b5fb9?ds=inline writing a paletted image as GIF should be a bit more efficient --- diff --git a/Changes b/Changes index 83568e15..2db114b1 100644 --- 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 ================================================================= diff --git a/Imager.pm b/Imager.pm index 3a85b127..bb9c40b1 100644 --- 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 diff --git a/Imager.xs b/Imager.xs index db3bc5d8..36a4041e 100644 --- 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 82091c61..dc23be98 100644 --- 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 5d82b41f..9e139fb1 100644 --- 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 c6236946..3e74e40b 100644 --- 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); diff --git a/t/t105gif.t b/t/t105gif.t index 4dec916e..f206eac5 100644 --- a/t/t105gif.t +++ b/t/t105gif.t @@ -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, "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, " }; + my $datai = do { local $/; }; close FH; open FH, " }; + my $datani = do { local $/; }; 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 {