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
75typedef unsigned char CMap[3][MAXCOLORMAPSIZE];
76
77/* Possible states we can be in. */
78enum {
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
90typedef struct _GifContext GifContext;
91struct _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. */
146static gboolean
147gif_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 */
174static void
175gif_set_get_colormap (GifContext *context)
176{
177 context->global_colormap_size = 0;
178 context->state = GIF_GET_COLORMAP;
179}
180
181static void
182gif_set_get_colormap2 (GifContext *context)
183{
184 context->state = GIF_GET_COLORMAP2;
185}
186
187static gint
188gif_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
208static gint
209gif_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
239static int
240get_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
264static void
265gif_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
273static int
274gif_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
357static void
358gif_set_get_lzw (GifContext *context)
359{
360 context->state = GIF_GET_LZW;
361}
362
363static int
364gif_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
490static void
491gif_set_prepare_lzw (GifContext *context)
492{
493 context->state = GIF_PREPARE_LZW;
494}
495static int
496gif_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 */
524static gint
525gif_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
605static void
606gif_set_get_frame_info (GifContext *context)
607{
608 context->state = GIF_GET_FRAME_INFO;
609}
610
611static gint
612gif_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
667static gint
668gif_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
703static gint
704gif_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
768static GifContext *
769new_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
802static void
803noop_size_notify (gint *width,
804 gint *height,
805 gpointer data)
806{
807}
808
809static void
810noop_prepared_notify (GdkPixbuf *pixbuf,
811 GdkPixbufAnimation *anim,
812 gpointer user_data)
813{
814}
815
816static void
817noop_updated_notify (GdkPixbuf *pixbuf,
818 int x,
819 int y,
820 int width,
821 int height,
822 gpointer user_data)
823{
824}
825
826static GifContext *
827new_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 */
833static GdkPixbuf *
834gdk_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
873out:
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
882static gpointer
883gdk_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
910static gboolean
911gdk_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
940static gboolean
941gdk_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
959static GdkPixbufAnimation *
960gdk_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
1011MODULE_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
1020MODULE_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

source code of gtk/subprojects/gdk-pixbuf/gdk-pixbuf/io-gif.c