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.
62 #if defined(GIFLIB_MAJOR) && GIFLIB_MAJOR >= 5
63 #define POST_SET_VERSION
64 #define myDGifOpen(userPtr, readFunc, Error) DGifOpen((userPtr), (readFunc), (Error))
65 #define myEGifOpen(userPtr, readFunc, Error) EGifOpen((userPtr), (readFunc), (Error))
66 #define myGifError(gif) ((gif)->Error)
67 #define MakeMapObject GifMakeMapObject
68 #define FreeMapObject GifFreeMapObject
69 #define gif_mutex_lock(mutex)
70 #define gif_mutex_unlock(mutex)
72 #define PRE_SET_VERSION
74 myDGifOpen(void *userPtr, InputFunc readFunc, int *error) {
75 GifFileType *result = DGifOpen(userPtr, readFunc);
77 *error = GifLastError();
82 myEGifOpen(void *userPtr, OutputFunc outputFunc, int *error) {
83 GifFileType *result = EGifOpen(userPtr, outputFunc);
85 *error = GifLastError();
89 #define myGifError(gif) GifLastError()
90 #define gif_mutex_lock(mutex) i_mutex_lock(mutex)
91 #define gif_mutex_unlock(mutex) i_mutex_unlock(mutex)
95 static char const *gif_error_msg(int code);
96 static void gif_push_error(int code);
98 /* Make some variables global, so we could access them faster: */
101 InterlacedOffset[] = { 0, 4, 2, 1 }, /* The way Interlaced image should. */
102 InterlacedJumps[] = { 8, 8, 4, 2 }; /* be read - offsets and jumps... */
104 #if !defined(GIFLIB_MAJOR) || GIFLIB_MAJOR < 5
105 static i_mutex_t mutex;
110 #if !defined(GIFLIB_MAJOR) || GIFLIB_MAJOR < 5
111 mutex = i_mutex_new();
117 i_colortable_copy(int **colour_table, int *colours, ColorMapObject *colourmap) {
118 GifColorType *mapentry;
120 int colourmapsize = colourmap->ColorCount;
122 if(colours) *colours = colourmapsize;
123 if(!colour_table) return;
125 *colour_table = mymalloc(sizeof(int) * colourmapsize * 3);
126 memset(*colour_table, 0, sizeof(int) * colourmapsize * 3);
128 for(q=0; q<colourmapsize; q++) {
129 mapentry = &colourmap->Colors[q];
130 (*colour_table)[q*3 + 0] = mapentry->Red;
131 (*colour_table)[q*3 + 1] = mapentry->Green;
132 (*colour_table)[q*3 + 2] = mapentry->Blue;
136 #ifdef GIF_LIB_VERSION
139 char gif_version_str[] = GIF_LIB_VERSION;
142 i_giflib_version(void) {
143 const char *p = gif_version_str;
145 while (*p && (*p < '0' || *p > '9'))
151 return strtod(p, NULL);
157 i_giflib_version(void) {
158 return GIFLIB_MAJOR + GIFLIB_MINOR * 0.1;
164 =item i_readgif_low(GifFileType *GifFile, int **colour_table, int *colours)
166 Internal. Low-level function for reading a GIF file. The caller must
167 create the appropriate GifFileType object and pass it in.
173 i_readgif_low(GifFileType *GifFile, int **colour_table, int *colours) {
175 int i, j, Size, Row, Col, Width, Height, ExtCode, Count, x;
176 int cmapcnt = 0, ImageNum = 0, BackGround = 0, ColorMapSize = 0;
177 ColorMapObject *ColorMap;
179 GifRecordType RecordType;
180 GifByteType *Extension;
183 GifColorType *ColorMapEntry;
186 mm_log((1,"i_readgif_low(GifFile %p, colour_table %p, colours %p)\n", GifFile, colour_table, colours));
188 /* it's possible that the caller has called us with *colour_table being
189 non-NULL, but we check that to see if we need to free an allocated
190 colour table on error.
192 if (colour_table) *colour_table = NULL;
194 BackGround = GifFile->SBackGroundColor;
195 ColorMap = (GifFile->Image.ColorMap ? GifFile->Image.ColorMap : GifFile->SColorMap);
198 ColorMapSize = ColorMap->ColorCount;
199 i_colortable_copy(colour_table, colours, ColorMap);
203 if (!i_int_check_image_file_limits(GifFile->SWidth, GifFile->SHeight, 3, sizeof(i_sample_t))) {
204 if (colour_table && *colour_table) {
205 myfree(*colour_table);
206 *colour_table = NULL;
208 DGifCloseFile(GifFile);
209 mm_log((1, "i_readgif: image size exceeds limits\n"));
213 im = i_img_8_new(GifFile->SWidth, GifFile->SHeight, 3);
215 if (colour_table && *colour_table) {
216 myfree(*colour_table);
217 *colour_table = NULL;
219 DGifCloseFile(GifFile);
223 Size = GifFile->SWidth * sizeof(GifPixelType);
225 GifRow = mymalloc(Size);
227 for (i = 0; i < GifFile->SWidth; i++) GifRow[i] = GifFile->SBackGroundColor;
229 /* Scan the content of the GIF file and load the image(s) in: */
231 if (DGifGetRecordType(GifFile, &RecordType) == GIF_ERROR) {
232 gif_push_error(myGifError(GifFile));
233 i_push_error(0, "Unable to get record type");
234 if (colour_table && *colour_table) {
235 myfree(*colour_table);
236 *colour_table = NULL;
240 DGifCloseFile(GifFile);
244 switch (RecordType) {
245 case IMAGE_DESC_RECORD_TYPE:
246 if (DGifGetImageDesc(GifFile) == GIF_ERROR) {
247 gif_push_error(myGifError(GifFile));
248 i_push_error(0, "Unable to get image descriptor");
249 if (colour_table && *colour_table) {
250 myfree(*colour_table);
251 *colour_table = NULL;
255 DGifCloseFile(GifFile);
259 if (( ColorMap = (GifFile->Image.ColorMap ? GifFile->Image.ColorMap : GifFile->SColorMap) )) {
260 mm_log((1, "Adding local colormap\n"));
261 ColorMapSize = ColorMap->ColorCount;
263 i_colortable_copy(colour_table, colours, ColorMap);
267 /* No colormap and we are about to read in the image - abandon for now */
268 mm_log((1, "Going in with no colormap\n"));
269 i_push_error(0, "Image does not have a local or a global color map");
270 /* we can't have allocated a colour table here */
273 DGifCloseFile(GifFile);
277 Row = GifFile->Image.Top; /* Image Position relative to Screen. */
278 Col = GifFile->Image.Left;
279 Width = GifFile->Image.Width;
280 Height = GifFile->Image.Height;
282 mm_log((1,"i_readgif_low: Image %d at (%d, %d) [%dx%d]: \n",ImageNum, Col, Row, Width, Height));
284 if (GifFile->Image.Left + GifFile->Image.Width > GifFile->SWidth ||
285 GifFile->Image.Top + GifFile->Image.Height > GifFile->SHeight) {
286 i_push_errorf(0, "Image %d is not confined to screen dimension, aborted.\n",ImageNum);
287 if (colour_table && *colour_table) {
288 myfree(*colour_table);
289 *colour_table = NULL;
293 DGifCloseFile(GifFile);
296 if (GifFile->Image.Interlace) {
298 for (Count = i = 0; i < 4; i++) for (j = Row + InterlacedOffset[i]; j < Row + Height; j += InterlacedJumps[i]) {
300 if (DGifGetLine(GifFile, GifRow, Width) == GIF_ERROR) {
301 gif_push_error(myGifError(GifFile));
302 i_push_error(0, "Reading GIF line");
303 if (colour_table && *colour_table) {
304 myfree(*colour_table);
305 *colour_table = NULL;
309 DGifCloseFile(GifFile);
313 for (x = 0; x < Width; x++) {
314 ColorMapEntry = &ColorMap->Colors[GifRow[x]];
315 col.rgb.r = ColorMapEntry->Red;
316 col.rgb.g = ColorMapEntry->Green;
317 col.rgb.b = ColorMapEntry->Blue;
318 i_ppix(im,Col+x,j,&col);
324 for (i = 0; i < Height; i++) {
325 if (DGifGetLine(GifFile, GifRow, Width) == GIF_ERROR) {
326 gif_push_error(myGifError(GifFile));
327 i_push_error(0, "Reading GIF line");
328 if (colour_table && *colour_table) {
329 myfree(*colour_table);
330 *colour_table = NULL;
334 DGifCloseFile(GifFile);
338 for (x = 0; x < Width; x++) {
339 ColorMapEntry = &ColorMap->Colors[GifRow[x]];
340 col.rgb.r = ColorMapEntry->Red;
341 col.rgb.g = ColorMapEntry->Green;
342 col.rgb.b = ColorMapEntry->Blue;
343 i_ppix(im, Col+x, Row, &col);
349 case EXTENSION_RECORD_TYPE:
350 /* Skip any extension blocks in file: */
351 if (DGifGetExtension(GifFile, &ExtCode, &Extension) == GIF_ERROR) {
352 gif_push_error(myGifError(GifFile));
353 i_push_error(0, "Reading extension record");
354 if (colour_table && *colour_table) {
355 myfree(*colour_table);
356 *colour_table = NULL;
360 DGifCloseFile(GifFile);
363 while (Extension != NULL) {
364 if (DGifGetExtensionNext(GifFile, &Extension) == GIF_ERROR) {
365 gif_push_error(myGifError(GifFile));
366 i_push_error(0, "reading next block of extension");
367 if (colour_table && *colour_table) {
368 myfree(*colour_table);
369 *colour_table = NULL;
373 DGifCloseFile(GifFile);
378 case TERMINATE_RECORD_TYPE:
380 default: /* Should be traps by DGifGetRecordType. */
383 } while (RecordType != TERMINATE_RECORD_TYPE);
387 if (DGifCloseFile(GifFile) == GIF_ERROR) {
388 gif_push_error(myGifError(GifFile));
389 i_push_error(0, "Closing GIF file object");
390 if (colour_table && *colour_table) {
391 myfree(*colour_table);
392 *colour_table = NULL;
398 i_tags_set(&im->tags, "i_format", "gif", -1);
405 Internal function called by i_readgif_multi_low() in error handling
409 free_images(i_img **imgs, int count) {
413 for (i = 0; i < count; ++i)
414 i_img_destroy(imgs[i]);
420 =item i_readgif_multi_low(GifFileType *gf, int *count, int page)
422 Reads one of more gif images from the given GIF file.
424 Returns a pointer to an array of i_img *, and puts the count into
427 If page is not -1 then the given image _only_ is returned from the
428 file, where the first image is 0, the second 1 and so on.
430 Unlike the normal i_readgif*() functions the images are paletted
431 images rather than a combined RGB image.
433 This functions sets tags on the images returned:
439 the offset of the image from the left of the "screen" ("Image Left
444 the offset of the image from the top of the "screen" ("Image Top Position")
448 non-zero if the image was interlaced ("Interlace Flag")
450 =item gif_screen_width
452 =item gif_screen_height
454 the size of the logical screen ("Logical Screen Width",
455 "Logical Screen Height")
459 Non-zero if this image had a local color map.
463 The index in the global colormap of the logical screen's background
464 color. This is only set if the current image uses the global
467 =item gif_trans_index
469 The index of the color in the colormap used for transparency. If the
470 image has a transparency then it is returned as a 4 channel image with
471 the alpha set to zero in this palette entry. ("Transparent Color Index")
475 The delay until the next frame is displayed, in 1/100 of a second.
480 whether or not a user input is expected before continuing (view dependent)
485 how the next frame is displayed ("Disposal Method")
489 the number of loops from the Netscape Loop extension. This may be zero.
493 the first block of the first gif comment before each image.
497 Where applicable, the ("name") is the name of that field from the GIF89
504 i_readgif_multi_low(GifFileType *GifFile, int *count, int page) {
506 int i, j, Size, Width, Height, ExtCode, Count;
507 int ImageNum = 0, BackGround = 0, ColorMapSize = 0;
508 ColorMapObject *ColorMap;
510 GifRecordType RecordType;
511 GifByteType *Extension;
515 int trans_index = 0; /* transparent index if we see a GCE */
516 int gif_delay = 0; /* delay from a GCE */
517 int user_input = 0; /* user input flag from a GCE */
518 int disposal = 0; /* disposal method from a GCE */
521 char *comment = NULL; /* a comment */
522 i_img **results = NULL;
523 int result_alloc = 0;
525 int image_colors = 0;
526 i_color black; /* used to expand the palette if needed */
528 for (i = 0; i < MAXCHANNELS; ++i)
529 black.channel[i] = 0;
533 mm_log((1,"i_readgif_multi_low(GifFile %p, , count %p)\n", GifFile, count));
535 BackGround = GifFile->SBackGroundColor;
537 Size = GifFile->SWidth * sizeof(GifPixelType);
539 GifRow = (GifRowType) mymalloc(Size);
541 /* Scan the content of the GIF file and load the image(s) in: */
543 if (DGifGetRecordType(GifFile, &RecordType) == GIF_ERROR) {
544 gif_push_error(myGifError(GifFile));
545 i_push_error(0, "Unable to get record type");
546 free_images(results, *count);
547 DGifCloseFile(GifFile);
554 switch (RecordType) {
555 case IMAGE_DESC_RECORD_TYPE:
556 if (DGifGetImageDesc(GifFile) == GIF_ERROR) {
557 gif_push_error(myGifError(GifFile));
558 i_push_error(0, "Unable to get image descriptor");
559 free_images(results, *count);
560 DGifCloseFile(GifFile);
567 Width = GifFile->Image.Width;
568 Height = GifFile->Image.Height;
569 if (page == -1 || page == ImageNum) {
570 if (( ColorMap = (GifFile->Image.ColorMap ? GifFile->Image.ColorMap : GifFile->SColorMap) )) {
571 mm_log((1, "Adding local colormap\n"));
572 ColorMapSize = ColorMap->ColorCount;
574 /* No colormap and we are about to read in the image -
576 mm_log((1, "Going in with no colormap\n"));
577 i_push_error(0, "Image does not have a local or a global color map");
578 free_images(results, *count);
579 DGifCloseFile(GifFile);
587 if (got_gce && trans_index >= 0)
589 if (!i_int_check_image_file_limits(Width, Height, channels, sizeof(i_sample_t))) {
590 free_images(results, *count);
591 mm_log((1, "i_readgif: image size exceeds limits\n"));
592 DGifCloseFile(GifFile);
598 img = i_img_pal_new(Width, Height, channels, 256);
600 free_images(results, *count);
601 DGifCloseFile(GifFile);
607 /* populate the palette of the new image */
608 mm_log((1, "ColorMapSize %d\n", ColorMapSize));
609 for (i = 0; i < ColorMapSize; ++i) {
611 col.rgba.r = ColorMap->Colors[i].Red;
612 col.rgba.g = ColorMap->Colors[i].Green;
613 col.rgba.b = ColorMap->Colors[i].Blue;
614 if (channels == 4 && trans_index == i)
619 i_addcolors(img, &col, 1);
621 image_colors = ColorMapSize;
623 if (*count > result_alloc) {
624 if (result_alloc == 0) {
626 results = mymalloc(result_alloc * sizeof(i_img *));
629 /* myrealloc never fails (it just dies if it can't allocate) */
631 results = myrealloc(results, result_alloc * sizeof(i_img *));
634 results[*count-1] = img;
635 i_tags_set(&img->tags, "i_format", "gif", -1);
636 i_tags_setn(&img->tags, "gif_left", GifFile->Image.Left);
638 i_tags_setn(&img->tags, "gif_top", GifFile->Image.Top);
639 i_tags_setn(&img->tags, "gif_interlace", GifFile->Image.Interlace);
640 i_tags_setn(&img->tags, "gif_screen_width", GifFile->SWidth);
641 i_tags_setn(&img->tags, "gif_screen_height", GifFile->SHeight);
642 i_tags_setn(&img->tags, "gif_colormap_size", ColorMapSize);
643 if (GifFile->SColorMap && !GifFile->Image.ColorMap) {
644 i_tags_setn(&img->tags, "gif_background",
645 GifFile->SBackGroundColor);
647 if (GifFile->Image.ColorMap) {
648 i_tags_setn(&img->tags, "gif_localmap", 1);
651 if (trans_index >= 0) {
653 i_tags_setn(&img->tags, "gif_trans_index", trans_index);
654 i_getcolors(img, trans_index, &trans, 1);
655 i_tags_set_color(&img->tags, "gif_trans_color", 0, &trans);
657 i_tags_setn(&img->tags, "gif_delay", gif_delay);
658 i_tags_setn(&img->tags, "gif_user_input", user_input);
659 i_tags_setn(&img->tags, "gif_disposal", disposal);
663 i_tags_setn(&img->tags, "gif_loop", ns_loop);
665 i_tags_set(&img->tags, "gif_comment", comment, strlen(comment));
670 mm_log((1,"i_readgif_multi_low: Image %d at (%d, %d) [%dx%d]: \n",
671 ImageNum, GifFile->Image.Left, GifFile->Image.Top, Width, Height));
673 if (GifFile->Image.Left + GifFile->Image.Width > GifFile->SWidth ||
674 GifFile->Image.Top + GifFile->Image.Height > GifFile->SHeight) {
675 i_push_errorf(0, "Image %d is not confined to screen dimension, aborted.\n",ImageNum);
676 free_images(results, *count);
677 DGifCloseFile(GifFile);
684 if (GifFile->Image.Interlace) {
685 for (Count = i = 0; i < 4; i++) {
686 for (j = InterlacedOffset[i]; j < Height;
687 j += InterlacedJumps[i]) {
689 if (DGifGetLine(GifFile, GifRow, Width) == GIF_ERROR) {
690 gif_push_error(myGifError(GifFile));
691 i_push_error(0, "Reading GIF line");
692 free_images(results, *count);
693 DGifCloseFile(GifFile);
700 /* range check the scanline if needed */
701 if (image_colors != 256) {
703 for (x = 0; x < Width; ++x) {
704 while (GifRow[x] >= image_colors) {
705 /* expand the palette since a palette index is too big */
706 i_addcolors(img, &black, 1);
712 i_ppal(img, 0, Width, j, GifRow);
717 for (i = 0; i < Height; i++) {
718 if (DGifGetLine(GifFile, GifRow, Width) == GIF_ERROR) {
719 gif_push_error(myGifError(GifFile));
720 i_push_error(0, "Reading GIF line");
721 free_images(results, *count);
722 DGifCloseFile(GifFile);
729 /* range check the scanline if needed */
730 if (image_colors != 256) {
732 for (x = 0; x < Width; ++x) {
733 while (GifRow[x] >= image_colors) {
734 /* expand the palette since a palette index is too big */
735 i_addcolors(img, &black, 1);
741 i_ppal(img, 0, Width, i, GifRow);
745 /* must be only one image wanted and that was it */
748 DGifCloseFile(GifFile);
756 /* whether interlaced or not, it has the same number of lines */
757 /* giflib does't have an interface to skip the image data */
758 for (i = 0; i < Height; i++) {
759 if (DGifGetLine(GifFile, GifRow, Width) == GIF_ERROR) {
760 gif_push_error(myGifError(GifFile));
761 i_push_error(0, "Reading GIF line");
762 free_images(results, *count);
764 DGifCloseFile(GifFile);
771 /* kill the comment so we get the right comment for the page */
779 case EXTENSION_RECORD_TYPE:
780 /* Skip any extension blocks in file: */
781 if (DGifGetExtension(GifFile, &ExtCode, &Extension) == GIF_ERROR) {
782 gif_push_error(myGifError(GifFile));
783 i_push_error(0, "Reading extension record");
784 free_images(results, *count);
786 DGifCloseFile(GifFile);
791 /* possibly this should be an error, but "be liberal in what you accept" */
794 if (ExtCode == 0xF9) {
796 if (Extension[1] & 1)
797 trans_index = Extension[4];
800 gif_delay = Extension[2] + 256 * Extension[3];
801 user_input = (Extension[1] & 2) != 0;
802 disposal = (Extension[1] >> 2) & 7;
804 if (ExtCode == 0xFF && *Extension == 11) {
805 if (memcmp(Extension+1, "NETSCAPE2.0", 11) == 0) {
806 if (DGifGetExtensionNext(GifFile, &Extension) == GIF_ERROR) {
807 gif_push_error(myGifError(GifFile));
808 i_push_error(0, "reading loop extension");
809 free_images(results, *count);
811 DGifCloseFile(GifFile);
816 if (Extension && *Extension == 3) {
818 ns_loop = Extension[2] + 256 * Extension[3];
822 else if (ExtCode == 0xFE) {
823 /* while it's possible for a GIF file to contain more than one
824 comment, I'm only implementing a single comment per image,
825 with the comment saved into the following image.
826 If someone wants more than that they can implement it.
827 I also don't handle comments that take more than one block.
830 comment = mymalloc(*Extension+1);
831 memcpy(comment, Extension+1, *Extension);
832 comment[*Extension] = '\0';
835 while (Extension != NULL) {
836 if (DGifGetExtensionNext(GifFile, &Extension) == GIF_ERROR) {
837 gif_push_error(myGifError(GifFile));
838 i_push_error(0, "reading next block of extension");
839 free_images(results, *count);
841 DGifCloseFile(GifFile);
848 case TERMINATE_RECORD_TYPE:
850 default: /* Should be trapped by DGifGetRecordType. */
853 } while (RecordType != TERMINATE_RECORD_TYPE);
857 i_tags_set(&(results[*count-1]->tags), "gif_comment", comment,
865 if (DGifCloseFile(GifFile) == GIF_ERROR) {
866 gif_push_error(myGifError(GifFile));
867 i_push_error(0, "Closing GIF file object");
868 free_images(results, *count);
872 if (ImageNum && page != -1) {
873 /* there were images, but the page selected wasn't found */
874 i_push_errorf(0, "page %d not found (%d total)", page, ImageNum);
875 free_images(results, *count);
882 static int io_glue_read_cb(GifFileType *gft, GifByteType *buf, int length);
885 =item i_readgif_multi_wiol(ig, int *count)
891 i_readgif_multi_wiol(io_glue *ig, int *count) {
892 GifFileType *GifFile;
896 gif_mutex_lock(mutex);
900 if ((GifFile = myDGifOpen((void *)ig, io_glue_read_cb, &gif_error )) == NULL) {
901 gif_push_error(gif_error);
902 i_push_error(0, "Cannot create giflib callback object");
903 mm_log((1,"i_readgif_multi_wiol: Unable to open callback datasource.\n"));
904 gif_mutex_unlock(mutex);
908 result = i_readgif_multi_low(GifFile, count, -1);
910 gif_mutex_unlock(mutex);
916 io_glue_read_cb(GifFileType *gft, GifByteType *buf, int length) {
917 io_glue *ig = (io_glue *)gft->UserData;
919 return i_io_read(ig, buf, length);
923 i_readgif_wiol(io_glue *ig, int **color_table, int *colors) {
924 GifFileType *GifFile;
928 gif_mutex_lock(mutex);
932 if ((GifFile = myDGifOpen((void *)ig, io_glue_read_cb, &gif_error )) == NULL) {
933 gif_push_error(gif_error);
934 i_push_error(0, "Cannot create giflib callback object");
935 mm_log((1,"i_readgif_wiol: Unable to open callback datasource.\n"));
936 gif_mutex_unlock(mutex);
940 result = i_readgif_low(GifFile, color_table, colors);
942 gif_mutex_unlock(mutex);
948 =item i_readgif_single_low(GifFile, page)
950 Lower level function to read a single image from a GIF.
952 page must be non-negative.
957 i_readgif_single_low(GifFileType *GifFile, int page) {
961 imgs = i_readgif_multi_low(GifFile, &count, page);
964 i_img *result = imgs[0];
970 /* i_readgif_multi_low() handles the errors appropriately */
976 =item i_readgif_single_wiol(ig, page)
978 Read a single page from a GIF image file, where the page is indexed
981 Returns NULL if the page isn't found.
987 i_readgif_single_wiol(io_glue *ig, int page) {
988 GifFileType *GifFile;
994 i_push_error(0, "page must be non-negative");
998 gif_mutex_lock(mutex);
1000 if ((GifFile = myDGifOpen((void *)ig, io_glue_read_cb, &gif_error )) == NULL) {
1001 gif_push_error(gif_error);
1002 i_push_error(0, "Cannot create giflib callback object");
1003 mm_log((1,"i_readgif_wiol: Unable to open callback datasource.\n"));
1004 gif_mutex_unlock(mutex);
1008 result = i_readgif_single_low(GifFile, page);
1010 gif_mutex_unlock(mutex);
1016 =item do_write(GifFileType *gf, i_gif_opts *opts, i_img *img, i_palidx *data)
1018 Internal. Low level image write function. Writes in interlace if
1019 that was requested in the GIF options.
1021 Returns non-zero on success.
1026 do_write(GifFileType *gf, int interlace, i_img *img, i_palidx *data) {
1029 for (i = 0; i < 4; ++i) {
1030 for (j = InterlacedOffset[i]; j < img->ysize; j += InterlacedJumps[i]) {
1031 if (EGifPutLine(gf, data+j*img->xsize, img->xsize) == GIF_ERROR) {
1032 gif_push_error(myGifError(gf));
1033 i_push_error(0, "Could not save image data:");
1034 mm_log((1, "Error in EGifPutLine\n"));
1043 for (y = 0; y < img->ysize; ++y) {
1044 if (EGifPutLine(gf, data, img->xsize) == GIF_ERROR) {
1045 gif_push_error(myGifError(gf));
1046 i_push_error(0, "Could not save image data:");
1047 mm_log((1, "Error in EGifPutLine\n"));
1059 =item do_gce(GifFileType *gf, int index, i_gif_opts *opts, int want_trans, int trans_index)
1061 Internal. Writes the GIF graphics control extension, if necessary.
1063 Returns non-zero on success.
1069 do_gce(GifFileType *gf, i_img *img, int want_trans, int trans_index)
1071 unsigned char gce[4] = {0};
1075 int disposal_method;
1079 gce[3] = trans_index;
1082 if (i_tags_get_int(&img->tags, "gif_delay", 0, &delay)) {
1083 gce[1] = delay % 256;
1084 gce[2] = delay / 256;
1087 if (i_tags_get_int(&img->tags, "gif_user_input", 0, &user_input)
1092 if (i_tags_get_int(&img->tags, "gif_disposal", 0, &disposal_method)) {
1093 gce[0] |= (disposal_method & 3) << 2;
1097 if (EGifPutExtension(gf, 0xF9, sizeof(gce), gce) == GIF_ERROR) {
1098 gif_push_error(myGifError(gf));
1099 i_push_error(0, "Could not save GCE");
1106 =item do_comments(gf, img)
1108 Write any comments in the image.
1114 do_comments(GifFileType *gf, i_img *img) {
1117 while (i_tags_find(&img->tags, "gif_comment", pos+1, &pos)) {
1118 if (img->tags.tags[pos].data) {
1119 if (EGifPutComment(gf, img->tags.tags[pos].data) == GIF_ERROR) {
1125 #ifdef IMAGER_SNPRINTF
1126 snprintf(buf, sizeof(buf), "%d", img->tags.tags[pos].idata);
1128 sprintf(buf, "%d", img->tags.tags[pos].idata);
1130 if (EGifPutComment(gf, buf) == GIF_ERROR) {
1140 =item do_ns_loop(GifFileType *gf, i_gif_opts *opts)
1142 Internal. Add the Netscape2.0 loop extension block, if requested.
1144 Giflib/libungif prior to 4.1.1 didn't support writing application
1145 extension blocks, so we don't attempt to write them for older versions.
1147 Giflib/libungif prior to 4.1.3 used the wrong write mechanism when
1148 writing extension blocks so that they could only be written to files.
1154 do_ns_loop(GifFileType *gf, i_img *img)
1156 /* EGifPutExtension() doesn't appear to handle application
1157 extension blocks in any way
1158 Since giflib wraps the fd with a FILE * (and puts that in its
1159 private data), we can't do an end-run and write the data
1161 There's no open interface that takes a FILE * either, so we
1162 can't workaround it that way either.
1163 If giflib's callback interface wasn't broken by default, I'd
1164 force file writes to use callbacks, but it is broken by default.
1166 /* yes this was another attempt at supporting the loop extension */
1168 if (i_tags_get_int(&img->tags, "gif_loop", 0, &loop_count)) {
1169 unsigned char nsle[12] = "NETSCAPE2.0";
1170 unsigned char subblock[3];
1173 subblock[1] = loop_count % 256;
1174 subblock[2] = loop_count / 256;
1176 #if defined(GIFLIB_MAJOR) && GIFLIB_MAJOR >= 5
1177 if (EGifPutExtensionLeader(gf, APPLICATION_EXT_FUNC_CODE) == GIF_ERROR
1178 || EGifPutExtensionBlock(gf, 11, nsle) == GIF_ERROR
1179 || EGifPutExtensionBlock(gf, 3, subblock) == GIF_ERROR
1180 || EGifPutExtensionTrailer(gf) == GIF_ERROR) {
1181 gif_push_error(myGifError(gf));
1182 i_push_error(0, "writing loop extension");
1187 if (EGifPutExtensionFirst(gf, APPLICATION_EXT_FUNC_CODE, 11, nsle) == GIF_ERROR) {
1188 gif_push_error(myGifError(gf));
1189 i_push_error(0, "writing loop extension");
1192 if (EGifPutExtensionLast(gf, APPLICATION_EXT_FUNC_CODE, 3, subblock) == GIF_ERROR) {
1193 gif_push_error(myGifError(gf));
1194 i_push_error(0, "writing loop extension sub-block");
1204 =item make_gif_map(i_quantize *quant, int want_trans)
1206 Create a giflib color map object from an Imager color map.
1211 static ColorMapObject *
1212 make_gif_map(i_quantize *quant, i_img *img, int want_trans) {
1213 GifColorType colors[256];
1215 int size = quant->mc_count;
1217 ColorMapObject *map;
1220 for (i = 0; i < quant->mc_count; ++i) {
1221 colors[i].Red = quant->mc_colors[i].rgb.r;
1222 colors[i].Green = quant->mc_colors[i].rgb.g;
1223 colors[i].Blue = quant->mc_colors[i].rgb.b;
1226 if (!i_tags_get_color(&img->tags, "gif_trans_color", 0, &trans))
1227 trans.rgb.r = trans.rgb.g = trans.rgb.b = 0;
1228 colors[size].Red = trans.rgb.r;
1229 colors[size].Green = trans.rgb.g;
1230 colors[size].Blue = trans.rgb.b;
1234 while (map_size < size)
1236 /* giflib spews for 1 colour maps, reasonable, I suppose */
1239 while (i < map_size) {
1240 colors[i].Red = colors[i].Green = colors[i].Blue = 0;
1244 map = MakeMapObject(map_size, colors);
1245 mm_log((1, "XXX map is at %p and colors at %p\n", map, map->Colors));
1247 i_push_error(0, "Could not create color map object");
1250 #if defined GIFLIB_MAJOR && GIFLIB_MAJOR >= 5
1257 =item need_version_89a(i_quantize *quant, i_img *imgs, int count)
1259 Return true if the file we're creating on these images needs a GIF89a
1266 need_version_89a(i_quantize *quant, i_img **imgs, int count) {
1271 for (i = 0; i < count; ++i) {
1272 if (quant->transp != tr_none &&
1273 (imgs[i]->channels == 2 || imgs[i]->channels == 4)) {
1277 if (i_tags_get_int(&imgs[i]->tags, "gif_delay", 0, &temp)) {
1281 if (i_tags_get_int(&imgs[i]->tags, "gif_user_input", 0, &temp) && temp) {
1285 if (i_tags_get_int(&imgs[i]->tags, "gif_disposal", 0, &temp)) {
1289 if (i_tags_get_int(&imgs[i]->tags, "gif_loop", 0, &temp)) {
1299 in_palette(i_color *c, i_quantize *quant, int size) {
1302 for (i = 0; i < size; ++i) {
1303 if (c->channel[0] == quant->mc_colors[i].channel[0]
1304 && c->channel[1] == quant->mc_colors[i].channel[1]
1305 && c->channel[2] == quant->mc_colors[i].channel[2]) {
1314 =item has_common_palette(imgs, count, quant)
1316 Tests if all the given images are paletted and their colors are in the
1319 Previously this would build a consolidated palette from the source,
1320 but that meant that if the caller supplied a static palette (or
1321 specified a fixed palette like "webmap") then we wouldn't be
1322 quantizing to the caller specified palette.
1328 has_common_palette(i_img **imgs, int count, i_quantize *quant) {
1334 /* we try to build a common palette here, if we can manage that, then
1335 that's the palette we use */
1336 for (imgn = 0; imgn < count; ++imgn) {
1337 int eliminate_unused;
1338 if (imgs[imgn]->type != i_palette_type)
1341 if (!i_tags_get_int(&imgs[imgn]->tags, "gif_eliminate_unused", 0,
1342 &eliminate_unused)) {
1343 eliminate_unused = 1;
1346 if (eliminate_unused) {
1347 i_palidx *line = mymalloc(sizeof(i_palidx) * imgs[imgn]->xsize);
1349 memset(used, 0, sizeof(used));
1351 for (y = 0; y < imgs[imgn]->ysize; ++y) {
1352 i_gpal(imgs[imgn], 0, imgs[imgn]->xsize, y, line);
1353 for (x = 0; x < imgs[imgn]->xsize; ++x)
1360 /* assume all are in use */
1361 memset(used, 1, sizeof(used));
1364 col_count = i_colorcount(imgs[imgn]);
1365 for (i = 0; i < col_count; ++i) {
1368 i_getcolors(imgs[imgn], i, &c, 1);
1370 if (in_palette(&c, quant, quant->mc_count) < 0) {
1371 mm_log((1, " color not found in palette, no palette shortcut\n"));
1379 mm_log((1, " all colors found in palette, palette shortcut\n"));
1385 quant_paletted(i_quantize *quant, i_img *img) {
1386 i_palidx *data = mymalloc(sizeof(i_palidx) * img->xsize * img->ysize);
1388 i_palidx trans[256];
1392 /* build a translation table */
1393 for (i = 0; i < i_colorcount(img); ++i) {
1395 i_getcolors(img, i, &c, 1);
1396 trans[i] = in_palette(&c, quant, quant->mc_count);
1399 for (y = 0; y < img->ysize; ++y) {
1400 i_gpal(img, 0, img->xsize, y, data+img->xsize * y);
1401 for (x = 0; x < img->xsize; ++x) {
1411 =item i_writegif_low(i_quantize *quant, GifFileType *gf, i_img **imgs, int count, i_gif_opts *opts)
1413 Internal. Low-level function that does the high-level GIF processing
1416 Returns non-zero on success.
1422 i_writegif_low(i_quantize *quant, GifFileType *gf, i_img **imgs, int count) {
1423 unsigned char *result = NULL;
1425 ColorMapObject *map;
1426 int scrw = 0, scrh = 0;
1427 int imgn, orig_count, orig_size;
1429 int trans_index = -1;
1432 i_img **glob_imgs; /* images that will use the global color map */
1434 i_color *orig_colors = quant->mc_colors;
1435 i_color *glob_colors = NULL;
1436 int glob_color_count = 0;
1437 int glob_want_trans;
1438 int glob_paletted = 0; /* the global map was made from the image palettes */
1439 int colors_paletted = 0;
1444 mm_log((1, "i_writegif_low(quant %p, gf %p, imgs %p, count %d)\n",
1445 quant, gf, imgs, count));
1447 /* *((char *)0) = 1; */ /* used to break into the debugger */
1450 i_push_error(0, "No images provided to write");
1451 return 0; /* what are you smoking? */
1454 /* sanity is nice */
1455 if (quant->mc_size > 256)
1456 quant->mc_size = 256;
1457 if (quant->mc_count > quant->mc_size)
1458 quant->mc_count = quant->mc_size;
1460 if (!i_tags_get_int(&imgs[0]->tags, "gif_screen_width", 0, &scrw))
1462 if (!i_tags_get_int(&imgs[0]->tags, "gif_screen_height", 0, &scrh))
1466 localmaps = mymalloc(sizeof(int) * count);
1467 glob_imgs = mymalloc(sizeof(i_img *) * count);
1469 glob_want_trans = 0;
1470 for (imgn = 0; imgn < count; ++imgn) {
1471 i_img *im = imgs[imgn];
1472 if (im->xsize > 0xFFFF || im->ysize > 0xFFFF) {
1473 i_push_error(0, "image too large for GIF");
1478 i_tags_get_int(&im->tags, "gif_left", 0, &posx);
1479 if (posx < 0) posx = 0;
1480 i_tags_get_int(&im->tags, "gif_top", 0, &posy);
1481 if (posy < 0) posy = 0;
1482 if (im->xsize + posx > scrw)
1483 scrw = im->xsize + posx;
1484 if (im->ysize + posy > scrh)
1485 scrh = im->ysize + posy;
1486 if (!i_tags_get_int(&im->tags, "gif_local_map", 0, localmaps+imgn))
1487 localmaps[imgn] = 0;
1488 if (localmaps[imgn])
1491 if (im->channels == 4) {
1492 glob_want_trans = 1;
1494 glob_imgs[glob_img_count++] = im;
1497 glob_want_trans = glob_want_trans && quant->transp != tr_none ;
1499 if (scrw > 0xFFFF || scrh > 0xFFFF) {
1500 i_push_error(0, "screen size too large for GIF");
1504 orig_count = quant->mc_count;
1505 orig_size = quant->mc_size;
1507 if (glob_img_count) {
1509 glob_colors = mymalloc(sizeof(i_color) * quant->mc_size);
1510 quant->mc_colors = glob_colors;
1511 memcpy(glob_colors, orig_colors, sizeof(i_color) * quant->mc_count);
1512 /* we have some images that want to use the global map */
1513 if (glob_want_trans && quant->mc_count == 256) {
1514 mm_log((2, " disabling transparency for global map - no space\n"));
1515 glob_want_trans = 0;
1517 if (glob_want_trans && quant->mc_size == 256) {
1518 mm_log((2, " reserving color for transparency\n"));
1522 i_quant_makemap(quant, glob_imgs, glob_img_count);
1523 glob_paletted = has_common_palette(glob_imgs, glob_img_count, quant);
1524 glob_color_count = quant->mc_count;
1525 quant->mc_colors = orig_colors;
1528 /* use the global map if we have one, otherwise use the local map */
1531 quant->mc_colors = glob_colors;
1532 quant->mc_count = glob_color_count;
1533 want_trans = glob_want_trans && imgs[0]->channels == 4;
1535 if (!i_tags_get_int(&imgs[0]->tags, "gif_background", 0, &gif_background))
1537 if (gif_background < 0)
1539 if (gif_background >= glob_color_count)
1543 want_trans = quant->transp != tr_none && imgs[0]->channels == 4;
1544 i_quant_makemap(quant, imgs, 1);
1545 colors_paletted = has_common_palette(imgs, 1, quant);
1548 if ((map = make_gif_map(quant, imgs[0], want_trans)) == NULL) {
1549 myfree(glob_colors);
1552 quant->mc_colors = orig_colors;
1554 mm_log((1, "Error in MakeMapObject"));
1559 /* since we don't know how big some the local palettes could be
1560 we need to base the bits on the maximum number of colors */
1561 while (orig_size > (1 << color_bits))
1565 int count = quant->mc_count;
1568 while (count > (1 << color_bits))
1572 if (EGifPutScreenDesc(gf, scrw, scrh, color_bits,
1573 gif_background, map) == GIF_ERROR) {
1574 myfree(glob_colors);
1577 quant->mc_colors = orig_colors;
1578 gif_push_error(myGifError(gf));
1579 i_push_error(0, "Could not save screen descriptor");
1583 mm_log((1, "Error in EGifPutScreenDesc."));
1588 if (!i_tags_get_int(&imgs[0]->tags, "gif_left", 0, &posx))
1590 if (!i_tags_get_int(&imgs[0]->tags, "gif_top", 0, &posy))
1593 if (!localmaps[0]) {
1595 colors_paletted = glob_paletted;
1598 /* if this image has a global map the colors in quant don't
1599 belong to this image, so build a palette */
1601 /* generate the local map for this image */
1602 quant->mc_colors = orig_colors;
1603 quant->mc_size = orig_size;
1604 quant->mc_count = orig_count;
1605 want_trans = quant->transp != tr_none && imgs[0]->channels == 4;
1607 /* if the caller gives us too many colours we can't do transparency */
1608 if (want_trans && quant->mc_count == 256)
1610 /* if they want transparency but give us a big size, make it smaller
1611 to give room for a transparency colour */
1612 if (want_trans && quant->mc_size == 256)
1614 i_quant_makemap(quant, imgs, 1);
1615 colors_paletted = has_common_palette(imgs, 1, quant);
1616 if ((map = make_gif_map(quant, imgs[0], want_trans)) == NULL) {
1617 myfree(glob_colors);
1621 quant->mc_colors = orig_colors;
1622 mm_log((1, "Error in MakeMapObject"));
1627 /* the map we wrote was the map for this image - don't set the local
1633 if (colors_paletted)
1634 result = quant_paletted(quant, imgs[0]);
1636 result = i_quant_translate(quant, imgs[0]);
1638 myfree(glob_colors);
1641 quant->mc_colors = orig_colors;
1646 i_quant_transparent(quant, result, imgs[0], quant->mc_count);
1647 trans_index = quant->mc_count;
1650 if (!do_ns_loop(gf, imgs[0])) {
1651 myfree(glob_colors);
1654 quant->mc_colors = orig_colors;
1658 if (!do_gce(gf, imgs[0], want_trans, trans_index)) {
1659 myfree(glob_colors);
1662 quant->mc_colors = orig_colors;
1668 if (!do_comments(gf, imgs[0])) {
1669 myfree(glob_colors);
1672 quant->mc_colors = orig_colors;
1678 if (!i_tags_get_int(&imgs[0]->tags, "gif_interlace", 0, &interlace))
1680 if (EGifPutImageDesc(gf, posx, posy, imgs[0]->xsize, imgs[0]->ysize,
1681 interlace, map) == GIF_ERROR) {
1682 myfree(glob_colors);
1685 quant->mc_colors = orig_colors;
1686 gif_push_error(myGifError(gf));
1687 i_push_error(0, "Could not save image descriptor");
1689 mm_log((1, "Error in EGifPutImageDesc."));
1695 if (!do_write(gf, interlace, imgs[0], result)) {
1696 myfree(glob_colors);
1699 quant->mc_colors = orig_colors;
1706 /* that first awful image is out of the way, do the rest */
1707 for (imgn = 1; imgn < count; ++imgn) {
1708 if (localmaps[imgn]) {
1709 quant->mc_colors = orig_colors;
1710 quant->mc_count = orig_count;
1711 quant->mc_size = orig_size;
1713 want_trans = quant->transp != tr_none
1714 && imgs[imgn]->channels == 4;
1715 /* if the caller gives us too many colours we can't do transparency */
1716 if (want_trans && quant->mc_count == 256)
1718 /* if they want transparency but give us a big size, make it smaller
1719 to give room for a transparency colour */
1720 if (want_trans && quant->mc_size == 256)
1723 if (has_common_palette(imgs+imgn, 1, quant)) {
1724 result = quant_paletted(quant, imgs[imgn]);
1727 i_quant_makemap(quant, imgs+imgn, 1);
1728 result = i_quant_translate(quant, imgs[imgn]);
1731 myfree(glob_colors);
1734 quant->mc_colors = orig_colors;
1736 mm_log((1, "error in i_quant_translate()"));
1740 i_quant_transparent(quant, result, imgs[imgn], quant->mc_count);
1741 trans_index = quant->mc_count;
1744 if ((map = make_gif_map(quant, imgs[imgn], want_trans)) == NULL) {
1745 myfree(glob_colors);
1748 quant->mc_colors = orig_colors;
1751 mm_log((1, "Error in MakeMapObject."));
1756 quant->mc_colors = glob_colors;
1757 quant->mc_count = glob_color_count;
1759 result = quant_paletted(quant, imgs[imgn]);
1761 result = i_quant_translate(quant, imgs[imgn]);
1762 want_trans = glob_want_trans && imgs[imgn]->channels == 4;
1764 i_quant_transparent(quant, result, imgs[imgn], quant->mc_count);
1765 trans_index = quant->mc_count;
1770 if (!do_gce(gf, imgs[imgn], want_trans, trans_index)) {
1771 myfree(glob_colors);
1774 quant->mc_colors = orig_colors;
1780 if (!do_comments(gf, imgs[imgn])) {
1781 myfree(glob_colors);
1784 quant->mc_colors = orig_colors;
1790 if (!i_tags_get_int(&imgs[imgn]->tags, "gif_left", 0, &posx))
1792 if (!i_tags_get_int(&imgs[imgn]->tags, "gif_top", 0, &posy))
1795 if (!i_tags_get_int(&imgs[imgn]->tags, "gif_interlace", 0, &interlace))
1797 if (EGifPutImageDesc(gf, posx, posy, imgs[imgn]->xsize,
1798 imgs[imgn]->ysize, interlace, map) == GIF_ERROR) {
1799 myfree(glob_colors);
1802 quant->mc_colors = orig_colors;
1803 gif_push_error(myGifError(gf));
1804 i_push_error(0, "Could not save image descriptor");
1809 mm_log((1, "Error in EGifPutImageDesc."));
1815 if (!do_write(gf, interlace, imgs[imgn], result)) {
1816 myfree(glob_colors);
1819 quant->mc_colors = orig_colors;
1827 if (EGifCloseFile(gf) == GIF_ERROR) {
1828 myfree(glob_colors);
1831 gif_push_error(myGifError(gf));
1832 i_push_error(0, "Could not close GIF file");
1833 mm_log((1, "Error in EGifCloseFile\n"));
1838 for (i = 0; i < glob_color_count; ++i)
1839 orig_colors[i] = glob_colors[i];
1842 myfree(glob_colors);
1845 quant->mc_colors = orig_colors;
1851 io_glue_write_cb(GifFileType *gft, const GifByteType *data, int length) {
1852 io_glue *ig = (io_glue *)gft->UserData;
1854 return i_io_write(ig, data, length);
1859 =item i_writegif_wiol(ig, quant, opts, imgs, count)
1864 i_writegif_wiol(io_glue *ig, i_quantize *quant, i_img **imgs,
1866 GifFileType *GifFile;
1870 gif_mutex_lock(mutex);
1874 #ifdef PRE_SET_VERSION
1875 EGifSetGifVersion(need_version_89a(quant, imgs, count) ? "89a" : "87a");
1878 if ((GifFile = myEGifOpen((void *)ig, io_glue_write_cb, &gif_error )) == NULL) {
1879 gif_push_error(gif_error);
1880 i_push_error(0, "Cannot create giflib callback object");
1881 mm_log((1,"i_writegif_wiol: Unable to open callback datasource.\n"));
1882 gif_mutex_unlock(mutex);
1886 #ifdef POST_SET_VERSION
1887 EGifSetGifVersion(GifFile, need_version_89a(quant, imgs, count));
1890 result = i_writegif_low(quant, GifFile, imgs, count);
1892 gif_mutex_unlock(mutex);
1901 =item gif_error_msg(int code)
1903 Grabs the most recent giflib error code from GifLastError() and
1904 returns a string that describes that error.
1906 Returns NULL for unknown error codes.
1912 gif_error_msg(int code) {
1913 #if defined(GIFLIB_MAJOR) && GIFLIB_MAJOR >= 5
1914 return GifErrorString(code);
1917 case E_GIF_ERR_OPEN_FAILED: /* should not see this */
1918 return "Failed to open given file";
1920 case E_GIF_ERR_WRITE_FAILED:
1921 return "Write failed";
1923 case E_GIF_ERR_HAS_SCRN_DSCR: /* should not see this */
1924 return "Screen descriptor already passed to giflib";
1926 case E_GIF_ERR_HAS_IMAG_DSCR: /* should not see this */
1927 return "Image descriptor already passed to giflib";
1929 case E_GIF_ERR_NO_COLOR_MAP: /* should not see this */
1930 return "Neither global nor local color map set";
1932 case E_GIF_ERR_DATA_TOO_BIG: /* should not see this */
1933 return "Too much pixel data passed to giflib";
1935 case E_GIF_ERR_NOT_ENOUGH_MEM:
1936 return "Out of memory";
1938 case E_GIF_ERR_DISK_IS_FULL:
1939 return "Disk is full";
1941 case E_GIF_ERR_CLOSE_FAILED: /* should not see this */
1942 return "File close failed";
1944 case E_GIF_ERR_NOT_WRITEABLE: /* should not see this */
1945 return "File not writable";
1947 case D_GIF_ERR_OPEN_FAILED:
1948 return "Failed to open file";
1950 case D_GIF_ERR_READ_FAILED:
1951 return "Failed to read from file";
1953 case D_GIF_ERR_NOT_GIF_FILE:
1954 return "File is not a GIF file";
1956 case D_GIF_ERR_NO_SCRN_DSCR:
1957 return "No screen descriptor detected - invalid file";
1959 case D_GIF_ERR_NO_IMAG_DSCR:
1960 return "No image descriptor detected - invalid file";
1962 case D_GIF_ERR_NO_COLOR_MAP:
1963 return "No global or local color map found";
1965 case D_GIF_ERR_WRONG_RECORD:
1966 return "Wrong record type detected - invalid file?";
1968 case D_GIF_ERR_DATA_TOO_BIG:
1969 return "Data in file too big for image";
1971 case D_GIF_ERR_NOT_ENOUGH_MEM:
1972 return "Out of memory";
1974 case D_GIF_ERR_CLOSE_FAILED:
1975 return "Close failed";
1977 case D_GIF_ERR_NOT_READABLE:
1978 return "File not opened for read";
1980 case D_GIF_ERR_IMAGE_DEFECT:
1981 return "Defective image";
1983 case D_GIF_ERR_EOF_TOO_SOON:
1984 return "Unexpected EOF - invalid file";
1993 =item gif_push_error(code)
1995 Utility function that takes the current GIF error code, converts it to
1996 an error message and pushes it on the error stack.
2002 gif_push_error(int code) {
2003 const char *msg = gif_error_msg(code);
2005 i_push_error(code, msg);
2007 i_push_errorf(code, "Unknown GIF error %d", code);
2013 Arnar M. Hrafnkelsson, addi@umich.edu
2015 Tony Cook <tonyc@cpan.org>