From: Tony Cook Date: Mon, 23 Aug 2010 09:38:20 +0000 (+0000) Subject: move the GIF file handling code into a sub-module X-Git-Tag: Imager-0.79~78 X-Git-Url: http://git.imager.perl.org/imager.git/commitdiff_plain/ec6d89084477350e1b224644166f4bd337f67055 move the GIF file handling code into a sub-module --- diff --git a/Changes b/Changes index 42dfbe35..025e5a00 100644 --- a/Changes +++ b/Changes @@ -8,6 +8,10 @@ Imager 0.78 - unreleased Thanks to Justin Davis. https://rt.cpan.org/Ticket/Display.html?id=60491 + - moved the GIF file handling code into a sub-module in preparation + for separate distribution. + https://rt.cpan.org/Ticket/Display.html?id=49616 (partial) + Bug fixes: - Imager::Probe was calling ExtUtils::Liblist to initialize diff --git a/GIF/GIF.pm b/GIF/GIF.pm new file mode 100644 index 00000000..de067cd7 --- /dev/null +++ b/GIF/GIF.pm @@ -0,0 +1,130 @@ +package Imager::File::GIF; +use strict; +use Imager; +use vars qw($VERSION @ISA); + +BEGIN { + $VERSION = "0.77"; + + eval { + require XSLoader; + XSLoader::load('Imager::File::GIF', $VERSION); + 1; + } or do { +print STDERR "Falling back to DynaLoader ($@)\n"; + require DynaLoader; + push @ISA, 'DynaLoader'; + bootstrap Imager::File::GIF $VERSION; + }; +} + +Imager->register_reader + ( + type=>'gif', + single => + sub { + my ($im, $io, %hsh) = @_; + + if ($hsh{gif_consolidate}) { + if ($hsh{colors}) { + my $colors; + ($im->{IMG}, $colors) =i_readgif_wiol( $io ); + if ($colors) { + ${ $hsh{colors} } = [ map { NC(@$_) } @$colors ]; + } + } + else { + $im->{IMG} =i_readgif_wiol( $io ); + } + } + else { + my $page = $hsh{page}; + defined $page or $page = 0; + $im->{IMG} = i_readgif_single_wiol($io, $page); + + unless ($im->{IMG}) { + $im->_set_error(Imager->_error_as_msg); + return; + } + if ($hsh{colors}) { + ${ $hsh{colors} } = [ $im->getcolors ]; + } + return $im; + } + }, + multiple => + sub { + my ($io, %hsh) = @_; + + my @imgs = i_readgif_multi_wiol($io); + unless (@imgs) { + Imager->_set_error(Imager->_error_as_msg); + return; + } + + return map bless({ IMG => $_, ERRSTR => undef }, "Imager"), @imgs; + }, + ); + +Imager->register_writer + ( + type=>'gif', + single => + sub { + my ($im, $io, %hsh) = @_; + + $im->_set_opts(\%hsh, "i_", $im); + $im->_set_opts(\%hsh, "gif_", $im); + + unless (i_writegif_wiol($io, \%hsh, $im->{IMG})) { + $im->_set_error(Imager->_error_as_msg); + return; + } + return $im; + }, + multiple => + sub { + my ($class, $io, $opts, @ims) = @_; + + Imager->_set_opts($opts, "gif_", @ims); + + my @work = map $_->{IMG}, @ims; + unless (i_writegif_wiol($io, $opts, @work)) { + Imager->_set_error(Imager->_error_as_msg); + return; + } + + return 1; + }, + ); + +__END__ + +=head1 NAME + +Imager::File::GIF - read and write GIF files + +=head1 SYNOPSIS + + use Imager; + + my $img = Imager->new; + $img->read(file=>"foo.gif") + or die $img->errstr; + + $img->write(file => "foo.gif") + or die $img->errstr; + +=head1 DESCRIPTION + +Imager's GIF support is documented in L. + +=head1 AUTHOR + +Tony Cook + +=head1 SEE ALSO + +Imager, Imager::Files. + +=cut diff --git a/GIF/GIF.xs b/GIF/GIF.xs new file mode 100644 index 00000000..312c181f --- /dev/null +++ b/GIF/GIF.xs @@ -0,0 +1,149 @@ +#define PERL_NO_GET_CONTEXT +#ifdef __cplusplus +extern "C" { +#endif +#include "EXTERN.h" +#include "perl.h" +#include "XSUB.h" +#include "imext.h" +#include "imperl.h" +#include "imgif.h" +#include "imextpl.h" + +DEFINE_IMAGER_CALLBACKS; +DEFINE_IMAGER_PERL_CALLBACKS; + +MODULE = Imager::File::GIF PACKAGE = Imager::File::GIF + +long +i_giflib_version() + +undef_int +i_writegif_wiol(ig, opts,...) + Imager::IO ig + PREINIT: + i_quantize quant; + i_img **imgs = NULL; + int img_count; + int i; + HV *hv; + CODE: + if (items < 3) + croak("Usage: i_writegif_wiol(IO,hashref, images...)"); + if (!SvROK(ST(1)) || ! SvTYPE(SvRV(ST(1)))) + croak("i_writegif_callback: Second argument must be a hash ref"); + hv = (HV *)SvRV(ST(1)); + memset(&quant, 0, sizeof(quant)); + quant.version = 1; + quant.mc_size = 256; + quant.transp = tr_threshold; + quant.tr_threshold = 127; + ip_handle_quant_opts(aTHX_ &quant, hv); + img_count = items - 2; + RETVAL = 1; + if (img_count < 1) { + RETVAL = 0; + } + else { + imgs = mymalloc(sizeof(i_img *) * img_count); + for (i = 0; i < img_count; ++i) { + SV *sv = ST(2+i); + imgs[i] = NULL; + if (SvROK(sv) && sv_derived_from(sv, "Imager::ImgRaw")) { + imgs[i] = INT2PTR(i_img *, SvIV((SV*)SvRV(sv))); + } + else { + RETVAL = 0; + break; + } + } + if (RETVAL) { + RETVAL = i_writegif_wiol(ig, &quant, imgs, img_count); + } + myfree(imgs); + if (RETVAL) { + ip_copy_colors_back(aTHX_ hv, &quant); + } + } + ST(0) = sv_newmortal(); + if (RETVAL == 0) ST(0)=&PL_sv_undef; + else sv_setiv(ST(0), (IV)RETVAL); + ip_cleanup_quant_opts(aTHX_ &quant); + + +void +i_readgif_wiol(ig) + Imager::IO ig + PREINIT: + int* colour_table; + int colours, q, w; + i_img* rimg; + SV* temp[3]; + AV* ct; + SV* r; + PPCODE: + colour_table = NULL; + colours = 0; + + if(GIMME_V == G_ARRAY) { + rimg = i_readgif_wiol(ig,&colour_table,&colours); + } else { + /* don't waste time with colours if they aren't wanted */ + rimg = i_readgif_wiol(ig,NULL,NULL); + } + + if (colour_table == NULL) { + EXTEND(SP,1); + r=sv_newmortal(); + sv_setref_pv(r, "Imager::ImgRaw", (void*)rimg); + PUSHs(r); + } else { + /* the following creates an [[r,g,b], [r, g, b], [r, g, b]...] */ + /* I don't know if I have the reference counts right or not :( */ + /* Neither do I :-) */ + /* No Idea here either */ + + ct=newAV(); + av_extend(ct, colours); + for(q=0; q \@libpaths, + "verbose|v" => \$verbose); + +our $BUILDING_IMAGER; + +my $MM_ver = eval $ExtUtils::MakeMaker::VERSION; + +my %opts = + ( + NAME => 'Imager::File::GIF', + VERSION_FROM => 'GIF.pm', + OBJECT => 'GIF.o imgif.o', + ); + +my @inc; +if ($BUILDING_IMAGER) { + unshift @inc, "-I.."; + unshift @INC, "../lib"; +} +else { + unshift @INC, "inc"; + print "GIF: building independently\n"; + require Imager::ExtUtils; + push @inc, Imager::ExtUtils->includes; + $opts{TYPEMAPS} = [ Imager::ExtUtils->typemap ]; + + # Imager required configure through use + my @Imager_req = ( Imager => "0.78" ); + if ($MM_ver >= 6.46) { + $opts{META_MERGE} = + { + configure_requires => + { + @Imager_req, + }, + build_requires => + { + @Imager_req, + "Test::More" => "0.47", + }, + resources => + { + homepage => "http://imager.perl.org/", + repository => + { + url => "http://imager.perl.org/svn/trunk/Imager-File-GIF", + web => "http://imager.perl.org/svnweb/public/browse/trunk/Imager-File-GIF", + type => "svn", + }, + }, + }; + $opts{PREREQ_PM} = + { + @Imager_req, + }; + } +} + +require Imager::Probe; + +my %probe = + ( + name => "GIF", + inccheck => sub { -e File::Spec->catfile($_[0], "gif_lib.h") }, + libbase => "gif", + testcode => _gif_test_code(), + testcodeheaders => [ "gif_lib.h", "stdio.h" ], + incpath => join($Config{path_sep}, @incpaths), + libpath => join($Config{path_sep}, @libpaths), + ); + +my $probe_res = Imager::Probe->probe(\%probe); +if ($probe_res) { + push @inc, $probe_res->{INC}; + $opts{LIBS} = $probe_res->{LIBS}; + + $opts{INC} = "@inc"; + + if ($MM_ver > 6.06) { + $opts{AUTHOR} = 'Tony Cook '; + $opts{ABSTRACT} = 'GIF Image file support'; + } + + WriteMakefile(%opts); +} +else { + if ($BUILDING_IMAGER) { + WriteEmptyMakefile(%opts); + } + else { + # fail in good way + die "OS unsupported: GIF libraries or headers not found\n"; + } +} + +sub _gif_test_code { + return <<'CODE'; +/* TODO: test DGifOpen() and processing with callbacks */ +GifFileType *gf; +const char vers[] = GIF_LIB_VERSION; +const char *versp = vers; +int ver_maj; +int ver_min; +fprintf(stderr, "GIF: header version '%s'\n", GIF_LIB_VERSION); +gf=DGifOpenFileName("testimg/expected.gif"); +if (!gf) { + fprintf(stderr, "GIF: Cannot open testimg/expected.gif\n"); + return 1; +} +if (gf->SWidth != 16 || gf->SHeight != 16) { + fprintf(stderr, "GIF: bad screen description (%d x %d)\n", gf->SWidth, gf->SHeight); + return 1; +} +/* crashes in older versions of giflib */ +EGifSetGifVersion("89a"); +EGifSetGifVersion("87a"); + +/* skip the " Version " */ +while (*versp && (*versp < '0' || *versp > '9')) + ++versp; +if (!*versp) { + fprintf(stderr, "GIF: Cannot find version number in '%s'\n", vers); + return 1; +} +ver_maj = 0; +while (*versp && *versp >= '0' && *versp <= '9') { + ver_maj *= 10; + ver_maj += *versp++ - '0'; +} +if (*versp != '.' || versp[1] < '0' || versp[1] > '9') { + fprintf(stderr, "Cannot parse major version number in '%s'\n", vers); + return 1; +} +++versp; /* skip '.' */ +ver_min = 0; +while (*versp && *versp >= '0' && *versp <= '9') { + ver_min *= 10; + ver_min += *versp++ - '0'; +} +if (ver_maj < 4) { + fprintf(stderr, "GIF: gif lib version 3 is no longer supported\n"); + return 1; +} +if (ver_maj == 4 && ver_min < 1) { + fprintf(stderr, "GIF: you need at least giflib 4.1\n"); + return 1; +} +fprintf(stderr, "GIF: Major %d, Minor %d\n", ver_maj, ver_min); +return 0; +CODE +} diff --git a/GIF/imgif.c b/GIF/imgif.c new file mode 100644 index 00000000..05614197 --- /dev/null +++ b/GIF/imgif.c @@ -0,0 +1,1925 @@ +#include "imgif.h" +#include +#ifdef _MSC_VER +#include +#else +#include +#endif +#include +#include + +/* +=head1 NAME + +gif.c - read and write gif files for Imager + +=head1 SYNOPSIS + + i_img *img; + i_img *imgs[count]; + int fd; + int *colour_table, + int colours; + int max_colours; // number of bits per colour + int pixdev; // how much noise to add + i_color fixed[N]; // fixed palette entries + int fixedlen; // number of fixed colours + int success; // non-zero on success + char *data; // a GIF file in memory + int length; // how big data is + int reader(char *, char *, int, int); + int writer(char *, char *, int); + char *userdata; // user's data, whatever it is + i_quantize quant; + i_gif_opts opts; + + img = i_readgif(fd, &colour_table, &colours); + success = i_writegif(img, fd, max_colours, pixdev, fixedlen, fixed); + success = i_writegifmc(img, fd, max_colours); + img = i_readgif_scalar(data, length, &colour_table, &colours); + img = i_readgif_callback(cb, userdata, &colour_table, &colours); + success = i_writegif_gen(&quant, fd, imgs, count, &opts); + success = i_writegif_callback(&quant, writer, userdata, maxlength, + imgs, count, &opts); + +=head1 DESCRIPTION + +This source file provides the C level interface to reading and writing +GIF files for Imager. + +This has been tested with giflib 3 and 4, though you lose the callback +functionality with giflib3. + +=head1 REFERENCE + +=over + +=cut +*/ + +static char const *gif_error_msg(int code); +static void gif_push_error(void); + +static int gif_read_callback(GifFileType *gft, GifByteType *buf, int length); + +/* Make some variables global, so we could access them faster: */ + +static int + InterlacedOffset[] = { 0, 4, 2, 1 }, /* The way Interlaced image should. */ + InterlacedJumps[] = { 8, 8, 4, 2 }; /* be read - offsets and jumps... */ + + + +static +void +i_colortable_copy(int **colour_table, int *colours, ColorMapObject *colourmap) { + GifColorType *mapentry; + int q; + int colourmapsize = colourmap->ColorCount; + + if(colours) *colours = colourmapsize; + if(!colour_table) return; + + *colour_table = mymalloc(sizeof(int) * colourmapsize * 3); + memset(*colour_table, 0, sizeof(int) * colourmapsize * 3); + + for(q=0; qColors[q]; + (*colour_table)[q*3 + 0] = mapentry->Red; + (*colour_table)[q*3 + 1] = mapentry->Green; + (*colour_table)[q*3 + 2] = mapentry->Blue; + } +} + +long +i_giflib_version(void) { + return 10; +} + +/* +=item i_readgif_low(GifFileType *GifFile, int **colour_table, int *colours) + +Internal. Low-level function for reading a GIF file. The caller must +create the appropriate GifFileType object and pass it in. + +=cut +*/ + +i_img * +i_readgif_low(GifFileType *GifFile, int **colour_table, int *colours) { + i_img *im; + int i, j, Size, Row, Col, Width, Height, ExtCode, Count, x; + int cmapcnt = 0, ImageNum = 0, BackGround = 0, ColorMapSize = 0; + ColorMapObject *ColorMap; + + GifRecordType RecordType; + GifByteType *Extension; + + GifRowType GifRow; + static GifColorType *ColorMapEntry; + i_color col; + + mm_log((1,"i_readgif_low(GifFile %p, colour_table %p, colours %p)\n", GifFile, colour_table, colours)); + + /* it's possible that the caller has called us with *colour_table being + non-NULL, but we check that to see if we need to free an allocated + colour table on error. + */ + if (colour_table) *colour_table = NULL; + + BackGround = GifFile->SBackGroundColor; + ColorMap = (GifFile->Image.ColorMap ? GifFile->Image.ColorMap : GifFile->SColorMap); + + if (ColorMap) { + ColorMapSize = ColorMap->ColorCount; + i_colortable_copy(colour_table, colours, ColorMap); + cmapcnt++; + } + + if (!i_int_check_image_file_limits(GifFile->SWidth, GifFile->SHeight, 3, sizeof(i_sample_t))) { + if (colour_table && *colour_table) { + myfree(*colour_table); + *colour_table = NULL; + } + DGifCloseFile(GifFile); + mm_log((1, "i_readgif: image size exceeds limits\n")); + return NULL; + } + + im = i_img_8_new(GifFile->SWidth, GifFile->SHeight, 3); + if (!im) { + if (colour_table && *colour_table) { + myfree(*colour_table); + *colour_table = NULL; + } + DGifCloseFile(GifFile); + return NULL; + } + + Size = GifFile->SWidth * sizeof(GifPixelType); + + GifRow = mymalloc(Size); + + for (i = 0; i < GifFile->SWidth; i++) GifRow[i] = GifFile->SBackGroundColor; + + /* Scan the content of the GIF file and load the image(s) in: */ + do { + if (DGifGetRecordType(GifFile, &RecordType) == GIF_ERROR) { + gif_push_error(); + i_push_error(0, "Unable to get record type"); + if (colour_table && *colour_table) { + myfree(*colour_table); + *colour_table = NULL; + } + myfree(GifRow); + i_img_destroy(im); + DGifCloseFile(GifFile); + return NULL; + } + + switch (RecordType) { + case IMAGE_DESC_RECORD_TYPE: + if (DGifGetImageDesc(GifFile) == GIF_ERROR) { + gif_push_error(); + i_push_error(0, "Unable to get image descriptor"); + if (colour_table && *colour_table) { + myfree(*colour_table); + *colour_table = NULL; + } + myfree(GifRow); + i_img_destroy(im); + DGifCloseFile(GifFile); + return NULL; + } + + if (( ColorMap = (GifFile->Image.ColorMap ? GifFile->Image.ColorMap : GifFile->SColorMap) )) { + mm_log((1, "Adding local colormap\n")); + ColorMapSize = ColorMap->ColorCount; + if ( cmapcnt == 0) { + i_colortable_copy(colour_table, colours, ColorMap); + cmapcnt++; + } + } else { + /* No colormap and we are about to read in the image - abandon for now */ + mm_log((1, "Going in with no colormap\n")); + i_push_error(0, "Image does not have a local or a global color map"); + /* we can't have allocated a colour table here */ + myfree(GifRow); + i_img_destroy(im); + DGifCloseFile(GifFile); + return NULL; + } + + Row = GifFile->Image.Top; /* Image Position relative to Screen. */ + Col = GifFile->Image.Left; + Width = GifFile->Image.Width; + Height = GifFile->Image.Height; + ImageNum++; + mm_log((1,"i_readgif_low: Image %d at (%d, %d) [%dx%d]: \n",ImageNum, Col, Row, Width, Height)); + + if (GifFile->Image.Left + GifFile->Image.Width > GifFile->SWidth || + GifFile->Image.Top + GifFile->Image.Height > GifFile->SHeight) { + i_push_errorf(0, "Image %d is not confined to screen dimension, aborted.\n",ImageNum); + if (colour_table && *colour_table) { + myfree(*colour_table); + *colour_table = NULL; + } + myfree(GifRow); + i_img_destroy(im); + DGifCloseFile(GifFile); + return NULL; + } + if (GifFile->Image.Interlace) { + + for (Count = i = 0; i < 4; i++) for (j = Row + InterlacedOffset[i]; j < Row + Height; j += InterlacedJumps[i]) { + Count++; + if (DGifGetLine(GifFile, GifRow, Width) == GIF_ERROR) { + gif_push_error(); + i_push_error(0, "Reading GIF line"); + if (colour_table && *colour_table) { + myfree(*colour_table); + *colour_table = NULL; + } + myfree(GifRow); + i_img_destroy(im); + DGifCloseFile(GifFile); + return NULL; + } + + for (x = 0; x < Width; x++) { + ColorMapEntry = &ColorMap->Colors[GifRow[x]]; + col.rgb.r = ColorMapEntry->Red; + col.rgb.g = ColorMapEntry->Green; + col.rgb.b = ColorMapEntry->Blue; + i_ppix(im,Col+x,j,&col); + } + + } + } + else { + for (i = 0; i < Height; i++) { + if (DGifGetLine(GifFile, GifRow, Width) == GIF_ERROR) { + gif_push_error(); + i_push_error(0, "Reading GIF line"); + if (colour_table && *colour_table) { + myfree(*colour_table); + *colour_table = NULL; + } + myfree(GifRow); + i_img_destroy(im); + DGifCloseFile(GifFile); + return NULL; + } + + for (x = 0; x < Width; x++) { + ColorMapEntry = &ColorMap->Colors[GifRow[x]]; + col.rgb.r = ColorMapEntry->Red; + col.rgb.g = ColorMapEntry->Green; + col.rgb.b = ColorMapEntry->Blue; + i_ppix(im, Col+x, Row, &col); + } + Row++; + } + } + break; + case EXTENSION_RECORD_TYPE: + /* Skip any extension blocks in file: */ + if (DGifGetExtension(GifFile, &ExtCode, &Extension) == GIF_ERROR) { + gif_push_error(); + i_push_error(0, "Reading extension record"); + if (colour_table && *colour_table) { + myfree(*colour_table); + *colour_table = NULL; + } + myfree(GifRow); + i_img_destroy(im); + DGifCloseFile(GifFile); + return NULL; + } + while (Extension != NULL) { + if (DGifGetExtensionNext(GifFile, &Extension) == GIF_ERROR) { + gif_push_error(); + i_push_error(0, "reading next block of extension"); + if (colour_table && *colour_table) { + myfree(*colour_table); + *colour_table = NULL; + } + myfree(GifRow); + i_img_destroy(im); + DGifCloseFile(GifFile); + return NULL; + } + } + break; + case TERMINATE_RECORD_TYPE: + break; + default: /* Should be traps by DGifGetRecordType. */ + break; + } + } while (RecordType != TERMINATE_RECORD_TYPE); + + myfree(GifRow); + + if (DGifCloseFile(GifFile) == GIF_ERROR) { + gif_push_error(); + i_push_error(0, "Closing GIF file object"); + if (colour_table && *colour_table) { + myfree(*colour_table); + *colour_table = NULL; + } + i_img_destroy(im); + return NULL; + } + + i_tags_set(&im->tags, "i_format", "gif", -1); + + return im; +} + +/* + +Internal function called by i_readgif_multi_low() in error handling + +*/ +static void free_images(i_img **imgs, int count) { + int i; + + if (count) { + for (i = 0; i < count; ++i) + i_img_destroy(imgs[i]); + myfree(imgs); + } +} + +/* +=item i_readgif_multi_low(GifFileType *gf, int *count, int page) + +Reads one of more gif images from the given GIF file. + +Returns a pointer to an array of i_img *, and puts the count into +*count. + +If page is not -1 then the given image _only_ is returned from the +file, where the first image is 0, the second 1 and so on. + +Unlike the normal i_readgif*() functions the images are paletted +images rather than a combined RGB image. + +This functions sets tags on the images returned: + +=over + +=item gif_left + +the offset of the image from the left of the "screen" ("Image Left +Position") + +=item gif_top + +the offset of the image from the top of the "screen" ("Image Top Position") + +=item gif_interlace + +non-zero if the image was interlaced ("Interlace Flag") + +=item gif_screen_width + +=item gif_screen_height + +the size of the logical screen ("Logical Screen Width", +"Logical Screen Height") + +=item gif_local_map + +Non-zero if this image had a local color map. + +=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. + +=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") + +=item gif_delay + +The delay until the next frame is displayed, in 1/100 of a second. +("Delay Time"). + +=item gif_user_input + +whether or not a user input is expected before continuing (view dependent) +("User Input Flag"). + +=item gif_disposal + +how the next frame is displayed ("Disposal Method") + +=item gif_loop + +the number of loops from the Netscape Loop extension. This may be zero. + +=item gif_comment + +the first block of the first gif comment before each image. + +=back + +Where applicable, the ("name") is the name of that field from the GIF89 +standard. + +=cut +*/ + +i_img **i_readgif_multi_low(GifFileType *GifFile, int *count, int page) { + i_img *img; + int i, j, Size, Width, Height, ExtCode, Count; + int ImageNum = 0, BackGround = 0, ColorMapSize = 0; + ColorMapObject *ColorMap; + + GifRecordType RecordType; + GifByteType *Extension; + + GifRowType GifRow; + int got_gce = 0; + int trans_index = 0; /* transparent index if we see a GCE */ + int gif_delay = 0; /* delay from a GCE */ + int user_input = 0; /* user input flag from a GCE */ + int disposal = 0; /* disposal method from a GCE */ + int got_ns_loop = 0; + int ns_loop = 0; + char *comment = NULL; /* a comment */ + i_img **results = NULL; + int result_alloc = 0; + int channels; + int image_colors = 0; + i_color black; /* used to expand the palette if needed */ + + for (i = 0; i < MAXCHANNELS; ++i) + black.channel[i] = 0; + + *count = 0; + + mm_log((1,"i_readgif_multi_low(GifFile %p, , count %p)\n", GifFile, count)); + + BackGround = GifFile->SBackGroundColor; + + Size = GifFile->SWidth * sizeof(GifPixelType); + + GifRow = (GifRowType) mymalloc(Size); + + /* Scan the content of the GIF file and load the image(s) in: */ + do { + if (DGifGetRecordType(GifFile, &RecordType) == GIF_ERROR) { + gif_push_error(); + i_push_error(0, "Unable to get record type"); + free_images(results, *count); + DGifCloseFile(GifFile); + myfree(GifRow); + if (comment) + myfree(comment); + return NULL; + } + + switch (RecordType) { + case IMAGE_DESC_RECORD_TYPE: + if (DGifGetImageDesc(GifFile) == GIF_ERROR) { + gif_push_error(); + i_push_error(0, "Unable to get image descriptor"); + free_images(results, *count); + DGifCloseFile(GifFile); + myfree(GifRow); + if (comment) + myfree(comment); + return NULL; + } + + Width = GifFile->Image.Width; + Height = GifFile->Image.Height; + if (page == -1 || page == ImageNum) { + if (( ColorMap = (GifFile->Image.ColorMap ? GifFile->Image.ColorMap : GifFile->SColorMap) )) { + mm_log((1, "Adding local colormap\n")); + ColorMapSize = ColorMap->ColorCount; + } else { + /* No colormap and we are about to read in the image - + abandon for now */ + mm_log((1, "Going in with no colormap\n")); + i_push_error(0, "Image does not have a local or a global color map"); + free_images(results, *count); + DGifCloseFile(GifFile); + myfree(GifRow); + if (comment) + myfree(comment); + return NULL; + } + + channels = 3; + if (got_gce && trans_index >= 0) + channels = 4; + if (!i_int_check_image_file_limits(Width, Height, channels, sizeof(i_sample_t))) { + free_images(results, *count); + mm_log((1, "i_readgif: image size exceeds limits\n")); + DGifCloseFile(GifFile); + myfree(GifRow); + if (comment) + myfree(comment); + return NULL; + } + img = i_img_pal_new(Width, Height, channels, 256); + if (!img) { + free_images(results, *count); + DGifCloseFile(GifFile); + if (comment) + myfree(comment); + myfree(GifRow); + return NULL; + } + /* populate the palette of the new image */ + mm_log((1, "ColorMapSize %d\n", ColorMapSize)); + for (i = 0; i < ColorMapSize; ++i) { + i_color col; + col.rgba.r = ColorMap->Colors[i].Red; + col.rgba.g = ColorMap->Colors[i].Green; + col.rgba.b = ColorMap->Colors[i].Blue; + if (channels == 4 && trans_index == i) + col.rgba.a = 0; + else + col.rgba.a = 255; + + i_addcolors(img, &col, 1); + } + image_colors = ColorMapSize; + ++*count; + if (*count > result_alloc) { + if (result_alloc == 0) { + result_alloc = 5; + results = mymalloc(result_alloc * sizeof(i_img *)); + } + else { + /* myrealloc never fails (it just dies if it can't allocate) */ + result_alloc *= 2; + results = myrealloc(results, result_alloc * sizeof(i_img *)); + } + } + results[*count-1] = img; + i_tags_set(&img->tags, "i_format", "gif", -1); + i_tags_setn(&img->tags, "gif_left", GifFile->Image.Left); + /**(char *)0 = 1;*/ + i_tags_setn(&img->tags, "gif_top", GifFile->Image.Top); + i_tags_setn(&img->tags, "gif_interlace", GifFile->Image.Interlace); + i_tags_setn(&img->tags, "gif_screen_width", GifFile->SWidth); + i_tags_setn(&img->tags, "gif_screen_height", GifFile->SHeight); + i_tags_setn(&img->tags, "gif_colormap_size", ColorMapSize); + if (GifFile->SColorMap && !GifFile->Image.ColorMap) { + i_tags_setn(&img->tags, "gif_background", + GifFile->SBackGroundColor); + } + if (GifFile->Image.ColorMap) { + i_tags_setn(&img->tags, "gif_localmap", 1); + } + if (got_gce) { + if (trans_index >= 0) { + i_color trans; + i_tags_setn(&img->tags, "gif_trans_index", trans_index); + i_getcolors(img, trans_index, &trans, 1); + i_tags_set_color(&img->tags, "gif_trans_color", 0, &trans); + } + i_tags_setn(&img->tags, "gif_delay", gif_delay); + i_tags_setn(&img->tags, "gif_user_input", user_input); + i_tags_setn(&img->tags, "gif_disposal", disposal); + } + got_gce = 0; + if (got_ns_loop) + i_tags_setn(&img->tags, "gif_loop", ns_loop); + if (comment) { + i_tags_set(&img->tags, "gif_comment", comment, strlen(comment)); + myfree(comment); + comment = NULL; + } + + mm_log((1,"i_readgif_multi_low: Image %d at (%d, %d) [%dx%d]: \n", + ImageNum, GifFile->Image.Left, GifFile->Image.Top, Width, Height)); + + if (GifFile->Image.Left + GifFile->Image.Width > GifFile->SWidth || + GifFile->Image.Top + GifFile->Image.Height > GifFile->SHeight) { + i_push_errorf(0, "Image %d is not confined to screen dimension, aborted.\n",ImageNum); + free_images(results, *count); + DGifCloseFile(GifFile); + myfree(GifRow); + if (comment) + myfree(comment); + return(0); + } + + if (GifFile->Image.Interlace) { + for (Count = i = 0; i < 4; i++) { + for (j = InterlacedOffset[i]; j < Height; + j += InterlacedJumps[i]) { + Count++; + if (DGifGetLine(GifFile, GifRow, Width) == GIF_ERROR) { + gif_push_error(); + i_push_error(0, "Reading GIF line"); + free_images(results, *count); + DGifCloseFile(GifFile); + myfree(GifRow); + if (comment) + myfree(comment); + return NULL; + } + + /* range check the scanline if needed */ + if (image_colors != 256) { + int x; + for (x = 0; x < Width; ++x) { + while (GifRow[x] >= image_colors) { + /* expand the palette since a palette index is too big */ + i_addcolors(img, &black, 1); + ++image_colors; + } + } + } + + i_ppal(img, 0, Width, j, GifRow); + } + } + } + else { + for (i = 0; i < Height; i++) { + if (DGifGetLine(GifFile, GifRow, Width) == GIF_ERROR) { + gif_push_error(); + i_push_error(0, "Reading GIF line"); + free_images(results, *count); + DGifCloseFile(GifFile); + myfree(GifRow); + if (comment) + myfree(comment); + return NULL; + } + + /* range check the scanline if needed */ + if (image_colors != 256) { + int x; + for (x = 0; x < Width; ++x) { + while (GifRow[x] >= image_colors) { + /* expand the palette since a palette index is too big */ + i_addcolors(img, &black, 1); + ++image_colors; + } + } + } + + i_ppal(img, 0, Width, i, GifRow); + } + } + + /* must be only one image wanted and that was it */ + if (page != -1) { + myfree(GifRow); + DGifCloseFile(GifFile); + if (comment) + myfree(comment); + return results; + } + } + else { + /* skip the image */ + /* whether interlaced or not, it has the same number of lines */ + /* giflib does't have an interface to skip the image data */ + for (i = 0; i < Height; i++) { + if (DGifGetLine(GifFile, GifRow, Width) == GIF_ERROR) { + gif_push_error(); + i_push_error(0, "Reading GIF line"); + free_images(results, *count); + myfree(GifRow); + DGifCloseFile(GifFile); + if (comment) + myfree(comment); + return NULL; + } + } + + /* kill the comment so we get the right comment for the page */ + if (comment) { + myfree(comment); + comment = NULL; + } + } + ImageNum++; + break; + case EXTENSION_RECORD_TYPE: + /* Skip any extension blocks in file: */ + if (DGifGetExtension(GifFile, &ExtCode, &Extension) == GIF_ERROR) { + gif_push_error(); + i_push_error(0, "Reading extension record"); + free_images(results, *count); + myfree(GifRow); + DGifCloseFile(GifFile); + if (comment) + myfree(comment); + return NULL; + } + /* possibly this should be an error, but "be liberal in what you accept" */ + if (!Extension) + break; + if (ExtCode == 0xF9) { + got_gce = 1; + if (Extension[1] & 1) + trans_index = Extension[4]; + else + trans_index = -1; + gif_delay = Extension[2] + 256 * Extension[3]; + user_input = (Extension[1] & 2) != 0; + disposal = (Extension[1] >> 2) & 7; + } + if (ExtCode == 0xFF && *Extension == 11) { + if (memcmp(Extension+1, "NETSCAPE2.0", 11) == 0) { + if (DGifGetExtensionNext(GifFile, &Extension) == GIF_ERROR) { + gif_push_error(); + i_push_error(0, "reading loop extension"); + free_images(results, *count); + myfree(GifRow); + DGifCloseFile(GifFile); + if (comment) + myfree(comment); + return NULL; + } + if (Extension && *Extension == 3) { + got_ns_loop = 1; + ns_loop = Extension[2] + 256 * Extension[3]; + } + } + } + else if (ExtCode == 0xFE) { + /* while it's possible for a GIF file to contain more than one + comment, I'm only implementing a single comment per image, + with the comment saved into the following image. + If someone wants more than that they can implement it. + I also don't handle comments that take more than one block. + */ + if (!comment) { + comment = mymalloc(*Extension+1); + memcpy(comment, Extension+1, *Extension); + comment[*Extension] = '\0'; + } + } + while (Extension != NULL) { + if (DGifGetExtensionNext(GifFile, &Extension) == GIF_ERROR) { + gif_push_error(); + i_push_error(0, "reading next block of extension"); + free_images(results, *count); + myfree(GifRow); + DGifCloseFile(GifFile); + if (comment) + myfree(comment); + return NULL; + } + } + break; + case TERMINATE_RECORD_TYPE: + break; + default: /* Should be trapped by DGifGetRecordType. */ + break; + } + } while (RecordType != TERMINATE_RECORD_TYPE); + + if (comment) { + if (*count) { + i_tags_set(&(results[*count-1]->tags), "gif_comment", comment, + strlen(comment)); + } + myfree(comment); + } + + myfree(GifRow); + + if (DGifCloseFile(GifFile) == GIF_ERROR) { + gif_push_error(); + i_push_error(0, "Closing GIF file object"); + free_images(results, *count); + return NULL; + } + + if (ImageNum && page != -1) { + /* there were images, but the page selected wasn't found */ + i_push_errorf(0, "page %d not found (%d total)", page, ImageNum); + free_images(results, *count); + return NULL; + } + + return results; +} + +static int io_glue_read_cb(GifFileType *gft, GifByteType *buf, int length); + +/* +=item i_readgif_multi_wiol(ig, int *count) + +=cut +*/ + +i_img ** +i_readgif_multi_wiol(io_glue *ig, int *count) { + GifFileType *GifFile; + + i_clear_error(); + + if ((GifFile = DGifOpen((void *)ig, io_glue_read_cb )) == NULL) { + gif_push_error(); + i_push_error(0, "Cannot create giflib callback object"); + mm_log((1,"i_readgif_multi_wiol: Unable to open callback datasource.\n")); + return NULL; + } + + return i_readgif_multi_low(GifFile, count, -1); +} + +static int +io_glue_read_cb(GifFileType *gft, GifByteType *buf, int length) { + io_glue *ig = (io_glue *)gft->UserData; + + return ig->readcb(ig, buf, length); +} + +i_img * +i_readgif_wiol(io_glue *ig, int **color_table, int *colors) { + GifFileType *GifFile; + + i_clear_error(); + + if ((GifFile = DGifOpen((void *)ig, io_glue_read_cb )) == NULL) { + gif_push_error(); + i_push_error(0, "Cannot create giflib callback object"); + mm_log((1,"i_readgif_wiol: Unable to open callback datasource.\n")); + return NULL; + } + + return i_readgif_low(GifFile, color_table, colors); +} + +/* +=item i_readgif_single_low(GifFile, page) + +Lower level function to read a single image from a GIF. + +page must be non-negative. + +=cut +*/ +static i_img * +i_readgif_single_low(GifFileType *GifFile, int page) { + int count = 0; + i_img **imgs; + + imgs = i_readgif_multi_low(GifFile, &count, page); + + if (imgs && count) { + i_img *result = imgs[0]; + + myfree(imgs); + return result; + } + else { + /* i_readgif_multi_low() handles the errors appropriately */ + return NULL; + } +} + +/* +=item i_readgif_single_wiol(ig, page) + +Read a single page from a GIF image file, where the page is indexed +from 0. + +Returns NULL if the page isn't found. + +=cut +*/ + +i_img * +i_readgif_single_wiol(io_glue *ig, int page) { + i_clear_error(); + + if (page < 0) { + i_push_error(0, "page must be non-negative"); + return NULL; + } + + GifFileType *GifFile; + + if ((GifFile = DGifOpen((void *)ig, io_glue_read_cb )) == NULL) { + gif_push_error(); + i_push_error(0, "Cannot create giflib callback object"); + mm_log((1,"i_readgif_wiol: Unable to open callback datasource.\n")); + return NULL; + } + + return i_readgif_single_low(GifFile, page); +} + +/* +=item do_write(GifFileType *gf, i_gif_opts *opts, i_img *img, i_palidx *data) + +Internal. Low level image write function. Writes in interlace if +that was requested in the GIF options. + +Returns non-zero on success. + +=cut +*/ +static undef_int +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]) { + if (EGifPutLine(gf, data+j*img->xsize, img->xsize) == GIF_ERROR) { + gif_push_error(); + i_push_error(0, "Could not save image data:"); + mm_log((1, "Error in EGifPutLine\n")); + EGifCloseFile(gf); + return 0; + } + } + } + } + else { + int y; + for (y = 0; y < img->ysize; ++y) { + if (EGifPutLine(gf, data, img->xsize) == GIF_ERROR) { + gif_push_error(); + i_push_error(0, "Could not save image data:"); + mm_log((1, "Error in EGifPutLine\n")); + EGifCloseFile(gf); + return 0; + } + data += img->xsize; + } + } + + return 1; +} + +/* +=item do_gce(GifFileType *gf, int index, i_gif_opts *opts, int want_trans, int trans_index) + +Internal. Writes the GIF graphics control extension, if necessary. + +Returns non-zero on success. + +=cut +*/ +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 (i_tags_get_int(&img->tags, "gif_delay", 0, &delay)) { + gce[1] = delay % 256; + gce[2] = delay / 256; + ++want_gce; + } + if (i_tags_get_int(&img->tags, "gif_user_input", 0, &user_input) + && user_input) { + gce[0] |= 2; + ++want_gce; + } + if (i_tags_get_int(&img->tags, "gif_disposal", 0, &disposal_method)) { + gce[0] |= (disposal_method & 3) << 2; + ++want_gce; + } + if (want_gce) { + if (EGifPutExtension(gf, 0xF9, sizeof(gce), gce) == GIF_ERROR) { + gif_push_error(); + i_push_error(0, "Could not save GCE"); + } + } + 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) + +Internal. Add the Netscape2.0 loop extension block, if requested. + +Giflib/libungif prior to 4.1.1 didn't support writing application +extension blocks, so we don't attempt to write them for older versions. + +Giflib/libungif prior to 4.1.3 used the wrong write mechanism when +writing extension blocks so that they could only be written to files. + +=cut +*/ +static int do_ns_loop(GifFileType *gf, i_img *img) +{ + /* EGifPutExtension() doesn't appear to handle application + extension blocks in any way + Since giflib wraps the fd with a FILE * (and puts that in its + private data), we can't do an end-run and write the data + directly to the fd. + There's no open interface that takes a FILE * either, so we + can't workaround it that way either. + If giflib's callback interface wasn't broken by default, I'd + force file writes to use callbacks, but it is broken by default. + */ + /* yes this was another attempt at supporting the loop extension */ + 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 (EGifPutExtensionFirst(gf, APPLICATION_EXT_FUNC_CODE, 11, nsle) == GIF_ERROR) { + gif_push_error(); + i_push_error(0, "writing loop extension"); + return 0; + } + subblock[0] = 1; + subblock[1] = loop_count % 256; + subblock[2] = loop_count / 256; + if (EGifPutExtensionLast(gf, APPLICATION_EXT_FUNC_CODE, 3, subblock) == GIF_ERROR) { + gif_push_error(); + i_push_error(0, "writing loop extension sub-block"); + return 0; + } + } + + return 1; +} + +/* +=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_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; + colors[i].Green = quant->mc_colors[i].rgb.g; + colors[i].Blue = quant->mc_colors[i].rgb.b; + } + if (want_trans) { + 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; + while (map_size < size) + map_size <<= 1; + /* giflib spews for 1 colour maps, reasonable, I suppose */ + if (map_size == 1) + map_size = 2; + while (i < map_size) { + colors[i].Red = colors[i].Green = colors[i].Blue = 0; + ++i; + } + + map = MakeMapObject(map_size, colors); + mm_log((1, "XXX map is at %p and colors at %p\n", map, map->Colors)); + if (!map) { + gif_push_error(); + i_push_error(0, "Could not create color map object"); + return NULL; + } + return map; +} + +/* +=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. + +Unfortunately giflib 4.1.0 crashes when we use this. Internally +giflib 4.1.0 has code: + + static char *GifVersionPrefix = GIF87_STAMP; + +and the code that sets the version internally does: + + strncpy(&GifVersionPrefix[3], Version, 3); + +which is very broken. + +Failing to set the correct GIF version doesn't seem to cause a problem +with readers. + +Modern versions (4.1.4 anyway) of giflib/libungif handle +EGifSetGifVersion correctly. + +If t/t105gif.t crashes here then run Makefile.PL with +--nogifsetversion, eg.: + + perl Makefile.PL --nogifsetversion + +or install a less buggy giflib. + +=cut +*/ + +static void gif_set_version(i_quantize *quant, i_img **imgs, int count) { + int need_89a = 0; + int temp; + int i; + + if (quant->transp != tr_none) + need_89a = 1; + else { + for (i = 0; i < count; ++i) { + if (i_tags_get_int(&imgs[i]->tags, "gif_delay", 0, &temp)) { + need_89a = 1; + break; + } + if (i_tags_get_int(&imgs[i]->tags, "gif_user_input", 0, &temp) && temp) { + need_89a = 1; + break; + } + if (i_tags_get_int(&imgs[i]->tags, "gif_disposal", 0, &temp)) { + need_89a = 1; + break; + } + if (i_tags_get_int(&imgs[i]->tags, "gif_loop", 0, &temp)) { + need_89a = 1; + break; + } + } + } + if (need_89a) + EGifSetGifVersion("89a"); + else + EGifSetGifVersion("87a"); +} + +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) { + int size = quant->mc_count; + int i; + int imgn; + 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) { + int eliminate_unused; + if (imgs[imgn]->type != i_palette_type) + return 0; + + 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)); + + 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) + +Internal. Low-level function that does the high-level GIF processing +:) + +Returns non-zero on success. + +=cut +*/ + +static undef_int +i_writegif_low(i_quantize *quant, GifFileType *gf, i_img **imgs, int count) { + unsigned char *result = NULL; + int color_bits; + ColorMapObject *map; + int scrw = 0, scrh = 0; + int imgn, orig_count, orig_size; + int posx, posy; + int trans_index = -1; + 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 = 0; + int glob_want_trans; + int glob_paletted = 0; /* the global map was made from the image palettes */ + int colors_paletted = 0; + int want_trans = 0; + 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? */ + } + + /* 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, &scrh)) + scrw = 0; + + anylocal = 0; + localmaps = mymalloc(sizeof(int) * count); + glob_imgs = mymalloc(sizeof(i_img *) * count); + glob_img_count = 0; + glob_want_trans = 0; + for (imgn = 0; imgn < count; ++imgn) { + 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]->channels == 4) { + glob_want_trans = 1; + } + glob_imgs[glob_img_count++] = imgs[imgn]; + } + } + glob_want_trans = glob_want_trans && quant->transp != tr_none ; + + orig_count = quant->mc_count; + orig_size = quant->mc_size; + + if (glob_img_count) { + /* this is ugly */ + glob_colors = mymalloc(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; + } + if (has_common_palette(glob_imgs, glob_img_count, quant, want_trans)) { + glob_paletted = 1; + } + else { + glob_paletted = 0; + i_quant_makemap(quant, glob_imgs, glob_img_count); + } + glob_color_count = quant->mc_count; + quant->mc_colors = orig_colors; + } + + /* 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; + } + else { + colors_paletted = 0; + i_quant_makemap(quant, imgs, 1); + } + } + if ((map = make_gif_map(quant, imgs[0], want_trans)) == NULL) { + myfree(glob_colors); + myfree(localmaps); + myfree(glob_imgs); + 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, + gif_background, map) == GIF_ERROR) { + myfree(glob_colors); + myfree(localmaps); + myfree(glob_imgs); + 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 (!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 (!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; + i_quant_makemap(quant, imgs, 1); + } + if ((map = make_gif_map(quant, imgs[0], want_trans)) == NULL) { + myfree(glob_colors); + myfree(localmaps); + myfree(glob_imgs); + EGifCloseFile(gf); + quant->mc_colors = orig_colors; + mm_log((1, "Error in MakeMapObject")); + 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 = i_quant_translate(quant, imgs[0]); + if (!result) { + myfree(glob_colors); + myfree(localmaps); + myfree(glob_imgs); + quant->mc_colors = orig_colors; + EGifCloseFile(gf); + return 0; + } + if (want_trans) { + i_quant_transparent(quant, result, imgs[0], quant->mc_count); + trans_index = quant->mc_count; + } + + if (!do_ns_loop(gf, imgs[0])) { + myfree(glob_colors); + myfree(localmaps); + myfree(glob_imgs); + quant->mc_colors = orig_colors; + return 0; + } + + if (!do_gce(gf, imgs[0], want_trans, trans_index)) { + myfree(glob_colors); + myfree(localmaps); + myfree(glob_imgs); + quant->mc_colors = orig_colors; + myfree(result); + EGifCloseFile(gf); + return 0; + } + + if (!do_comments(gf, imgs[0])) { + myfree(glob_colors); + myfree(localmaps); + myfree(glob_imgs); + 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) { + myfree(glob_colors); + myfree(localmaps); + myfree(glob_imgs); + 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)) { + myfree(glob_colors); + myfree(localmaps); + myfree(glob_imgs); + 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[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; + /* 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+imgn, 1, quant, want_trans)) { + result = quant_paletted(quant, imgs[imgn]); + } + else { + i_quant_makemap(quant, imgs+imgn, 1); + result = i_quant_translate(quant, imgs[imgn]); + } + if (!result) { + myfree(glob_colors); + myfree(localmaps); + myfree(glob_imgs); + quant->mc_colors = orig_colors; + EGifCloseFile(gf); + mm_log((1, "error in i_quant_translate()")); + return 0; + } + if (want_trans) { + i_quant_transparent(quant, result, imgs[imgn], quant->mc_count); + trans_index = quant->mc_count; + } + + if ((map = make_gif_map(quant, imgs[imgn], want_trans)) == NULL) { + myfree(glob_colors); + myfree(localmaps); + myfree(glob_imgs); + quant->mc_colors = orig_colors; + myfree(result); + EGifCloseFile(gf); + mm_log((1, "Error in MakeMapObject.")); + return 0; + } + } + else { + quant->mc_colors = glob_colors; + quant->mc_count = glob_color_count; + if (glob_paletted) + result = quant_paletted(quant, imgs[imgn]); + else + result = i_quant_translate(quant, imgs[imgn]); + want_trans = glob_want_trans && imgs[imgn]->channels == 4; + if (want_trans) { + i_quant_transparent(quant, result, imgs[imgn], quant->mc_count); + trans_index = quant->mc_count; + } + map = NULL; + } + + if (!do_gce(gf, imgs[imgn], want_trans, trans_index)) { + myfree(glob_colors); + myfree(localmaps); + myfree(glob_imgs); + quant->mc_colors = orig_colors; + myfree(result); + EGifCloseFile(gf); + return 0; + } + + if (!do_comments(gf, imgs[imgn])) { + myfree(glob_colors); + myfree(localmaps); + myfree(glob_imgs); + quant->mc_colors = orig_colors; + myfree(result); + EGifCloseFile(gf); + return 0; + } + + 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) { + myfree(glob_colors); + myfree(localmaps); + myfree(glob_imgs); + 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 (map) + FreeMapObject(map); + + if (!do_write(gf, interlace, imgs[imgn], result)) { + myfree(glob_colors); + myfree(localmaps); + myfree(glob_imgs); + quant->mc_colors = orig_colors; + EGifCloseFile(gf); + myfree(result); + return 0; + } + myfree(result); + } + + if (EGifCloseFile(gf) == GIF_ERROR) { + myfree(glob_colors); + myfree(localmaps); + myfree(glob_imgs); + gif_push_error(); + i_push_error(0, "Could not close GIF file"); + mm_log((1, "Error in EGifCloseFile\n")); + return 0; + } + if (glob_colors) { + int i; + for (i = 0; i < glob_color_count; ++i) + orig_colors[i] = glob_colors[i]; + } + + myfree(glob_colors); + myfree(localmaps); + myfree(glob_imgs); + quant->mc_colors = orig_colors; + + return 1; +} + +static int +io_glue_write_cb(GifFileType *gft, const GifByteType *data, int length) { + io_glue *ig = (io_glue *)gft->UserData; + + return ig->writecb(ig, data, length); +} + + +/* +=item i_writegif_wiol(ig, quant, opts, imgs, count) + +=cut +*/ +undef_int +i_writegif_wiol(io_glue *ig, i_quantize *quant, i_img **imgs, + int count) { + GifFileType *GifFile; + int result; + + i_clear_error(); + + gif_set_version(quant, imgs, count); + + if ((GifFile = EGifOpen((void *)ig, io_glue_write_cb )) == NULL) { + gif_push_error(); + i_push_error(0, "Cannot create giflib callback object"); + mm_log((1,"i_writegif_wiol: Unable to open callback datasource.\n")); + return 0; + } + + result = i_writegif_low(quant, GifFile, imgs, count); + + ig->closecb(ig); + + return result; +} + +/* +=item gif_error_msg(int code) + +Grabs the most recent giflib error code from GifLastError() and +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 +*/ + +static char const *gif_error_msg(int code) { + static char msg[80]; + + switch (code) { + case E_GIF_ERR_OPEN_FAILED: /* should not see this */ + return "Failed to open given file"; + + case E_GIF_ERR_WRITE_FAILED: + return "Write failed"; + + case E_GIF_ERR_HAS_SCRN_DSCR: /* should not see this */ + return "Screen descriptor already passed to giflib"; + + case E_GIF_ERR_HAS_IMAG_DSCR: /* should not see this */ + return "Image descriptor already passed to giflib"; + + case E_GIF_ERR_NO_COLOR_MAP: /* should not see this */ + return "Neither global nor local color map set"; + + case E_GIF_ERR_DATA_TOO_BIG: /* should not see this */ + return "Too much pixel data passed to giflib"; + + case E_GIF_ERR_NOT_ENOUGH_MEM: + return "Out of memory"; + + case E_GIF_ERR_DISK_IS_FULL: + return "Disk is full"; + + case E_GIF_ERR_CLOSE_FAILED: /* should not see this */ + return "File close failed"; + + case E_GIF_ERR_NOT_WRITEABLE: /* should not see this */ + return "File not writable"; + + case D_GIF_ERR_OPEN_FAILED: + return "Failed to open file"; + + case D_GIF_ERR_READ_FAILED: + return "Failed to read from file"; + + case D_GIF_ERR_NOT_GIF_FILE: + return "File is not a GIF file"; + + case D_GIF_ERR_NO_SCRN_DSCR: + return "No screen descriptor detected - invalid file"; + + case D_GIF_ERR_NO_IMAG_DSCR: + return "No image descriptor detected - invalid file"; + + case D_GIF_ERR_NO_COLOR_MAP: + return "No global or local color map found"; + + case D_GIF_ERR_WRONG_RECORD: + return "Wrong record type detected - invalid file?"; + + case D_GIF_ERR_DATA_TOO_BIG: + return "Data in file too big for image"; + + case D_GIF_ERR_NOT_ENOUGH_MEM: + return "Out of memory"; + + case D_GIF_ERR_CLOSE_FAILED: + return "Close failed"; + + case D_GIF_ERR_NOT_READABLE: + return "File not opened for read"; + + case D_GIF_ERR_IMAGE_DEFECT: + return "Defective image"; + + case D_GIF_ERR_EOF_TOO_SOON: + return "Unexpected EOF - invalid file"; + + default: + sprintf(msg, "Unknown giflib error code %d", code); + return msg; + } +} + +/* +=item gif_push_error() + +Utility function that takes the current GIF error code, converts it to +an error message and pushes it on the error stack. + +=cut +*/ + +static void gif_push_error(void) { + int code = GifLastError(); /* clears saved error */ + + i_push_error(code, gif_error_msg(code)); +} + +/* +=head1 BUGS + +The Netscape loop extension isn't implemented. Giflib's extension +writing code doesn't seem to support writing named extensions in this +form. + +A bug in giflib is tickled by the i_writegif_callback(). This isn't a +problem on ungiflib, but causes a SEGV on giflib. A patch is provided +in t/t10formats.t + +The GIF file tag (GIF87a vs GIF89a) currently isn't set. Using the +supplied interface in giflib 4.1.0 causes a SEGV in +EGifSetGifVersion(). See L for an explanation. + +=head1 AUTHOR + +Arnar M. Hrafnkelsson, addi@umich.edu + +=head1 SEE ALSO + +perl(1), Imager(3) + +=cut + +*/ diff --git a/GIF/imgif.h b/GIF/imgif.h new file mode 100644 index 00000000..e58d0135 --- /dev/null +++ b/GIF/imgif.h @@ -0,0 +1,13 @@ +#ifndef IMAGER_IMGIF_H +#define IMAGER_IMGIF_H + +#include "imext.h" + +long i_giflib_version(void); +i_img *i_readgif_wiol(io_glue *ig, int **colour_table, int *colours); +i_img *i_readgif_single_wiol(io_glue *ig, int page); +extern i_img **i_readgif_multi_wiol(io_glue *ig, int *count); +undef_int i_writegif_wiol(io_glue *ig, i_quantize *quant, + i_img **imgs, int count); + +#endif diff --git a/GIF/t/t10gif.t b/GIF/t/t10gif.t new file mode 100644 index 00000000..40a8860f --- /dev/null +++ b/GIF/t/t10gif.t @@ -0,0 +1,845 @@ +#!perl -w + +=pod + +IF THIS TEST CRASHES + +Giflib/libungif have a long history of bugs, so if this script crashes +and you aren't running version 4.1.4 of giflib or libungif then +UPGRADE. + +=cut + +use strict; +$|=1; +use Test::More; +use Imager qw(:all); +use Imager::Test qw(is_color3 test_image test_image_raw); +use Imager::File::GIF; + +use Carp 'confess'; +$SIG{__DIE__} = sub { confess @_ }; + +-d "testout" or mkdir "testout"; + +init_log("testout/t105gif.log",1); + +plan tests => 144; + +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); + +my $img=test_image_raw; + +my $gifver = Imager::File::GIF::i_giflib_version(); +diag("giflib version (from header) $gifver"); + +{ + open(FH,">testout/t105.gif") || die "Cannot open testout/t105.gif\n"; + binmode(FH); + my $io = Imager::io_new_fd(fileno(FH)); + ok(Imager::File::GIF::i_writegif_wiol($io, {}, $img), "write low") or + die "Cannot write testout/t105.gif\n"; + $io->close; + close(FH); +} + +{ + open(FH,"testout/t105.gif") || die "Cannot open testout/t105.gif\n"; + binmode(FH); + my $io = Imager::io_new_fd(fileno(FH)); + ok($img=Imager::File::GIF::i_readgif_wiol($io), "read low") + or die "Cannot read testout/t105.gif\n"; + close(FH); +} + +{ + open(FH,"testout/t105.gif") || die "Cannot open testout/t105.gif\n"; + binmode(FH); + my $io = Imager::io_new_fd(fileno(FH)); + ($img, my $palette)=Imager::File::GIF::i_readgif_wiol($io); + ok($img, "read palette") or die "Cannot read testout/t105.gif\n"; + close(FH); + $palette=''; # just to skip a warning. +} + + +# check that reading interlaced/non-interlaced versions of +# the same GIF produce the same image +# I could replace this with code that used Imager's built-in +# 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; + my $IO = Imager::io_new_fd( fileno(FH) ); + i_writeppm_wiol($imgi, $IO) + or die "Cannot write testout/t105i.ppm"; + close FH; + + open FH, ">testout/t105ni.ppm" or die "Cannot create testout/t105ni.ppm"; + binmode FH; + $IO = Imager::io_new_fd( fileno(FH) ); + i_writeppm_wiol($imgni, $IO) + or die "Cannot write testout/t105ni.ppm"; + close FH; + + # compare them + open FH, " }; + close FH; + + open FH, " }; + close FH; + is($datai, $datani, "images match"); +} + +{ + # reading with a callback + # various sizes to make sure the buffering works + # requested size + open FH, "testout/t105_gen.gif" or die $!; + binmode FH; + my $io = Imager::io_new_fd(fileno(FH)); + ok(Imager::File::GIF::i_writegif_wiol + ($io, { make_colors=>'webmap', + translate=>'errdiff', + errdiff=>'custom', + errdiff_width=>2, + errdiff_height=>2, + errdiff_map=>[0, 1, 1, 0]}, $img), + "webmap, custom errdif map"); + close FH; +} + +print "# the following tests are fairly slow\n"; + +# test animation, mc_addi, error diffusion, ordered transparency +my @imgs; +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); + } + i_box_filled($im, 0, $i*40, 199, 199, $blue); + push(@imgs, $im); +} +{ + my @gif_delays = (50) x 5; + my @gif_disposal = (2) x 5; + open FH, ">testout/t105_anim.gif" or die $!; + binmode FH; + my $io = Imager::io_new_fd(fileno(FH)); + ok(Imager::File::GIF::i_writegif_wiol + ($io, { make_colors=>'addi', + translate=>'closest', + gif_delays=>\@gif_delays, + gif_disposal=>\@gif_disposal, + gif_positions=> [ map [ $_*10, $_*10 ], 0..4 ], + gif_user_input=>[ 1, 0, 1, 0, 1 ], + transp=>'ordered', + 'tr_orddith'=>'dot8'}, @imgs), + "write anim gif"); + close FH; +} + +@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) { + $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); + } + } + push(@imgs, $im); +} +# test giflib with multiple palettes +# (it was meant to test the NS loop extension too, but that's broken) +# this looks better with make_colors=>'addi', translate=>'errdiff' +# this test aims to overload the palette for each image, so the +# output looks moderately horrible +{ + open FH, ">testout/t105_mult_pall.gif" or die "Cannot create file: $!"; + binmode FH; + my $io = Imager::io_new_fd(fileno(FH)); + ok(Imager::File::GIF::i_writegif_wiol + ($io, { #make_colors=>'webmap', + translate=>'giflib', + }, @imgs), "write multiple palettes") + or print "# ", join(":", map $_->[1], Imager::i_errors()),"\n"; + close FH; +} + +{ + # regression test: giflib doesn't like 1 colour images + my $img1 = Imager::ImgRaw::new(100, 100, 3); + i_box_filled($img1, 0, 0, 100, 100, $red); + open FH, ">testout/t105_onecol.gif" or die $!; + binmode FH; + my $io = Imager::io_new_fd(fileno(FH)); + ok(Imager::File::GIF::i_writegif_wiol($io, { translate=>'giflib'}, $img1), + "single colour write regression"); + close FH; +} + +{ + # transparency test + # previously it was harder do write transparent images + # tests the improvements + my $timg = Imager::ImgRaw::new(20, 20, 4); + my $trans = i_color_new(255, 0, 0, 127); + i_box_filled($timg, 0, 0, 20, 20, $green); + i_box_filled($timg, 2, 2, 18, 18, $trans); + open FH, ">testout/t105_trans.gif" or die $!; + binmode FH; + my $io = Imager::io_new_fd(fileno(FH)); + ok(Imager::File::GIF::i_writegif_wiol + ($io, { make_colors=>'addi', + translate=>'closest', + transp=>'ordered', + }, $timg), "write transparent"); + close FH; +} + +# some error handling tests +# open a file handle for read and try to save to it +# is this idea portable? +# whether or not it is, giflib segfaults on this +#open FH, "testout/t105_none.gif" + or die "Cannot open testout/t105_none.gif: $!"; + binmode FH; + my $io = Imager::io_new_fd(fileno(FH)); + if (ok(!Imager::File::GIF::i_writegif_wiol($io, {}, "hello"), "shouldn't be able to write a string as a gif")) { + print "# ",Imager::_error_as_msg(),"\n"; + } +} + +# try to read a truncated gif (no image descriptors) +read_failure('testimg/trimgdesc.gif'); +# file truncated just after the image descriptor tag +read_failure('testimg/trmiddesc.gif'); +# image has no colour map +read_failure('testimg/nocmap.gif'); + +{ + # image has a local colour map + open FH, "< testimg/loccmap.gif" + or die "Cannot open testimg/loccmap.gif: $!"; + binmode FH; + my $io = Imager::io_new_fd(fileno(FH)); + ok(Imager::File::GIF::i_readgif_wiol($io), + "read an image with only a local colour map"); + close FH; +} + +{ + # image has global and local colour maps + open FH, "< testimg/screen2.gif" + or die "Cannot open testimg/screen2.gif: $!"; + binmode FH; + my $io = Imager::io_new_fd(fileno(FH)); + my $ims = Imager::File::GIF::i_readgif_wiol($io); + unless (ok($ims, "read an image with global and local colour map")) { + print "# ",Imager::_error_as_msg(),"\n"; + } + close FH; + + open FH, "< testimg/expected.gif" + or die "Cannot open testimg/expected.gif: $!"; + binmode FH; + $io = Imager::io_new_fd(fileno(FH)); + my $ime = Imager::File::GIF::i_readgif_wiol($io); + close FH; + ok($ime, "reading testimg/expected.gif"); + SKIP: + { + skip("could not read one or both of expected.gif or loccamp.gif", 1) + unless $ims and $ime; + unless (is(i_img_diff($ime, $ims), 0, + "compare loccmap and expected")) { + # save the bad one + open FH, "> testout/t105_screen2.gif" + or die "Cannot create testout/t105_screen.gif: $!"; + binmode FH; + my $io = Imager::io_new_fd(fileno(FH)); + Imager::File::GIF::i_writegif_wiol($io, {}, $ims) + or print "# could not save t105_screen.gif\n"; + close FH; + } + } +} + +{ + # test reading a multi-image file into multiple images + open FH, "< testimg/screen2.gif" + or die "Cannot open testimg/screen2.gif: $!"; + binmode FH; + my $io = Imager::io_new_fd(fileno(FH)); + @imgs = Imager::File::GIF::i_readgif_multi_wiol($io); + ok(@imgs, "read multi-image file into multiple images"); + close FH; + is(@imgs, 2, "should be 2 images"); + my $paletted = 1; + for my $img (@imgs) { + unless (Imager::i_img_type($img) == 1) { + $paletted = 0; + last; + } + } + ok($paletted, "both images should be paletted"); + is(Imager::i_colorcount($imgs[0]), 4, "4 colours in first image"); + is(Imager::i_colorcount($imgs[1]), 2, "2 colours in second image"); + ok(Imager::i_tags_find($imgs[0], "gif_left", 0), + "gif_left tag should be there"); + my @tags = map {[ Imager::i_tags_get($imgs[1], $_) ]} 0..Imager::i_tags_count($imgs[1])-1; + my ($left) = grep $_->[0] eq 'gif_left', @tags; + ok($left && $left->[1] == 3, "check gif_left value"); +} + +{ + # screen3.gif was saved with + open FH, "< testimg/screen3.gif" + or die "Cannot open testimg/screen3.gif: $!"; + binmode FH; + my $io = Imager::io_new_fd(fileno(FH)); + @imgs = Imager::File::GIF::i_readgif_multi_wiol($io); + ok(@imgs, "read screen3.gif"); + close FH; + eval { + require 'Data/Dumper.pm'; + Data::Dumper->import(); + }; + + unless ($@) { + # build a big map of all tags for all images + my @tags = + map { + my $im = $_; + [ + map { join ",", map { defined() ? $_ : "undef" } Imager::i_tags_get($im, $_) } + 0..Imager::i_tags_count($_)-1 + ] + } @imgs; + my $dump = Dumper(\@tags); + $dump =~ s/^/# /mg; + print "# tags from gif\n", $dump; + } + + # at this point @imgs should contain only paletted images + ok(Imager::i_img_type($imgs[0]) == 1, "imgs[0] paletted"); + ok(Imager::i_img_type($imgs[1]) == 1, "imgs[1] paletted"); + + # see how we go saving it + open FH, ">testout/t105_pal.gif" or die $!; + binmode FH; + $io = Imager::io_new_fd(fileno(FH)); + ok(Imager::File::GIF::i_writegif_wiol + ($io, { 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; + $io = Imager::io_new_fd(fileno(FH)); + ok((my @imgs2 = Imager::File::GIF::i_readgif_multi_wiol($io)) == 2, + "re-reading saved paletted images"); + ok(i_img_diff($imgs[0], $imgs2[0]) == 0, "imgs[0] mismatch"); + ok(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($ooim->read(file=>"testout/t105.gif"), "read into object"); + ok($ooim->write(file=>"testout/t105_warn.gif", interlace=>1), + "save from object") + or print "# ", $ooim->errstr, "\n"; + ok(grep(/Obsolete .* interlace .* gif_interlace/, @warns), + "check for warning"); + init(warn_obsolete=>0); + @warns = (); + ok($ooim->write(file=>"testout/t105_warn.gif", interlace=>1), + "save from object"); + ok(!grep(/Obsolete .* interlace .* gif_interlace/, @warns), + "check for warning"); +} + +# test that we get greyscale from 1 channel images +# we check for each makemap, and for each translate +print "# test writes of grayscale images - ticket #365\n"; +my $ooim = Imager->new(xsize=>50, ysize=>50, channels=>1); +for (my $y = 0; $y < 50; $y += 10) { + $ooim->box(box=>[ 0, $y, 49, $y+9], color=>NC($y*5,0,0), filled=>1); +} +my $ooim3 = $ooim->convert(preset=>'rgb'); +#$ooim3->write(file=>'testout/t105gray.ppm'); +my %maxerror = ( mediancut => 51000, + addi => 0, + closest => 0, + perturb => 0, + errdiff => 0 ); +for my $makemap (qw(mediancut addi)) { + print "# make_colors => $makemap\n"; + ok( $ooim->write(file=>"testout/t105gray-$makemap.gif", + make_colors=>$makemap, + gifquant=>'gen'), + "writing gif with makemap $makemap"); + my $im2 = Imager->new; + if (ok($im2->read(file=>"testout/t105gray-$makemap.gif"), + "reading written grayscale gif")) { + my $diff = i_img_diff($ooim3->{IMG}, $im2->{IMG}); + ok($diff <= $maxerror{$makemap}, "comparing images $diff"); + #$im2->write(file=>"testout/t105gray-$makemap.ppm"); + } + else { + SKIP: { skip("could not get test image", 1); } + } +} +for my $translate (qw(closest perturb errdiff)) { + print "# translate => $translate\n"; + my @colors = map NC($_*50, $_*50, $_*50), 0..4; + ok($ooim->write(file=>"testout/t105gray-$translate.gif", + translate=>$translate, + make_colors=>'none', + colors=>\@colors, + gifquant=>'gen'), + "writing gif with translate $translate"); + my $im2 = Imager->new; + if (ok($im2->read(file=>"testout/t105gray-$translate.gif"), + "reading written grayscale gif")) { + my $diff = i_img_diff($ooim3->{IMG}, $im2->{IMG}); + ok($diff <= $maxerror{$translate}, "comparing images $diff"); + #$im2->write(file=>"testout/t105gray-$translate.ppm"); + } + else { + SKIP: { skip("could not load test image", 1) } + } + } + +# try to write an image with no colors - should error +ok(!$ooim->write(file=>"testout/t105nocolors.gif", + make_colors=>'none', + colors=>[], gifquant=>'gen'), + "write with no colors"); + +# try to write multiple with no colors, with separate maps +# I don't see a way to test this, since we don't have a mechanism +# to give the second image different quant options, we can't trigger +# a failure just for the second image + +# check that the i_format tag is set for both multiple and single +# image reads +{ + my @anim = Imager->read_multi(file=>"testout/t105_anim.gif"); + ok(@anim == 5, "check we got all the images"); + for my $frame (@anim) { + my ($type) = $frame->tags(name=>'i_format'); + is($type, 'gif', "check i_format for animation frame"); + } + + my $im = Imager->new; + ok($im->read(file=>"testout/t105.gif"), "read some gif"); + my ($type) = $im->tags(name=>'i_format'); + is($type, 'gif', 'check i_format for single image read'); +} + +{ # check file limits are checked + my $limit_file = "testout/t105.gif"; + ok(Imager->set_file_limits(reset=>1, width=>149), "set width limit 149"); + my $im = Imager->new; + ok(!$im->read(file=>$limit_file), + "should fail read due to size limits"); + print "# ",$im->errstr,"\n"; + like($im->errstr, qr/image width/, "check message"); + + ok(Imager->set_file_limits(reset=>1, height=>149), "set height limit 149"); + ok(!$im->read(file=>$limit_file), + "should fail read due to size limits"); + print "# ",$im->errstr,"\n"; + like($im->errstr, qr/image height/, "check message"); + + ok(Imager->set_file_limits(reset=>1, width=>150), "set width limit 150"); + ok($im->read(file=>$limit_file), + "should succeed - just inside width limit"); + ok(Imager->set_file_limits(reset=>1, height=>150), "set height limit 150"); + ok($im->read(file=>$limit_file), + "should succeed - just inside height limit"); + + # 150 x 150 x 3 channel image uses 67500 bytes + ok(Imager->set_file_limits(reset=>1, bytes=>67499), + "set bytes limit 67499"); + ok(!$im->read(file=>$limit_file), + "should fail - too many bytes"); + print "# ",$im->errstr,"\n"; + like($im->errstr, qr/storage size/, "check error message"); + ok(Imager->set_file_limits(reset=>1, bytes=>67500), + "set bytes limit 67500"); + ok($im->read(file=>$limit_file), + "should succeed - just inside bytes limit"); + Imager->set_file_limits(reset=>1); +} + +{ + print "# test OO interface reading of consolidated images\n"; + my $im = Imager->new; + ok($im->read(file=>'testimg/screen2.gif', gif_consolidate=>1), + "read image to consolidate"); + my $expected = Imager->new; + ok($expected->read(file=>'testimg/expected.gif'), + "read expected via OO"); + is(i_img_diff($im->{IMG}, $expected->{IMG}), 0, + "compare them"); + + # check the default read doesn't match + ok($im->read(file=>'testimg/screen2.gif'), + "read same image without consolidate"); + isnt(i_img_diff($im->{IMG}, $expected->{IMG}), 0, + "compare them - shouldn't include the overlayed second image"); +} +{ + print "# test the reading of single pages\n"; + # build a test file + my $test_file = 'testout/t105_multi_sing.gif'; + my $im1 = Imager->new(xsize=>100, ysize=>100); + $im1->box(filled=>1, color=>$blue); + $im1->addtag(name=>'gif_left', value=>10); + $im1->addtag(name=>'gif_top', value=>15); + $im1->addtag(name=>'gif_comment', value=>'First page'); + my $im2 = Imager->new(xsize=>50, ysize=>50); + $im2->box(filled=>1, color=>$red); + $im2->addtag(name=>'gif_left', value=>30); + $im2->addtag(name=>'gif_top', value=>25); + $im2->addtag(name=>'gif_comment', value=>'Second page'); + my $im3 = Imager->new(xsize=>25, ysize=>25); + $im3->box(filled=>1, color=>$green); + $im3->addtag(name=>'gif_left', value=>35); + $im3->addtag(name=>'gif_top', value=>45); + # don't set comment for $im3 + ok(Imager->write_multi({ file=> $test_file}, $im1, $im2, $im3), + "write test file for single page reads"); + + my $res = Imager->new; + # check we get the first image + ok($res->read(file=>$test_file), "read default (first) page"); + is(i_img_diff($im1->{IMG}, $res->{IMG}), 0, "compare against first"); + # check tags + is($res->tags(name=>'gif_left'), 10, "gif_left"); + is($res->tags(name=>'gif_top'), 15, "gif_top"); + is($res->tags(name=>'gif_comment'), 'First page', "gif_comment"); + + # get the second image + ok($res->read(file=>$test_file, page=>1), "read second page") + or print "# ",$res->errstr, "\n"; + is(i_img_diff($im2->{IMG}, $res->{IMG}), 0, "compare against second"); + # check tags + is($res->tags(name=>'gif_left'), 30, "gif_left"); + is($res->tags(name=>'gif_top'), 25, "gif_top"); + is($res->tags(name=>'gif_comment'), 'Second page', "gif_comment"); + + # get the third image + ok($res->read(file=>$test_file, page=>2), "read third page") + or print "# ",$res->errstr, "\n"; + is(i_img_diff($im3->{IMG}, $res->{IMG}), 0, "compare against third"); + is($res->tags(name=>'gif_left'), 35, "gif_left"); + is($res->tags(name=>'gif_top'), 45, "gif_top"); + is($res->tags(name=>'gif_comment'), undef, 'gif_comment undef'); + + # try to read a fourth page + ok(!$res->read(file=>$test_file, page=>3), "fail reading fourth page"); + cmp_ok($res->errstr, "=~", 'page 3 not found', + "check error message"); +} +SKIP: +{ + skip("gif_loop not supported on giflib before 4.1", 6) + unless $gifver >= 4.1; + # testing writing the loop extension + my $im1 = Imager->new(xsize => 100, ysize => 100); + $im1->box(filled => 1, color => '#FF0000'); + my $im2 = Imager->new(xsize => 100, ysize => 100); + $im2->box(filled => 1, color => '#00FF00'); + ok(Imager->write_multi({ + gif_loop => 5, + gif_delay => 50, + file => 'testout/t105loop.gif' + }, $im1, $im2), + "write with loop extension"); + + my @im = Imager->read_multi(file => 'testout/t105loop.gif'); + is(@im, 2, "read loop images back"); + is($im[0]->tags(name => 'gif_loop'), 5, "first loop read back"); + is($im[1]->tags(name => 'gif_loop'), 5, "second loop read back"); + is($im[0]->tags(name => 'gif_delay'), 50, "first delay read back"); + is($im[1]->tags(name => 'gif_delay'), 50, "second delay read back"); +} +SKIP: +{ # check graphic control extension and ns loop tags are read correctly + print "# check GCE and netscape loop extension tag values\n"; + my @im = Imager->read_multi(file => 'testimg/screen3.gif'); + is(@im, 2, "read 2 images from screen3.gif") + or skip("Could not load testimg/screen3.gif:".Imager->errstr, 11); + is($im[0]->tags(name => 'gif_delay'), 50, "0 - gif_delay"); + is($im[0]->tags(name => 'gif_disposal'), 2, "0 - gif_disposal"); + is($im[0]->tags(name => 'gif_trans_index'), undef, "0 - gif_trans_index"); + is($im[0]->tags(name => 'gif_user_input'), 0, "0 - gif_user_input"); + is($im[0]->tags(name => 'gif_loop'), 0, "0 - gif_loop"); + is($im[1]->tags(name => 'gif_delay'), 50, "1 - gif_delay"); + is($im[1]->tags(name => 'gif_disposal'), 2, "1 - gif_disposal"); + is($im[1]->tags(name => 'gif_trans_index'), 7, "1 - gif_trans_index"); + is($im[1]->tags(name => 'gif_trans_color'), 'color(255,255,255,0)', + "1 - gif_trans_index"); + is($im[1]->tags(name => 'gif_user_input'), 0, "1 - gif_user_input"); + is($im[1]->tags(name => 'gif_loop'), 0, "1 - gif_loop"); +} + +{ + # manually modified from a small gif, this had the palette + # size changed to half the size, leaving an index out of range + my $im = Imager->new; + ok($im->read(file => 'testimg/badindex.gif', type => 'gif'), + "read bad index gif") + or print "# ", $im->errstr, "\n"; + my @indexes = $im->getscanline('y' => 0, type => 'index'); + is_deeply(\@indexes, [ 0..4 ], "check for correct indexes"); + is($im->colorcount, 5, "check the palette was adjusted"); + is_color3($im->getpixel('y' => 0, x => 4), 0, 0, 0, + "check it was black added"); + is($im->tags(name => 'gif_colormap_size'), 4, 'color map size tag'); +} + +{ + ok(grep($_ eq 'gif', Imager->read_types), "check gif in read types"); + ok(grep($_ eq 'gif', Imager->write_types), "check gif in write types"); +} + +{ + # check screen tags handled correctly note the screen size + # supplied is larger than the box covered by the images + my $im1 = Imager->new(xsize => 10, ysize => 8); + $im1->settag(name => 'gif_top', value => 4); + $im1->settag(name => 'gif_screen_width', value => 18); + $im1->settag(name => 'gif_screen_height', value => 16); + my $im2 = Imager->new(xsize => 7, ysize => 10); + $im2->settag(name => 'gif_left', value => 3); + my @im = ( $im1, $im2 ); + + my $data; + ok(Imager->write_multi({ data => \$data, type => 'gif' }, @im), + "write with screen settings") + or print "# ", Imager->errstr, "\n"; + my @result = Imager->read_multi(data => $data); + is(@result, 2, "got 2 images back"); + is($result[0]->tags(name => 'gif_screen_width'), 18, + "check result screen width"); + is($result[0]->tags(name => 'gif_screen_height'), 16, + "check result screen height"); + is($result[0]->tags(name => 'gif_left'), 0, + "check first gif_left"); + is($result[0]->tags(name => 'gif_top'), 4, + "check first gif_top"); + is($result[1]->tags(name => 'gif_left'), 3, + "check second gif_left"); + is($result[1]->tags(name => 'gif_top'), 0, + "check second gif_top"); +} + +{ # test colors array returns colors + my $data; + my $im = test_image(); + my @colors; + ok($im->write(data => \$data, + colors => \@colors, + make_colors => 'webmap', + translate => 'closest', + gifquant => 'gen', + type => 'gif'), + "write using webmap to check color table"); + is(@colors, 216, "should be 216 colors in the webmap"); + is_color3($colors[0], 0, 0, 0, "first should be 000000"); + is_color3($colors[1], 0, 0, 0x33, "second should be 000033"); + is_color3($colors[8], 0, 0x33, 0x66, "9th should be 003366"); +} + +{ # a zero length extension could make read_/read_multi crash + my ($im) = Imager->read_multi(file => "testimg/zerocomm.gif"); + ok($im, "read image with zero-length extension"); +} + +sub test_readgif_cb { + my ($size) = @_; + + open FH, "deltag(code=>0); + } +} + +sub _add_tags { + my ($img, %tags) = @_; + + for my $key (keys %tags) { + Imager::i_tags_add($img, $key, 0, $tags{$key}, 0); + } +} + +sub ext_test { + my ($testnum, $code, $count, $name) = @_; + + $count ||= 1; + $name ||= "gif$testnum"; + + # build our code + my $script = "testout/$name.pl"; + if (open SCRIPT, "> $script") { + print SCRIPT <<'PROLOG'; +#!perl -w +if (lc $^O eq 'mswin32') { + # avoid the dialog box that window's pops up on a GPF + # if you want to debug this stuff, I suggest you comment out the + # following + eval { + require Win32API::File; + Win32API::File::SetErrorMode( Win32API::File::SEM_NOGPFAULTERRORBOX()); + }; +} +PROLOG + + print SCRIPT $code; + close SCRIPT; + + my $perl = $^X; + $perl = qq/"$perl"/ if $perl =~ / /; + + print "# script: $script\n"; + my $cmd = "$perl -Mblib $script"; + print "# command: $cmd\n"; + + my $ok = 1; + my @out = `$cmd`; # should work on DOS and Win32 + my $found = 0; + for (@out) { + if (/^not ok\s+(?:\d+\s*)?#(.*)/ || /^not ok/) { + my $msg = $1 || ''; + ok(0, $msg); + $ok = 0; + ++$found; + } + elsif (/^ok\s+(?:\d+\s*)?#(.*)/ || /^ok/) { + my $msg = $1 || ''; + ok(1, $msg); + ++$found; + } + } + unless ($count == $found) { + print "# didn't see enough ok/not ok\n"; + $ok = 0; + } + return $ok; + } + else { + return skip("could not create test script $script: $!"); + return 0; + } +} diff --git a/GIF/t/t20new.t b/GIF/t/t20new.t new file mode 100644 index 00000000..f68d9a29 --- /dev/null +++ b/GIF/t/t20new.t @@ -0,0 +1,108 @@ +#!perl -w +# Before `make install' is performed this script should be runnable with +# `make test'. After `make install' it should work as `perl test.pl' + +######################### We start with some black magic to print on failure. + +# Change 1..1 below to 1..last_test_to_print . +# (It may become useful if the test is moved to ./t subdirectory.) + + +use strict; +use Test::More tests => 21; + +use Imager qw(:all :handy); +use Imager::Test qw(test_image is_color3); + +Imager::init('log'=>'testout/t70newgif.log'); + +my $green=i_color_new(0,255,0,0); +my $blue=i_color_new(0,0,255,0); + +{ + my $img = test_image(); + + ok($img->write(file=>'testout/t70newgif.gif',type=>'gif',gifplanes=>1,gifquant=>'lm',lmfixed=>[$green,$blue])) + or print "# failed: ",$img->{ERRSTR}, "\n"; +} + +SKIP: +{ + # make sure the palette is loaded properly (minimal test) + my $im2 = Imager->new(); + my $map; + ok($im2->read(file=>'testimg/bandw.gif', colors=>\$map)) + or skip("Can't load bandw.gif", 5); + # check the palette + ok($map) + or skip("No palette", 4); + is(@$map, 2) + or skip("Bad map count", 3); + my @sorted = sort { comp_entry($a,$b) } @$map; + # first entry must be #000000 and second #FFFFFF + is_color3($sorted[0], 0,0,0, "check first palette entry"); + is_color3($sorted[1], 255,255,255, "check second palette entry"); +} + +{ + # test the read_multi interface + my @imgs = Imager->read_multi(); + ok(!@imgs, "read with no sources should fail"); + like(Imager->errstr, qr/callback parameter missing/, "check error"); + print "# ",Imager->errstr,"\n"; + + @imgs = Imager->read_multi(type=>'gif'); + ok(!@imgs, "read multi no source but type should fail"); + like(Imager->errstr, qr/file/, "check error"); + + # kill warning + *NONESUCH = \20; + @imgs = Imager->read_multi(type=>'gif', fh=>*NONESUCH); + ok(!@imgs, "read from bad fh"); + like(Imager->errstr, qr/fh option not open/, "check message"); + print "# ",Imager->errstr,"\n"; + { + @imgs = Imager->read_multi(type=>'gif', file=>'testimg/screen2.gif'); + is(@imgs, 2, "should read 2 images"); + isa_ok($imgs[0], "Imager"); + isa_ok($imgs[1], "Imager"); + is($imgs[0]->type, "paletted"); + is($imgs[1]->type, "paletted"); + my @left = $imgs[0]->tags(name=>'gif_left'); + is(@left, 1); + my $left = $imgs[1]->tags(name=>'gif_left'); + is($left, 3); + } + { + open FH, "< testimg/screen2.gif" + or die "Cannot open testimg/screen2.gif: $!"; + binmode FH; + my $cb = + sub { + my $tmp; + read(FH, $tmp, $_[0]) and $tmp + }; + @imgs = Imager->read_multi(type=>'gif', + callback => $cb); + close FH; + is(@imgs, 2, "read multi from callback"); + + open FH, "< testimg/screen2.gif" + or die "Cannot open testimg/screen2.gif: $!"; + binmode FH; + my $data = do { local $/; ; }; + close FH; + @imgs = Imager->read_multi(type=>'gif', + data=>$data); + is(@imgs, 2, "read multi from data"); + } +} + +sub comp_entry { + my ($l, $r) = @_; + my @l = $l->rgba; + my @r = $r->rgba; + return $l[0] <=> $r[0] + || $l[1] <=> $r[1] + || $l[2] <=> $r[2]; +} diff --git a/GIF/testimg/badindex.gif b/GIF/testimg/badindex.gif new file mode 100644 index 00000000..3591f487 Binary files /dev/null and b/GIF/testimg/badindex.gif differ diff --git a/GIF/testimg/bandw.gif b/GIF/testimg/bandw.gif new file mode 100644 index 00000000..93fb1d72 Binary files /dev/null and b/GIF/testimg/bandw.gif differ diff --git a/GIF/testimg/expected.gif b/GIF/testimg/expected.gif new file mode 100644 index 00000000..3409d386 Binary files /dev/null and b/GIF/testimg/expected.gif differ diff --git a/GIF/testimg/loccmap.gif b/GIF/testimg/loccmap.gif new file mode 100644 index 00000000..9dd264eb Binary files /dev/null and b/GIF/testimg/loccmap.gif differ diff --git a/GIF/testimg/nocmap.gif b/GIF/testimg/nocmap.gif new file mode 100644 index 00000000..83941109 Binary files /dev/null and b/GIF/testimg/nocmap.gif differ diff --git a/GIF/testimg/scale.gif b/GIF/testimg/scale.gif new file mode 100644 index 00000000..265ed7f9 Binary files /dev/null and b/GIF/testimg/scale.gif differ diff --git a/GIF/testimg/scalei.gif b/GIF/testimg/scalei.gif new file mode 100644 index 00000000..3b3234a6 Binary files /dev/null and b/GIF/testimg/scalei.gif differ diff --git a/GIF/testimg/screen2.gif b/GIF/testimg/screen2.gif new file mode 100644 index 00000000..4dddf699 Binary files /dev/null and b/GIF/testimg/screen2.gif differ diff --git a/GIF/testimg/screen3.gif b/GIF/testimg/screen3.gif new file mode 100644 index 00000000..77f808ed Binary files /dev/null and b/GIF/testimg/screen3.gif differ diff --git a/GIF/testimg/trimgdesc.gif b/GIF/testimg/trimgdesc.gif new file mode 100644 index 00000000..f352b0ed Binary files /dev/null and b/GIF/testimg/trimgdesc.gif differ diff --git a/GIF/testimg/trmiddesc.gif b/GIF/testimg/trmiddesc.gif new file mode 100644 index 00000000..386e3c12 Binary files /dev/null and b/GIF/testimg/trmiddesc.gif differ diff --git a/GIF/testimg/zerocomm.gif b/GIF/testimg/zerocomm.gif new file mode 100644 index 00000000..ede9f6af Binary files /dev/null and b/GIF/testimg/zerocomm.gif differ diff --git a/Imager.pm b/Imager.pm index fe3b0c50..d817cb8a 100644 --- a/Imager.pm +++ b/Imager.pm @@ -81,14 +81,6 @@ use Imager::Font; i_writetiff_wiol i_writetiff_wiol_faxable - i_readgif - i_readgif_wiol - i_readgif_callback - i_writegif - i_writegifmc - i_writegif_gen - i_writegif_callback - i_readpnm_wiol i_writeppm_wiol @@ -1907,21 +1899,7 @@ sub write_multi { ($IO, $file) = $class->_get_writer_io($opts, $type) or return undef; - if ($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}) { - # assume the caller wants the same delay for each frame - $opts->{gif_delays} = [ ($gif_delays) x @images ]; - } - unless (i_writegif_wiol($IO, $opts, @work)) { - $class->_set_error($class->_error_as_msg()); - return undef; - } - } - elsif ($type eq 'tiff') { + if ($type eq 'tiff') { $class->_set_opts($opts, "tiff_", @images) or return; $class->_set_opts($opts, "exif_", @images) diff --git a/Imager.xs b/Imager.xs index f42d22d7..821b1a1f 100644 --- a/Imager.xs +++ b/Imager.xs @@ -19,6 +19,7 @@ extern "C" { #include "dynaload.h" #include "regmach.h" #include "imextdef.h" +#include "imextpltypes.h" #if i_int_hlines_testing() #include "imageri.h" @@ -590,7 +591,8 @@ static struct value_name orddith_names[] = }; /* look through the hash for quantization options */ -static void handle_quant_opts(pTHX_ i_quantize *quant, HV *hv) +static void +ip_handle_quant_opts(pTHX_ i_quantize *quant, HV *hv) { /*** POSSIBLY BROKEN: do I need to unref the SV from hv_fetch ***/ SV **sv; @@ -721,14 +723,16 @@ static void handle_quant_opts(pTHX_ i_quantize *quant, HV *hv) quant->perturb = SvIV(*sv); } -static void cleanup_quant_opts(i_quantize *quant) { +static void +ip_cleanup_quant_opts(pTHX_ i_quantize *quant) { myfree(quant->mc_colors); if (quant->ed_map) myfree(quant->ed_map); } /* copies the color map from the hv into the colors member of the HV */ -static void copy_colors_back(pTHX_ HV *hv, i_quantize *quant) { +static void +ip_copy_colors_back(pTHX_ HV *hv, i_quantize *quant) { SV **sv; AV *av; int i; @@ -928,6 +932,18 @@ i_int_hlines_dump(i_int_hlines *hlines) { #endif +static im_pl_ext_funcs im_perl_funcs = +{ + IMAGER_PL_API_VERSION, + IMAGER_PL_API_LEVEL, + ip_handle_quant_opts, + ip_cleanup_quant_opts, + ip_copy_colors_back +}; + +#define PERL_PL_SET_GLOBAL_CALLBACKS \ + sv_setiv(get_sv(PERL_PL_FUNCTION_TABLE_NAME, 1), PTR2IV(&im_perl_funcs)); + #ifdef IMEXIF_ENABLE #define i_exif_enabled() 1 #else @@ -2505,499 +2521,6 @@ i_tiff_has_compression(name) #endif -#ifdef HAVE_LIBGIF - -void -i_giflib_version() - PPCODE: - PUSHs(sv_2mortal(newSVnv(IM_GIFMAJOR+IM_GIFMINOR*0.1))); - -undef_int -i_writegif(im,fd,colors,pixdev,fixed) - Imager::ImgRaw im - int fd - int colors - int pixdev - PREINIT: - int fixedlen; - Imager__Color fixed; - Imager__Color tmp; - AV* av; - SV* sv1; - IV Itmp; - int i; - CODE: - if (!SvROK(ST(4))) croak("Imager: Parameter 4 must be a reference to an array\n"); - if (SvTYPE(SvRV(ST(4))) != SVt_PVAV) croak("Imager: Parameter 4 must be a reference to an array\n"); - av=(AV*)SvRV(ST(4)); - fixedlen=av_len(av)+1; - fixed=mymalloc( fixedlen*sizeof(i_color) ); - for(i=0;i \$help, @@ -55,7 +54,6 @@ GetOptions("help" => \$help, "verbose|v" => \$VERBOSE, "nolog" => \$NOLOG, "noexif" => \$noexif, - "nogifsetversion" => \$no_gif_set_version, 'coverage' => \$coverage, "assert|a" => \$assert); @@ -127,9 +125,6 @@ if ($MANUAL) { automatic(); } -# Make sure there isn't a clash between the gif libraries. -gifcheck(); - my $lib_cflags = ''; my $lib_lflags = ''; my $F_LIBS = ''; @@ -220,6 +215,7 @@ if ($MM_ver >= 6.46) { directory => [ "PNG", + "GIF", ], }, resources => @@ -329,43 +325,43 @@ sub automatic { } -sub gifcheck { - if ($formats{'gif'} and $formats{'ungif'}) { - print "ungif and gif can not coexist - removing ungif support\n"; - delete $formats{'ungif'}; - } - - for my $frm (qw(gif ungif)) { - checkformat($frm) if ($MANUAL and $formats{$frm}); - } - - my @dirs; - for my $frm (grep $formats{$_}, qw(gif ungif)) { - push(@dirs, @{$formats{$frm}{incdir}}) if $formats{$frm}{incdir}; - } - my $minor = 0; - my $major = 0; - FILES: for my $dir (@dirs) { - my $h = "$dir/gif_lib.h"; - open H, "< $h" or next; - while () { - if (/GIF_LIB_VERSION\s+"\s*version\s*(\d+)\.(\d+)/i) { - $major = $1; - $minor = $2; - close H; - last FILES; - } - } - close H; - } - - # we need the version in a #ifdefable form - - push @defines, [ IM_GIFMAJOR => $major, "Parsed giflib version" ]; - push @defines, [ IM_GIFMINOR => $minor ]; - push @defines, [ IM_NO_SET_GIF_VERSION => 1, "Disable EGifSetGifVersion" ] - if $no_gif_set_version; -} +# sub gifcheck { +# if ($formats{'gif'} and $formats{'ungif'}) { +# print "ungif and gif can not coexist - removing ungif support\n"; +# delete $formats{'ungif'}; +# } + +# for my $frm (qw(gif ungif)) { +# checkformat($frm) if ($MANUAL and $formats{$frm}); +# } + +# my @dirs; +# for my $frm (grep $formats{$_}, qw(gif ungif)) { +# push(@dirs, @{$formats{$frm}{incdir}}) if $formats{$frm}{incdir}; +# } +# my $minor = 0; +# my $major = 0; +# FILES: for my $dir (@dirs) { +# my $h = "$dir/gif_lib.h"; +# open H, "< $h" or next; +# while () { +# if (/GIF_LIB_VERSION\s+"\s*version\s*(\d+)\.(\d+)/i) { +# $major = $1; +# $minor = $2; +# close H; +# last FILES; +# } +# } +# close H; +# } + +# # we need the version in a #ifdefable form + +# push @defines, [ IM_GIFMAJOR => $major, "Parsed giflib version" ]; +# push @defines, [ IM_GIFMINOR => $minor ]; +# push @defines, [ IM_NO_SET_GIF_VERSION => 1, "Disable EGifSetGifVersion" ] +# if $no_gif_set_version; +# } sub grep_directory { @@ -576,34 +572,34 @@ sub init { # code => \&png_probe, # }; - $formats{'gif'}={ - order=>'20', - def=>'HAVE_LIBGIF', - inccheck=>sub { -e catfile($_[0], 'gif_lib.h') }, - libcheck=>sub { $_[0] eq "libgif$aext" or $_[0] eq "libgif.$lext" }, - libfiles=>'-lgif', - objfiles=>'gif.o', - docs=>q{ - gif is the de facto standard for webgraphics at the moment, - it does have some patent problems. If you have giflib and - are not in violation with the unisys patent you should use - this instead of the 'ungif' option. Note that they cannot - be in use at the same time} - }; - - $formats{'ungif'}={ - order=>'21', - def=>'HAVE_LIBGIF', - inccheck=>sub { -e catfile($_[0], 'gif_lib.h') }, - libcheck=>sub { $_[0] eq "libungif$aext" or $_[0] eq "libungif.$lext" }, - libfiles=>'-lungif', - objfiles=>'gif.o', - docs=>q{ - gif is the de facto standard for webgraphics at the moment, - it does have some patent problems. If you have libungif and - want to create images free from LZW patented compression you - should use this option instead of the 'gif' option} - }; +# $formats{'gif'}={ +# order=>'20', +# def=>'HAVE_LIBGIF', +# inccheck=>sub { -e catfile($_[0], 'gif_lib.h') }, +# libcheck=>sub { $_[0] eq "libgif$aext" or $_[0] eq "libgif.$lext" }, +# libfiles=>'-lgif', +# objfiles=>'gif.o', +# docs=>q{ +# gif is the de facto standard for webgraphics at the moment, +# it does have some patent problems. If you have giflib and +# are not in violation with the unisys patent you should use +# this instead of the 'ungif' option. Note that they cannot +# be in use at the same time} +# }; + +# $formats{'ungif'}={ +# order=>'21', +# def=>'HAVE_LIBGIF', +# inccheck=>sub { -e catfile($_[0], 'gif_lib.h') }, +# libcheck=>sub { $_[0] eq "libungif$aext" or $_[0] eq "libungif.$lext" }, +# libfiles=>'-lungif', +# objfiles=>'gif.o', +# docs=>q{ +# gif is the de facto standard for webgraphics at the moment, +# it does have some patent problems. If you have libungif and +# want to create images free from LZW patented compression you +# should use this option instead of the 'gif' option} +# }; $formats{'T1-fonts'}={ order=>'30', @@ -970,7 +966,7 @@ Usage: $0 [--enable feature1,feature2,...] [other options] $0 [--disable feature1,feature2,...] [other options] $0 --help Possible feature names are: - png gif ungif jpeg tiff T1-fonts TT-fonts freetype2 + jpeg tiff T1-fonts TT-fonts freetype2 Other options: --verbose | -v Verbose library probing (or set IM_VERBOSE in the environment) diff --git a/gif.c b/gif.c deleted file mode 100644 index ae365372..00000000 --- a/gif.c +++ /dev/null @@ -1,2411 +0,0 @@ -#include "imageri.h" -#include -#ifdef _MSC_VER -#include -#else -#include -#endif -#include -/* XXX: Reading still needs to support reading all those gif properties */ - -/* -=head1 NAME - -gif.c - read and write gif files for Imager - -=head1 SYNOPSIS - - i_img *img; - i_img *imgs[count]; - int fd; - int *colour_table, - int colours; - int max_colours; // number of bits per colour - int pixdev; // how much noise to add - i_color fixed[N]; // fixed palette entries - int fixedlen; // number of fixed colours - int success; // non-zero on success - char *data; // a GIF file in memory - int length; // how big data is - int reader(char *, char *, int, int); - int writer(char *, char *, int); - char *userdata; // user's data, whatever it is - i_quantize quant; - i_gif_opts opts; - - img = i_readgif(fd, &colour_table, &colours); - success = i_writegif(img, fd, max_colours, pixdev, fixedlen, fixed); - success = i_writegifmc(img, fd, max_colours); - img = i_readgif_scalar(data, length, &colour_table, &colours); - img = i_readgif_callback(cb, userdata, &colour_table, &colours); - success = i_writegif_gen(&quant, fd, imgs, count, &opts); - success = i_writegif_callback(&quant, writer, userdata, maxlength, - imgs, count, &opts); - -=head1 DESCRIPTION - -This source file provides the C level interface to reading and writing -GIF files for Imager. - -This has been tested with giflib 3 and 4, though you lose the callback -functionality with giflib3. - -=head1 REFERENCE - -=over - -=cut -*/ - -static char const *gif_error_msg(int code); -static void gif_push_error(void); - -#if IM_GIFMAJOR >= 4 - -static int gif_read_callback(GifFileType *gft, GifByteType *buf, int length); - -/* -=item gif_scalar_info - -Internal. A structure passed to the reader function used for reading -GIFs from scalars. - -Used with giflib 4 and later. - -=cut -*/ - -struct gif_scalar_info { - char *data; - int length; - int cpos; -}; - -/* -=item my_gif_inputfunc(GifFileType *gft, GifByteType *buf, int length) - -Internal. The reader callback passed to giflib. - -Used with giflib 4 and later. - -=cut -*/ - -int -my_gif_inputfunc(GifFileType* gft, GifByteType *buf,int length) { - struct gif_scalar_info *gsi=(struct gif_scalar_info *)gft->UserData; - /* fprintf(stderr,"my_gif_inputfunc: length=%d cpos=%d tlength=%d\n",length,gsi->cpos,gsi->length); */ - - if (gsi->cpos == gsi->length) return 0; - if (gsi->cpos+length > gsi->length) length=gsi->length-gsi->cpos; /* Don't read too much */ - memcpy(buf,gsi->data+gsi->cpos,length); - gsi->cpos+=length; - return length; -} - -#endif - - - -/* Make some variables global, so we could access them faster: */ - -static int - InterlacedOffset[] = { 0, 4, 2, 1 }, /* The way Interlaced image should. */ - InterlacedJumps[] = { 8, 8, 4, 2 }; /* be read - offsets and jumps... */ - - - -static -void -i_colortable_copy(int **colour_table, int *colours, ColorMapObject *colourmap) { - GifColorType *mapentry; - int q; - int colourmapsize = colourmap->ColorCount; - - if(colours) *colours = colourmapsize; - if(!colour_table) return; - - *colour_table = mymalloc(sizeof(int) * colourmapsize * 3); - memset(*colour_table, 0, sizeof(int) * colourmapsize * 3); - - for(q=0; qColors[q]; - (*colour_table)[q*3 + 0] = mapentry->Red; - (*colour_table)[q*3 + 1] = mapentry->Green; - (*colour_table)[q*3 + 2] = mapentry->Blue; - } -} - - -/* -=item i_readgif_low(GifFileType *GifFile, int **colour_table, int *colours) - -Internal. Low-level function for reading a GIF file. The caller must -create the appropriate GifFileType object and pass it in. - -=cut -*/ - -i_img * -i_readgif_low(GifFileType *GifFile, int **colour_table, int *colours) { - i_img *im; - int i, j, Size, Row, Col, Width, Height, ExtCode, Count, x; - int cmapcnt = 0, ImageNum = 0, BackGround = 0, ColorMapSize = 0; - ColorMapObject *ColorMap; - - GifRecordType RecordType; - GifByteType *Extension; - - GifRowType GifRow; - static GifColorType *ColorMapEntry; - i_color col; - - mm_log((1,"i_readgif_low(GifFile %p, colour_table %p, colours %p)\n", GifFile, colour_table, colours)); - - /* it's possible that the caller has called us with *colour_table being - non-NULL, but we check that to see if we need to free an allocated - colour table on error. - */ - if (colour_table) *colour_table = NULL; - - BackGround = GifFile->SBackGroundColor; - ColorMap = (GifFile->Image.ColorMap ? GifFile->Image.ColorMap : GifFile->SColorMap); - - if (ColorMap) { - ColorMapSize = ColorMap->ColorCount; - i_colortable_copy(colour_table, colours, ColorMap); - cmapcnt++; - } - - if (!i_int_check_image_file_limits(GifFile->SWidth, GifFile->SHeight, 3, sizeof(i_sample_t))) { - if (colour_table && *colour_table) { - myfree(*colour_table); - *colour_table = NULL; - } - DGifCloseFile(GifFile); - mm_log((1, "i_readgif: image size exceeds limits\n")); - return NULL; - } - - im = i_img_empty_ch(NULL, GifFile->SWidth, GifFile->SHeight, 3); - if (!im) { - if (colour_table && *colour_table) { - myfree(*colour_table); - *colour_table = NULL; - } - DGifCloseFile(GifFile); - return NULL; - } - - Size = GifFile->SWidth * sizeof(GifPixelType); - - GifRow = mymalloc(Size); - - for (i = 0; i < GifFile->SWidth; i++) GifRow[i] = GifFile->SBackGroundColor; - - /* Scan the content of the GIF file and load the image(s) in: */ - do { - if (DGifGetRecordType(GifFile, &RecordType) == GIF_ERROR) { - gif_push_error(); - i_push_error(0, "Unable to get record type"); - if (colour_table && *colour_table) { - myfree(*colour_table); - *colour_table = NULL; - } - myfree(GifRow); - i_img_destroy(im); - DGifCloseFile(GifFile); - return NULL; - } - - switch (RecordType) { - case IMAGE_DESC_RECORD_TYPE: - if (DGifGetImageDesc(GifFile) == GIF_ERROR) { - gif_push_error(); - i_push_error(0, "Unable to get image descriptor"); - if (colour_table && *colour_table) { - myfree(*colour_table); - *colour_table = NULL; - } - myfree(GifRow); - i_img_destroy(im); - DGifCloseFile(GifFile); - return NULL; - } - - if (( ColorMap = (GifFile->Image.ColorMap ? GifFile->Image.ColorMap : GifFile->SColorMap) )) { - mm_log((1, "Adding local colormap\n")); - ColorMapSize = ColorMap->ColorCount; - if ( cmapcnt == 0) { - i_colortable_copy(colour_table, colours, ColorMap); - cmapcnt++; - } - } else { - /* No colormap and we are about to read in the image - abandon for now */ - mm_log((1, "Going in with no colormap\n")); - i_push_error(0, "Image does not have a local or a global color map"); - /* we can't have allocated a colour table here */ - myfree(GifRow); - i_img_destroy(im); - DGifCloseFile(GifFile); - return NULL; - } - - Row = GifFile->Image.Top; /* Image Position relative to Screen. */ - Col = GifFile->Image.Left; - Width = GifFile->Image.Width; - Height = GifFile->Image.Height; - ImageNum++; - mm_log((1,"i_readgif_low: Image %d at (%d, %d) [%dx%d]: \n",ImageNum, Col, Row, Width, Height)); - - if (GifFile->Image.Left + GifFile->Image.Width > GifFile->SWidth || - GifFile->Image.Top + GifFile->Image.Height > GifFile->SHeight) { - i_push_errorf(0, "Image %d is not confined to screen dimension, aborted.\n",ImageNum); - if (colour_table && *colour_table) { - myfree(*colour_table); - *colour_table = NULL; - } - myfree(GifRow); - i_img_destroy(im); - DGifCloseFile(GifFile); - return NULL; - } - if (GifFile->Image.Interlace) { - - for (Count = i = 0; i < 4; i++) for (j = Row + InterlacedOffset[i]; j < Row + Height; j += InterlacedJumps[i]) { - Count++; - if (DGifGetLine(GifFile, GifRow, Width) == GIF_ERROR) { - gif_push_error(); - i_push_error(0, "Reading GIF line"); - if (colour_table && *colour_table) { - myfree(*colour_table); - *colour_table = NULL; - } - myfree(GifRow); - i_img_destroy(im); - DGifCloseFile(GifFile); - return NULL; - } - - for (x = 0; x < Width; x++) { - ColorMapEntry = &ColorMap->Colors[GifRow[x]]; - col.rgb.r = ColorMapEntry->Red; - col.rgb.g = ColorMapEntry->Green; - col.rgb.b = ColorMapEntry->Blue; - i_ppix(im,Col+x,j,&col); - } - - } - } - else { - for (i = 0; i < Height; i++) { - if (DGifGetLine(GifFile, GifRow, Width) == GIF_ERROR) { - gif_push_error(); - i_push_error(0, "Reading GIF line"); - if (colour_table && *colour_table) { - myfree(*colour_table); - *colour_table = NULL; - } - myfree(GifRow); - i_img_destroy(im); - DGifCloseFile(GifFile); - return NULL; - } - - for (x = 0; x < Width; x++) { - ColorMapEntry = &ColorMap->Colors[GifRow[x]]; - col.rgb.r = ColorMapEntry->Red; - col.rgb.g = ColorMapEntry->Green; - col.rgb.b = ColorMapEntry->Blue; - i_ppix(im, Col+x, Row, &col); - } - Row++; - } - } - break; - case EXTENSION_RECORD_TYPE: - /* Skip any extension blocks in file: */ - if (DGifGetExtension(GifFile, &ExtCode, &Extension) == GIF_ERROR) { - gif_push_error(); - i_push_error(0, "Reading extension record"); - if (colour_table && *colour_table) { - myfree(*colour_table); - *colour_table = NULL; - } - myfree(GifRow); - i_img_destroy(im); - DGifCloseFile(GifFile); - return NULL; - } - while (Extension != NULL) { - if (DGifGetExtensionNext(GifFile, &Extension) == GIF_ERROR) { - gif_push_error(); - i_push_error(0, "reading next block of extension"); - if (colour_table && *colour_table) { - myfree(*colour_table); - *colour_table = NULL; - } - myfree(GifRow); - i_img_destroy(im); - DGifCloseFile(GifFile); - return NULL; - } - } - break; - case TERMINATE_RECORD_TYPE: - break; - default: /* Should be traps by DGifGetRecordType. */ - break; - } - } while (RecordType != TERMINATE_RECORD_TYPE); - - myfree(GifRow); - - if (DGifCloseFile(GifFile) == GIF_ERROR) { - gif_push_error(); - i_push_error(0, "Closing GIF file object"); - if (colour_table && *colour_table) { - myfree(*colour_table); - *colour_table = NULL; - } - i_img_destroy(im); - return NULL; - } - - i_tags_add(&im->tags, "i_format", 0, "gif", -1, 0); - - return im; -} - -/* -=item i_readgif(int fd, int **colour_table, int *colours) - -Reads in a GIF file from a file handle and converts it to an Imager -RGB image object. - -Returns the palette for the object in colour_table for colours -colours. - -Returns NULL on failure. - -=cut -*/ - -i_img * -i_readgif(int fd, int **colour_table, int *colours) { - GifFileType *GifFile; - - i_clear_error(); - - mm_log((1,"i_readgif(fd %d, colour_table %p, colours %p)\n", fd, colour_table, colours)); - - if ((GifFile = DGifOpenFileHandle(fd)) == NULL) { - gif_push_error(); - i_push_error(0, "Cannot create giflib file object"); - mm_log((1,"i_readgif: Unable to open file\n")); - return NULL; - } - - return i_readgif_low(GifFile, colour_table, colours); -} - -/* - -Internal function called by i_readgif_multi_low() in error handling - -*/ -static void free_images(i_img **imgs, int count) { - int i; - - if (count) { - for (i = 0; i < count; ++i) - i_img_destroy(imgs[i]); - myfree(imgs); - } -} - -/* -=item i_readgif_multi_low(GifFileType *gf, int *count, int page) - -Reads one of more gif images from the given GIF file. - -Returns a pointer to an array of i_img *, and puts the count into -*count. - -If page is not -1 then the given image _only_ is returned from the -file, where the first image is 0, the second 1 and so on. - -Unlike the normal i_readgif*() functions the images are paletted -images rather than a combined RGB image. - -This functions sets tags on the images returned: - -=over - -=item gif_left - -the offset of the image from the left of the "screen" ("Image Left -Position") - -=item gif_top - -the offset of the image from the top of the "screen" ("Image Top Position") - -=item gif_interlace - -non-zero if the image was interlaced ("Interlace Flag") - -=item gif_screen_width - -=item gif_screen_height - -the size of the logical screen ("Logical Screen Width", -"Logical Screen Height") - -=item gif_local_map - -Non-zero if this image had a local color map. - -=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. - -=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") - -=item gif_delay - -The delay until the next frame is displayed, in 1/100 of a second. -("Delay Time"). - -=item gif_user_input - -whether or not a user input is expected before continuing (view dependent) -("User Input Flag"). - -=item gif_disposal - -how the next frame is displayed ("Disposal Method") - -=item gif_loop - -the number of loops from the Netscape Loop extension. This may be zero. - -=item gif_comment - -the first block of the first gif comment before each image. - -=back - -Where applicable, the ("name") is the name of that field from the GIF89 -standard. - -=cut -*/ - -i_img **i_readgif_multi_low(GifFileType *GifFile, int *count, int page) { - i_img *img; - int i, j, Size, Width, Height, ExtCode, Count; - int ImageNum = 0, BackGround = 0, ColorMapSize = 0; - ColorMapObject *ColorMap; - - GifRecordType RecordType; - GifByteType *Extension; - - GifRowType GifRow; - int got_gce = 0; - int trans_index = 0; /* transparent index if we see a GCE */ - int gif_delay = 0; /* delay from a GCE */ - int user_input = 0; /* user input flag from a GCE */ - int disposal = 0; /* disposal method from a GCE */ - int got_ns_loop = 0; - int ns_loop = 0; - char *comment = NULL; /* a comment */ - i_img **results = NULL; - int result_alloc = 0; - int channels; - int image_colors = 0; - i_color black; /* used to expand the palette if needed */ - - for (i = 0; i < MAXCHANNELS; ++i) - black.channel[i] = 0; - - *count = 0; - - mm_log((1,"i_readgif_multi_low(GifFile %p, , count %p)\n", GifFile, count)); - - BackGround = GifFile->SBackGroundColor; - - Size = GifFile->SWidth * sizeof(GifPixelType); - - if ((GifRow = (GifRowType) mymalloc(Size)) == NULL) - i_fatal(0,"Failed to allocate memory required, aborted."); /* First row. */ - - /* Scan the content of the GIF file and load the image(s) in: */ - do { - if (DGifGetRecordType(GifFile, &RecordType) == GIF_ERROR) { - gif_push_error(); - i_push_error(0, "Unable to get record type"); - free_images(results, *count); - DGifCloseFile(GifFile); - myfree(GifRow); - if (comment) - myfree(comment); - return NULL; - } - - switch (RecordType) { - case IMAGE_DESC_RECORD_TYPE: - if (DGifGetImageDesc(GifFile) == GIF_ERROR) { - gif_push_error(); - i_push_error(0, "Unable to get image descriptor"); - free_images(results, *count); - DGifCloseFile(GifFile); - myfree(GifRow); - if (comment) - myfree(comment); - return NULL; - } - - Width = GifFile->Image.Width; - Height = GifFile->Image.Height; - if (page == -1 || page == ImageNum) { - if (( ColorMap = (GifFile->Image.ColorMap ? GifFile->Image.ColorMap : GifFile->SColorMap) )) { - mm_log((1, "Adding local colormap\n")); - ColorMapSize = ColorMap->ColorCount; - } else { - /* No colormap and we are about to read in the image - - abandon for now */ - mm_log((1, "Going in with no colormap\n")); - i_push_error(0, "Image does not have a local or a global color map"); - free_images(results, *count); - DGifCloseFile(GifFile); - myfree(GifRow); - if (comment) - myfree(comment); - return NULL; - } - - channels = 3; - if (got_gce && trans_index >= 0) - channels = 4; - if (!i_int_check_image_file_limits(Width, Height, channels, sizeof(i_sample_t))) { - free_images(results, *count); - mm_log((1, "i_readgif: image size exceeds limits\n")); - DGifCloseFile(GifFile); - myfree(GifRow); - if (comment) - myfree(comment); - return NULL; - } - img = i_img_pal_new(Width, Height, channels, 256); - if (!img) { - free_images(results, *count); - DGifCloseFile(GifFile); - if (comment) - myfree(comment); - myfree(GifRow); - return NULL; - } - /* populate the palette of the new image */ - mm_log((1, "ColorMapSize %d\n", ColorMapSize)); - for (i = 0; i < ColorMapSize; ++i) { - i_color col; - col.rgba.r = ColorMap->Colors[i].Red; - col.rgba.g = ColorMap->Colors[i].Green; - col.rgba.b = ColorMap->Colors[i].Blue; - if (channels == 4 && trans_index == i) - col.rgba.a = 0; - else - col.rgba.a = 255; - - i_addcolors(img, &col, 1); - } - image_colors = ColorMapSize; - ++*count; - if (*count > result_alloc) { - if (result_alloc == 0) { - result_alloc = 5; - results = mymalloc(result_alloc * sizeof(i_img *)); - } - else { - /* myrealloc never fails (it just dies if it can't allocate) */ - result_alloc *= 2; - results = myrealloc(results, result_alloc * sizeof(i_img *)); - } - } - results[*count-1] = img; - i_tags_add(&img->tags, "i_format", 0, "gif", -1, 0); - i_tags_addn(&img->tags, "gif_left", 0, GifFile->Image.Left); - /**(char *)0 = 1;*/ - i_tags_addn(&img->tags, "gif_top", 0, GifFile->Image.Top); - i_tags_addn(&img->tags, "gif_interlace", 0, GifFile->Image.Interlace); - i_tags_addn(&img->tags, "gif_screen_width", 0, GifFile->SWidth); - i_tags_addn(&img->tags, "gif_screen_height", 0, GifFile->SHeight); - i_tags_addn(&img->tags, "gif_colormap_size", 0, ColorMapSize); - if (GifFile->SColorMap && !GifFile->Image.ColorMap) { - i_tags_addn(&img->tags, "gif_background", 0, - GifFile->SBackGroundColor); - } - if (GifFile->Image.ColorMap) { - i_tags_addn(&img->tags, "gif_localmap", 0, 1); - } - if (got_gce) { - 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); - } - got_gce = 0; - if (got_ns_loop) - i_tags_addn(&img->tags, "gif_loop", 0, ns_loop); - if (comment) { - i_tags_add(&img->tags, "gif_comment", 0, comment, strlen(comment), 0); - myfree(comment); - comment = NULL; - } - - mm_log((1,"i_readgif_multi_low: Image %d at (%d, %d) [%dx%d]: \n", - ImageNum, GifFile->Image.Left, GifFile->Image.Top, Width, Height)); - - if (GifFile->Image.Left + GifFile->Image.Width > GifFile->SWidth || - GifFile->Image.Top + GifFile->Image.Height > GifFile->SHeight) { - i_push_errorf(0, "Image %d is not confined to screen dimension, aborted.\n",ImageNum); - free_images(results, *count); - DGifCloseFile(GifFile); - myfree(GifRow); - if (comment) - myfree(comment); - return(0); - } - - if (GifFile->Image.Interlace) { - for (Count = i = 0; i < 4; i++) { - for (j = InterlacedOffset[i]; j < Height; - j += InterlacedJumps[i]) { - Count++; - if (DGifGetLine(GifFile, GifRow, Width) == GIF_ERROR) { - gif_push_error(); - i_push_error(0, "Reading GIF line"); - free_images(results, *count); - DGifCloseFile(GifFile); - myfree(GifRow); - if (comment) - myfree(comment); - return NULL; - } - - /* range check the scanline if needed */ - if (image_colors != 256) { - int x; - for (x = 0; x < Width; ++x) { - while (GifRow[x] >= image_colors) { - /* expand the palette since a palette index is too big */ - i_addcolors(img, &black, 1); - ++image_colors; - } - } - } - - i_ppal(img, 0, Width, j, GifRow); - } - } - } - else { - for (i = 0; i < Height; i++) { - if (DGifGetLine(GifFile, GifRow, Width) == GIF_ERROR) { - gif_push_error(); - i_push_error(0, "Reading GIF line"); - free_images(results, *count); - DGifCloseFile(GifFile); - myfree(GifRow); - if (comment) - myfree(comment); - return NULL; - } - - /* range check the scanline if needed */ - if (image_colors != 256) { - int x; - for (x = 0; x < Width; ++x) { - while (GifRow[x] >= image_colors) { - /* expand the palette since a palette index is too big */ - i_addcolors(img, &black, 1); - ++image_colors; - } - } - } - - i_ppal(img, 0, Width, i, GifRow); - } - } - - /* must be only one image wanted and that was it */ - if (page != -1) { - myfree(GifRow); - DGifCloseFile(GifFile); - if (comment) - myfree(comment); - return results; - } - } - else { - /* skip the image */ - /* whether interlaced or not, it has the same number of lines */ - /* giflib does't have an interface to skip the image data */ - for (i = 0; i < Height; i++) { - if (DGifGetLine(GifFile, GifRow, Width) == GIF_ERROR) { - gif_push_error(); - i_push_error(0, "Reading GIF line"); - free_images(results, *count); - myfree(GifRow); - DGifCloseFile(GifFile); - if (comment) - myfree(comment); - return NULL; - } - } - - /* kill the comment so we get the right comment for the page */ - if (comment) { - myfree(comment); - comment = NULL; - } - } - ImageNum++; - break; - case EXTENSION_RECORD_TYPE: - /* Skip any extension blocks in file: */ - if (DGifGetExtension(GifFile, &ExtCode, &Extension) == GIF_ERROR) { - gif_push_error(); - i_push_error(0, "Reading extension record"); - free_images(results, *count); - myfree(GifRow); - DGifCloseFile(GifFile); - if (comment) - myfree(comment); - return NULL; - } - /* possibly this should be an error, but "be liberal in what you accept" */ - if (!Extension) - break; - if (ExtCode == 0xF9) { - got_gce = 1; - if (Extension[1] & 1) - trans_index = Extension[4]; - else - trans_index = -1; - gif_delay = Extension[2] + 256 * Extension[3]; - user_input = (Extension[1] & 2) != 0; - disposal = (Extension[1] >> 2) & 7; - } - if (ExtCode == 0xFF && *Extension == 11) { - if (memcmp(Extension+1, "NETSCAPE2.0", 11) == 0) { - if (DGifGetExtensionNext(GifFile, &Extension) == GIF_ERROR) { - gif_push_error(); - i_push_error(0, "reading loop extension"); - free_images(results, *count); - myfree(GifRow); - DGifCloseFile(GifFile); - if (comment) - myfree(comment); - return NULL; - } - if (Extension && *Extension == 3) { - got_ns_loop = 1; - ns_loop = Extension[2] + 256 * Extension[3]; - } - } - } - else if (ExtCode == 0xFE) { - /* while it's possible for a GIF file to contain more than one - comment, I'm only implementing a single comment per image, - with the comment saved into the following image. - If someone wants more than that they can implement it. - I also don't handle comments that take more than one block. - */ - if (!comment) { - comment = mymalloc(*Extension+1); - memcpy(comment, Extension+1, *Extension); - comment[*Extension] = '\0'; - } - } - while (Extension != NULL) { - if (DGifGetExtensionNext(GifFile, &Extension) == GIF_ERROR) { - gif_push_error(); - i_push_error(0, "reading next block of extension"); - free_images(results, *count); - myfree(GifRow); - DGifCloseFile(GifFile); - if (comment) - myfree(comment); - return NULL; - } - } - break; - case TERMINATE_RECORD_TYPE: - break; - default: /* Should be trapped by DGifGetRecordType. */ - break; - } - } while (RecordType != TERMINATE_RECORD_TYPE); - - if (comment) { - if (*count) { - i_tags_add(&(results[*count-1]->tags), "gif_comment", 0, comment, - strlen(comment), 0); - } - myfree(comment); - } - - myfree(GifRow); - - if (DGifCloseFile(GifFile) == GIF_ERROR) { - gif_push_error(); - i_push_error(0, "Closing GIF file object"); - free_images(results, *count); - return NULL; - } - - if (ImageNum && page != -1) { - /* there were images, but the page selected wasn't found */ - i_push_errorf(0, "page %d not found (%d total)", page, ImageNum); - free_images(results, *count); - return NULL; - } - - return results; -} - -#if IM_GIFMAJOR >= 4 -/* giflib declares this incorrectly as EgifOpen */ -extern GifFileType *EGifOpen(void *userData, OutputFunc writeFunc); - -static int io_glue_read_cb(GifFileType *gft, GifByteType *buf, int length); -#endif - -/* -=item i_readgif_multi_wiol(ig, int *count) - -=cut -*/ - -i_img ** -i_readgif_multi_wiol(io_glue *ig, int *count) { - io_glue_commit_types(ig); - - if (ig->source.type == FDSEEK || ig->source.type == FDNOSEEK) { - return i_readgif_multi(ig->source.fdseek.fd, count); - } - else { -#if IM_GIFMAJOR >= 4 - GifFileType *GifFile; - - i_clear_error(); - - if ((GifFile = DGifOpen((void *)ig, io_glue_read_cb )) == NULL) { - gif_push_error(); - i_push_error(0, "Cannot create giflib callback object"); - mm_log((1,"i_readgif_multi_wiol: Unable to open callback datasource.\n")); - return NULL; - } - - return i_readgif_multi_low(GifFile, count, -1); -#else - i_clear_error(); - i_push_error(0, "callbacks not supported with giflib3"); - - return NULL; -#endif - } -} - -/* -=item i_readgif_multi(int fd, int *count) - -=cut -*/ -i_img ** -i_readgif_multi(int fd, int *count) { - GifFileType *GifFile; - - i_clear_error(); - - mm_log((1,"i_readgif_multi(fd %d, &count %p)\n", fd, count)); - - if ((GifFile = DGifOpenFileHandle(fd)) == NULL) { - gif_push_error(); - i_push_error(0, "Cannot create giflib file object"); - mm_log((1,"i_readgif: Unable to open file\n")); - return NULL; - } - - return i_readgif_multi_low(GifFile, count, -1); -} - -/* -=item i_readgif_multi_scalar(char *data, int length, int *count) - -=cut -*/ -i_img ** -i_readgif_multi_scalar(char *data, int length, int *count) { -#if IM_GIFMAJOR >= 4 - GifFileType *GifFile; - struct gif_scalar_info gsi; - - i_clear_error(); - - gsi.cpos=0; - gsi.length=length; - gsi.data=data; - - mm_log((1,"i_readgif_multi_scalar(data %p, length %d, &count %p)\n", - data, length, count)); - - if ((GifFile = DGifOpen( (void*) &gsi, my_gif_inputfunc )) == NULL) { - gif_push_error(); - i_push_error(0, "Cannot create giflib callback object"); - mm_log((1,"i_readgif_multi_scalar: Unable to open scalar datasource.\n")); - return NULL; - } - - return i_readgif_multi_low(GifFile, count, -1); -#else - return NULL; -#endif -} - -/* -=item i_readgif_callback(i_read_callback_t cb, char *userdata, int **colour_table, int *colours) - -Read a GIF file into an Imager RGB file, the data of the GIF file is -retreived by callin the user supplied callback function. - -This function is only used with giflib 4 and higher. - -=cut -*/ - -i_img** -i_readgif_multi_callback(i_read_callback_t cb, char *userdata, int *count) { -#if IM_GIFMAJOR >= 4 - GifFileType *GifFile; - i_img **result; - - i_gen_read_data *gci = i_gen_read_data_new(cb, userdata); - - i_clear_error(); - - mm_log((1,"i_readgif_multi_callback(callback %p, userdata %p, count %p)\n", cb, userdata, count)); - if ((GifFile = DGifOpen( (void*) gci, gif_read_callback )) == NULL) { - gif_push_error(); - i_push_error(0, "Cannot create giflib callback object"); - mm_log((1,"i_readgif_callback: Unable to open callback datasource.\n")); - myfree(gci); - return NULL; - } - - result = i_readgif_multi_low(GifFile, count, -1); - i_free_gen_read_data(gci); - - return result; -#else - return NULL; -#endif -} - -/* -=item i_writegif(i_img *im, int fd, int max_colors, int pixdev, int fixedlen, i_color fixed[]) - -Write I to the file handle I. The resulting GIF will use a -maximum of 1< colours, with the first I -colours taken from I. - -Returns non-zero on success. - -=cut -*/ - -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; - - memset(&quant, 0, sizeof(quant)); - quant.make_colors = mc_addi; - quant.mc_colors = colors; - quant.mc_size = 1< to the file handle I. The resulting GIF will use a -maximum of 1< colours. - -Returns non-zero on success. - -=cut -*/ - -undef_int -i_writegifmc(i_img *im, int fd, int max_colors) { - i_color colors[256]; - i_quantize quant; - -/* *(char *)0 = 1; */ - - memset(&quant, 0, sizeof(quant)); - quant.make_colors = mc_none; /* ignored for pt_giflib */ - quant.mc_colors = colors; - quant.mc_size = 1 << max_colors; - quant.mc_count = 0; - quant.translate = pt_giflib; - return i_writegif_gen(&quant, fd, &im, 1); -} - - -/* -=item i_readgif_scalar(char *data, int length, int **colour_table, int *colours) - -Reads a GIF file from an in memory copy of the file. This can be used -if you get the 'file' from some source other than an actual file (or -some other file handle). - -This function is only available with giflib 4 and higher. - -=cut -*/ -i_img* -i_readgif_scalar(char *data, int length, int **colour_table, int *colours) { -#if IM_GIFMAJOR >= 4 - GifFileType *GifFile; - struct gif_scalar_info gsi; - - i_clear_error(); - - gsi.cpos=0; - gsi.length=length; - gsi.data=data; - - mm_log((1,"i_readgif_scalar(char* data, int length, colour_table %p, colours %p)\n", data, length, colour_table, colours)); - if ((GifFile = DGifOpen( (void*) &gsi, my_gif_inputfunc )) == NULL) { - gif_push_error(); - i_push_error(0, "Cannot create giflib callback object"); - mm_log((1,"i_readgif_scalar: Unable to open scalar datasource.\n")); - return NULL; - } - - return i_readgif_low(GifFile, colour_table, colours); -#else - return NULL; -#endif -} - -#if IM_GIFMAJOR >= 4 - -/* -=item gif_read_callback(GifFileType *gft, GifByteType *buf, int length) - -Internal. The reader callback wrapper passed to giflib. - -This function is only used with giflib 4 and higher. - -=cut -*/ - -static int -gif_read_callback(GifFileType *gft, GifByteType *buf, int length) { - return i_gen_reader((i_gen_read_data *)gft->UserData, (char*)buf, length); -} - -#endif - - -/* -=item i_readgif_callback(i_read_callback_t cb, char *userdata, int **colour_table, int *colours) - -Read a GIF file into an Imager RGB file, the data of the GIF file is -retreived by callin the user supplied callback function. - -This function is only used with giflib 4 and higher. - -=cut -*/ - -i_img* -i_readgif_callback(i_read_callback_t cb, char *userdata, int **colour_table, int *colours) { -#if IM_GIFMAJOR >= 4 - GifFileType *GifFile; - i_img *result; - - i_gen_read_data *gci = i_gen_read_data_new(cb, userdata); - - i_clear_error(); - - mm_log((1,"i_readgif_callback(callback %p, userdata %p, colour_table %p, colours %p)\n", cb, userdata, colour_table, colours)); - if ((GifFile = DGifOpen( (void*) gci, gif_read_callback )) == NULL) { - gif_push_error(); - i_push_error(0, "Cannot create giflib callback object"); - mm_log((1,"i_readgif_callback: Unable to open callback datasource.\n")); - myfree(gci); - return NULL; - } - - result = i_readgif_low(GifFile, colour_table, colours); - i_free_gen_read_data(gci); - - return result; -#else - i_clear_error(); - i_push_error(0, "callbacks not supported with giflib3"); - - return NULL; -#endif -} - -#if IM_GIFMAJOR >= 4 - -static int -io_glue_read_cb(GifFileType *gft, GifByteType *buf, int length) { - io_glue *ig = (io_glue *)gft->UserData; - - return ig->readcb(ig, buf, length); -} - -#endif - -i_img * -i_readgif_wiol(io_glue *ig, int **color_table, int *colors) { - io_glue_commit_types(ig); - - if (ig->source.type == FDSEEK || ig->source.type == FDNOSEEK) { - int fd = dup(ig->source.fdseek.fd); - if (fd < 0) { - i_push_error(errno, "dup() failed"); - return 0; - } - return i_readgif(fd, color_table, colors); - } - else { -#if IM_GIFMAJOR >= 4 - GifFileType *GifFile; - - i_clear_error(); - - if ((GifFile = DGifOpen((void *)ig, io_glue_read_cb )) == NULL) { - gif_push_error(); - i_push_error(0, "Cannot create giflib callback object"); - mm_log((1,"i_readgif_wiol: Unable to open callback datasource.\n")); - return NULL; - } - - return i_readgif_low(GifFile, color_table, colors); - -#else - i_clear_error(); - i_push_error(0, "callbacks not supported with giflib3"); - - return NULL; -#endif - } -} - -/* -=item i_readgif_single_low(GifFile, page) - -Lower level function to read a single image from a GIF. - -page must be non-negative. - -=cut -*/ -static i_img * -i_readgif_single_low(GifFileType *GifFile, int page) { - int count = 0; - i_img **imgs; - - imgs = i_readgif_multi_low(GifFile, &count, page); - - if (imgs && count) { - i_img *result = imgs[0]; - - myfree(imgs); - return result; - } - else { - /* i_readgif_multi_low() handles the errors appropriately */ - return NULL; - } -} - -/* -=item i_readgif_single_wiol(ig, page) - -Read a single page from a GIF image file, where the page is indexed -from 0. - -Returns NULL if the page isn't found. - -=cut -*/ - -i_img * -i_readgif_single_wiol(io_glue *ig, int page) { - io_glue_commit_types(ig); - - i_clear_error(); - - if (page < 0) { - i_push_error(0, "page must be non-negative"); - return NULL; - } - - if (ig->source.type == FDSEEK || ig->source.type == FDNOSEEK) { - GifFileType *GifFile; - int fd = dup(ig->source.fdseek.fd); - if (fd < 0) { - i_push_error(errno, "dup() failed"); - return NULL; - } - if ((GifFile = DGifOpenFileHandle(fd)) == NULL) { - gif_push_error(); - i_push_error(0, "Cannot create giflib file object"); - mm_log((1,"i_readgif: Unable to open file\n")); - return NULL; - } - return i_readgif_single_low(GifFile, page); - } - else { -#if IM_GIFMAJOR >= 4 - GifFileType *GifFile; - - if ((GifFile = DGifOpen((void *)ig, io_glue_read_cb )) == NULL) { - gif_push_error(); - i_push_error(0, "Cannot create giflib callback object"); - mm_log((1,"i_readgif_wiol: Unable to open callback datasource.\n")); - return NULL; - } - - return i_readgif_single_low(GifFile, page); -#else - i_push_error(0, "callbacks not supported with giflib3"); - - return NULL; -#endif - } -} - -/* -=item do_write(GifFileType *gf, i_gif_opts *opts, i_img *img, i_palidx *data) - -Internal. Low level image write function. Writes in interlace if -that was requested in the GIF options. - -Returns non-zero on success. - -=cut -*/ -static undef_int -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]) { - if (EGifPutLine(gf, data+j*img->xsize, img->xsize) == GIF_ERROR) { - gif_push_error(); - i_push_error(0, "Could not save image data:"); - mm_log((1, "Error in EGifPutLine\n")); - EGifCloseFile(gf); - return 0; - } - } - } - } - else { - int y; - for (y = 0; y < img->ysize; ++y) { - if (EGifPutLine(gf, data, img->xsize) == GIF_ERROR) { - gif_push_error(); - i_push_error(0, "Could not save image data:"); - mm_log((1, "Error in EGifPutLine\n")); - EGifCloseFile(gf); - return 0; - } - data += img->xsize; - } - } - - return 1; -} - -/* -=item do_gce(GifFileType *gf, int index, i_gif_opts *opts, int want_trans, int trans_index) - -Internal. Writes the GIF graphics control extension, if necessary. - -Returns non-zero on success. - -=cut -*/ -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 (i_tags_get_int(&img->tags, "gif_delay", 0, &delay)) { - gce[1] = delay % 256; - gce[2] = delay / 256; - ++want_gce; - } - if (i_tags_get_int(&img->tags, "gif_user_input", 0, &user_input) - && user_input) { - gce[0] |= 2; - ++want_gce; - } - if (i_tags_get_int(&img->tags, "gif_disposal", 0, &disposal_method)) { - gce[0] |= (disposal_method & 3) << 2; - ++want_gce; - } - if (want_gce) { - if (EGifPutExtension(gf, 0xF9, sizeof(gce), gce) == GIF_ERROR) { - gif_push_error(); - i_push_error(0, "Could not save GCE"); - } - } - 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) - -Internal. Add the Netscape2.0 loop extension block, if requested. - -Giflib/libungif prior to 4.1.1 didn't support writing application -extension blocks, so we don't attempt to write them for older versions. - -Giflib/libungif prior to 4.1.3 used the wrong write mechanism when -writing extension blocks so that they could only be written to files. - -=cut -*/ -static int do_ns_loop(GifFileType *gf, i_img *img) -{ - /* EGifPutExtension() doesn't appear to handle application - extension blocks in any way - Since giflib wraps the fd with a FILE * (and puts that in its - private data), we can't do an end-run and write the data - directly to the fd. - There's no open interface that takes a FILE * either, so we - can't workaround it that way either. - If giflib's callback interface wasn't broken by default, I'd - force file writes to use callbacks, but it is broken by default. - */ - /* yes this was another attempt at supporting the loop extension */ -#if IM_GIFMAJOR == 4 && IM_GIFMINOR >= 1 - 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 (EGifPutExtensionFirst(gf, APPLICATION_EXT_FUNC_CODE, 11, nsle) == GIF_ERROR) { - gif_push_error(); - i_push_error(0, "writing loop extension"); - return 0; - } - subblock[0] = 1; - subblock[1] = loop_count % 256; - subblock[2] = loop_count / 256; - if (EGifPutExtensionLast(gf, APPLICATION_EXT_FUNC_CODE, 3, subblock) == GIF_ERROR) { - gif_push_error(); - i_push_error(0, "writing loop extension sub-block"); - return 0; - } - } -#endif - - return 1; -} - -/* -=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_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; - colors[i].Green = quant->mc_colors[i].rgb.g; - colors[i].Blue = quant->mc_colors[i].rgb.b; - } - if (want_trans) { - 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; - while (map_size < size) - map_size <<= 1; - /* giflib spews for 1 colour maps, reasonable, I suppose */ - if (map_size == 1) - map_size = 2; - while (i < map_size) { - colors[i].Red = colors[i].Green = colors[i].Blue = 0; - ++i; - } - - map = MakeMapObject(map_size, colors); - mm_log((1, "XXX map is at %p and colors at %p\n", map, map->Colors)); - if (!map) { - gif_push_error(); - i_push_error(0, "Could not create color map object"); - return NULL; - } - return map; -} - -/* -=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. - -Unfortunately giflib 4.1.0 crashes when we use this. Internally -giflib 4.1.0 has code: - - static char *GifVersionPrefix = GIF87_STAMP; - -and the code that sets the version internally does: - - strncpy(&GifVersionPrefix[3], Version, 3); - -which is very broken. - -Failing to set the correct GIF version doesn't seem to cause a problem -with readers. - -Modern versions (4.1.4 anyway) of giflib/libungif handle -EGifSetGifVersion correctly. - -If t/t105gif.t crashes here then run Makefile.PL with ---nogifsetversion, eg.: - - perl Makefile.PL --nogifsetversion - -or install a less buggy giflib. - -=cut -*/ - -static void gif_set_version(i_quantize *quant, i_img **imgs, int count) { -#if (IM_GIFMAJOR >= 4 || IM_GIFMAJOR == 4 && IM_GIFMINOR >= 1) \ - && !defined(IM_NO_SET_GIF_VERSION) - int need_89a = 0; - int temp; - int i; - - if (quant->transp != tr_none) - need_89a = 1; - else { - for (i = 0; i < count; ++i) { - if (i_tags_get_int(&imgs[i]->tags, "gif_delay", 0, &temp)) { - need_89a = 1; - break; - } - if (i_tags_get_int(&imgs[i]->tags, "gif_user_input", 0, &temp) && temp) { - need_89a = 1; - break; - } - if (i_tags_get_int(&imgs[i]->tags, "gif_disposal", 0, &temp)) { - need_89a = 1; - break; - } - if (i_tags_get_int(&imgs[i]->tags, "gif_loop", 0, &temp)) { - need_89a = 1; - break; - } - } - } - if (need_89a) - EGifSetGifVersion("89a"); - else - EGifSetGifVersion("87a"); -#endif -} - -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) { - int size = quant->mc_count; - int i; - int imgn; - 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) { - int eliminate_unused; - if (imgs[imgn]->type != i_palette_type) - return 0; - - 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)); - - 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) - -Internal. Low-level function that does the high-level GIF processing -:) - -Returns non-zero on success. - -=cut -*/ - -static undef_int -i_writegif_low(i_quantize *quant, GifFileType *gf, i_img **imgs, int count) { - unsigned char *result = NULL; - int color_bits; - ColorMapObject *map; - int scrw = 0, scrh = 0; - int imgn, orig_count, orig_size; - int posx, posy; - int trans_index = -1; - 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 = 0; - int glob_want_trans; - int glob_paletted = 0; /* the global map was made from the image palettes */ - int colors_paletted = 0; - int want_trans = 0; - 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? */ - } - - i_mempool_init(&mp); - - /* 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, &scrh)) - 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) { - 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]->channels == 4) { - glob_want_trans = 1; - } - glob_imgs[glob_img_count++] = imgs[imgn]; - } - } - glob_want_trans = glob_want_trans && quant->transp != tr_none ; - - orig_count = quant->mc_count; - orig_size = quant->mc_size; - - 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; - } - if (has_common_palette(glob_imgs, glob_img_count, quant, want_trans)) { - glob_paletted = 1; - } - else { - glob_paletted = 0; - i_quant_makemap(quant, glob_imgs, glob_img_count); - } - glob_color_count = quant->mc_count; - quant->mc_colors = orig_colors; - } - - /* 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; - } - else { - colors_paletted = 0; - i_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, - 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 (!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 (!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; - i_quant_makemap(quant, imgs, 1); - } - if ((map = make_gif_map(quant, imgs[0], want_trans)) == NULL) { - i_mempool_destroy(&mp); - EGifCloseFile(gf); - quant->mc_colors = orig_colors; - mm_log((1, "Error in MakeMapObject")); - 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 = i_quant_translate(quant, imgs[0]); - if (!result) { - i_mempool_destroy(&mp); - quant->mc_colors = orig_colors; - EGifCloseFile(gf); - return 0; - } - if (want_trans) { - i_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); - 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[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; - /* 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+imgn, 1, quant, want_trans)) { - result = quant_paletted(quant, imgs[imgn]); - } - else { - i_quant_makemap(quant, imgs+imgn, 1); - result = i_quant_translate(quant, imgs[imgn]); - } - if (!result) { - i_mempool_destroy(&mp); - quant->mc_colors = orig_colors; - EGifCloseFile(gf); - mm_log((1, "error in i_quant_translate()")); - return 0; - } - if (want_trans) { - i_quant_transparent(quant, result, imgs[imgn], quant->mc_count); - trans_index = quant->mc_count; - } - - 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; - } - } - else { - quant->mc_colors = glob_colors; - quant->mc_count = glob_color_count; - if (glob_paletted) - result = quant_paletted(quant, imgs[imgn]); - else - result = i_quant_translate(quant, imgs[imgn]); - want_trans = glob_want_trans && imgs[imgn]->channels == 4; - if (want_trans) { - i_quant_transparent(quant, result, imgs[imgn], quant->mc_count); - trans_index = quant->mc_count; - } - map = NULL; - } - - if (!do_gce(gf, imgs[imgn], want_trans, trans_index)) { - i_mempool_destroy(&mp); - quant->mc_colors = orig_colors; - myfree(result); - EGifCloseFile(gf); - return 0; - } - - if (!do_comments(gf, imgs[imgn])) { - i_mempool_destroy(&mp); - quant->mc_colors = orig_colors; - myfree(result); - EGifCloseFile(gf); - return 0; - } - - 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 (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); - } - - 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; - } - if (glob_colors) { - int i; - for (i = 0; i < glob_color_count; ++i) - orig_colors[i] = glob_colors[i]; - } - - i_mempool_destroy(&mp); - quant->mc_colors = orig_colors; - - return 1; -} - -/* -=item i_writegif_gen(i_quantize *quant, int fd, i_img **imgs, int count, i_gif_opts *opts) - -General high-level function to write a GIF to a file. - -Writes the GIF images to the specified file handle using the options -in quant and opts. See L and -L. - -Returns non-zero on success. - -=cut -*/ - -undef_int -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)\n", - quant, fd, imgs, count)); - - gif_set_version(quant, imgs, count); - - if ((gf = EGifOpenFileHandle(fd)) == NULL) { - gif_push_error(); - i_push_error(0, "Cannot create GIF file object"); - mm_log((1, "Error in EGifOpenFileHandle, unable to write image.\n")); - return 0; - } - - return i_writegif_low(quant, gf, imgs, count); -} - -#if IM_GIFMAJOR >= 4 - -/* -=item gif_writer_callback(GifFileType *gf, const GifByteType *data, int size) - -Internal. Wrapper for the user write callback function. - -=cut -*/ - -static int gif_writer_callback(GifFileType *gf, const GifByteType *data, int size) -{ - i_gen_write_data *gwd = (i_gen_write_data *)gf->UserData; - - return i_gen_writer(gwd, (char*)data, size) ? size : 0; -} - -#endif - -/* -=item i_writegif_callback(i_quantize *quant, i_write_callback_t cb, char *userdata, int maxlength, i_img **imgs, int count, i_gif_opts *opts) - -General high-level function to write a GIF using callbacks to send -back the data. - -Returns non-zero on success. - -=cut -*/ - -undef_int -i_writegif_callback(i_quantize *quant, i_write_callback_t cb, char *userdata, - int maxlength, i_img **imgs, int count) -{ -#if IM_GIFMAJOR >= 4 - GifFileType *gf; - i_gen_write_data *gwd = i_gen_write_data_new(cb, userdata, maxlength); - int result; - - i_clear_error(); - - 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(); - i_push_error(0, "Cannot create GIF file object"); - mm_log((1, "Error in EGifOpenFileHandle, unable to write image.\n")); - i_free_gen_write_data(gwd, 0); - return 0; - } - - result = i_writegif_low(quant, gf, imgs, count); - return i_free_gen_write_data(gwd, result); -#else - i_clear_error(); - i_push_error(0, "callbacks not supported with giflib3"); - - return 0; -#endif -} - -#if IM_GIFMAJOR >= 4 - -static int -io_glue_write_cb(GifFileType *gft, const GifByteType *data, int length) { - io_glue *ig = (io_glue *)gft->UserData; - - return ig->writecb(ig, data, length); -} - -#endif - -/* -=item i_writegif_wiol(ig, quant, opts, imgs, count) - -=cut -*/ -undef_int -i_writegif_wiol(io_glue *ig, i_quantize *quant, i_img **imgs, - int count) { - io_glue_commit_types(ig); - - if (ig->source.type == FDSEEK || ig->source.type == FDNOSEEK) { - int fd = dup(ig->source.fdseek.fd); - if (fd < 0) { - i_push_error(errno, "dup() failed"); - return 0; - } - /* 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); - } - else { -#if IM_GIFMAJOR >= 4 - GifFileType *GifFile; - int result; - - i_clear_error(); - - gif_set_version(quant, imgs, count); - - if ((GifFile = EGifOpen((void *)ig, io_glue_write_cb )) == NULL) { - gif_push_error(); - i_push_error(0, "Cannot create giflib callback object"); - mm_log((1,"i_writegif_wiol: Unable to open callback datasource.\n")); - return 0; - } - - result = i_writegif_low(quant, GifFile, imgs, count); - - ig->closecb(ig); - - return result; -#else - i_clear_error(); - i_push_error(0, "callbacks not supported with giflib3"); - - return 0; -#endif - } -} - -/* -=item gif_error_msg(int code) - -Grabs the most recent giflib error code from GifLastError() and -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 -*/ - -static char const *gif_error_msg(int code) { - static char msg[80]; - - switch (code) { - case E_GIF_ERR_OPEN_FAILED: /* should not see this */ - return "Failed to open given file"; - - case E_GIF_ERR_WRITE_FAILED: - return "Write failed"; - - case E_GIF_ERR_HAS_SCRN_DSCR: /* should not see this */ - return "Screen descriptor already passed to giflib"; - - case E_GIF_ERR_HAS_IMAG_DSCR: /* should not see this */ - return "Image descriptor already passed to giflib"; - - case E_GIF_ERR_NO_COLOR_MAP: /* should not see this */ - return "Neither global nor local color map set"; - - case E_GIF_ERR_DATA_TOO_BIG: /* should not see this */ - return "Too much pixel data passed to giflib"; - - case E_GIF_ERR_NOT_ENOUGH_MEM: - return "Out of memory"; - - case E_GIF_ERR_DISK_IS_FULL: - return "Disk is full"; - - case E_GIF_ERR_CLOSE_FAILED: /* should not see this */ - return "File close failed"; - - case E_GIF_ERR_NOT_WRITEABLE: /* should not see this */ - return "File not writable"; - - case D_GIF_ERR_OPEN_FAILED: - return "Failed to open file"; - - case D_GIF_ERR_READ_FAILED: - return "Failed to read from file"; - - case D_GIF_ERR_NOT_GIF_FILE: - return "File is not a GIF file"; - - case D_GIF_ERR_NO_SCRN_DSCR: - return "No screen descriptor detected - invalid file"; - - case D_GIF_ERR_NO_IMAG_DSCR: - return "No image descriptor detected - invalid file"; - - case D_GIF_ERR_NO_COLOR_MAP: - return "No global or local color map found"; - - case D_GIF_ERR_WRONG_RECORD: - return "Wrong record type detected - invalid file?"; - - case D_GIF_ERR_DATA_TOO_BIG: - return "Data in file too big for image"; - - case D_GIF_ERR_NOT_ENOUGH_MEM: - return "Out of memory"; - - case D_GIF_ERR_CLOSE_FAILED: - return "Close failed"; - - case D_GIF_ERR_NOT_READABLE: - return "File not opened for read"; - - case D_GIF_ERR_IMAGE_DEFECT: - return "Defective image"; - - case D_GIF_ERR_EOF_TOO_SOON: - return "Unexpected EOF - invalid file"; - - default: - sprintf(msg, "Unknown giflib error code %d", code); - return msg; - } -} - -/* -=item gif_push_error() - -Utility function that takes the current GIF error code, converts it to -an error message and pushes it on the error stack. - -=cut -*/ - -static void gif_push_error(void) { - int code = GifLastError(); /* clears saved error */ - - i_push_error(code, gif_error_msg(code)); -} - -/* -=head1 BUGS - -The Netscape loop extension isn't implemented. Giflib's extension -writing code doesn't seem to support writing named extensions in this -form. - -A bug in giflib is tickled by the i_writegif_callback(). This isn't a -problem on ungiflib, but causes a SEGV on giflib. A patch is provided -in t/t10formats.t - -The GIF file tag (GIF87a vs GIF89a) currently isn't set. Using the -supplied interface in giflib 4.1.0 causes a SEGV in -EGifSetGifVersion(). See L for an explanation. - -=head1 AUTHOR - -Arnar M. Hrafnkelsson, addi@umich.edu - -=head1 SEE ALSO - -perl(1), Imager(3) - -=cut - -*/ diff --git a/imdatatypes.h b/imdatatypes.h index cfa10864..7b6e6591 100644 --- a/imdatatypes.h +++ b/imdatatypes.h @@ -624,6 +624,8 @@ typedef struct i_gif_pos_tag { /* passed into i_writegif_gen() to control quantization */ typedef struct i_quantize_tag { + int version; + /* how to handle transparency */ i_transp transp; /* the threshold at which to make pixels opaque */ @@ -659,6 +661,7 @@ typedef struct i_quantize_tag { /* the amount of perturbation to use for translate is mc_perturb */ int perturb; + /* version 2 members after here */ } i_quantize; typedef struct i_gif_opts { diff --git a/imextpl.h b/imextpl.h new file mode 100644 index 00000000..0d98f8ef --- /dev/null +++ b/imextpl.h @@ -0,0 +1,33 @@ +#ifndef IMAGER_IMEXTPL_H_ +#define IMAGER_IMEXTPL_H_ + +#include "imextpltypes.h" +#include "immacros.h" + +extern im_pl_ext_funcs *imager_perl_function_ext_table; + +#define DEFINE_IMAGER_PERL_CALLBACKS im_pl_ext_funcs *imager_perl_function_ext_table + +#ifndef IMAGER_MIN_PL_API_LEVEL +#define IMAGER_MIN_PL_API_LEVEL IMAGER_PL_API_LEVEL +#endif + +#define PERL_INITIALIZE_IMAGER_PERL_CALLBACKS \ + do { \ + imager_perl_function_ext_table = INT2PTR(im_pl_ext_funcs *, SvIV(get_sv(PERL_PL_FUNCTION_TABLE_NAME, 1))); \ + if (!imager_perl_function_ext_table) \ + croak("Imager Perl API function table not found!"); \ + if (imager_perl_function_ext_table->version != IMAGER_API_VERSION) \ + croak("Imager Perl API version incorrect"); \ + if (imager_perl_function_ext_table->level < IMAGER_MIN_PL_API_LEVEL) \ + croak("perl API level %d below minimum of %d", imager_perl_function_ext_table->level, IMAGER_MIN_PL_API_LEVEL); \ + } while (0) + +/* just for use here */ +#define im_exttpl imager_perl_function_ext_table + +#define ip_handle_quant_opts (im_exttpl->f_ip_handle_quant_opts) +#define ip_cleanup_quant_opts (im_exttpl->f_ip_cleanup_quant_opts) +#define ip_copy_colors_back (im_exttpl->f_ip_copy_colors_back) + +#endif diff --git a/imextpltypes.h b/imextpltypes.h new file mode 100644 index 00000000..e7722a21 --- /dev/null +++ b/imextpltypes.h @@ -0,0 +1,30 @@ +#ifndef IMAGER_IMEXTPLTYPES_H_ +#define IMAGER_IMEXTPLTYPES_H_ + +#ifndef PERL_NO_GET_CONTEXT +#error Sorry, you need to build with PERL_NO_GET_CONTEXT +#endif + +#define IMAGER_PL_API_VERSION 1 + +/* This file provides functions useful for external code in + interfacing with perl - these functions aren't part of the core + Imager API. */ + +#define IMAGER_PL_API_LEVEL 1 + +typedef struct { + int version; + int level; + + /* IMAGER_PL_API_LEVEL 1 functions */ + void (*f_ip_handle_quant_opts)(pTHX_ i_quantize *quant, HV *hv); + void (*f_ip_cleanup_quant_opts)(pTHX_ i_quantize *quant); + void (*f_ip_copy_colors_back)(pTHX_ HV *hv, i_quantize *quant); + + /* IMAGER_PL_API_LEVEL 2 functions will go here */ +} im_pl_ext_funcs; + +#define PERL_PL_FUNCTION_TABLE_NAME "Imager::__ext_pl_func_table" + +#endif diff --git a/t/t105gif.t b/t/t105gif.t deleted file mode 100644 index 7514ac21..00000000 --- a/t/t105gif.t +++ /dev/null @@ -1,847 +0,0 @@ -#!perl -w - -=pod - -IF THIS TEST CRASHES - -Giflib/libungif have a long history of bugs, so if this script crashes -and you aren't running version 4.1.4 of giflib or libungif then -UPGRADE. - -=cut - -use strict; -$|=1; -use Test::More; -use Imager qw(:all); -use Imager::Test qw(is_color3 test_image test_image_raw); - -use Carp 'confess'; -$SIG{__DIE__} = sub { confess @_ }; - -my $buggy_giflib_file = "buggy_giflib.txt"; - -init_log("testout/t105gif.log",1); - -i_has_format("gif") - or plan skip_all => "no gif support"; - -plan tests => 146; - -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); - -my $img=test_image_raw; - -my $gifver = Imager::i_giflib_version(); -diag("giflib version (from header) $gifver"); - -open(FH,">testout/t105.gif") || die "Cannot open testout/t105.gif\n"; -binmode(FH); -ok(i_writegifmc($img,fileno(FH),6), "write low") or - die "Cannot write testout/t105.gif\n"; -close(FH); - -open(FH,"testout/t105.gif") || die "Cannot open testout/t105.gif\n"; -binmode(FH); -ok($img=i_readgif(fileno(FH)), "read low") - or die "Cannot read testout/t105.gif\n"; -close(FH); - -open(FH,"testout/t105.gif") || die "Cannot open testout/t105.gif\n"; -binmode(FH); -($img, my $palette)=i_readgif(fileno(FH)); -ok($img, "read palette") or die "Cannot read testout/t105.gif\n"; -close(FH); - -$palette=''; # just to skip a warning. - -# check that reading interlaced/non-interlaced versions of -# the same GIF produce the same image -# I could replace this with code that used Imager's built-in -# 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; -my $IO = Imager::io_new_fd( fileno(FH) ); -i_writeppm_wiol($imgi, $IO) - or die "Cannot write testout/t105i.ppm"; -close FH; - -open FH, ">testout/t105ni.ppm" or die "Cannot create testout/t105ni.ppm"; -binmode FH; -$IO = Imager::io_new_fd( fileno(FH) ); -i_writeppm_wiol($imgni, $IO) - or die "Cannot write testout/t105ni.ppm"; -close FH; - -# compare them -open FH, " }; -close FH; - -open FH, " }; -close FH; -is($datai, $datani, "images match"); - -SKIP: -{ - skip("giflib3 doesn't support callbacks", 4) unless $gifver >= 4.0; - # reading with a callback - # various sizes to make sure the buffering works - # requested size - open FH, "testout/t105_mc.gif" or die "Cannot open testout/t105_mc.gif"; -binmode FH; -ok(i_writegifmc($img, fileno(FH), 7), "writegifmc"); -close(FH); - -# new writegif_gen -# test webmap, custom errdiff map -# (looks fairly awful) -open FH, ">testout/t105_gen.gif" or die $!; -binmode FH; -ok(i_writegif_gen(fileno(FH), { make_colors=>'webmap', - translate=>'errdiff', - errdiff=>'custom', - errdiff_width=>2, - errdiff_height=>2, - errdiff_map=>[0, 1, 1, 0]}, $img), - "webmap, custom errdif map"); -close FH; - -print "# the following tests are fairly slow\n"; - -# test animation, mc_addi, error diffusion, ordered transparency -my @imgs; -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); - } - i_box_filled($im, 0, $i*40, 199, 199, $blue); - push(@imgs, $im); -} -my @gif_delays = (50) x 5; -my @gif_disposal = (2) x 5; -open FH, ">testout/t105_anim.gif" or die $!; -binmode FH; -ok(i_writegif_gen(fileno(FH), { make_colors=>'addi', - translate=>'closest', - gif_delays=>\@gif_delays, - gif_disposal=>\@gif_disposal, - gif_positions=> [ map [ $_*10, $_*10 ], 0..4 ], - gif_user_input=>[ 1, 0, 1, 0, 1 ], - transp=>'ordered', - 'tr_orddith'=>'dot8'}, @imgs), - "write anim gif"); -close FH; - -my $can_write_callback = 0; -unlink $buggy_giflib_file; -SKIP: -{ - skip("giflib3 doesn't support callbacks", 1) unless $gifver >= 4.0; - ++$can_write_callback; - my $good = ext_test(14, <<'ENDOFCODE'); -use Imager qw(:all); -use Imager::Test qw(test_image_raw); -my $timg = test_image_raw(); -my @gif_delays = (50) x 5; -my @gif_disposal = (2) x 5; -my @imgs = ($timg) x 5; -open FH, "> testout/t105_anim_cb.gif" or die $!; -binmode FH; -i_writegif_callback(sub { - print FH $_[0] - }, - -1, # max buffering - { make_colors=>'webmap', - translate=>'closest', - gif_delays=>\@gif_delays, - gif_disposal=>\@gif_disposal, - #transp=>'ordered', - tr_orddith=>'dot8'}, @imgs) - or die "Cannot write anim gif"; -close FH; -print "ok 14\n"; -exit; -ENDOFCODE - unless ($good) { - $can_write_callback = 0; - fail("see $buggy_giflib_file"); - print STDERR "\nprobable buggy giflib - skipping tests that depend on a good giflib\n"; - print STDERR "see $buggy_giflib_file for more information\n"; - open FLAG, "> $buggy_giflib_file" or die; - print FLAG <1, gif_delay=>150, gif_loop=>10); - for my $x (0 .. 39) { - for my $y (0 .. 39) { - $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); - } - } - push(@imgs, $im); -} -# test giflib with multiple palettes -# (it was meant to test the NS loop extension too, but that's broken) -# this looks better with make_colors=>'addi', translate=>'errdiff' -# this test aims to overload the palette for each image, so the -# output looks moderately horrible -open FH, ">testout/t105_mult_pall.gif" or die "Cannot create file: $!"; -binmode FH; -ok(i_writegif_gen(fileno(FH), { #make_colors=>'webmap', - translate=>'giflib', - }, @imgs), "write multiple palettes") - or print "# ", join(":", map $_->[1], Imager::i_errors()),"\n"; -close FH; - -# regression test: giflib doesn't like 1 colour images -my $img1 = Imager::ImgRaw::new(100, 100, 3); -i_box_filled($img1, 0, 0, 100, 100, $red); -open FH, ">testout/t105_onecol.gif" or die $!; -binmode FH; -ok(i_writegif_gen(fileno(FH), { translate=>'giflib'}, $img1), - "single colour write regression"); -close FH; - -# transparency test -# previously it was harder do write transparent images -# tests the improvements -my $timg = Imager::ImgRaw::new(20, 20, 4); -my $trans = i_color_new(255, 0, 0, 127); -i_box_filled($timg, 0, 0, 20, 20, $green); -i_box_filled($timg, 2, 2, 18, 18, $trans); -open FH, ">testout/t105_trans.gif" or die $!; -binmode FH; -ok(i_writegif_gen(fileno(FH), { make_colors=>'addi', - translate=>'closest', - transp=>'ordered', - }, $timg), "write transparent"); -close FH; - -# some error handling tests -# open a file handle for read and try to save to it -# is this idea portable? -# whether or not it is, giflib segfaults on this -#open FH, "testout/t105_none.gif" - or die "Cannot open testout/t105_none.gif: $!"; -binmode FH; -if (ok(!i_writegif_gen(fileno(FH), {}, "hello"), "shouldn't be able to write a string as a gif")) { - print "# ",Imager::_error_as_msg(),"\n"; -} - -# try to read a truncated gif (no image descriptors) -read_failure('testimg/trimgdesc.gif'); -# file truncated just after the image descriptor tag -read_failure('testimg/trmiddesc.gif'); -# image has no colour map -read_failure('testimg/nocmap.gif'); - -SKIP: -{ - skip("see $buggy_giflib_file", 18) if -e $buggy_giflib_file; - # image has a local colour map - open FH, "< testimg/loccmap.gif" - or die "Cannot open testimg/loccmap.gif: $!"; - binmode FH; - ok(i_readgif(fileno(FH)), "read an image with only a local colour map"); - close FH; - - # image has global and local colour maps - open FH, "< testimg/screen2.gif" - or die "Cannot open testimg/screen2.gif: $!"; - binmode FH; - my $ims = i_readgif(fileno(FH)); - unless (ok($ims, "read an image with global and local colour map")) { - print "# ",Imager::_error_as_msg(),"\n"; - } - close FH; - - open FH, "< testimg/expected.gif" - or die "Cannot open testimg/expected.gif: $!"; - binmode FH; - my $ime = i_readgif(fileno(FH)); - close FH; - ok($ime, "reading testimg/expected.gif"); - SKIP: - { - skip("could not read one or both of expected.gif or loccamp.gif", 1) - unless $ims and $ime; - unless (is(i_img_diff($ime, $ims), 0, - "compare loccmap and expected")) { - # save the bad one - open FH, "> testout/t105_screen2.gif" - or die "Cannot create testout/t105_screen.gif: $!"; - binmode FH; - i_writegifmc($ims, fileno(FH), 7) - or print "# could not save t105_screen.gif\n"; - close FH; - } - } - - # test reading a multi-image file into multiple images - open FH, "< testimg/screen2.gif" - or die "Cannot open testimg/screen2.gif: $!"; - binmode FH; - @imgs = Imager::i_readgif_multi(fileno(FH)); - ok(@imgs, "read multi-image file into multiple images"); - close FH; - is(@imgs, 2, "should be 2 images"); - my $paletted = 1; - for my $img (@imgs) { - unless (Imager::i_img_type($img) == 1) { - $paletted = 0; - last; - } - } - ok($paletted, "both images should be paletted"); - is(Imager::i_colorcount($imgs[0]), 4, "4 colours in first image"); - is(Imager::i_colorcount($imgs[1]), 2, "2 colours in second image"); - ok(Imager::i_tags_find($imgs[0], "gif_left", 0), - "gif_left tag should be there"); - my @tags = map {[ Imager::i_tags_get($imgs[1], $_) ]} 0..Imager::i_tags_count($imgs[1])-1; - my ($left) = grep $_->[0] eq 'gif_left', @tags; - ok($left && $left->[1] == 3, "check gif_left value"); - - # screen3.gif was saved with - open FH, "< testimg/screen3.gif" - or die "Cannot open testimg/screen3.gif: $!"; - binmode FH; - @imgs = Imager::i_readgif_multi(fileno(FH)); - ok(@imgs, "read screen3.gif"); - close FH; - eval { - require 'Data/Dumper.pm'; - Data::Dumper->import(); - }; - unless ($@) { - # build a big map of all tags for all images - @tags = - map { - my $im = $_; - [ - map { join ",", map { defined() ? $_ : "undef" } Imager::i_tags_get($im, $_) } - 0..Imager::i_tags_count($_)-1 - ] - } @imgs; - my $dump = Dumper(\@tags); - $dump =~ s/^/# /mg; - print "# tags from gif\n", $dump; - } - - # at this point @imgs should contain only paletted images - ok(Imager::i_img_type($imgs[0]) == 1, "imgs[0] paletted"); - ok(Imager::i_img_type($imgs[1]) == 1, "imgs[1] paletted"); - - # see how we go saving it - open FH, ">testout/t105_pal.gif" or die $!; - binmode FH; - ok(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((my @imgs2 = Imager::i_readgif_multi(fileno(FH))) == 2, - "re-reading saved paletted images"); - ok(i_img_diff($imgs[0], $imgs2[0]) == 0, "imgs[0] mismatch"); - ok(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($ooim->read(file=>"testout/t105.gif"), "read into object"); - ok($ooim->write(file=>"testout/t105_warn.gif", interlace=>1), - "save from object") - or print "# ", $ooim->errstr, "\n"; - ok(grep(/Obsolete .* interlace .* gif_interlace/, @warns), - "check for warning"); - init(warn_obsolete=>0); - @warns = (); - ok($ooim->write(file=>"testout/t105_warn.gif", interlace=>1), - "save from object"); - ok(!grep(/Obsolete .* interlace .* gif_interlace/, @warns), - "check for warning"); -} - -# test that we get greyscale from 1 channel images -# we check for each makemap, and for each translate -print "# test writes of grayscale images - ticket #365\n"; -my $ooim = Imager->new(xsize=>50, ysize=>50, channels=>1); -for (my $y = 0; $y < 50; $y += 10) { - $ooim->box(box=>[ 0, $y, 49, $y+9], color=>NC($y*5,0,0), filled=>1); -} -my $ooim3 = $ooim->convert(preset=>'rgb'); -#$ooim3->write(file=>'testout/t105gray.ppm'); -my %maxerror = ( mediancut => 51000, - addi => 0, - closest => 0, - perturb => 0, - errdiff => 0 ); -for my $makemap (qw(mediancut addi)) { - print "# make_colors => $makemap\n"; - ok( $ooim->write(file=>"testout/t105gray-$makemap.gif", - make_colors=>$makemap, - gifquant=>'gen'), - "writing gif with makemap $makemap"); - my $im2 = Imager->new; - if (ok($im2->read(file=>"testout/t105gray-$makemap.gif"), - "reading written grayscale gif")) { - my $diff = i_img_diff($ooim3->{IMG}, $im2->{IMG}); - ok($diff <= $maxerror{$makemap}, "comparing images $diff"); - #$im2->write(file=>"testout/t105gray-$makemap.ppm"); - } - else { - SKIP: { skip("could not get test image", 1); } - } -} -for my $translate (qw(closest perturb errdiff)) { - print "# translate => $translate\n"; - my @colors = map NC($_*50, $_*50, $_*50), 0..4; - ok($ooim->write(file=>"testout/t105gray-$translate.gif", - translate=>$translate, - make_colors=>'none', - colors=>\@colors, - gifquant=>'gen'), - "writing gif with translate $translate"); - my $im2 = Imager->new; - if (ok($im2->read(file=>"testout/t105gray-$translate.gif"), - "reading written grayscale gif")) { - my $diff = i_img_diff($ooim3->{IMG}, $im2->{IMG}); - ok($diff <= $maxerror{$translate}, "comparing images $diff"); - #$im2->write(file=>"testout/t105gray-$translate.ppm"); - } - else { - SKIP: { skip("could not load test image", 1) } - } - } - -# try to write an image with no colors - should error -ok(!$ooim->write(file=>"testout/t105nocolors.gif", - make_colors=>'none', - colors=>[], gifquant=>'gen'), - "write with no colors"); - -# try to write multiple with no colors, with separate maps -# I don't see a way to test this, since we don't have a mechanism -# to give the second image different quant options, we can't trigger -# a failure just for the second image - -# check that the i_format tag is set for both multiple and single -# image reads -{ - my @anim = Imager->read_multi(file=>"testout/t105_anim.gif"); - ok(@anim == 5, "check we got all the images"); - for my $frame (@anim) { - my ($type) = $frame->tags(name=>'i_format'); - is($type, 'gif', "check i_format for animation frame"); - } - - my $im = Imager->new; - ok($im->read(file=>"testout/t105.gif"), "read some gif"); - my ($type) = $im->tags(name=>'i_format'); - is($type, 'gif', 'check i_format for single image read'); -} - -{ # check file limits are checked - my $limit_file = "testout/t105.gif"; - ok(Imager->set_file_limits(reset=>1, width=>149), "set width limit 149"); - my $im = Imager->new; - ok(!$im->read(file=>$limit_file), - "should fail read due to size limits"); - print "# ",$im->errstr,"\n"; - like($im->errstr, qr/image width/, "check message"); - - ok(Imager->set_file_limits(reset=>1, height=>149), "set height limit 149"); - ok(!$im->read(file=>$limit_file), - "should fail read due to size limits"); - print "# ",$im->errstr,"\n"; - like($im->errstr, qr/image height/, "check message"); - - ok(Imager->set_file_limits(reset=>1, width=>150), "set width limit 150"); - ok($im->read(file=>$limit_file), - "should succeed - just inside width limit"); - ok(Imager->set_file_limits(reset=>1, height=>150), "set height limit 150"); - ok($im->read(file=>$limit_file), - "should succeed - just inside height limit"); - - # 150 x 150 x 3 channel image uses 67500 bytes - ok(Imager->set_file_limits(reset=>1, bytes=>67499), - "set bytes limit 67499"); - ok(!$im->read(file=>$limit_file), - "should fail - too many bytes"); - print "# ",$im->errstr,"\n"; - like($im->errstr, qr/storage size/, "check error message"); - ok(Imager->set_file_limits(reset=>1, bytes=>67500), - "set bytes limit 67500"); - ok($im->read(file=>$limit_file), - "should succeed - just inside bytes limit"); - Imager->set_file_limits(reset=>1); -} - -{ - print "# test OO interface reading of consolidated images\n"; - my $im = Imager->new; - ok($im->read(file=>'testimg/screen2.gif', gif_consolidate=>1), - "read image to consolidate"); - my $expected = Imager->new; - ok($expected->read(file=>'testimg/expected.gif'), - "read expected via OO"); - is(i_img_diff($im->{IMG}, $expected->{IMG}), 0, - "compare them"); - - # check the default read doesn't match - ok($im->read(file=>'testimg/screen2.gif'), - "read same image without consolidate"); - isnt(i_img_diff($im->{IMG}, $expected->{IMG}), 0, - "compare them - shouldn't include the overlayed second image"); -} -{ - print "# test the reading of single pages\n"; - # build a test file - my $test_file = 'testout/t105_multi_sing.gif'; - my $im1 = Imager->new(xsize=>100, ysize=>100); - $im1->box(filled=>1, color=>$blue); - $im1->addtag(name=>'gif_left', value=>10); - $im1->addtag(name=>'gif_top', value=>15); - $im1->addtag(name=>'gif_comment', value=>'First page'); - my $im2 = Imager->new(xsize=>50, ysize=>50); - $im2->box(filled=>1, color=>$red); - $im2->addtag(name=>'gif_left', value=>30); - $im2->addtag(name=>'gif_top', value=>25); - $im2->addtag(name=>'gif_comment', value=>'Second page'); - my $im3 = Imager->new(xsize=>25, ysize=>25); - $im3->box(filled=>1, color=>$green); - $im3->addtag(name=>'gif_left', value=>35); - $im3->addtag(name=>'gif_top', value=>45); - # don't set comment for $im3 - ok(Imager->write_multi({ file=> $test_file}, $im1, $im2, $im3), - "write test file for single page reads"); - - my $res = Imager->new; - # check we get the first image - ok($res->read(file=>$test_file), "read default (first) page"); - is(i_img_diff($im1->{IMG}, $res->{IMG}), 0, "compare against first"); - # check tags - is($res->tags(name=>'gif_left'), 10, "gif_left"); - is($res->tags(name=>'gif_top'), 15, "gif_top"); - is($res->tags(name=>'gif_comment'), 'First page', "gif_comment"); - - # get the second image - ok($res->read(file=>$test_file, page=>1), "read second page") - or print "# ",$res->errstr, "\n"; - is(i_img_diff($im2->{IMG}, $res->{IMG}), 0, "compare against second"); - # check tags - is($res->tags(name=>'gif_left'), 30, "gif_left"); - is($res->tags(name=>'gif_top'), 25, "gif_top"); - is($res->tags(name=>'gif_comment'), 'Second page', "gif_comment"); - - # get the third image - ok($res->read(file=>$test_file, page=>2), "read third page") - or print "# ",$res->errstr, "\n"; - is(i_img_diff($im3->{IMG}, $res->{IMG}), 0, "compare against third"); - is($res->tags(name=>'gif_left'), 35, "gif_left"); - is($res->tags(name=>'gif_top'), 45, "gif_top"); - is($res->tags(name=>'gif_comment'), undef, 'gif_comment undef'); - - # try to read a fourth page - ok(!$res->read(file=>$test_file, page=>3), "fail reading fourth page"); - cmp_ok($res->errstr, "=~", 'page 3 not found', - "check error message"); -} -SKIP: -{ - skip("gif_loop not supported on giflib before 4.1", 6) - unless $gifver >= 4.1; - # testing writing the loop extension - my $im1 = Imager->new(xsize => 100, ysize => 100); - $im1->box(filled => 1, color => '#FF0000'); - my $im2 = Imager->new(xsize => 100, ysize => 100); - $im2->box(filled => 1, color => '#00FF00'); - ok(Imager->write_multi({ - gif_loop => 5, - gif_delay => 50, - file => 'testout/t105loop.gif' - }, $im1, $im2), - "write with loop extension"); - - my @im = Imager->read_multi(file => 'testout/t105loop.gif'); - is(@im, 2, "read loop images back"); - is($im[0]->tags(name => 'gif_loop'), 5, "first loop read back"); - is($im[1]->tags(name => 'gif_loop'), 5, "second loop read back"); - is($im[0]->tags(name => 'gif_delay'), 50, "first delay read back"); - is($im[1]->tags(name => 'gif_delay'), 50, "second delay read back"); -} -SKIP: -{ # check graphic control extension and ns loop tags are read correctly - print "# check GCE and netscape loop extension tag values\n"; - my @im = Imager->read_multi(file => 'testimg/screen3.gif'); - is(@im, 2, "read 2 images from screen3.gif") - or skip("Could not load testimg/screen3.gif:".Imager->errstr, 11); - is($im[0]->tags(name => 'gif_delay'), 50, "0 - gif_delay"); - is($im[0]->tags(name => 'gif_disposal'), 2, "0 - gif_disposal"); - is($im[0]->tags(name => 'gif_trans_index'), undef, "0 - gif_trans_index"); - is($im[0]->tags(name => 'gif_user_input'), 0, "0 - gif_user_input"); - is($im[0]->tags(name => 'gif_loop'), 0, "0 - gif_loop"); - is($im[1]->tags(name => 'gif_delay'), 50, "1 - gif_delay"); - is($im[1]->tags(name => 'gif_disposal'), 2, "1 - gif_disposal"); - is($im[1]->tags(name => 'gif_trans_index'), 7, "1 - gif_trans_index"); - is($im[1]->tags(name => 'gif_trans_color'), 'color(255,255,255,0)', - "1 - gif_trans_index"); - is($im[1]->tags(name => 'gif_user_input'), 0, "1 - gif_user_input"); - is($im[1]->tags(name => 'gif_loop'), 0, "1 - gif_loop"); -} - -{ - # manually modified from a small gif, this had the palette - # size changed to half the size, leaving an index out of range - my $im = Imager->new; - ok($im->read(file => 'testimg/badindex.gif', type => 'gif'), - "read bad index gif") - or print "# ", $im->errstr, "\n"; - my @indexes = $im->getscanline('y' => 0, type => 'index'); - is_deeply(\@indexes, [ 0..4 ], "check for correct indexes"); - is($im->colorcount, 5, "check the palette was adjusted"); - is_color3($im->getpixel('y' => 0, x => 4), 0, 0, 0, - "check it was black added"); - is($im->tags(name => 'gif_colormap_size'), 4, 'color map size tag'); -} - -{ - ok(grep($_ eq 'gif', Imager->read_types), "check gif in read types"); - ok(grep($_ eq 'gif', Imager->write_types), "check gif in write types"); -} - -{ - # check screen tags handled correctly note the screen size - # supplied is larger than the box covered by the images - my $im1 = Imager->new(xsize => 10, ysize => 8); - $im1->settag(name => 'gif_top', value => 4); - $im1->settag(name => 'gif_screen_width', value => 18); - $im1->settag(name => 'gif_screen_height', value => 16); - my $im2 = Imager->new(xsize => 7, ysize => 10); - $im2->settag(name => 'gif_left', value => 3); - my @im = ( $im1, $im2 ); - - my $data; - ok(Imager->write_multi({ data => \$data, type => 'gif' }, @im), - "write with screen settings") - or print "# ", Imager->errstr, "\n"; - my @result = Imager->read_multi(data => $data); - is(@result, 2, "got 2 images back"); - is($result[0]->tags(name => 'gif_screen_width'), 18, - "check result screen width"); - is($result[0]->tags(name => 'gif_screen_height'), 16, - "check result screen height"); - is($result[0]->tags(name => 'gif_left'), 0, - "check first gif_left"); - is($result[0]->tags(name => 'gif_top'), 4, - "check first gif_top"); - is($result[1]->tags(name => 'gif_left'), 3, - "check second gif_left"); - is($result[1]->tags(name => 'gif_top'), 0, - "check second gif_top"); -} - -{ # test colors array returns colors - my $data; - my $im = test_image(); - my @colors; - ok($im->write(data => \$data, - colors => \@colors, - make_colors => 'webmap', - translate => 'closest', - gifquant => 'gen', - type => 'gif'), - "write using webmap to check color table"); - is(@colors, 216, "should be 216 colors in the webmap"); - is_color3($colors[0], 0, 0, 0, "first should be 000000"); - is_color3($colors[1], 0, 0, 0x33, "second should be 000033"); - is_color3($colors[8], 0, 0x33, 0x66, "9th should be 003366"); -} - -{ # a zero length extension could make read_/read_multi crash - my ($im) = Imager->read_multi(file => "testimg/zerocomm.gif"); - ok($im, "read image with zero-length extension"); -} - -sub test_readgif_cb { - my ($size) = @_; - - open FH, "deltag(code=>0); - } -} - -sub _add_tags { - my ($img, %tags) = @_; - - for my $key (keys %tags) { - Imager::i_tags_add($img, $key, 0, $tags{$key}, 0); - } -} - -sub ext_test { - my ($testnum, $code, $count, $name) = @_; - - $count ||= 1; - $name ||= "gif$testnum"; - - # build our code - my $script = "testout/$name.pl"; - if (open SCRIPT, "> $script") { - print SCRIPT <<'PROLOG'; -#!perl -w -if (lc $^O eq 'mswin32') { - # avoid the dialog box that window's pops up on a GPF - # if you want to debug this stuff, I suggest you comment out the - # following - eval { - require Win32API::File; - Win32API::File::SetErrorMode( Win32API::File::SEM_NOGPFAULTERRORBOX()); - }; -} -PROLOG - - print SCRIPT $code; - close SCRIPT; - - my $perl = $^X; - $perl = qq/"$perl"/ if $perl =~ / /; - - print "# script: $script\n"; - my $cmd = "$perl -Mblib $script"; - print "# command: $cmd\n"; - - my $ok = 1; - my @out = `$cmd`; # should work on DOS and Win32 - my $found = 0; - for (@out) { - if (/^not ok\s+(?:\d+\s*)?#(.*)/ || /^not ok/) { - my $msg = $1 || ''; - ok(0, $msg); - $ok = 0; - ++$found; - } - elsif (/^ok\s+(?:\d+\s*)?#(.*)/ || /^ok/) { - my $msg = $1 || ''; - ok(1, $msg); - ++$found; - } - } - unless ($count == $found) { - print "# didn't see enough ok/not ok\n"; - $ok = 0; - } - return $ok; - } - else { - return skip("could not create test script $script: $!"); - return 0; - } -} diff --git a/t/t105nogif.t b/t/t105nogif.t index 586bd461..b93ef724 100644 --- a/t/t105nogif.t +++ b/t/t105nogif.t @@ -4,7 +4,7 @@ $|=1; use Test::More; use Imager qw(:all); -i_has_format("gif") +$Imager::formats{"gif"} and plan skip_all => "gif support available and this tests the lack of it"; plan tests => 12; diff --git a/t/t50basicoo.t b/t/t50basicoo.t index 39d907ac..1da09f02 100644 --- a/t/t50basicoo.t +++ b/t/t50basicoo.t @@ -44,7 +44,7 @@ my %files; { file => "testimg/test.png" }, { file => "testimg/test.raw", xsize=>150, ysize=>150, type=>'raw', interleave => 0}, { file => "testimg/penguin-base.ppm" }, - { file => "testimg/expected.gif" }, + { file => "GIF/testimg/expected.gif" }, { file => "testimg/comp8.tif" }, { file => "testimg/winrgb24.bmp" }, { file => "testimg/test.tga" }, ); @@ -99,71 +99,66 @@ for my $type (@types) { skip("couldn't open the damn file: $!", 7); } - if ($type ne 'gif' || Imager::i_giflib_version() >= 4) { - # read from a memory buffer - open DATA, "< $opts{file}" - or die "Cannot open $opts{file}: $!"; - binmode DATA; - my $data = do { local $/; }; - close DATA; - my $bimg = Imager->new; - - if (ok($bimg->read(data=>$data, %mopts, type=>$type), "read from buffer", - $img)) { - ok(Imager::i_img_diff($img->{IMG}, $bimg->{IMG}) == 0, - "comparing buffer read image"); - } - else { - skip("nothing to compare"); - } - - # read from callbacks, both with minimum and maximum reads - my $buf = $data; - my $seekpos = 0; - my $reader_min = - sub { - my ($size, $maxread) = @_; - my $out = substr($buf, $seekpos, $size); - $seekpos += length $out; - $out; - }; - my $reader_max = - sub { - my ($size, $maxread) = @_; - my $out = substr($buf, $seekpos, $maxread); - $seekpos += length $out; - $out; - }; - my $seeker = - sub { - my ($offset, $whence) = @_; - #print "io_seeker($offset, $whence)\n"; - if ($whence == SEEK_SET) { - $seekpos = $offset; - } - elsif ($whence == SEEK_CUR) { - $seekpos += $offset; - } - else { # SEEK_END - $seekpos = length($buf) + $offset; - } - #print "-> $seekpos\n"; - $seekpos; - }; - my $cbimg = Imager->new; - ok($cbimg->read(callback=>$reader_min, seekcb=>$seeker, type=>$type, %mopts), - "read from callback min", $cbimg); - ok(Imager::i_img_diff($cbimg->{IMG}, $img->{IMG}) == 0, - "comparing mincb image"); - $seekpos = 0; - ok($cbimg->read(callback=>$reader_max, seekcb=>$seeker, type=>$type, %mopts), - "read from callback max", $cbimg); - ok(Imager::i_img_diff($cbimg->{IMG}, $img->{IMG}) == 0, - "comparing maxcb image"); + # read from a memory buffer + open DATA, "< $opts{file}" + or die "Cannot open $opts{file}: $!"; + binmode DATA; + my $data = do { local $/; }; + close DATA; + my $bimg = Imager->new; + + if (ok($bimg->read(data=>$data, %mopts, type=>$type), "read from buffer", + $img)) { + ok(Imager::i_img_diff($img->{IMG}, $bimg->{IMG}) == 0, + "comparing buffer read image"); } else { - skip("giflib < 4 doesn't support callbacks", 6); + skip("nothing to compare"); } + + # read from callbacks, both with minimum and maximum reads + my $buf = $data; + my $seekpos = 0; + my $reader_min = + sub { + my ($size, $maxread) = @_; + my $out = substr($buf, $seekpos, $size); + $seekpos += length $out; + $out; + }; + my $reader_max = + sub { + my ($size, $maxread) = @_; + my $out = substr($buf, $seekpos, $maxread); + $seekpos += length $out; + $out; + }; + my $seeker = + sub { + my ($offset, $whence) = @_; + #print "io_seeker($offset, $whence)\n"; + if ($whence == SEEK_SET) { + $seekpos = $offset; + } + elsif ($whence == SEEK_CUR) { + $seekpos += $offset; + } + else { # SEEK_END + $seekpos = length($buf) + $offset; + } + #print "-> $seekpos\n"; + $seekpos; + }; + my $cbimg = Imager->new; + ok($cbimg->read(callback=>$reader_min, seekcb=>$seeker, type=>$type, %mopts), + "read from callback min", $cbimg); + ok(Imager::i_img_diff($cbimg->{IMG}, $img->{IMG}) == 0, + "comparing mincb image"); + $seekpos = 0; + ok($cbimg->read(callback=>$reader_max, seekcb=>$seeker, type=>$type, %mopts), + "read from callback max", $cbimg); + ok(Imager::i_img_diff($cbimg->{IMG}, $img->{IMG}) == 0, + "comparing maxcb image"); } for my $type (@types) { @@ -199,106 +194,95 @@ for my $type (@types) { "write to FH after writing $type"); ok($fh->close, "closing FH after writing $type"); - if ($type ne 'gif' || - (Imager::i_giflib_version() >= 4 && !-e $buggy_giflib_file)) { - if (ok(open(DATA, "< $file"), "opening data source")) { - binmode DATA; - my $data = do { local $/; }; - close DATA; - - # writing to a buffer - print "# writing $type to a buffer\n"; - my $buf = ''; - ok($wimg->write(data=>\$buf, %extraopts, type=>$type), - "writing $type to a buffer", $wimg); - $buf .= "SUFFIX\n"; - open DATA, "> testout/t50_buf.$type" - or die "Cannot create $type buffer file: $!"; - binmode DATA; - print DATA $buf; - close DATA; - ok($data eq $buf, "comparing file data to buffer"); - - $buf = ''; - my $seekpos = 0; - my $did_close; - my $writer = - sub { - my ($what) = @_; - if ($seekpos > length $buf) { - $buf .= "\0" x ($seekpos - length $buf); - } - substr($buf, $seekpos, length $what) = $what; - $seekpos += length $what; - $did_close = 0; # the close must be last - 1; - }; - my $reader_min = - sub { - my ($size, $maxread) = @_; - my $out = substr($buf, $seekpos, $size); - $seekpos += length $out; - $out; - }; - my $reader_max = - sub { - my ($size, $maxread) = @_; - my $out = substr($buf, $seekpos, $maxread); - $seekpos += length $out; - $out; - }; - use IO::Seekable; - my $seeker = - sub { - my ($offset, $whence) = @_; - #print "io_seeker($offset, $whence)\n"; - if ($whence == SEEK_SET) { - $seekpos = $offset; - } - elsif ($whence == SEEK_CUR) { - $seekpos += $offset; - } - else { # SEEK_END - $seekpos = length($buf) + $offset; - } - #print "-> $seekpos\n"; - $seekpos; - }; - - my $closer = sub { ++$did_close; }; - - print "# writing $type via callbacks (mb=1)\n"; - ok($wimg->write(writecb=>$writer, seekcb=>$seeker, closecb=>$closer, - readcb=>$reader_min, - %extraopts, type=>$type, maxbuffer=>1), - "writing $type to callback (mb=1)", $wimg); - - ok($did_close, "checking closecb called"); - $buf .= "SUFFIX\n"; - ok($data eq $buf, "comparing callback output to file data"); - print "# writing $type via callbacks (no mb)\n"; - $buf = ''; - $did_close = 0; - $seekpos = 0; - # we don't use the closecb here - used to make sure we don't get - # a warning/error on an attempt to call an undef close sub - ok($wimg->write(writecb=>$writer, seekcb=>$seeker, readcb=>$reader_min, - %extraopts, type=>$type), - "writing $type to callback (no mb)", $wimg); - $buf .= "SUFFIX\n"; - ok($data eq $buf, "comparing callback output to file data"); - } - else { - skip("couldn't open data source", 7); - } + if (ok(open(DATA, "< $file"), "opening data source")) { + binmode DATA; + my $data = do { local $/; }; + close DATA; + + # writing to a buffer + print "# writing $type to a buffer\n"; + my $buf = ''; + ok($wimg->write(data=>\$buf, %extraopts, type=>$type), + "writing $type to a buffer", $wimg); + $buf .= "SUFFIX\n"; + open DATA, "> testout/t50_buf.$type" + or die "Cannot create $type buffer file: $!"; + binmode DATA; + print DATA $buf; + close DATA; + ok($data eq $buf, "comparing file data to buffer"); + + $buf = ''; + my $seekpos = 0; + my $did_close; + my $writer = + sub { + my ($what) = @_; + if ($seekpos > length $buf) { + $buf .= "\0" x ($seekpos - length $buf); + } + substr($buf, $seekpos, length $what) = $what; + $seekpos += length $what; + $did_close = 0; # the close must be last + 1; + }; + my $reader_min = + sub { + my ($size, $maxread) = @_; + my $out = substr($buf, $seekpos, $size); + $seekpos += length $out; + $out; + }; + my $reader_max = + sub { + my ($size, $maxread) = @_; + my $out = substr($buf, $seekpos, $maxread); + $seekpos += length $out; + $out; + }; + use IO::Seekable; + my $seeker = + sub { + my ($offset, $whence) = @_; + #print "io_seeker($offset, $whence)\n"; + if ($whence == SEEK_SET) { + $seekpos = $offset; + } + elsif ($whence == SEEK_CUR) { + $seekpos += $offset; + } + else { # SEEK_END + $seekpos = length($buf) + $offset; + } + #print "-> $seekpos\n"; + $seekpos; + }; + + my $closer = sub { ++$did_close; }; + + print "# writing $type via callbacks (mb=1)\n"; + ok($wimg->write(writecb=>$writer, seekcb=>$seeker, closecb=>$closer, + readcb=>$reader_min, + %extraopts, type=>$type, maxbuffer=>1), + "writing $type to callback (mb=1)", $wimg); + + ok($did_close, "checking closecb called"); + $buf .= "SUFFIX\n"; + ok($data eq $buf, "comparing callback output to file data"); + print "# writing $type via callbacks (no mb)\n"; + $buf = ''; + $did_close = 0; + $seekpos = 0; + # we don't use the closecb here - used to make sure we don't get + # a warning/error on an attempt to call an undef close sub + ok($wimg->write(writecb=>$writer, seekcb=>$seeker, readcb=>$reader_min, + %extraopts, type=>$type), + "writing $type to callback (no mb)", $wimg); + $buf .= "SUFFIX\n"; + ok($data eq $buf, "comparing callback output to file data"); } else { - if (-e $buggy_giflib_file) { - skip("see $buggy_giflib_file", 8); - } - else { - skip("giflib < 4 doesn't support callbacks", 8); - } + skip("couldn't open data source", 7); } } diff --git a/t/t70newgif.t b/t/t70newgif.t deleted file mode 100644 index 6b989da1..00000000 --- a/t/t70newgif.t +++ /dev/null @@ -1,170 +0,0 @@ -#!perl -w -# Before `make install' is performed this script should be runnable with -# `make test'. After `make install' it should work as `perl test.pl' - -######################### We start with some black magic to print on failure. - -# Change 1..1 below to 1..last_test_to_print . -# (It may become useful if the test is moved to ./t subdirectory.) - - -BEGIN { $| = 1; print "1..24\n"; } -END {print "not ok 1\n" unless $loaded;} - -my $buggy_giflib_file = "buggy_giflib.txt"; - -use Imager qw(:all :handy); -$loaded=1; - -print "ok 1\n"; - -Imager::init('log'=>'testout/t70newgif.log'); - -$green=i_color_new(0,255,0,0); -$blue=i_color_new(0,0,255,0); - -$img=Imager->new(); -$img->open(file=>'testimg/scale.ppm',type=>'pnm') || print "failed: ",$img->{ERRSTR},"\n"; -print "ok 2\n"; - - -if (i_has_format("gif")) { - $img->write(file=>'testout/t70newgif.gif',type=>'gif',gifplanes=>1,gifquant=>'lm',lmfixed=>[$green,$blue]) || print "failed: ",$img->{ERRSTR},"\nnot "; - print "ok 3\n"; - - # make sure the palette is loaded properly (minimal test) - my $im2 = Imager->new(); - my $map; - if ($im2->read(file=>'testimg/bandw.gif', colors=>\$map)) { - print "ok 4\n"; - # check the palette - if ($map) { - print "ok 5\n"; - if (@$map == 2) { - print "ok 6\n"; - my @sorted = sort { comp_entry($a,$b) } @$map; - # first entry must be #000000 and second #FFFFFF - if (comp_entry($sorted[0], NC(0,0,0)) == 0) { - print "ok 7\n"; - } - else { - print "not ok 7 # entry should be black\n"; - } - if (comp_entry($sorted[1], NC(255,255,255)) == 0) { - print "ok 8\n"; - } - else { - print "not ok 8 # entry should be white\n"; - } - } - else { - print "not ok 6 # bad map size\n"; - print "ok 7 # skipped bad map size\n"; - print "ok 8 # skipped bad map size\n"; - } - } - else { - print "not ok 5 # no map returned\n"; - for (6..8) { - print "ok $_ # skipped no map returned\n"; - } - } - } - else { - print "not ok 4 # ",$im2->errstr,"\n"; - print "ok 5 # skipped - couldn't load image\n"; - } - - # test the read_multi interface - my @imgs = Imager->read_multi(); - @imgs and print "not "; - print "ok 9\n"; - Imager->errstr =~ /callback parameter missing/ or print "not "; - print "ok 10 # ",Imager->errstr,"\n"; - - @imgs = Imager->read_multi(type=>'gif'); - @imgs and print "not "; - print "ok 11\n"; - Imager->errstr =~ /file/ or print "not "; - print "ok 12 # ",Imager->errstr,"\n"; - # kill warning - *NONESUCH = \20; - @imgs = Imager->read_multi(type=>'gif', fh=>*NONESUCH); - @imgs and print "not "; - print "ok 13\n"; - Imager->errstr =~ /fh option not open/ or print "not "; - print "ok 14 # ",Imager->errstr,"\n"; - unless (-e $buggy_giflib_file) { - @imgs = Imager->read_multi(type=>'gif', file=>'testimg/screen2.gif'); - @imgs == 2 or print "not "; - print "ok 15\n"; - grep(!UNIVERSAL::isa($_, 'Imager'), @imgs) and print "not "; - print "ok 16\n"; - grep($_->type eq 'direct', @imgs) and print "not "; - print "ok 17\n"; - (my @left = $imgs[0]->tags(name=>'gif_left')) == 1 or print "not "; - print "ok 18\n"; - my $left = $imgs[1]->tags(name=>'gif_left') or print "not "; - print "ok 19\n"; - $left == 3 or print "not "; - print "ok 20\n"; - } - else { - for (15 .. 20) { - print "ok $_ # skip see $buggy_giflib_file\n"; - } - } - if (Imager::i_giflib_version() >= 4.0) { - unless (-e $buggy_giflib_file) { - open FH, "< testimg/screen2.gif" - or die "Cannot open testimg/screen2.gif: $!"; - binmode FH; - my $cb = - sub { - my $tmp; - read(FH, $tmp, $_[0]) and $tmp - }; - @imgs = Imager->read_multi(type=>'gif', - callback => $cb) or print "not "; - print "ok 21\n"; - close FH; - @imgs == 2 or print "not "; - print "ok 22\n"; - - open FH, "< testimg/screen2.gif" - or die "Cannot open testimg/screen2.gif: $!"; - binmode FH; - my $data = do { local $/; ; }; - close FH; - @imgs = Imager->read_multi(type=>'gif', - data=>$data) or print "not "; - print "ok 23\n"; - @imgs = 2 or print "not "; - print "ok 24\n"; - } - else { - for (21..24) { - print "ok $_ # skip see $buggy_giflib_file\n"; - } - } - } - else { - for (21..24) { - print "ok $_ # skipped - giflib3 doesn't support callbacks\n"; - } - } -} -else { - for (3..24) { - print "ok $_ # skipped: no gif support\n"; - } -} - -sub comp_entry { - my ($l, $r) = @_; - my @l = $l->rgba; - my @r = $r->rgba; - return $l[0] <=> $r[0] - || $l[1] <=> $r[1] - || $l[2] <=> $r[2]; -} diff --git a/testimg/badindex.gif b/testimg/badindex.gif deleted file mode 100644 index 3591f487..00000000 Binary files a/testimg/badindex.gif and /dev/null differ diff --git a/testimg/bandw.gif b/testimg/bandw.gif deleted file mode 100644 index 93fb1d72..00000000 Binary files a/testimg/bandw.gif and /dev/null differ diff --git a/testimg/expected.gif b/testimg/expected.gif deleted file mode 100644 index 3409d386..00000000 Binary files a/testimg/expected.gif and /dev/null differ diff --git a/testimg/loccmap.gif b/testimg/loccmap.gif deleted file mode 100644 index 9dd264eb..00000000 Binary files a/testimg/loccmap.gif and /dev/null differ diff --git a/testimg/nocmap.gif b/testimg/nocmap.gif deleted file mode 100644 index 83941109..00000000 Binary files a/testimg/nocmap.gif and /dev/null differ diff --git a/testimg/scale.gif b/testimg/scale.gif deleted file mode 100644 index 265ed7f9..00000000 Binary files a/testimg/scale.gif and /dev/null differ diff --git a/testimg/scalei.gif b/testimg/scalei.gif deleted file mode 100644 index 3b3234a6..00000000 Binary files a/testimg/scalei.gif and /dev/null differ diff --git a/testimg/screen2.gif b/testimg/screen2.gif deleted file mode 100644 index 4dddf699..00000000 Binary files a/testimg/screen2.gif and /dev/null differ diff --git a/testimg/screen3.gif b/testimg/screen3.gif deleted file mode 100644 index 77f808ed..00000000 Binary files a/testimg/screen3.gif and /dev/null differ diff --git a/testimg/trimgdesc.gif b/testimg/trimgdesc.gif deleted file mode 100644 index f352b0ed..00000000 Binary files a/testimg/trimgdesc.gif and /dev/null differ diff --git a/testimg/trmiddesc.gif b/testimg/trmiddesc.gif deleted file mode 100644 index 386e3c12..00000000 Binary files a/testimg/trmiddesc.gif and /dev/null differ diff --git a/testimg/zerocomm.gif b/testimg/zerocomm.gif deleted file mode 100644 index ede9f6af..00000000 Binary files a/testimg/zerocomm.gif and /dev/null differ