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