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
--- /dev/null
+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<Imager::Files>.
+
+=head1 AUTHOR
+
+Tony Cook <tony@imager.perl.org>
+
+=head1 SEE ALSO
+
+Imager, Imager::Files.
+
+=cut
--- /dev/null
+#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<colours; q++) {
+ for(w=0; w<3; w++)
+ temp[w]=sv_2mortal(newSViv(colour_table[q*3 + w]));
+ av_store(ct, q, (SV*)newRV_noinc((SV*)av_make(3, temp)));
+ }
+ myfree(colour_table);
+
+ EXTEND(SP,2);
+ r = sv_newmortal();
+ sv_setref_pv(r, "Imager::ImgRaw", (void*)rimg);
+ PUSHs(r);
+ PUSHs(newRV_noinc((SV*)ct));
+ }
+
+Imager::ImgRaw
+i_readgif_single_wiol(ig, page=0)
+ Imager::IO ig
+ int page
+
+void
+i_readgif_multi_wiol(ig)
+ Imager::IO ig
+ PREINIT:
+ i_img **imgs;
+ int count;
+ int i;
+ PPCODE:
+ imgs = i_readgif_multi_wiol(ig, &count);
+ if (imgs) {
+ EXTEND(SP, count);
+ for (i = 0; i < count; ++i) {
+ SV *sv = sv_newmortal();
+ sv_setref_pv(sv, "Imager::ImgRaw", (void *)imgs[i]);
+ PUSHs(sv);
+ }
+ myfree(imgs);
+ }
+
+
+BOOT:
+ PERL_INITIALIZE_IMAGER_CALLBACKS;
+ PERL_INITIALIZE_IMAGER_PERL_CALLBACKS;
--- /dev/null
+#!perl -w
+use strict;
+use ExtUtils::MakeMaker qw(WriteMakefile WriteEmptyMakefile);
+use Getopt::Long;
+use Config;
+
+my $verbose = $ENV{IM_VERBOSE};
+my @libpaths;
+my @incpaths;
+
+GetOptions("incpath=s", \@incpaths,
+ "libpath=s" => \@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 <tony@imager.perl.org>';
+ $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
+}
--- /dev/null
+#include "imgif.h"
+#include <gif_lib.h>
+#ifdef _MSC_VER
+#include <io.h>
+#else
+#include <unistd.h>
+#endif
+#include <errno.h>
+#include <string.h>
+
+/*
+=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; q<colourmapsize; q++) {
+ mapentry = &colourmap->Colors[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<gif_set_version> for an explanation.
+
+=head1 AUTHOR
+
+Arnar M. Hrafnkelsson, addi@umich.edu
+
+=head1 SEE ALSO
+
+perl(1), Imager(3)
+
+=cut
+
+*/
--- /dev/null
+#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
--- /dev/null
+#!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, "<testimg/scalei.gif") || die "Cannot open testimg/scalei.gif";
+ binmode FH;
+ my $io = Imager::io_new_fd(fileno(FH));
+ my ($imgi) = Imager::File::GIF::i_readgif_wiol($io);
+ ok($imgi, "read interlaced") or die "Cannot read testimg/scalei.gif";
+ close FH;
+
+ open FH, "<testimg/scale.gif" or die "Cannot open testimg/scale.gif";
+ binmode FH;
+ $io = Imager::io_new_fd(fileno(FH));
+ my ($imgni) = Imager::File::GIF::i_readgif_wiol($io);
+ ok($imgni, "read normal") or die "Cannot read testimg/scale.gif";
+ close FH;
+
+ 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, "<testout/t105i.ppm" or die "Cannot open testout/t105i.ppm";
+ my $datai = do { local $/; <FH> };
+ close FH;
+
+ open FH, "<testout/t105ni.ppm" or die "Cannot open testout/t105ni.ppm";
+ my $datani = do { local $/; <FH> };
+ close FH;
+ is($datai, $datani, "images match");
+}
+
+{
+ # reading with a callback
+ # various sizes to make sure the buffering works
+ # requested size
+ open FH, "<testimg/scale.gif" or die "Cannot open testimg/scale.gif";
+ binmode FH;
+ my $io = Imager::io_new_cb
+ (
+ undef,
+ sub { my $tmp; read(FH, $tmp, $_[0]) and $tmp },
+ undef,
+ undef
+ );
+ # no callback version in giflib3, so don't overwrite a good image
+ my $img2 = Imager::File::GIF::i_readgif_wiol($io);
+ close FH;
+ ok($img, "reading with a callback");
+
+ ok(test_readgif_cb(1), "read callback 1 char buffer");
+ ok(test_readgif_cb(512), "read callback 512 char buffer");
+ ok(test_readgif_cb(1024), "read callback 1024 char buffer");
+}
+# new writegif_gen
+# test webmap, custom errdiff map
+# (looks fairly awful)
+{
+ 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 <sigh>
+#open FH, "<testout/t105_trans.gif" or die $!;
+#binmode FH; # habit, I suppose
+#if (i_writegif_gen(fileno(FH), {}, $timg)) {
+# # this is meant to _fail_
+# print "not ok 18 # writing to read-only should fail";
+#}
+#else {
+# print "ok 18 # ",Imager::_error_as_msg(),"\n";
+#}
+#close FH;
+
+{
+ # try to read a file of the wrong format - the script will do
+ open FH, "<t/t10gif.t"
+ or die "Cannot open this script!: $!";
+ binmode FH;
+ my $io = Imager::io_new_fd(fileno(FH));
+ ok(!Imager::File::GIF::i_readgif_wiol($io),
+ "read test script as gif should fail ". Imager::_error_as_msg());
+ close FH;
+}
+
+{
+ # try to save no images :)
+ 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, "<testimg/scale.gif" or die "Cannot open testimg/scale.gif";
+ binmode FH;
+ my $io = Imager::io_new_cb
+ (
+ undef,
+ sub { my $tmp; read(FH, $tmp, $size) and $tmp },
+ undef,
+ undef
+ );
+ my $img = Imager::File::GIF::i_readgif_wiol($io);
+ close FH;
+ return $img;
+}
+
+# tests for reading bad gif files
+sub read_failure {
+ my ($filename) = @_;
+
+ open FH, "< $filename"
+ or die "Cannot open $filename: $!";
+ binmode FH;
+ my $io = Imager::io_new_fd(fileno(FH));
+ my ($result, $map) = Imager::File::GIF::i_readgif_wiol($io);
+ ok(!$result, "attempt to read invalid image $filename ".Imager::_error_as_msg());
+ close FH;
+}
+
+sub _clear_tags {
+ my (@imgs) = @_;
+
+ for my $img (@imgs) {
+ $img->deltag(code=>0);
+ }
+}
+
+sub _add_tags {
+ my ($img, %tags) = @_;
+
+ for my $key (keys %tags) {
+ Imager::i_tags_add($img, $key, 0, $tags{$key}, 0);
+ }
+}
+
+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;
+ }
+}
--- /dev/null
+#!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 $/; <FH>; };
+ 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];
+}
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
($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)
#include "dynaload.h"
#include "regmach.h"
#include "imextdef.h"
+#include "imextpltypes.h"
#if i_int_hlines_testing()
#include "imageri.h"
};
/* 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;
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;
#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
#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<fixedlen;i++) {
- sv1=(*(av_fetch(av,i,0)));
- if (sv_derived_from(sv1, "Imager::Color")) {
- Itmp = SvIV((SV*)SvRV(sv1));
- tmp = INT2PTR(i_color*, Itmp);
- } else croak("Imager: one of the elements of array ref is not of Imager::Color type\n");
- fixed[i]=*tmp;
- }
- RETVAL=i_writegif(im,fd,colors,pixdev,fixedlen,fixed);
- myfree(fixed);
- ST(0) = sv_newmortal();
- if (RETVAL == 0) ST(0)=&PL_sv_undef;
- else sv_setiv(ST(0), (IV)RETVAL);
-
-
-
-
-undef_int
-i_writegifmc(im,fd,colors)
- Imager::ImgRaw im
- int fd
- int colors
-
-
-undef_int
-i_writegif_gen(fd, ...)
- int fd
- PROTOTYPE: $$@
- PREINIT:
- i_quantize quant;
- i_img **imgs = NULL;
- int img_count;
- int i;
- HV *hv;
- CODE:
- if (items < 3)
- croak("Usage: i_writegif_gen(fd,hashref, images...)");
- if (!SvROK(ST(1)) || ! SvTYPE(SvRV(ST(1))))
- croak("i_writegif_gen: Second argument must be a hash ref");
- hv = (HV *)SvRV(ST(1));
- memset(&quant, 0, sizeof(quant));
- quant.mc_size = 256;
- quant.transp = tr_threshold;
- quant.tr_threshold = 127;
- handle_quant_opts(aTHX_ &quant, hv);
- img_count = items - 2;
- RETVAL = 1;
- if (img_count < 1) {
- RETVAL = 0;
- i_clear_error();
- i_push_error(0, "You need to specify images to save");
- }
- 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 {
- i_clear_error();
- i_push_error(0, "Only images can be saved");
- RETVAL = 0;
- break;
- }
- }
- if (RETVAL) {
- RETVAL = i_writegif_gen(&quant, fd, imgs, img_count);
- }
- myfree(imgs);
- if (RETVAL) {
- 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);
- cleanup_quant_opts(&quant);
-
-
-undef_int
-i_writegif_callback(cb, maxbuffer,...)
- int maxbuffer;
- PREINIT:
- i_quantize quant;
- i_img **imgs = NULL;
- int img_count;
- int i;
- HV *hv;
- i_writer_data wd;
- CODE:
- if (items < 4)
- croak("Usage: i_writegif_callback(\\&callback,maxbuffer,hashref, images...)");
- if (!SvROK(ST(2)) || ! SvTYPE(SvRV(ST(2))))
- croak("i_writegif_callback: Second argument must be a hash ref");
- hv = (HV *)SvRV(ST(2));
- memset(&quant, 0, sizeof(quant));
- quant.mc_size = 256;
- quant.transp = tr_threshold;
- quant.tr_threshold = 127;
- handle_quant_opts(aTHX_ &quant, hv);
- img_count = items - 3;
- 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(3+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) {
- wd.sv = ST(0);
- RETVAL = i_writegif_callback(&quant, write_callback, (char *)&wd, maxbuffer, imgs, img_count);
- }
- myfree(imgs);
- if (RETVAL) {
- 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);
- cleanup_quant_opts(&quant);
-
-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.mc_size = 256;
- quant.transp = tr_threshold;
- quant.tr_threshold = 127;
- 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) {
- 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);
- cleanup_quant_opts(&quant);
-
-void
-i_readgif(fd)
- int fd
- 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(fd,&colour_table,&colours);
- } else {
- /* don't waste time with colours if they aren't wanted */
- rimg = i_readgif(fd,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<colours; q++) {
- for(w=0; w<3; w++)
- temp[w]=sv_2mortal(newSViv(colour_table[q*3 + w]));
- av_store(ct, q, (SV*)newRV_noinc((SV*)av_make(3, temp)));
- }
- myfree(colour_table);
-
- EXTEND(SP,2);
- r = sv_newmortal();
- sv_setref_pv(r, "Imager::ImgRaw", (void*)rimg);
- PUSHs(r);
- PUSHs(newRV_noinc((SV*)ct));
- }
-
-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<colours; q++) {
- for(w=0; w<3; w++)
- temp[w]=sv_2mortal(newSViv(colour_table[q*3 + w]));
- av_store(ct, q, (SV*)newRV_noinc((SV*)av_make(3, temp)));
- }
- myfree(colour_table);
-
- EXTEND(SP,2);
- r = sv_newmortal();
- sv_setref_pv(r, "Imager::ImgRaw", (void*)rimg);
- PUSHs(r);
- PUSHs(newRV_noinc((SV*)ct));
- }
-
-Imager::ImgRaw
-i_readgif_single_wiol(ig, page=0)
- Imager::IO ig
- int page
-
-void
-i_readgif_scalar(...)
- PROTOTYPE: $
- PREINIT:
- char* data;
- STRLEN length;
- int* colour_table;
- int colours, q, w;
- i_img* rimg;
- SV* temp[3];
- AV* ct;
- SV* r;
- PPCODE:
- data = (char *)SvPV(ST(0), length);
- colour_table=NULL;
- colours=0;
-
- if(GIMME_V == G_ARRAY) {
- rimg=i_readgif_scalar(data,length,&colour_table,&colours);
- } else {
- /* don't waste time with colours if they aren't wanted */
- rimg=i_readgif_scalar(data,length,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 :-) */
- ct=newAV();
- av_extend(ct, colours);
- for(q=0; q<colours; q++) {
- for(w=0; w<3; w++)
- temp[w]=sv_2mortal(newSViv(colour_table[q*3 + w]));
- av_store(ct, q, (SV*)newRV_noinc((SV*)av_make(3, temp)));
- }
- myfree(colour_table);
-
- EXTEND(SP,2);
- r=sv_newmortal();
- sv_setref_pv(r, "Imager::ImgRaw", (void*)rimg);
- PUSHs(r);
- PUSHs(newRV_noinc((SV*)ct));
- }
-
-void
-i_readgif_callback(...)
- PROTOTYPE: &
- PREINIT:
- int* colour_table;
- int colours, q, w;
- i_img* rimg;
- SV* temp[3];
- AV* ct;
- SV* r;
- i_reader_data rd;
- PPCODE:
- rd.sv = ST(0);
- colour_table=NULL;
- colours=0;
-
- if(GIMME_V == G_ARRAY) {
- rimg=i_readgif_callback(read_callback, (char *)&rd,&colour_table,&colours);
- } else {
- /* don't waste time with colours if they aren't wanted */
- rimg=i_readgif_callback(read_callback, (char *)&rd,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 :-) */
- /* Neither do I - maybe I'll move this somewhere */
- ct=newAV();
- av_extend(ct, colours);
- for(q=0; q<colours; q++) {
- for(w=0; w<3; w++)
- temp[w]=sv_2mortal(newSViv(colour_table[q*3 + w]));
- av_store(ct, q, (SV*)newRV_noinc((SV*)av_make(3, temp)));
- }
- myfree(colour_table);
-
- EXTEND(SP,2);
- r=sv_newmortal();
- sv_setref_pv(r, "Imager::ImgRaw", (void*)rimg);
- PUSHs(r);
- PUSHs(newRV_noinc((SV*)ct));
- }
-
-void
-i_readgif_multi(fd)
- int fd
- PREINIT:
- i_img **imgs;
- int count;
- int i;
- PPCODE:
- imgs = i_readgif_multi(fd, &count);
- if (imgs) {
- EXTEND(SP, count);
- for (i = 0; i < count; ++i) {
- SV *sv = sv_newmortal();
- sv_setref_pv(sv, "Imager::ImgRaw", (void *)imgs[i]);
- PUSHs(sv);
- }
- myfree(imgs);
- }
-
-void
-i_readgif_multi_scalar(data)
- PREINIT:
- i_img **imgs;
- int count;
- char *data;
- STRLEN length;
- int i;
- PPCODE:
- data = (char *)SvPV(ST(0), length);
- imgs = i_readgif_multi_scalar(data, length, &count);
- if (imgs) {
- EXTEND(SP, count);
- for (i = 0; i < count; ++i) {
- SV *sv = sv_newmortal();
- sv_setref_pv(sv, "Imager::ImgRaw", (void *)imgs[i]);
- PUSHs(sv);
- }
- myfree(imgs);
- }
-
-void
-i_readgif_multi_callback(cb)
- PREINIT:
- i_reader_data rd;
- i_img **imgs;
- int count;
- int i;
- PPCODE:
- rd.sv = ST(0);
- imgs = i_readgif_multi_callback(read_callback, (char *)&rd, &count);
- if (imgs) {
- EXTEND(SP, count);
- for (i = 0; i < count; ++i) {
- SV *sv = sv_newmortal();
- sv_setref_pv(sv, "Imager::ImgRaw", (void *)imgs[i]);
- PUSHs(sv);
- }
- myfree(imgs);
- }
-
-void
-i_readgif_multi_wiol(ig)
- Imager::IO ig
- PREINIT:
- i_img **imgs;
- int count;
- int i;
- PPCODE:
- imgs = i_readgif_multi_wiol(ig, &count);
- if (imgs) {
- EXTEND(SP, count);
- for (i = 0; i < count; ++i) {
- SV *sv = sv_newmortal();
- sv_setref_pv(sv, "Imager::ImgRaw", (void *)imgs[i]);
- PUSHs(sv);
- }
- myfree(imgs);
- }
-
-
-#endif
-
Imager::ImgRaw
croak("i_img_to_pal: second argument must be a hash ref");
hv = (HV *)SvRV(ST(1));
memset(&quant, 0, sizeof(quant));
+ quant.version = 1;
quant.mc_size = 256;
- handle_quant_opts(aTHX_ &quant, hv);
+ ip_handle_quant_opts(aTHX_ &quant, hv);
RETVAL = i_img_to_pal(src, &quant);
if (RETVAL) {
- copy_colors_back(aTHX_ hv, &quant);
+ ip_copy_colors_back(aTHX_ hv, &quant);
}
- cleanup_quant_opts(&quant);
+ ip_cleanup_quant_opts(aTHX_ &quant);
OUTPUT:
RETVAL
BOOT:
PERL_SET_GLOBAL_CALLBACKS;
+ PERL_PL_SET_GLOBAL_CALLBACKS;
\ No newline at end of file
Flines/Flines.xs
Flines/Makefile.PL
Flines/t/t00flines.t
+GIF/GIF.pm
+GIF/GIF.xs
+GIF/Makefile.PL
+GIF/imgif.c
+GIF/imgif.h
+GIF/testimg/badindex.gif GIF with a bad color index
+GIF/testimg/bandw.gif
+GIF/testimg/expected.gif
+GIF/testimg/loccmap.gif
+GIF/testimg/nocmap.gif
+GIF/testimg/scale.gif
+GIF/testimg/scalei.gif
+GIF/testimg/screen2.gif
+GIF/testimg/screen3.gif
+GIF/testimg/trimgdesc.gif
+GIF/testimg/trmiddesc.gif
+GIF/testimg/zerocomm.gif Image with a zero-length comment extension
ICO/ICO.pm Windows Icon file support
ICO/ICO.xs
ICO/Makefile.PL
fontfiles/dodge.ttf
freetyp2.c Implements freetype2 font support
gaussian.im
-gif.c
hlines.c Manage sets of horizontal line segments
image.c
imager.h
imext.c Defines the function table
imext.h Included by external modules for API access
imextdef.h
+imextpl.h Included by external modules for Perl API access
+imextpltypes.h Define Perl API function table type
imexttypes.h Define API function table type
imexif.c Experimental JPEG EXIF decoding
imexif.h
testimg/badbits.bmp bad bits value in header
testimg/badcomp1.bmp bad compression for 1-bit/pixel
testimg/badcomp4.bmp bad compression for 4-bit/pixel
-testimg/badindex.gif GIF with a bad color index
testimg/badplanes.bmp bad planes value in header
testimg/badused1.bmp 1-bit/pixel, out of range colors used value
testimg/badused4a.bmp 4-bit/pixel, badly out of range used value (SEGV test)
testimg/bad_asc.pbm ASCII PBM with invalid image data
testimg/bad_asc.pgm ASCII PGM with invalid image data
testimg/bad_asc.ppm ASCII PPM with invalid image data
-testimg/bandw.gif
testimg/base.jpg Base JPEG test image
testimg/comp4.bmp Compressed 4-bit/pixel BMP
testimg/comp4.tif 4-bit/pixel paletted TIFF
testimg/comp8.bmp Compressed 8-bit/pixel BMP
testimg/comp8.tif 8-bit/pixel paletted TIFF
testimg/exiftest.jpg Test image for EXIF parsing
-testimg/expected.gif
testimg/gimpgrad A GIMP gradient file
testimg/gralpha.tif Grey alpha test image
testimg/grey16.tif 16-bit/sample greyscale TIFF
testimg/imager.pbm Test bi-level
testimg/imager.tif Test bi-level
testimg/junk.ppm
-testimg/loccmap.gif
testimg/longid.tga Test TGA with a long id string
testimg/maxval.ppm For ppm file maxval handling
testimg/maxval_0.ppm
testimg/maxval_asc.ppm
testimg/multiple.ppm Test multiple PPM reading
testimg/newgimpgrad.ggr Test GIMP Gradient file (newer type)
-testimg/nocmap.gif
testimg/penguin-base.ppm
testimg/pengtile.tif Tiled tiff image, same as penguin-base.ppm
testimg/pgm.pgm Simple pgm for testing the right sample is in the right place
testimg/rgb16.tif 16-bit/sample RGB image - strips
testimg/rgb16t.tif 16-bit/sample RGB image - tiled
testimg/rgbatsep.tif Tiled/separated for testing RGBA codepath
-testimg/scale.gif
testimg/scale.ppm
-testimg/scalei.gif
testimg/scmyk.tif Simple CMYK TIFF image
testimg/scmyk.jpg Simple CMYK JPEG image
testimg/scmyka.tif CMYK with one alpha channel
testimg/scmyka16.tif CMYK with one alpha channel (16-bit)
testimg/scmykaa.tif CMYK with 2 alpha channels
-testimg/screen2.gif
-testimg/screen3.gif
testimg/short1.bmp 1-bit/pixel, data missing from EOF
testimg/short24.bmp 24-bit/pixel, data missing from EOF
testimg/short4.bmp truncated 4bit/pixel uncompressed BMP
testimg/test.tga Standard test image as TGA
testimg/test_gimp_pal A simple GIMP palette file
testimg/tiffwarn.tif Generates a warning while being read
-testimg/trimgdesc.gif
-testimg/trmiddesc.gif
testimg/winrgb2.bmp 1-bit bmp base
testimg/winrgb24.bmp 24-bit bmp base
testimg/winrgb24off.bmp 24-bit bmp with image data offset from header
testimg/winrgb8.bmp 8-bit bmp base
testimg/winrgb8off.bmp 8-bit bmp with image data offset from header
testimg/zerotype.jpg Image with a zero type entry in the EXIF data
-testimg/zerocomm.gif Image with a zero-length comment extension
tga.c Reading and writing Targa files
tiff.c
trans2.c
my @incpaths; # places to look for headers
my @libpaths; # places to look for libraries
my $noexif; # non-zero to disable EXIF parsing of JPEGs
-my $no_gif_set_version; # disable calling EGifSetGifVersion
my $coverage; # build for coverage testing
my $assert; # build with assertions
GetOptions("help" => \$help,
"verbose|v" => \$VERBOSE,
"nolog" => \$NOLOG,
"noexif" => \$noexif,
- "nogifsetversion" => \$no_gif_set_version,
'coverage' => \$coverage,
"assert|a" => \$assert);
automatic();
}
-# Make sure there isn't a clash between the gif libraries.
-gifcheck();
-
my $lib_cflags = '';
my $lib_lflags = '';
my $F_LIBS = '';
directory =>
[
"PNG",
+ "GIF",
],
},
resources =>
}
-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 (<H>) {
- 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 (<H>) {
+# 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 {
# 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',
$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)
+++ /dev/null
-#include "imageri.h"
-#include <gif_lib.h>
-#ifdef _MSC_VER
-#include <io.h>
-#else
-#include <unistd.h>
-#endif
-#include <errno.h>
-/* 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; q<colourmapsize; q++) {
- mapentry = &colourmap->Colors[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<img> to the file handle I<fd>. The resulting GIF will use a
-maximum of 1<<I<max_colours> colours, with the first I<fixedlen>
-colours taken from I<fixed>.
-
-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<<max_colors;
- quant.mc_count = fixedlen;
- memcpy(colors, fixed, fixedlen * sizeof(i_color));
- quant.translate = pt_perturb;
- quant.perturb = pixdev;
- return i_writegif_gen(&quant, fd, &im, 1);
-}
-
-/*
-=item i_writegifmc(i_img *im, int fd, int max_colors)
-
-Write I<img> to the file handle I<fd>. The resulting GIF will use a
-maximum of 1<<I<max_colours> 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<image.h/i_quantize> and
-L<image.h/i_gif_opts>.
-
-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<gif_set_version> for an explanation.
-
-=head1 AUTHOR
-
-Arnar M. Hrafnkelsson, addi@umich.edu
-
-=head1 SEE ALSO
-
-perl(1), Imager(3)
-
-=cut
-
-*/
/* 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 */
/* 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 {
--- /dev/null
+#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
--- /dev/null
+#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
+++ /dev/null
-#!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, "<testimg/scalei.gif") || die "Cannot open testimg/scalei.gif";
-binmode FH;
-my ($imgi) = i_readgif(fileno(FH));
-ok($imgi, "read interlaced") or die "Cannot read testimg/scalei.gif";
-close FH;
-open FH, "<testimg/scale.gif" or die "Cannot open testimg/scale.gif";
-binmode FH;
-my ($imgni) = i_readgif(fileno(FH));
-ok($imgni, "read normal") or die "Cannot read testimg/scale.gif";
-close FH;
-
-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, "<testout/t105i.ppm" or die "Cannot open testout/t105i.ppm";
-my $datai = do { local $/; <FH> };
-close FH;
-
-open FH, "<testout/t105ni.ppm" or die "Cannot open testout/t105ni.ppm";
-my $datani = do { local $/; <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, "<testimg/scale.gif" or die "Cannot open testimg/scale.gif";
- binmode FH;
- # no callback version in giflib3, so don't overwrite a good image
- my $img2 = i_readgif_callback(sub { my $tmp; read(FH, $tmp, $_[0]) and $tmp });
- close FH;
- ok($img, "reading with a callback");
-
- ok(test_readgif_cb(1), "read callback 1 char buffer");
- ok(test_readgif_cb(512), "read callback 512 char buffer");
- ok(test_readgif_cb(1024), "read callback 1024 char buffer");
-}
-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 <<EOS;
-This file is created by t105gif.t when test 14 fails.
-
-This failure usually indicates you\'re using the original versions
-of giflib 4.1.0 - 4.1.3, which have a few bugs that Imager tickles.
-
-You can apply the patch from:
-
-http://www.develop-help.com/imager/giflib.patch
-
-or you can just install Imager as is, if you only need to write GIFs to
-files or file descriptors (such as sockets).
-
-One hunk of this patch is rejected (correctly) with giflib 4.1.3,
-since one bug that the patch fixes is fixed in 4.1.3.
-
-If you don't feel comfortable with that apply the patch file that
-belongs to the following patch entry on sourceforge:
-
-https://sourceforge.net/tracker/index.php?func=detail&aid=981255&group_id=102202&atid=631306
-
-In previous versions of Imager only this test was careful about catching
-the error, we now skip any tests that crashed or failed when the buggy
-giflib was present.
-EOS
- }
-}
-@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;
-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 <sigh>
-#open FH, "<testout/t105_trans.gif" or die $!;
-#binmode FH; # habit, I suppose
-#if (i_writegif_gen(fileno(FH), {}, $timg)) {
-# # this is meant to _fail_
-# print "not ok 18 # writing to read-only should fail";
-#}
-#else {
-# print "ok 18 # ",Imager::_error_as_msg(),"\n";
-#}
-#close FH;
-
-# try to read a file of the wrong format - the script will do
-open FH, "<t/t105gif.t"
- or die "Cannot open this script!: $!";
-binmode FH;
-ok(!i_readgif(fileno(FH)),
- "read test script as gif should fail ". Imager::_error_as_msg());
-close FH;
-
-# try to save no images :)
-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, "<testimg/scale.gif" or die "Cannot open testimg/scale.gif";
- binmode FH;
- my $img = i_readgif_callback(sub { my $tmp; read(FH, $tmp, $size) and $tmp });
- close FH;
- return $img;
-}
-
-# tests for reading bad gif files
-sub read_failure {
- my ($filename) = @_;
-
- open FH, "< $filename"
- or die "Cannot open $filename: $!";
- binmode FH;
- my ($result, $map) = i_readgif(fileno(FH));
- ok(!$result, "attempt to read invalid image $filename ".Imager::_error_as_msg());
- close FH;
-}
-
-sub _clear_tags {
- my (@imgs) = @_;
-
- for my $img (@imgs) {
- $img->deltag(code=>0);
- }
-}
-
-sub _add_tags {
- my ($img, %tags) = @_;
-
- for my $key (keys %tags) {
- Imager::i_tags_add($img, $key, 0, $tags{$key}, 0);
- }
-}
-
-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;
- }
-}
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;
{ 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" }, );
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 $/; <DATA> };
- 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 $/; <DATA> };
+ 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) {
"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 $/; <DATA> };
- 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 $/; <DATA> };
+ 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);
}
}
+++ /dev/null
-#!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 $/; <FH>; };
- 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];
-}