1 | /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */ |
2 | /* GdkPixbuf library - GIF image loader |
3 | * |
4 | * Copyright (C) 1999 Mark Crichton |
5 | * Copyright (C) 1999 The Free Software Foundation |
6 | * |
7 | * Authors: Jonathan Blandford <jrb@redhat.com> |
8 | * Adapted from the gimp gif filter written by Adam Moss <adam@gimp.org> |
9 | * Gimp work based on earlier work. |
10 | * Permission to relicense under the LGPL obtained. |
11 | * |
12 | * This library is free software; you can redistribute it and/or |
13 | * modify it under the terms of the GNU Lesser General Public |
14 | * License as published by the Free Software Foundation; either |
15 | * version 2 of the License, or (at your option) any later version. |
16 | * |
17 | * This library is distributed in the hope that it will be useful, |
18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
20 | * Lesser General Public License for more details. |
21 | * |
22 | * You should have received a copy of the GNU Lesser General Public |
23 | * License along with this library; if not, see <http://www.gnu.org/licenses/>. |
24 | */ |
25 | |
26 | /* This loader is very hairy code. |
27 | * |
28 | * The main loop was not designed for incremental loading, so when it was hacked |
29 | * in it got a bit messy. Basically, every function is written to expect a failed |
30 | * read_gif, and lets you call it again assuming that the bytes are there. |
31 | * |
32 | * Return vals. |
33 | * Unless otherwise specified, these are the return vals for most functions: |
34 | * |
35 | * 0 -> success |
36 | * -1 -> more bytes needed. |
37 | * -2 -> failure; abort the load |
38 | * -3 -> control needs to be passed back to the main loop |
39 | * \_ (most of the time returning 0 will get this, but not always) |
40 | * |
41 | * >1 -> for functions that get a guchar, the char will be returned. |
42 | * |
43 | * -jrb (11/03/1999) |
44 | */ |
45 | |
46 | /* |
47 | * If you have any images that crash this code, please, please let me know and |
48 | * send them to me. |
49 | * <jrb@redhat.com> |
50 | */ |
51 | |
52 | |
53 | |
54 | #include "config.h" |
55 | #include <stdio.h> |
56 | #include <string.h> |
57 | #include <errno.h> |
58 | #include <glib/gi18n-lib.h> |
59 | #include "gdk-pixbuf-io.h" |
60 | #include "io-gif-animation.h" |
61 | |
62 | |
63 | |
64 | #undef DUMP_IMAGE_DETAILS |
65 | |
66 | #define MAXCOLORMAPSIZE 256 |
67 | |
68 | #define INTERLACE 0x40 |
69 | #define LOCALCOLORMAP 0x80 |
70 | #define BitSet(byte, bit) (((byte) & (bit)) == (bit)) |
71 | #define LM_to_uint(a,b) (((b)<<8)|(a)) |
72 | |
73 | |
74 | |
75 | typedef unsigned char CMap[3][MAXCOLORMAPSIZE]; |
76 | |
77 | /* Possible states we can be in. */ |
78 | enum { |
79 | GIF_START, |
80 | GIF_GET_COLORMAP, |
81 | GIF_GET_NEXT_STEP, |
82 | GIF_GET_FRAME_INFO, |
83 | GIF_GET_EXTENSION, |
84 | GIF_GET_COLORMAP2, |
85 | GIF_PREPARE_LZW, |
86 | GIF_GET_LZW, |
87 | GIF_DONE |
88 | }; |
89 | |
90 | typedef struct _GifContext GifContext; |
91 | struct _GifContext |
92 | { |
93 | int state; /* really only relevant for progressive loading */ |
94 | unsigned int width; |
95 | unsigned int height; |
96 | |
97 | gboolean has_global_cmap; |
98 | |
99 | gint global_colormap_size; |
100 | unsigned int global_bit_pixel; |
101 | |
102 | CMap frame_color_map; |
103 | gint frame_colormap_size; |
104 | unsigned int frame_bit_pixel; |
105 | |
106 | GdkPixbufGifAnim *animation; |
107 | GdkPixbufFrame *frame; |
108 | int transparent_index; |
109 | int delay_time; |
110 | int disposal; |
111 | |
112 | /* stuff per frame. */ |
113 | int frame_len; |
114 | int frame_height; |
115 | int frame_interlace; |
116 | int x_offset; |
117 | int y_offset; |
118 | |
119 | /* Static read only */ |
120 | FILE *file; |
121 | |
122 | /* progressive read, only. */ |
123 | GdkPixbufModuleSizeFunc size_func; |
124 | GdkPixbufModulePreparedFunc prepared_func; |
125 | GdkPixbufModuleUpdatedFunc updated_func; |
126 | gpointer user_data; |
127 | GByteArray *buf; |
128 | |
129 | /* extension context */ |
130 | guchar extension_label; |
131 | guchar extension_flag; |
132 | gboolean in_loop_extension; |
133 | |
134 | /* get block context */ |
135 | guchar block_count; |
136 | guchar block_buf[280]; |
137 | |
138 | guchar lzw_set_code_size; |
139 | |
140 | /* error pointer */ |
141 | GError **error; |
142 | }; |
143 | |
144 | /* Returns TRUE if read is OK, |
145 | * FALSE if more memory is needed. */ |
146 | static gboolean |
147 | gif_read (GifContext *context, guchar *buffer, size_t len) |
148 | { |
149 | gboolean retval; |
150 | if (context->file) { |
151 | retval = (fread (ptr: buffer, size: 1, n: len, stream: context->file) == len); |
152 | |
153 | if (!retval && ferror (stream: context->file)) { |
154 | gint save_errno = errno; |
155 | g_set_error (err: context->error, |
156 | G_FILE_ERROR, |
157 | code: g_file_error_from_errno (err_no: save_errno), |
158 | _("Failure reading GIF: %s" ), |
159 | g_strerror (errnum: save_errno)); |
160 | } |
161 | |
162 | return retval; |
163 | } else { |
164 | if (context->buf->len >= len) { |
165 | memcpy (dest: buffer, src: context->buf->data, n: len); |
166 | g_byte_array_remove_range (array: context->buf, index_: 0, length: len); |
167 | return TRUE; |
168 | } |
169 | } |
170 | return FALSE; |
171 | } |
172 | |
173 | /* Changes the stage to be GIF_GET_COLORMAP */ |
174 | static void |
175 | gif_set_get_colormap (GifContext *context) |
176 | { |
177 | context->global_colormap_size = 0; |
178 | context->state = GIF_GET_COLORMAP; |
179 | } |
180 | |
181 | static void |
182 | gif_set_get_colormap2 (GifContext *context) |
183 | { |
184 | context->state = GIF_GET_COLORMAP2; |
185 | } |
186 | |
187 | static gint |
188 | gif_get_colormap (GifContext *context) |
189 | { |
190 | unsigned char rgb[3]; |
191 | |
192 | while (context->global_colormap_size < context->global_bit_pixel) { |
193 | if (!gif_read (context, buffer: rgb, len: sizeof (rgb))) { |
194 | return -1; |
195 | } |
196 | |
197 | context->animation->color_map[context->global_colormap_size * 3 + 0] = rgb[0]; |
198 | context->animation->color_map[context->global_colormap_size * 3 + 1] = rgb[1]; |
199 | context->animation->color_map[context->global_colormap_size * 3 + 2] = rgb[2]; |
200 | |
201 | context->global_colormap_size ++; |
202 | } |
203 | |
204 | return 0; |
205 | } |
206 | |
207 | |
208 | static gint |
209 | gif_get_colormap2 (GifContext *context) |
210 | { |
211 | unsigned char rgb[3]; |
212 | |
213 | while (context->frame_colormap_size < context->frame_bit_pixel) { |
214 | if (!gif_read (context, buffer: rgb, len: sizeof (rgb))) { |
215 | return -1; |
216 | } |
217 | |
218 | context->frame_color_map[0][context->frame_colormap_size] = rgb[0]; |
219 | context->frame_color_map[1][context->frame_colormap_size] = rgb[1]; |
220 | context->frame_color_map[2][context->frame_colormap_size] = rgb[2]; |
221 | |
222 | context->frame_colormap_size ++; |
223 | } |
224 | |
225 | return 0; |
226 | } |
227 | |
228 | /* |
229 | * in order for this function to work, we need to perform some black magic. |
230 | * We want to return -1 to let the calling function know, as before, that it needs |
231 | * more bytes. If we return 0, we were able to successfully read all block->count bytes. |
232 | * Problem is, we don't want to reread block_count every time, so we check to see if |
233 | * context->block_count is 0 before we read in the function. |
234 | * |
235 | * As a result, context->block_count MUST be 0 the first time the get_data_block is called |
236 | * within a context, and cannot be 0 the second time it's called. |
237 | */ |
238 | |
239 | static int |
240 | get_data_block (GifContext *context, |
241 | unsigned char *buf, |
242 | gint *empty_block) |
243 | { |
244 | |
245 | if (context->block_count == 0) { |
246 | if (!gif_read (context, buffer: &context->block_count, len: 1)) { |
247 | return -1; |
248 | } |
249 | } |
250 | |
251 | if (context->block_count == 0) |
252 | if (empty_block) { |
253 | *empty_block = TRUE; |
254 | return 0; |
255 | } |
256 | |
257 | if (!gif_read (context, buffer: buf, len: context->block_count)) { |
258 | return -1; |
259 | } |
260 | |
261 | return 0; |
262 | } |
263 | |
264 | static void |
265 | gif_set_get_extension (GifContext *context) |
266 | { |
267 | context->state = GIF_GET_EXTENSION; |
268 | context->extension_flag = TRUE; |
269 | context->extension_label = 0; |
270 | context->block_count = 0; |
271 | } |
272 | |
273 | static int |
274 | gif_get_extension (GifContext *context) |
275 | { |
276 | gint retval; |
277 | gint empty_block = FALSE; |
278 | |
279 | if (context->extension_flag) { |
280 | if (context->extension_label == 0) { |
281 | /* I guess bad things can happen if we have an extension of 0 )-: */ |
282 | /* I should look into this sometime */ |
283 | if (!gif_read (context, buffer: & context->extension_label , len: 1)) { |
284 | return -1; |
285 | } |
286 | } |
287 | |
288 | switch (context->extension_label) { |
289 | case 0xf9: /* Graphic Control Extension */ |
290 | retval = get_data_block (context, buf: (unsigned char *) context->block_buf, NULL); |
291 | if (retval != 0) |
292 | return retval; |
293 | |
294 | if (context->frame == NULL) { |
295 | /* I only want to set the transparency if I haven't |
296 | * created the frame yet. |
297 | */ |
298 | context->disposal = (context->block_buf[0] >> 2) & 0x7; |
299 | context->delay_time = LM_to_uint (context->block_buf[1], context->block_buf[2]); |
300 | |
301 | if ((context->block_buf[0] & 0x1) != 0) { |
302 | context->transparent_index = context->block_buf[3]; |
303 | } else { |
304 | context->transparent_index = -1; |
305 | } |
306 | } |
307 | |
308 | /* Now we've successfully loaded this one, we continue on our way */ |
309 | context->block_count = 0; |
310 | context->extension_flag = FALSE; |
311 | break; |
312 | case 0xff: /* application extension */ |
313 | if (!context->in_loop_extension) { |
314 | retval = get_data_block (context, buf: (unsigned char *) context->block_buf, NULL); |
315 | if (retval != 0) |
316 | return retval; |
317 | if (!strncmp (s1: (gchar *)context->block_buf, s2: "NETSCAPE2.0" , n: 11) || |
318 | !strncmp (s1: (gchar *)context->block_buf, s2: "ANIMEXTS1.0" , n: 11)) { |
319 | context->in_loop_extension = TRUE; |
320 | } |
321 | context->block_count = 0; |
322 | } |
323 | if (context->in_loop_extension) { |
324 | do { |
325 | retval = get_data_block (context, buf: (unsigned char *) context->block_buf, empty_block: &empty_block); |
326 | if (retval != 0) |
327 | return retval; |
328 | if (context->block_buf[0] == 0x01) { |
329 | context->animation->loop = context->block_buf[1] + (context->block_buf[2] << 8); |
330 | if (context->animation->loop != 0) |
331 | context->animation->loop++; |
332 | } |
333 | context->block_count = 0; |
334 | } |
335 | while (!empty_block); |
336 | context->in_loop_extension = FALSE; |
337 | context->extension_flag = FALSE; |
338 | return 0; |
339 | } |
340 | break; |
341 | default: |
342 | /* Unhandled extension */ |
343 | break; |
344 | } |
345 | } |
346 | /* read all blocks, until I get an empty block, in case there was an extension I didn't know about. */ |
347 | do { |
348 | retval = get_data_block (context, buf: (unsigned char *) context->block_buf, empty_block: &empty_block); |
349 | if (retval != 0) |
350 | return retval; |
351 | context->block_count = 0; |
352 | } while (!empty_block); |
353 | |
354 | return 0; |
355 | } |
356 | |
357 | static void |
358 | gif_set_get_lzw (GifContext *context) |
359 | { |
360 | context->state = GIF_GET_LZW; |
361 | } |
362 | |
363 | static int |
364 | gif_get_lzw (GifContext *context) |
365 | { |
366 | if (context->frame == NULL) { |
367 | int rowstride; |
368 | guint64 len; |
369 | |
370 | rowstride = gdk_pixbuf_calculate_rowstride (colorspace: GDK_COLORSPACE_RGB, |
371 | TRUE, |
372 | bits_per_sample: 8, |
373 | width: context->frame_len, |
374 | height: context->frame_height); |
375 | if (rowstride < 0 || |
376 | !g_uint64_checked_mul (&len, rowstride, context->frame_height) || |
377 | len >= G_MAXINT) { |
378 | g_set_error_literal (err: context->error, |
379 | GDK_PIXBUF_ERROR, |
380 | code: GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY, |
381 | _("Not enough memory to load GIF file" )); |
382 | return -2; |
383 | } |
384 | |
385 | context->frame = g_new0 (GdkPixbufFrame, 1); |
386 | |
387 | context->frame->lzw_data = g_byte_array_new (); |
388 | context->frame->lzw_code_size = context->lzw_set_code_size; |
389 | |
390 | context->frame->width = context->frame_len; |
391 | context->frame->height = context->frame_height; |
392 | context->frame->x_offset = context->x_offset; |
393 | context->frame->y_offset = context->y_offset; |
394 | context->frame->interlace = context->frame_interlace; |
395 | |
396 | if (context->frame_colormap_size > 0) { |
397 | int i; |
398 | |
399 | context->frame->color_map = g_malloc (n_bytes: 256 * 3); |
400 | context->frame->color_map_allocated = TRUE; |
401 | for (i = 0; i < 256; i++) { |
402 | context->frame->color_map[i * 3 + 0] = context->frame_color_map[0][i]; |
403 | context->frame->color_map[i * 3 + 1] = context->frame_color_map[1][i]; |
404 | context->frame->color_map[i * 3 + 2] = context->frame_color_map[2][i]; |
405 | } |
406 | } |
407 | else { |
408 | context->frame->color_map = context->animation->color_map; |
409 | } |
410 | |
411 | context->frame->transparent_index = context->transparent_index; |
412 | |
413 | /* GIF delay is in hundredths, we want thousandths */ |
414 | context->frame->delay_time = context->delay_time * 10; |
415 | |
416 | /* GIFs with delay time 0 are mostly broken, but they |
417 | * just want a default, "not that fast" delay. |
418 | */ |
419 | if (context->frame->delay_time == 0) |
420 | context->frame->delay_time = 100; |
421 | |
422 | /* No GIFs gets to play faster than 50 fps. They just |
423 | * lock up poor gtk. |
424 | */ |
425 | if (context->frame->delay_time < 20) |
426 | context->frame->delay_time = 20; /* 20 = "fast" */ |
427 | |
428 | context->frame->elapsed = context->animation->total_time; |
429 | context->animation->total_time += context->frame->delay_time; |
430 | |
431 | switch (context->disposal) { |
432 | case 0: |
433 | case 1: |
434 | context->frame->action = GDK_PIXBUF_FRAME_RETAIN; |
435 | break; |
436 | case 2: |
437 | context->frame->action = GDK_PIXBUF_FRAME_DISPOSE; |
438 | break; |
439 | case 3: |
440 | context->frame->action = GDK_PIXBUF_FRAME_REVERT; |
441 | break; |
442 | default: |
443 | context->frame->action = GDK_PIXBUF_FRAME_RETAIN; |
444 | break; |
445 | } |
446 | |
447 | context->animation->frames = g_list_append (list: context->animation->frames, data: context->frame); |
448 | |
449 | /* Notify when have first frame */ |
450 | if (context->animation->frames->next == NULL) { |
451 | GdkPixbuf *pixbuf = gdk_pixbuf_animation_get_static_image (GDK_PIXBUF_ANIMATION (context->animation)); |
452 | if (pixbuf != NULL) |
453 | (* context->prepared_func) (pixbuf, |
454 | GDK_PIXBUF_ANIMATION (context->animation), |
455 | context->user_data); |
456 | } |
457 | } |
458 | |
459 | /* read all blocks */ |
460 | while (TRUE) { |
461 | gint retval, empty_block = FALSE; |
462 | |
463 | retval = get_data_block (context, buf: (unsigned char *) context->block_buf, empty_block: &empty_block); |
464 | |
465 | /* Notify frame update */ |
466 | if ((retval != 0 || empty_block) && context->animation->frames->next == NULL) { |
467 | GdkPixbuf *pixbuf = gdk_pixbuf_animation_get_static_image (GDK_PIXBUF_ANIMATION (context->animation)); |
468 | if (pixbuf) |
469 | (* context->updated_func) (pixbuf, |
470 | 0, 0, context->frame->width, context->frame->height, |
471 | context->user_data); |
472 | } |
473 | |
474 | if (retval != 0) |
475 | return retval; |
476 | |
477 | if (empty_block) { |
478 | context->frame = NULL; |
479 | context->state = GIF_GET_NEXT_STEP; |
480 | return 0; |
481 | } |
482 | |
483 | g_byte_array_append (array: context->frame->lzw_data, data: context->block_buf, len: context->block_count); |
484 | if (context->animation->last_frame == context->frame) |
485 | context->animation->last_frame = NULL; |
486 | context->block_count = 0; |
487 | } |
488 | } |
489 | |
490 | static void |
491 | gif_set_prepare_lzw (GifContext *context) |
492 | { |
493 | context->state = GIF_PREPARE_LZW; |
494 | } |
495 | static int |
496 | gif_prepare_lzw (GifContext *context) |
497 | { |
498 | if (!gif_read (context, buffer: &(context->lzw_set_code_size), len: 1)) { |
499 | /*g_message (_("GIF: EOF / read error on image data\n"));*/ |
500 | return -1; |
501 | } |
502 | |
503 | if (context->lzw_set_code_size >= 12) { |
504 | g_set_error_literal (err: context->error, |
505 | GDK_PIXBUF_ERROR, |
506 | code: GDK_PIXBUF_ERROR_CORRUPT_IMAGE, |
507 | _("GIF image is corrupt (incorrect LZW compression)" )); |
508 | return -2; |
509 | } |
510 | |
511 | gif_set_get_lzw (context); |
512 | |
513 | return 0; |
514 | } |
515 | |
516 | /* |
517 | * Read the GIF signature and screen descriptor. |
518 | * |
519 | * | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | |
520 | * |-----------|-----------|-----------------------------------| |
521 | * | magic | version | screen descriptor | |
522 | * | G | I | F | 8 | 9 | a | width | height | colors | ignored | |
523 | */ |
524 | static gint |
525 | gif_init (GifContext *context) |
526 | { |
527 | unsigned char buf[13]; |
528 | char version[4]; |
529 | gint width, height; |
530 | |
531 | if (!gif_read (context, buffer: buf, len: 13)) { |
532 | /* Unable to read magic number, |
533 | * gif_read() should have set error |
534 | */ |
535 | return -1; |
536 | } |
537 | |
538 | if (strncmp (s1: (char *) buf, s2: "GIF" , n: 3) != 0) { |
539 | /* Not a GIF file */ |
540 | g_set_error_literal (err: context->error, |
541 | GDK_PIXBUF_ERROR, |
542 | code: GDK_PIXBUF_ERROR_CORRUPT_IMAGE, |
543 | _("File does not appear to be a GIF file" )); |
544 | return -2; |
545 | } |
546 | |
547 | strncpy (dest: version, src: (char *) buf + 3, n: 3); |
548 | version[3] = '\0'; |
549 | |
550 | if ((strcmp (s1: version, s2: "87a" ) != 0) && (strcmp (s1: version, s2: "89a" ) != 0)) { |
551 | gchar *escaped_version; |
552 | |
553 | /* bad version number, not '87a' or '89a' */ |
554 | escaped_version = g_strescape (source: version, NULL); |
555 | g_set_error (err: context->error, |
556 | GDK_PIXBUF_ERROR, |
557 | code: GDK_PIXBUF_ERROR_CORRUPT_IMAGE, |
558 | _("Version %s of the GIF file format is not supported" ), |
559 | escaped_version); |
560 | g_free (mem: escaped_version); |
561 | return -2; |
562 | } |
563 | |
564 | context->width = LM_to_uint (buf[6], buf[7]); |
565 | context->height = LM_to_uint (buf[8], buf[9]); |
566 | /* The 4th byte is |
567 | * high bit: whether to use the background index |
568 | * next 3: color resolution |
569 | * next: whether colormap is sorted by priority of allocation |
570 | * last 3: size of colormap |
571 | */ |
572 | context->global_bit_pixel = 2 << (buf[10] & 0x07); |
573 | context->has_global_cmap = (buf[10] & 0x80) != 0; |
574 | |
575 | context->animation->width = context->width; |
576 | context->animation->height = context->height; |
577 | |
578 | width = context->width; |
579 | height = context->height; |
580 | |
581 | (*context->size_func) (&width, &height, context->user_data); |
582 | |
583 | if (width == 0 || height == 0) { |
584 | g_set_error_literal (err: context->error, |
585 | GDK_PIXBUF_ERROR, |
586 | code: GDK_PIXBUF_ERROR_CORRUPT_IMAGE, |
587 | _("Resulting GIF image has zero size" )); |
588 | return -2; |
589 | } |
590 | |
591 | if (context->has_global_cmap) { |
592 | gif_set_get_colormap (context); |
593 | } else { |
594 | context->state = GIF_GET_NEXT_STEP; |
595 | } |
596 | |
597 | #ifdef DUMP_IMAGE_DETAILS |
598 | g_print (">Image width: %d height: %d global_cmap: %d\n" , |
599 | context->width, context->height, context->has_global_cmap); |
600 | #endif |
601 | |
602 | return 0; |
603 | } |
604 | |
605 | static void |
606 | gif_set_get_frame_info (GifContext *context) |
607 | { |
608 | context->state = GIF_GET_FRAME_INFO; |
609 | } |
610 | |
611 | static gint |
612 | gif_get_frame_info (GifContext *context) |
613 | { |
614 | unsigned char buf[9]; |
615 | |
616 | if (!gif_read (context, buffer: buf, len: 9)) { |
617 | return -1; |
618 | } |
619 | |
620 | /* Okay, we got all the info we need. Lets record it */ |
621 | context->frame_len = LM_to_uint (buf[4], buf[5]); |
622 | context->frame_height = LM_to_uint (buf[6], buf[7]); |
623 | context->x_offset = LM_to_uint (buf[0], buf[1]); |
624 | context->y_offset = LM_to_uint (buf[2], buf[3]); |
625 | |
626 | context->frame_interlace = BitSet (buf[8], INTERLACE); |
627 | |
628 | #ifdef DUMP_IMAGE_DETAILS |
629 | g_print (">width: %d height: %d xoffset: %d yoffset: %d disposal: %d delay: %d transparent: %d interlace: %d\n" , |
630 | context->frame_len, context->frame_height, context->x_offset, context->y_offset, |
631 | context->disposal, context->delay_time, context->transparent_index, context->frame_interlace); |
632 | #endif |
633 | |
634 | context->frame_colormap_size = 0; |
635 | if (BitSet (buf[8], LOCALCOLORMAP)) { |
636 | |
637 | #ifdef DUMP_IMAGE_DETAILS |
638 | g_print (">has local colormap\n" ); |
639 | #endif |
640 | |
641 | /* Does this frame have it's own colormap. */ |
642 | /* really only relevant when looking at the first frame |
643 | * of an animated gif. */ |
644 | /* if it does, we need to re-read in the colormap, |
645 | * the gray_scale, and the bit_pixel */ |
646 | context->frame_bit_pixel = 1 << ((buf[8] & 0x07) + 1); |
647 | gif_set_get_colormap2 (context); |
648 | return 0; |
649 | } |
650 | |
651 | if (!context->has_global_cmap) { |
652 | context->state = GIF_DONE; |
653 | |
654 | g_set_error_literal (err: context->error, |
655 | GDK_PIXBUF_ERROR, |
656 | code: GDK_PIXBUF_ERROR_CORRUPT_IMAGE, |
657 | _("GIF image has no global colormap, and a frame inside it has no local colormap." )); |
658 | |
659 | return -2; |
660 | } |
661 | |
662 | gif_set_prepare_lzw (context); |
663 | return 0; |
664 | |
665 | } |
666 | |
667 | static gint |
668 | gif_get_next_step (GifContext *context) |
669 | { |
670 | unsigned char c; |
671 | while (TRUE) { |
672 | if (!gif_read (context, buffer: &c, len: 1)) { |
673 | return -1; |
674 | } |
675 | if (c == ';') { |
676 | /* GIF terminator */ |
677 | /* hmm. Not 100% sure what to do about this. Should |
678 | * i try to return a blank image instead? */ |
679 | context->state = GIF_DONE; |
680 | return 0; |
681 | } |
682 | |
683 | if (c == '!') { |
684 | /* Check the extension */ |
685 | gif_set_get_extension (context); |
686 | return 0; |
687 | } |
688 | |
689 | /* look for frame */ |
690 | if (c != ',') { |
691 | /* Not a valid start character */ |
692 | continue; |
693 | } |
694 | /* load the frame */ |
695 | gif_set_get_frame_info (context); |
696 | return 0; |
697 | } |
698 | } |
699 | |
700 | |
701 | #define LOG(x) /* g_print ("%s: %s\n", G_STRLOC, x); */ |
702 | |
703 | static gint |
704 | gif_main_loop (GifContext *context) |
705 | { |
706 | gint retval = 0; |
707 | |
708 | do { |
709 | switch (context->state) { |
710 | case GIF_START: |
711 | LOG("start\n" ); |
712 | retval = gif_init (context); |
713 | break; |
714 | |
715 | case GIF_GET_COLORMAP: |
716 | LOG("get_colormap\n" ); |
717 | retval = gif_get_colormap (context); |
718 | if (retval == 0) |
719 | context->state = GIF_GET_NEXT_STEP; |
720 | break; |
721 | |
722 | case GIF_GET_NEXT_STEP: |
723 | LOG("next_step\n" ); |
724 | retval = gif_get_next_step (context); |
725 | break; |
726 | |
727 | case GIF_GET_FRAME_INFO: |
728 | LOG("frame_info\n" ); |
729 | retval = gif_get_frame_info (context); |
730 | break; |
731 | |
732 | case GIF_GET_EXTENSION: |
733 | LOG("get_extension\n" ); |
734 | retval = gif_get_extension (context); |
735 | if (retval == 0) |
736 | context->state = GIF_GET_NEXT_STEP; |
737 | break; |
738 | |
739 | case GIF_GET_COLORMAP2: |
740 | LOG("get_colormap2\n" ); |
741 | retval = gif_get_colormap2 (context); |
742 | if (retval == 0) |
743 | gif_set_prepare_lzw (context); |
744 | break; |
745 | |
746 | case GIF_PREPARE_LZW: |
747 | LOG("prepare_lzw\n" ); |
748 | retval = gif_prepare_lzw (context); |
749 | break; |
750 | |
751 | case GIF_GET_LZW: |
752 | LOG("get_lzw\n" ); |
753 | retval = gif_get_lzw (context); |
754 | break; |
755 | |
756 | case GIF_DONE: |
757 | LOG("done\n" ); |
758 | /* fall through */ |
759 | default: |
760 | retval = 0; |
761 | goto done; |
762 | }; |
763 | } while ((retval == 0) || (retval == -3)); |
764 | done: |
765 | return retval; |
766 | } |
767 | |
768 | static GifContext * |
769 | new_context (GdkPixbufModuleSizeFunc size_func, |
770 | GdkPixbufModulePreparedFunc prepared_func, |
771 | GdkPixbufModuleUpdatedFunc updated_func, |
772 | gpointer user_data) |
773 | { |
774 | GifContext *context; |
775 | |
776 | g_assert (size_func != NULL); |
777 | g_assert (prepared_func != NULL); |
778 | g_assert (updated_func != NULL); |
779 | |
780 | context = g_try_malloc (n_bytes: sizeof (GifContext)); |
781 | if (context == NULL) |
782 | return NULL; |
783 | |
784 | memset (s: context, c: 0, n: sizeof (GifContext)); |
785 | |
786 | context->animation = g_object_new (GDK_TYPE_PIXBUF_GIF_ANIM, NULL); |
787 | context->frame = NULL; |
788 | context->transparent_index = -1; |
789 | context->file = NULL; |
790 | context->state = GIF_START; |
791 | context->size_func = size_func; |
792 | context->prepared_func = prepared_func; |
793 | context->updated_func = updated_func; |
794 | context->user_data = user_data; |
795 | context->buf = g_byte_array_new (); |
796 | context->animation->loop = 1; |
797 | context->in_loop_extension = FALSE; |
798 | |
799 | return context; |
800 | } |
801 | |
802 | static void |
803 | noop_size_notify (gint *width, |
804 | gint *height, |
805 | gpointer data) |
806 | { |
807 | } |
808 | |
809 | static void |
810 | noop_prepared_notify (GdkPixbuf *pixbuf, |
811 | GdkPixbufAnimation *anim, |
812 | gpointer user_data) |
813 | { |
814 | } |
815 | |
816 | static void |
817 | noop_updated_notify (GdkPixbuf *pixbuf, |
818 | int x, |
819 | int y, |
820 | int width, |
821 | int height, |
822 | gpointer user_data) |
823 | { |
824 | } |
825 | |
826 | static GifContext * |
827 | new_context_without_callbacks (void) |
828 | { |
829 | return new_context (size_func: noop_size_notify, prepared_func: noop_prepared_notify, updated_func: noop_updated_notify, NULL); |
830 | } |
831 | |
832 | /* Shared library entry point */ |
833 | static GdkPixbuf * |
834 | gdk_pixbuf__gif_image_load (FILE *file, GError **error) |
835 | { |
836 | GifContext *context; |
837 | GdkPixbuf *pixbuf; |
838 | gint retval; |
839 | |
840 | g_return_val_if_fail (file != NULL, NULL); |
841 | |
842 | context = new_context_without_callbacks (); |
843 | |
844 | if (context == NULL) { |
845 | g_set_error_literal (err: error, |
846 | GDK_PIXBUF_ERROR, |
847 | code: GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY, |
848 | _("Not enough memory to load GIF file" )); |
849 | return NULL; |
850 | } |
851 | |
852 | context->file = file; |
853 | context->error = error; |
854 | |
855 | retval = gif_main_loop (context); |
856 | if (retval == -1 || context->animation->frames == NULL) { |
857 | if (context->error && *(context->error) == NULL) |
858 | g_set_error_literal (err: context->error, |
859 | GDK_PIXBUF_ERROR, |
860 | code: GDK_PIXBUF_ERROR_CORRUPT_IMAGE, |
861 | _("GIF file was missing some data (perhaps it was truncated somehow?)" )); |
862 | } |
863 | else if (retval == -2) { |
864 | pixbuf = NULL; |
865 | goto out; |
866 | } |
867 | |
868 | pixbuf = gdk_pixbuf_animation_get_static_image (GDK_PIXBUF_ANIMATION (context->animation)); |
869 | |
870 | if (pixbuf) |
871 | g_object_ref (pixbuf); |
872 | |
873 | out: |
874 | g_object_unref (object: context->animation); |
875 | |
876 | g_byte_array_unref (array: context->buf); |
877 | g_free (mem: context); |
878 | |
879 | return pixbuf; |
880 | } |
881 | |
882 | static gpointer |
883 | gdk_pixbuf__gif_image_begin_load (GdkPixbufModuleSizeFunc size_func, |
884 | GdkPixbufModulePreparedFunc prepared_func, |
885 | GdkPixbufModuleUpdatedFunc updated_func, |
886 | gpointer user_data, |
887 | GError **error) |
888 | { |
889 | GifContext *context; |
890 | |
891 | g_assert (size_func != NULL); |
892 | g_assert (prepared_func != NULL); |
893 | g_assert (updated_func != NULL); |
894 | |
895 | context = new_context (size_func, prepared_func, updated_func, user_data); |
896 | |
897 | if (context == NULL) { |
898 | g_set_error_literal (err: error, |
899 | GDK_PIXBUF_ERROR, |
900 | code: GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY, |
901 | _("Not enough memory to load GIF file" )); |
902 | return NULL; |
903 | } |
904 | |
905 | context->error = error; |
906 | |
907 | return (gpointer) context; |
908 | } |
909 | |
910 | static gboolean |
911 | gdk_pixbuf__gif_image_stop_load (gpointer data, GError **error) |
912 | { |
913 | GifContext *context = (GifContext *) data; |
914 | gboolean retval = TRUE; |
915 | |
916 | if (context->animation->frames == NULL) { |
917 | g_set_error_literal (err: error, |
918 | GDK_PIXBUF_ERROR, |
919 | code: GDK_PIXBUF_ERROR_CORRUPT_IMAGE, |
920 | _("GIF image was truncated or incomplete." )); |
921 | |
922 | retval = FALSE; |
923 | } else if (context->state != GIF_DONE) { |
924 | g_set_error_literal (err: error, |
925 | GDK_PIXBUF_ERROR, |
926 | code: GDK_PIXBUF_ERROR_INCOMPLETE_ANIMATION, |
927 | _("Not all frames of the GIF image were loaded." )); |
928 | |
929 | retval = FALSE; |
930 | } |
931 | |
932 | g_object_unref (object: context->animation); |
933 | |
934 | g_byte_array_unref (array: context->buf); |
935 | g_free (mem: context); |
936 | |
937 | return retval; |
938 | } |
939 | |
940 | static gboolean |
941 | gdk_pixbuf__gif_image_load_increment (gpointer data, |
942 | const guchar *buf, guint size, |
943 | GError **error) |
944 | { |
945 | gint retval; |
946 | GifContext *context = (GifContext *) data; |
947 | |
948 | context->error = error; |
949 | |
950 | g_byte_array_append (array: context->buf, data: buf, len: size); |
951 | |
952 | retval = gif_main_loop (context); |
953 | if (retval == -2) |
954 | return FALSE; |
955 | |
956 | return TRUE; |
957 | } |
958 | |
959 | static GdkPixbufAnimation * |
960 | gdk_pixbuf__gif_image_load_animation (FILE *file, |
961 | GError **error) |
962 | { |
963 | GifContext *context; |
964 | GdkPixbufAnimation *animation; |
965 | |
966 | g_return_val_if_fail (file != NULL, NULL); |
967 | |
968 | context = new_context_without_callbacks (); |
969 | |
970 | if (context == NULL) { |
971 | g_set_error_literal (err: error, |
972 | GDK_PIXBUF_ERROR, |
973 | code: GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY, |
974 | _("Not enough memory to load GIF file" )); |
975 | return NULL; |
976 | } |
977 | |
978 | context->error = error; |
979 | context->file = file; |
980 | |
981 | if (gif_main_loop (context) == -1 || context->animation->frames == NULL) { |
982 | if (context->error && *(context->error) == NULL) |
983 | g_set_error_literal (err: context->error, |
984 | GDK_PIXBUF_ERROR, |
985 | code: GDK_PIXBUF_ERROR_CORRUPT_IMAGE, |
986 | _("GIF file was missing some data (perhaps it was truncated somehow?)" )); |
987 | |
988 | g_object_unref (object: context->animation); |
989 | context->animation = NULL; |
990 | } |
991 | |
992 | if (context->animation) |
993 | animation = GDK_PIXBUF_ANIMATION (context->animation); |
994 | else |
995 | animation = NULL; |
996 | |
997 | if (context->error && *(context->error)) |
998 | g_print (format: "%s\n" , (*(context->error))->message); |
999 | |
1000 | g_byte_array_unref (array: context->buf); |
1001 | g_free (mem: context); |
1002 | return animation; |
1003 | } |
1004 | |
1005 | #ifndef INCLUDE_gif |
1006 | #define MODULE_ENTRY(function) G_MODULE_EXPORT void function |
1007 | #else |
1008 | #define MODULE_ENTRY(function) void _gdk_pixbuf__gif_ ## function |
1009 | #endif |
1010 | |
1011 | MODULE_ENTRY (fill_vtable) (GdkPixbufModule *module) |
1012 | { |
1013 | module->load = gdk_pixbuf__gif_image_load; |
1014 | module->begin_load = gdk_pixbuf__gif_image_begin_load; |
1015 | module->stop_load = gdk_pixbuf__gif_image_stop_load; |
1016 | module->load_increment = gdk_pixbuf__gif_image_load_increment; |
1017 | module->load_animation = gdk_pixbuf__gif_image_load_animation; |
1018 | } |
1019 | |
1020 | MODULE_ENTRY (fill_info) (GdkPixbufFormat *info) |
1021 | { |
1022 | static const GdkPixbufModulePattern signature[] = { |
1023 | { "GIF8" , NULL, 100 }, |
1024 | { NULL, NULL, 0 } |
1025 | }; |
1026 | static const gchar *mime_types[] = { |
1027 | "image/gif" , |
1028 | NULL |
1029 | }; |
1030 | static const gchar *extensions[] = { |
1031 | "gif" , |
1032 | NULL |
1033 | }; |
1034 | |
1035 | info->name = "gif" ; |
1036 | info->signature = (GdkPixbufModulePattern *) signature; |
1037 | info->description = NC_("image format" , "GIF" ); |
1038 | info->mime_types = (gchar **) mime_types; |
1039 | info->extensions = (gchar **) extensions; |
1040 | info->flags = GDK_PIXBUF_FORMAT_THREADSAFE; |
1041 | info->license = "LGPL" ; |
1042 | } |
1043 | |