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 |
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 ; |
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 |
93 | read_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 | |
104 | static guint32 |
105 | read_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 | |
113 | static void |
114 | context_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 | |
133 | static void |
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, 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 | |
182 | static void |
183 | updated_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 | |
196 | static gboolean |
197 | ani_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 | |
507 | static gboolean |
508 | gdk_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 | |
578 | static gpointer |
579 | gdk_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 | |
617 | static gboolean |
618 | gdk_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 | |
646 | MODULE_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 | |
653 | MODULE_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 | |