#ifdef HAVE_LIBGIF
/* This version of the gif module is taken from IV, please update the
 * IV version and then copy it to here instead of updating this
 * one
 */
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <gif_lib.h>

#include "rgba_to_cidx.h"
#include "gif.h"


/* Last error message pointer */
const char *imgio_last_load_error;
const char *imgio_last_write_error;


/*
 *	RGBAToCIdx Callback Data:
 */
typedef struct {
	int		(*progress_cb)(
		void *,		/* Data */
		int, int,	/* Progress current & end values */
		int, int,	/* Width & height */
		int, int,	/* Bytes per line & bytes per pixel */
		u_int8_t *	/* RGBA data */
	);
	void		*data;
	u_int8_t	*rgba;
	int		width, height,
			bpp, bpl;
	int		*user_aborted;
} RGBAToCIdxData;


/* GIF Library Version */
void ImgGIFVersion(int *major, int *minor, int *release); 

mode_t ImgGIFGetFileMode(const char *filename);

/* GIF Reading */
int ImgGIFReadRGBA(
	const char *filename,
	int *width_rtn, int *height_rtn, int *bpp_rtn, int *bpl_rtn,
	u_int8_t ***rgba_rtn, unsigned long **delay_list_rtn,
	int *nframes_rtn,
	u_int8_t *bg_color,	/* 4 bytes in RGBA format, will be modified */
	int *x_rtn, int *y_rtn,
	int *base_width_rtn, int *base_height_rtn,
	char **creator_rtn, char **title_rtn,
	char **author_rtn, char **comments_rtn,
	u_int8_t def_alpha_value,
	void *client_data,
	int (*progress_cb)(
		void *,		/* Client data */
		int, int,	/* Progress current & end values */
		int, int,	/* Width & height */
		int, int,	/* Bytes per line & bytes per pixel */
		u_int8_t *	/* RGBA data */
	),
	int *user_aborted
);


/* GIF Dithering */
static int ImgGIFRGBAToCIdxCB(int i, int m, void *data);
static int ImgGIFDitherRGBA(
	u_int8_t *rgba,
	int width, int height, int bpl,
	const u_int8_t *bg_color, int *bg_color_num,
	int *trans_color_num,
	ColorMapObject **colormap_rtn,
	GifByteType **data_rtn,
	void *client_data,
	int (*progress_cb)(
		void *,		/* Client data */
		int, int,	/* Progress current & end values */
		int, int,	/* Width & height */
		int, int,	/* Bytes per line & bytes per pixel */
		u_int8_t *	/* RGBA data */
	),
	int *user_aborted
);
/* GIF Writing */
static int ImgWriteGIFFileRGBAToColor(
	const char *filename,
	int width, int height,
	int bpl, int bpp,	/* bpp must be 4 */
	u_int8_t **rgba, unsigned long *delay_list,
	int nframes,
	const u_int8_t *bg_color,	/* 4 bytes in RGBA format */
	const char *comments,
	int interlaced,		/* 0 or 1 */
	int transparency,	/* 0 or 1 */
	void *client_data,
	int (*progress_cb)(
		void *,		/* Client data */
		int, int,	/* Progress current & end values */
		int, int,	/* Width & height */
		int, int,	/* Bytes per line & bytes per pixel */
		u_int8_t *	/* RGBA data */
	),
	int *user_aborted
);
static int ImgWriteGIFFileRGBAToGreyscale(
	const char *filename,
	int width, int height,
	int bpl, int bpp,	/* bpp must be 4 */
	u_int8_t **rgba, unsigned long *delay_list,
	int nframes,
	const u_int8_t *bg_color,	/* 4 bytes in RGBA format */
	const char *comments,
	int interlaced,		/* 0 or 1 */
	int transparency,	/* 0 or 1 */
	void *client_data,
	int (*progress_cb)(
		void *,		/* Client data */
		int, int,	/* Progress current & end values */
		int, int,	/* Width & height */
		int, int,	/* Bytes per line & bytes per pixel */
		u_int8_t *	/* RGBA data */
	),
	int *user_aborted
);
static int ImgWriteGIFFileRGBAToBW(
	const char *filename,
	int width, int height,
	int bpl, int bpp,	/* bpp must be 4 */
	u_int8_t **rgba, unsigned long *delay_list,
	int nframes,
	const u_int8_t *bg_color,	/* 4 bytes in RGBA format */
	const char *comments,
	int interlaced,		/* 0 or 1 */
	int transparency,	/* 0 or 1 */
	void *client_data,
	int (*progress_cb)(
		void *,		/* Client data */
		int, int,	/* Progress current & end values */
		int, int,	/* Width & height */
		int, int,	/* Bytes per line & bytes per pixel */
		u_int8_t *	/* RGBA data */
	),
	int *user_aborted
);
int ImgWriteGIFFileRGBA(
	const char *filename,
	int width, int height,
	int bpl, int bpp,	/* bpp must be 4 */
	u_int8_t **rgba, unsigned long *delay_list,
	int nframes,
	const u_int8_t *bg_color,	/* 4 bytes in RGBA format */
	const char *comments,
	int interlaced,		/* 0 or 1 */
	int format,		/* 0 = B&W
				 * 1 = Greyscale
				 * 2 = Color */
	int transparency,	/* 0 or 1 */
	void *client_data,
	int (*progress_cb)(
		void *,		/* Client data */
		int, int,	/* Progress current & end values */
		int, int,	/* Width & height */
		int, int,	/* Bytes per line & bytes per pixel */
		u_int8_t *	/* RGBA data */
	) 
);


#define ATOI(s)		(((s) != NULL) ? atoi(s) : 0)
#define ATOL(s)		(((s) != NULL) ? atol(s) : 0)
#define ATOF(s)		(((s) != NULL) ? atof(s) : 0.0f)
#define STRDUP(s)	(((s) != NULL) ? strdup(s) : NULL)

#define MAX(a,b)	(((a) > (b)) ? (a) : (b))
#define MIN(a,b)	(((a) < (b)) ? (a) : (b))
#define CLIP(a,l,h)	(MIN(MAX((a),(l)),(h)))
#define STRLEN(s)	(((s) != NULL) ? strlen(s) : 0)
#define STRISEMPTY(s)	(((s) != NULL) ? (*(s) == '\0') : TRUE)


static int	gif_interlace_offset[] = { 0, 4, 2, 1 },
		gif_interlace_jumps[] = { 8, 8, 4, 2 };


/*
 *	Gets the GIF library's version.
 */
void ImgGIFVersion(int *major, int *minor, int *release) 
{
	const char *ver_str = GIF_LIB_VERSION, *s;

	s = strstr(ver_str, "Version");
	while((*s != '\0') && (*s != ' '))
	    s++;
	while(*s == ' ')
	    s++;
	if(major != NULL)
	    *major = ATOI(s);

	while((*s != '\0') && (*s != '.'))
	    s++;
	while(*s == '.')
	    s++;
	if(minor != NULL)
	    *minor = ATOI(s);

	while((*s != '\0') && (*s != '.'))
	    s++;
	while(*s == '.')
	    s++;
	if(release != NULL)
	    *release = ATOI(s);
}


