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 | static void gdk_pixbuf_gif_anim_finalize (GObject *object); |
31 | |
32 | static gboolean gdk_pixbuf_gif_anim_is_static_image (GdkPixbufAnimation *animation); |
33 | static GdkPixbuf* gdk_pixbuf_gif_anim_get_static_image (GdkPixbufAnimation *animation); |
34 | |
35 | static void gdk_pixbuf_gif_anim_get_size (GdkPixbufAnimation *anim, |
36 | int *width, |
37 | int *height); |
38 | G_GNUC_BEGIN_IGNORE_DEPRECATIONS |
39 | static GdkPixbufAnimationIter* gdk_pixbuf_gif_anim_get_iter (GdkPixbufAnimation *anim, |
40 | const GTimeVal *start_time); |
41 | G_GNUC_END_IGNORE_DEPRECATIONS |
42 | static GdkPixbuf* gdk_pixbuf_gif_anim_iter_get_pixbuf (GdkPixbufAnimationIter *iter); |
43 | |
44 | |
45 | |
46 | G_DEFINE_TYPE (GdkPixbufGifAnim, gdk_pixbuf_gif_anim, GDK_TYPE_PIXBUF_ANIMATION); |
47 | |
48 | static void |
49 | gdk_pixbuf_gif_anim_init (GdkPixbufGifAnim *anim) |
50 | { |
51 | } |
52 | |
53 | static void |
54 | gdk_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 | |
67 | static void |
68 | gdk_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 | |
91 | static gboolean |
92 | gdk_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 | |
102 | G_GNUC_BEGIN_IGNORE_DEPRECATIONS |
103 | static GdkPixbuf* |
104 | gdk_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 | } |
122 | G_GNUC_END_IGNORE_DEPRECATIONS |
123 | |
124 | static void |
125 | gdk_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 | |
141 | static void |
142 | iter_clear (GdkPixbufGifAnimIter *iter) |
143 | { |
144 | iter->current_frame = NULL; |
145 | } |
146 | |
147 | static void |
148 | iter_restart (GdkPixbufGifAnimIter *iter) |
149 | { |
150 | iter_clear (iter); |
151 | |
152 | iter->current_frame = iter->gif_anim->frames; |
153 | } |
154 | |
155 | G_GNUC_BEGIN_IGNORE_DEPRECATIONS |
156 | static GdkPixbufAnimationIter* |
157 | gdk_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 | } |
176 | G_GNUC_END_IGNORE_DEPRECATIONS |
177 | |
178 | |
179 | |
180 | static void gdk_pixbuf_gif_anim_iter_finalize (GObject *object); |
181 | |
182 | static int gdk_pixbuf_gif_anim_iter_get_delay_time (GdkPixbufAnimationIter *iter); |
183 | static gboolean gdk_pixbuf_gif_anim_iter_on_currently_loading_frame (GdkPixbufAnimationIter *iter); |
184 | G_GNUC_BEGIN_IGNORE_DEPRECATIONS |
185 | static gboolean gdk_pixbuf_gif_anim_iter_advance (GdkPixbufAnimationIter *iter, |
186 | const GTimeVal *current_time); |
187 | G_GNUC_END_IGNORE_DEPRECATIONS |
188 | |
189 | |
190 | |
191 | G_DEFINE_TYPE (GdkPixbufGifAnimIter, gdk_pixbuf_gif_anim_iter, GDK_TYPE_PIXBUF_ANIMATION_ITER); |
192 | |
193 | static void |
194 | gdk_pixbuf_gif_anim_iter_init (GdkPixbufGifAnimIter *iter) |
195 | { |
196 | } |
197 | |
198 | static void |
199 | gdk_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 | |
213 | static void |
214 | gdk_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 | |
225 | G_GNUC_BEGIN_IGNORE_DEPRECATIONS |
226 | static gboolean |
227 | gdk_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 | } |
294 | G_GNUC_END_IGNORE_DEPRECATIONS |
295 | |
296 | int |
297 | gdk_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 | |
320 | static void |
321 | composite_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 | |
391 | out: |
392 | g_clear_object (&lzw_decoder); |
393 | g_free (mem: index_buffer); |
394 | g_free (mem: interlace_rows); |
395 | } |
396 | |
397 | GdkPixbuf* |
398 | gdk_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 | |
477 | static gboolean |
478 | gdk_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 | |