- 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
=================================================================
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
{ "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)
{
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);
}
}
/* 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 */
- 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:
- 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,
- 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
*/
}
+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)
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));
/* 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);
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;
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);
}
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 */
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);
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);
/* 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);
+#!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);
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);
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);
# 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;
# 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";
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) {
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: $!";
@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 =
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 {