Permalink
Fetching contributors…
Cannot retrieve contributors at this time
1985 lines (1659 sloc) 50.9 KB
#include "imgif.h"
#include <gif_lib.h>
#ifdef _MSC_VER
#include <io.h>
#else
#include <unistd.h>
#endif
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
/*
=head1 NAME
imgif.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
*/
#ifdef GIFLIB_MAJOR
#define IMGIFLIB_API_VERSION (GIFLIB_MAJOR * 100 + GIFLIB_MINOR)
#else
/* only matters for pre-5.0 which we either reject, or which contains
no significant API changes */
#define IMGIFLIB_API_VERSION 0
#endif
#if IMGIFLIB_API_VERSION >= 500
#define POST_SET_VERSION
#define myDGifOpen(userPtr, readFunc, Error) DGifOpen((userPtr), (readFunc), (Error))
#define myEGifOpen(userPtr, readFunc, Error) EGifOpen((userPtr), (readFunc), (Error))
#define myGifError(gif) ((gif)->Error)
#define MakeMapObject GifMakeMapObject
#define FreeMapObject GifFreeMapObject
#define gif_mutex_lock(mutex)
#define gif_mutex_unlock(mutex)
#else
#define PRE_SET_VERSION
static GifFileType *
myDGifOpen(void *userPtr, InputFunc readFunc, int *error) {
GifFileType *result = DGifOpen(userPtr, readFunc);
if (!result)
*error = GifLastError();
return result;
}
static GifFileType *
myEGifOpen(void *userPtr, OutputFunc outputFunc, int *error) {
GifFileType *result = EGifOpen(userPtr, outputFunc);
if (!result)
*error = GifLastError();
return result;
}
#define myGifError(gif) GifLastError()
#define gif_mutex_lock(mutex) i_mutex_lock(mutex)
#define gif_mutex_unlock(mutex) i_mutex_unlock(mutex)
#endif
#if IMGIFLIB_API_VERSION >= 501
#define myDGifCloseFile(gif, perror) (DGifCloseFile((gif), (perror)))
#define myEGifCloseFile(gif, perror) (EGifCloseFile((gif), (perror)))
#else
static int
myDGifCloseFile(GifFileType *GifFile, int *ErrorCode) {
int result = DGifCloseFile(GifFile);
if (result == GIF_ERROR) {
if (ErrorCode)
*ErrorCode = myGifError(GifFile);
free(GifFile->Private);
free(GifFile);
}
return result;
}
static int
myEGifCloseFile(GifFileType *GifFile, int *ErrorCode) {
int result = EGifCloseFile(GifFile);
if (result == GIF_ERROR) {
if (ErrorCode)
*ErrorCode = myGifError(GifFile);
free(GifFile->Private);
free(GifFile);
}
return result;
}
#endif
static char const *gif_error_msg(int code);
static void gif_push_error(int code);
/* Make some variables global, so we could access them faster: */
static const int
InterlacedOffset[] = { 0, 4, 2, 1 }, /* The way Interlaced image should. */
InterlacedJumps[] = { 8, 8, 4, 2 }; /* be read - offsets and jumps... */
#if IMGIFLIB_API_VERSION < 500
static i_mutex_t mutex;
#endif
void
i_init_gif(void) {
#if IMGIFLIB_API_VERSION < 500
mutex = i_mutex_new();
#endif
}
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;
}
}
#ifdef GIF_LIB_VERSION
static const
char gif_version_str[] = GIF_LIB_VERSION;
double
i_giflib_version(void) {
const char *p = gif_version_str;
while (*p && (*p < '0' || *p > '9'))
++p;
if (!*p)
return 0;
return strtod(p, NULL);
}
#else
double
i_giflib_version(void) {
return GIFLIB_MAJOR + GIFLIB_MINOR * 0.1;
}
#endif
/*
=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;
ColorMapObject *ColorMap;
GifRecordType RecordType;
GifByteType *Extension;
GifRowType GifRow;
GifColorType *ColorMapEntry;
i_color col;
int error;
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;
ColorMap = (GifFile->Image.ColorMap ? GifFile->Image.ColorMap : GifFile->SColorMap);
if (ColorMap) {
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;
}
(void)myDGifCloseFile(GifFile, NULL);
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;
}
(void)myDGifCloseFile(GifFile, NULL);
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(myGifError(GifFile));
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);
(void)myDGifCloseFile(GifFile, NULL);
return NULL;
}
switch (RecordType) {
case IMAGE_DESC_RECORD_TYPE:
if (DGifGetImageDesc(GifFile) == GIF_ERROR) {
gif_push_error(myGifError(GifFile));
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);
(void)myDGifCloseFile(GifFile, NULL);
return NULL;
}
if (( ColorMap = (GifFile->Image.ColorMap ? GifFile->Image.ColorMap : GifFile->SColorMap) )) {
mm_log((1, "Adding local colormap\n"));
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);
(void)myDGifCloseFile(GifFile, NULL);
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);
(void)myDGifCloseFile(GifFile, NULL);
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(myGifError(GifFile));
i_push_error(0, "Reading GIF line");
if (colour_table && *colour_table) {
myfree(*colour_table);
*colour_table = NULL;
}
myfree(GifRow);
i_img_destroy(im);
(void)myDGifCloseFile(GifFile, NULL);
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(myGifError(GifFile));
i_push_error(0, "Reading GIF line");
if (colour_table && *colour_table) {
myfree(*colour_table);
*colour_table = NULL;
}
myfree(GifRow);
i_img_destroy(im);
(void)myDGifCloseFile(GifFile, NULL);
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(myGifError(GifFile));
i_push_error(0, "Reading extension record");
if (colour_table && *colour_table) {
myfree(*colour_table);
*colour_table = NULL;
}
myfree(GifRow);
i_img_destroy(im);
(void)myDGifCloseFile(GifFile, NULL);
return NULL;
}
while (Extension != NULL) {
if (DGifGetExtensionNext(GifFile, &Extension) == GIF_ERROR) {
gif_push_error(myGifError(GifFile));
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);
(void)myDGifCloseFile(GifFile, NULL);
return NULL;
}
}
break;
case TERMINATE_RECORD_TYPE:
break;
default: /* Should be traps by DGifGetRecordType. */
break;
}
} while (RecordType != TERMINATE_RECORD_TYPE);
myfree(GifRow);
if (myDGifCloseFile(GifFile, &error) == GIF_ERROR) {
gif_push_error(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, 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 */
int error;
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));
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(myGifError(GifFile));
i_push_error(0, "Unable to get record type");
free_images(results, *count);
(void)myDGifCloseFile(GifFile, NULL);
myfree(GifRow);
if (comment)
myfree(comment);
return NULL;
}
switch (RecordType) {
case IMAGE_DESC_RECORD_TYPE:
if (DGifGetImageDesc(GifFile) == GIF_ERROR) {
gif_push_error(myGifError(GifFile));
i_push_error(0, "Unable to get image descriptor");
free_images(results, *count);
(void)myDGifCloseFile(GifFile, NULL);
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);
(void)myDGifCloseFile(GifFile, NULL);
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"));
(void)myDGifCloseFile(GifFile, NULL);
myfree(GifRow);
if (comment)
myfree(comment);
return NULL;
}
img = i_img_pal_new(Width, Height, channels, 256);
if (!img) {
free_images(results, *count);
(void)myDGifCloseFile(GifFile, NULL);
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);
(void)myDGifCloseFile(GifFile, NULL);
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(myGifError(GifFile));
i_push_error(0, "Reading GIF line");
free_images(results, *count);
(void)myDGifCloseFile(GifFile, NULL);
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(myGifError(GifFile));
i_push_error(0, "Reading GIF line");
free_images(results, *count);
(void)myDGifCloseFile(GifFile, NULL);
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);
(void)myDGifCloseFile(GifFile, NULL);
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(myGifError(GifFile));
i_push_error(0, "Reading GIF line");
free_images(results, *count);
myfree(GifRow);
(void)myDGifCloseFile(GifFile, NULL);
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(myGifError(GifFile));
i_push_error(0, "Reading extension record");
free_images(results, *count);
myfree(GifRow);
(void)myDGifCloseFile(GifFile, NULL);
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(myGifError(GifFile));
i_push_error(0, "reading loop extension");
free_images(results, *count);
myfree(GifRow);
(void)myDGifCloseFile(GifFile, NULL);
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(myGifError(GifFile));
i_push_error(0, "reading next block of extension");
free_images(results, *count);
myfree(GifRow);
(void)myDGifCloseFile(GifFile, NULL);
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 (myDGifCloseFile(GifFile, &error) == GIF_ERROR) {
gif_push_error(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;
int gif_error;
i_img **result;
gif_mutex_lock(mutex);
i_clear_error();
if ((GifFile = myDGifOpen((void *)ig, io_glue_read_cb, &gif_error )) == NULL) {
gif_push_error(gif_error);
i_push_error(0, "Cannot create giflib callback object");
mm_log((1,"i_readgif_multi_wiol: Unable to open callback datasource.\n"));
gif_mutex_unlock(mutex);
return NULL;
}
result = i_readgif_multi_low(GifFile, count, -1);
gif_mutex_unlock(mutex);
return result;
}
static int
io_glue_read_cb(GifFileType *gft, GifByteType *buf, int length) {
io_glue *ig = (io_glue *)gft->UserData;
return i_io_read(ig, buf, length);
}
i_img *
i_readgif_wiol(io_glue *ig, int **color_table, int *colors) {
GifFileType *GifFile;
int gif_error;
i_img *result;
gif_mutex_lock(mutex);
i_clear_error();
if ((GifFile = myDGifOpen((void *)ig, io_glue_read_cb, &gif_error )) == NULL) {
gif_push_error(gif_error);
i_push_error(0, "Cannot create giflib callback object");
mm_log((1,"i_readgif_wiol: Unable to open callback datasource.\n"));
gif_mutex_unlock(mutex);
return NULL;
}
result = i_readgif_low(GifFile, color_table, colors);
gif_mutex_unlock(mutex);
return result;
}
/*
=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) {
GifFileType *GifFile;
int gif_error;
i_img *result;
i_clear_error();
if (page < 0) {
i_push_error(0, "page must be non-negative");
return NULL;
}
gif_mutex_lock(mutex);
if ((GifFile = myDGifOpen((void *)ig, io_glue_read_cb, &gif_error )) == NULL) {
gif_push_error(gif_error);
i_push_error(0, "Cannot create giflib callback object");
mm_log((1,"i_readgif_wiol: Unable to open callback datasource.\n"));
gif_mutex_unlock(mutex);
return NULL;
}
result = i_readgif_single_low(GifFile, page);
gif_mutex_unlock(mutex);
return result;
}
/*
=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(myGifError(gf));
i_push_error(0, "Could not save image data:");
mm_log((1, "Error in EGifPutLine\n"));
return 0;
}
}
}
}
else {
int y;
for (y = 0; y < img->ysize; ++y) {
if (EGifPutLine(gf, data, img->xsize) == GIF_ERROR) {
gif_push_error(myGifError(gf));
i_push_error(0, "Could not save image data:");
mm_log((1, "Error in EGifPutLine\n"));
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(myGifError(gf));
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];
#ifdef IMAGER_SNPRINTF
snprintf(buf, sizeof(buf), "%d", img->tags.tags[pos].idata);
#else
sprintf(buf, "%d", img->tags.tags[pos].idata);
#endif
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];
subblock[0] = 1;
subblock[1] = loop_count % 256;
subblock[2] = loop_count / 256;
#if IMGIFLIB_API_VERSION >= 500
if (EGifPutExtensionLeader(gf, APPLICATION_EXT_FUNC_CODE) == GIF_ERROR
|| EGifPutExtensionBlock(gf, 11, nsle) == GIF_ERROR
|| EGifPutExtensionBlock(gf, 3, subblock) == GIF_ERROR
|| EGifPutExtensionTrailer(gf) == GIF_ERROR) {
gif_push_error(myGifError(gf));
i_push_error(0, "writing loop extension");
return 0;
}
#else
if (EGifPutExtensionFirst(gf, APPLICATION_EXT_FUNC_CODE, 11, nsle) == GIF_ERROR) {
gif_push_error(myGifError(gf));
i_push_error(0, "writing loop extension");
return 0;
}
if (EGifPutExtensionLast(gf, APPLICATION_EXT_FUNC_CODE, 3, subblock) == GIF_ERROR) {
gif_push_error(myGifError(gf));
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) {
i_push_error(0, "Could not create color map object");
return NULL;
}
#if IMGIFLIB_API_VERSION >= 500
map->SortFlag = 0;
#endif
return map;
}
/*
=item need_version_89a(i_quantize *quant, i_img *imgs, int count)
Return true if the file we're creating on these images needs a GIF89a
header.
=cut
*/
static int
need_version_89a(i_quantize *quant, i_img **imgs, int count) {
int need_89a = 0;
int temp;
int i;
for (i = 0; i < count; ++i) {
if (quant->transp != tr_none &&
(imgs[i]->channels == 2 || imgs[i]->channels == 4)) {
need_89a = 1;
break;
}
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;
}
}
return need_89a;
}
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)
Tests if all the given images are paletted and their colors are in the
palette produced.
Previously this would build a consolidated palette from the source,
but that meant that if the caller supplied a static palette (or
specified a fixed palette like "webmap") then we wouldn't be
quantizing to the caller specified palette.
=cut
*/
static int
has_common_palette(i_img **imgs, int count, i_quantize *quant) {
int i;
int imgn;
char used[256];
int col_count;
/* 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));
}
col_count = i_colorcount(imgs[imgn]);
for (i = 0; i < col_count; ++i) {
i_color c;
i_getcolors(imgs[imgn], i, &c, 1);
if (used[i]) {
if (in_palette(&c, quant, quant->mc_count) < 0) {
mm_log((1, " color not found in palette, no palette shortcut\n"));
return 0;
}
}
}
}
mm_log((1, " all colors found in palette, palette shortcut\n"));
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;
i_img_dim 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 = NULL;
int anylocal;
i_img **glob_imgs = NULL; /* 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;
int error;
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");
goto fail_cleanup;
}
/* 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))
scrh = 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) {
i_img *im = imgs[imgn];
if (im->xsize > 0xFFFF || im->ysize > 0xFFFF) {
i_push_error(0, "image too large for GIF");
goto fail_cleanup;
}
posx = posy = 0;
i_tags_get_int(&im->tags, "gif_left", 0, &posx);
if (posx < 0) posx = 0;
i_tags_get_int(&im->tags, "gif_top", 0, &posy);
if (posy < 0) posy = 0;
if (im->xsize + posx > scrw)
scrw = im->xsize + posx;
if (im->ysize + posy > scrh)
scrh = im->ysize + posy;
if (!i_tags_get_int(&im->tags, "gif_local_map", 0, localmaps+imgn))
localmaps[imgn] = 0;
if (localmaps[imgn])
anylocal = 1;
else {
if (im->channels == 4) {
glob_want_trans = 1;
}
glob_imgs[glob_img_count++] = im;
}
}
glob_want_trans = glob_want_trans && quant->transp != tr_none ;
if (scrw > 0xFFFF || scrh > 0xFFFF) {
i_push_error(0, "screen size too large for GIF");
goto fail_cleanup;
}
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;
}
i_quant_makemap(quant, glob_imgs, glob_img_count);
glob_paletted = has_common_palette(glob_imgs, glob_img_count, quant);
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;
i_quant_makemap(quant, imgs, 1);
colors_paletted = has_common_palette(imgs, 1, quant);
}
if ((map = make_gif_map(quant, imgs[0], want_trans)) == NULL) {
mm_log((1, "Error in MakeMapObject"));
goto fail_cleanup;
}
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) {
gif_push_error(myGifError(gf));
i_push_error(0, "Could not save screen descriptor");
FreeMapObject(map);
mm_log((1, "Error in EGifPutScreenDesc."));
goto fail_cleanup;
}
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;
i_quant_makemap(quant, imgs, 1);
colors_paletted = has_common_palette(imgs, 1, quant);
if ((map = make_gif_map(quant, imgs[0], want_trans)) == NULL) {
mm_log((1, "Error in MakeMapObject"));
goto fail_cleanup;
}
}
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) {
goto fail_cleanup;
}
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])) {
goto fail_cleanup;
}
if (!do_gce(gf, imgs[0], want_trans, trans_index)) {
goto fail_cleanup;
}
if (!do_comments(gf, imgs[0])) {
goto fail_cleanup;
}
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) {
if (map)
FreeMapObject(map);
gif_push_error(myGifError(gf));
i_push_error(0, "Could not save image descriptor");
mm_log((1, "Error in EGifPutImageDesc."));
goto fail_cleanup;
}
if (map)
FreeMapObject(map);
if (!do_write(gf, interlace, imgs[0], result)) {
goto fail_cleanup;
}
myfree(result);
result = NULL;
/* 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)) {
result = quant_paletted(quant, imgs[imgn]);
}
else {
i_quant_makemap(quant, imgs+imgn, 1);
result = i_quant_translate(quant, imgs[imgn]);
}
if (!result) {
mm_log((1, "error in i_quant_translate()"));
goto fail_cleanup;
}
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) {
mm_log((1, "Error in MakeMapObject."));
goto fail_cleanup;
}
}
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)) {
goto fail_cleanup;
}
if (!do_comments(gf, imgs[imgn])) {
goto fail_cleanup;
}
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) {
gif_push_error(myGifError(gf));
i_push_error(0, "Could not save image descriptor");
if (map)
FreeMapObject(map);
mm_log((1, "Error in EGifPutImageDesc."));
goto fail_cleanup;
}
if (map)
FreeMapObject(map);
if (!do_write(gf, interlace, imgs[imgn], result)) {
goto fail_cleanup;
}
myfree(result);
result = NULL;
}
if (myEGifCloseFile(gf, &error) == GIF_ERROR) {
gif_push_error(error);
i_push_error(0, "Could not close GIF file");
goto fail_cleanup;
}
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;
fail_cleanup:
quant->mc_colors = orig_colors;
myfree(result);
myfree(glob_colors);
myfree(localmaps);
myfree(glob_imgs);
(void)myEGifCloseFile(gf, &error);
return 0;
}
static int
io_glue_write_cb(GifFileType *gft, const GifByteType *data, int length) {
io_glue *ig = (io_glue *)gft->UserData;
return i_io_write(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 gif_error;
int result;
gif_mutex_lock(mutex);
i_clear_error();
#ifdef PRE_SET_VERSION
EGifSetGifVersion(need_version_89a(quant, imgs, count) ? "89a" : "87a");
#endif
if ((GifFile = myEGifOpen((void *)ig, io_glue_write_cb, &gif_error )) == NULL) {
gif_push_error(gif_error);
i_push_error(0, "Cannot create giflib callback object");
mm_log((1,"i_writegif_wiol: Unable to open callback datasource.\n"));
gif_mutex_unlock(mutex);
return 0;
}
#ifdef POST_SET_VERSION
EGifSetGifVersion(GifFile, need_version_89a(quant, imgs, count));
#endif
result = i_writegif_low(quant, GifFile, imgs, count);
gif_mutex_unlock(mutex);
if (i_io_close(ig))
return 0;
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.
Returns NULL for unknown error codes.
=cut
*/
static char const *
gif_error_msg(int code) {
#if IMGIFLIB_API_VERSION >= 500
return GifErrorString(code);
#else
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:
return NULL;
}
#endif
}
/*
=item gif_push_error(code)
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(int code) {
const char *msg = gif_error_msg(code);
if (msg)
i_push_error(code, msg);
else
i_push_errorf(code, "Unknown GIF error %d", code);
}
/*
=head1 AUTHOR
Arnar M. Hrafnkelsson, addi@umich.edu
Tony Cook <tonyc@cpan.org>
=head1 SEE ALSO
perl(1), Imager(3)
=cut
*/