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
35 struct _GdkPixbufSimpleAnimClass
36 {
37 GdkPixbufAnimationClass parent_class;
38 };
39
40 /* Private part of the GdkPixbufSimpleAnim structure */
41 struct _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
59 typedef struct _GdkPixbufSimpleAnimIter GdkPixbufSimpleAnimIter;
60 typedef 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
70 GType gdk_pixbuf_simple_anim_iter_get_type (void) G_GNUC_CONST;
71
72
73 struct _GdkPixbufSimpleAnimIterClass
74 {
75 GdkPixbufAnimationIterClass parent_class;
76 };
77
78 struct _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
92 typedef struct _GdkPixbufFrame GdkPixbufFrame;
93 struct _GdkPixbufFrame
94 {
95 GdkPixbuf *pixbuf;
96 gint delay_time;
97 gint elapsed;
98 };
99
100 static void gdk_pixbuf_simple_anim_finalize (GObject *object);
101
102 static gboolean is_static_image (GdkPixbufAnimation *animation);
103 static GdkPixbuf *get_static_image (GdkPixbufAnimation *animation);
104
105 static void get_size (GdkPixbufAnimation *anim,
106 gint *width,
107 gint *height);
108 static GdkPixbufAnimationIter *get_iter (GdkPixbufAnimation *anim,
109 const GTimeVal *start_time);
110
111
112 static void gdk_pixbuf_simple_anim_set_property (GObject *object,
113 guint prop_id,
114 const GValue *value,
115 GParamSpec *pspec);
116 static void gdk_pixbuf_simple_anim_get_property (GObject *object,
117 guint prop_id,
118 GValue *value,
119 GParamSpec *pspec);
120
121 enum
122 {
123 PROP_0,
124 PROP_LOOP
125 };
126
G_DEFINE_TYPE(GdkPixbufSimpleAnim,gdk_pixbuf_simple_anim,GDK_TYPE_PIXBUF_ANIMATION)127 G_DEFINE_TYPE (GdkPixbufSimpleAnim, gdk_pixbuf_simple_anim, GDK_TYPE_PIXBUF_ANIMATION)
128
129 static void
130 gdk_pixbuf_simple_anim_init (GdkPixbufSimpleAnim *anim)
131 {
132 }
133
134 static void
gdk_pixbuf_simple_anim_class_init(GdkPixbufSimpleAnimClass * klass)135 gdk_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 (object_class,
160 PROP_LOOP,
161 g_param_spec_boolean ("loop",
162 _("Loop"),
163 _("Whether the animation should loop when it reaches the end"),
164 FALSE,
165 G_PARAM_READWRITE));
166 }
167
168 static void
gdk_pixbuf_simple_anim_finalize(GObject * object)169 gdk_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 (frame->pixbuf);
180 g_free (frame);
181 }
182
183 g_list_free (anim->frames);
184
185 G_OBJECT_CLASS (gdk_pixbuf_simple_anim_parent_class)->finalize (object);
186 }
187
188 static gboolean
is_static_image(GdkPixbufAnimation * animation)189 is_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
198 static GdkPixbuf *
get_static_image(GdkPixbufAnimation * animation)199 get_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
211 static void
get_size(GdkPixbufAnimation * animation,gint * width,gint * height)212 get_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
227 static void
iter_clear(GdkPixbufSimpleAnimIter * iter)228 iter_clear (GdkPixbufSimpleAnimIter *iter)
229 {
230 iter->current_frame = NULL;
231 }
232
233 static void
iter_restart(GdkPixbufSimpleAnimIter * iter)234 iter_restart (GdkPixbufSimpleAnimIter *iter)
235 {
236 iter_clear (iter);
237
238 iter->current_frame = iter->simple_anim->frames;
239 }
240
241 static GdkPixbufAnimationIter *
get_iter(GdkPixbufAnimation * anim,const GTimeVal * start_time)242 get_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
261 static void gdk_pixbuf_simple_anim_iter_finalize (GObject *object);
262
263 static gint get_delay_time (GdkPixbufAnimationIter *iter);
264 static GdkPixbuf *get_pixbuf (GdkPixbufAnimationIter *iter);
265 static gboolean on_currently_loading_frame (GdkPixbufAnimationIter *iter);
266 static gboolean advance (GdkPixbufAnimationIter *iter,
267 const GTimeVal *current_time);
268
G_DEFINE_TYPE(GdkPixbufSimpleAnimIter,gdk_pixbuf_simple_anim_iter,GDK_TYPE_PIXBUF_ANIMATION_ITER)269 G_DEFINE_TYPE (GdkPixbufSimpleAnimIter, gdk_pixbuf_simple_anim_iter, GDK_TYPE_PIXBUF_ANIMATION_ITER)
270
271 static void
272 gdk_pixbuf_simple_anim_iter_init (GdkPixbufSimpleAnimIter *iter)
273 {
274 }
275
276 static void
gdk_pixbuf_simple_anim_iter_class_init(GdkPixbufSimpleAnimIterClass * klass)277 gdk_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
293 static void
gdk_pixbuf_simple_anim_iter_finalize(GObject * object)294 gdk_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 (iter->simple_anim);
302
303 G_OBJECT_CLASS (gdk_pixbuf_simple_anim_iter_parent_class)->finalize (object);
304 }
305
306 static gboolean
advance(GdkPixbufAnimationIter * anim_iter,const GTimeVal * current_time)307 advance (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
365 static gint
get_delay_time(GdkPixbufAnimationIter * anim_iter)366 get_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
382 static GdkPixbuf *
get_pixbuf(GdkPixbufAnimationIter * anim_iter)383 get_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 (iter->simple_anim->frames) > 0)
393 frame = g_list_last (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
403 static gboolean
on_currently_loading_frame(GdkPixbufAnimationIter * anim_iter)404 on_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 */
425 GdkPixbufSimpleAnim *
gdk_pixbuf_simple_anim_new(gint width,gint height,gfloat rate)426 gdk_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 */
451 void
gdk_pixbuf_simple_anim_add_frame(GdkPixbufSimpleAnim * animation,GdkPixbuf * pixbuf)452 gdk_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 (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 (animation->frames, frame);
470 }
471
472 static void
gdk_pixbuf_simple_anim_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)473 gdk_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 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
491 static void
gdk_pixbuf_simple_anim_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)492 gdk_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 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 **/
519 void
gdk_pixbuf_simple_anim_set_loop(GdkPixbufSimpleAnim * animation,gboolean loop)520 gdk_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), "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 **/
541 gboolean
gdk_pixbuf_simple_anim_get_loop(GdkPixbufSimpleAnim * animation)542 gdk_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