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