1/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
2/* GdkPixbuf library - Simple frame-based animations
3 *
4 * Copyright (C) Dom Lachowicz
5 *
6 * Authors: Dom Lachowicz <cinamod@hotmail.com>
7 *
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License as published by the Free Software Foundation; either
11 * version 2 of the License, or (at your option) any later version.
12 *
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with this library; if not, see <http://www.gnu.org/licenses/>.
20 *
21 * Based on code originally by:
22 * Jonathan Blandford <jrb@redhat.com>
23 * Havoc Pennington <hp@redhat.com>
24 */
25
26#include "config.h"
27#define GLIB_DISABLE_DEPRECATION_WARNINGS
28#include <glib.h>
29
30#define GDK_PIXBUF_C_COMPILATION
31#include "gdk-pixbuf.h"
32#include "gdk-pixbuf-private.h"
33#include "gdk-pixbuf-simple-anim.h"
34
35struct _GdkPixbufSimpleAnimClass
36{
37 GdkPixbufAnimationClass parent_class;
38};
39
40/* Private part of the GdkPixbufSimpleAnim structure */
41struct _GdkPixbufSimpleAnim
42{
43 GdkPixbufAnimation parent_instance;
44
45 gint n_frames;
46
47 gfloat rate;
48 gint total_time;
49
50 GList *frames;
51
52 gint width;
53 gint height;
54
55 gboolean loop;
56};
57
58
59typedef struct _GdkPixbufSimpleAnimIter GdkPixbufSimpleAnimIter;
60typedef struct _GdkPixbufSimpleAnimIterClass GdkPixbufSimpleAnimIterClass;
61
62#define GDK_TYPE_PIXBUF_SIMPLE_ANIM_ITER (gdk_pixbuf_simple_anim_iter_get_type ())
63#define GDK_PIXBUF_SIMPLE_ANIM_ITER(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), GDK_TYPE_PIXBUF_SIMPLE_ANIM_ITER, GdkPixbufSimpleAnimIter))
64#define GDK_IS_PIXBUF_SIMPLE_ANIM_ITER(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), GDK_TYPE_PIXBUF_SIMPLE_ANIM_ITER))
65
66#define GDK_PIXBUF_SIMPLE_ANIM_ITER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GDK_TYPE_PIXBUF_SIMPLE_ANIM_ITER, GdkPixbufSimpleAnimIterClass))
67#define GDK_IS_PIXBUF_SIMPLE_ANIM_ITER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GDK_TYPE_PIXBUF_SIMPLE_ANIM_ITER))
68#define GDK_PIXBUF_SIMPLE_ANIM_ITER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GDK_TYPE_PIXBUF_SIMPLE_ANIM_ITER, GdkPixbufSimpleAnimIterClass))
69
70GType gdk_pixbuf_simple_anim_iter_get_type (void) G_GNUC_CONST;
71
72
73struct _GdkPixbufSimpleAnimIterClass
74{
75 GdkPixbufAnimationIterClass parent_class;
76};
77
78struct _GdkPixbufSimpleAnimIter
79{
80 GdkPixbufAnimationIter parent_instance;
81
82 GdkPixbufSimpleAnim *simple_anim;
83
84 GTimeVal start_time;
85 GTimeVal current_time;
86
87 gint position;
88
89 GList *current_frame;
90};
91
92typedef struct _GdkPixbufFrame GdkPixbufFrame;
93struct _GdkPixbufFrame
94{
95 GdkPixbuf *pixbuf;
96 gint delay_time;
97 gint elapsed;
98};
99
100static void gdk_pixbuf_simple_anim_finalize (GObject *object);
101
102static gboolean is_static_image (GdkPixbufAnimation *animation);
103static GdkPixbuf *get_static_image (GdkPixbufAnimation *animation);
104
105static void get_size (GdkPixbufAnimation *anim,
106 gint *width,
107 gint *height);
108static GdkPixbufAnimationIter *get_iter (GdkPixbufAnimation *anim,
109 const GTimeVal *start_time);
110
111
112static void gdk_pixbuf_simple_anim_set_property (GObject *object,
113 guint prop_id,
114 const GValue *value,
115 GParamSpec *pspec);
116static void gdk_pixbuf_simple_anim_get_property (GObject *object,
117 guint prop_id,
118 GValue *value,
119 GParamSpec *pspec);
120
121enum
122{
123 PROP_0,
124 PROP_LOOP
125};
126
127G_DEFINE_TYPE (GdkPixbufSimpleAnim, gdk_pixbuf_simple_anim, GDK_TYPE_PIXBUF_ANIMATION)
128
129static void
130gdk_pixbuf_simple_anim_init (GdkPixbufSimpleAnim *anim)
131{
132}
133
134static void
135gdk_pixbuf_simple_anim_class_init (GdkPixbufSimpleAnimClass *klass)
136{
137 GObjectClass *object_class;
138 GdkPixbufAnimationClass *anim_class;
139
140 object_class = G_OBJECT_CLASS (klass);
141 anim_class = GDK_PIXBUF_ANIMATION_CLASS (klass);
142
143 object_class->set_property = gdk_pixbuf_simple_anim_set_property;
144 object_class->get_property = gdk_pixbuf_simple_anim_get_property;
145 object_class->finalize = gdk_pixbuf_simple_anim_finalize;
146
147 anim_class->is_static_image = is_static_image;
148 anim_class->get_static_image = get_static_image;
149 anim_class->get_size = get_size;
150 anim_class->get_iter = get_iter;
151
152 /**
153 * GdkPixbufSimpleAnim:loop:
154 *
155 * Whether the animation should loop when it reaches the end.
156 *
157 * Since: 2.18
158 */
159 g_object_class_install_property (oclass: object_class,
160 property_id: PROP_LOOP,
161 pspec: g_param_spec_boolean (name: "loop",
162 _("Loop"),
163 _("Whether the animation should loop when it reaches the end"),
164 FALSE,
165 flags: G_PARAM_READWRITE));
166}
167
168static void
169gdk_pixbuf_simple_anim_finalize (GObject *object)
170{
171 GdkPixbufSimpleAnim *anim;
172 GList *l;
173 GdkPixbufFrame *frame;
174
175 anim = GDK_PIXBUF_SIMPLE_ANIM (object);
176
177 for (l = anim->frames; l; l = l->next) {
178 frame = l->data;
179 g_object_unref (object: frame->pixbuf);
180 g_free (mem: frame);
181 }
182
183 g_list_free (list: anim->frames);
184
185 G_OBJECT_CLASS (gdk_pixbuf_simple_anim_parent_class)->finalize (object);
186}
187
188static gboolean
189is_static_image (GdkPixbufAnimation *animation)
190{
191 GdkPixbufSimpleAnim *anim;
192
193 anim = GDK_PIXBUF_SIMPLE_ANIM (animation);
194
195 return (anim->frames != NULL && anim->frames->next == NULL);
196}
197
198static GdkPixbuf *
199get_static_image (GdkPixbufAnimation *animation)
200{
201 GdkPixbufSimpleAnim *anim;
202
203 anim = GDK_PIXBUF_SIMPLE_ANIM (animation);
204
205 if (anim->frames == NULL)
206 return NULL;
207 else
208 return ((GdkPixbufFrame *)anim->frames->data)->pixbuf;
209}
210
211static void
212get_size (GdkPixbufAnimation *animation,
213 gint *width,
214 gint *height)
215{
216 GdkPixbufSimpleAnim *anim;
217
218 anim = GDK_PIXBUF_SIMPLE_ANIM (animation);
219
220 if (width)
221 *width = anim->width;
222
223 if (height)
224 *height = anim->height;
225}
226
227static void
228iter_clear (GdkPixbufSimpleAnimIter *iter)
229{
230 iter->current_frame = NULL;
231}
232
233static void
234iter_restart (GdkPixbufSimpleAnimIter *iter)
235{
236 iter_clear (iter);
237
238 iter->current_frame = iter->simple_anim->frames;
239}
240
241static GdkPixbufAnimationIter *
242get_iter (GdkPixbufAnimation *anim,
243 const GTimeVal *start_time)
244{
245 GdkPixbufSimpleAnimIter *iter;
246
247 iter = g_object_new (GDK_TYPE_PIXBUF_SIMPLE_ANIM_ITER, NULL);
248
249 iter->simple_anim = GDK_PIXBUF_SIMPLE_ANIM (anim);
250
251 g_object_ref (iter->simple_anim);
252
253 iter_restart (iter);
254
255 iter->start_time = *start_time;
256 iter->current_time = *start_time;
257
258 return GDK_PIXBUF_ANIMATION_ITER (iter);
259}
260
261static void gdk_pixbuf_simple_anim_iter_finalize (GObject *object);
262
263static gint get_delay_time (GdkPixbufAnimationIter *iter);
264static GdkPixbuf *get_pixbuf (GdkPixbufAnimationIter *iter);
265static gboolean on_currently_loading_frame (GdkPixbufAnimationIter *iter);
266static gboolean advance (GdkPixbufAnimationIter *iter,
267 const GTimeVal *current_time);
268
269G_DEFINE_TYPE (GdkPixbufSimpleAnimIter, gdk_pixbuf_simple_anim_iter, GDK_TYPE_PIXBUF_ANIMATION_ITER)
270
271static void
272gdk_pixbuf_simple_anim_iter_init (GdkPixbufSimpleAnimIter *iter)
273{
274}
275
276static void
277gdk_pixbuf_simple_anim_iter_class_init (GdkPixbufSimpleAnimIterClass *klass)
278{
279 GObjectClass *object_class;
280 GdkPixbufAnimationIterClass *anim_iter_class;
281
282 object_class = G_OBJECT_CLASS (klass);
283 anim_iter_class = GDK_PIXBUF_ANIMATION_ITER_CLASS (klass);
284
285 object_class->finalize = gdk_pixbuf_simple_anim_iter_finalize;
286
287 anim_iter_class->get_delay_time = get_delay_time;
288 anim_iter_class->get_pixbuf = get_pixbuf;
289 anim_iter_class->on_currently_loading_frame = on_currently_loading_frame;
290 anim_iter_class->advance = advance;
291}
292
293static void
294gdk_pixbuf_simple_anim_iter_finalize (GObject *object)
295{
296 GdkPixbufSimpleAnimIter *iter;
297
298 iter = GDK_PIXBUF_SIMPLE_ANIM_ITER (object);
299 iter_clear (iter);
300
301 g_object_unref (object: iter->simple_anim);
302
303 G_OBJECT_CLASS (gdk_pixbuf_simple_anim_iter_parent_class)->finalize (object);
304}
305
306static gboolean
307advance (GdkPixbufAnimationIter *anim_iter,
308 const GTimeVal *current_time)
309{
310 GdkPixbufSimpleAnimIter *iter;
311 gint elapsed;
312 gint loop_count;
313 GList *tmp;
314 GList *old;
315
316 iter = GDK_PIXBUF_SIMPLE_ANIM_ITER (anim_iter);
317
318 iter->current_time = *current_time;
319
320 /* We use milliseconds for all times */
321 elapsed = (((iter->current_time.tv_sec - iter->start_time.tv_sec) * G_USEC_PER_SEC +
322 iter->current_time.tv_usec - iter->start_time.tv_usec)) / 1000;
323
324 if (elapsed < 0) {
325 /* Try to compensate; probably the system clock
326 * was set backwards
327 */
328 iter->start_time = iter->current_time;
329 elapsed = 0;
330 }
331
332 g_assert (iter->simple_anim->total_time > 0);
333
334 /* See how many times we've already played the full animation,
335 * and subtract time for that.
336 */
337 loop_count = elapsed / iter->simple_anim->total_time;
338 elapsed = elapsed % iter->simple_anim->total_time;
339
340 iter->position = elapsed;
341
342 /* Now move to the proper frame */
343 if (loop_count < 1 || iter->simple_anim->loop)
344 tmp = iter->simple_anim->frames;
345 else
346 tmp = NULL;
347
348 while (tmp != NULL) {
349 GdkPixbufFrame *frame = tmp->data;
350
351 if (iter->position >= frame->elapsed &&
352 iter->position < (frame->elapsed + frame->delay_time))
353 break;
354
355 tmp = tmp->next;
356 }
357
358 old = iter->current_frame;
359
360 iter->current_frame = tmp;
361
362 return iter->current_frame != old;
363}
364
365static gint
366get_delay_time (GdkPixbufAnimationIter *anim_iter)
367{
368 GdkPixbufFrame *frame;
369 GdkPixbufSimpleAnimIter *iter;
370
371 iter = GDK_PIXBUF_SIMPLE_ANIM_ITER (anim_iter);
372
373 if (iter->current_frame) {
374 frame = iter->current_frame->data;
375 return frame->delay_time - (iter->position - frame->elapsed);
376 }
377 else {
378 return -1; /* show last frame forever */
379 }
380}
381
382static GdkPixbuf *
383get_pixbuf (GdkPixbufAnimationIter *anim_iter)
384{
385 GdkPixbufSimpleAnimIter *iter;
386 GdkPixbufFrame *frame;
387
388 iter = GDK_PIXBUF_SIMPLE_ANIM_ITER (anim_iter);
389
390 if (iter->current_frame)
391 frame = iter->current_frame->data;
392 else if (g_list_length (list: iter->simple_anim->frames) > 0)
393 frame = g_list_last (list: iter->simple_anim->frames)->data;
394 else
395 frame = NULL;
396
397 if (frame == NULL)
398 return NULL;
399
400 return frame->pixbuf;
401}
402
403static gboolean
404on_currently_loading_frame (GdkPixbufAnimationIter *anim_iter)
405{
406 GdkPixbufSimpleAnimIter *iter;
407
408 iter = GDK_PIXBUF_SIMPLE_ANIM_ITER (anim_iter);
409
410 return iter->current_frame == NULL || iter->current_frame->next == NULL;
411}
412
413/**
414 * gdk_pixbuf_simple_anim_new:
415 * @width: the width of the animation
416 * @height: the height of the animation
417 * @rate: the speed of the animation, in frames per second
418 *
419 * Creates a new, empty animation.
420 *
421 * Returns: a newly allocated #GdkPixbufSimpleAnim
422 *
423 * Since: 2.8
424 */
425GdkPixbufSimpleAnim *
426gdk_pixbuf_simple_anim_new (gint width,
427 gint height,
428 gfloat rate)
429{
430 GdkPixbufSimpleAnim *anim;
431
432 anim = g_object_new (GDK_TYPE_PIXBUF_SIMPLE_ANIM, NULL);
433 anim->width = width;
434 anim->height = height;
435 anim->rate = rate;
436
437 return anim;
438}
439
440/**
441 * gdk_pixbuf_simple_anim_add_frame:
442 * @animation: a #GdkPixbufSimpleAnim
443 * @pixbuf: the pixbuf to add
444 *
445 * Adds a new frame to @animation. The @pixbuf must
446 * have the dimensions specified when the animation
447 * was constructed.
448 *
449 * Since: 2.8
450 */
451void
452gdk_pixbuf_simple_anim_add_frame (GdkPixbufSimpleAnim *animation,
453 GdkPixbuf *pixbuf)
454{
455 GdkPixbufFrame *frame;
456 int nframe = 0;
457
458 g_return_if_fail (GDK_IS_PIXBUF_SIMPLE_ANIM (animation));
459 g_return_if_fail (GDK_IS_PIXBUF (pixbuf));
460
461 nframe = g_list_length (list: animation->frames);
462
463 frame = g_new0 (GdkPixbufFrame, 1);
464 frame->delay_time = (gint) (1000 / animation->rate);
465 frame->elapsed = (gint) (frame->delay_time * nframe);
466 animation->total_time += frame->delay_time;
467 frame->pixbuf = g_object_ref (pixbuf);
468
469 animation->frames = g_list_append (list: animation->frames, data: frame);
470}
471
472static void
473gdk_pixbuf_simple_anim_get_property (GObject *object,
474 guint prop_id,
475 GValue *value,
476 GParamSpec *pspec)
477{
478 GdkPixbufSimpleAnim *animation = GDK_PIXBUF_SIMPLE_ANIM (object);
479
480 switch (prop_id) {
481 case PROP_LOOP:
482 g_value_set_boolean (value,
483 v_boolean: gdk_pixbuf_simple_anim_get_loop (animation));
484 break;
485 default:
486 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
487 break;
488 }
489}
490
491static void
492gdk_pixbuf_simple_anim_set_property (GObject *object,
493 guint prop_id,
494 const GValue *value,
495 GParamSpec *pspec)
496{
497 GdkPixbufSimpleAnim *animation = GDK_PIXBUF_SIMPLE_ANIM (object);
498
499 switch (prop_id) {
500 case PROP_LOOP:
501 gdk_pixbuf_simple_anim_set_loop (animation,
502 loop: g_value_get_boolean (value));
503 break;
504 default:
505 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
506 break;
507 }
508}
509
510/**
511 * gdk_pixbuf_simple_anim_set_loop:
512 * @animation: a #GdkPixbufSimpleAnim
513 * @loop: whether to loop the animation
514 *
515 * Sets whether @animation should loop indefinitely when it reaches the end.
516 *
517 * Since: 2.18
518 **/
519void
520gdk_pixbuf_simple_anim_set_loop (GdkPixbufSimpleAnim *animation,
521 gboolean loop)
522{
523 g_return_if_fail (GDK_IS_PIXBUF_SIMPLE_ANIM (animation));
524
525 if (loop != animation->loop) {
526 animation->loop = loop;
527 g_object_notify (G_OBJECT (animation), property_name: "loop");
528 }
529}
530
531/**
532 * gdk_pixbuf_simple_anim_get_loop:
533 * @animation: a #GdkPixbufSimpleAnim
534 *
535 * Gets whether @animation should loop indefinitely when it reaches the end.
536 *
537 * Returns: %TRUE if the animation loops forever, %FALSE otherwise
538 *
539 * Since: 2.18
540 **/
541gboolean
542gdk_pixbuf_simple_anim_get_loop (GdkPixbufSimpleAnim *animation)
543{
544 g_return_val_if_fail (GDK_IS_PIXBUF_SIMPLE_ANIM (animation), FALSE);
545
546 return animation->loop;
547}
548

source code of gtk/subprojects/gdk-pixbuf/gdk-pixbuf/gdk-pixbuf-simple-anim.c