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