#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 */ #if defined(GIFLIB_MAJOR) && GIFLIB_MAJOR >= 5 #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 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 !defined(GIFLIB_MAJOR) || GIFLIB_MAJOR < 5 static i_mutex_t mutex; #endif void i_init_gif(void) { #if !defined(GIFLIB_MAJOR) || GIFLIB_MAJOR < 5 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; 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; } DGifCloseFile(GifFile); mm_log((1, "i_readgif: image size exceeds limits\n")); return NULL; } im = i_img_8_new(GifFile->SWidth, GifFile->SHeight, 3); if (!im) { if (colour_table && *colour_table) { myfree(*colour_table); *colour_table = NULL; } DGifCloseFile(GifFile); return NULL; } Size = GifFile->SWidth * sizeof(GifPixelType); GifRow = mymalloc(Size); for (i = 0; i < GifFile->SWidth; i++) GifRow[i] = GifFile->SBackGroundColor; /* Scan the content of the GIF file and load the image(s) in: */ do { if (DGifGetRecordType(GifFile, &RecordType) == GIF_ERROR) { gif_push_error(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); DGifCloseFile(GifFile); 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); DGifCloseFile(GifFile); 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); DGifCloseFile(GifFile); return NULL; } Row = GifFile->Image.Top; /* Image Position relative to Screen. */ Col = GifFile->Image.Left; Width = GifFile->Image.Width; Height = GifFile->Image.Height; ImageNum++; mm_log((1,"i_readgif_low: Image %d at (%d, %d) [%dx%d]: \n",ImageNum, Col, Row, Width, Height)); if (GifFile->Image.Left + GifFile->Image.Width > GifFile->SWidth || GifFile->Image.Top + GifFile->Image.Height > GifFile->SHeight) { i_push_errorf(0, "Image %d is not confined to screen dimension, aborted.\n",ImageNum); if (colour_table && *colour_table) { myfree(*colour_table); *colour_table = NULL; } myfree(GifRow); i_img_destroy(im); DGifCloseFile(GifFile); return NULL; } if (GifFile->Image.Interlace) { for (Count = i = 0; i < 4; i++) for (j = Row + InterlacedOffset[i]; j < Row + Height; j += InterlacedJumps[i]) { Count++; if (DGifGetLine(GifFile, GifRow, Width) == GIF_ERROR) { gif_push_error(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); DGifCloseFile(GifFile); return NULL; } for (x = 0; x < Width; x++) { ColorMapEntry = &ColorMap->Colors[GifRow[x]]; col.rgb.r = ColorMapEntry->Red; col.rgb.g = ColorMapEntry->Green; col.rgb.b = ColorMapEntry->Blue; i_ppix(im,Col+x,j,&col); } } } else { for (i = 0; i < Height; i++) { if (DGifGetLine(GifFile, GifRow, Width) == GIF_ERROR) { gif_push_error(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); DGifCloseFile(GifFile); return NULL; } for (x = 0; x < Width; x++) { ColorMapEntry = &ColorMap->Colors[GifRow[x]]; col.rgb.r = ColorMapEntry->Red; col.rgb.g = ColorMapEntry->Green; col.rgb.b = ColorMapEntry->Blue; i_ppix(im, Col+x, Row, &col); } Row++; } } break; case EXTENSION_RECORD_TYPE: /* Skip any extension blocks in file: */ if (DGifGetExtension(GifFile, &ExtCode, &Extension) == GIF_ERROR) { gif_push_error(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); DGifCloseFile(GifFile); 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); DGifCloseFile(GifFile); return NULL; } } break; case TERMINATE_RECORD_TYPE: break; default: /* Should be traps by DGifGetRecordType. */ break; } } while (RecordType != TERMINATE_RECORD_TYPE); myfree(GifRow); if (DGifCloseFile(GifFile) == GIF_ERROR) { gif_push_error(myGifError(GifFile)); 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 */ 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); DGifCloseFile(GifFile); myfree(GifRow); if (comment) myfree(comment); return NULL; } switch (RecordType) { case IMAGE_DESC_RECORD_TYPE: if (DGifGetImageDesc(GifFile) == GIF_ERROR) { gif_push_error(myGifError(GifFile)); i_push_error(0, "Unable to get image descriptor"); free_images(results, *count); DGifCloseFile(GifFile); myfree(GifRow); if (comment) myfree(comment); return NULL; } Width = GifFile->Image.Width; Height = GifFile->Image.Height; if (page == -1 || page == ImageNum) { if (( ColorMap = (GifFile->Image.ColorMap ? GifFile->Image.ColorMap : GifFile->SColorMap) )) { mm_log((1, "Adding local colormap\n")); ColorMapSize = ColorMap->ColorCount; } else { /* No colormap and we are about to read in the image - abandon for now */ mm_log((1, "Going in with no colormap\n")); i_push_error(0, "Image does not have a local or a global color map"); free_images(results, *count); DGifCloseFile(GifFile); myfree(GifRow); if (comment) myfree(comment); return NULL; } channels = 3; if (got_gce && trans_index >= 0) channels = 4; if (!i_int_check_image_file_limits(Width, Height, channels, sizeof(i_sample_t))) { free_images(results, *count); mm_log((1, "i_readgif: image size exceeds limits\n")); DGifCloseFile(GifFile); myfree(GifRow); if (comment) myfree(comment); return NULL; } img = i_img_pal_new(Width, Height, channels, 256); if (!img) { free_images(results, *count); DGifCloseFile(GifFile); if (comment) myfree(comment); myfree(GifRow); return NULL; } /* populate the palette of the new image */ mm_log((1, "ColorMapSize %d\n", ColorMapSize)); for (i = 0; i < ColorMapSize; ++i) { i_color col; col.rgba.r = ColorMap->Colors[i].Red; col.rgba.g = ColorMap->Colors[i].Green; col.rgba.b = ColorMap->Colors[i].Blue; if (channels == 4 && trans_index == i) col.rgba.a = 0; else col.rgba.a = 255; i_addcolors(img, &col, 1); } image_colors = ColorMapSize; ++*count; if (*count > result_alloc) { if (result_alloc == 0) { result_alloc = 5; results = mymalloc(result_alloc * sizeof(i_img *)); } else { /* myrealloc never fails (it just dies if it can't allocate) */ result_alloc *= 2; results = myrealloc(results, result_alloc * sizeof(i_img *)); } } results[*count-1] = img; i_tags_set(&img->tags, "i_format", "gif", -1); i_tags_setn(&img->tags, "gif_left", GifFile->Image.Left); /**(char *)0 = 1;*/ i_tags_setn(&img->tags, "gif_top", GifFile->Image.Top); i_tags_setn(&img->tags, "gif_interlace", GifFile->Image.Interlace); i_tags_setn(&img->tags, "gif_screen_width", GifFile->SWidth); i_tags_setn(&img->tags, "gif_screen_height", GifFile->SHeight); i_tags_setn(&img->tags, "gif_colormap_size", ColorMapSize); if (GifFile->SColorMap && !GifFile->Image.ColorMap) { i_tags_setn(&img->tags, "gif_background", GifFile->SBackGroundColor); } if (GifFile->Image.ColorMap) { i_tags_setn(&img->tags, "gif_localmap", 1); } if (got_gce) { if (trans_index >= 0) { i_color trans; i_tags_setn(&img->tags, "gif_trans_index", trans_index); i_getcolors(img, trans_index, &trans, 1); i_tags_set_color(&img->tags, "gif_trans_color", 0, &trans); } i_tags_setn(&img->tags, "gif_delay", gif_delay); i_tags_setn(&img->tags, "gif_user_input", user_input); i_tags_setn(&img->tags, "gif_disposal", disposal); } got_gce = 0; if (got_ns_loop) i_tags_setn(&img->tags, "gif_loop", ns_loop); if (comment) { i_tags_set(&img->tags, "gif_comment", comment, strlen(comment)); myfree(comment); comment = NULL; } mm_log((1,"i_readgif_multi_low: Image %d at (%d, %d) [%dx%d]: \n", ImageNum, GifFile->Image.Left, GifFile->Image.Top, Width, Height)); if (GifFile->Image.Left + GifFile->Image.Width > GifFile->SWidth || GifFile->Image.Top + GifFile->Image.Height > GifFile->SHeight) { i_push_errorf(0, "Image %d is not confined to screen dimension, aborted.\n",ImageNum); free_images(results, *count); DGifCloseFile(GifFile); myfree(GifRow); if (comment) myfree(comment); return(0); } if (GifFile->Image.Interlace) { for (Count = i = 0; i < 4; i++) { for (j = InterlacedOffset[i]; j < Height; j += InterlacedJumps[i]) { Count++; if (DGifGetLine(GifFile, GifRow, Width) == GIF_ERROR) { gif_push_error(myGifError(GifFile)); i_push_error(0, "Reading GIF line"); free_images(results, *count); DGifCloseFile(GifFile); myfree(GifRow); if (comment) myfree(comment); return NULL; } /* range check the scanline if needed */ if (image_colors != 256) { int x; for (x = 0; x < Width; ++x) { while (GifRow[x] >= image_colors) { /* expand the palette since a palette index is too big */ i_addcolors(img, &black, 1); ++image_colors; } } } i_ppal(img, 0, Width, j, GifRow); } } } else { for (i = 0; i < Height; i++) { if (DGifGetLine(GifFile, GifRow, Width) == GIF_ERROR) { gif_push_error(myGifError(GifFile)); i_push_error(0, "Reading GIF line"); free_images(results, *count); DGifCloseFile(GifFile); myfree(GifRow); if (comment) myfree(comment); return NULL; } /* range check the scanline if needed */ if (image_colors != 256) { int x; for (x = 0; x < Width; ++x) { while (GifRow[x] >= image_colors) { /* expand the palette since a palette index is too big */ i_addcolors(img, &black, 1); ++image_colors; } } } i_ppal(img, 0, Width, i, GifRow); } } /* must be only one image wanted and that was it */ if (page != -1) { myfree(GifRow); DGifCloseFile(GifFile); if (comment) myfree(comment); return results; } } else { /* skip the image */ /* whether interlaced or not, it has the same number of lines */ /* giflib does't have an interface to skip the image data */ for (i = 0; i < Height; i++) { if (DGifGetLine(GifFile, GifRow, Width) == GIF_ERROR) { gif_push_error(myGifError(GifFile)); i_push_error(0, "Reading GIF line"); free_images(results, *count); myfree(GifRow); DGifCloseFile(GifFile); if (comment) myfree(comment); return NULL; } } /* kill the comment so we get the right comment for the page */ if (comment) { myfree(comment); comment = NULL; } } ImageNum++; break; case EXTENSION_RECORD_TYPE: /* Skip any extension blocks in file: */ if (DGifGetExtension(GifFile, &ExtCode, &Extension) == GIF_ERROR) { gif_push_error(myGifError(GifFile)); i_push_error(0, "Reading extension record"); free_images(results, *count); myfree(GifRow); DGifCloseFile(GifFile); if (comment) myfree(comment); return NULL; } /* possibly this should be an error, but "be liberal in what you accept" */ if (!Extension) break; if (ExtCode == 0xF9) { got_gce = 1; if (Extension[1] & 1) trans_index = Extension[4]; else trans_index = -1; gif_delay = Extension[2] + 256 * Extension[3]; user_input = (Extension[1] & 2) != 0; disposal = (Extension[1] >> 2) & 7; } if (ExtCode == 0xFF && *Extension == 11) { if (memcmp(Extension+1, "NETSCAPE2.0", 11) == 0) { if (DGifGetExtensionNext(GifFile, &Extension) == GIF_ERROR) { gif_push_error(myGifError(GifFile)); i_push_error(0, "reading loop extension"); free_images(results, *count); myfree(GifRow); DGifCloseFile(GifFile); if (comment) myfree(comment); return NULL; } if (Extension && *Extension == 3) { got_ns_loop = 1; ns_loop = Extension[2] + 256 * Extension[3]; } } } else if (ExtCode == 0xFE) { /* while it's possible for a GIF file to contain more than one comment, I'm only implementing a single comment per image, with the comment saved into the following image. If someone wants more than that they can implement it. I also don't handle comments that take more than one block. */ if (!comment) { comment = mymalloc(*Extension+1); memcpy(comment, Extension+1, *Extension); comment[*Extension] = '\0'; } } while (Extension != NULL) { if (DGifGetExtensionNext(GifFile, &Extension) == GIF_ERROR) { gif_push_error(myGifError(GifFile)); i_push_error(0, "reading next block of extension"); free_images(results, *count); myfree(GifRow); DGifCloseFile(GifFile); if (comment) myfree(comment); return NULL; } } break; case TERMINATE_RECORD_TYPE: break; default: /* Should be trapped by DGifGetRecordType. */ break; } } while (RecordType != TERMINATE_RECORD_TYPE); if (comment) { if (*count) { i_tags_set(&(results[*count-1]->tags), "gif_comment", comment, strlen(comment)); } myfree(comment); } myfree(GifRow); if (DGifCloseFile(GifFile) == GIF_ERROR) { gif_push_error(myGifError(GifFile)); 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")); EGifCloseFile(gf); return 0; } } } } else { int y; for (y = 0; y < img->ysize; ++y) { if (EGifPutLine(gf, data, img->xsize) == GIF_ERROR) { gif_push_error(myGifError(gf)); i_push_error(0, "Could not save image data:"); mm_log((1, "Error in EGifPutLine\n")); EGifCloseFile(gf); return 0; } data += img->xsize; } } return 1; } /* =item do_gce(GifFileType *gf, int index, i_gif_opts *opts, int want_trans, int trans_index) Internal. Writes the GIF graphics control extension, if necessary. Returns non-zero on success. =cut */ static int do_gce(GifFileType *gf, i_img *img, int want_trans, int trans_index) { unsigned char gce[4] = {0}; int want_gce = 0; int delay; int user_input; int disposal_method; if (want_trans) { gce[0] |= 1; gce[3] = trans_index; ++want_gce; } if (i_tags_get_int(&img->tags, "gif_delay", 0, &delay)) { gce[1] = delay % 256; gce[2] = delay / 256; ++want_gce; } if (i_tags_get_int(&img->tags, "gif_user_input", 0, &user_input) && user_input) { gce[0] |= 2; ++want_gce; } if (i_tags_get_int(&img->tags, "gif_disposal", 0, &disposal_method)) { gce[0] |= (disposal_method & 3) << 2; ++want_gce; } if (want_gce) { if (EGifPutExtension(gf, 0xF9, sizeof(gce), gce) == GIF_ERROR) { gif_push_error(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 defined(GIFLIB_MAJOR) && GIFLIB_MAJOR >= 5 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 defined GIFLIB_MAJOR && GIFLIB_MAJOR >= 5 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; int anylocal; i_img **glob_imgs; /* images that will use the global color map */ int glob_img_count; i_color *orig_colors = quant->mc_colors; i_color *glob_colors = NULL; int glob_color_count = 0; int glob_want_trans; int glob_paletted = 0; /* the global map was made from the image palettes */ int colors_paletted = 0; int want_trans = 0; int interlace; int gif_background; mm_log((1, "i_writegif_low(quant %p, gf %p, imgs %p, count %d)\n", quant, gf, imgs, count)); /* *((char *)0) = 1; */ /* used to break into the debugger */ if (count <= 0) { i_push_error(0, "No images provided to write"); return 0; /* what are you smoking? */ } /* sanity is nice */ if (quant->mc_size > 256) quant->mc_size = 256; if (quant->mc_count > quant->mc_size) quant->mc_count = quant->mc_size; if (!i_tags_get_int(&imgs[0]->tags, "gif_screen_width", 0, &scrw)) scrw = 0; if (!i_tags_get_int(&imgs[0]->tags, "gif_screen_height", 0, &scrh)) 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"); return 0; } 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"); return 0; } 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) { myfree(glob_colors); myfree(localmaps); myfree(glob_imgs); quant->mc_colors = orig_colors; EGifCloseFile(gf); mm_log((1, "Error in MakeMapObject")); return 0; } color_bits = 1; if (anylocal) { /* since we don't know how big some the local palettes could be we need to base the bits on the maximum number of colors */ while (orig_size > (1 << color_bits)) ++color_bits; } else { int count = quant->mc_count; if (want_trans) ++count; while (count > (1 << color_bits)) ++color_bits; } if (EGifPutScreenDesc(gf, scrw, scrh, color_bits, gif_background, map) == GIF_ERROR) { myfree(glob_colors); myfree(localmaps); myfree(glob_imgs); quant->mc_colors = orig_colors; gif_push_error(myGifError(gf)); i_push_error(0, "Could not save screen descriptor"); FreeMapObject(map); myfree(result); EGifCloseFile(gf); mm_log((1, "Error in EGifPutScreenDesc.")); return 0; } FreeMapObject(map); if (!i_tags_get_int(&imgs[0]->tags, "gif_left", 0, &posx)) posx = 0; if (!i_tags_get_int(&imgs[0]->tags, "gif_top", 0, &posy)) posy = 0; if (!localmaps[0]) { map = NULL; colors_paletted = glob_paletted; } else { /* if this image has a global map the colors in quant don't belong to this image, so build a palette */ if (glob_colors) { /* generate the local map for this image */ quant->mc_colors = orig_colors; quant->mc_size = orig_size; quant->mc_count = orig_count; want_trans = quant->transp != tr_none && imgs[0]->channels == 4; /* if the caller gives us too many colours we can't do transparency */ if (want_trans && quant->mc_count == 256) want_trans = 0; /* if they want transparency but give us a big size, make it smaller to give room for a transparency colour */ if (want_trans && quant->mc_size == 256) --quant->mc_size; 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) { myfree(glob_colors); myfree(localmaps); myfree(glob_imgs); EGifCloseFile(gf); quant->mc_colors = orig_colors; mm_log((1, "Error in MakeMapObject")); return 0; } } else { /* the map we wrote was the map for this image - don't set the local map */ map = NULL; } } if (colors_paletted) result = quant_paletted(quant, imgs[0]); else result = i_quant_translate(quant, imgs[0]); if (!result) { myfree(glob_colors); myfree(localmaps); myfree(glob_imgs); quant->mc_colors = orig_colors; EGifCloseFile(gf); return 0; } if (want_trans) { i_quant_transparent(quant, result, imgs[0], quant->mc_count); trans_index = quant->mc_count; } if (!do_ns_loop(gf, imgs[0])) { myfree(glob_colors); myfree(localmaps); myfree(glob_imgs); quant->mc_colors = orig_colors; return 0; } if (!do_gce(gf, imgs[0], want_trans, trans_index)) { myfree(glob_colors); myfree(localmaps); myfree(glob_imgs); quant->mc_colors = orig_colors; myfree(result); EGifCloseFile(gf); return 0; } if (!do_comments(gf, imgs[0])) { myfree(glob_colors); myfree(localmaps); myfree(glob_imgs); quant->mc_colors = orig_colors; myfree(result); EGifCloseFile(gf); return 0; } if (!i_tags_get_int(&imgs[0]->tags, "gif_interlace", 0, &interlace)) interlace = 0; if (EGifPutImageDesc(gf, posx, posy, imgs[0]->xsize, imgs[0]->ysize, interlace, map) == GIF_ERROR) { myfree(glob_colors); myfree(localmaps); myfree(glob_imgs); quant->mc_colors = orig_colors; gif_push_error(myGifError(gf)); i_push_error(0, "Could not save image descriptor"); EGifCloseFile(gf); mm_log((1, "Error in EGifPutImageDesc.")); return 0; } if (map) FreeMapObject(map); if (!do_write(gf, interlace, imgs[0], result)) { myfree(glob_colors); myfree(localmaps); myfree(glob_imgs); quant->mc_colors = orig_colors; EGifCloseFile(gf); myfree(result); return 0; } myfree(result); /* that first awful image is out of the way, do the rest */ for (imgn = 1; imgn < count; ++imgn) { if (localmaps[imgn]) { quant->mc_colors = orig_colors; quant->mc_count = orig_count; quant->mc_size = orig_size; want_trans = quant->transp != tr_none && imgs[imgn]->channels == 4; /* if the caller gives us too many colours we can't do transparency */ if (want_trans && quant->mc_count == 256) want_trans = 0; /* if they want transparency but give us a big size, make it smaller to give room for a transparency colour */ if (want_trans && quant->mc_size == 256) --quant->mc_size; if (has_common_palette(imgs+imgn, 1, quant)) { result = quant_paletted(quant, imgs[imgn]); } else { i_quant_makemap(quant, imgs+imgn, 1); result = i_quant_translate(quant, imgs[imgn]); } if (!result) { myfree(glob_colors); myfree(localmaps); myfree(glob_imgs); quant->mc_colors = orig_colors; EGifCloseFile(gf); mm_log((1, "error in i_quant_translate()")); return 0; } if (want_trans) { i_quant_transparent(quant, result, imgs[imgn], quant->mc_count); trans_index = quant->mc_count; } if ((map = make_gif_map(quant, imgs[imgn], want_trans)) == NULL) { myfree(glob_colors); myfree(localmaps); myfree(glob_imgs); quant->mc_colors = orig_colors; myfree(result); EGifCloseFile(gf); mm_log((1, "Error in MakeMapObject.")); return 0; } } else { quant->mc_colors = glob_colors; quant->mc_count = glob_color_count; if (glob_paletted) result = quant_paletted(quant, imgs[imgn]); else result = i_quant_translate(quant, imgs[imgn]); want_trans = glob_want_trans && imgs[imgn]->channels == 4; if (want_trans) { i_quant_transparent(quant, result, imgs[imgn], quant->mc_count); trans_index = quant->mc_count; } map = NULL; } if (!do_gce(gf, imgs[imgn], want_trans, trans_index)) { myfree(glob_colors); myfree(localmaps); myfree(glob_imgs); quant->mc_colors = orig_colors; myfree(result); EGifCloseFile(gf); return 0; } if (!do_comments(gf, imgs[imgn])) { myfree(glob_colors); myfree(localmaps); myfree(glob_imgs); quant->mc_colors = orig_colors; myfree(result); EGifCloseFile(gf); return 0; } if (!i_tags_get_int(&imgs[imgn]->tags, "gif_left", 0, &posx)) posx = 0; if (!i_tags_get_int(&imgs[imgn]->tags, "gif_top", 0, &posy)) posy = 0; if (!i_tags_get_int(&imgs[imgn]->tags, "gif_interlace", 0, &interlace)) interlace = 0; if (EGifPutImageDesc(gf, posx, posy, imgs[imgn]->xsize, imgs[imgn]->ysize, interlace, map) == GIF_ERROR) { myfree(glob_colors); myfree(localmaps); myfree(glob_imgs); quant->mc_colors = orig_colors; gif_push_error(myGifError(gf)); i_push_error(0, "Could not save image descriptor"); myfree(result); if (map) FreeMapObject(map); EGifCloseFile(gf); mm_log((1, "Error in EGifPutImageDesc.")); return 0; } if (map) FreeMapObject(map); if (!do_write(gf, interlace, imgs[imgn], result)) { myfree(glob_colors); myfree(localmaps); myfree(glob_imgs); quant->mc_colors = orig_colors; EGifCloseFile(gf); myfree(result); return 0; } myfree(result); } if (EGifCloseFile(gf) == GIF_ERROR) { myfree(glob_colors); myfree(localmaps); myfree(glob_imgs); gif_push_error(myGifError(gf)); i_push_error(0, "Could not close GIF file"); mm_log((1, "Error in EGifCloseFile\n")); return 0; } if (glob_colors) { int i; for (i = 0; i < glob_color_count; ++i) orig_colors[i] = glob_colors[i]; } myfree(glob_colors); myfree(localmaps); myfree(glob_imgs); quant->mc_colors = orig_colors; return 1; } static int io_glue_write_cb(GifFileType *gft, const GifByteType *data, int length) { io_glue *ig = (io_glue *)gft->UserData; return 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 defined(GIFLIB_MAJOR) && GIFLIB_MAJOR >= 5 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 */