From: Tony Cook Date: Wed, 6 Mar 2002 12:08:23 +0000 (+0000) Subject: merge write to gif tags updates X-Git-Tag: Imager-0.48^2~424 X-Git-Url: http://git.imager.perl.org/imager.git/commitdiff_plain/97c4effc5cb6c972260ba40636d3885fe1fcbbd6 merge write to gif tags updates --- diff --git a/Changes b/Changes index 6b5ea5d1..3311fbac 100644 --- 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 ================================================================= diff --git a/Imager.pm b/Imager.pm index f485bd54..3bcee4f1 100644 --- 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') { diff --git a/Imager.xs b/Imager.xs index 1d8ad779..cc24d902 100644 --- 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 2dc13c74..3830ff62 100644 --- 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<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 7025b2da..83cb50ca 100644 --- 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 1936ee8a..73fbd3be 100644 --- 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); } diff --git a/lib/Imager/Files.pod b/lib/Imager/Files.pod index 88067f3d..25a84ec4 100644 --- a/lib/Imager/Files.pod +++ b/lib/Imager/Files.pod @@ -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 or C 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 function with +the C 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 method. - -These options can be specified when calling write_multi() or when -writing a single image with the C option set to 'gen' - -Note that some viewers will ignore some of these options -(C 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) 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 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 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 and C 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 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) 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 in new code. + +=item interlace + +The images are written interlaced if this is non-zero. + +Use C in new code. + +=item gif_delays + +A reference to an array containing the delays between images, in 1/100 +seconds. + +Use C 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 and C 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 diff --git a/lib/Imager/Matrix2d.pm b/lib/Imager/Matrix2d.pm index 6917f18f..fd68e712 100644 --- a/lib/Imager/Matrix2d.pm +++ b/lib/Imager/Matrix2d.pm @@ -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 b80c0538..023a7d42 100644 --- 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 diff --git a/t/t105gif.t b/t/t105gif.t index 998788bf..94ede0ee 100644 --- a/t/t105gif.t +++ b/t/t105gif.t @@ -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); + } +} diff --git a/t/t106tiff.t b/t/t106tiff.t index 68e3cf6d..a1521b7c 100644 --- a/t/t106tiff.t +++ b/t/t106tiff.t @@ -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"; diff --git a/t/t50basicoo.t b/t/t50basicoo.t index 61373a1e..66f4537e 100644 --- a/t/t50basicoo.t +++ b/t/t50basicoo.t @@ -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 f5a21fc9..f53a18e3 100644 --- a/tags.c +++ b/tags.c @@ -47,6 +47,8 @@ A tag is represented by an i_img_tag structure: #include "image.h" #include #include +#include +#include /* 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('\\');