16 imgif.c - read and write gif files for Imager
25 int max_colours; // number of bits per colour
26 int pixdev; // how much noise to add
27 i_color fixed[N]; // fixed palette entries
28 int fixedlen; // number of fixed colours
29 int success; // non-zero on success
30 char *data; // a GIF file in memory
31 int length; // how big data is
32 int reader(char *, char *, int, int);
33 int writer(char *, char *, int);
34 char *userdata; // user's data, whatever it is
38 img = i_readgif(fd, &colour_table, &colours);
39 success = i_writegif(img, fd, max_colours, pixdev, fixedlen, fixed);
40 success = i_writegifmc(img, fd, max_colours);
41 img = i_readgif_scalar(data, length, &colour_table, &colours);
42 img = i_readgif_callback(cb, userdata, &colour_table, &colours);
43 success = i_writegif_gen(&quant, fd, imgs, count, &opts);
44 success = i_writegif_callback(&quant, writer, userdata, maxlength,
49 This source file provides the C level interface to reading and writing
52 This has been tested with giflib 3 and 4, though you lose the callback
53 functionality with giflib3.
63 #define IMGIFLIB_API_VERSION (GIFLIB_MAJOR * 100 + GIFLIB_MINOR)
65 /* only matters for pre-5.0 which we either reject, or which contains
66 no significant API changes */
67 #define IMGIFLIB_API_VERSION 0
70 #if IMGIFLIB_API_VERSION >= 500
71 #define POST_SET_VERSION
72 #define myDGifOpen(userPtr, readFunc, Error) DGifOpen((userPtr), (readFunc), (Error))
73 #define myEGifOpen(userPtr, readFunc, Error) EGifOpen((userPtr), (readFunc), (Error))
74 #define myGifError(gif) ((gif)->Error)
75 #define MakeMapObject GifMakeMapObject
76 #define FreeMapObject GifFreeMapObject
77 #define gif_mutex_lock(mutex)
78 #define gif_mutex_unlock(mutex)
80 #define PRE_SET_VERSION
82 myDGifOpen(void *userPtr, InputFunc readFunc, int *error) {
83 GifFileType *result = DGifOpen(userPtr, readFunc);
85 *error = GifLastError();
90 myEGifOpen(void *userPtr, OutputFunc outputFunc, int *error) {
91 GifFileType *result = EGifOpen(userPtr, outputFunc);
93 *error = GifLastError();
97 #define myGifError(gif) GifLastError()
98 #define gif_mutex_lock(mutex) i_mutex_lock(mutex)
99 #define gif_mutex_unlock(mutex) i_mutex_unlock(mutex)
103 #if IMGIFLIB_API_VERSION >= 501
104 #define myDGifCloseFile(gif, perror) (DGifCloseFile((gif), (perror)))
105 #define myEGifCloseFile(gif, perror) (EGifCloseFile((gif), (perror)))
108 myDGifCloseFile(GifFileType *GifFile, int *ErrorCode) {
109 int result = DGifCloseFile(GifFile);
110 if (result == GIF_ERROR) {
112 *ErrorCode = myGifError(GifFile);
113 free(GifFile->Private);
121 myEGifCloseFile(GifFileType *GifFile, int *ErrorCode) {
122 int result = EGifCloseFile(GifFile);
123 if (result == GIF_ERROR) {
125 *ErrorCode = myGifError(GifFile);
126 free(GifFile->Private);
134 static char const *gif_error_msg(int code);
135 static void gif_push_error(int code);
137 /* Make some variables global, so we could access them faster: */
140 InterlacedOffset[] = { 0, 4, 2, 1 }, /* The way Interlaced image should. */
141 InterlacedJumps[] = { 8, 8, 4, 2 }; /* be read - offsets and jumps... */
143 #if IMGIFLIB_API_VERSION < 500
144 static i_mutex_t mutex;
149 #if IMGIFLIB_API_VERSION < 500
150 mutex = i_mutex_new();
156 i_colortable_copy(int **colour_table, int *colours, ColorMapObject *colourmap) {
157 GifColorType *mapentry;
159 int colourmapsize = colourmap->ColorCount;
161 if(colours) *colours = colourmapsize;
162 if(!colour_table) return;
164 *colour_table = mymalloc(sizeof(int) * colourmapsize * 3);
165 memset(*colour_table, 0, sizeof(int) * colourmapsize * 3);
167 for(q=0; q<colourmapsize; q++) {
168 mapentry = &colourmap->Colors[q];
169 (*colour_table)[q*3 + 0] = mapentry->Red;
170 (*colour_table)[q*3 + 1] = mapentry->Green;
171 (*colour_table)[q*3 + 2] = mapentry->Blue;
175 #ifdef GIF_LIB_VERSION
178 char gif_version_str[] = GIF_LIB_VERSION;
181 i_giflib_version(void) {
182 const char *p = gif_version_str;
184 while (*p && (*p < '0' || *p > '9'))
190 return strtod(p, NULL);
196 i_giflib_version(void) {
197 return GIFLIB_MAJOR + GIFLIB_MINOR * 0.1;
203 =item i_readgif_low(GifFileType *GifFile, int **colour_table, int *colours)
205 Internal. Low-level function for reading a GIF file. The caller must
206 create the appropriate GifFileType object and pass it in.
212 i_readgif_low(GifFileType *GifFile, int **colour_table, int *colours) {
214 int i, j, Size, Row, Col, Width, Height, ExtCode, Count, x;
215 int cmapcnt = 0, ImageNum = 0;
216 ColorMapObject *ColorMap;
218 GifRecordType RecordType;
219 GifByteType *Extension;
222 GifColorType *ColorMapEntry;
226 mm_log((1,"i_readgif_low(GifFile %p, colour_table %p, colours %p)\n", GifFile, colour_table, colours));
228 /* it's possible that the caller has called us with *colour_table being
229 non-NULL, but we check that to see if we need to free an allocated
230 colour table on error.
232 if (colour_table) *colour_table = NULL;
234 ColorMap = (GifFile->Image.ColorMap ? GifFile->Image.ColorMap : GifFile->SColorMap);
237 i_colortable_copy(colour_table, colours, ColorMap);
241 if (!i_int_check_image_file_limits(GifFile->SWidth, GifFile->SHeight, 3, sizeof(i_sample_t))) {
242 if (colour_table && *colour_table) {
243 myfree(*colour_table);
244 *colour_table = NULL;
246 (void)myDGifCloseFile(GifFile, NULL);
247 mm_log((1, "i_readgif: image size exceeds limits\n"));
251 im = i_img_8_new(GifFile->SWidth, GifFile->SHeight, 3);
253 if (colour_table && *colour_table) {
254 myfree(*colour_table);
255 *colour_table = NULL;
257 (void)myDGifCloseFile(GifFile, NULL);
261 Size = GifFile->SWidth * sizeof(GifPixelType);
263 GifRow = mymalloc(Size);
265 for (i = 0; i < GifFile->SWidth; i++) GifRow[i] = GifFile->SBackGroundColor;
267 /* Scan the content of the GIF file and load the image(s) in: */
269 if (DGifGetRecordType(GifFile, &RecordType) == GIF_ERROR) {
270 gif_push_error(myGifError(GifFile));
271 i_push_error(0, "Unable to get record type");
272 if (colour_table && *colour_table) {
273 myfree(*colour_table);
274 *colour_table = NULL;
278 (void)myDGifCloseFile(GifFile, NULL);
282 switch (RecordType) {
283 case IMAGE_DESC_RECORD_TYPE:
284 if (DGifGetImageDesc(GifFile) == GIF_ERROR) {
285 gif_push_error(myGifError(GifFile));
286 i_push_error(0, "Unable to get image descriptor");
287 if (colour_table && *colour_table) {
288 myfree(*colour_table);
289 *colour_table = NULL;
293 (void)myDGifCloseFile(GifFile, NULL);
297 if (( ColorMap = (GifFile->Image.ColorMap ? GifFile->Image.ColorMap : GifFile->SColorMap) )) {
298 mm_log((1, "Adding local colormap\n"));
300 i_colortable_copy(colour_table, colours, ColorMap);
304 /* No colormap and we are about to read in the image - abandon for now */
305 mm_log((1, "Going in with no colormap\n"));
306 i_push_error(0, "Image does not have a local or a global color map");
307 /* we can't have allocated a colour table here */
310 (void)myDGifCloseFile(GifFile, NULL);
314 Row = GifFile->Image.Top; /* Image Position relative to Screen. */
315 Col = GifFile->Image.Left;
316 Width = GifFile->Image.Width;
317 Height = GifFile->Image.Height;
319 mm_log((1,"i_readgif_low: Image %d at (%d, %d) [%dx%d]: \n",ImageNum, Col, Row, Width, Height));
321 if (GifFile->Image.Left + GifFile->Image.Width > GifFile->SWidth ||
322 GifFile->Image.Top + GifFile->Image.Height > GifFile->SHeight) {
323 i_push_errorf(0, "Image %d is not confined to screen dimension, aborted.\n",ImageNum);
324 if (colour_table && *colour_table) {
325 myfree(*colour_table);
326 *colour_table = NULL;
330 (void)myDGifCloseFile(GifFile, NULL);
333 if (GifFile->Image.Interlace) {
335 for (Count = i = 0; i < 4; i++) for (j = Row + InterlacedOffset[i]; j < Row + Height; j += InterlacedJumps[i]) {
337 if (DGifGetLine(GifFile, GifRow, Width) == GIF_ERROR) {
338 gif_push_error(myGifError(GifFile));
339 i_push_error(0, "Reading GIF line");
340 if (colour_table && *colour_table) {
341 myfree(*colour_table);
342 *colour_table = NULL;
346 (void)myDGifCloseFile(GifFile, NULL);
350 for (x = 0; x < Width; x++) {
351 ColorMapEntry = &ColorMap->Colors[GifRow[x]];
352 col.rgb.r = ColorMapEntry->Red;
353 col.rgb.g = ColorMapEntry->Green;
354 col.rgb.b = ColorMapEntry->Blue;
355 i_ppix(im,Col+x,j,&col);
361 for (i = 0; i < Height; i++) {
362 if (DGifGetLine(GifFile, GifRow, Width) == GIF_ERROR) {
363 gif_push_error(myGifError(GifFile));
364 i_push_error(0, "Reading GIF line");
365 if (colour_table && *colour_table) {
366 myfree(*colour_table);
367 *colour_table = NULL;
371 (void)myDGifCloseFile(GifFile, NULL);
375 for (x = 0; x < Width; x++) {
376 ColorMapEntry = &ColorMap->Colors[GifRow[x]];
377 col.rgb.r = ColorMapEntry->Red;
378 col.rgb.g = ColorMapEntry->Green;
379 col.rgb.b = ColorMapEntry->Blue;
380 i_ppix(im, Col+x, Row, &col);
386 case EXTENSION_RECORD_TYPE:
387 /* Skip any extension blocks in file: */
388 if (DGifGetExtension(GifFile, &ExtCode, &Extension) == GIF_ERROR) {
389 gif_push_error(myGifError(GifFile));
390 i_push_error(0, "Reading extension record");
391 if (colour_table && *colour_table) {
392 myfree(*colour_table);
393 *colour_table = NULL;
397 (void)myDGifCloseFile(GifFile, NULL);
400 while (Extension != NULL) {
401 if (DGifGetExtensionNext(GifFile, &Extension) == GIF_ERROR) {
402 gif_push_error(myGifError(GifFile));
403 i_push_error(0, "reading next block of extension");
404 if (colour_table && *colour_table) {
405 myfree(*colour_table);
406 *colour_table = NULL;
410 (void)myDGifCloseFile(GifFile, NULL);
415 case TERMINATE_RECORD_TYPE:
417 default: /* Should be traps by DGifGetRecordType. */
420 } while (RecordType != TERMINATE_RECORD_TYPE);
424 if (myDGifCloseFile(GifFile, &error) == GIF_ERROR) {
425 gif_push_error(error);
426 i_push_error(0, "Closing GIF file object");
427 if (colour_table && *colour_table) {
428 myfree(*colour_table);
429 *colour_table = NULL;
435 i_tags_set(&im->tags, "i_format", "gif", -1);
442 Internal function called by i_readgif_multi_low() in error handling
446 free_images(i_img **imgs, int count) {
450 for (i = 0; i < count; ++i)
451 i_img_destroy(imgs[i]);
457 =item i_readgif_multi_low(GifFileType *gf, int *count, int page)
459 Reads one of more gif images from the given GIF file.
461 Returns a pointer to an array of i_img *, and puts the count into
464 If page is not -1 then the given image _only_ is returned from the
465 file, where the first image is 0, the second 1 and so on.
467 Unlike the normal i_readgif*() functions the images are paletted
468 images rather than a combined RGB image.
470 This functions sets tags on the images returned:
476 the offset of the image from the left of the "screen" ("Image Left
481 the offset of the image from the top of the "screen" ("Image Top Position")
485 non-zero if the image was interlaced ("Interlace Flag")
487 =item gif_screen_width
489 =item gif_screen_height
491 the size of the logical screen ("Logical Screen Width",
492 "Logical Screen Height")
496 Non-zero if this image had a local color map.
500 The index in the global colormap of the logical screen's background
501 color. This is only set if the current image uses the global
504 =item gif_trans_index
506 The index of the color in the colormap used for transparency. If the
507 image has a transparency then it is returned as a 4 channel image with
508 the alpha set to zero in this palette entry. ("Transparent Color Index")
512 The delay until the next frame is displayed, in 1/100 of a second.
517 whether or not a user input is expected before continuing (view dependent)
522 how the next frame is displayed ("Disposal Method")
526 the number of loops from the Netscape Loop extension. This may be zero.
530 the first block of the first gif comment before each image.
534 Where applicable, the ("name") is the name of that field from the GIF89
541 i_readgif_multi_low(GifFileType *GifFile, int *count, int page) {
543 int i, j, Size, Width, Height, ExtCode, Count;
544 int ImageNum = 0, ColorMapSize = 0;
545 ColorMapObject *ColorMap;
547 GifRecordType RecordType;
548 GifByteType *Extension;
552 int trans_index = 0; /* transparent index if we see a GCE */
553 int gif_delay = 0; /* delay from a GCE */
554 int user_input = 0; /* user input flag from a GCE */
555 int disposal = 0; /* disposal method from a GCE */
558 char *comment = NULL; /* a comment */
559 i_img **results = NULL;
560 int result_alloc = 0;
562 int image_colors = 0;
563 i_color black; /* used to expand the palette if needed */
566 for (i = 0; i < MAXCHANNELS; ++i)
567 black.channel[i] = 0;
571 mm_log((1,"i_readgif_multi_low(GifFile %p, , count %p)\n", GifFile, count));
573 Size = GifFile->SWidth * sizeof(GifPixelType);
575 GifRow = (GifRowType) mymalloc(Size);
577 /* Scan the content of the GIF file and load the image(s) in: */
579 if (DGifGetRecordType(GifFile, &RecordType) == GIF_ERROR) {
580 gif_push_error(myGifError(GifFile));
581 i_push_error(0, "Unable to get record type");
582 free_images(results, *count);
583 (void)myDGifCloseFile(GifFile, NULL);
590 switch (RecordType) {
591 case IMAGE_DESC_RECORD_TYPE:
592 if (DGifGetImageDesc(GifFile) == GIF_ERROR) {
593 gif_push_error(myGifError(GifFile));
594 i_push_error(0, "Unable to get image descriptor");
595 free_images(results, *count);
596 (void)myDGifCloseFile(GifFile, NULL);
603 Width = GifFile->Image.Width;
604 Height = GifFile->Image.Height;
605 if (page == -1 || page == ImageNum) {
606 if (( ColorMap = (GifFile->Image.ColorMap ? GifFile->Image.ColorMap : GifFile->SColorMap) )) {
607 mm_log((1, "Adding local colormap\n"));
608 ColorMapSize = ColorMap->ColorCount;
610 /* No colormap and we are about to read in the image -
612 mm_log((1, "Going in with no colormap\n"));
613 i_push_error(0, "Image does not have a local or a global color map");
614 free_images(results, *count);
615 (void)myDGifCloseFile(GifFile, NULL);
623 if (got_gce && trans_index >= 0)
625 if (!i_int_check_image_file_limits(Width, Height, channels, sizeof(i_sample_t))) {
626 free_images(results, *count);
627 mm_log((1, "i_readgif: image size exceeds limits\n"));
628 (void)myDGifCloseFile(GifFile, NULL);
634 img = i_img_pal_new(Width, Height, channels, 256);
636 free_images(results, *count);
637 (void)myDGifCloseFile(GifFile, NULL);
643 /* populate the palette of the new image */
644 mm_log((1, "ColorMapSize %d\n", ColorMapSize));
645 for (i = 0; i < ColorMapSize; ++i) {
647 col.rgba.r = ColorMap->Colors[i].Red;
648 col.rgba.g = ColorMap->Colors[i].Green;
649 col.rgba.b = ColorMap->Colors[i].Blue;
650 if (channels == 4 && trans_index == i)
655 i_addcolors(img, &col, 1);
657 image_colors = ColorMapSize;
659 if (*count > result_alloc) {
660 if (result_alloc == 0) {
662 results = mymalloc(result_alloc * sizeof(i_img *));
665 /* myrealloc never fails (it just dies if it can't allocate) */
667 results = myrealloc(results, result_alloc * sizeof(i_img *));
670 results[*count-1] = img;
671 i_tags_set(&img->tags, "i_format", "gif", -1);
672 i_tags_setn(&img->tags, "gif_left", GifFile->Image.Left);
674 i_tags_setn(&img->tags, "gif_top", GifFile->Image.Top);
675 i_tags_setn(&img->tags, "gif_interlace", GifFile->Image.Interlace);
676 i_tags_setn(&img->tags, "gif_screen_width", GifFile->SWidth);
677 i_tags_setn(&img->tags, "gif_screen_height", GifFile->SHeight);
678 i_tags_setn(&img->tags, "gif_colormap_size", ColorMapSize);
679 if (GifFile->SColorMap && !GifFile->Image.ColorMap) {
680 i_tags_setn(&img->tags, "gif_background",
681 GifFile->SBackGroundColor);
683 if (GifFile->Image.ColorMap) {
684 i_tags_setn(&img->tags, "gif_localmap", 1);
687 if (trans_index >= 0) {
689 i_tags_setn(&img->tags, "gif_trans_index", trans_index);
690 i_getcolors(img, trans_index, &trans, 1);
691 i_tags_set_color(&img->tags, "gif_trans_color", 0, &trans);
693 i_tags_setn(&img->tags, "gif_delay", gif_delay);
694 i_tags_setn(&img->tags, "gif_user_input", user_input);
695 i_tags_setn(&img->tags, "gif_disposal", disposal);
699 i_tags_setn(&img->tags, "gif_loop", ns_loop);
701 i_tags_set(&img->tags, "gif_comment", comment, strlen(comment));
706 mm_log((1,"i_readgif_multi_low: Image %d at (%d, %d) [%dx%d]: \n",
707 ImageNum, GifFile->Image.Left, GifFile->Image.Top, Width, Height));
709 if (GifFile->Image.Left + GifFile->Image.Width > GifFile->SWidth ||
710 GifFile->Image.Top + GifFile->Image.Height > GifFile->SHeight) {
711 i_push_errorf(0, "Image %d is not confined to screen dimension, aborted.\n",ImageNum);
712 free_images(results, *count);
713 (void)myDGifCloseFile(GifFile, NULL);
720 if (GifFile->Image.Interlace) {
721 for (Count = i = 0; i < 4; i++) {
722 for (j = InterlacedOffset[i]; j < Height;
723 j += InterlacedJumps[i]) {
725 if (DGifGetLine(GifFile, GifRow, Width) == GIF_ERROR) {
726 gif_push_error(myGifError(GifFile));
727 i_push_error(0, "Reading GIF line");
728 free_images(results, *count);
729 (void)myDGifCloseFile(GifFile, NULL);
736 /* range check the scanline if needed */
737 if (image_colors != 256) {
739 for (x = 0; x < Width; ++x) {
740 while (GifRow[x] >= image_colors) {
741 /* expand the palette since a palette index is too big */
742 i_addcolors(img, &black, 1);
748 i_ppal(img, 0, Width, j, GifRow);
753 for (i = 0; i < Height; i++) {
754 if (DGifGetLine(GifFile, GifRow, Width) == GIF_ERROR) {
755 gif_push_error(myGifError(GifFile));
756 i_push_error(0, "Reading GIF line");
757 free_images(results, *count);
758 (void)myDGifCloseFile(GifFile, NULL);
765 /* range check the scanline if needed */
766 if (image_colors != 256) {
768 for (x = 0; x < Width; ++x) {
769 while (GifRow[x] >= image_colors) {
770 /* expand the palette since a palette index is too big */
771 i_addcolors(img, &black, 1);
777 i_ppal(img, 0, Width, i, GifRow);
781 /* must be only one image wanted and that was it */
784 (void)myDGifCloseFile(GifFile, NULL);
792 /* whether interlaced or not, it has the same number of lines */
793 /* giflib does't have an interface to skip the image data */
794 for (i = 0; i < Height; i++) {
795 if (DGifGetLine(GifFile, GifRow, Width) == GIF_ERROR) {
796 gif_push_error(myGifError(GifFile));
797 i_push_error(0, "Reading GIF line");
798 free_images(results, *count);
800 (void)myDGifCloseFile(GifFile, NULL);
807 /* kill the comment so we get the right comment for the page */
815 case EXTENSION_RECORD_TYPE:
816 /* Skip any extension blocks in file: */
817 if (DGifGetExtension(GifFile, &ExtCode, &Extension) == GIF_ERROR) {
818 gif_push_error(myGifError(GifFile));
819 i_push_error(0, "Reading extension record");
820 free_images(results, *count);
822 (void)myDGifCloseFile(GifFile, NULL);
827 /* possibly this should be an error, but "be liberal in what you accept" */
830 if (ExtCode == 0xF9) {
832 if (Extension[1] & 1)
833 trans_index = Extension[4];
836 gif_delay = Extension[2] + 256 * Extension[3];
837 user_input = (Extension[1] & 2) != 0;
838 disposal = (Extension[1] >> 2) & 7;
840 if (ExtCode == 0xFF && *Extension == 11) {
841 if (memcmp(Extension+1, "NETSCAPE2.0", 11) == 0) {
842 if (DGifGetExtensionNext(GifFile, &Extension) == GIF_ERROR) {
843 gif_push_error(myGifError(GifFile));
844 i_push_error(0, "reading loop extension");
845 free_images(results, *count);
847 (void)myDGifCloseFile(GifFile, NULL);
852 if (Extension && *Extension == 3) {
854 ns_loop = Extension[2] + 256 * Extension[3];
858 else if (ExtCode == 0xFE) {
859 /* while it's possible for a GIF file to contain more than one
860 comment, I'm only implementing a single comment per image,
861 with the comment saved into the following image.
862 If someone wants more than that they can implement it.
863 I also don't handle comments that take more than one block.
866 comment = mymalloc(*Extension+1);
867 memcpy(comment, Extension+1, *Extension);
868 comment[*Extension] = '\0';
871 while (Extension != NULL) {
872 if (DGifGetExtensionNext(GifFile, &Extension) == GIF_ERROR) {
873 gif_push_error(myGifError(GifFile));
874 i_push_error(0, "reading next block of extension");
875 free_images(results, *count);
877 (void)myDGifCloseFile(GifFile, NULL);
884 case TERMINATE_RECORD_TYPE:
886 default: /* Should be trapped by DGifGetRecordType. */
889 } while (RecordType != TERMINATE_RECORD_TYPE);
893 i_tags_set(&(results[*count-1]->tags), "gif_comment", comment,
901 if (myDGifCloseFile(GifFile, &error) == GIF_ERROR) {
902 gif_push_error(error);
903 i_push_error(0, "Closing GIF file object");
904 free_images(results, *count);
909 /* there were no images */
910 i_push_error(0, "no images found in file");
914 if (ImageNum && page != -1) {
915 /* there were images, but the page selected wasn't found */
916 i_push_errorf(0, "page %d not found (%d total)", page, ImageNum);
917 free_images(results, *count);
924 static int io_glue_read_cb(GifFileType *gft, GifByteType *buf, int length);
927 =item i_readgif_multi_wiol(ig, int *count)
933 i_readgif_multi_wiol(io_glue *ig, int *count) {
934 GifFileType *GifFile;
938 gif_mutex_lock(mutex);
942 if ((GifFile = myDGifOpen((void *)ig, io_glue_read_cb, &gif_error )) == NULL) {
943 gif_push_error(gif_error);
944 i_push_error(0, "Cannot create giflib callback object");
945 mm_log((1,"i_readgif_multi_wiol: Unable to open callback datasource.\n"));
946 gif_mutex_unlock(mutex);
950 result = i_readgif_multi_low(GifFile, count, -1);
952 gif_mutex_unlock(mutex);
958 io_glue_read_cb(GifFileType *gft, GifByteType *buf, int length) {
959 io_glue *ig = (io_glue *)gft->UserData;
961 return i_io_read(ig, buf, length);
965 i_readgif_wiol(io_glue *ig, int **color_table, int *colors) {
966 GifFileType *GifFile;
970 gif_mutex_lock(mutex);
974 if ((GifFile = myDGifOpen((void *)ig, io_glue_read_cb, &gif_error )) == NULL) {
975 gif_push_error(gif_error);
976 i_push_error(0, "Cannot create giflib callback object");
977 mm_log((1,"i_readgif_wiol: Unable to open callback datasource.\n"));
978 gif_mutex_unlock(mutex);
982 result = i_readgif_low(GifFile, color_table, colors);
984 gif_mutex_unlock(mutex);
990 =item i_readgif_single_low(GifFile, page)
992 Lower level function to read a single image from a GIF.
994 page must be non-negative.
999 i_readgif_single_low(GifFileType *GifFile, int page) {
1003 imgs = i_readgif_multi_low(GifFile, &count, page);
1005 if (imgs && count) {
1006 i_img *result = imgs[0];
1012 /* i_readgif_multi_low() handles the errors appropriately */
1018 =item i_readgif_single_wiol(ig, page)
1020 Read a single page from a GIF image file, where the page is indexed
1023 Returns NULL if the page isn't found.
1029 i_readgif_single_wiol(io_glue *ig, int page) {
1030 GifFileType *GifFile;
1036 i_push_error(0, "page must be non-negative");
1040 gif_mutex_lock(mutex);
1042 if ((GifFile = myDGifOpen((void *)ig, io_glue_read_cb, &gif_error )) == NULL) {
1043 gif_push_error(gif_error);
1044 i_push_error(0, "Cannot create giflib callback object");
1045 mm_log((1,"i_readgif_wiol: Unable to open callback datasource.\n"));
1046 gif_mutex_unlock(mutex);
1050 result = i_readgif_single_low(GifFile, page);
1052 gif_mutex_unlock(mutex);
1058 =item do_write(GifFileType *gf, i_gif_opts *opts, i_img *img, i_palidx *data)
1060 Internal. Low level image write function. Writes in interlace if
1061 that was requested in the GIF options.
1063 Returns non-zero on success.
1068 do_write(GifFileType *gf, int interlace, i_img *img, i_palidx *data) {
1071 for (i = 0; i < 4; ++i) {
1072 for (j = InterlacedOffset[i]; j < img->ysize; j += InterlacedJumps[i]) {
1073 if (EGifPutLine(gf, data+j*img->xsize, img->xsize) == GIF_ERROR) {
1074 gif_push_error(myGifError(gf));
1075 i_push_error(0, "Could not save image data:");
1076 mm_log((1, "Error in EGifPutLine\n"));
1084 for (y = 0; y < img->ysize; ++y) {
1085 if (EGifPutLine(gf, data, img->xsize) == GIF_ERROR) {
1086 gif_push_error(myGifError(gf));
1087 i_push_error(0, "Could not save image data:");
1088 mm_log((1, "Error in EGifPutLine\n"));
1099 =item do_gce(GifFileType *gf, int index, i_gif_opts *opts, int want_trans, int trans_index)
1101 Internal. Writes the GIF graphics control extension, if necessary.
1103 Returns non-zero on success.
1109 do_gce(GifFileType *gf, i_img *img, int want_trans, int trans_index)
1111 unsigned char gce[4] = {0};
1115 int disposal_method;
1119 gce[3] = trans_index;
1122 if (i_tags_get_int(&img->tags, "gif_delay", 0, &delay)) {
1123 gce[1] = delay % 256;
1124 gce[2] = delay / 256;
1127 if (i_tags_get_int(&img->tags, "gif_user_input", 0, &user_input)
1132 if (i_tags_get_int(&img->tags, "gif_disposal", 0, &disposal_method)) {
1133 gce[0] |= (disposal_method & 3) << 2;
1137 if (EGifPutExtension(gf, 0xF9, sizeof(gce), gce) == GIF_ERROR) {
1138 gif_push_error(myGifError(gf));
1139 i_push_error(0, "Could not save GCE");
1146 =item do_comments(gf, img)
1148 Write any comments in the image.
1154 do_comments(GifFileType *gf, i_img *img) {
1157 while (i_tags_find(&img->tags, "gif_comment", pos+1, &pos)) {
1158 if (img->tags.tags[pos].data) {
1159 if (EGifPutComment(gf, img->tags.tags[pos].data) == GIF_ERROR) {
1165 #ifdef IMAGER_SNPRINTF
1166 snprintf(buf, sizeof(buf), "%d", img->tags.tags[pos].idata);
1168 sprintf(buf, "%d", img->tags.tags[pos].idata);
1170 if (EGifPutComment(gf, buf) == GIF_ERROR) {
1180 =item do_ns_loop(GifFileType *gf, i_gif_opts *opts)
1182 Internal. Add the Netscape2.0 loop extension block, if requested.
1184 Giflib/libungif prior to 4.1.1 didn't support writing application
1185 extension blocks, so we don't attempt to write them for older versions.
1187 Giflib/libungif prior to 4.1.3 used the wrong write mechanism when
1188 writing extension blocks so that they could only be written to files.
1194 do_ns_loop(GifFileType *gf, i_img *img)
1196 /* EGifPutExtension() doesn't appear to handle application
1197 extension blocks in any way
1198 Since giflib wraps the fd with a FILE * (and puts that in its
1199 private data), we can't do an end-run and write the data
1201 There's no open interface that takes a FILE * either, so we
1202 can't workaround it that way either.
1203 If giflib's callback interface wasn't broken by default, I'd
1204 force file writes to use callbacks, but it is broken by default.
1206 /* yes this was another attempt at supporting the loop extension */
1208 if (i_tags_get_int(&img->tags, "gif_loop", 0, &loop_count)) {
1209 unsigned char nsle[12] = "NETSCAPE2.0";
1210 unsigned char subblock[3];
1213 subblock[1] = loop_count % 256;
1214 subblock[2] = loop_count / 256;
1216 #if IMGIFLIB_API_VERSION >= 500
1217 if (EGifPutExtensionLeader(gf, APPLICATION_EXT_FUNC_CODE) == GIF_ERROR
1218 || EGifPutExtensionBlock(gf, 11, nsle) == GIF_ERROR
1219 || EGifPutExtensionBlock(gf, 3, subblock) == GIF_ERROR
1220 || EGifPutExtensionTrailer(gf) == GIF_ERROR) {
1221 gif_push_error(myGifError(gf));
1222 i_push_error(0, "writing loop extension");
1227 if (EGifPutExtensionFirst(gf, APPLICATION_EXT_FUNC_CODE, 11, nsle) == GIF_ERROR) {
1228 gif_push_error(myGifError(gf));
1229 i_push_error(0, "writing loop extension");
1232 if (EGifPutExtensionLast(gf, APPLICATION_EXT_FUNC_CODE, 3, subblock) == GIF_ERROR) {
1233 gif_push_error(myGifError(gf));
1234 i_push_error(0, "writing loop extension sub-block");
1244 =item make_gif_map(i_quantize *quant, int want_trans)
1246 Create a giflib color map object from an Imager color map.
1251 static ColorMapObject *
1252 make_gif_map(i_quantize *quant, i_img *img, int want_trans) {
1253 GifColorType colors[256];
1255 int size = quant->mc_count;
1257 ColorMapObject *map;
1260 for (i = 0; i < quant->mc_count; ++i) {
1261 colors[i].Red = quant->mc_colors[i].rgb.r;
1262 colors[i].Green = quant->mc_colors[i].rgb.g;
1263 colors[i].Blue = quant->mc_colors[i].rgb.b;
1266 if (!i_tags_get_color(&img->tags, "gif_trans_color", 0, &trans))
1267 trans.rgb.r = trans.rgb.g = trans.rgb.b = 0;
1268 colors[size].Red = trans.rgb.r;
1269 colors[size].Green = trans.rgb.g;
1270 colors[size].Blue = trans.rgb.b;
1274 while (map_size < size)
1276 /* giflib spews for 1 colour maps, reasonable, I suppose */
1279 while (i < map_size) {
1280 colors[i].Red = colors[i].Green = colors[i].Blue = 0;
1284 map = MakeMapObject(map_size, colors);
1286 i_push_error(0, "Could not create color map object");
1289 mm_log((1, "XXX map is at %p and colors at %p\n", map, map->Colors));
1290 #if IMGIFLIB_API_VERSION >= 500
1297 =item need_version_89a(i_quantize *quant, i_img *imgs, int count)
1299 Return true if the file we're creating on these images needs a GIF89a
1306 need_version_89a(i_quantize *quant, i_img **imgs, int count) {
1311 for (i = 0; i < count; ++i) {
1312 if (quant->transp != tr_none &&
1313 (imgs[i]->channels == 2 || imgs[i]->channels == 4)) {
1317 if (i_tags_get_int(&imgs[i]->tags, "gif_delay", 0, &temp)) {
1321 if (i_tags_get_int(&imgs[i]->tags, "gif_user_input", 0, &temp) && temp) {
1325 if (i_tags_get_int(&imgs[i]->tags, "gif_disposal", 0, &temp)) {
1329 if (i_tags_get_int(&imgs[i]->tags, "gif_loop", 0, &temp)) {
1339 in_palette(i_color *c, i_quantize *quant, int size) {
1342 for (i = 0; i < size; ++i) {
1343 if (c->channel[0] == quant->mc_colors[i].channel[0]
1344 && c->channel[1] == quant->mc_colors[i].channel[1]
1345 && c->channel[2] == quant->mc_colors[i].channel[2]) {
1354 =item has_common_palette(imgs, count, quant)
1356 Tests if all the given images are paletted and their colors are in the
1359 Previously this would build a consolidated palette from the source,
1360 but that meant that if the caller supplied a static palette (or
1361 specified a fixed palette like "webmap") then we wouldn't be
1362 quantizing to the caller specified palette.
1368 has_common_palette(i_img **imgs, int count, i_quantize *quant) {
1374 /* we try to build a common palette here, if we can manage that, then
1375 that's the palette we use */
1376 for (imgn = 0; imgn < count; ++imgn) {
1377 int eliminate_unused;
1378 if (imgs[imgn]->type != i_palette_type)
1381 if (!i_tags_get_int(&imgs[imgn]->tags, "gif_eliminate_unused", 0,
1382 &eliminate_unused)) {
1383 eliminate_unused = 1;
1386 if (eliminate_unused) {
1387 i_palidx *line = mymalloc(sizeof(i_palidx) * imgs[imgn]->xsize);
1389 memset(used, 0, sizeof(used));
1391 for (y = 0; y < imgs[imgn]->ysize; ++y) {
1392 i_gpal(imgs[imgn], 0, imgs[imgn]->xsize, y, line);
1393 for (x = 0; x < imgs[imgn]->xsize; ++x)
1400 /* assume all are in use */
1401 memset(used, 1, sizeof(used));
1404 col_count = i_colorcount(imgs[imgn]);
1405 for (i = 0; i < col_count; ++i) {
1408 i_getcolors(imgs[imgn], i, &c, 1);
1410 if (in_palette(&c, quant, quant->mc_count) < 0) {
1411 mm_log((1, " color not found in palette, no palette shortcut\n"));
1419 mm_log((1, " all colors found in palette, palette shortcut\n"));
1425 quant_paletted(i_quantize *quant, i_img *img) {
1426 i_palidx *data = mymalloc(sizeof(i_palidx) * img->xsize * img->ysize);
1428 i_palidx trans[256];
1432 /* build a translation table */
1433 for (i = 0; i < i_colorcount(img); ++i) {
1435 i_getcolors(img, i, &c, 1);
1436 trans[i] = in_palette(&c, quant, quant->mc_count);
1439 for (y = 0; y < img->ysize; ++y) {
1440 i_gpal(img, 0, img->xsize, y, data+img->xsize * y);
1441 for (x = 0; x < img->xsize; ++x) {
1451 =item i_writegif_low(i_quantize *quant, GifFileType *gf, i_img **imgs, int count, i_gif_opts *opts)
1453 Internal. Low-level function that does the high-level GIF processing
1456 Returns non-zero on success.
1462 i_writegif_low(i_quantize *quant, GifFileType *gf, i_img **imgs, int count) {
1463 unsigned char *result = NULL;
1465 ColorMapObject *map;
1466 int scrw = 0, scrh = 0;
1467 int imgn, orig_count, orig_size;
1469 int trans_index = -1;
1470 int *localmaps = NULL;
1472 i_img **glob_imgs = NULL; /* images that will use the global color map */
1474 i_color *orig_colors = quant->mc_colors;
1475 i_color *glob_colors = NULL;
1476 int glob_color_count = 0;
1477 int glob_want_trans;
1478 int glob_paletted = 0; /* the global map was made from the image palettes */
1479 int colors_paletted = 0;
1485 mm_log((1, "i_writegif_low(quant %p, gf %p, imgs %p, count %d)\n",
1486 quant, gf, imgs, count));
1488 /* *((char *)0) = 1; */ /* used to break into the debugger */
1491 i_push_error(0, "No images provided to write");
1495 /* sanity is nice */
1496 if (quant->mc_size > 256)
1497 quant->mc_size = 256;
1498 if (quant->mc_count > quant->mc_size)
1499 quant->mc_count = quant->mc_size;
1501 if (!i_tags_get_int(&imgs[0]->tags, "gif_screen_width", 0, &scrw))
1503 if (!i_tags_get_int(&imgs[0]->tags, "gif_screen_height", 0, &scrh))
1507 localmaps = mymalloc(sizeof(int) * count);
1508 glob_imgs = mymalloc(sizeof(i_img *) * count);
1510 glob_want_trans = 0;
1511 for (imgn = 0; imgn < count; ++imgn) {
1512 i_img *im = imgs[imgn];
1513 if (im->xsize > 0xFFFF || im->ysize > 0xFFFF) {
1514 i_push_error(0, "image too large for GIF");
1519 i_tags_get_int(&im->tags, "gif_left", 0, &posx);
1520 if (posx < 0) posx = 0;
1521 i_tags_get_int(&im->tags, "gif_top", 0, &posy);
1522 if (posy < 0) posy = 0;
1523 if (im->xsize + posx > scrw)
1524 scrw = im->xsize + posx;
1525 if (im->ysize + posy > scrh)
1526 scrh = im->ysize + posy;
1527 if (!i_tags_get_int(&im->tags, "gif_local_map", 0, localmaps+imgn))
1528 localmaps[imgn] = 0;
1529 if (localmaps[imgn])
1532 if (im->channels == 4) {
1533 glob_want_trans = 1;
1535 glob_imgs[glob_img_count++] = im;
1538 glob_want_trans = glob_want_trans && quant->transp != tr_none ;
1540 if (scrw > 0xFFFF || scrh > 0xFFFF) {
1541 i_push_error(0, "screen size too large for GIF");
1545 orig_count = quant->mc_count;
1546 orig_size = quant->mc_size;
1548 if (glob_img_count) {
1550 glob_colors = mymalloc(sizeof(i_color) * quant->mc_size);
1551 quant->mc_colors = glob_colors;
1552 memcpy(glob_colors, orig_colors, sizeof(i_color) * quant->mc_count);
1553 /* we have some images that want to use the global map */
1554 if (glob_want_trans && quant->mc_count == 256) {
1555 mm_log((2, " disabling transparency for global map - no space\n"));
1556 glob_want_trans = 0;
1558 if (glob_want_trans && quant->mc_size == 256) {
1559 mm_log((2, " reserving color for transparency\n"));
1563 i_quant_makemap(quant, glob_imgs, glob_img_count);
1564 glob_paletted = has_common_palette(glob_imgs, glob_img_count, quant);
1565 glob_color_count = quant->mc_count;
1566 quant->mc_colors = orig_colors;
1569 /* use the global map if we have one, otherwise use the local map */
1572 quant->mc_colors = glob_colors;
1573 quant->mc_count = glob_color_count;
1574 want_trans = glob_want_trans && imgs[0]->channels == 4;
1576 if (!i_tags_get_int(&imgs[0]->tags, "gif_background", 0, &gif_background))
1578 if (gif_background < 0)
1580 if (gif_background >= glob_color_count)
1584 want_trans = quant->transp != tr_none && imgs[0]->channels == 4;
1585 i_quant_makemap(quant, imgs, 1);
1586 colors_paletted = has_common_palette(imgs, 1, quant);
1589 if ((map = make_gif_map(quant, imgs[0], want_trans)) == NULL) {
1590 mm_log((1, "Error in MakeMapObject"));
1595 /* since we don't know how big some the local palettes could be
1596 we need to base the bits on the maximum number of colors */
1597 while (orig_size > (1 << color_bits))
1601 int count = quant->mc_count;
1604 while (count > (1 << color_bits))
1608 if (EGifPutScreenDesc(gf, scrw, scrh, color_bits,
1609 gif_background, map) == GIF_ERROR) {
1610 gif_push_error(myGifError(gf));
1611 i_push_error(0, "Could not save screen descriptor");
1613 mm_log((1, "Error in EGifPutScreenDesc."));
1618 if (!i_tags_get_int(&imgs[0]->tags, "gif_left", 0, &posx))
1620 if (!i_tags_get_int(&imgs[0]->tags, "gif_top", 0, &posy))
1623 if (!localmaps[0]) {
1625 colors_paletted = glob_paletted;
1628 /* if this image has a global map the colors in quant don't
1629 belong to this image, so build a palette */
1631 /* generate the local map for this image */
1632 quant->mc_colors = orig_colors;
1633 quant->mc_size = orig_size;
1634 quant->mc_count = orig_count;
1635 want_trans = quant->transp != tr_none && imgs[0]->channels == 4;
1637 /* if the caller gives us too many colours we can't do transparency */
1638 if (want_trans && quant->mc_count == 256)
1640 /* if they want transparency but give us a big size, make it smaller
1641 to give room for a transparency colour */
1642 if (want_trans && quant->mc_size == 256)
1644 i_quant_makemap(quant, imgs, 1);
1645 colors_paletted = has_common_palette(imgs, 1, quant);
1646 if ((map = make_gif_map(quant, imgs[0], want_trans)) == NULL) {
1647 mm_log((1, "Error in MakeMapObject"));
1652 /* the map we wrote was the map for this image - don't set the local
1658 if (colors_paletted)
1659 result = quant_paletted(quant, imgs[0]);
1661 result = i_quant_translate(quant, imgs[0]);
1666 i_quant_transparent(quant, result, imgs[0], quant->mc_count);
1667 trans_index = quant->mc_count;
1670 if (!do_ns_loop(gf, imgs[0])) {
1674 if (!do_gce(gf, imgs[0], want_trans, trans_index)) {
1678 if (!do_comments(gf, imgs[0])) {
1682 if (!i_tags_get_int(&imgs[0]->tags, "gif_interlace", 0, &interlace))
1684 if (EGifPutImageDesc(gf, posx, posy, imgs[0]->xsize, imgs[0]->ysize,
1685 interlace, map) == GIF_ERROR) {
1688 gif_push_error(myGifError(gf));
1689 i_push_error(0, "Could not save image descriptor");
1690 mm_log((1, "Error in EGifPutImageDesc."));
1696 if (!do_write(gf, interlace, imgs[0], result)) {
1702 /* that first awful image is out of the way, do the rest */
1703 for (imgn = 1; imgn < count; ++imgn) {
1704 if (localmaps[imgn]) {
1705 quant->mc_colors = orig_colors;
1706 quant->mc_count = orig_count;
1707 quant->mc_size = orig_size;
1709 want_trans = quant->transp != tr_none
1710 && imgs[imgn]->channels == 4;
1711 /* if the caller gives us too many colours we can't do transparency */
1712 if (want_trans && quant->mc_count == 256)
1714 /* if they want transparency but give us a big size, make it smaller
1715 to give room for a transparency colour */
1716 if (want_trans && quant->mc_size == 256)
1719 if (has_common_palette(imgs+imgn, 1, quant)) {
1720 result = quant_paletted(quant, imgs[imgn]);
1723 i_quant_makemap(quant, imgs+imgn, 1);
1724 result = i_quant_translate(quant, imgs[imgn]);
1727 mm_log((1, "error in i_quant_translate()"));
1731 i_quant_transparent(quant, result, imgs[imgn], quant->mc_count);
1732 trans_index = quant->mc_count;
1735 if ((map = make_gif_map(quant, imgs[imgn], want_trans)) == NULL) {
1736 mm_log((1, "Error in MakeMapObject."));
1741 quant->mc_colors = glob_colors;
1742 quant->mc_count = glob_color_count;
1744 result = quant_paletted(quant, imgs[imgn]);
1746 result = i_quant_translate(quant, imgs[imgn]);
1747 want_trans = glob_want_trans && imgs[imgn]->channels == 4;
1749 i_quant_transparent(quant, result, imgs[imgn], quant->mc_count);
1750 trans_index = quant->mc_count;
1755 if (!do_gce(gf, imgs[imgn], want_trans, trans_index)) {
1759 if (!do_comments(gf, imgs[imgn])) {
1763 if (!i_tags_get_int(&imgs[imgn]->tags, "gif_left", 0, &posx))
1765 if (!i_tags_get_int(&imgs[imgn]->tags, "gif_top", 0, &posy))
1768 if (!i_tags_get_int(&imgs[imgn]->tags, "gif_interlace", 0, &interlace))
1770 if (EGifPutImageDesc(gf, posx, posy, imgs[imgn]->xsize,
1771 imgs[imgn]->ysize, interlace, map) == GIF_ERROR) {
1772 gif_push_error(myGifError(gf));
1773 i_push_error(0, "Could not save image descriptor");
1776 mm_log((1, "Error in EGifPutImageDesc."));
1782 if (!do_write(gf, interlace, imgs[imgn], result)) {
1789 if (myEGifCloseFile(gf, &error) == GIF_ERROR) {
1790 gif_push_error(error);
1791 i_push_error(0, "Could not close GIF file");
1796 for (i = 0; i < glob_color_count; ++i)
1797 orig_colors[i] = glob_colors[i];
1800 myfree(glob_colors);
1803 quant->mc_colors = orig_colors;
1808 quant->mc_colors = orig_colors;
1810 myfree(glob_colors);
1813 (void)myEGifCloseFile(gf, &error);
1818 io_glue_write_cb(GifFileType *gft, const GifByteType *data, int length) {
1819 io_glue *ig = (io_glue *)gft->UserData;
1821 return i_io_write(ig, data, length);
1826 =item i_writegif_wiol(ig, quant, opts, imgs, count)
1831 i_writegif_wiol(io_glue *ig, i_quantize *quant, i_img **imgs,
1833 GifFileType *GifFile;
1837 gif_mutex_lock(mutex);
1841 #ifdef PRE_SET_VERSION
1842 EGifSetGifVersion(need_version_89a(quant, imgs, count) ? "89a" : "87a");
1845 if ((GifFile = myEGifOpen((void *)ig, io_glue_write_cb, &gif_error )) == NULL) {
1846 gif_push_error(gif_error);
1847 i_push_error(0, "Cannot create giflib callback object");
1848 mm_log((1,"i_writegif_wiol: Unable to open callback datasource.\n"));
1849 gif_mutex_unlock(mutex);
1853 #ifdef POST_SET_VERSION
1854 EGifSetGifVersion(GifFile, need_version_89a(quant, imgs, count));
1857 result = i_writegif_low(quant, GifFile, imgs, count);
1859 gif_mutex_unlock(mutex);
1868 =item gif_error_msg(int code)
1870 Grabs the most recent giflib error code from GifLastError() and
1871 returns a string that describes that error.
1873 Returns NULL for unknown error codes.
1879 gif_error_msg(int code) {
1880 #if IMGIFLIB_API_VERSION >= 500
1881 return GifErrorString(code);
1884 case E_GIF_ERR_OPEN_FAILED: /* should not see this */
1885 return "Failed to open given file";
1887 case E_GIF_ERR_WRITE_FAILED:
1888 return "Write failed";
1890 case E_GIF_ERR_HAS_SCRN_DSCR: /* should not see this */
1891 return "Screen descriptor already passed to giflib";
1893 case E_GIF_ERR_HAS_IMAG_DSCR: /* should not see this */
1894 return "Image descriptor already passed to giflib";
1896 case E_GIF_ERR_NO_COLOR_MAP: /* should not see this */
1897 return "Neither global nor local color map set";
1899 case E_GIF_ERR_DATA_TOO_BIG: /* should not see this */
1900 return "Too much pixel data passed to giflib";
1902 case E_GIF_ERR_NOT_ENOUGH_MEM:
1903 return "Out of memory";
1905 case E_GIF_ERR_DISK_IS_FULL:
1906 return "Disk is full";
1908 case E_GIF_ERR_CLOSE_FAILED: /* should not see this */
1909 return "File close failed";
1911 case E_GIF_ERR_NOT_WRITEABLE: /* should not see this */
1912 return "File not writable";
1914 case D_GIF_ERR_OPEN_FAILED:
1915 return "Failed to open file";
1917 case D_GIF_ERR_READ_FAILED:
1918 return "Failed to read from file";
1920 case D_GIF_ERR_NOT_GIF_FILE:
1921 return "File is not a GIF file";
1923 case D_GIF_ERR_NO_SCRN_DSCR:
1924 return "No screen descriptor detected - invalid file";
1926 case D_GIF_ERR_NO_IMAG_DSCR:
1927 return "No image descriptor detected - invalid file";
1929 case D_GIF_ERR_NO_COLOR_MAP:
1930 return "No global or local color map found";
1932 case D_GIF_ERR_WRONG_RECORD:
1933 return "Wrong record type detected - invalid file?";
1935 case D_GIF_ERR_DATA_TOO_BIG:
1936 return "Data in file too big for image";
1938 case D_GIF_ERR_NOT_ENOUGH_MEM:
1939 return "Out of memory";
1941 case D_GIF_ERR_CLOSE_FAILED:
1942 return "Close failed";
1944 case D_GIF_ERR_NOT_READABLE:
1945 return "File not opened for read";
1947 case D_GIF_ERR_IMAGE_DEFECT:
1948 return "Defective image";
1950 case D_GIF_ERR_EOF_TOO_SOON:
1951 return "Unexpected EOF - invalid file";
1960 =item gif_push_error(code)
1962 Utility function that takes the current GIF error code, converts it to
1963 an error message and pushes it on the error stack.
1969 gif_push_error(int code) {
1970 const char *msg = gif_error_msg(code);
1972 i_push_error(code, msg);
1974 i_push_errorf(code, "Unknown GIF error %d", code);
1980 Arnar M. Hrafnkelsson, addi@umich.edu
1982 Tony Cook <tonyc@cpan.org>