/*
 *	Returns the permissions of the specified file.
 *
 *	If the file does not exist then the default permissions are
 *	returned.
 *
 *	This is needed for the GIF write functions since they do not
 *	set the file permissions on a newly created file.
 */
mode_t ImgGIFGetFileMode(const char *filename)
{
	struct stat stat_buf;

	if(stat(filename, &stat_buf))
	{
	    const mode_t m = umask(0);
	    umask(m);
	    return(
		(~m) &
		    (S_IRUSR | S_IWUSR |
		     S_IRGRP | S_IWGRP |
		     S_IROTH | S_IWOTH)
	    );
	}
	else
	{
	    return(stat_buf.st_mode);
	}
}


/*
 *	Reads the GIF file.
 *
 *	Return values:
 *
 *	0	Success
 *	-1	General error
 *	-2	Bad value (invalid format/not a gif file)
 *	-3	Systems error
 *	-4	User abort
 */
int ImgGIFReadRGBA(
	const char *filename,
	int *width_rtn, int *height_rtn, int *bpp_rtn, int *bpl_rtn,
	u_int8_t ***rgba_rtn, unsigned long **delay_list_rtn,
	int *nframes_rtn,
	u_int8_t *bg_color,	/* 4 bytes in RGBA format, will be modified */
	int *x_rtn, int *y_rtn,
	int *base_width_rtn, int *base_height_rtn,
	char **creator_rtn, char **title_rtn,
	char **author_rtn, char **comments_rtn,
	u_int8_t def_alpha_value,
	void *client_data,
	int (*progress_cb)(
		void *,		/* Client data */
		int, int,	/* Progress current & end values */
		int, int,	/* Width & height */
		int, int,	/* Bytes per line & bytes per pixel */
		u_int8_t *	/* RGBA data */
	), 
	int *user_aborted
)
{
	int		i,
			scr_width, scr_height,
			scr_bpp, scr_bpl,
			scr_buf_len,
			bg_color_num, trans_color_num = -1,
			disposal_method = 0,
			prev_disposal_method = disposal_method;
	char		*comment = NULL;
	GifFileType *ft;
	GifRowType *scr_buf;		/* Main GIF screen buffer */
	GifRecordType rt;

	/* Reset last load error */
	imgio_last_load_error = NULL;

	/* Skip if user aborted */
	if(*user_aborted)
	{
	    imgio_last_load_error = "User aborted operation";
	    return(-4);
	}

	/* Open GIF file */
	ft = DGifOpenFileName(filename);
	if(ft == NULL)
	{
	    imgio_last_load_error = "Unable to open the file for reading";
	    return(-1);
	}

	/* Get the GIF screen values */
	scr_width = ft->SWidth;
	scr_height = ft->SHeight;
	bg_color_num = ft->SBackGroundColor;

#if 0
printf(
 "Total Colors: %i  BG Color: #%i  Res: %i\n",
 ft->SColorMap->ColorCount,
 ft->SBackGroundColor,
 ft->SColorResolution
);
#endif

	/* Get the GIF background color */
	if((ft->SColorMap != NULL) && (bg_color != NULL))
	{
	    const ColorMapObject *colormap = ft->SColorMap;
	    const GifColorType *c = &colormap->Colors[bg_color_num];
	    bg_color[0] = (u_int8_t)c->Red;
	    bg_color[1] = (u_int8_t)c->Green;
	    bg_color[2] = (u_int8_t)c->Blue;
	    bg_color[3] = 0xff;
	}

	/* Allocate the main GIF screen buffer, this is an array of
	 * GifRowType *
	 */
	scr_buf_len = scr_height * sizeof(GifRowType *);
	scr_buf = (scr_buf_len > 0) ?
	    (GifRowType *)malloc(scr_buf_len) : NULL;
	if(scr_buf == NULL)
	{
	    DGifCloseFile(ft);
	    imgio_last_load_error = "Memory allocation error";
	    return(-3);
	}

	/* Calculate the bytes per line of the screen buffer */
	scr_bpp = sizeof(GifPixelType);
	scr_bpl = scr_width * scr_bpp;

	/* Allocate first row and set its color to the background */
	scr_buf[0] = (GifRowType)malloc(scr_bpl);
	for(i = 0; i < scr_width; i++)
	    scr_buf[0][i] = bg_color_num;

	/* Allocate subsequent rows and set their colors to the
	 * background too
	 */
	for(i = 1; i < scr_height; i++)
	{
	    scr_buf[i] = (GifRowType)malloc(scr_bpl);
	    memcpy(scr_buf[i], scr_buf[0], scr_bpl);
	}


	/* Begin reading the GIF file and load the images
	 *
	 * Each image will be rendered on to the target RGBA image
	 * buffer as it is loaded
	 */
	do
	{
	    if(*user_aborted)
		break;

	    if(DGifGetRecordType(ft, &rt) == GIF_ERROR)
		break;

	    switch(rt)
	    {
	      case UNDEFINED_RECORD_TYPE:
		break;

	      case SCREEN_DESC_RECORD_TYPE:
		break;

	      case IMAGE_DESC_RECORD_TYPE:
		if(DGifGetImageDesc(ft) == GIF_OK)
		{
		    const int	rgba_bpp = 4,
				rgba_bpl = scr_width * rgba_bpp,
				frame_num = *nframes_rtn,
				prev_frame_num = frame_num - 1;
		    GifImageDesc *img = &ft->Image;
		    int		x, y, j, c_num,
				top = img->Top,
				left = img->Left,
				width = img->Width,
				height = img->Height;
		    const u_int8_t *prev_rgba = (prev_frame_num >= 0) ?
			(*rgba_rtn)[prev_frame_num] : NULL;
		    u_int8_t *rgba, *rgba_ptr;
		    const GifColorType *c;
		    GifRowType scr_row;
		    const ColorMapObject *colormap = (img->ColorMap != NULL) ?
			img->ColorMap : ft->SColorMap;

		    /* No colormap defined by local image or system? */
		    if((colormap != NULL) ? (colormap->ColorCount <= 0) : TRUE)
			break;

		    /* Allocate the RGBA image data */
		    rgba = (u_int8_t *)malloc(rgba_bpl * scr_height);
		    if(rgba == NULL)
			break;

		    /* Load this GIF image to the GIF screen buffer */

		    /* Interlaced? */
		    if(img->Interlace)
		    {
			const int	po = 0,
					pm = 2 * height;
			int lines_loaded = 0;

			/* Interlace needs 4 passes on the image */
			for(i = 0; i < 4; i++)
			{
			    for(j = top + gif_interlace_offset[i];
				j < (top + height);
				j += gif_interlace_jumps[i]
			    )
			    {
				if(DGifGetLine(
				    ft,
				    (GifPixelType *)&scr_buf[j][left],
				    width
				) == GIF_ERROR)
				    break;

				lines_loaded++;

				/* Report progress */
				if((progress_cb != NULL) &&
				   ((lines_loaded % 5) == 0)
				)
				{
				    if(!progress_cb(
					client_data,
					po + lines_loaded, pm,
					scr_width, scr_height,
					rgba_bpl, rgba_bpp,
					rgba
				    ))
				    {
					*user_aborted = 1;
					break;
				    }
				}
			    }
			}
		    }
		    else
		    {
			const int	po = 0,
					pm = 2 * height;
			/* Read each line sequentially */
			for(i = 0; i < height; i++)
			{
			    if(DGifGetLine(
				ft,
				(GifPixelType *)&scr_buf[top++][left],
				width
			    ) == GIF_ERROR)
				break;

			    /* Report progress */
			    if((progress_cb != NULL) &&
			       ((i % 5) == 0)
			    )
			    {
				if(!progress_cb(
				    client_data,
				    po + i, pm,
				    scr_width, scr_height,
				    rgba_bpl, rgba_bpp,
				    rgba
				))
				{ 
				    *user_aborted = 1;
				    break;
				}
			    }
			}
		    }
		    if(*user_aborted)
		    {
			free(rgba);
			break;
		    }

		    /* Copy the entire GIF screen buffer to the RGBA
		     * image data
		     */
		    for(y = 0; y < scr_height; y++)
		    {
			scr_row = scr_buf[y];
			rgba_ptr = rgba + (y * rgba_bpl);
			for(x = 0; x < scr_width; x++)
			{
			    /* Current pixel value is colormap index */
			    c_num = (int)scr_row[x];

			    /* Not transparent? */
			    if(c_num != trans_color_num)
			    {
				c = &colormap->Colors[c_num];
				*rgba_ptr++ = (u_int8_t)c->Red;
				*rgba_ptr++ = (u_int8_t)c->Green;
				*rgba_ptr++ = (u_int8_t)c->Blue;
				*rgba_ptr++ = def_alpha_value;
			    }
			    /* Is transparent, set it to transparent
			     * only if the previous disposal method
			     * specified to clear the background
			     */
			    else if(prev_disposal_method == 2)
			    {
				c = &colormap->Colors[c_num];
				*rgba_ptr++ = (u_int8_t)c->Red;
				*rgba_ptr++ = (u_int8_t)c->Green;
				*rgba_ptr++ = (u_int8_t)c->Blue;
				*rgba_ptr++ = 0x00;
			    }
			    /* Is transparent, but the disposal method
			     * specified to not clear or to use the
			     * previous image's pixel
			     */
			    else if(prev_rgba != NULL)
			    {
				memcpy(
				    rgba_ptr,
				    prev_rgba + (y * rgba_bpl) +
					(x * rgba_bpp),
				    rgba_bpp
				);
				rgba_ptr += rgba_bpp;
			    }
			    /* Is transparent and this is the first
			     * frame, so just set as transparent
			     */
			    else
			    {
				c = &colormap->Colors[c_num];
				*rgba_ptr++ = (u_int8_t)c->Red;
				*rgba_ptr++ = (u_int8_t)c->Green;
				*rgba_ptr++ = (u_int8_t)c->Blue;
				*rgba_ptr++ = 0x00;
			    }
			}

			/* Report progress */
			if((progress_cb != NULL) && ((y % 5) == 0))
			{
			    if(!progress_cb(
				client_data,
				scr_height + y, 2 * scr_height,
				scr_width, scr_height,
				rgba_bpl, rgba_bpp,
				rgba
			    ))
			    {
				*user_aborted = 1;
				break;
			    }
			}
		    }
		    if(*user_aborted)
		    {
			free(rgba);
			break;
		    }

		    /* Append the RGBA image data to the list */
		    *nframes_rtn = frame_num + 1;
		    *rgba_rtn = (u_int8_t **)realloc(
			*rgba_rtn,
			(*nframes_rtn) * sizeof(u_int8_t *)
		    );
		    if(*rgba_rtn != NULL)
			(*rgba_rtn)[frame_num] = rgba;
		    else
			free(rgba);

		    /* Handle disposal method
		     *
		     * Clear with background?
		     */
		    if(disposal_method == 2)
		    {
			/* Clear first line */
			for(i = 0; i < scr_width; i++)
			    scr_buf[0][i] = bg_color_num;
			/* Copy first line to subsequent lines */
			for(i = 1; i < scr_height; i++)
			    memcpy(scr_buf[i], scr_buf[0], scr_bpl);
		    }
		}
		break;

	      case EXTENSION_RECORD_TYPE:
		if(1)
		{
		    int code;
		    GifByteType *ext, ext_len;

		    /* Get first extension */
		    if(DGifGetExtension(ft, &code, &ext) == GIF_OK)
		    {
			/* Iterate through extensions */
			while(ext != NULL)
			{
			    /* Get this extension's length */
			    ext_len = ext[0];

			    /* Handle by extension code */
			    switch(code)
			    {
			      case COMMENT_EXT_FUNC_CODE:	/* Comment */
				ext[ext_len + 1] = '\0';
				if(comment != NULL)
				{
				    comment = (char *)realloc(
					comment,
					STRLEN(comment) + ext_len + 1
				    );
				}
				else
				{
				    comment = (char *)malloc(ext_len + 1);
				    *comment = '\0';
				}
				strcat(comment, ext + 1);
				break;

			      case GRAPHICS_EXT_FUNC_CODE:	/* Graphics Control */
				/* Format (in bytes):
				 *
				 * <len>
				 * <flags> <delay_time> <delay_time_cnt>
				 * <transparent_color>
				 */
				if(ext_len >= 4)
				{
				    const int nframes = *nframes_rtn;
				    const unsigned int flags = (unsigned int)ext[1];
				    const unsigned int dm_flags = (flags >> 2);
				    char user_input;

				    /* Get delay
				     *
				     * Needed to add 1 to frame since
				     * Graphic Controls come before
				     * each frame
				     */
				    *delay_list_rtn = (unsigned long *)realloc(
					*delay_list_rtn,
					(nframes + 1) * sizeof(unsigned long)
				    );
				    if(*delay_list_rtn != NULL)
					(*delay_list_rtn)[nframes] = (unsigned long)ext[2] * 10;

				    /* Get the transparent color number */
				    trans_color_num = (flags & (1 << 0)) ?
					(int)ext[4] : -1;

				    /* Get user input */
				    user_input = (flags & (1 << 1)) ?
					1 : 0;
				    if(user_input)
				    {
					/* Wait for user input */

				    }

				    /* Get disposal method */
				    prev_disposal_method = disposal_method;
				    disposal_method = (int)(dm_flags &
					((1 << 0) | (1 << 1) | (1 << 2)));
				}
				break;

			      case PLAINTEXT_EXT_FUNC_CODE:
			      case APPLICATION_EXT_FUNC_CODE:
				break;
			    }

			    /* Get next extension */
			    if(DGifGetExtensionNext(ft, &ext) == GIF_ERROR)
				break;
			}
		    }
		}
		break;

	    case TERMINATE_RECORD_TYPE:
		break;
	    }
	}
	while(rt != TERMINATE_RECORD_TYPE);


	/* Close the GIF file */
	DGifCloseFile(ft);

	/* Delete the GIF screen buffer */
	for(i = 0; i < scr_height; i++)
	    free(scr_buf[i]);
	free(scr_buf);

	/* Update returns */
	if(width_rtn != NULL)
	    *width_rtn = scr_width;
	if(height_rtn != NULL)
	    *height_rtn = scr_height;
	if(bpp_rtn != NULL)
	    *bpp_rtn = 4;
	if(bpl_rtn != NULL)
	    *bpl_rtn = scr_width * 4;
	if(base_width_rtn != NULL)
	    *base_width_rtn = scr_width;
	if(base_height_rtn != NULL)
	    *base_height_rtn = scr_height;
	if(comments_rtn != NULL)
	{
	    free(*comments_rtn);
	    *comments_rtn = comment;
	    comment = NULL;
	}

	/* Delete values that were not transfered to the returns */
	free(comment);

	if(*user_aborted)
	{
	    imgio_last_load_error = "User aborted operation";
	    return(-4);
	}
	else
	    return(0);
}


