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 | |
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 |
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 (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 | |
168 | static void |
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 (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 | |
188 | static gboolean |
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 * |
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 |
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 |
228 | iter_clear (GdkPixbufSimpleAnimIter *iter) |
229 | { |
230 | iter->current_frame = NULL; |
231 | } |
232 | |
233 | static void |
234 | iter_restart (GdkPixbufSimpleAnimIter *iter) |
235 | { |
236 | iter_clear (iter); |
237 | |
238 | iter->current_frame = iter->simple_anim->frames; |
239 | } |
240 | |
241 | static GdkPixbufAnimationIter * |
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 | |
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 |
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 |
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 (object: iter->simple_anim); |
302 | |
303 | G_OBJECT_CLASS (gdk_pixbuf_simple_anim_iter_parent_class)->finalize (object); |
304 | } |
305 | |
306 | static gboolean |
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 |
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 * |
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 (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 | |
403 | static gboolean |
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 * |
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 |
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 (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 | |
472 | static void |
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 | 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 | |
491 | static void |
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 | 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 | **/ |
519 | void |
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), 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 | **/ |
541 | gboolean |
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 | |