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 static char const *gif_error_msg(int code);
63 static void gif_push_error(void);
65 /* Make some variables global, so we could access them faster: */
68 InterlacedOffset[] = { 0, 4, 2, 1 }, /* The way Interlaced image should. */
69 InterlacedJumps[] = { 8, 8, 4, 2 }; /* be read - offsets and jumps... */
75 i_colortable_copy(int **colour_table, int *colours, ColorMapObject *colourmap) {
76 GifColorType *mapentry;
78 int colourmapsize = colourmap->ColorCount;
80 if(colours) *colours = colourmapsize;
81 if(!colour_table) return;
83 *colour_table = mymalloc(sizeof(int) * colourmapsize * 3);
84 memset(*colour_table, 0, sizeof(int) * colourmapsize * 3);
86 for(q=0; q<colourmapsize; q++) {
87 mapentry = &colourmap->Colors[q];
88 (*colour_table)[q*3 + 0] = mapentry->Red;
89 (*colour_table)[q*3 + 1] = mapentry->Green;
90 (*colour_table)[q*3 + 2] = mapentry->Blue;
95 char gif_version_str[] = GIF_LIB_VERSION;
98 i_giflib_version(void) {
99 const char *p = gif_version_str;
101 while (*p && (*p < '0' || *p > '9'))
107 return strtod(p, NULL);
111 =item i_readgif_low(GifFileType *GifFile, int **colour_table, int *colours)
113 Internal. Low-level function for reading a GIF file. The caller must
114 create the appropriate GifFileType object and pass it in.
120 i_readgif_low(GifFileType *GifFile, int **colour_table, int *colours) {
122 int i, j, Size, Row, Col, Width, Height, ExtCode, Count, x;
123 int cmapcnt = 0, ImageNum = 0, BackGround = 0, ColorMapSize = 0;
124 ColorMapObject *ColorMap;
126 GifRecordType RecordType;
127 GifByteType *Extension;
130 static GifColorType *ColorMapEntry;
133 mm_log((1,"i_readgif_low(GifFile %p, colour_table %p, colours %p)\n", GifFile, colour_table, colours));
135 /* it's possible that the caller has called us with *colour_table being
136 non-NULL, but we check that to see if we need to free an allocated
137 colour table on error.
139 if (colour_table) *colour_table = NULL;
141 BackGround = GifFile->SBackGroundColor;
142 ColorMap = (GifFile->Image.ColorMap ? GifFile->Image.ColorMap : GifFile->SColorMap);
145 ColorMapSize = ColorMap->ColorCount;
146 i_colortable_copy(colour_table, colours, ColorMap);
150 if (!i_int_check_image_file_limits(GifFile->SWidth, GifFile->SHeight, 3, sizeof(i_sample_t))) {
151 if (colour_table && *colour_table) {
152 myfree(*colour_table);
153 *colour_table = NULL;
155 DGifCloseFile(GifFile);
156 mm_log((1, "i_readgif: image size exceeds limits\n"));
160 im = i_img_8_new(GifFile->SWidth, GifFile->SHeight, 3);
162 if (colour_table && *colour_table) {
163 myfree(*colour_table);
164 *colour_table = NULL;
166 DGifCloseFile(GifFile);
170 Size = GifFile->SWidth * sizeof(GifPixelType);
172 GifRow = mymalloc(Size);
174 for (i = 0; i < GifFile->SWidth; i++) GifRow[i] = GifFile->SBackGroundColor;
176 /* Scan the content of the GIF file and load the image(s) in: */
178 if (DGifGetRecordType(GifFile, &RecordType) == GIF_ERROR) {
180 i_push_error(0, "Unable to get record type");
181 if (colour_table && *colour_table) {
182 myfree(*colour_table);
183 *colour_table = NULL;
187 DGifCloseFile(GifFile);
191 switch (RecordType) {
192 case IMAGE_DESC_RECORD_TYPE:
193 if (DGifGetImageDesc(GifFile) == GIF_ERROR) {
195 i_push_error(0, "Unable to get image descriptor");
196 if (colour_table && *colour_table) {
197 myfree(*colour_table);
198 *colour_table = NULL;
202 DGifCloseFile(GifFile);
206 if (( ColorMap = (GifFile->Image.ColorMap ? GifFile->Image.ColorMap : GifFile->SColorMap) )) {
207 mm_log((1, "Adding local colormap\n"));
208 ColorMapSize = ColorMap->ColorCount;
210 i_colortable_copy(colour_table, colours, ColorMap);
214 /* No colormap and we are about to read in the image - abandon for now */
215 mm_log((1, "Going in with no colormap\n"));
216 i_push_error(0, "Image does not have a local or a global color map");
217 /* we can't have allocated a colour table here */
220 DGifCloseFile(GifFile);
224 Row = GifFile->Image.Top; /* Image Position relative to Screen. */
225 Col = GifFile->Image.Left;
226 Width = GifFile->Image.Width;
227 Height = GifFile->Image.Height;
229 mm_log((1,"i_readgif_low: Image %d at (%d, %d) [%dx%d]: \n",ImageNum, Col, Row, Width, Height));
231 if (GifFile->Image.Left + GifFile->Image.Width > GifFile->SWidth ||
232 GifFile->Image.Top + GifFile->Image.Height > GifFile->SHeight) {
233 i_push_errorf(0, "Image %d is not confined to screen dimension, aborted.\n",ImageNum);
234 if (colour_table && *colour_table) {
235 myfree(*colour_table);
236 *colour_table = NULL;
240 DGifCloseFile(GifFile);
243 if (GifFile->Image.Interlace) {
245 for (Count = i = 0; i < 4; i++) for (j = Row + InterlacedOffset[i]; j < Row + Height; j += InterlacedJumps[i]) {
247 if (DGifGetLine(GifFile, GifRow, Width) == GIF_ERROR) {
249 i_push_error(0, "Reading GIF line");
250 if (colour_table && *colour_table) {
251 myfree(*colour_table);
252 *colour_table = NULL;
256 DGifCloseFile(GifFile);
260 for (x = 0; x < Width; x++) {
261 ColorMapEntry = &ColorMap->Colors[GifRow[x]];
262 col.rgb.r = ColorMapEntry->Red;
263 col.rgb.g = ColorMapEntry->Green;
264 col.rgb.b = ColorMapEntry->Blue;
265 i_ppix(im,Col+x,j,&col);
271 for (i = 0; i < Height; i++) {
272 if (DGifGetLine(GifFile, GifRow, Width) == GIF_ERROR) {
274 i_push_error(0, "Reading GIF line");
275 if (colour_table && *colour_table) {
276 myfree(*colour_table);
277 *colour_table = NULL;
281 DGifCloseFile(GifFile);
285 for (x = 0; x < Width; x++) {
286 ColorMapEntry = &ColorMap->Colors[GifRow[x]];
287 col.rgb.r = ColorMapEntry->Red;
288 col.rgb.g = ColorMapEntry->Green;
289 col.rgb.b = ColorMapEntry->Blue;
290 i_ppix(im, Col+x, Row, &col);
296 case EXTENSION_RECORD_TYPE:
297 /* Skip any extension blocks in file: */
298 if (DGifGetExtension(GifFile, &ExtCode, &Extension) == GIF_ERROR) {
300 i_push_error(0, "Reading extension record");
301 if (colour_table && *colour_table) {
302 myfree(*colour_table);
303 *colour_table = NULL;
307 DGifCloseFile(GifFile);
310 while (Extension != NULL) {
311 if (DGifGetExtensionNext(GifFile, &Extension) == GIF_ERROR) {
313 i_push_error(0, "reading next block of extension");
314 if (colour_table && *colour_table) {
315 myfree(*colour_table);
316 *colour_table = NULL;
320 DGifCloseFile(GifFile);
325 case TERMINATE_RECORD_TYPE:
327 default: /* Should be traps by DGifGetRecordType. */
330 } while (RecordType != TERMINATE_RECORD_TYPE);
334 if (DGifCloseFile(GifFile) == GIF_ERROR) {
336 i_push_error(0, "Closing GIF file object");
337 if (colour_table && *colour_table) {
338 myfree(*colour_table);
339 *colour_table = NULL;
345 i_tags_set(&im->tags, "i_format", "gif", -1);
352 Internal function called by i_readgif_multi_low() in error handling
355 static void free_images(i_img **imgs, int count) {
359 for (i = 0; i < count; ++i)
360 i_img_destroy(imgs[i]);
366 =item i_readgif_multi_low(GifFileType *gf, int *count, int page)
368 Reads one of more gif images from the given GIF file.
370 Returns a pointer to an array of i_img *, and puts the count into
373 If page is not -1 then the given image _only_ is returned from the
374 file, where the first image is 0, the second 1 and so on.
376 Unlike the normal i_readgif*() functions the images are paletted
377 images rather than a combined RGB image.
379 This functions sets tags on the images returned:
385 the offset of the image from the left of the "screen" ("Image Left
390 the offset of the image from the top of the "screen" ("Image Top Position")
394 non-zero if the image was interlaced ("Interlace Flag")
396 =item gif_screen_width
398 =item gif_screen_height
400 the size of the logical screen ("Logical Screen Width",
401 "Logical Screen Height")
405 Non-zero if this image had a local color map.
409 The index in the global colormap of the logical screen's background
410 color. This is only set if the current image uses the global
413 =item gif_trans_index
415 The index of the color in the colormap used for transparency. If the
416 image has a transparency then it is returned as a 4 channel image with
417 the alpha set to zero in this palette entry. ("Transparent Color Index")
421 The delay until the next frame is displayed, in 1/100 of a second.
426 whether or not a user input is expected before continuing (view dependent)
431 how the next frame is displayed ("Disposal Method")
435 the number of loops from the Netscape Loop extension. This may be zero.
439 the first block of the first gif comment before each image.
443 Where applicable, the ("name") is the name of that field from the GIF89
449 i_img **i_readgif_multi_low(GifFileType *GifFile, int *count, int page) {
451 int i, j, Size, Width, Height, ExtCode, Count;
452 int ImageNum = 0, BackGround = 0, ColorMapSize = 0;
453 ColorMapObject *ColorMap;
455 GifRecordType RecordType;
456 GifByteType *Extension;
460 int trans_index = 0; /* transparent index if we see a GCE */
461 int gif_delay = 0; /* delay from a GCE */
462 int user_input = 0; /* user input flag from a GCE */
463 int disposal = 0; /* disposal method from a GCE */
466 char *comment = NULL; /* a comment */
467 i_img **results = NULL;
468 int result_alloc = 0;
470 int image_colors = 0;
471 i_color black; /* used to expand the palette if needed */
473 for (i = 0; i < MAXCHANNELS; ++i)
474 black.channel[i] = 0;
478 mm_log((1,"i_readgif_multi_low(GifFile %p, , count %p)\n", GifFile, count));
480 BackGround = GifFile->SBackGroundColor;
482 Size = GifFile->SWidth * sizeof(GifPixelType);
484 GifRow = (GifRowType) mymalloc(Size);
486 /* Scan the content of the GIF file and load the image(s) in: */
488 if (DGifGetRecordType(GifFile, &RecordType) == GIF_ERROR) {
490 i_push_error(0, "Unable to get record type");
491 free_images(results, *count);
492 DGifCloseFile(GifFile);
499 switch (RecordType) {
500 case IMAGE_DESC_RECORD_TYPE:
501 if (DGifGetImageDesc(GifFile) == GIF_ERROR) {
503 i_push_error(0, "Unable to get image descriptor");
504 free_images(results, *count);
505 DGifCloseFile(GifFile);
512 Width = GifFile->Image.Width;
513 Height = GifFile->Image.Height;
514 if (page == -1 || page == ImageNum) {
515 if (( ColorMap = (GifFile->Image.ColorMap ? GifFile->Image.ColorMap : GifFile->SColorMap) )) {
516 mm_log((1, "Adding local colormap\n"));
517 ColorMapSize = ColorMap->ColorCount;
519 /* No colormap and we are about to read in the image -
521 mm_log((1, "Going in with no colormap\n"));
522 i_push_error(0, "Image does not have a local or a global color map");
523 free_images(results, *count);
524 DGifCloseFile(GifFile);
532 if (got_gce && trans_index >= 0)
534 if (!i_int_check_image_file_limits(Width, Height, channels, sizeof(i_sample_t))) {
535 free_images(results, *count);
536 mm_log((1, "i_readgif: image size exceeds limits\n"));
537 DGifCloseFile(GifFile);
543 img = i_img_pal_new(Width, Height, channels, 256);
545 free_images(results, *count);
546 DGifCloseFile(GifFile);
552 /* populate the palette of the new image */
553 mm_log((1, "ColorMapSize %d\n", ColorMapSize));
554 for (i = 0; i < ColorMapSize; ++i) {
556 col.rgba.r = ColorMap->Colors[i].Red;
557 col.rgba.g = ColorMap->Colors[i].Green;
558 col.rgba.b = ColorMap->Colors[i].Blue;
559 if (channels == 4 && trans_index == i)
564 i_addcolors(img, &col, 1);
566 image_colors = ColorMapSize;
568 if (*count > result_alloc) {
569 if (result_alloc == 0) {
571 results = mymalloc(result_alloc * sizeof(i_img *));
574 /* myrealloc never fails (it just dies if it can't allocate) */
576 results = myrealloc(results, result_alloc * sizeof(i_img *));
579 results[*count-1] = img;
580 i_tags_set(&img->tags, "i_format", "gif", -1);
581 i_tags_setn(&img->tags, "gif_left", GifFile->Image.Left);
583 i_tags_setn(&img->tags, "gif_top", GifFile->Image.Top);
584 i_tags_setn(&img->tags, "gif_interlace", GifFile->Image.Interlace);
585 i_tags_setn(&img->tags, "gif_screen_width", GifFile->SWidth);
586 i_tags_setn(&img->tags, "gif_screen_height", GifFile->SHeight);
587 i_tags_setn(&img->tags, "gif_colormap_size", ColorMapSize);
588 if (GifFile->SColorMap && !GifFile->Image.ColorMap) {
589 i_tags_setn(&img->tags, "gif_background",
590 GifFile->SBackGroundColor);
592 if (GifFile->Image.ColorMap) {
593 i_tags_setn(&img->tags, "gif_localmap", 1);
596 if (trans_index >= 0) {
598 i_tags_setn(&img->tags, "gif_trans_index", trans_index);
599 i_getcolors(img, trans_index, &trans, 1);
600 i_tags_set_color(&img->tags, "gif_trans_color", 0, &trans);
602 i_tags_setn(&img->tags, "gif_delay", gif_delay);
603 i_tags_setn(&img->tags, "gif_user_input", user_input);
604 i_tags_setn(&img->tags, "gif_disposal", disposal);
608 i_tags_setn(&img->tags, "gif_loop", ns_loop);
610 i_tags_set(&img->tags, "gif_comment", comment, strlen(comment));
615 mm_log((1,"i_readgif_multi_low: Image %d at (%d, %d) [%dx%d]: \n",
616 ImageNum, GifFile->Image.Left, GifFile->Image.Top, Width, Height));
618 if (GifFile->Image.Left + GifFile->Image.Width > GifFile->SWidth ||
619 GifFile->Image.Top + GifFile->Image.Height > GifFile->SHeight) {
620 i_push_errorf(0, "Image %d is not confined to screen dimension, aborted.\n",ImageNum);
621 free_images(results, *count);
622 DGifCloseFile(GifFile);
629 if (GifFile->Image.Interlace) {
630 for (Count = i = 0; i < 4; i++) {
631 for (j = InterlacedOffset[i]; j < Height;
632 j += InterlacedJumps[i]) {
634 if (DGifGetLine(GifFile, GifRow, Width) == GIF_ERROR) {
636 i_push_error(0, "Reading GIF line");
637 free_images(results, *count);
638 DGifCloseFile(GifFile);
645 /* range check the scanline if needed */
646 if (image_colors != 256) {
648 for (x = 0; x < Width; ++x) {
649 while (GifRow[x] >= image_colors) {
650 /* expand the palette since a palette index is too big */
651 i_addcolors(img, &black, 1);
657 i_ppal(img, 0, Width, j, GifRow);
662 for (i = 0; i < Height; i++) {
663 if (DGifGetLine(GifFile, GifRow, Width) == GIF_ERROR) {
665 i_push_error(0, "Reading GIF line");
666 free_images(results, *count);
667 DGifCloseFile(GifFile);
674 /* range check the scanline if needed */
675 if (image_colors != 256) {
677 for (x = 0; x < Width; ++x) {
678 while (GifRow[x] >= image_colors) {
679 /* expand the palette since a palette index is too big */
680 i_addcolors(img, &black, 1);
686 i_ppal(img, 0, Width, i, GifRow);
690 /* must be only one image wanted and that was it */
693 DGifCloseFile(GifFile);
701 /* whether interlaced or not, it has the same number of lines */
702 /* giflib does't have an interface to skip the image data */
703 for (i = 0; i < Height; i++) {
704 if (DGifGetLine(GifFile, GifRow, Width) == GIF_ERROR) {
706 i_push_error(0, "Reading GIF line");
707 free_images(results, *count);
709 DGifCloseFile(GifFile);
716 /* kill the comment so we get the right comment for the page */
724 case EXTENSION_RECORD_TYPE:
725 /* Skip any extension blocks in file: */
726 if (DGifGetExtension(GifFile, &ExtCode, &Extension) == GIF_ERROR) {
728 i_push_error(0, "Reading extension record");
729 free_images(results, *count);
731 DGifCloseFile(GifFile);
736 /* possibly this should be an error, but "be liberal in what you accept" */
739 if (ExtCode == 0xF9) {
741 if (Extension[1] & 1)
742 trans_index = Extension[4];
745 gif_delay = Extension[2] + 256 * Extension[3];
746 user_input = (Extension[1] & 2) != 0;
747 disposal = (Extension[1] >> 2) & 7;
749 if (ExtCode == 0xFF && *Extension == 11) {
750 if (memcmp(Extension+1, "NETSCAPE2.0", 11) == 0) {
751 if (DGifGetExtensionNext(GifFile, &Extension) == GIF_ERROR) {
753 i_push_error(0, "reading loop extension");
754 free_images(results, *count);
756 DGifCloseFile(GifFile);
761 if (Extension && *Extension == 3) {
763 ns_loop = Extension[2] + 256 * Extension[3];
767 else if (ExtCode == 0xFE) {
768 /* while it's possible for a GIF file to contain more than one
769 comment, I'm only implementing a single comment per image,
770 with the comment saved into the following image.
771 If someone wants more than that they can implement it.
772 I also don't handle comments that take more than one block.
775 comment = mymalloc(*Extension+1);
776 memcpy(comment, Extension+1, *Extension);
777 comment[*Extension] = '\0';
780 while (Extension != NULL) {
781 if (DGifGetExtensionNext(GifFile, &Extension) == GIF_ERROR) {
783 i_push_error(0, "reading next block of extension");
784 free_images(results, *count);
786 DGifCloseFile(GifFile);
793 case TERMINATE_RECORD_TYPE:
795 default: /* Should be trapped by DGifGetRecordType. */
798 } while (RecordType != TERMINATE_RECORD_TYPE);
802 i_tags_set(&(results[*count-1]->tags), "gif_comment", comment,
810 if (DGifCloseFile(GifFile) == GIF_ERROR) {
812 i_push_error(0, "Closing GIF file object");
813 free_images(results, *count);
817 if (ImageNum && page != -1) {
818 /* there were images, but the page selected wasn't found */
819 i_push_errorf(0, "page %d not found (%d total)", page, ImageNum);
820 free_images(results, *count);
827 static int io_glue_read_cb(GifFileType *gft, GifByteType *buf, int length);
830 =item i_readgif_multi_wiol(ig, int *count)
836 i_readgif_multi_wiol(io_glue *ig, int *count) {
837 GifFileType *GifFile;
841 if ((GifFile = DGifOpen((void *)ig, io_glue_read_cb )) == NULL) {
843 i_push_error(0, "Cannot create giflib callback object");
844 mm_log((1,"i_readgif_multi_wiol: Unable to open callback datasource.\n"));
848 return i_readgif_multi_low(GifFile, count, -1);
852 io_glue_read_cb(GifFileType *gft, GifByteType *buf, int length) {
853 io_glue *ig = (io_glue *)gft->UserData;
855 return i_io_read(ig, buf, length);
859 i_readgif_wiol(io_glue *ig, int **color_table, int *colors) {
860 GifFileType *GifFile;
864 if ((GifFile = DGifOpen((void *)ig, io_glue_read_cb )) == NULL) {
866 i_push_error(0, "Cannot create giflib callback object");
867 mm_log((1,"i_readgif_wiol: Unable to open callback datasource.\n"));
871 return i_readgif_low(GifFile, color_table, colors);
875 =item i_readgif_single_low(GifFile, page)
877 Lower level function to read a single image from a GIF.
879 page must be non-negative.
884 i_readgif_single_low(GifFileType *GifFile, int page) {
888 imgs = i_readgif_multi_low(GifFile, &count, page);
891 i_img *result = imgs[0];
897 /* i_readgif_multi_low() handles the errors appropriately */
903 =item i_readgif_single_wiol(ig, page)
905 Read a single page from a GIF image file, where the page is indexed
908 Returns NULL if the page isn't found.
914 i_readgif_single_wiol(io_glue *ig, int page) {
918 i_push_error(0, "page must be non-negative");
922 GifFileType *GifFile;
924 if ((GifFile = DGifOpen((void *)ig, io_glue_read_cb )) == NULL) {
926 i_push_error(0, "Cannot create giflib callback object");
927 mm_log((1,"i_readgif_wiol: Unable to open callback datasource.\n"));
931 return i_readgif_single_low(GifFile, page);
935 =item do_write(GifFileType *gf, i_gif_opts *opts, i_img *img, i_palidx *data)
937 Internal. Low level image write function. Writes in interlace if
938 that was requested in the GIF options.
940 Returns non-zero on success.
945 do_write(GifFileType *gf, int interlace, i_img *img, i_palidx *data) {
948 for (i = 0; i < 4; ++i) {
949 for (j = InterlacedOffset[i]; j < img->ysize; j += InterlacedJumps[i]) {
950 if (EGifPutLine(gf, data+j*img->xsize, img->xsize) == GIF_ERROR) {
952 i_push_error(0, "Could not save image data:");
953 mm_log((1, "Error in EGifPutLine\n"));
962 for (y = 0; y < img->ysize; ++y) {
963 if (EGifPutLine(gf, data, img->xsize) == GIF_ERROR) {
965 i_push_error(0, "Could not save image data:");
966 mm_log((1, "Error in EGifPutLine\n"));
978 =item do_gce(GifFileType *gf, int index, i_gif_opts *opts, int want_trans, int trans_index)
980 Internal. Writes the GIF graphics control extension, if necessary.
982 Returns non-zero on success.
986 static int do_gce(GifFileType *gf, i_img *img, int want_trans, int trans_index)
988 unsigned char gce[4] = {0};
996 gce[3] = trans_index;
999 if (i_tags_get_int(&img->tags, "gif_delay", 0, &delay)) {
1000 gce[1] = delay % 256;
1001 gce[2] = delay / 256;
1004 if (i_tags_get_int(&img->tags, "gif_user_input", 0, &user_input)
1009 if (i_tags_get_int(&img->tags, "gif_disposal", 0, &disposal_method)) {
1010 gce[0] |= (disposal_method & 3) << 2;
1014 if (EGifPutExtension(gf, 0xF9, sizeof(gce), gce) == GIF_ERROR) {
1016 i_push_error(0, "Could not save GCE");
1023 =item do_comments(gf, img)
1025 Write any comments in the image.
1029 static int do_comments(GifFileType *gf, i_img *img) {
1032 while (i_tags_find(&img->tags, "gif_comment", pos+1, &pos)) {
1033 if (img->tags.tags[pos].data) {
1034 if (EGifPutComment(gf, img->tags.tags[pos].data) == GIF_ERROR) {
1040 #ifdef IMAGER_SNPRINTF
1041 snprintf(buf, sizeof(buf), "%d", img->tags.tags[pos].idata);
1043 sprintf(buf, "%d", img->tags.tags[pos].idata);
1045 if (EGifPutComment(gf, buf) == GIF_ERROR) {
1055 =item do_ns_loop(GifFileType *gf, i_gif_opts *opts)
1057 Internal. Add the Netscape2.0 loop extension block, if requested.
1059 Giflib/libungif prior to 4.1.1 didn't support writing application
1060 extension blocks, so we don't attempt to write them for older versions.
1062 Giflib/libungif prior to 4.1.3 used the wrong write mechanism when
1063 writing extension blocks so that they could only be written to files.
1067 static int do_ns_loop(GifFileType *gf, i_img *img)
1069 /* EGifPutExtension() doesn't appear to handle application
1070 extension blocks in any way
1071 Since giflib wraps the fd with a FILE * (and puts that in its
1072 private data), we can't do an end-run and write the data
1074 There's no open interface that takes a FILE * either, so we
1075 can't workaround it that way either.
1076 If giflib's callback interface wasn't broken by default, I'd
1077 force file writes to use callbacks, but it is broken by default.
1079 /* yes this was another attempt at supporting the loop extension */
1081 if (i_tags_get_int(&img->tags, "gif_loop", 0, &loop_count)) {
1082 unsigned char nsle[12] = "NETSCAPE2.0";
1083 unsigned char subblock[3];
1084 if (EGifPutExtensionFirst(gf, APPLICATION_EXT_FUNC_CODE, 11, nsle) == GIF_ERROR) {
1086 i_push_error(0, "writing loop extension");
1090 subblock[1] = loop_count % 256;
1091 subblock[2] = loop_count / 256;
1092 if (EGifPutExtensionLast(gf, APPLICATION_EXT_FUNC_CODE, 3, subblock) == GIF_ERROR) {
1094 i_push_error(0, "writing loop extension sub-block");
1103 =item make_gif_map(i_quantize *quant, int want_trans)
1105 Create a giflib color map object from an Imager color map.
1110 static ColorMapObject *make_gif_map(i_quantize *quant, i_img *img,
1112 GifColorType colors[256];
1114 int size = quant->mc_count;
1116 ColorMapObject *map;
1119 for (i = 0; i < quant->mc_count; ++i) {
1120 colors[i].Red = quant->mc_colors[i].rgb.r;
1121 colors[i].Green = quant->mc_colors[i].rgb.g;
1122 colors[i].Blue = quant->mc_colors[i].rgb.b;
1125 if (!i_tags_get_color(&img->tags, "gif_trans_color", 0, &trans))
1126 trans.rgb.r = trans.rgb.g = trans.rgb.b = 0;
1127 colors[size].Red = trans.rgb.r;
1128 colors[size].Green = trans.rgb.g;
1129 colors[size].Blue = trans.rgb.b;
1133 while (map_size < size)
1135 /* giflib spews for 1 colour maps, reasonable, I suppose */
1138 while (i < map_size) {
1139 colors[i].Red = colors[i].Green = colors[i].Blue = 0;
1143 map = MakeMapObject(map_size, colors);
1144 mm_log((1, "XXX map is at %p and colors at %p\n", map, map->Colors));
1147 i_push_error(0, "Could not create color map object");
1154 =item gif_set_version(i_quantize *quant, i_img *imgs, int count)
1156 We need to call EGifSetGifVersion() before opening the file - put that
1159 Unfortunately giflib 4.1.0 crashes when we use this. Internally
1160 giflib 4.1.0 has code:
1162 static char *GifVersionPrefix = GIF87_STAMP;
1164 and the code that sets the version internally does:
1166 strncpy(&GifVersionPrefix[3], Version, 3);
1168 which is very broken.
1170 Failing to set the correct GIF version doesn't seem to cause a problem
1173 Modern versions (4.1.4 anyway) of giflib/libungif handle
1174 EGifSetGifVersion correctly.
1176 If t/t105gif.t crashes here then run Makefile.PL with
1177 --nogifsetversion, eg.:
1179 perl Makefile.PL --nogifsetversion
1181 or install a less buggy giflib.
1186 static void gif_set_version(i_quantize *quant, i_img **imgs, int count) {
1191 if (quant->transp != tr_none)
1194 for (i = 0; i < count; ++i) {
1195 if (i_tags_get_int(&imgs[i]->tags, "gif_delay", 0, &temp)) {
1199 if (i_tags_get_int(&imgs[i]->tags, "gif_user_input", 0, &temp) && temp) {
1203 if (i_tags_get_int(&imgs[i]->tags, "gif_disposal", 0, &temp)) {
1207 if (i_tags_get_int(&imgs[i]->tags, "gif_loop", 0, &temp)) {
1214 EGifSetGifVersion("89a");
1216 EGifSetGifVersion("87a");
1220 in_palette(i_color *c, i_quantize *quant, int size) {
1223 for (i = 0; i < size; ++i) {
1224 if (c->channel[0] == quant->mc_colors[i].channel[0]
1225 && c->channel[1] == quant->mc_colors[i].channel[1]
1226 && c->channel[2] == quant->mc_colors[i].channel[2]) {
1235 =item has_common_palette(imgs, count, quant)
1237 Tests if all the given images are paletted and their colors are in the
1240 Previously this would build a consolidated palette from the source,
1241 but that meant that if the caller supplied a static palette (or
1242 specified a fixed palette like "webmap") then we wouldn't be
1243 quantizing to the caller specified palette.
1249 has_common_palette(i_img **imgs, int count, i_quantize *quant) {
1255 /* we try to build a common palette here, if we can manage that, then
1256 that's the palette we use */
1257 for (imgn = 0; imgn < count; ++imgn) {
1258 int eliminate_unused;
1259 if (imgs[imgn]->type != i_palette_type)
1262 if (!i_tags_get_int(&imgs[imgn]->tags, "gif_eliminate_unused", 0,
1263 &eliminate_unused)) {
1264 eliminate_unused = 1;
1267 if (eliminate_unused) {
1268 i_palidx *line = mymalloc(sizeof(i_palidx) * imgs[imgn]->xsize);
1270 memset(used, 0, sizeof(used));
1272 for (y = 0; y < imgs[imgn]->ysize; ++y) {
1273 i_gpal(imgs[imgn], 0, imgs[imgn]->xsize, y, line);
1274 for (x = 0; x < imgs[imgn]->xsize; ++x)
1281 /* assume all are in use */
1282 memset(used, 1, sizeof(used));
1285 col_count = i_colorcount(imgs[imgn]);
1286 for (i = 0; i < col_count; ++i) {
1289 i_getcolors(imgs[imgn], i, &c, 1);
1291 if (in_palette(&c, quant, quant->mc_count) < 0) {
1292 mm_log((1, " color not found in palette, no palette shortcut\n"));
1300 mm_log((1, " all colors found in palette, palette shortcut\n"));
1306 quant_paletted(i_quantize *quant, i_img *img) {
1307 i_palidx *data = mymalloc(sizeof(i_palidx) * img->xsize * img->ysize);
1309 i_palidx trans[256];
1313 /* build a translation table */
1314 for (i = 0; i < i_colorcount(img); ++i) {
1316 i_getcolors(img, i, &c, 1);
1317 trans[i] = in_palette(&c, quant, quant->mc_count);
1320 for (y = 0; y < img->ysize; ++y) {
1321 i_gpal(img, 0, img->xsize, y, data+img->xsize * y);
1322 for (x = 0; x < img->xsize; ++x) {
1332 =item i_writegif_low(i_quantize *quant, GifFileType *gf, i_img **imgs, int count, i_gif_opts *opts)
1334 Internal. Low-level function that does the high-level GIF processing
1337 Returns non-zero on success.
1343 i_writegif_low(i_quantize *quant, GifFileType *gf, i_img **imgs, int count) {
1344 unsigned char *result = NULL;
1346 ColorMapObject *map;
1347 int scrw = 0, scrh = 0;
1348 int imgn, orig_count, orig_size;
1350 int trans_index = -1;
1353 i_img **glob_imgs; /* images that will use the global color map */
1355 i_color *orig_colors = quant->mc_colors;
1356 i_color *glob_colors = NULL;
1357 int glob_color_count = 0;
1358 int glob_want_trans;
1359 int glob_paletted = 0; /* the global map was made from the image palettes */
1360 int colors_paletted = 0;
1365 mm_log((1, "i_writegif_low(quant %p, gf %p, imgs %p, count %d)\n",
1366 quant, gf, imgs, count));
1368 /* *((char *)0) = 1; */ /* used to break into the debugger */
1371 i_push_error(0, "No images provided to write");
1372 return 0; /* what are you smoking? */
1375 /* sanity is nice */
1376 if (quant->mc_size > 256)
1377 quant->mc_size = 256;
1378 if (quant->mc_count > quant->mc_size)
1379 quant->mc_count = quant->mc_size;
1381 if (!i_tags_get_int(&imgs[0]->tags, "gif_screen_width", 0, &scrw))
1383 if (!i_tags_get_int(&imgs[0]->tags, "gif_screen_height", 0, &scrh))
1387 localmaps = mymalloc(sizeof(int) * count);
1388 glob_imgs = mymalloc(sizeof(i_img *) * count);
1390 glob_want_trans = 0;
1391 for (imgn = 0; imgn < count; ++imgn) {
1392 i_img *im = imgs[imgn];
1393 if (im->xsize > 0xFFFF || im->ysize > 0xFFFF) {
1394 i_push_error(0, "image too large for GIF");
1399 i_tags_get_int(&im->tags, "gif_left", 0, &posx);
1400 if (posx < 0) posx = 0;
1401 i_tags_get_int(&im->tags, "gif_top", 0, &posy);
1402 if (posy < 0) posy = 0;
1403 if (im->xsize + posx > scrw)
1404 scrw = im->xsize + posx;
1405 if (im->ysize + posy > scrh)
1406 scrh = im->ysize + posy;
1407 if (!i_tags_get_int(&im->tags, "gif_local_map", 0, localmaps+imgn))
1408 localmaps[imgn] = 0;
1409 if (localmaps[imgn])
1412 if (im->channels == 4) {
1413 glob_want_trans = 1;
1415 glob_imgs[glob_img_count++] = im;
1418 glob_want_trans = glob_want_trans && quant->transp != tr_none ;
1420 if (scrw > 0xFFFF || scrh > 0xFFFF) {
1421 i_push_error(0, "screen size too large for GIF");
1425 orig_count = quant->mc_count;
1426 orig_size = quant->mc_size;
1428 if (glob_img_count) {
1430 glob_colors = mymalloc(sizeof(i_color) * quant->mc_size);
1431 quant->mc_colors = glob_colors;
1432 memcpy(glob_colors, orig_colors, sizeof(i_color) * quant->mc_count);
1433 /* we have some images that want to use the global map */
1434 if (glob_want_trans && quant->mc_count == 256) {
1435 mm_log((2, " disabling transparency for global map - no space\n"));
1436 glob_want_trans = 0;
1438 if (glob_want_trans && quant->mc_size == 256) {
1439 mm_log((2, " reserving color for transparency\n"));
1443 i_quant_makemap(quant, glob_imgs, glob_img_count);
1444 glob_paletted = has_common_palette(glob_imgs, glob_img_count, quant);
1445 glob_color_count = quant->mc_count;
1446 quant->mc_colors = orig_colors;
1449 /* use the global map if we have one, otherwise use the local map */
1452 quant->mc_colors = glob_colors;
1453 quant->mc_count = glob_color_count;
1454 want_trans = glob_want_trans && imgs[0]->channels == 4;
1456 if (!i_tags_get_int(&imgs[0]->tags, "gif_background", 0, &gif_background))
1458 if (gif_background < 0)
1460 if (gif_background >= glob_color_count)
1464 want_trans = quant->transp != tr_none && imgs[0]->channels == 4;
1465 i_quant_makemap(quant, imgs, 1);
1466 colors_paletted = has_common_palette(imgs, 1, quant);
1469 if ((map = make_gif_map(quant, imgs[0], want_trans)) == NULL) {
1470 myfree(glob_colors);
1473 quant->mc_colors = orig_colors;
1475 mm_log((1, "Error in MakeMapObject"));
1480 /* since we don't know how big some the local palettes could be
1481 we need to base the bits on the maximum number of colors */
1482 while (orig_size > (1 << color_bits))
1486 int count = quant->mc_count;
1489 while (count > (1 << color_bits))
1493 if (EGifPutScreenDesc(gf, scrw, scrh, color_bits,
1494 gif_background, map) == GIF_ERROR) {
1495 myfree(glob_colors);
1498 quant->mc_colors = orig_colors;
1500 i_push_error(0, "Could not save screen descriptor");
1504 mm_log((1, "Error in EGifPutScreenDesc."));
1509 if (!i_tags_get_int(&imgs[0]->tags, "gif_left", 0, &posx))
1511 if (!i_tags_get_int(&imgs[0]->tags, "gif_top", 0, &posy))
1514 if (!localmaps[0]) {
1516 colors_paletted = glob_paletted;
1519 /* if this image has a global map the colors in quant don't
1520 belong to this image, so build a palette */
1522 /* generate the local map for this image */
1523 quant->mc_colors = orig_colors;
1524 quant->mc_size = orig_size;
1525 quant->mc_count = orig_count;
1526 want_trans = quant->transp != tr_none && imgs[0]->channels == 4;
1528 /* if the caller gives us too many colours we can't do transparency */
1529 if (want_trans && quant->mc_count == 256)
1531 /* if they want transparency but give us a big size, make it smaller
1532 to give room for a transparency colour */
1533 if (want_trans && quant->mc_size == 256)
1535 i_quant_makemap(quant, imgs, 1);
1536 colors_paletted = has_common_palette(imgs, 1, quant);
1537 if ((map = make_gif_map(quant, imgs[0], want_trans)) == NULL) {
1538 myfree(glob_colors);
1542 quant->mc_colors = orig_colors;
1543 mm_log((1, "Error in MakeMapObject"));
1548 /* the map we wrote was the map for this image - don't set the local
1554 if (colors_paletted)
1555 result = quant_paletted(quant, imgs[0]);
1557 result = i_quant_translate(quant, imgs[0]);
1559 myfree(glob_colors);
1562 quant->mc_colors = orig_colors;
1567 i_quant_transparent(quant, result, imgs[0], quant->mc_count);
1568 trans_index = quant->mc_count;
1571 if (!do_ns_loop(gf, imgs[0])) {
1572 myfree(glob_colors);
1575 quant->mc_colors = orig_colors;
1579 if (!do_gce(gf, imgs[0], want_trans, trans_index)) {
1580 myfree(glob_colors);
1583 quant->mc_colors = orig_colors;
1589 if (!do_comments(gf, imgs[0])) {
1590 myfree(glob_colors);
1593 quant->mc_colors = orig_colors;
1599 if (!i_tags_get_int(&imgs[0]->tags, "gif_interlace", 0, &interlace))
1601 if (EGifPutImageDesc(gf, posx, posy, imgs[0]->xsize, imgs[0]->ysize,
1602 interlace, map) == GIF_ERROR) {
1603 myfree(glob_colors);
1606 quant->mc_colors = orig_colors;
1608 i_push_error(0, "Could not save image descriptor");
1610 mm_log((1, "Error in EGifPutImageDesc."));
1616 if (!do_write(gf, interlace, imgs[0], result)) {
1617 myfree(glob_colors);
1620 quant->mc_colors = orig_colors;
1627 /* that first awful image is out of the way, do the rest */
1628 for (imgn = 1; imgn < count; ++imgn) {
1629 if (localmaps[imgn]) {
1630 quant->mc_colors = orig_colors;
1631 quant->mc_count = orig_count;
1632 quant->mc_size = orig_size;
1634 want_trans = quant->transp != tr_none
1635 && imgs[imgn]->channels == 4;
1636 /* if the caller gives us too many colours we can't do transparency */
1637 if (want_trans && quant->mc_count == 256)
1639 /* if they want transparency but give us a big size, make it smaller
1640 to give room for a transparency colour */
1641 if (want_trans && quant->mc_size == 256)
1644 if (has_common_palette(imgs+imgn, 1, quant)) {
1645 result = quant_paletted(quant, imgs[imgn]);
1648 i_quant_makemap(quant, imgs+imgn, 1);
1649 result = i_quant_translate(quant, imgs[imgn]);
1652 myfree(glob_colors);
1655 quant->mc_colors = orig_colors;
1657 mm_log((1, "error in i_quant_translate()"));
1661 i_quant_transparent(quant, result, imgs[imgn], quant->mc_count);
1662 trans_index = quant->mc_count;
1665 if ((map = make_gif_map(quant, imgs[imgn], want_trans)) == NULL) {
1666 myfree(glob_colors);
1669 quant->mc_colors = orig_colors;
1672 mm_log((1, "Error in MakeMapObject."));
1677 quant->mc_colors = glob_colors;
1678 quant->mc_count = glob_color_count;
1680 result = quant_paletted(quant, imgs[imgn]);
1682 result = i_quant_translate(quant, imgs[imgn]);
1683 want_trans = glob_want_trans && imgs[imgn]->channels == 4;
1685 i_quant_transparent(quant, result, imgs[imgn], quant->mc_count);
1686 trans_index = quant->mc_count;
1691 if (!do_gce(gf, imgs[imgn], want_trans, trans_index)) {
1692 myfree(glob_colors);
1695 quant->mc_colors = orig_colors;
1701 if (!do_comments(gf, imgs[imgn])) {
1702 myfree(glob_colors);
1705 quant->mc_colors = orig_colors;
1711 if (!i_tags_get_int(&imgs[imgn]->tags, "gif_left", 0, &posx))
1713 if (!i_tags_get_int(&imgs[imgn]->tags, "gif_top", 0, &posy))
1716 if (!i_tags_get_int(&imgs[imgn]->tags, "gif_interlace", 0, &interlace))
1718 if (EGifPutImageDesc(gf, posx, posy, imgs[imgn]->xsize,
1719 imgs[imgn]->ysize, interlace, map) == GIF_ERROR) {
1720 myfree(glob_colors);
1723 quant->mc_colors = orig_colors;
1725 i_push_error(0, "Could not save image descriptor");
1730 mm_log((1, "Error in EGifPutImageDesc."));
1736 if (!do_write(gf, interlace, imgs[imgn], result)) {
1737 myfree(glob_colors);
1740 quant->mc_colors = orig_colors;
1748 if (EGifCloseFile(gf) == GIF_ERROR) {
1749 myfree(glob_colors);
1753 i_push_error(0, "Could not close GIF file");
1754 mm_log((1, "Error in EGifCloseFile\n"));
1759 for (i = 0; i < glob_color_count; ++i)
1760 orig_colors[i] = glob_colors[i];
1763 myfree(glob_colors);
1766 quant->mc_colors = orig_colors;
1772 io_glue_write_cb(GifFileType *gft, const GifByteType *data, int length) {
1773 io_glue *ig = (io_glue *)gft->UserData;
1775 return i_io_write(ig, data, length);
1780 =item i_writegif_wiol(ig, quant, opts, imgs, count)
1785 i_writegif_wiol(io_glue *ig, i_quantize *quant, i_img **imgs,
1787 GifFileType *GifFile;
1792 gif_set_version(quant, imgs, count);
1794 if ((GifFile = EGifOpen((void *)ig, io_glue_write_cb )) == NULL) {
1796 i_push_error(0, "Cannot create giflib callback object");
1797 mm_log((1,"i_writegif_wiol: Unable to open callback datasource.\n"));
1801 result = i_writegif_low(quant, GifFile, imgs, count);
1810 =item gif_error_msg(int code)
1812 Grabs the most recent giflib error code from GifLastError() and
1813 returns a string that describes that error.
1815 The returned pointer points to a static buffer, either from a literal
1816 C string or a static buffer.
1821 static char const *gif_error_msg(int code) {
1822 static char msg[80];
1825 case E_GIF_ERR_OPEN_FAILED: /* should not see this */
1826 return "Failed to open given file";
1828 case E_GIF_ERR_WRITE_FAILED:
1829 return "Write failed";
1831 case E_GIF_ERR_HAS_SCRN_DSCR: /* should not see this */
1832 return "Screen descriptor already passed to giflib";
1834 case E_GIF_ERR_HAS_IMAG_DSCR: /* should not see this */
1835 return "Image descriptor already passed to giflib";
1837 case E_GIF_ERR_NO_COLOR_MAP: /* should not see this */
1838 return "Neither global nor local color map set";
1840 case E_GIF_ERR_DATA_TOO_BIG: /* should not see this */
1841 return "Too much pixel data passed to giflib";
1843 case E_GIF_ERR_NOT_ENOUGH_MEM:
1844 return "Out of memory";
1846 case E_GIF_ERR_DISK_IS_FULL:
1847 return "Disk is full";
1849 case E_GIF_ERR_CLOSE_FAILED: /* should not see this */
1850 return "File close failed";
1852 case E_GIF_ERR_NOT_WRITEABLE: /* should not see this */
1853 return "File not writable";
1855 case D_GIF_ERR_OPEN_FAILED:
1856 return "Failed to open file";
1858 case D_GIF_ERR_READ_FAILED:
1859 return "Failed to read from file";
1861 case D_GIF_ERR_NOT_GIF_FILE:
1862 return "File is not a GIF file";
1864 case D_GIF_ERR_NO_SCRN_DSCR:
1865 return "No screen descriptor detected - invalid file";
1867 case D_GIF_ERR_NO_IMAG_DSCR:
1868 return "No image descriptor detected - invalid file";
1870 case D_GIF_ERR_NO_COLOR_MAP:
1871 return "No global or local color map found";
1873 case D_GIF_ERR_WRONG_RECORD:
1874 return "Wrong record type detected - invalid file?";
1876 case D_GIF_ERR_DATA_TOO_BIG:
1877 return "Data in file too big for image";
1879 case D_GIF_ERR_NOT_ENOUGH_MEM:
1880 return "Out of memory";
1882 case D_GIF_ERR_CLOSE_FAILED:
1883 return "Close failed";
1885 case D_GIF_ERR_NOT_READABLE:
1886 return "File not opened for read";
1888 case D_GIF_ERR_IMAGE_DEFECT:
1889 return "Defective image";
1891 case D_GIF_ERR_EOF_TOO_SOON:
1892 return "Unexpected EOF - invalid file";
1895 #ifdef IMAGER_SNPRINTF
1896 snprintf(msg, sizeof(msg), "Unknown giflib error code %d", code);
1898 sprintf(msg, "Unknown giflib error code %d", code);
1905 =item gif_push_error()
1907 Utility function that takes the current GIF error code, converts it to
1908 an error message and pushes it on the error stack.
1913 static void gif_push_error(void) {
1914 int code = GifLastError(); /* clears saved error */
1916 i_push_error(code, gif_error_msg(code));
1922 The Netscape loop extension isn't implemented. Giflib's extension
1923 writing code doesn't seem to support writing named extensions in this
1926 A bug in giflib is tickled by the i_writegif_callback(). This isn't a
1927 problem on ungiflib, but causes a SEGV on giflib. A patch is provided
1930 The GIF file tag (GIF87a vs GIF89a) currently isn't set. Using the
1931 supplied interface in giflib 4.1.0 causes a SEGV in
1932 EGifSetGifVersion(). See L<gif_set_version> for an explanation.
1936 Arnar M. Hrafnkelsson, addi@umich.edu