/*
 *	RGBAToCIdx progress callback.
 */
static int ImgGIFRGBAToCIdxCB(int i, int m, void *data)
{
	RGBAToCIdxData *d = (RGBAToCIdxData *)data;
	if(d == NULL)
	    return(1);

	if((d->progress_cb != NULL) && ((i % 5) == 0))
	{
	    if(!d->progress_cb(
		d->data,
		i / 2, m,
		d->width, d->height,
		d->bpl, d->bpp,
		d->rgba
	    ))
		*d->user_aborted = 1;
	}

	return(*d->user_aborted);
}

/*
 *	Creates a GIF ColorMapObject and GIF image from the specified
 *	RGBA image data.
 *
 *	If bg_color and bg_color_num are not NULL then the background
 *	color specified by bg_color will be added to the colormap.
 *	*bg_color_num will be updated with the background color number.
 *
 *	If trans_color_num is not NULL then transparency will be
 *	processed and a transparent color will be added to the colormap.
 *	*trans_color_num will be updated with the transparent color
 *	number.
 *
 *	Inputs assumed valid except as noted above.
 */
static int ImgGIFDitherRGBA(
	u_int8_t *rgba,
	int width, int height, int bpl,
	const u_int8_t *bg_color, int *bg_color_num,
	int *trans_color_num,
	ColorMapObject **colormap_rtn,
	GifByteType **data_rtn,
	void *client_data,
	int (*progress_cb)(
		void *,		/* Client data */
		int, int,	/* Progress current & end values */
		int, int,	/* Width & height */
		int, int,	/* Bytes per line & bytes per pixel */
		u_int8_t *	/* RGBA data */
	),
	int *user_aborted
)
{
	const int bpp = 4;
	int		i, n, ncolors,
			max_colors = 256;
	u_int8_t *cidx;
	GifColorType *gif_c;
	ColormapColor *c, *colors_list;
	ColorMapObject *colormap;
	GifByteType *data;
	RGBAToCIdxData *d;

	/* Reduce the maximum number of allowed dither colors for
	 * the background and transparent colors
	 */
	if((bg_color != NULL) || (trans_color_num != NULL))
	    max_colors--;

	/* Allocate the RGBAToCIdx() callback data */
	d = (RGBAToCIdxData *)calloc(1, sizeof(RGBAToCIdxData));
	d->progress_cb = progress_cb;
	d->data = client_data;
	d->width = width;
	d->height = height;
	d->bpp = bpp;
	d->bpl = bpl;
	d->rgba = rgba;
	d->user_aborted = user_aborted;

	/* Generate the color index image data and the corresponding
	 * colormap
	 */
 	RGBAToCIdx(
	    rgba, width, height, bpl,
	    max_colors,
	    &cidx,
	    &colors_list,
	    &ncolors,
	    ImgGIFRGBAToCIdxCB,
	    d
	);
	free(d);

	/* User aborted? */
	if(*user_aborted)
	{
	    free(cidx);
	    free(colors_list);
	    imgio_last_write_error = "User aborted operation";
	    return(-4);
	}

	/* Get the generated color index image as the GIF image */
	data = (GifByteType *)cidx;
	if(data == NULL)
	{
	    free(colors_list);
	    imgio_last_write_error =
		"Unable to generate color index image";
	    return(-1);
	}

	/* Unable to generate the colors list? */
	if(colors_list == NULL)
	{
	    free(data);
	    imgio_last_write_error =
		"Unable to generate color palette";
	    return(-1);
	}

	/* Append background color? */
	if(bg_color != NULL)
	{
	    i = ncolors;
	    ncolors = i + 1;
	    colors_list = (ColormapColor *)realloc(
		colors_list,
		ncolors * sizeof(ColormapColor)
	    );
	    if(colors_list == NULL)
	    {
		free(data);
		imgio_last_write_error = "Memory allocation error";
		return(-3);
	    }
	    c = &colors_list[i];
	    c->r = bg_color[0];
	    c->g = bg_color[1];
	    c->b = bg_color[2];
	    if(bg_color_num != NULL)
		*bg_color_num = i;
	}

	/* Need transparent color and background color available? */
	if((trans_color_num != NULL) && (bg_color != NULL) &&
	   (bg_color_num != NULL)
	)
	{
	    /* Use background color as the transparent color */
	    *trans_color_num = *bg_color_num;
	}
	/* Append transparent color? */
	else if(trans_color_num != NULL)
	{
	    i = ncolors;
	    ncolors = i + 1;
	    colors_list = (ColormapColor *)realloc(
		colors_list,
		ncolors * sizeof(ColormapColor)
	    );
	    if(colors_list == NULL)
	    {
		imgio_last_write_error = "Memory allocation error";
		return(-3);
	    }
	    c = &colors_list[i];
	    c->r = 0x00;
	    c->g = 0x00;
	    c->b = 0x00;
	    *trans_color_num = i;
	}

	/* Need to apply transparency to the color index image data? */
	if(trans_color_num != NULL)
	{
	    const int	cidx_bpp = sizeof(GifByteType),
			cidx_bpl = width * cidx_bpp;
	    const GifByteType v = (GifByteType)(*trans_color_num);
	    int y;
	    const u_int8_t alpha_threshold = 0x80;
	    const u_int8_t *rgba_ptr, *rgba_end;
	    GifByteType *cidx_ptr, *cidx_end;


	    /* Scan the RGBA image data, set all values on the color
	     * index image to the transparent color number v that
	     * correspond to a transparent pixel on the RGBA image
	     * data
	     */
	    for(y = 0; y < height; y++)
	    {
		rgba_ptr = rgba + (y * bpl);
		rgba_end = rgba_ptr + (width * bpp);
		cidx_ptr = data + (y * cidx_bpl);
	        cidx_end = cidx_ptr + (width * cidx_bpp);
	        while((rgba_ptr < rgba_end) &&
		      (cidx_ptr < cidx_end)
		)
	        {
		    /* Transparent? */
		    if(rgba_ptr[3] < alpha_threshold)
			*cidx_ptr = v;
		    rgba_ptr += 4;
		    cidx_ptr += 1;
		}
	    }
	}

	/* Create the GIF colormap */
	colormap = MakeMapObject(
	    256,		/* Must be 256 or else fails */
	    NULL
	);
	if(colormap == NULL)
	{
	    free(data);
	    free(colors_list);
	    imgio_last_write_error = "Memory allocation error";
	    return(-3);
	}

	/* Copy the colors from the colors list to the GIF colormap */
	n = MIN(ncolors, colormap->ColorCount);
	for(i = 0; i < n; i++)
	{
	    c = &colors_list[i];
	    gif_c = &colormap->Colors[i];
	    gif_c->Red = (GifByteType)c->r;
	    gif_c->Green = (GifByteType)c->g;
	    gif_c->Blue = (GifByteType)c->b;
	}

	/* Delete the colors list */
	free(colors_list);

	*data_rtn = data;
	*colormap_rtn = colormap;

	return(0);
}


