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