]> Pileus Git - ~andy/gtk/blob - gdk-pixbuf/io-gif-animation.c
391e9285032234aabdddf30ce54bde7243ba87f2
[~andy/gtk] / gdk-pixbuf / io-gif-animation.c
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, write to the
21  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
22  * Boston, MA 02111-1307, USA.
23  */
24
25 #include "config.h"
26 #include <errno.h>
27 #include "gdk-pixbuf-private.h"
28 #include "io-gif-animation.h"
29
30 static void gdk_pixbuf_gif_anim_class_init (GdkPixbufGifAnimClass *klass);
31 static void gdk_pixbuf_gif_anim_finalize   (GObject        *object);
32
33 static gboolean                gdk_pixbuf_gif_anim_is_static_image  (GdkPixbufAnimation *animation);
34 static GdkPixbuf*              gdk_pixbuf_gif_anim_get_static_image (GdkPixbufAnimation *animation);
35
36 static void                    gdk_pixbuf_gif_anim_get_size (GdkPixbufAnimation *anim,
37                                                              int                *width,
38                                                              int                *height);
39 static GdkPixbufAnimationIter* gdk_pixbuf_gif_anim_get_iter (GdkPixbufAnimation *anim,
40                                                              const GTimeVal     *start_time);
41
42
43 \f
44
45 static gpointer parent_class;
46
47 GType
48 gdk_pixbuf_gif_anim_get_type (void)
49 {
50         static GType object_type = 0;
51
52         if (!object_type) {
53                 const GTypeInfo object_info = {
54                         sizeof (GdkPixbufGifAnimClass),
55                         (GBaseInitFunc) NULL,
56                         (GBaseFinalizeFunc) NULL,
57                         (GClassInitFunc) gdk_pixbuf_gif_anim_class_init,
58                         NULL,           /* class_finalize */
59                         NULL,           /* class_data */
60                         sizeof (GdkPixbufGifAnim),
61                         0,              /* n_preallocs */
62                         (GInstanceInitFunc) NULL,
63                 };
64                 
65                 object_type = g_type_register_static (GDK_TYPE_PIXBUF_ANIMATION,
66                                                       g_intern_static_string ("GdkPixbufGifAnim"),
67                                                       &object_info, 0);
68         }
69         
70         return object_type;
71 }
72
73 static void
74 gdk_pixbuf_gif_anim_class_init (GdkPixbufGifAnimClass *klass)
75 {
76         GObjectClass *object_class = G_OBJECT_CLASS (klass);
77         GdkPixbufAnimationClass *anim_class = GDK_PIXBUF_ANIMATION_CLASS (klass);
78         
79         parent_class = g_type_class_peek_parent (klass);
80         
81         object_class->finalize = gdk_pixbuf_gif_anim_finalize;
82
83         anim_class->is_static_image = gdk_pixbuf_gif_anim_is_static_image;
84         anim_class->get_static_image = gdk_pixbuf_gif_anim_get_static_image;
85         anim_class->get_size = gdk_pixbuf_gif_anim_get_size;
86         anim_class->get_iter = gdk_pixbuf_gif_anim_get_iter;
87 }
88
89 static void
90 gdk_pixbuf_gif_anim_finalize (GObject *object)
91 {
92         GdkPixbufGifAnim *gif_anim = GDK_PIXBUF_GIF_ANIM (object);
93
94         GList *l;
95         GdkPixbufFrame *frame;
96         
97         for (l = gif_anim->frames; l; l = l->next) {
98                 frame = l->data;
99                 g_object_unref (frame->pixbuf);
100                 if (frame->composited)
101                         g_object_unref (frame->composited);
102                 if (frame->revert)
103                         g_object_unref (frame->revert);
104                 g_free (frame);
105         }
106         
107         g_list_free (gif_anim->frames);
108         
109         G_OBJECT_CLASS (parent_class)->finalize (object);
110 }
111
112 static gboolean
113 gdk_pixbuf_gif_anim_is_static_image  (GdkPixbufAnimation *animation)
114 {
115         GdkPixbufGifAnim *gif_anim;
116
117         gif_anim = GDK_PIXBUF_GIF_ANIM (animation);
118
119         return (gif_anim->frames != NULL &&
120                 gif_anim->frames->next == NULL);
121 }
122
123 static GdkPixbuf*
124 gdk_pixbuf_gif_anim_get_static_image (GdkPixbufAnimation *animation)
125 {
126         GdkPixbufGifAnim *gif_anim;
127
128         gif_anim = GDK_PIXBUF_GIF_ANIM (animation);
129
130         if (gif_anim->frames == NULL)
131                 return NULL;
132         else
133                 return GDK_PIXBUF (((GdkPixbufFrame*)gif_anim->frames->data)->pixbuf);        
134 }
135
136 static void
137 gdk_pixbuf_gif_anim_get_size (GdkPixbufAnimation *anim,
138                               int                *width,
139                               int                *height)
140 {
141         GdkPixbufGifAnim *gif_anim;
142
143         gif_anim = GDK_PIXBUF_GIF_ANIM (anim);
144
145         if (width)
146                 *width = gif_anim->width;
147
148         if (height)
149                 *height = gif_anim->height;
150 }
151
152
153 static void
154 iter_clear (GdkPixbufGifAnimIter *iter)
155 {
156         iter->current_frame = NULL;
157 }
158
159 static void
160 iter_restart (GdkPixbufGifAnimIter *iter)
161 {
162         iter_clear (iter);
163   
164         iter->current_frame = iter->gif_anim->frames;
165 }
166
167 static GdkPixbufAnimationIter*
168 gdk_pixbuf_gif_anim_get_iter (GdkPixbufAnimation *anim,
169                               const GTimeVal     *start_time)
170 {
171         GdkPixbufGifAnimIter *iter;
172
173         iter = g_object_new (GDK_TYPE_PIXBUF_GIF_ANIM_ITER, NULL);
174
175         iter->gif_anim = GDK_PIXBUF_GIF_ANIM (anim);
176
177         g_object_ref (iter->gif_anim);
178         
179         iter_restart (iter);
180
181         iter->start_time = *start_time;
182         iter->current_time = *start_time;
183         iter->first_loop_slowness = 0;
184         
185         return GDK_PIXBUF_ANIMATION_ITER (iter);
186 }
187
188 \f
189
190 static void gdk_pixbuf_gif_anim_iter_class_init (GdkPixbufGifAnimIterClass *klass);
191 static void gdk_pixbuf_gif_anim_iter_finalize   (GObject                   *object);
192
193 static int        gdk_pixbuf_gif_anim_iter_get_delay_time             (GdkPixbufAnimationIter *iter);
194 static GdkPixbuf* gdk_pixbuf_gif_anim_iter_get_pixbuf                 (GdkPixbufAnimationIter *iter);
195 static gboolean   gdk_pixbuf_gif_anim_iter_on_currently_loading_frame (GdkPixbufAnimationIter *iter);
196 static gboolean   gdk_pixbuf_gif_anim_iter_advance                    (GdkPixbufAnimationIter *iter,
197                                                                        const GTimeVal         *current_time);
198
199 \f
200
201 static gpointer iter_parent_class;
202
203 GType
204 gdk_pixbuf_gif_anim_iter_get_type (void)
205 {
206         static GType object_type = 0;
207
208         if (!object_type) {
209                 const GTypeInfo object_info = {
210                         sizeof (GdkPixbufGifAnimIterClass),
211                         (GBaseInitFunc) NULL,
212                         (GBaseFinalizeFunc) NULL,
213                         (GClassInitFunc) gdk_pixbuf_gif_anim_iter_class_init,
214                         NULL,           /* class_finalize */
215                         NULL,           /* class_data */
216                         sizeof (GdkPixbufGifAnimIter),
217                         0,              /* n_preallocs */
218                         (GInstanceInitFunc) NULL,
219                 };
220                 
221                 object_type = g_type_register_static (GDK_TYPE_PIXBUF_ANIMATION_ITER,
222                                                       g_intern_static_string ("GdkPixbufGifAnimIter"),
223                                                       &object_info, 0);
224         }
225         
226         return object_type;
227 }
228
229 static void
230 gdk_pixbuf_gif_anim_iter_class_init (GdkPixbufGifAnimIterClass *klass)
231 {
232         GObjectClass *object_class = G_OBJECT_CLASS (klass);
233         GdkPixbufAnimationIterClass *anim_iter_class =
234                 GDK_PIXBUF_ANIMATION_ITER_CLASS (klass);
235         
236         iter_parent_class = g_type_class_peek_parent (klass);
237         
238         object_class->finalize = gdk_pixbuf_gif_anim_iter_finalize;
239
240         anim_iter_class->get_delay_time = gdk_pixbuf_gif_anim_iter_get_delay_time;
241         anim_iter_class->get_pixbuf = gdk_pixbuf_gif_anim_iter_get_pixbuf;
242         anim_iter_class->on_currently_loading_frame = gdk_pixbuf_gif_anim_iter_on_currently_loading_frame;
243         anim_iter_class->advance = gdk_pixbuf_gif_anim_iter_advance;
244 }
245
246 static void
247 gdk_pixbuf_gif_anim_iter_finalize (GObject *object)
248 {
249         GdkPixbufGifAnimIter *iter = GDK_PIXBUF_GIF_ANIM_ITER (object);
250
251         iter_clear (iter);
252
253         g_object_unref (iter->gif_anim);
254         
255         G_OBJECT_CLASS (iter_parent_class)->finalize (object);
256 }
257
258 static gboolean
259 gdk_pixbuf_gif_anim_iter_advance (GdkPixbufAnimationIter *anim_iter,
260                                   const GTimeVal         *current_time)
261 {
262         GdkPixbufGifAnimIter *iter;
263         gint elapsed;
264         gint loop;
265         GList *tmp;
266         GList *old;
267         
268         iter = GDK_PIXBUF_GIF_ANIM_ITER (anim_iter);
269         
270         iter->current_time = *current_time;
271
272         /* We use milliseconds for all times */
273         elapsed =
274           (((iter->current_time.tv_sec - iter->start_time.tv_sec) * G_USEC_PER_SEC +
275             iter->current_time.tv_usec - iter->start_time.tv_usec)) / 1000;
276
277         if (elapsed < 0) {
278                 /* Try to compensate; probably the system clock
279                  * was set backwards
280                  */
281                 iter->start_time = iter->current_time;
282                 elapsed = 0;
283         }
284
285         g_assert (iter->gif_anim->total_time > 0);
286         
287         /* See how many times we've already played the full animation,
288          * and subtract time for that.
289          */
290
291         if (iter->gif_anim->loading)
292                 loop = 0;
293         else {
294                 /* If current_frame is NULL at this point, we have loaded the
295                  * animation from a source which fell behind the speed of the 
296                  * display. We remember how much slower the first loop was due
297                  * to this and correct the position calculation in order to not
298                  * jump in the middle of the second loop.
299                  */
300                 if (iter->current_frame == NULL)
301                         iter->first_loop_slowness = MAX(0, elapsed - iter->gif_anim->total_time);
302
303                 loop = (elapsed - iter->first_loop_slowness) / iter->gif_anim->total_time;
304                 elapsed = (elapsed - iter->first_loop_slowness) % iter->gif_anim->total_time;
305         }
306
307         iter->position = elapsed;
308
309         /* Now move to the proper frame */
310         if (iter->gif_anim->loop == 0 || loop < iter->gif_anim->loop) 
311                 tmp = iter->gif_anim->frames;
312         else 
313                 tmp = NULL;
314         while (tmp != NULL) {
315                 GdkPixbufFrame *frame = tmp->data;
316                 
317                 if (iter->position >= frame->elapsed &&
318                     iter->position < (frame->elapsed + frame->delay_time))
319                         break;
320                 
321                 tmp = tmp->next;
322         }
323
324         old = iter->current_frame;
325         
326         iter->current_frame = tmp;
327
328         return iter->current_frame != old;
329 }
330
331 int
332 gdk_pixbuf_gif_anim_iter_get_delay_time (GdkPixbufAnimationIter *anim_iter)
333 {
334         GdkPixbufFrame *frame;
335         GdkPixbufGifAnimIter *iter;
336   
337         iter = GDK_PIXBUF_GIF_ANIM_ITER (anim_iter);
338
339         if (iter->current_frame) {
340                 frame = iter->current_frame->data;
341
342 #if 0
343                 g_print ("frame start: %d pos: %d frame len: %d frame remaining: %d\n",
344                          frame->elapsed,
345                          iter->position,
346                          frame->delay_time,
347                          frame->delay_time - (iter->position - frame->elapsed));
348 #endif
349                 
350                 return frame->delay_time - (iter->position - frame->elapsed);
351         } else 
352                 return -1; /* show last frame forever */
353 }
354
355 void
356 gdk_pixbuf_gif_anim_frame_composite (GdkPixbufGifAnim *gif_anim,
357                                      GdkPixbufFrame   *frame)
358 {  
359         GList *link;
360         GList *tmp;
361         
362         link = g_list_find (gif_anim->frames, frame);
363         
364         if (frame->need_recomposite || frame->composited == NULL) {
365                 /* For now, to composite we start with the last
366                  * composited frame and composite everything up to
367                  * here.
368                  */
369
370                 /* Rewind to last composited frame. */
371                 tmp = link;
372                 while (tmp != NULL) {
373                         GdkPixbufFrame *f = tmp->data;
374                         
375                         if (f->need_recomposite) {
376                                 if (f->composited) {
377                                         g_object_unref (f->composited);
378                                         f->composited = NULL;
379                                 }
380                         }
381
382                         if (f->composited != NULL)
383                                 break;
384                         
385                         tmp = tmp->prev;
386                 }
387
388                 /* Go forward, compositing all frames up to the current frame */
389                 if (tmp == NULL)
390                         tmp = gif_anim->frames;
391                 
392                 while (tmp != NULL) {
393                         GdkPixbufFrame *f = tmp->data;
394                         gint clipped_width, clipped_height;
395
396                         if (f->pixbuf == NULL)
397                                 return;
398
399                         clipped_width = MIN (gif_anim->width - f->x_offset, gdk_pixbuf_get_width (f->pixbuf));
400                         clipped_height = MIN (gif_anim->height - f->y_offset, gdk_pixbuf_get_height (f->pixbuf));
401   
402                         if (f->need_recomposite) {
403                                 if (f->composited) {
404                                         g_object_unref (f->composited);
405                                         f->composited = NULL;
406                                 }
407                         }
408                         
409                         if (f->composited != NULL)
410                                 goto next;
411
412                         if (tmp->prev == NULL) {
413                                 /* First frame may be smaller than the whole image;
414                                  * if so, we make the area outside it full alpha if the
415                                  * image has alpha, and background color otherwise.
416                                  * GIF spec doesn't actually say what to do about this.
417                                  */
418                                 f->composited = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
419                                                                 TRUE,
420                                                                 8, gif_anim->width, gif_anim->height);
421
422                                 if (f->composited == NULL)
423                                         return;
424
425                                 /* alpha gets dumped if f->composited has no alpha */
426                                 
427                                 gdk_pixbuf_fill (f->composited,
428                                                  (gif_anim->bg_red << 24) |
429                                                  (gif_anim->bg_green << 16) |
430                                                  (gif_anim->bg_blue << 8));
431
432                                 if (clipped_width > 0 && clipped_height > 0)
433                                         gdk_pixbuf_composite (f->pixbuf,
434                                                               f->composited,
435                                                               f->x_offset,
436                                                               f->y_offset,
437                                                               clipped_width,
438                                                               clipped_height,
439                                                               f->x_offset, f->y_offset,
440                                                               1.0, 1.0,
441                                                               GDK_INTERP_BILINEAR,
442                                                               255);
443                                 
444                                 if (f->action == GDK_PIXBUF_FRAME_REVERT)
445                                         g_warning ("First frame of GIF has bad dispose mode, GIF loader should not have loaded this image");
446
447                                 f->need_recomposite = FALSE;
448                         } else {
449                                 GdkPixbufFrame *prev_frame;
450                                 gint prev_clipped_width;
451                                 gint prev_clipped_height;
452                                 
453                                 prev_frame = tmp->prev->data;
454
455                                 prev_clipped_width = MIN (gif_anim->width - prev_frame->x_offset, gdk_pixbuf_get_width (prev_frame->pixbuf));
456                                 prev_clipped_height = MIN (gif_anim->height - prev_frame->y_offset, gdk_pixbuf_get_height (prev_frame->pixbuf));
457
458                                 /* Init f->composited with what we should have after the previous
459                                  * frame
460                                  */
461                                 
462                                 if (prev_frame->action == GDK_PIXBUF_FRAME_RETAIN) {
463                                         f->composited = gdk_pixbuf_copy (prev_frame->composited);
464
465                                         if (f->composited == NULL)
466                                                 return;
467                                         
468                                 } else if (prev_frame->action == GDK_PIXBUF_FRAME_DISPOSE) {
469                                         f->composited = gdk_pixbuf_copy (prev_frame->composited);
470
471                                         if (f->composited == NULL)
472                                                 return;
473
474                                         if (prev_clipped_width > 0 && prev_clipped_height > 0) {
475                                                 /* Clear area of previous frame to background */
476                                                 GdkPixbuf *area;
477
478                                                 area = gdk_pixbuf_new_subpixbuf (f->composited,
479                                                                                  prev_frame->x_offset,
480                                                                                  prev_frame->y_offset,
481                                                                                  prev_clipped_width,
482                                                                                  prev_clipped_height);
483
484                                                 if (area == NULL)
485                                                         return;
486                                                 
487                                                 gdk_pixbuf_fill (area,
488                                                                  (gif_anim->bg_red << 24) |
489                                                                  (gif_anim->bg_green << 16) |
490                                                                  (gif_anim->bg_blue << 8));
491                                                 
492                                                 g_object_unref (area);
493                                         }                                        
494                                 } else if (prev_frame->action == GDK_PIXBUF_FRAME_REVERT) {
495                                         f->composited = gdk_pixbuf_copy (prev_frame->composited);
496
497                                         if (f->composited == NULL)
498                                                 return;
499
500                                         if (prev_frame->revert != NULL &&
501                                             prev_clipped_width > 0 && prev_clipped_height > 0) {
502                                                 /* Copy in the revert frame */
503                                                 gdk_pixbuf_copy_area (prev_frame->revert,
504                                                                       0, 0,
505                                                                       gdk_pixbuf_get_width (prev_frame->revert),
506                                                                       gdk_pixbuf_get_height (prev_frame->revert),
507                                                                       f->composited,
508                                                                       prev_frame->x_offset,
509                                                                       prev_frame->y_offset);
510                                         }
511                                 } else {
512                                         g_warning ("Unknown revert action for GIF frame");
513                                 }
514
515                                 if (f->revert == NULL &&
516                                     f->action == GDK_PIXBUF_FRAME_REVERT) {
517                                         if (clipped_width > 0 && clipped_height > 0) {
518                                                 /* We need to save the contents before compositing */
519                                                 GdkPixbuf *area;
520                                                 
521                                                 area = gdk_pixbuf_new_subpixbuf (f->composited,
522                                                                                  f->x_offset,
523                                                                                  f->y_offset,
524                                                                                  clipped_width,
525                                                                                  clipped_height);
526                                             
527                                                 if (area == NULL)
528                                                         return;
529                                                 
530                                                 f->revert = gdk_pixbuf_copy (area);
531
532                                                 g_object_unref (area);
533
534                                                 if (f->revert == NULL)
535                                                         return;
536                                         }
537                                 }
538
539                                 if (clipped_width > 0 && clipped_height > 0 &&
540                                     f->pixbuf != NULL && f->composited != NULL) {
541                                         /* Put current frame onto f->composited */
542                                         gdk_pixbuf_composite (f->pixbuf,
543                                                               f->composited,
544                                                               f->x_offset,
545                                                               f->y_offset,
546                                                               clipped_width,
547                                                               clipped_height,
548                                                               f->x_offset, f->y_offset,
549                                                               1.0, 1.0,
550                                                               GDK_INTERP_NEAREST,
551                                                               255);
552                                 }
553                         
554                                 f->need_recomposite = FALSE;
555                         }
556                         
557                 next:
558                         if (tmp == link)
559                                 break;
560                         
561                         tmp = tmp->next;
562                 }
563         }
564 }
565
566 GdkPixbuf*
567 gdk_pixbuf_gif_anim_iter_get_pixbuf (GdkPixbufAnimationIter *anim_iter)
568 {
569         GdkPixbufGifAnimIter *iter;
570         GdkPixbufFrame *frame;
571         
572         iter = GDK_PIXBUF_GIF_ANIM_ITER (anim_iter);
573
574         frame = iter->current_frame ? iter->current_frame->data : g_list_last (iter->gif_anim->frames)->data;
575
576 #if 0
577         if (FALSE && frame)
578           g_print ("current frame %d dispose mode %d  %d x %d\n",
579                    g_list_index (iter->gif_anim->frames,
580                                  frame),
581                    frame->action,
582                    gdk_pixbuf_get_width (frame->pixbuf),
583                    gdk_pixbuf_get_height (frame->pixbuf));
584 #endif
585         
586         if (frame == NULL)
587                 return NULL;
588
589         gdk_pixbuf_gif_anim_frame_composite (iter->gif_anim, frame);
590         
591         return frame->composited;
592 }
593
594 static gboolean
595 gdk_pixbuf_gif_anim_iter_on_currently_loading_frame (GdkPixbufAnimationIter *anim_iter)
596 {
597         GdkPixbufGifAnimIter *iter;
598   
599         iter = GDK_PIXBUF_GIF_ANIM_ITER (anim_iter);
600
601         return iter->current_frame == NULL || iter->current_frame->next == NULL;  
602 }