/*
 *	Writes the specified image data to a GIF file in color.
 */
static int ImgWriteGIFFileRGBAToColor(
	const char *filename,
	int width, int height,
	int bpl, int bpp,	/* bpp must be 4 */
	u_int8_t **rgba, unsigned long *delay_list,
	int nframes,
	const u_int8_t *bg_color,	/* 4 bytes in RGBA format */
	const char *comments,
	int interlaced,		/* 0 or 1 */
	int transparency,	/* 0 or 1 */
	void *client_data,
	int (*progress_cb)(
		void *,		/* Client data */
		int, int,	/* Progress current & end values */
		int, int,	/* Width & height */
		int, int,	/* Bytes per line & bytes per pixel */
		u_int8_t *	/* RGBA data */
	),
	int *user_aborted
)
{
	int	status, frame_num, disposal_method,
		color_resolution = 8,
		bg_color_num = 0,
		trans_color_num = -1;
	char user_input = 0;
	unsigned long delay;
	u_int8_t *rgba_cur;
	const mode_t mode = ImgGIFGetFileMode(filename);
	ColorMapObject *colormap = NULL;
	GifByteType *cidx = NULL;
	GifFileType *ft;

	/* Open the GIF file for writing */
	ft = EGifOpenFileName(filename, 0);
	if(ft == NULL)
	{
	    imgio_last_write_error = "Unable to open the file for writing";
	    return(-1);
	}

	/* Iterate through each frame */
	for(frame_num = 0; frame_num < nframes; frame_num++)
	{
	    if(*user_aborted)
	       break;

	    rgba_cur = rgba[frame_num];
	    if(rgba_cur == NULL)
		continue;

	    /* Get the delay for this frame */
	    delay = (delay_list != NULL) ? delay_list[frame_num] : 0l;

	    /* Dither this RGBA image data frame and get the GIF
	     * colormap and the GIF color index image data
	     */
	    status = ImgGIFDitherRGBA(
		rgba_cur, width, height, bpl,
		bg_color, &bg_color_num,
		&trans_color_num,
		&colormap,
		&cidx,
		client_data,
		progress_cb,
		user_aborted
	    );
	    if(status || (colormap == NULL) || (cidx == NULL))
	    {
		if(colormap != NULL)
		    FreeMapObject(colormap);
		free(cidx);
		continue;
	    }
	    if(*user_aborted)
	    {
		FreeMapObject(colormap);
		free(cidx);
		break;
	    }

	    /* Write the GIF screen descriptor for the first frame */
	    if(frame_num == 0)
	    {
		EGifPutScreenDesc(
		    ft,
		    width, height,	/* Screen width & height */
		    color_resolution,	/* Color resolution */
		    bg_color_num,	/* Background color number */
		    colormap		/* Screen colormap */
		);
	    }

	    /* Set the disposal method based on the existance of
	     * transparency
	     */
	    if(transparency)
		disposal_method = 2;	/* Clear with background */
	    else
		disposal_method = 1;	/* Do not clear */

	    /* Write the graphic control */
	    if(transparency || user_input ||
	       (disposal_method != 0) || (delay > 0l)
	    )
	    {
		const u_int8_t	transparency_flag = (1 << 0),
				user_input_flag = (1 << 1),
				disposal_method_mask = (1 << 0) |
				    (1 << 1) | (1 << 2);
		const u_int8_t buf[] = {
		    /* Flags */
		    (transparency ? transparency_flag : 0) |
		    (user_input ? user_input_flag : 0) |
 (u_int8_t)((disposal_method & disposal_method_mask) << 2),
		    /* Delay in 100th of a second */
		    (u_int8_t)(delay / 10l),
		    /* Delay time cntd */
		    0x00,
		    /* Transparent color number */
		    (u_int8_t)trans_color_num,
	        };
		EGifPutExtension(
		    ft, GRAPHICS_EXT_FUNC_CODE,
		    sizeof(buf), buf
		);
	    }

	    /* Write the image descriptor */
	    EGifPutImageDesc(
		ft,
		0, 0,		/* Top & left */
		width, height,	/* Width & height */
		interlaced,	/* Interlace */
		/* Specify local colormap only if multiple frames */
		(nframes > 1) ? colormap : NULL
	    );

	    /* Delete the GIF colormap */
	    FreeMapObject(colormap);

	    /* Write the image data for this frame */
	    if(interlaced)
	    {
		const int	po = height,
				pm = 2 * height;
		int i, y, lines_written = 0;

		/* Interlace needs 4 passes on the image */
		for(i = 0; i < 4; i++)
		{
		    for(y = gif_interlace_offset[i];
			y < height;
			y += gif_interlace_jumps[i]
		    )
		    {
			EGifPutLine(
			    ft,
			    (GifPixelType *)(cidx + (y * width)),
			    width
			);
			lines_written++;

			/* Report progress */
			if((progress_cb != NULL) && ((lines_written % 5) == 0))
			{
			    if(!progress_cb(
				client_data,
				po + lines_written, pm,
				width, height,
				bpl, bpp,
				rgba_cur
			    ))
			    {
				*user_aborted = 1;
				break;
			    }
			} 
		    }
		}
	    }
	    else
	    {
		const int	po = height,
				pm = 2 * height;
		int y;
		GifByteType *cidx_ptr = cidx;
		for(y = 0; y < height; y++)
		{
		    EGifPutLine(
			ft, (GifPixelType *)cidx_ptr, width
		    );
		    cidx_ptr += width;

		    /* Report progress */
		    if((progress_cb != NULL) && ((y % 5) == 0))
		    {
			if(!progress_cb(
			    client_data,
			    po + y, pm,
			    width, height,
			    bpl, bpp,
			    rgba_cur
			))
			{
			    *user_aborted = 1;
			    break;
			}
		    }
		}
	    }

	    /* Delete the GIF color index image data */
	    free(cidx);
	}

	/* Write comments */
	if(!(*user_aborted))
	{
	    if(!STRISEMPTY(comments))
		EGifPutExtension(
		    ft, COMMENT_EXT_FUNC_CODE,
		    STRLEN(comments) + 1, comments
		);
	}

	/* Close the GIF file */
	EGifCloseFile(ft);

	/* Set/restore the file permissions */
	chmod(filename, mode);

	if(*user_aborted)
	{
	    imgio_last_write_error = "User aborted operation";
	    return(-4);
	}
	else
	    return(0);
}

