]> Pileus Git - ~andy/gtk/blob - gdk-pixbuf/io-ani.c
New loader, for .ANI animations.
[~andy/gtk] / gdk-pixbuf / io-ani.c
1 /* -*- mode: C; c-file-style: "linux" -*- */
2 /* GdkPixbuf library - ANI image loader
3  *
4  * Copyright (C) 2002 The Free Software Foundation
5  *
6  * Authors: Matthias Clasen <maclas@gmx.de>
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, write to the
20  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
21  * Boston, MA 02111-1307, USA.
22  */
23
24 #undef DEBUG_ANI
25
26 #include <config.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include "gdk-pixbuf-private.h"
30 #include "gdk-pixbuf-io.h"
31 #include "io-ani-animation.h"
32
33 static int
34 lsb_32 (guchar *src)
35 {
36         return src[0] | (src[1] << 8) | (src[2] << 16) | (src[3] << 24);
37 }
38
39 #define MAKE_TAG(a,b,c,d) ( (guint32)d << 24 | \
40                             (guint32)c << 16 | \
41                             (guint32)b <<  8 | \
42                             (guint32)a )
43
44 #define TAG_RIFF MAKE_TAG('R','I','F','F')
45 #define TAG_ACON MAKE_TAG('A','C','O','N')
46 #define TAG_LIST MAKE_TAG('L','I','S','T')
47 #define TAG_INAM MAKE_TAG('I','N','A','M')
48 #define TAG_IART MAKE_TAG('I','A','R','T')
49 #define TAG_anih MAKE_TAG('a','n','i','h')
50 #define TAG_seq  MAKE_TAG('s','e','q',' ')
51 #define TAG_rate MAKE_TAG('r','a','t','e')
52 #define TAG_icon MAKE_TAG('i','c','o','n')
53
54 typedef struct _AniLoaderContext 
55 {
56         guint32 cp;
57         
58         guchar *buffer;
59         guchar *byte;
60         guint   n_bytes;
61         guint   buffer_size;
62         
63         ModulePreparedNotifyFunc prepared_func;
64         ModuleUpdatedNotifyFunc updated_func;
65         gpointer user_data;
66         
67         guint32 data_size;
68         
69         guint32 HeaderSize;
70         guint32 NumFrames; 
71         guint32 NumSteps; 
72         guint32 Width;   
73         guint32 Height; 
74         guint32 BitCount; 
75         guint32 NumPlanes; 
76         guint32 DisplayRate; 
77         guint32 Flags;
78         
79         guint32 chunk_id;
80         guint32 chunk_size;
81         
82         gchar  *title;
83         gchar  *author;
84
85         GdkPixbufAniAnim *animation;
86         GdkPixbufLoader *loader;
87
88         int     pos;
89 } AniLoaderContext;
90
91
92 #define BYTES_LEFT(context) \
93         ((context)->n_bytes - ((context)->byte - (context)->buffer))
94
95 static void
96 read_int8 (AniLoaderContext *context,
97            guchar     *data,
98            int        count)
99 {
100         int total = MIN (count, BYTES_LEFT (context));
101         memcpy (data, context->byte, total);
102         context->byte += total;
103         context->cp += total;
104 }
105
106
107 static guint32
108 read_int32 (AniLoaderContext *context)
109 {
110         guint32 result;
111
112         read_int8 (context, (guchar*) &result, 4);
113         return lsb_32 ((guchar *) &result);
114 }
115
116 static void
117 context_free (AniLoaderContext *context)
118 {
119         if (!context)
120                 return;
121
122         if (context->loader) 
123         {
124                 gdk_pixbuf_loader_close (context->loader, NULL);
125                 g_object_unref (context->loader);
126         }
127         if (context->animation) 
128                 g_object_unref (context->animation);
129         g_free (context->buffer);
130         g_free (context->title);
131         g_free (context->author);
132         
133         g_free (context);
134 }
135
136 static void
137 prepared_callback (GdkPixbufLoader *loader,
138                    gpointer data)
139 {
140         AniLoaderContext *context = (AniLoaderContext*)data;
141
142 #ifdef DEBUG_ANI
143         g_print ("%d pixbuf prepared\n", context->pos);
144 #endif
145
146         GdkPixbuf *pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
147         if (!pixbuf)
148                 return;
149
150         if (gdk_pixbuf_get_width (pixbuf) > context->animation->width) 
151                 context->animation->width = gdk_pixbuf_get_width (pixbuf);
152         
153         if (gdk_pixbuf_get_height (pixbuf) > context->animation->height) 
154                 context->animation->height = gdk_pixbuf_get_height (pixbuf);
155         
156         if (context->title != NULL) 
157                 gdk_pixbuf_set_option (pixbuf, "Title", context->title);
158         
159         if (context->author != NULL) 
160                 gdk_pixbuf_set_option (pixbuf, "Author", context->author);
161
162         g_object_ref (pixbuf);
163         context->animation->pixbufs[context->pos] = pixbuf;
164
165         if (context->pos == 0) 
166         {
167                 if (context->prepared_func)
168                         (* context->prepared_func) (pixbuf, 
169                                                     GDK_PIXBUF_ANIMATION (context->animation), 
170                                                     context->user_data);
171         }
172         else {
173                 /* FIXME - this is necessary for nice display of loading 
174                    animations because GtkImage ignores 
175                    gdk_pixbuf_animation_iter_on_currently_loading_frame()
176                    and always exposes the full frame */
177                 GdkPixbuf *last = context->animation->pixbufs[context->pos - 1];
178                 gint width = MIN (gdk_pixbuf_get_width (last), gdk_pixbuf_get_width (pixbuf));
179                 gint height = MIN (gdk_pixbuf_get_height (last), gdk_pixbuf_get_height (pixbuf));
180                 gdk_pixbuf_copy_area (last, 0, 0, width, height, pixbuf, 0, 0);
181         }
182
183         context->pos++;
184 }
185
186 static void
187 updated_callback (GdkPixbufLoader* loader,
188                   gint x, gint y, gint width, gint height,
189                   gpointer data)
190 {
191         AniLoaderContext *context = (AniLoaderContext*)data;
192
193         GdkPixbuf *pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
194         
195         if (context->updated_func)
196                 (* context->updated_func) (pixbuf, 
197                                            x, y, width, height,
198                                            context->user_data);
199 }
200
201 static gboolean
202 ani_load_chunk (AniLoaderContext *context, GError **error)
203 {
204         int i;
205         
206         if (context->chunk_id == 0x0) {
207                 if (BYTES_LEFT (context) < 8)
208                         return FALSE;
209                 context->chunk_id = read_int32 (context);
210                 context->chunk_size = read_int32 (context);
211                 /* Pad it up to word length */
212                 if (context->chunk_size % 2)
213                         context->chunk_size += (2 - (context->chunk_size % 2));
214         
215         }
216         
217         while (context->chunk_id == TAG_LIST)
218         {
219                 if (BYTES_LEFT (context) < 12)
220                         return FALSE;
221                         
222                 read_int32 (context);
223                 context->chunk_id = read_int32 (context);
224                 context->chunk_size = read_int32 (context);
225                 /* Pad it up to word length */
226                 if (context->chunk_size % 2)
227                         context->chunk_size += (2 - (context->chunk_size % 2));
228         
229         }
230         
231         if (context->chunk_id == TAG_icon) 
232         {
233                 GError *loader_error = NULL;
234                 guchar *data;
235                 guint32 towrite;
236
237                 if (context->loader == NULL) 
238                 {
239                         if (context->pos >= context->NumFrames) 
240                         {
241                                 g_set_error (error,
242                                              GDK_PIXBUF_ERROR,
243                                              GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
244                                              _("Unexpected icon chunk in animation"));
245                                 return FALSE; 
246                         }
247
248 #ifdef DEBUG_ANI
249                         g_print ("opening loader\n");
250 #endif
251                         context->loader = gdk_pixbuf_loader_new_with_type ("ico", &loader_error);
252                         if (loader_error) 
253                         {
254                                 g_propagate_error (error, loader_error);
255                                 return FALSE;
256                         }
257                         g_signal_connect (context->loader, "area_prepared",
258                                           G_CALLBACK (prepared_callback), context);
259                         g_signal_connect (context->loader, "area_updated",
260                                           G_CALLBACK (updated_callback), context);
261                 }
262
263                 towrite = MIN (context->chunk_size, BYTES_LEFT (context));
264                 data = context->byte;
265                 context->byte += towrite;
266                 context->cp += towrite;
267 #ifdef DEBUG_ANI
268                 g_print ("miss %d, get %d, leftover %d\n", context->chunk_size, towrite, BYTES_LEFT (context));
269 #endif
270                 context->chunk_size -= towrite;
271                 if (!gdk_pixbuf_loader_write (context->loader, data, towrite, &loader_error)) 
272                 {
273                         g_propagate_error (error, loader_error);
274                         gdk_pixbuf_loader_close (context->loader, NULL);
275                         g_object_unref (context->loader);
276                         context->loader = NULL;
277                         return FALSE; 
278                 }
279                 if (context->chunk_size == 0) 
280                 {
281 #ifdef DEBUG_ANI
282                         g_print ("closing loader\n");
283 #endif
284                         if (!gdk_pixbuf_loader_close (context->loader, &loader_error)) 
285                         {
286                                 g_propagate_error (error, loader_error);
287                                 g_object_unref (context->loader);
288                                 context->loader = NULL;
289                                 return FALSE;
290                         }
291                         g_object_unref (context->loader);
292                         context->loader = NULL;
293                         context->chunk_id = 0x0;
294                 }
295                 return BYTES_LEFT (context) > 0;
296         }
297
298         if (BYTES_LEFT (context) < context->chunk_size)
299                 return FALSE;
300         
301         if (context->chunk_id == TAG_anih) 
302         {
303                 context->HeaderSize = read_int32 (context);
304                 context->NumFrames = read_int32 (context);
305                 context->NumSteps = read_int32 (context);
306                 context->Width = read_int32 (context);
307                 context->Height = read_int32 (context);
308                 context->BitCount = read_int32 (context);
309                 context->NumPlanes = read_int32 (context);
310                 context->DisplayRate = read_int32 (context);
311                 context->Flags = read_int32 (context);
312                         
313 #ifdef DEBUG_ANI          
314                 g_print ("HeaderSize \t%" G_GUINT32_FORMAT
315                          "\nNumFrames \t%" G_GUINT32_FORMAT
316                          "\nNumSteps \t%" G_GUINT32_FORMAT
317                          "\nWidth    \t%" G_GUINT32_FORMAT
318                          "\nHeight   \t%" G_GUINT32_FORMAT
319                          "\nBitCount \t%" G_GUINT32_FORMAT
320                          "\nNumPlanes \t%" G_GUINT32_FORMAT
321                          "\nDisplayRate \t%" G_GUINT32_FORMAT
322                          "\nSequenceFlag \t%d"
323                          "\nIconFlag \t%d"
324                          "\n",
325                          context->HeaderSize, context->NumFrames, 
326                          context->NumSteps, context->Width, 
327                          context->Height, context->BitCount, 
328                          context->NumPlanes, context->DisplayRate, 
329                          (context->Flags & 0x2) != 0, 
330                          (context->Flags & 0x1) != 0);
331 #endif
332                 if (!context->Flags & 0x2) 
333                 {
334                         g_set_error (error,
335                                      GDK_PIXBUF_ERROR,
336                                      GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
337                                      _("Unsupported animation type"));
338                         return FALSE; 
339                 }
340                 if (context->NumFrames == 0 || 
341                     context->NumFrames >= 1024 || 
342                     context->NumSteps == 0 || 
343                     context->NumSteps >= 1024) 
344                 {
345                         g_set_error (error,
346                                      GDK_PIXBUF_ERROR,
347                                      GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
348                                      _("Invalid header in animation"));
349                         return FALSE;
350                 }
351       
352                 context->animation = g_object_new (GDK_TYPE_PIXBUF_ANI_ANIM, NULL);        
353                 if (!context->animation) 
354                 {
355                         g_set_error (error,
356                                      GDK_PIXBUF_ERROR,
357                                      GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
358                                      _("Not enough memory to load animation"));
359                         return FALSE;
360                 }
361
362                 context->animation->n_pixbufs = context->NumFrames;
363                 context->animation->n_frames = context->NumSteps;
364
365                 context->animation->total_time = context->NumSteps * (context->DisplayRate * 1000 / 60);
366                 context->animation->width = 0;
367                 context->animation->height = 0;
368
369                 context->animation->pixbufs = (GdkPixbuf**) g_try_malloc (sizeof (GdkPixbuf*) * context->NumFrames);
370                 if (context->animation->pixbufs) 
371                         memset (context->animation->pixbufs, 0, sizeof (GdkPixbuf*) * context->NumFrames);
372                                                 
373                 context->animation->delay = (guint32*) g_try_malloc (sizeof (guint32) * context->NumSteps);
374                 context->animation->sequence = (guint32*) g_try_malloc (sizeof (guint32) * context->NumSteps);
375                 if (!context->animation->pixbufs || 
376                     !context->animation->delay || 
377                     !context->animation->sequence) 
378                 {
379                         g_set_error (error,
380                                      GDK_PIXBUF_ERROR,
381                                      GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
382                                      _("Not enough memory to load animation"));
383                         return FALSE;
384                 }
385
386                 for (i = 0; i < context->NumSteps; i++) 
387                 {
388                         /* default values if the corresponding chunks are absent */
389                         context->animation->delay[i] = context->DisplayRate * 1000 / 60;
390                         context->animation->sequence[i] = MIN (i, context->NumFrames  - 1);       
391                 }
392         }
393         else if (context->chunk_id == TAG_rate) 
394         {
395                 if (context->chunk_size != 4 * context->NumSteps) 
396                 {
397                         g_set_error (error,
398                                      GDK_PIXBUF_ERROR,
399                                      GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
400                                      _("Malformed chunk in animation"));
401                         return FALSE; 
402                 }
403                 context->animation->total_time = 0;
404                 for (i = 0; i < context->NumSteps; i++) 
405                 {
406                         context->animation->delay[i] = read_int32 (context) * 1000 / 60;
407                         context->animation->total_time += context->animation->delay[i];
408                 }
409         }
410         else if (context->chunk_id == TAG_seq) 
411         {
412                 if (context->chunk_size != 4 * context->NumSteps) 
413                 {
414                         g_set_error (error,
415                                      GDK_PIXBUF_ERROR,
416                                      GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
417                                      _("Malformed chunk in animation"));
418                         return FALSE; 
419                 }
420                 for (i = 0; i < context->NumSteps; i++) 
421                 {
422                         context->animation->sequence[i] = read_int32 (context);
423                         if (context->animation->sequence[i] >= context->NumFrames) 
424                         {
425                                 g_set_error (error,
426                                              GDK_PIXBUF_ERROR,
427                                              GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
428                                              _("Malformed chunk in animation"));
429                                 return FALSE; 
430                         }
431                 }
432         }
433         else if (context->chunk_id == TAG_INAM) 
434         {
435                 context->title = g_try_malloc (context->chunk_size + 1);
436                 if (!context->title) 
437                 {
438                         g_set_error (error,
439                                      GDK_PIXBUF_ERROR,
440                                      GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
441                                      _("Not enough memory to load animation"));
442                         return FALSE;
443                 }
444                 context->title[context->chunk_size] = 0;
445                 read_int8 (context, context->title, context->chunk_size);
446 #ifdef DEBUG_ANI
447                 g_print ("INAM %s\n", context->title);
448 #endif
449                 for (i = 0; i < context->pos; i++)
450                         gdk_pixbuf_set_option (context->animation->pixbufs[i], "Title", context->title);                        
451         }
452         else if (context->chunk_id == TAG_IART) 
453         {
454                 context->author = g_try_malloc (context->chunk_size + 1);
455                 if (!context->author) 
456                 {
457                         g_set_error (error,
458                                      GDK_PIXBUF_ERROR,
459                                      GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
460                                      _("Not enough memory to load animation"));
461                         return FALSE;
462                 }
463                 context->author[context->chunk_size] = 0;
464                 read_int8 (context, context->author, context->chunk_size);
465 #ifdef DEBUG_ANI
466                 g_print ("IART %s\n", context->author);
467 #endif
468                 for (i = 0; i < context->pos; i++)
469                         gdk_pixbuf_set_option (context->animation->pixbufs[i], "Author", context->author);                      
470         }
471
472 #ifdef DEBUG_ANI
473         {
474                 gint32 dummy = lsb_32 ((guchar *)&context->chunk_id);
475         
476                 g_print ("Loaded chunk with ID '%c%c%c%c' and length %" G_GUINT32_FORMAT "\n",
477                          ((char*)&dummy)[0], ((char*)&dummy)[1],
478                          ((char*)&dummy)[2], ((char*)&dummy)[3], 
479                          context->chunk_size);
480         }
481 #endif 
482                 
483         context->chunk_id = 0x0;
484         return TRUE;
485 }
486
487 static gboolean
488 gdk_pixbuf__ani_image_load_increment (gpointer data,
489                                       const guchar *buf, guint size,
490                                       GError **error)
491 {
492         AniLoaderContext *context = (AniLoaderContext *)data;
493         
494         if (context->n_bytes + size >= context->buffer_size) {
495                 int drop = context->byte - context->buffer;
496                 memmove (context->buffer, context->byte, context->n_bytes - drop);
497                 context->n_bytes -= drop;
498                 context->byte = context->buffer;
499                 if (context->n_bytes + size >= context->buffer_size) {
500                         guchar *tmp;
501                         context->buffer_size = MAX (context->n_bytes + size, context->buffer_size + 4096);
502 #ifdef DEBUG_ANI
503                         g_print ("growing buffer to %" G_GUINT32_FORMAT "\n", context->buffer_size);
504 #endif
505                         tmp = g_try_realloc (context->buffer, context->buffer_size);
506                         if (!tmp) 
507                         {
508                                 g_set_error (error,
509                                              GDK_PIXBUF_ERROR,
510                                              GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
511                                              _("Not enough memory to load animation"));
512                                 return FALSE;
513                         }
514                         context->byte = context->buffer = tmp;
515                 }
516         }
517         memcpy (context->buffer + context->n_bytes, buf, size);
518         context->n_bytes += size;
519
520         if (context->data_size == 0) 
521         {
522                 guint32 riff_id, chunk_id;
523                         
524                 if (BYTES_LEFT (context) < 12)
525                         return TRUE;
526                         
527                 riff_id = read_int32 (context);
528                 context->data_size = read_int32 (context);
529                 chunk_id = read_int32 (context);
530                         
531                 if (riff_id != TAG_RIFF || 
532                     context->data_size == 0 || 
533                     chunk_id != TAG_ACON) 
534                 {
535                         g_set_error (error,
536                                      GDK_PIXBUF_ERROR,
537                                      GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
538                                      _("Invalid header in animation"));
539                         return FALSE; 
540                 }
541         }
542         
543         if (context->cp < context->data_size + 8) 
544         {
545                 GError *chunk_error = NULL;
546
547                 while (ani_load_chunk (context, &chunk_error)) ;
548                 if (chunk_error) 
549                 {
550                         g_propagate_error (error, chunk_error);
551                         return FALSE;
552                 }
553         }
554
555         return TRUE;
556 }
557
558 static gpointer
559 gdk_pixbuf__ani_image_begin_load (ModuleSizeFunc size_func, 
560                                   ModulePreparedNotifyFunc prepared_func, 
561                                   ModuleUpdatedNotifyFunc  updated_func,
562                                   gpointer user_data,
563                                   GError **error)
564 {
565         AniLoaderContext *context;
566         
567         context = g_new0 (AniLoaderContext, 1);
568         
569         context->prepared_func = prepared_func;
570         context->updated_func = updated_func;
571         context->user_data = user_data;
572         
573         context->pos = 0;
574         
575         context->buffer_size = 4096;
576         context->buffer = g_try_malloc (context->buffer_size);
577         if (!context->buffer) 
578         {
579                 context_free (context);
580                 g_set_error (error,
581                              GDK_PIXBUF_ERROR,
582                              GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
583                              _("Not enough memory to load animation"));
584                 return NULL;
585         }
586         
587         context->byte = context->buffer;
588         context->n_bytes = 0;
589         
590         return (gpointer) context;
591 }
592
593 static gboolean
594 gdk_pixbuf__ani_image_stop_load (gpointer data,
595                                  GError **error)
596 {
597         AniLoaderContext *context = (AniLoaderContext *) data;
598         
599         g_return_val_if_fail (context != NULL, TRUE);
600         context_free (context);
601         
602         return TRUE;
603 }
604
605 static void
606 prepared_notify (GdkPixbuf *pixbuf, 
607                  GdkPixbufAnimation *anim, 
608                  gpointer user_data)
609 {
610         if (anim != NULL)
611                 g_object_ref (anim);
612         *((GdkPixbufAnimation **)user_data) = anim;
613 }
614
615 static GdkPixbufAnimation *
616 gdk_pixbuf__ani_image_load_animation (FILE *f, GError **error)
617 {
618         guchar buffer[4096];
619         size_t length;
620         GdkPixbufAnimation *anim = NULL;
621         gpointer context;
622
623         context = gdk_pixbuf__ani_image_begin_load (NULL, prepared_notify, 
624                                                     NULL, &anim, error);
625         
626         if (!context)
627                 return NULL;
628         
629         while (!feof (f)) {
630                 length = fread (buffer, 1, sizeof (buffer), f);
631                 if (length > 0)
632                         if (!gdk_pixbuf__ani_image_load_increment (context, buffer, length, error)) {
633                                 gdk_pixbuf__ani_image_stop_load (context, NULL);
634                                 if (anim != NULL)
635                                         g_object_unref (anim);
636                                 return NULL;
637                         }
638         }
639
640         if (!gdk_pixbuf__ani_image_stop_load (context, error)) {
641                 if (anim != NULL)
642                         g_object_unref (anim);
643                 return NULL;
644         }
645         
646         return anim;
647 }
648
649 void
650 gdk_pixbuf__ani_fill_vtable (GdkPixbufModule *module)
651 {
652         module->load_animation = gdk_pixbuf__ani_image_load_animation;
653         module->begin_load = gdk_pixbuf__ani_image_begin_load;
654         module->stop_load = gdk_pixbuf__ani_image_stop_load;
655         module->load_increment = gdk_pixbuf__ani_image_load_increment;
656 }