1/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
2/* GdkPixbuf library - animated gif support
3 *
4 * Copyright (C) 1999 The Free Software Foundation
5 *
6 * Authors: Jonathan Blandford <jrb@redhat.com>
7 * Havoc Pennington <hp@redhat.com>
8 *
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public
11 * License as published by the Free Software Foundation; either
12 * version 2 of the License, or (at your option) any later version.
13 *
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
18 *
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with this library; if not, see <http://www.gnu.org/licenses/>.
21 */
22
23#include "config.h"
24#include <string.h>
25#include <errno.h>
26#include "gdk-pixbuf-transform.h"
27#include "io-gif-animation.h"
28#include "lzw.h"
29
30static void gdk_pixbuf_gif_anim_finalize (GObject *object);
31
32static gboolean gdk_pixbuf_gif_anim_is_static_image (GdkPixbufAnimation *animation);
33static GdkPixbuf* gdk_pixbuf_gif_anim_get_static_image (GdkPixbufAnimation *animation);
34
35static void gdk_pixbuf_gif_anim_get_size (GdkPixbufAnimation *anim,
36 int *width,
37 int *height);
38G_GNUC_BEGIN_IGNORE_DEPRECATIONS
39static GdkPixbufAnimationIter* gdk_pixbuf_gif_anim_get_iter (GdkPixbufAnimation *anim,
40 const GTimeVal *start_time);
41G_GNUC_END_IGNORE_DEPRECATIONS
42static GdkPixbuf* gdk_pixbuf_gif_anim_iter_get_pixbuf (GdkPixbufAnimationIter *iter);
43
44
45
46G_DEFINE_TYPE (GdkPixbufGifAnim, gdk_pixbuf_gif_anim, GDK_TYPE_PIXBUF_ANIMATION);
47
48static void
49gdk_pixbuf_gif_anim_init (GdkPixbufGifAnim *anim)
50{
51}
52
53static void
54gdk_pixbuf_gif_anim_class_init (GdkPixbufGifAnimClass *klass)
55{
56 GObjectClass *object_class = G_OBJECT_CLASS (klass);
57 GdkPixbufAnimationClass *anim_class = GDK_PIXBUF_ANIMATION_CLASS (klass);
58
59 object_class->finalize = gdk_pixbuf_gif_anim_finalize;
60
61 anim_class->is_static_image = gdk_pixbuf_gif_anim_is_static_image;
62 anim_class->get_static_image = gdk_pixbuf_gif_anim_get_static_image;
63 anim_class->get_size = gdk_pixbuf_gif_anim_get_size;
64 anim_class->get_iter = gdk_pixbuf_gif_anim_get_iter;
65}
66
67static void
68gdk_pixbuf_gif_anim_finalize (GObject *object)
69{
70 GdkPixbufGifAnim *gif_anim = GDK_PIXBUF_GIF_ANIM (object);
71
72 GList *l;
73 GdkPixbufFrame *frame;
74
75 for (l = gif_anim->frames; l; l = l->next) {
76 frame = l->data;
77 g_byte_array_unref (array: frame->lzw_data);
78 if (frame->color_map_allocated)
79 g_free (mem: frame->color_map);
80 g_free (mem: frame);
81 }
82
83 g_list_free (list: gif_anim->frames);
84
85 g_clear_object (&gif_anim->last_frame_data);
86 g_clear_object (&gif_anim->last_frame_revert_data);
87
88 G_OBJECT_CLASS (gdk_pixbuf_gif_anim_parent_class)->finalize (object);
89}
90
91static gboolean
92gdk_pixbuf_gif_anim_is_static_image (GdkPixbufAnimation *animation)
93{
94 GdkPixbufGifAnim *gif_anim;
95
96 gif_anim = GDK_PIXBUF_GIF_ANIM (animation);
97
98 return (gif_anim->frames != NULL &&
99 gif_anim->frames->next == NULL);
100}
101
102G_GNUC_BEGIN_IGNORE_DEPRECATIONS
103static GdkPixbuf*
104gdk_pixbuf_gif_anim_get_static_image (GdkPixbufAnimation *animation)
105{
106 GdkPixbufGifAnim *gif_anim;
107 GdkPixbufAnimationIter *iter;
108 GdkPixbuf *pixbuf;
109 GTimeVal start_time = { 0, 0 };
110
111 gif_anim = GDK_PIXBUF_GIF_ANIM (animation);
112
113 if (gif_anim->frames == NULL)
114 return NULL;
115
116 iter = gdk_pixbuf_gif_anim_get_iter (anim: animation, start_time: &start_time);
117 pixbuf = gdk_pixbuf_gif_anim_iter_get_pixbuf (iter);
118 g_object_unref (object: iter);
119
120 return pixbuf;
121}
122G_GNUC_END_IGNORE_DEPRECATIONS
123
124static void
125gdk_pixbuf_gif_anim_get_size (GdkPixbufAnimation *anim,
126 int *width,
127 int *height)
128{
129 GdkPixbufGifAnim *gif_anim;
130
131 gif_anim = GDK_PIXBUF_GIF_ANIM (anim);
132
133 if (width)
134 *width = gif_anim->width;
135
136 if (height)
137 *height = gif_anim->height;
138}
139
140
141static void
142iter_clear (GdkPixbufGifAnimIter *iter)
143{
144 iter->current_frame = NULL;
145}
146
147static void
148iter_restart (GdkPixbufGifAnimIter *iter)
149{
150 iter_clear (iter);
151
152 iter->current_frame = iter->gif_anim->frames;
153}
154
155G_GNUC_BEGIN_IGNORE_DEPRECATIONS
156static GdkPixbufAnimationIter*
157gdk_pixbuf_gif_anim_get_iter (GdkPixbufAnimation *anim,
158 const GTimeVal *start_time)
159{
160 GdkPixbufGifAnimIter *iter;
161
162 iter = g_object_new (GDK_TYPE_PIXBUF_GIF_ANIM_ITER, NULL);
163
164 iter->gif_anim = GDK_PIXBUF_GIF_ANIM (anim);
165
166 g_object_ref (iter->gif_anim);
167
168 iter_restart (iter);
169
170 iter->start_time = *start_time;
171 iter->current_time = *start_time;
172 iter->first_loop_slowness = 0;
173
174 return GDK_PIXBUF_ANIMATION_ITER (iter);
175}
176G_GNUC_END_IGNORE_DEPRECATIONS
177
178
179
180static void gdk_pixbuf_gif_anim_iter_finalize (GObject *object);
181
182static int gdk_pixbuf_gif_anim_iter_get_delay_time (GdkPixbufAnimationIter *iter);
183static gboolean gdk_pixbuf_gif_anim_iter_on_currently_loading_frame (GdkPixbufAnimationIter *iter);
184G_GNUC_BEGIN_IGNORE_DEPRECATIONS
185static gboolean gdk_pixbuf_gif_anim_iter_advance (GdkPixbufAnimationIter *iter,
186 const GTimeVal *current_time);
187G_GNUC_END_IGNORE_DEPRECATIONS
188
189
190
191G_DEFINE_TYPE (GdkPixbufGifAnimIter, gdk_pixbuf_gif_anim_iter, GDK_TYPE_PIXBUF_ANIMATION_ITER);
192
193static void
194gdk_pixbuf_gif_anim_iter_init (GdkPixbufGifAnimIter *iter)
195{
196}
197
198static void
199gdk_pixbuf_gif_anim_iter_class_init (GdkPixbufGifAnimIterClass *klass)
200{
201 GObjectClass *object_class = G_OBJECT_CLASS (klass);
202 GdkPixbufAnimationIterClass *anim_iter_class =
203 GDK_PIXBUF_ANIMATION_ITER_CLASS (klass);
204
205 object_class->finalize = gdk_pixbuf_gif_anim_iter_finalize;
206
207 anim_iter_class->get_delay_time = gdk_pixbuf_gif_anim_iter_get_delay_time;
208 anim_iter_class->get_pixbuf = gdk_pixbuf_gif_anim_iter_get_pixbuf;
209 anim_iter_class->on_currently_loading_frame = gdk_pixbuf_gif_anim_iter_on_currently_loading_frame;
210 anim_iter_class->advance = gdk_pixbuf_gif_anim_iter_advance;
211}
212
213static void
214gdk_pixbuf_gif_anim_iter_finalize (GObject *object)
215{
216 GdkPixbufGifAnimIter *iter = GDK_PIXBUF_GIF_ANIM_ITER (object);
217
218 iter_clear (iter);
219
220 g_object_unref (object: iter->gif_anim);
221
222 G_OBJECT_CLASS (gdk_pixbuf_gif_anim_iter_parent_class)->finalize (object);
223}
224
225G_GNUC_BEGIN_IGNORE_DEPRECATIONS
226static gboolean
227gdk_pixbuf_gif_anim_iter_advance (GdkPixbufAnimationIter *anim_iter,
228 const GTimeVal *current_time)
229{
230 GdkPixbufGifAnimIter *iter;
231 gint elapsed;
232 gint loop;
233 GList *tmp;
234 GList *old;
235
236 iter = GDK_PIXBUF_GIF_ANIM_ITER (anim_iter);
237
238 iter->current_time = *current_time;
239
240 /* We use milliseconds for all times */
241 elapsed =
242 (((iter->current_time.tv_sec - iter->start_time.tv_sec) * G_USEC_PER_SEC +
243 iter->current_time.tv_usec - iter->start_time.tv_usec)) / 1000;
244
245 if (elapsed < 0) {
246 /* Try to compensate; probably the system clock
247 * was set backwards
248 */
249 iter->start_time = iter->current_time;
250 elapsed = 0;
251 }
252
253 g_assert (iter->gif_anim->total_time > 0);
254
255 /* See how many times we've already played the full animation,
256 * and subtract time for that.
257 */
258
259 /* If current_frame is NULL at this point, we have loaded the
260 * animation from a source which fell behind the speed of the
261 * display. We remember how much slower the first loop was due
262 * to this and correct the position calculation in order to not
263 * jump in the middle of the second loop.
264 */
265 if (iter->current_frame == NULL)
266 iter->first_loop_slowness = MAX(0, elapsed - iter->gif_anim->total_time);
267
268 loop = (elapsed - iter->first_loop_slowness) / iter->gif_anim->total_time;
269 elapsed = (elapsed - iter->first_loop_slowness) % iter->gif_anim->total_time;
270
271 iter->position = elapsed;
272
273 /* Now move to the proper frame */
274 if (iter->gif_anim->loop == 0 || loop < iter->gif_anim->loop)
275 tmp = iter->gif_anim->frames;
276 else
277 tmp = NULL;
278 while (tmp != NULL) {
279 GdkPixbufFrame *frame = tmp->data;
280
281 if (iter->position >= frame->elapsed &&
282 iter->position < (frame->elapsed + frame->delay_time))
283 break;
284
285 tmp = tmp->next;
286 }
287
288 old = iter->current_frame;
289
290 iter->current_frame = tmp;
291
292 return iter->current_frame != old;
293}
294G_GNUC_END_IGNORE_DEPRECATIONS
295
296int
297gdk_pixbuf_gif_anim_iter_get_delay_time (GdkPixbufAnimationIter *anim_iter)
298{
299 GdkPixbufFrame *frame;
300 GdkPixbufGifAnimIter *iter;
301
302 iter = GDK_PIXBUF_GIF_ANIM_ITER (anim_iter);
303
304 if (iter->current_frame) {
305 frame = iter->current_frame->data;
306
307#if 0
308 g_print ("frame start: %d pos: %d frame len: %d frame remaining: %d\n",
309 frame->elapsed,
310 iter->position,
311 frame->delay_time,
312 frame->delay_time - (iter->position - frame->elapsed));
313#endif
314
315 return frame->delay_time - (iter->position - frame->elapsed);
316 } else
317 return -1; /* show last frame forever */
318}
319
320static void
321composite_frame (GdkPixbufGifAnim *anim, GdkPixbufFrame *frame)
322{
323 LZWDecoder *lzw_decoder = NULL;
324 guint8 *index_buffer = NULL;
325 gsize n_indexes, i;
326 guint16 *interlace_rows = NULL;
327 guchar *pixels;
328
329 anim->last_frame = frame;
330
331 /* Store overwritten data if required */
332 g_clear_object (&anim->last_frame_revert_data);
333 if (frame->action == GDK_PIXBUF_FRAME_REVERT) {
334 anim->last_frame_revert_data = gdk_pixbuf_new (colorspace: GDK_COLORSPACE_RGB, TRUE, bits_per_sample: 8, width: frame->width, height: frame->height);
335 if (anim->last_frame_revert_data != NULL)
336 gdk_pixbuf_copy_area (src_pixbuf: anim->last_frame_data,
337 src_x: frame->x_offset, src_y: frame->y_offset, width: frame->width, height: frame->height,
338 dest_pixbuf: anim->last_frame_revert_data,
339 dest_x: 0, dest_y: 0);
340 }
341
342 lzw_decoder = lzw_decoder_new (code_size: frame->lzw_code_size + 1);
343 index_buffer = g_new (guint8, frame->width * frame->height);
344 if (index_buffer == NULL)
345 goto out;
346
347 interlace_rows = g_new (guint16, frame->height);
348 if (interlace_rows == NULL)
349 goto out;
350 if (frame->interlace) {
351 int row, n = 0;
352 for (row = 0; row < frame->height; row += 8)
353 interlace_rows[n++] = row;
354 for (row = 4; row < frame->height; row += 8)
355 interlace_rows[n++] = row;
356 for (row = 2; row < frame->height; row += 4)
357 interlace_rows[n++] = row;
358 for (row = 1; row < frame->height; row += 2)
359 interlace_rows[n++] = row;
360 }
361 else {
362 int row;
363 for (row = 0; row < frame->height; row++)
364 interlace_rows[row] = row;
365 }
366
367 n_indexes = lzw_decoder_feed (decoder: lzw_decoder, input: frame->lzw_data->data, input_length: frame->lzw_data->len, output: index_buffer, output_length: frame->width * frame->height);
368 pixels = gdk_pixbuf_get_pixels (pixbuf: anim->last_frame_data);
369 for (i = 0; i < n_indexes; i++) {
370 guint8 index = index_buffer[i];
371 guint x, y;
372 gsize offset;
373
374 if (index == frame->transparent_index)
375 continue;
376
377 x = i % frame->width + frame->x_offset;
378 y = interlace_rows[i / frame->width] + frame->y_offset;
379 if (x >= anim->width || y >= anim->height)
380 continue;
381
382 if (g_size_checked_mul (&offset, gdk_pixbuf_get_rowstride (anim->last_frame_data), y) &&
383 g_size_checked_add (&offset, offset, x * 4)) {
384 pixels[offset + 0] = frame->color_map[index * 3 + 0];
385 pixels[offset + 1] = frame->color_map[index * 3 + 1];
386 pixels[offset + 2] = frame->color_map[index * 3 + 2];
387 pixels[offset + 3] = 255;
388 }
389 }
390
391out:
392 g_clear_object (&lzw_decoder);
393 g_free (mem: index_buffer);
394 g_free (mem: interlace_rows);
395}
396
397GdkPixbuf*
398gdk_pixbuf_gif_anim_iter_get_pixbuf (GdkPixbufAnimationIter *anim_iter)
399{
400 GdkPixbufGifAnimIter *iter = GDK_PIXBUF_GIF_ANIM_ITER (anim_iter);
401 GdkPixbufGifAnim *anim = iter->gif_anim;
402 GdkPixbufFrame *requested_frame;
403 GList *link;
404
405 if (iter->current_frame != NULL)
406 requested_frame = iter->current_frame->data;
407 else
408 requested_frame = g_list_last (list: anim->frames)->data;
409
410 /* If the previously rendered frame is not before this one, then throw it away */
411 if (anim->last_frame != NULL) {
412 link = g_list_find (list: anim->frames, data: anim->last_frame);
413 while (link != NULL && link->data != requested_frame)
414 link = link->next;
415 if (link == NULL)
416 anim->last_frame = NULL;
417 }
418
419 /* If no rendered frame, render the first frame */
420 if (anim->last_frame == NULL) {
421 gsize len = 0;
422 if (anim->last_frame_data == NULL)
423 anim->last_frame_data = gdk_pixbuf_new (colorspace: GDK_COLORSPACE_RGB, TRUE, bits_per_sample: 8, width: anim->width, height: anim->height);
424 if (anim->last_frame_data == NULL)
425 return NULL;
426 if (g_size_checked_mul (&len, gdk_pixbuf_get_rowstride (anim->last_frame_data), anim->height))
427 memset (s: gdk_pixbuf_get_pixels (pixbuf: anim->last_frame_data), c: 0, n: len);
428 else
429 return NULL;
430 composite_frame (anim, frame: g_list_nth_data (list: anim->frames, n: 0));
431 }
432
433 /* If the requested frame is already rendered, then no action required */
434 if (requested_frame == anim->last_frame)
435 return anim->last_frame_data;
436
437 /* Starting from the last rendered frame, render to the current frame */
438 for (link = g_list_find (list: anim->frames, data: anim->last_frame); link->next != NULL && link->data != requested_frame; link = link->next) {
439 GdkPixbufFrame *frame = link->data;
440 guchar *pixels;
441 int y, x_end, y_end;
442
443 /* Remove last frame if required */
444 switch (frame->action) {
445 case GDK_PIXBUF_FRAME_RETAIN:
446 break;
447 case GDK_PIXBUF_FRAME_DISPOSE:
448 /* Replace previous area with background */
449 pixels = gdk_pixbuf_get_pixels (pixbuf: anim->last_frame_data);
450 x_end = MIN (anim->last_frame->x_offset + anim->last_frame->width, anim->width);
451 y_end = MIN (anim->last_frame->y_offset + anim->last_frame->height, anim->height);
452 for (y = anim->last_frame->y_offset; y < y_end; y++) {
453 gsize offset;
454 if (g_size_checked_mul (&offset, gdk_pixbuf_get_rowstride (anim->last_frame_data), y) &&
455 g_size_checked_add (&offset, offset, anim->last_frame->x_offset * 4)) {
456 memset (s: pixels + offset, c: 0, n: (x_end - anim->last_frame->x_offset) * 4);
457 }
458 }
459 break;
460 case GDK_PIXBUF_FRAME_REVERT:
461 /* Replace previous area with last retained area */
462 if (anim->last_frame_revert_data != NULL)
463 gdk_pixbuf_copy_area (src_pixbuf: anim->last_frame_revert_data,
464 src_x: 0, src_y: 0, width: anim->last_frame->width, height: anim->last_frame->height,
465 dest_pixbuf: anim->last_frame_data,
466 dest_x: anim->last_frame->x_offset, dest_y: anim->last_frame->y_offset);
467 break;
468 }
469
470 /* Render next frame */
471 composite_frame (anim, frame: link->next->data);
472 }
473
474 return anim->last_frame_data;
475}
476
477static gboolean
478gdk_pixbuf_gif_anim_iter_on_currently_loading_frame (GdkPixbufAnimationIter *anim_iter)
479{
480 GdkPixbufGifAnimIter *iter;
481
482 iter = GDK_PIXBUF_GIF_ANIM_ITER (anim_iter);
483
484 return iter->current_frame == NULL || iter->current_frame->next == NULL;
485}
486

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