/*
 *	Writes the specified image data to a GIF file in greyscale.
 */
static int ImgWriteGIFFileRGBAToGreyscale(
	const char *filename,
	int width, int height,
	int bpl, int bpp,	/* bpp must be 4 */
	u_int8_t **rgba, unsigned long *delay_list,
	int nframes,
	const u_int8_t *bg_color,	/* 4 bytes in RGBA format */
	const char *comments,
	int interlaced,		/* 0 or 1 */
	int transparency,	/* 0 or 1 */
	void *client_data,
	int (*progress_cb)(
		void *,		/* Client data */
		int, int,	/* Progress current & end values */
		int, int,	/* Width & height */
		int, int,	/* Bytes per line & bytes per pixel */
		u_int8_t *	/* RGBA data */
	),
	int *user_aborted
)
{
	int	frame_num, disposal_method,
		color_resolution = 8,
		colormap_ncolors_set = 256,	/* Must be 256 or else fails */
		bg_color_num = 0,
		trans_color_num = -1;
	char user_input = 0;
	unsigned long delay;
	u_int8_t *rgba_cur;
	const mode_t mode = ImgGIFGetFileMode(filename);
	ColorMapObject *colormap;
	GifByteType *cidx;
	GifFileType *ft;

	/* Create the GIF colormap */
	colormap = (ColorMapObject *)malloc(sizeof(ColorMapObject));
	colormap->ColorCount = colormap_ncolors_set;
	colormap->BitsPerPixel = 8;
	colormap->Colors = (GifColorType *)calloc(
	    colormap->ColorCount, sizeof(GifColorType)
	);
	/* Generate greyscale colors */
	if(colormap->Colors != NULL)
	{
	    if(transparency)
	    {
		/* Generate 255 colors from black to white, leaving
		 * color 255 for the transparent pixel
		 */
		int i, m = colormap->ColorCount - 1, n = MAX(m - 1, 1);
		GifColorType *c;
		for(i = 0; i < m; i++)
		{
		    c = &colormap->Colors[i];
		    c->Red = c->Green = c->Blue =
			(GifByteType)(i * 0xff / n);
		}

		/* Set transparent color */
		trans_color_num = m;
		c = &colormap->Colors[trans_color_num];
		c->Red = c->Green = c->Blue = 0xff;
	    }
	    else
	    {
		/* Generate 256 colors from black to white */
		int i, m = colormap->ColorCount, n = MAX(m - 1, 1);
		GifColorType *c;
		for(i = 0; i < m; i++)
		{
		    c = &colormap->Colors[i];
		    c->Red = c->Green = c->Blue =
			(GifByteType)(i * 0xff / n);
		}
	    }
	}

	/* Create the GIF color index image data */
	cidx = (GifByteType *)malloc(
	    width * height * sizeof(GifByteType)
	);
	if(cidx == NULL)
	{
	    FreeMapObject(colormap);
	    imgio_last_write_error = "Memory allocation error";
	    return(-3);
	}

	/* Open the GIF file for writing */
	ft = EGifOpenFileName(filename, 0);
	if(ft == NULL)
	{
	    FreeMapObject(colormap);
	    free(cidx);
	    imgio_last_write_error = "Unable to open the file for writing";
	    return(-1);
	}
	 
	/* Iterate through each frame */
	for(frame_num = 0; frame_num < nframes; frame_num++)
	{
	    if(*user_aborted)
	       break;

	    rgba_cur = rgba[frame_num];
	    if(rgba_cur == NULL)
		continue;

	    /* Get the delay for this frame */
	    delay = (delay_list != NULL) ? delay_list[frame_num] : 0l;

	    /* Copy/convert the RGBA image data to the GIF color
	     * index image data in greyscale
	     */
	    if(transparency && (trans_color_num > -1))
	    {
		GifByteType	*cidx_ptr = cidx,
				*cidx_end = cidx_ptr +
				    (width * height);
		const u_int8_t *rgba_ptr = rgba_cur;
		while(cidx_ptr < cidx_end)
	        {
		    /* Solid? */
		    if(rgba_ptr[3] >= 0x80)
			*cidx_ptr++ = (GifByteType)(
		((rgba_ptr[0] + rgba_ptr[1] + rgba_ptr[2]) / 3) *
			    255 / 256
			);
		    else
		        *cidx_ptr++ = (GifByteType)trans_color_num;
		    rgba_ptr += 4;
	        }
	    }
	    else
	    {
		GifByteType	*cidx_ptr = cidx,
				*cidx_end = cidx_ptr +
				    (width * height);
		const u_int8_t *rgba_ptr = rgba_cur;
		while(cidx_ptr < cidx_end)
		{
		    *cidx_ptr++ = (GifByteType)(
		    (rgba_ptr[0] + rgba_ptr[1] + rgba_ptr[2]) / 3
		    );
		    rgba_ptr += 4;
		}
	    }

	    /* Write the GIF screen descriptor for the first frame */
	    if(frame_num == 0)
	    {
		EGifPutScreenDesc(
		    ft,
		    width, height,	/* Screen width & height */
		    color_resolution,	/* Color resolution */
		    bg_color_num,	/* Background color number */
		    colormap		/* Screen colormap */
		);
	    }

	    /* Set the disposal method based on the existance of
	     * transparency
	     */
	    if(transparency)
		disposal_method = 2;	/* Clear with background */
	    else
		disposal_method = 1;	/* Do not clear */

	    /* Need to write the graphic control? */
	    if(transparency || user_input ||
	       (disposal_method != 0) || (delay > 0l)
	    )
	    {
		const u_int8_t  transparency_flag = (1 << 0),
				user_input_flag = (1 << 1),
				disposal_method_mask = (1 << 0) |
				    (1 << 1) | (1 << 2);
		u_int8_t buf[] = {
		    /* Flags */
		    (transparency ? transparency_flag : 0) |
		    (user_input ? user_input_flag : 0) |
 (u_int8_t)((disposal_method & disposal_method_mask) << 2),
		    /* Delay in 100th of a second */
		    (u_int8_t)(delay / 10l),
		    /* Delay time cntd */
		    0x00,
		    /* Transparent color number */
		    (u_int8_t)trans_color_num,
		};
		EGifPutExtension(
		    ft, GRAPHICS_EXT_FUNC_CODE,
		    sizeof(buf), buf
		);
	    }

	    /* Write the image descriptor */
	    EGifPutImageDesc(
		ft,
		0, 0,		/* Top & left */
		width, height,	/* Width & height */
		interlaced,	/* Interlace */
		NULL		/* No local colormap */
	    );

	    /* Write image data */
	    if(interlaced)
	    {
		const int	po = 0,
				pm = height;
		int i, y, lines_written = 0;

		/* Interlace needs 4 passes on the image */
		for(i = 0; i < 4; i++)
		{
		    for(y = gif_interlace_offset[i];
			y < height;
			y += gif_interlace_jumps[i]
		    )
		    {
			EGifPutLine( 
			    ft,
			    (GifPixelType *)(cidx + (y * width)),
			    width
			);
			lines_written++;

			/* Report progress */
			if((progress_cb != NULL) && ((lines_written % 5) == 0))
			{
			    if(!progress_cb(
			        client_data,
				po + lines_written, pm,
				width, height,
				bpl, bpp,
				rgba_cur
			    ))
			    {
				*user_aborted = 1;
				break;
			    }
			}
		    }
		}
	    }
	    else
	    {
		const int	po = 0,
				pm = height;
 		int y;
		GifByteType *cidx_ptr = cidx;
		for(y = 0; y < height; y++)
		{
		    EGifPutLine(ft, (GifPixelType *)cidx_ptr, width);
		    cidx_ptr += width;

		    /* Report progress */
		    if((progress_cb != NULL) && ((y % 5) == 0))
		    {
			if(!progress_cb(
			    client_data,
			    po + y, pm,
			    width, height,
			    bpl, bpp,
			    rgba_cur
			))
			{
			    *user_aborted = 1;
			    break;
			}
		    }
		}
	    }
	}

	/* Write the comments */
	if(!STRISEMPTY(comments) && !(*user_aborted))
	    EGifPutExtension(
		ft, COMMENT_EXT_FUNC_CODE,
		STRLEN(comments) + 1, comments
	    );

	/* Close the GIF file */ 
	EGifCloseFile(ft);

	/* Set/restore file permissions */
	chmod(filename, mode);

	/* Delete the GIF colormap */
	FreeMapObject(colormap);

	/* Delete the GIF color index image data */
	free(cidx);

	if(*user_aborted)
	{
	    imgio_last_write_error = "User aborted operation";
	    return(-4);
	}
	else
	    return(0);
}

