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