update Changes
[imager.git] / GIF / imgif.c
CommitLineData
ec6d8908 1#include "imgif.h"
02d1d628 2#include <gif_lib.h>
55f2fb90 3#ifdef _MSC_VER
10461f9a
TC
4#include <io.h>
5#else
6#include <unistd.h>
7#endif
8#include <errno.h>
ec6d8908 9#include <string.h>
4922fb3a 10#include <stdlib.h>
8d14daab 11#include <stdio.h>
5b0d044f 12
a3923855
TC
13/*
14=head1 NAME
15
4922fb3a 16imgif.c - read and write gif files for Imager
a3923855
TC
17
18=head1 SYNOPSIS
19
20 i_img *img;
21 i_img *imgs[count];
22 int fd;
23 int *colour_table,
24 int colours;
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
3bb1c1f3 30 char *data; // a GIF file in memory
a3923855
TC
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
35 i_quantize quant;
36 i_gif_opts opts;
37
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,
45 imgs, count, &opts);
46
47=head1 DESCRIPTION
48
49This source file provides the C level interface to reading and writing
50GIF files for Imager.
51
52This has been tested with giflib 3 and 4, though you lose the callback
53functionality with giflib3.
54
55=head1 REFERENCE
56
57=over
58
59=cut
60*/
02d1d628 61
7b486870
TC
62#ifdef GIFLIB_MAJOR
63#define IMGIFLIB_API_VERSION (GIFLIB_MAJOR * 100 + GIFLIB_MINOR)
64#else
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
68#endif
69
70#if IMGIFLIB_API_VERSION >= 500
a93c43b4 71#define POST_SET_VERSION
7a5f7bc6
TC
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
536b7775
TC
77#define gif_mutex_lock(mutex)
78#define gif_mutex_unlock(mutex)
7a5f7bc6 79#else
a93c43b4 80#define PRE_SET_VERSION
7a5f7bc6
TC
81static GifFileType *
82myDGifOpen(void *userPtr, InputFunc readFunc, int *error) {
83 GifFileType *result = DGifOpen(userPtr, readFunc);
84 if (!result)
85 *error = GifLastError();
86
87 return result;
88}
89static GifFileType *
90myEGifOpen(void *userPtr, OutputFunc outputFunc, int *error) {
91 GifFileType *result = EGifOpen(userPtr, outputFunc);
92 if (!result)
93 *error = GifLastError();
94
95 return result;
96}
97#define myGifError(gif) GifLastError()
536b7775
TC
98#define gif_mutex_lock(mutex) i_mutex_lock(mutex)
99#define gif_mutex_unlock(mutex) i_mutex_unlock(mutex)
7a5f7bc6
TC
100
101#endif
102
7b486870
TC
103#if IMGIFLIB_API_VERSION >= 501
104#define myDGifCloseFile(gif, perror) (DGifCloseFile((gif), (perror)))
105#define myEGifCloseFile(gif, perror) (EGifCloseFile((gif), (perror)))
106#else
107static int
108myDGifCloseFile(GifFileType *GifFile, int *ErrorCode) {
109 int result = DGifCloseFile(GifFile);
110 if (result == GIF_ERROR) {
111 if (ErrorCode)
112 *ErrorCode = myGifError(GifFile);
113 free(GifFile->Private);
114 free(GifFile);
115 }
116
117 return result;
118}
119
120static int
121myEGifCloseFile(GifFileType *GifFile, int *ErrorCode) {
122 int result = EGifCloseFile(GifFile);
123 if (result == GIF_ERROR) {
124 if (ErrorCode)
125 *ErrorCode = myGifError(GifFile);
126 free(GifFile->Private);
127 free(GifFile);
128 }
129
130 return result;
131}
132#endif
133
c48818b1 134static char const *gif_error_msg(int code);
7a5f7bc6 135static void gif_push_error(int code);
c48818b1 136
02d1d628
AMH
137/* Make some variables global, so we could access them faster: */
138
7a5f7bc6 139static const int
2ff8ed30
AMH
140 InterlacedOffset[] = { 0, 4, 2, 1 }, /* The way Interlaced image should. */
141 InterlacedJumps[] = { 8, 8, 4, 2 }; /* be read - offsets and jumps... */
7828ca98 142
7b486870 143#if IMGIFLIB_API_VERSION < 500
536b7775
TC
144static i_mutex_t mutex;
145#endif
146
147void
148i_init_gif(void) {
7b486870 149#if IMGIFLIB_API_VERSION < 500
536b7775
TC
150 mutex = i_mutex_new();
151#endif
152}
153
7828ca98
AMH
154static
155void
2ff8ed30 156i_colortable_copy(int **colour_table, int *colours, ColorMapObject *colourmap) {
7828ca98
AMH
157 GifColorType *mapentry;
158 int q;
2ff8ed30 159 int colourmapsize = colourmap->ColorCount;
7828ca98 160
2ff8ed30 161 if(colours) *colours = colourmapsize;
7828ca98
AMH
162 if(!colour_table) return;
163
2d20f216
TC
164 *colour_table = mymalloc(sizeof(int) * colourmapsize * 3);
165 memset(*colour_table, 0, sizeof(int) * colourmapsize * 3);
7828ca98 166
2ff8ed30
AMH
167 for(q=0; q<colourmapsize; q++) {
168 mapentry = &colourmap->Colors[q];
7828ca98
AMH
169 (*colour_table)[q*3 + 0] = mapentry->Red;
170 (*colour_table)[q*3 + 1] = mapentry->Green;
171 (*colour_table)[q*3 + 2] = mapentry->Blue;
172 }
173}
174
0dfd57de
TC
175#ifdef GIF_LIB_VERSION
176
4922fb3a
TC
177static const
178char gif_version_str[] = GIF_LIB_VERSION;
179
180double
ec6d8908 181i_giflib_version(void) {
4922fb3a
TC
182 const char *p = gif_version_str;
183
184 while (*p && (*p < '0' || *p > '9'))
185 ++p;
186
187 if (!*p)
188 return 0;
189
190 return strtod(p, NULL);
ec6d8908 191}
7828ca98 192
0dfd57de
TC
193#else
194
195double
196i_giflib_version(void) {
197 return GIFLIB_MAJOR + GIFLIB_MINOR * 0.1;
198}
199
200#endif
201
a3923855
TC
202/*
203=item i_readgif_low(GifFileType *GifFile, int **colour_table, int *colours)
204
205Internal. Low-level function for reading a GIF file. The caller must
206create the appropriate GifFileType object and pass it in.
207
208=cut
209*/
7828ca98 210
02d1d628
AMH
211i_img *
212i_readgif_low(GifFileType *GifFile, int **colour_table, int *colours) {
213 i_img *im;
214 int i, j, Size, Row, Col, Width, Height, ExtCode, Count, x;
98747309 215 int cmapcnt = 0, ImageNum = 0;
2ff8ed30 216 ColorMapObject *ColorMap;
98c79495 217
02d1d628
AMH
218 GifRecordType RecordType;
219 GifByteType *Extension;
220
221 GifRowType GifRow;
7a5f7bc6 222 GifColorType *ColorMapEntry;
02d1d628 223 i_color col;
7b486870 224 int error;
02d1d628 225
02d1d628
AMH
226 mm_log((1,"i_readgif_low(GifFile %p, colour_table %p, colours %p)\n", GifFile, colour_table, colours));
227
e6172a17
TC
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.
231 */
895dbd34 232 if (colour_table) *colour_table = NULL;
e6172a17 233
02d1d628 234 ColorMap = (GifFile->Image.ColorMap ? GifFile->Image.ColorMap : GifFile->SColorMap);
02d1d628 235
7828ca98 236 if (ColorMap) {
7828ca98
AMH
237 i_colortable_copy(colour_table, colours, ColorMap);
238 cmapcnt++;
02d1d628 239 }
02d1d628 240
77157728
TC
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;
245 }
7b486870 246 (void)myDGifCloseFile(GifFile, NULL);
77157728
TC
247 mm_log((1, "i_readgif: image size exceeds limits\n"));
248 return NULL;
249 }
7828ca98 250
ec6d8908 251 im = i_img_8_new(GifFile->SWidth, GifFile->SHeight, 3);
8c68bf11
TC
252 if (!im) {
253 if (colour_table && *colour_table) {
254 myfree(*colour_table);
255 *colour_table = NULL;
256 }
7b486870 257 (void)myDGifCloseFile(GifFile, NULL);
8c68bf11
TC
258 return NULL;
259 }
7828ca98 260
02d1d628
AMH
261 Size = GifFile->SWidth * sizeof(GifPixelType);
262
a73aeb5f 263 GifRow = mymalloc(Size);
02d1d628
AMH
264
265 for (i = 0; i < GifFile->SWidth; i++) GifRow[i] = GifFile->SBackGroundColor;
266
267 /* Scan the content of the GIF file and load the image(s) in: */
268 do {
269 if (DGifGetRecordType(GifFile, &RecordType) == GIF_ERROR) {
7a5f7bc6 270 gif_push_error(myGifError(GifFile));
77fc6afd 271 i_push_error(0, "Unable to get record type");
e6172a17 272 if (colour_table && *colour_table) {
ea9f6207 273 myfree(*colour_table);
e6172a17
TC
274 *colour_table = NULL;
275 }
a73aeb5f 276 myfree(GifRow);
77fc6afd 277 i_img_destroy(im);
7b486870 278 (void)myDGifCloseFile(GifFile, NULL);
77fc6afd 279 return NULL;
02d1d628
AMH
280 }
281
282 switch (RecordType) {
283 case IMAGE_DESC_RECORD_TYPE:
284 if (DGifGetImageDesc(GifFile) == GIF_ERROR) {
7a5f7bc6 285 gif_push_error(myGifError(GifFile));
77fc6afd 286 i_push_error(0, "Unable to get image descriptor");
e6172a17 287 if (colour_table && *colour_table) {
ea9f6207 288 myfree(*colour_table);
e6172a17
TC
289 *colour_table = NULL;
290 }
a73aeb5f 291 myfree(GifRow);
77fc6afd 292 i_img_destroy(im);
7b486870 293 (void)myDGifCloseFile(GifFile, NULL);
77fc6afd 294 return NULL;
02d1d628 295 }
7828ca98 296
43a881d3
TC
297 if (( ColorMap = (GifFile->Image.ColorMap ? GifFile->Image.ColorMap : GifFile->SColorMap) )) {
298 mm_log((1, "Adding local colormap\n"));
43a881d3 299 if ( cmapcnt == 0) {
7828ca98
AMH
300 i_colortable_copy(colour_table, colours, ColorMap);
301 cmapcnt++;
7828ca98 302 }
43a881d3
TC
303 } else {
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 */
a73aeb5f 308 myfree(GifRow);
43a881d3 309 i_img_destroy(im);
7b486870 310 (void)myDGifCloseFile(GifFile, NULL);
43a881d3 311 return NULL;
7828ca98 312 }
43a881d3 313
02d1d628
AMH
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;
318 ImageNum++;
895dbd34 319 mm_log((1,"i_readgif_low: Image %d at (%d, %d) [%dx%d]: \n",ImageNum, Col, Row, Width, Height));
02d1d628
AMH
320
321 if (GifFile->Image.Left + GifFile->Image.Width > GifFile->SWidth ||
322 GifFile->Image.Top + GifFile->Image.Height > GifFile->SHeight) {
77fc6afd 323 i_push_errorf(0, "Image %d is not confined to screen dimension, aborted.\n",ImageNum);
e6172a17 324 if (colour_table && *colour_table) {
ea9f6207 325 myfree(*colour_table);
e6172a17
TC
326 *colour_table = NULL;
327 }
a73aeb5f 328 myfree(GifRow);
77fc6afd 329 i_img_destroy(im);
7b486870 330 (void)myDGifCloseFile(GifFile, NULL);
a73aeb5f 331 return NULL;
02d1d628
AMH
332 }
333 if (GifFile->Image.Interlace) {
334
335 for (Count = i = 0; i < 4; i++) for (j = Row + InterlacedOffset[i]; j < Row + Height; j += InterlacedJumps[i]) {
336 Count++;
43a881d3 337 if (DGifGetLine(GifFile, GifRow, Width) == GIF_ERROR) {
7a5f7bc6 338 gif_push_error(myGifError(GifFile));
77fc6afd 339 i_push_error(0, "Reading GIF line");
e6172a17 340 if (colour_table && *colour_table) {
ea9f6207 341 myfree(*colour_table);
e6172a17
TC
342 *colour_table = NULL;
343 }
a73aeb5f 344 myfree(GifRow);
77fc6afd 345 i_img_destroy(im);
7b486870 346 (void)myDGifCloseFile(GifFile, NULL);
77fc6afd 347 return NULL;
02d1d628
AMH
348 }
349
43a881d3 350 for (x = 0; x < Width; x++) {
02d1d628
AMH
351 ColorMapEntry = &ColorMap->Colors[GifRow[x]];
352 col.rgb.r = ColorMapEntry->Red;
353 col.rgb.g = ColorMapEntry->Green;
354 col.rgb.b = ColorMapEntry->Blue;
43a881d3 355 i_ppix(im,Col+x,j,&col);
02d1d628
AMH
356 }
357
358 }
359 }
360 else {
361 for (i = 0; i < Height; i++) {
43a881d3 362 if (DGifGetLine(GifFile, GifRow, Width) == GIF_ERROR) {
7a5f7bc6 363 gif_push_error(myGifError(GifFile));
77fc6afd 364 i_push_error(0, "Reading GIF line");
e6172a17 365 if (colour_table && *colour_table) {
ea9f6207 366 myfree(*colour_table);
e6172a17
TC
367 *colour_table = NULL;
368 }
a73aeb5f 369 myfree(GifRow);
77fc6afd 370 i_img_destroy(im);
7b486870 371 (void)myDGifCloseFile(GifFile, NULL);
77fc6afd 372 return NULL;
02d1d628
AMH
373 }
374
43a881d3 375 for (x = 0; x < Width; x++) {
02d1d628
AMH
376 ColorMapEntry = &ColorMap->Colors[GifRow[x]];
377 col.rgb.r = ColorMapEntry->Red;
378 col.rgb.g = ColorMapEntry->Green;
379 col.rgb.b = ColorMapEntry->Blue;
43a881d3 380 i_ppix(im, Col+x, Row, &col);
02d1d628
AMH
381 }
382 Row++;
383 }
384 }
385 break;
386 case EXTENSION_RECORD_TYPE:
387 /* Skip any extension blocks in file: */
388 if (DGifGetExtension(GifFile, &ExtCode, &Extension) == GIF_ERROR) {
7a5f7bc6 389 gif_push_error(myGifError(GifFile));
77fc6afd 390 i_push_error(0, "Reading extension record");
e6172a17 391 if (colour_table && *colour_table) {
ea9f6207 392 myfree(*colour_table);
e6172a17
TC
393 *colour_table = NULL;
394 }
a73aeb5f 395 myfree(GifRow);
77fc6afd 396 i_img_destroy(im);
7b486870 397 (void)myDGifCloseFile(GifFile, NULL);
77fc6afd 398 return NULL;
02d1d628
AMH
399 }
400 while (Extension != NULL) {
401 if (DGifGetExtensionNext(GifFile, &Extension) == GIF_ERROR) {
7a5f7bc6 402 gif_push_error(myGifError(GifFile));
77fc6afd 403 i_push_error(0, "reading next block of extension");
e6172a17 404 if (colour_table && *colour_table) {
ea9f6207 405 myfree(*colour_table);
e6172a17
TC
406 *colour_table = NULL;
407 }
a73aeb5f 408 myfree(GifRow);
77fc6afd 409 i_img_destroy(im);
7b486870 410 (void)myDGifCloseFile(GifFile, NULL);
77fc6afd 411 return NULL;
02d1d628
AMH
412 }
413 }
414 break;
415 case TERMINATE_RECORD_TYPE:
416 break;
417 default: /* Should be traps by DGifGetRecordType. */
418 break;
419 }
420 } while (RecordType != TERMINATE_RECORD_TYPE);
421
422 myfree(GifRow);
423
7b486870
TC
424 if (myDGifCloseFile(GifFile, &error) == GIF_ERROR) {
425 gif_push_error(error);
77fc6afd 426 i_push_error(0, "Closing GIF file object");
e6172a17 427 if (colour_table && *colour_table) {
ea9f6207 428 myfree(*colour_table);
e6172a17
TC
429 *colour_table = NULL;
430 }
77fc6afd
TC
431 i_img_destroy(im);
432 return NULL;
02d1d628 433 }
8c68bf11 434
ec6d8908 435 i_tags_set(&im->tags, "i_format", "gif", -1);
8c68bf11 436
02d1d628
AMH
437 return im;
438}
439
faa9b3e7
TC
440/*
441
442Internal function called by i_readgif_multi_low() in error handling
443
444*/
7a5f7bc6
TC
445static void
446free_images(i_img **imgs, int count) {
faa9b3e7 447 int i;
e310e5f9
TC
448
449 if (count) {
450 for (i = 0; i < count; ++i)
451 i_img_destroy(imgs[i]);
452 myfree(imgs);
453 }
faa9b3e7
TC
454}
455
456/*
f1adece7 457=item i_readgif_multi_low(GifFileType *gf, int *count, int page)
faa9b3e7
TC
458
459Reads one of more gif images from the given GIF file.
460
461Returns a pointer to an array of i_img *, and puts the count into
462*count.
463
f1adece7
TC
464If page is not -1 then the given image _only_ is returned from the
465file, where the first image is 0, the second 1 and so on.
466
faa9b3e7
TC
467Unlike the normal i_readgif*() functions the images are paletted
468images rather than a combined RGB image.
469
470This functions sets tags on the images returned:
471
472=over
473
474=item gif_left
475
476the offset of the image from the left of the "screen" ("Image Left
477Position")
478
479=item gif_top
480
481the offset of the image from the top of the "screen" ("Image Top Position")
482
483=item gif_interlace
484
485non-zero if the image was interlaced ("Interlace Flag")
486
487=item gif_screen_width
488
489=item gif_screen_height
490
491the size of the logical screen ("Logical Screen Width",
492"Logical Screen Height")
493
494=item gif_local_map
495
496Non-zero if this image had a local color map.
497
498=item gif_background
499
500The index in the global colormap of the logical screen's background
501color. This is only set if the current image uses the global
502colormap.
503
504=item gif_trans_index
505
506The index of the color in the colormap used for transparency. If the
507image has a transparency then it is returned as a 4 channel image with
508the alpha set to zero in this palette entry. ("Transparent Color Index")
509
510=item gif_delay
511
512The delay until the next frame is displayed, in 1/100 of a second.
513("Delay Time").
514
515=item gif_user_input
516
517whether or not a user input is expected before continuing (view dependent)
518("User Input Flag").
519
520=item gif_disposal
521
522how the next frame is displayed ("Disposal Method")
523
524=item gif_loop
525
526the number of loops from the Netscape Loop extension. This may be zero.
527
528=item gif_comment
529
530the first block of the first gif comment before each image.
531
532=back
533
534Where applicable, the ("name") is the name of that field from the GIF89
535standard.
536
537=cut
538*/
539
7a5f7bc6
TC
540i_img **
541i_readgif_multi_low(GifFileType *GifFile, int *count, int page) {
faa9b3e7 542 i_img *img;
a659442a 543 int i, j, Size, Width, Height, ExtCode, Count;
98747309 544 int ImageNum = 0, ColorMapSize = 0;
faa9b3e7
TC
545 ColorMapObject *ColorMap;
546
547 GifRecordType RecordType;
548 GifByteType *Extension;
549
550 GifRowType GifRow;
551 int got_gce = 0;
336f5078
TC
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 */
faa9b3e7 556 int got_ns_loop = 0;
336f5078 557 int ns_loop = 0;
faa9b3e7
TC
558 char *comment = NULL; /* a comment */
559 i_img **results = NULL;
560 int result_alloc = 0;
561 int channels;
bcff4dd9 562 int image_colors = 0;
919e0000 563 i_color black; /* used to expand the palette if needed */
7b486870 564 int error;
919e0000
TC
565
566 for (i = 0; i < MAXCHANNELS; ++i)
567 black.channel[i] = 0;
faa9b3e7
TC
568
569 *count = 0;
570
571 mm_log((1,"i_readgif_multi_low(GifFile %p, , count %p)\n", GifFile, count));
572
faa9b3e7
TC
573 Size = GifFile->SWidth * sizeof(GifPixelType);
574
ec6d8908 575 GifRow = (GifRowType) mymalloc(Size);
faa9b3e7
TC
576
577 /* Scan the content of the GIF file and load the image(s) in: */
578 do {
579 if (DGifGetRecordType(GifFile, &RecordType) == GIF_ERROR) {
7a5f7bc6 580 gif_push_error(myGifError(GifFile));
faa9b3e7
TC
581 i_push_error(0, "Unable to get record type");
582 free_images(results, *count);
7b486870 583 (void)myDGifCloseFile(GifFile, NULL);
f1adece7 584 myfree(GifRow);
b4810f72
TC
585 if (comment)
586 myfree(comment);
faa9b3e7
TC
587 return NULL;
588 }
589
590 switch (RecordType) {
591 case IMAGE_DESC_RECORD_TYPE:
592 if (DGifGetImageDesc(GifFile) == GIF_ERROR) {
7a5f7bc6 593 gif_push_error(myGifError(GifFile));
faa9b3e7
TC
594 i_push_error(0, "Unable to get image descriptor");
595 free_images(results, *count);
7b486870 596 (void)myDGifCloseFile(GifFile, NULL);
f1adece7 597 myfree(GifRow);
b4810f72
TC
598 if (comment)
599 myfree(comment);
faa9b3e7
TC
600 return NULL;
601 }
602
faa9b3e7
TC
603 Width = GifFile->Image.Width;
604 Height = GifFile->Image.Height;
f1adece7
TC
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;
609 } else {
610 /* No colormap and we are about to read in the image -
611 abandon for now */
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);
7b486870 615 (void)myDGifCloseFile(GifFile, NULL);
f1adece7 616 myfree(GifRow);
b4810f72
TC
617 if (comment)
618 myfree(comment);
f1adece7
TC
619 return NULL;
620 }
621
622 channels = 3;
623 if (got_gce && trans_index >= 0)
624 channels = 4;
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"));
7b486870 628 (void)myDGifCloseFile(GifFile, NULL);
f1adece7 629 myfree(GifRow);
b4810f72
TC
630 if (comment)
631 myfree(comment);
f1adece7
TC
632 return NULL;
633 }
634 img = i_img_pal_new(Width, Height, channels, 256);
635 if (!img) {
636 free_images(results, *count);
7b486870 637 (void)myDGifCloseFile(GifFile, NULL);
b4810f72
TC
638 if (comment)
639 myfree(comment);
640 myfree(GifRow);
f1adece7
TC
641 return NULL;
642 }
643 /* populate the palette of the new image */
644 mm_log((1, "ColorMapSize %d\n", ColorMapSize));
645 for (i = 0; i < ColorMapSize; ++i) {
646 i_color col;
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)
651 col.rgba.a = 0;
652 else
653 col.rgba.a = 255;
654
655 i_addcolors(img, &col, 1);
656 }
bcff4dd9 657 image_colors = ColorMapSize;
f1adece7
TC
658 ++*count;
659 if (*count > result_alloc) {
660 if (result_alloc == 0) {
661 result_alloc = 5;
662 results = mymalloc(result_alloc * sizeof(i_img *));
663 }
664 else {
665 /* myrealloc never fails (it just dies if it can't allocate) */
666 result_alloc *= 2;
667 results = myrealloc(results, result_alloc * sizeof(i_img *));
668 }
669 }
670 results[*count-1] = img;
ec6d8908
TC
671 i_tags_set(&img->tags, "i_format", "gif", -1);
672 i_tags_setn(&img->tags, "gif_left", GifFile->Image.Left);
f1adece7 673 /**(char *)0 = 1;*/
ec6d8908
TC
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);
f1adece7 679 if (GifFile->SColorMap && !GifFile->Image.ColorMap) {
ec6d8908 680 i_tags_setn(&img->tags, "gif_background",
f1adece7
TC
681 GifFile->SBackGroundColor);
682 }
683 if (GifFile->Image.ColorMap) {
ec6d8908 684 i_tags_setn(&img->tags, "gif_localmap", 1);
f1adece7
TC
685 }
686 if (got_gce) {
687 if (trans_index >= 0) {
688 i_color trans;
ec6d8908 689 i_tags_setn(&img->tags, "gif_trans_index", trans_index);
f1adece7
TC
690 i_getcolors(img, trans_index, &trans, 1);
691 i_tags_set_color(&img->tags, "gif_trans_color", 0, &trans);
692 }
ec6d8908
TC
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);
f1adece7
TC
696 }
697 got_gce = 0;
698 if (got_ns_loop)
ec6d8908 699 i_tags_setn(&img->tags, "gif_loop", ns_loop);
f1adece7 700 if (comment) {
ec6d8908 701 i_tags_set(&img->tags, "gif_comment", comment, strlen(comment));
f1adece7
TC
702 myfree(comment);
703 comment = NULL;
704 }
705
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));
708
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);
7b486870 713 (void)myDGifCloseFile(GifFile, NULL);
f1adece7 714 myfree(GifRow);
b4810f72
TC
715 if (comment)
716 myfree(comment);
f1adece7
TC
717 return(0);
718 }
719
720 if (GifFile->Image.Interlace) {
721 for (Count = i = 0; i < 4; i++) {
722 for (j = InterlacedOffset[i]; j < Height;
723 j += InterlacedJumps[i]) {
724 Count++;
725 if (DGifGetLine(GifFile, GifRow, Width) == GIF_ERROR) {
7a5f7bc6 726 gif_push_error(myGifError(GifFile));
f1adece7
TC
727 i_push_error(0, "Reading GIF line");
728 free_images(results, *count);
7b486870 729 (void)myDGifCloseFile(GifFile, NULL);
f1adece7 730 myfree(GifRow);
b4810f72
TC
731 if (comment)
732 myfree(comment);
f1adece7
TC
733 return NULL;
734 }
bcff4dd9
TC
735
736 /* range check the scanline if needed */
737 if (image_colors != 256) {
738 int x;
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);
743 ++image_colors;
744 }
745 }
746 }
747
f1adece7
TC
748 i_ppal(img, 0, Width, j, GifRow);
749 }
750 }
751 }
752 else {
753 for (i = 0; i < Height; i++) {
754 if (DGifGetLine(GifFile, GifRow, Width) == GIF_ERROR) {
7a5f7bc6 755 gif_push_error(myGifError(GifFile));
f1adece7
TC
756 i_push_error(0, "Reading GIF line");
757 free_images(results, *count);
7b486870 758 (void)myDGifCloseFile(GifFile, NULL);
f1adece7 759 myfree(GifRow);
b4810f72
TC
760 if (comment)
761 myfree(comment);
f1adece7
TC
762 return NULL;
763 }
764
bcff4dd9
TC
765 /* range check the scanline if needed */
766 if (image_colors != 256) {
767 int x;
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);
772 ++image_colors;
773 }
774 }
775 }
776
f1adece7
TC
777 i_ppal(img, 0, Width, i, GifRow);
778 }
779 }
faa9b3e7 780
f1adece7
TC
781 /* must be only one image wanted and that was it */
782 if (page != -1) {
783 myfree(GifRow);
7b486870 784 (void)myDGifCloseFile(GifFile, NULL);
b4810f72
TC
785 if (comment)
786 myfree(comment);
f1adece7 787 return results;
faa9b3e7
TC
788 }
789 }
790 else {
f1adece7
TC
791 /* skip the image */
792 /* whether interlaced or not, it has the same number of lines */
793 /* giflib does't have an interface to skip the image data */
faa9b3e7
TC
794 for (i = 0; i < Height; i++) {
795 if (DGifGetLine(GifFile, GifRow, Width) == GIF_ERROR) {
7a5f7bc6 796 gif_push_error(myGifError(GifFile));
faa9b3e7 797 i_push_error(0, "Reading GIF line");
f1adece7
TC
798 free_images(results, *count);
799 myfree(GifRow);
7b486870 800 (void)myDGifCloseFile(GifFile, NULL);
b4810f72
TC
801 if (comment)
802 myfree(comment);
faa9b3e7
TC
803 return NULL;
804 }
f1adece7 805 }
faa9b3e7 806
f1adece7
TC
807 /* kill the comment so we get the right comment for the page */
808 if (comment) {
809 myfree(comment);
810 comment = NULL;
faa9b3e7
TC
811 }
812 }
f1adece7 813 ImageNum++;
faa9b3e7
TC
814 break;
815 case EXTENSION_RECORD_TYPE:
816 /* Skip any extension blocks in file: */
817 if (DGifGetExtension(GifFile, &ExtCode, &Extension) == GIF_ERROR) {
7a5f7bc6 818 gif_push_error(myGifError(GifFile));
faa9b3e7
TC
819 i_push_error(0, "Reading extension record");
820 free_images(results, *count);
b4810f72 821 myfree(GifRow);
7b486870 822 (void)myDGifCloseFile(GifFile, NULL);
b4810f72
TC
823 if (comment)
824 myfree(comment);
faa9b3e7
TC
825 return NULL;
826 }
2b82e731
TC
827 /* possibly this should be an error, but "be liberal in what you accept" */
828 if (!Extension)
829 break;
faa9b3e7
TC
830 if (ExtCode == 0xF9) {
831 got_gce = 1;
832 if (Extension[1] & 1)
833 trans_index = Extension[4];
834 else
835 trans_index = -1;
836 gif_delay = Extension[2] + 256 * Extension[3];
336f5078
TC
837 user_input = (Extension[1] & 2) != 0;
838 disposal = (Extension[1] >> 2) & 7;
faa9b3e7
TC
839 }
840 if (ExtCode == 0xFF && *Extension == 11) {
841 if (memcmp(Extension+1, "NETSCAPE2.0", 11) == 0) {
842 if (DGifGetExtensionNext(GifFile, &Extension) == GIF_ERROR) {
7a5f7bc6 843 gif_push_error(myGifError(GifFile));
faa9b3e7
TC
844 i_push_error(0, "reading loop extension");
845 free_images(results, *count);
b4810f72 846 myfree(GifRow);
7b486870 847 (void)myDGifCloseFile(GifFile, NULL);
b4810f72
TC
848 if (comment)
849 myfree(comment);
faa9b3e7
TC
850 return NULL;
851 }
852 if (Extension && *Extension == 3) {
853 got_ns_loop = 1;
854 ns_loop = Extension[2] + 256 * Extension[3];
855 }
856 }
857 }
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.
864 */
865 if (!comment) {
866 comment = mymalloc(*Extension+1);
867 memcpy(comment, Extension+1, *Extension);
868 comment[*Extension] = '\0';
869 }
870 }
871 while (Extension != NULL) {
872 if (DGifGetExtensionNext(GifFile, &Extension) == GIF_ERROR) {
7a5f7bc6 873 gif_push_error(myGifError(GifFile));
faa9b3e7
TC
874 i_push_error(0, "reading next block of extension");
875 free_images(results, *count);
b4810f72 876 myfree(GifRow);
7b486870 877 (void)myDGifCloseFile(GifFile, NULL);
b4810f72
TC
878 if (comment)
879 myfree(comment);
faa9b3e7
TC
880 return NULL;
881 }
882 }
883 break;
884 case TERMINATE_RECORD_TYPE:
885 break;
886 default: /* Should be trapped by DGifGetRecordType. */
887 break;
888 }
889 } while (RecordType != TERMINATE_RECORD_TYPE);
890
891 if (comment) {
892 if (*count) {
ec6d8908
TC
893 i_tags_set(&(results[*count-1]->tags), "gif_comment", comment,
894 strlen(comment));
faa9b3e7
TC
895 }
896 myfree(comment);
897 }
898
899 myfree(GifRow);
900
7b486870
TC
901 if (myDGifCloseFile(GifFile, &error) == GIF_ERROR) {
902 gif_push_error(error);
faa9b3e7
TC
903 i_push_error(0, "Closing GIF file object");
904 free_images(results, *count);
905 return NULL;
906 }
907
ed7bed5c
TC
908 if (!ImageNum) {
909 /* there were no images */
910 i_push_error(0, "no images found in file");
911 return NULL;
912 }
913
f1adece7
TC
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);
918 return NULL;
919 }
920
faa9b3e7
TC
921 return results;
922}
923
10461f9a 924static int io_glue_read_cb(GifFileType *gft, GifByteType *buf, int length);
10461f9a
TC
925
926/*
927=item i_readgif_multi_wiol(ig, int *count)
928
929=cut
930*/
931
932i_img **
933i_readgif_multi_wiol(io_glue *ig, int *count) {
faa9b3e7 934 GifFileType *GifFile;
7a5f7bc6 935 int gif_error;
536b7775
TC
936 i_img **result;
937
938 gif_mutex_lock(mutex);
7a5f7bc6 939
faa9b3e7
TC
940 i_clear_error();
941
7a5f7bc6
TC
942 if ((GifFile = myDGifOpen((void *)ig, io_glue_read_cb, &gif_error )) == NULL) {
943 gif_push_error(gif_error);
faa9b3e7 944 i_push_error(0, "Cannot create giflib callback object");
ec6d8908 945 mm_log((1,"i_readgif_multi_wiol: Unable to open callback datasource.\n"));
536b7775 946 gif_mutex_unlock(mutex);
faa9b3e7
TC
947 return NULL;
948 }
ec6d8908 949
536b7775
TC
950 result = i_readgif_multi_low(GifFile, count, -1);
951
952 gif_mutex_unlock(mutex);
953
954 return result;
faa9b3e7
TC
955}
956
10461f9a
TC
957static int
958io_glue_read_cb(GifFileType *gft, GifByteType *buf, int length) {
959 io_glue *ig = (io_glue *)gft->UserData;
960
6d5c85a2 961 return i_io_read(ig, buf, length);
10461f9a
TC
962}
963
10461f9a
TC
964i_img *
965i_readgif_wiol(io_glue *ig, int **color_table, int *colors) {
ec6d8908 966 GifFileType *GifFile;
7a5f7bc6 967 int gif_error;
536b7775
TC
968 i_img *result;
969
970 gif_mutex_lock(mutex);
10461f9a 971
10461f9a 972 i_clear_error();
10461f9a 973
7a5f7bc6
TC
974 if ((GifFile = myDGifOpen((void *)ig, io_glue_read_cb, &gif_error )) == NULL) {
975 gif_push_error(gif_error);
ec6d8908
TC
976 i_push_error(0, "Cannot create giflib callback object");
977 mm_log((1,"i_readgif_wiol: Unable to open callback datasource.\n"));
536b7775 978 gif_mutex_unlock(mutex);
ec6d8908 979 return NULL;
10461f9a 980 }
ec6d8908 981
536b7775
TC
982 result = i_readgif_low(GifFile, color_table, colors);
983
984 gif_mutex_unlock(mutex);
985
986 return result;
10461f9a
TC
987}
988
f1adece7
TC
989/*
990=item i_readgif_single_low(GifFile, page)
991
992Lower level function to read a single image from a GIF.
993
994page must be non-negative.
995
996=cut
997*/
998static i_img *
999i_readgif_single_low(GifFileType *GifFile, int page) {
1000 int count = 0;
1001 i_img **imgs;
1002
1003 imgs = i_readgif_multi_low(GifFile, &count, page);
1004
1005 if (imgs && count) {
1006 i_img *result = imgs[0];
1007
1008 myfree(imgs);
1009 return result;
1010 }
1011 else {
1012 /* i_readgif_multi_low() handles the errors appropriately */
1013 return NULL;
1014 }
1015}
1016
1017/*
1018=item i_readgif_single_wiol(ig, page)
1019
1020Read a single page from a GIF image file, where the page is indexed
1021from 0.
1022
1023Returns NULL if the page isn't found.
1024
1025=cut
1026*/
1027
1028i_img *
1029i_readgif_single_wiol(io_glue *ig, int page) {
272fcca5 1030 GifFileType *GifFile;
7a5f7bc6 1031 int gif_error;
536b7775 1032 i_img *result;
f1adece7 1033
272fcca5 1034 i_clear_error();
f1adece7
TC
1035 if (page < 0) {
1036 i_push_error(0, "page must be non-negative");
1037 return NULL;
1038 }
1039
536b7775 1040 gif_mutex_lock(mutex);
7a5f7bc6
TC
1041
1042 if ((GifFile = myDGifOpen((void *)ig, io_glue_read_cb, &gif_error )) == NULL) {
1043 gif_push_error(gif_error);
ec6d8908
TC
1044 i_push_error(0, "Cannot create giflib callback object");
1045 mm_log((1,"i_readgif_wiol: Unable to open callback datasource.\n"));
536b7775 1046 gif_mutex_unlock(mutex);
f1adece7 1047 return NULL;
f1adece7 1048 }
ec6d8908 1049
536b7775
TC
1050 result = i_readgif_single_low(GifFile, page);
1051
1052 gif_mutex_unlock(mutex);
1053
1054 return result;
f1adece7
TC
1055}
1056
a3923855
TC
1057/*
1058=item do_write(GifFileType *gf, i_gif_opts *opts, i_img *img, i_palidx *data)
1059
1060Internal. Low level image write function. Writes in interlace if
1061that was requested in the GIF options.
1062
1063Returns non-zero on success.
1064
1065=cut
1066*/
02d1d628 1067static undef_int
97c4effc
TC
1068do_write(GifFileType *gf, int interlace, i_img *img, i_palidx *data) {
1069 if (interlace) {
02d1d628
AMH
1070 int i, j;
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) {
7a5f7bc6 1074 gif_push_error(myGifError(gf));
c48818b1 1075 i_push_error(0, "Could not save image data:");
02d1d628 1076 mm_log((1, "Error in EGifPutLine\n"));
02d1d628
AMH
1077 return 0;
1078 }
1079 }
1080 }
1081 }
1082 else {
1083 int y;
1084 for (y = 0; y < img->ysize; ++y) {
1085 if (EGifPutLine(gf, data, img->xsize) == GIF_ERROR) {
7a5f7bc6 1086 gif_push_error(myGifError(gf));
c48818b1 1087 i_push_error(0, "Could not save image data:");
02d1d628 1088 mm_log((1, "Error in EGifPutLine\n"));
02d1d628
AMH
1089 return 0;
1090 }
1091 data += img->xsize;
1092 }
1093 }
1094
1095 return 1;
1096}
1097
a3923855
TC
1098/*
1099=item do_gce(GifFileType *gf, int index, i_gif_opts *opts, int want_trans, int trans_index)
1100
1101Internal. Writes the GIF graphics control extension, if necessary.
1102
1103Returns non-zero on success.
1104
1105=cut
1106*/
7a5f7bc6
TC
1107
1108static int
1109do_gce(GifFileType *gf, i_img *img, int want_trans, int trans_index)
02d1d628
AMH
1110{
1111 unsigned char gce[4] = {0};
1112 int want_gce = 0;
97c4effc
TC
1113 int delay;
1114 int user_input;
1115 int disposal_method;
1116
02d1d628
AMH
1117 if (want_trans) {
1118 gce[0] |= 1;
1119 gce[3] = trans_index;
1120 ++want_gce;
1121 }
97c4effc
TC
1122 if (i_tags_get_int(&img->tags, "gif_delay", 0, &delay)) {
1123 gce[1] = delay % 256;
1124 gce[2] = delay / 256;
02d1d628
AMH
1125 ++want_gce;
1126 }
97c4effc
TC
1127 if (i_tags_get_int(&img->tags, "gif_user_input", 0, &user_input)
1128 && user_input) {
1129 gce[0] |= 2;
02d1d628
AMH
1130 ++want_gce;
1131 }
97c4effc
TC
1132 if (i_tags_get_int(&img->tags, "gif_disposal", 0, &disposal_method)) {
1133 gce[0] |= (disposal_method & 3) << 2;
02d1d628
AMH
1134 ++want_gce;
1135 }
1136 if (want_gce) {
c48818b1 1137 if (EGifPutExtension(gf, 0xF9, sizeof(gce), gce) == GIF_ERROR) {
7a5f7bc6 1138 gif_push_error(myGifError(gf));
c48818b1
TC
1139 i_push_error(0, "Could not save GCE");
1140 }
02d1d628
AMH
1141 }
1142 return 1;
1143}
1144
97c4effc
TC
1145/*
1146=item do_comments(gf, img)
1147
1148Write any comments in the image.
1149
1150=cut
1151*/
7a5f7bc6
TC
1152
1153static int
1154do_comments(GifFileType *gf, i_img *img) {
97c4effc
TC
1155 int pos = -1;
1156
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) {
1160 return 0;
1161 }
1162 }
1163 else {
1164 char buf[50];
86c8d19a
TC
1165#ifdef IMAGER_SNPRINTF
1166 snprintf(buf, sizeof(buf), "%d", img->tags.tags[pos].idata);
1167#else
97c4effc 1168 sprintf(buf, "%d", img->tags.tags[pos].idata);
86c8d19a 1169#endif
97c4effc
TC
1170 if (EGifPutComment(gf, buf) == GIF_ERROR) {
1171 return 0;
1172 }
1173 }
1174 }
1175
1176 return 1;
1177}
1178
a3923855
TC
1179/*
1180=item do_ns_loop(GifFileType *gf, i_gif_opts *opts)
1181
1182Internal. Add the Netscape2.0 loop extension block, if requested.
1183
1660561c
TC
1184Giflib/libungif prior to 4.1.1 didn't support writing application
1185extension blocks, so we don't attempt to write them for older versions.
1186
1187Giflib/libungif prior to 4.1.3 used the wrong write mechanism when
1188writing extension blocks so that they could only be written to files.
a3923855
TC
1189
1190=cut
1191*/
7a5f7bc6
TC
1192
1193static int
1194do_ns_loop(GifFileType *gf, i_img *img)
02d1d628 1195{
02d1d628
AMH
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
1200 directly to the fd.
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.
1205 */
28a9109d 1206 /* yes this was another attempt at supporting the loop extension */
97c4effc
TC
1207 int loop_count;
1208 if (i_tags_get_int(&img->tags, "gif_loop", 0, &loop_count)) {
28a9109d
TC
1209 unsigned char nsle[12] = "NETSCAPE2.0";
1210 unsigned char subblock[3];
7a5f7bc6
TC
1211
1212 subblock[0] = 1;
1213 subblock[1] = loop_count % 256;
1214 subblock[2] = loop_count / 256;
1215
7b486870 1216#if IMGIFLIB_API_VERSION >= 500
7a5f7bc6
TC
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");
1223 return 0;
1224 }
1225
1226#else
1660561c 1227 if (EGifPutExtensionFirst(gf, APPLICATION_EXT_FUNC_CODE, 11, nsle) == GIF_ERROR) {
7a5f7bc6 1228 gif_push_error(myGifError(gf));
28a9109d
TC
1229 i_push_error(0, "writing loop extension");
1230 return 0;
1231 }
1660561c 1232 if (EGifPutExtensionLast(gf, APPLICATION_EXT_FUNC_CODE, 3, subblock) == GIF_ERROR) {
7a5f7bc6 1233 gif_push_error(myGifError(gf));
7468f3fa 1234 i_push_error(0, "writing loop extension sub-block");
28a9109d
TC
1235 return 0;
1236 }
7a5f7bc6 1237#endif
02d1d628 1238 }
1660561c 1239
02d1d628
AMH
1240 return 1;
1241}
1242
a3923855 1243/*
97c4effc 1244=item make_gif_map(i_quantize *quant, int want_trans)
a3923855
TC
1245
1246Create a giflib color map object from an Imager color map.
1247
1248=cut
1249*/
1250
7a5f7bc6
TC
1251static ColorMapObject *
1252make_gif_map(i_quantize *quant, i_img *img, int want_trans) {
02d1d628
AMH
1253 GifColorType colors[256];
1254 int i;
1255 int size = quant->mc_count;
1256 int map_size;
c48818b1 1257 ColorMapObject *map;
97c4effc 1258 i_color trans;
02d1d628
AMH
1259
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;
1264 }
1265 if (want_trans) {
97c4effc
TC
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;
02d1d628
AMH
1271 ++size;
1272 }
1273 map_size = 1;
1274 while (map_size < size)
1275 map_size <<= 1;
85363ac2
TC
1276 /* giflib spews for 1 colour maps, reasonable, I suppose */
1277 if (map_size == 1)
1278 map_size = 2;
10461f9a
TC
1279 while (i < map_size) {
1280 colors[i].Red = colors[i].Green = colors[i].Blue = 0;
1281 ++i;
1282 }
a743c0a6 1283
c48818b1
TC
1284 map = MakeMapObject(map_size, colors);
1285 if (!map) {
c48818b1
TC
1286 i_push_error(0, "Could not create color map object");
1287 return NULL;
1288 }
4cc5f913 1289 mm_log((1, "XXX map is at %p and colors at %p\n", map, map->Colors));
7b486870 1290#if IMGIFLIB_API_VERSION >= 500
2e720544
TC
1291 map->SortFlag = 0;
1292#endif
c48818b1 1293 return map;
02d1d628
AMH
1294}
1295
a3923855 1296/*
a93c43b4 1297=item need_version_89a(i_quantize *quant, i_img *imgs, int count)
a3923855 1298
a93c43b4
TC
1299Return true if the file we're creating on these images needs a GIF89a
1300header.
7a5f7bc6 1301
a3923855 1302=cut
02d1d628 1303*/
a3923855 1304
a93c43b4
TC
1305static int
1306need_version_89a(i_quantize *quant, i_img **imgs, int count) {
1660561c
TC
1307 int need_89a = 0;
1308 int temp;
1309 int i;
1310
58138933
TC
1311 for (i = 0; i < count; ++i) {
1312 if (quant->transp != tr_none &&
1313 (imgs[i]->channels == 2 || imgs[i]->channels == 4)) {
1314 need_89a = 1;
1315 break;
1316 }
1317 if (i_tags_get_int(&imgs[i]->tags, "gif_delay", 0, &temp)) {
1318 need_89a = 1;
1319 break;
1320 }
1321 if (i_tags_get_int(&imgs[i]->tags, "gif_user_input", 0, &temp) && temp) {
1322 need_89a = 1;
1323 break;
1324 }
1325 if (i_tags_get_int(&imgs[i]->tags, "gif_disposal", 0, &temp)) {
1326 need_89a = 1;
1327 break;
1328 }
1329 if (i_tags_get_int(&imgs[i]->tags, "gif_loop", 0, &temp)) {
1330 need_89a = 1;
1331 break;
1660561c
TC
1332 }
1333 }
a93c43b4
TC
1334
1335 return need_89a;
02d1d628
AMH
1336}
1337
bf9dd17c
TC
1338static int
1339in_palette(i_color *c, i_quantize *quant, int size) {
1340 int i;
1341
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]) {
1346 return i;
1347 }
1348 }
1349
1350 return -1;
1351}
1352
1353/*
59c150a4 1354=item has_common_palette(imgs, count, quant)
bf9dd17c 1355
59c150a4
TC
1356Tests if all the given images are paletted and their colors are in the
1357palette produced.
bf9dd17c 1358
59c150a4
TC
1359Previously this would build a consolidated palette from the source,
1360but that meant that if the caller supplied a static palette (or
1361specified a fixed palette like "webmap") then we wouldn't be
1362quantizing to the caller specified palette.
bf9dd17c 1363
97c4effc
TC
1364=cut
1365*/
59c150a4 1366
bf9dd17c 1367static int
59c150a4 1368has_common_palette(i_img **imgs, int count, i_quantize *quant) {
a659442a 1369 int i;
bf9dd17c 1370 int imgn;
bf9dd17c 1371 char used[256];
59c150a4 1372 int col_count;
bf9dd17c
TC
1373
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) {
97c4effc 1377 int eliminate_unused;
bf9dd17c
TC
1378 if (imgs[imgn]->type != i_palette_type)
1379 return 0;
1380
97c4effc
TC
1381 if (!i_tags_get_int(&imgs[imgn]->tags, "gif_eliminate_unused", 0,
1382 &eliminate_unused)) {
1383 eliminate_unused = 1;
1384 }
1385
1386 if (eliminate_unused) {
bf9dd17c
TC
1387 i_palidx *line = mymalloc(sizeof(i_palidx) * imgs[imgn]->xsize);
1388 int x, y;
1389 memset(used, 0, sizeof(used));
1390
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)
1394 used[line[x]] = 1;
1395 }
1396
1397 myfree(line);
1398 }
1399 else {
1400 /* assume all are in use */
1401 memset(used, 1, sizeof(used));
1402 }
1403
59c150a4
TC
1404 col_count = i_colorcount(imgs[imgn]);
1405 for (i = 0; i < col_count; ++i) {
bf9dd17c
TC
1406 i_color c;
1407
1408 i_getcolors(imgs[imgn], i, &c, 1);
1409 if (used[i]) {
59c150a4
TC
1410 if (in_palette(&c, quant, quant->mc_count) < 0) {
1411 mm_log((1, " color not found in palette, no palette shortcut\n"));
1412
1413 return 0;
bf9dd17c
TC
1414 }
1415 }
1416 }
1417 }
1418
59c150a4 1419 mm_log((1, " all colors found in palette, palette shortcut\n"));
bf9dd17c
TC
1420
1421 return 1;
1422}
1423
1424static i_palidx *
1425quant_paletted(i_quantize *quant, i_img *img) {
1426 i_palidx *data = mymalloc(sizeof(i_palidx) * img->xsize * img->ysize);
1427 i_palidx *p = data;
1428 i_palidx trans[256];
1429 int i;
8d14daab 1430 i_img_dim x, y;
bf9dd17c
TC
1431
1432 /* build a translation table */
1433 for (i = 0; i < i_colorcount(img); ++i) {
1434 i_color c;
1435 i_getcolors(img, i, &c, 1);
1436 trans[i] = in_palette(&c, quant, quant->mc_count);
1437 }
1438
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) {
1442 *p = trans[*p];
1443 ++p;
1444 }
1445 }
1446
1447 return data;
1448}
1449
a3923855
TC
1450/*
1451=item i_writegif_low(i_quantize *quant, GifFileType *gf, i_img **imgs, int count, i_gif_opts *opts)
1452
1453Internal. Low-level function that does the high-level GIF processing
1454:)
1455
1456Returns non-zero on success.
1457
1458=cut
1459*/
1460
02d1d628 1461static undef_int
97c4effc 1462i_writegif_low(i_quantize *quant, GifFileType *gf, i_img **imgs, int count) {
336f5078 1463 unsigned char *result = NULL;
02d1d628
AMH
1464 int color_bits;
1465 ColorMapObject *map;
1466 int scrw = 0, scrh = 0;
1467 int imgn, orig_count, orig_size;
1468 int posx, posy;
336f5078 1469 int trans_index = -1;
a139e7e9 1470 int *localmaps = NULL;
97c4effc 1471 int anylocal;
a139e7e9 1472 i_img **glob_imgs = NULL; /* images that will use the global color map */
97c4effc
TC
1473 int glob_img_count;
1474 i_color *orig_colors = quant->mc_colors;
1475 i_color *glob_colors = NULL;
336f5078 1476 int glob_color_count = 0;
97c4effc 1477 int glob_want_trans;
336f5078
TC
1478 int glob_paletted = 0; /* the global map was made from the image palettes */
1479 int colors_paletted = 0;
1480 int want_trans = 0;
97c4effc
TC
1481 int interlace;
1482 int gif_background;
7b486870 1483 int error;
97c4effc
TC
1484
1485 mm_log((1, "i_writegif_low(quant %p, gf %p, imgs %p, count %d)\n",
1486 quant, gf, imgs, count));
1487
1488 /* *((char *)0) = 1; */ /* used to break into the debugger */
1489
1490 if (count <= 0) {
1491 i_push_error(0, "No images provided to write");
a139e7e9 1492 goto fail_cleanup;
97c4effc 1493 }
02d1d628 1494
02d1d628
AMH
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;
1500
97c4effc
TC
1501 if (!i_tags_get_int(&imgs[0]->tags, "gif_screen_width", 0, &scrw))
1502 scrw = 0;
43b2b326 1503 if (!i_tags_get_int(&imgs[0]->tags, "gif_screen_height", 0, &scrh))
8d14daab 1504 scrh = 0;
97c4effc
TC
1505
1506 anylocal = 0;
ec6d8908
TC
1507 localmaps = mymalloc(sizeof(int) * count);
1508 glob_imgs = mymalloc(sizeof(i_img *) * count);
97c4effc
TC
1509 glob_img_count = 0;
1510 glob_want_trans = 0;
02d1d628 1511 for (imgn = 0; imgn < count; ++imgn) {
8d14daab
TC
1512 i_img *im = imgs[imgn];
1513 if (im->xsize > 0xFFFF || im->ysize > 0xFFFF) {
1514 i_push_error(0, "image too large for GIF");
a139e7e9 1515 goto fail_cleanup;
8d14daab
TC
1516 }
1517
97c4effc 1518 posx = posy = 0;
8d14daab
TC
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))
97c4effc
TC
1528 localmaps[imgn] = 0;
1529 if (localmaps[imgn])
1530 anylocal = 1;
02d1d628 1531 else {
8d14daab 1532 if (im->channels == 4) {
97c4effc
TC
1533 glob_want_trans = 1;
1534 }
8d14daab 1535 glob_imgs[glob_img_count++] = im;
02d1d628
AMH
1536 }
1537 }
97c4effc 1538 glob_want_trans = glob_want_trans && quant->transp != tr_none ;
02d1d628 1539
8d14daab
TC
1540 if (scrw > 0xFFFF || scrh > 0xFFFF) {
1541 i_push_error(0, "screen size too large for GIF");
a139e7e9 1542 goto fail_cleanup;
8d14daab
TC
1543 }
1544
02d1d628
AMH
1545 orig_count = quant->mc_count;
1546 orig_size = quant->mc_size;
1547
97c4effc
TC
1548 if (glob_img_count) {
1549 /* this is ugly */
ec6d8908 1550 glob_colors = mymalloc(sizeof(i_color) * quant->mc_size);
97c4effc
TC
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;
1557 }
1558 if (glob_want_trans && quant->mc_size == 256) {
1559 mm_log((2, " reserving color for transparency\n"));
5dcf9039 1560 --quant->mc_size;
bf9dd17c 1561 }
59c150a4
TC
1562
1563 i_quant_makemap(quant, glob_imgs, glob_img_count);
1564 glob_paletted = has_common_palette(glob_imgs, glob_img_count, quant);
97c4effc
TC
1565 glob_color_count = quant->mc_count;
1566 quant->mc_colors = orig_colors;
1567 }
02d1d628 1568
97c4effc
TC
1569 /* use the global map if we have one, otherwise use the local map */
1570 gif_background = 0;
1571 if (glob_colors) {
1572 quant->mc_colors = glob_colors;
1573 quant->mc_count = glob_color_count;
1574 want_trans = glob_want_trans && imgs[0]->channels == 4;
1575
1576 if (!i_tags_get_int(&imgs[0]->tags, "gif_background", 0, &gif_background))
1577 gif_background = 0;
1578 if (gif_background < 0)
1579 gif_background = 0;
1580 if (gif_background >= glob_color_count)
1581 gif_background = 0;
1582 }
1583 else {
1584 want_trans = quant->transp != tr_none && imgs[0]->channels == 4;
59c150a4
TC
1585 i_quant_makemap(quant, imgs, 1);
1586 colors_paletted = has_common_palette(imgs, 1, quant);
97c4effc 1587 }
59c150a4 1588
97c4effc 1589 if ((map = make_gif_map(quant, imgs[0], want_trans)) == NULL) {
97c4effc 1590 mm_log((1, "Error in MakeMapObject"));
a139e7e9 1591 goto fail_cleanup;
97c4effc
TC
1592 }
1593 color_bits = 1;
1594 if (anylocal) {
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))
1598 ++color_bits;
1599 }
1600 else {
1601 int count = quant->mc_count;
1602 if (want_trans)
1603 ++count;
1604 while (count > (1 << color_bits))
02d1d628 1605 ++color_bits;
97c4effc 1606 }
02d1d628 1607
97c4effc
TC
1608 if (EGifPutScreenDesc(gf, scrw, scrh, color_bits,
1609 gif_background, map) == GIF_ERROR) {
7a5f7bc6 1610 gif_push_error(myGifError(gf));
97c4effc 1611 i_push_error(0, "Could not save screen descriptor");
02d1d628 1612 FreeMapObject(map);
97c4effc 1613 mm_log((1, "Error in EGifPutScreenDesc."));
a139e7e9 1614 goto fail_cleanup;
97c4effc
TC
1615 }
1616 FreeMapObject(map);
02d1d628 1617
97c4effc
TC
1618 if (!i_tags_get_int(&imgs[0]->tags, "gif_left", 0, &posx))
1619 posx = 0;
1620 if (!i_tags_get_int(&imgs[0]->tags, "gif_top", 0, &posy))
1621 posy = 0;
02d1d628 1622
97c4effc
TC
1623 if (!localmaps[0]) {
1624 map = NULL;
1625 colors_paletted = glob_paletted;
1626 }
1627 else {
1628 /* if this image has a global map the colors in quant don't
1629 belong to this image, so build a palette */
1630 if (glob_colors) {
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;
1636
1637 /* if the caller gives us too many colours we can't do transparency */
1638 if (want_trans && quant->mc_count == 256)
1639 want_trans = 0;
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)
1643 --quant->mc_size;
59c150a4
TC
1644 i_quant_makemap(quant, imgs, 1);
1645 colors_paletted = has_common_palette(imgs, 1, quant);
97c4effc 1646 if ((map = make_gif_map(quant, imgs[0], want_trans)) == NULL) {
97c4effc 1647 mm_log((1, "Error in MakeMapObject"));
a139e7e9 1648 goto fail_cleanup;
97c4effc 1649 }
02d1d628 1650 }
97c4effc
TC
1651 else {
1652 /* the map we wrote was the map for this image - don't set the local
1653 map */
1654 map = NULL;
02d1d628 1655 }
97c4effc
TC
1656 }
1657
1658 if (colors_paletted)
1659 result = quant_paletted(quant, imgs[0]);
1660 else
92bda632 1661 result = i_quant_translate(quant, imgs[0]);
1501d9b3 1662 if (!result) {
a139e7e9 1663 goto fail_cleanup;
1501d9b3 1664 }
97c4effc 1665 if (want_trans) {
92bda632 1666 i_quant_transparent(quant, result, imgs[0], quant->mc_count);
97c4effc
TC
1667 trans_index = quant->mc_count;
1668 }
1669
1670 if (!do_ns_loop(gf, imgs[0])) {
a139e7e9 1671 goto fail_cleanup;
97c4effc
TC
1672 }
1673
1674 if (!do_gce(gf, imgs[0], want_trans, trans_index)) {
a139e7e9 1675 goto fail_cleanup;
97c4effc
TC
1676 }
1677
1678 if (!do_comments(gf, imgs[0])) {
a139e7e9 1679 goto fail_cleanup;
97c4effc
TC
1680 }
1681
1682 if (!i_tags_get_int(&imgs[0]->tags, "gif_interlace", 0, &interlace))
1683 interlace = 0;
1684 if (EGifPutImageDesc(gf, posx, posy, imgs[0]->xsize, imgs[0]->ysize,
1685 interlace, map) == GIF_ERROR) {
a139e7e9
TC
1686 if (map)
1687 FreeMapObject(map);
7a5f7bc6 1688 gif_push_error(myGifError(gf));
97c4effc 1689 i_push_error(0, "Could not save image descriptor");
97c4effc 1690 mm_log((1, "Error in EGifPutImageDesc."));
a139e7e9 1691 goto fail_cleanup;
97c4effc
TC
1692 }
1693 if (map)
1694 FreeMapObject(map);
1695
1696 if (!do_write(gf, interlace, imgs[0], result)) {
a139e7e9 1697 goto fail_cleanup;
97c4effc
TC
1698 }
1699 myfree(result);
a139e7e9 1700 result = NULL;
97c4effc
TC
1701
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;
02d1d628
AMH
1706 quant->mc_count = orig_count;
1707 quant->mc_size = orig_size;
97c4effc 1708
5dcf9039 1709 want_trans = quant->transp != tr_none
97c4effc 1710 && imgs[imgn]->channels == 4;
5dcf9039
TC
1711 /* if the caller gives us too many colours we can't do transparency */
1712 if (want_trans && quant->mc_count == 256)
1713 want_trans = 0;
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)
1717 --quant->mc_size;
1718
59c150a4 1719 if (has_common_palette(imgs+imgn, 1, quant)) {
bf9dd17c
TC
1720 result = quant_paletted(quant, imgs[imgn]);
1721 }
1722 else {
92bda632
TC
1723 i_quant_makemap(quant, imgs+imgn, 1);
1724 result = i_quant_translate(quant, imgs[imgn]);
bf9dd17c 1725 }
1501d9b3 1726 if (!result) {
92bda632 1727 mm_log((1, "error in i_quant_translate()"));
a139e7e9 1728 goto fail_cleanup;
1501d9b3 1729 }
bf9dd17c 1730 if (want_trans) {
92bda632 1731 i_quant_transparent(quant, result, imgs[imgn], quant->mc_count);
bf9dd17c
TC
1732 trans_index = quant->mc_count;
1733 }
1734
97c4effc 1735 if ((map = make_gif_map(quant, imgs[imgn], want_trans)) == NULL) {
97c4effc 1736 mm_log((1, "Error in MakeMapObject."));
a139e7e9 1737 goto fail_cleanup;
02d1d628 1738 }
bf9dd17c
TC
1739 }
1740 else {
97c4effc
TC
1741 quant->mc_colors = glob_colors;
1742 quant->mc_count = glob_color_count;
1743 if (glob_paletted)
1744 result = quant_paletted(quant, imgs[imgn]);
1745 else
92bda632 1746 result = i_quant_translate(quant, imgs[imgn]);
97c4effc
TC
1747 want_trans = glob_want_trans && imgs[imgn]->channels == 4;
1748 if (want_trans) {
92bda632 1749 i_quant_transparent(quant, result, imgs[imgn], quant->mc_count);
97c4effc
TC
1750 trans_index = quant->mc_count;
1751 }
1752 map = NULL;
bf9dd17c 1753 }
5dcf9039 1754
97c4effc 1755 if (!do_gce(gf, imgs[imgn], want_trans, trans_index)) {
a139e7e9 1756 goto fail_cleanup;
02d1d628 1757 }
02d1d628 1758
97c4effc 1759 if (!do_comments(gf, imgs[imgn])) {
a139e7e9 1760 goto fail_cleanup;
02d1d628 1761 }
02d1d628 1762
97c4effc
TC
1763 if (!i_tags_get_int(&imgs[imgn]->tags, "gif_left", 0, &posx))
1764 posx = 0;
1765 if (!i_tags_get_int(&imgs[imgn]->tags, "gif_top", 0, &posy))
1766 posy = 0;
1767
1768 if (!i_tags_get_int(&imgs[imgn]->tags, "gif_interlace", 0, &interlace))
1769 interlace = 0;
1770 if (EGifPutImageDesc(gf, posx, posy, imgs[imgn]->xsize,
1771 imgs[imgn]->ysize, interlace, map) == GIF_ERROR) {
7a5f7bc6 1772 gif_push_error(myGifError(gf));
c48818b1 1773 i_push_error(0, "Could not save image descriptor");
97c4effc
TC
1774 if (map)
1775 FreeMapObject(map);
02d1d628 1776 mm_log((1, "Error in EGifPutImageDesc."));
a139e7e9 1777 goto fail_cleanup;
02d1d628 1778 }
97c4effc
TC
1779 if (map)
1780 FreeMapObject(map);
1781
1782 if (!do_write(gf, interlace, imgs[imgn], result)) {
a139e7e9 1783 goto fail_cleanup;
02d1d628
AMH
1784 }
1785 myfree(result);
a139e7e9 1786 result = NULL;
02d1d628 1787 }
97c4effc 1788
7b486870 1789 if (myEGifCloseFile(gf, &error) == GIF_ERROR) {
7b486870 1790 gif_push_error(error);
c48818b1 1791 i_push_error(0, "Could not close GIF file");
a139e7e9 1792 goto fail_cleanup;
02d1d628 1793 }
7ac6a2e9
TC
1794 if (glob_colors) {
1795 int i;
1796 for (i = 0; i < glob_color_count; ++i)
1797 orig_colors[i] = glob_colors[i];
1798 }
1799
ec6d8908
TC
1800 myfree(glob_colors);
1801 myfree(localmaps);
1802 myfree(glob_imgs);
97c4effc 1803 quant->mc_colors = orig_colors;
02d1d628
AMH
1804
1805 return 1;
a139e7e9
TC
1806
1807 fail_cleanup:
1808 quant->mc_colors = orig_colors;
1809 myfree(result);
1810 myfree(glob_colors);
1811 myfree(localmaps);
1812 myfree(glob_imgs);
1813 (void)myEGifCloseFile(gf, &error);
1814 return 0;
02d1d628
AMH
1815}
1816
10461f9a
TC
1817static int
1818io_glue_write_cb(GifFileType *gft, const GifByteType *data, int length) {
1819 io_glue *ig = (io_glue *)gft->UserData;
1820
6d5c85a2 1821 return i_io_write(ig, data, length);
10461f9a
TC
1822}
1823
10461f9a
TC
1824
1825/*
1826=item i_writegif_wiol(ig, quant, opts, imgs, count)
1827
1828=cut
1829*/
1830undef_int
97c4effc 1831i_writegif_wiol(io_glue *ig, i_quantize *quant, i_img **imgs,
10461f9a 1832 int count) {
ec6d8908 1833 GifFileType *GifFile;
7a5f7bc6 1834 int gif_error;
ec6d8908 1835 int result;
62adede5 1836
536b7775
TC
1837 gif_mutex_lock(mutex);
1838
ec6d8908 1839 i_clear_error();
10461f9a 1840
a93c43b4
TC
1841#ifdef PRE_SET_VERSION
1842 EGifSetGifVersion(need_version_89a(quant, imgs, count) ? "89a" : "87a");
1843#endif
ec6d8908 1844
7a5f7bc6
TC
1845 if ((GifFile = myEGifOpen((void *)ig, io_glue_write_cb, &gif_error )) == NULL) {
1846 gif_push_error(gif_error);
ec6d8908
TC
1847 i_push_error(0, "Cannot create giflib callback object");
1848 mm_log((1,"i_writegif_wiol: Unable to open callback datasource.\n"));
536b7775 1849 gif_mutex_unlock(mutex);
10461f9a 1850 return 0;
10461f9a 1851 }
ec6d8908 1852
a93c43b4
TC
1853#ifdef POST_SET_VERSION
1854 EGifSetGifVersion(GifFile, need_version_89a(quant, imgs, count));
1855#endif
1856
ec6d8908
TC
1857 result = i_writegif_low(quant, GifFile, imgs, count);
1858
536b7775
TC
1859 gif_mutex_unlock(mutex);
1860
6d5c85a2
TC
1861 if (i_io_close(ig))
1862 return 0;
ec6d8908
TC
1863
1864 return result;
10461f9a
TC
1865}
1866
c48818b1
TC
1867/*
1868=item gif_error_msg(int code)
1869
1870Grabs the most recent giflib error code from GifLastError() and
1871returns a string that describes that error.
1872
7a5f7bc6 1873Returns NULL for unknown error codes.
c48818b1 1874
97c4effc
TC
1875=cut
1876*/
c48818b1 1877
7a5f7bc6
TC
1878static char const *
1879gif_error_msg(int code) {
7b486870 1880#if IMGIFLIB_API_VERSION >= 500
7a5f7bc6
TC
1881 return GifErrorString(code);
1882#else
c48818b1
TC
1883 switch (code) {
1884 case E_GIF_ERR_OPEN_FAILED: /* should not see this */
1885 return "Failed to open given file";
1886
1887 case E_GIF_ERR_WRITE_FAILED:
1888 return "Write failed";
1889
1890 case E_GIF_ERR_HAS_SCRN_DSCR: /* should not see this */
1891 return "Screen descriptor already passed to giflib";
1892
1893 case E_GIF_ERR_HAS_IMAG_DSCR: /* should not see this */
1894 return "Image descriptor already passed to giflib";
1895
1896 case E_GIF_ERR_NO_COLOR_MAP: /* should not see this */
1897 return "Neither global nor local color map set";
1898
1899 case E_GIF_ERR_DATA_TOO_BIG: /* should not see this */
1900 return "Too much pixel data passed to giflib";
1901
1902 case E_GIF_ERR_NOT_ENOUGH_MEM:
1903 return "Out of memory";
1904
1905 case E_GIF_ERR_DISK_IS_FULL:
1906 return "Disk is full";
1907
1908 case E_GIF_ERR_CLOSE_FAILED: /* should not see this */
1909 return "File close failed";
1910
1911 case E_GIF_ERR_NOT_WRITEABLE: /* should not see this */
1912 return "File not writable";
1913
1914 case D_GIF_ERR_OPEN_FAILED:
1915 return "Failed to open file";
1916
1917 case D_GIF_ERR_READ_FAILED:
1918 return "Failed to read from file";
1919
1920 case D_GIF_ERR_NOT_GIF_FILE:
1921 return "File is not a GIF file";
1922
1923 case D_GIF_ERR_NO_SCRN_DSCR:
1924 return "No screen descriptor detected - invalid file";
1925
1926 case D_GIF_ERR_NO_IMAG_DSCR:
1927 return "No image descriptor detected - invalid file";
1928
1929 case D_GIF_ERR_NO_COLOR_MAP:
1930 return "No global or local color map found";
1931
1932 case D_GIF_ERR_WRONG_RECORD:
1933 return "Wrong record type detected - invalid file?";
1934
1935 case D_GIF_ERR_DATA_TOO_BIG:
1936 return "Data in file too big for image";
1937
1938 case D_GIF_ERR_NOT_ENOUGH_MEM:
1939 return "Out of memory";
1940
1941 case D_GIF_ERR_CLOSE_FAILED:
1942 return "Close failed";
1943
1944 case D_GIF_ERR_NOT_READABLE:
1945 return "File not opened for read";
1946
1947 case D_GIF_ERR_IMAGE_DEFECT:
1948 return "Defective image";
1949
1950 case D_GIF_ERR_EOF_TOO_SOON:
1951 return "Unexpected EOF - invalid file";
1952
1953 default:
7a5f7bc6 1954 return NULL;
d03fd5a4 1955 }
7a5f7bc6 1956#endif
c48818b1
TC
1957}
1958
1959/*
7a5f7bc6 1960=item gif_push_error(code)
c48818b1
TC
1961
1962Utility function that takes the current GIF error code, converts it to
1963an error message and pushes it on the error stack.
1964
1965=cut
1966*/
1967
7a5f7bc6
TC
1968static void
1969gif_push_error(int code) {
1970 const char *msg = gif_error_msg(code);
1971 if (msg)
1972 i_push_error(code, msg);
1973 else
1974 i_push_errorf(code, "Unknown GIF error %d", code);
c48818b1
TC
1975}
1976
a3923855 1977/*
a3923855
TC
1978=head1 AUTHOR
1979
1980Arnar M. Hrafnkelsson, addi@umich.edu
1981
536b7775
TC
1982Tony Cook <tonyc@cpan.org>
1983
a3923855
TC
1984=head1 SEE ALSO
1985
1986perl(1), Imager(3)
1987
1988=cut
1989
1990*/