/*
 *	Writes the specified image data to a GIF file in black & white.
 */
static int ImgWriteGIFFileRGBAToBW(
	const char *filename,
	int width, int height,
	int bpl, int bpp,	/* bpp must be 4 */
	u_int8_t **rgba, unsigned long *delay_list,
	int nframes,
	const u_int8_t *bg_color,	/* 4 bytes in RGBA format */
	const char *comments,
	int interlaced,		/* 0 or 1 */
	int transparency,	/* 0 or 1 */
	void *client_data,
	int (*progress_cb)(
		void *,		/* Client data */
		int, int,	/* Progress current & end values */
		int, int,	/* Width & height */
		int, int,	/* Bytes per line & bytes per pixel */
		u_int8_t *	/* RGBA data */
	),
	int *user_aborted
)
{
	int	frame_num, disposal_method,
		color_resolution = 8,
		colormap_ncolors_set = 256,	/* Must be 256 or else fails */
		bg_color_num = 0,
		trans_color_num = -1;
	char user_input = 0;
	unsigned long delay;
	u_int8_t *rgba_cur;
	const mode_t mode = ImgGIFGetFileMode(filename);
	ColorMapObject *colormap;
	GifByteType *cidx;
	GifFileType *ft;

	/* Create the GIF colormap */
	colormap = (ColorMapObject *)malloc(sizeof(ColorMapObject));
	colormap->ColorCount = colormap_ncolors_set;
	colormap->BitsPerPixel = 8;
	colormap->Colors = (GifColorType *)calloc(
	    colormap->ColorCount, sizeof(GifColorType)
	);
	/* Generate black and white colors */
	if(colormap->Colors != NULL)
	{
	    if(transparency)
	    {
		/* Generate 3 colors; black, white, and the rest
		 * are black (including color 255 for the transparent
		 * pixel)
		 */

		/* Set color 0 to black */
		int i = 0, m = colormap->ColorCount;
		GifColorType *c = &colormap->Colors[i];
		c->Red = c->Green = c->Blue = (GifByteType)0x00;

		/* Set color 1 to white */
		i = 1;
		if(i < m)
		{
		    c = &colormap->Colors[i]; 
		    c->Red = c->Green = c->Blue = (GifByteType)0xff;
		}

		/* Set color 2 (transparent) to black */
		trans_color_num = i = 2;
		if(i < m)
		{
		    c = &colormap->Colors[i];
		    c->Red = c->Green = c->Blue = (GifByteType)0x00;
		}

		/* Set the rest of the colors to black */
		for(i++; i < m; i++)
		{
		    c = &colormap->Colors[i];
		    c->Red = c->Green = c->Blue = (GifByteType)0x00;
		}
	    }
	    else
	    {
		/* Set color 0 to black */
		int i = 0, m = colormap->ColorCount;
		GifColorType *c = &colormap->Colors[i];
		c->Red = c->Green = c->Blue = (GifByteType)0x00;

		/* Set color 1 to white */
		i = 1;
		if(i < m)
		{
		    c = &colormap->Colors[i];
		    c->Red = c->Green = c->Blue = (GifByteType)0xff;
		}

		/* Set the rest of the colors to black */
		for(i++; i < m; i++)
		{
		    c = &colormap->Colors[i];
		    c->Red = c->Green = c->Blue = (GifByteType)0x00;
		}
	    }
	}

	/* Create the GIF color index image data */
	cidx = (GifByteType *)malloc(
	    width * height * sizeof(GifByteType)
	);
	if(cidx == NULL)
	{
	    FreeMapObject(colormap);
	    imgio_last_write_error = "Memory allocation error";
	    return(-3);
	}

	/* Open the GIF file for writing */
	ft = EGifOpenFileName(filename, 0);
	if(ft == NULL)
	{
	    FreeMapObject(colormap);
	    free(cidx);
	    imgio_last_write_error = "Unable to open the file for writing";
	    return(-1);
	}

	/* Iterate through each frame */
	for(frame_num = 0; frame_num < nframes; frame_num++)
	{
	    if(*user_aborted)
	       break;

	    rgba_cur = rgba[frame_num];
	    if(rgba_cur == NULL)
		continue;

	    /* Get the delay for this frame */
	    delay = (delay_list != NULL) ? delay_list[frame_num] : 0l;

	    /* Copy/convert the RGBA image data to the GIF color
	     * index image data in black & white
	     */
	    if(transparency && (trans_color_num > -1))
	    {
	        GifByteType	*cidx_ptr = cidx,
				*cidx_end = cidx_ptr +
				    (width * height);
		const u_int8_t *rgba_ptr = rgba_cur;
		while(cidx_ptr < cidx_end)
		{
		    /* Solid? */
		    if(rgba_ptr[3] >= 0x80)
			*cidx_ptr++ = (GifByteType)(
	(((rgba_ptr[0] + rgba_ptr[1] + rgba_ptr[2]) / 3) >= 0x80) ?
			    1 : 0
			);
		    else
		        *cidx_ptr++ = (GifByteType)trans_color_num;
		    rgba_ptr += 4;
	        }
	    }
	    else
	    {
		GifByteType	*cidx_ptr = cidx,
				*cidx_end = cidx_ptr +
				    (width * height);
		const u_int8_t *rgba_ptr = rgba_cur;
	        while(cidx_ptr < cidx_end)
	        {
		    *cidx_ptr++ = (GifByteType)(
	(((rgba_ptr[0] + rgba_ptr[1] + rgba_ptr[2]) / 3) > 0x80) ?
			1 : 0
		    );
		    rgba_ptr += 4;
	        }
	    }

	    /* Write the GIF screen descriptor for the first frame */
	    if(frame_num == 0)
	    {
		EGifPutScreenDesc(
		    ft,
		    width, height,	/* Screen width & height */
		    color_resolution,	/* Color resolution */
		    bg_color_num,	/* Background color number */
		    colormap		/* Screen colormap */
		);
	    }

	    /* Set the disposal method based on the existance of
	     * transparency
	     */
	    if(transparency)
		disposal_method = 2;	/* Clear with background */
	    else
		disposal_method = 1;	/* Do not clear */

	    /* Need to write the graphic control? */
	    if(transparency || user_input ||
	       (disposal_method != 0) || (delay > 0l)
	    )
	    {
		const u_int8_t	transparency_flag = (1 << 0),
				user_input_flag = (1 << 1),
				disposal_method_mask = (1 << 0) |
				    (1 << 1) | (1 << 2);
		u_int8_t buf[] = {
		    /* Flags */
		    (transparency ? transparency_flag : 0) |
		    (user_input ? user_input_flag : 0) |
 (u_int8_t)((disposal_method & disposal_method_mask) << 2),
		    /* Delay in 100th of a second */
		    (u_int8_t)(delay / 10l),
		    /* Delay time cntd */
		    0x00,
		    /* Transparent color number */
		    (u_int8_t)trans_color_num,
		};
		EGifPutExtension(
		    ft, GRAPHICS_EXT_FUNC_CODE,
		    sizeof(buf), buf
		);
	    }

	    /* Write the image descriptor */
	    EGifPutImageDesc(
		ft,
		0, 0,		/* Top & left */
		width, height,	/* Width & height */
		interlaced,	/* Interlace */
		NULL		/* No local colormap */
	    );

	    /* Write image data */
	    if(interlaced)
	    {
		const int	po = 0,
				pm = height;
		int i, y, lines_written = 0;

		/* Interlace needs 4 passes on the image */
		for(i = 0; i < 4; i++)
		{
		    for(y = gif_interlace_offset[i];
		        y < height;
		        y += gif_interlace_jumps[i]
		    )
		    {
			EGifPutLine(
			    ft,
			    (GifPixelType *)(cidx + (y * width)),
			    width                               
			);
			lines_written++;

			/* Report progress */
			if((progress_cb != NULL) && ((lines_written % 5) == 0))
			{
			    if(!progress_cb(
				client_data,
				po + lines_written, pm,
				width, height,
				bpl, bpp,
				rgba_cur
			    ))
			    {
				*user_aborted = 1;
				break;
			    }
			}
		    }
		}
	    }
	    else
	    {
		const int	po = 0,
				pm = height;
		int y;
		GifByteType *cidx_ptr = cidx;
		for(y = 0; y < height; y++)
		{
		    EGifPutLine(ft, (GifPixelType *)cidx_ptr, width);
		    cidx_ptr += width;

		    /* Report progress */
		    if((progress_cb != NULL) && ((y % 5) == 0))
		    {
			if(!progress_cb(
			    client_data,
			    po + y, pm,
			    width, height,
			    bpl, bpp,
			    rgba_cur
			))
			{
			    *user_aborted = 1;
			    break;
			}
		    }
		}
	    }
	}

	/* Write comments */
	if(!STRISEMPTY(comments) && !(*user_aborted))
	    EGifPutExtension(
		ft, COMMENT_EXT_FUNC_CODE,
		STRLEN(comments) + 1, comments
	    );

	/* Close the GIF file */
	EGifCloseFile(ft);

	/* Set/restore file permissions */
	chmod(filename, mode);

	/* Delete the GIF colormap */
	FreeMapObject(colormap);

	/* Delete the GIF color index image data */
	free(cidx);

	if(*user_aborted)
	{
	    imgio_last_write_error = "User aborted operation";
	    return(-4);
	}
	else
	    return(0);
}

