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