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

source code of gtk/subprojects/gdk-pixbuf/gdk-pixbuf/io-ani.c