/*
 *	Writes the specified image data to a GIF file.
 *
 *	Return values:
 *
 *	0	Success
 *	-1	General error
 *	-2	Bad value
 *	-3	Systems error
 *	-4	User abort
 */
int ImgWriteGIFFileRGBA(
	const char *filename,
	int width, int height,
	int bpl, int bpp,	/* bpp must be 4 */
	u_int8_t **rgba, unsigned long *delay_list,
	int nframes,
	const u_int8_t *bg_color,	/* 4 bytes in RGBA format */
	const char *comments,
	int interlaced,		/* 0 or 1 */
	int format,		/* 0 = B&W
				 * 1 = Greyscale
				 * 2 = Color */
	int transparency,	/* 0 or 1 */
	void *client_data,
	int (*progress_cb)(
		void *,		/* Client data */
		int, int,	/* Progress current & end values */
		int, int,	/* Width & height */
		int, int,	/* Bytes per line & bytes per pixel */
		u_int8_t *	/* RGBA data */
	)
)
{
	int status;
	int user_aborted = 0;

	/* Reset global last write error message pointer */
	imgio_last_write_error = NULL;

	if(STRISEMPTY(filename) || (rgba == NULL) || (nframes <= 0) ||
	   (width <= 0) || (height <= 0) ||
	   (bpp != 4)
	)
	{
	    imgio_last_write_error = "Invalid value used to describe image";
	    return(-2);
	}

	/* Automatically calculate bytes per line? */
	if(bpl <= 0)
	    bpl = width * bpp;

	/* Report initial progress */
	if(progress_cb != NULL)
	{
	    if(!progress_cb(
		client_data,
		0, height,
		width, height,
		bpl, bpp,
		rgba[0]
	    ))
		user_aborted = 1;
	}

	/* Save by format */
	switch(format)
	{
	  case 2:
	    status = ImgWriteGIFFileRGBAToColor(
		filename,
		width, height, bpl, bpp,
		rgba, delay_list, nframes,
		bg_color,
		comments,
		interlaced,
		transparency,
		client_data, progress_cb, &user_aborted
	    );
	    break;

	  case 1:
	    status = ImgWriteGIFFileRGBAToGreyscale(
		filename,
		width, height, bpl, bpp,
		rgba, delay_list, nframes,
		bg_color,
		comments,
		interlaced,
		transparency,
		client_data, progress_cb, &user_aborted
	    );
	    break;

	  case 0:
	    status = ImgWriteGIFFileRGBAToBW(
		filename,
		width, height, bpl, bpp,
		rgba, delay_list, nframes,
		bg_color,
		comments,
		interlaced,
		transparency,
		client_data, progress_cb, &user_aborted
	    );
	    break;

	  default:
	    imgio_last_write_error = "Unsupported GIF color format";
	    status = -2;
	    break;
	}

	/* Report final progress */
	if((progress_cb != NULL) && !user_aborted)
	{
	    if(!progress_cb(
		client_data,
		height, height,
		width, height,
		bpl, bpp,
		rgba[nframes - 1]
	    ))
		user_aborted = 1;
	}

	if(user_aborted)
	{
	    imgio_last_write_error = "User aborted operation";
	    return(-4);
	}
	else
	    return(status);
}

#endif	/* HAVE_LIBGIF */
