1 | /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */ |
2 | /* GdkPixbuf library - Simple animation support |
3 | * |
4 | * Copyright (C) 1999 The Free Software Foundation |
5 | * |
6 | * Authors: Jonathan Blandford <jrb@redhat.com> |
7 | * Havoc Pennington <hp@redhat.com> |
8 | * |
9 | * This library is free software; you can redistribute it and/or |
10 | * modify it under the terms of the GNU Lesser General Public |
11 | * License as published by the Free Software Foundation; either |
12 | * version 2 of the License, or (at your option) any later version. |
13 | * |
14 | * This library is distributed in the hope that it will be useful, |
15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
17 | * Lesser General Public License for more details. |
18 | * |
19 | * You should have received a copy of the GNU Lesser General Public |
20 | * License along with this library; if not, see <http://www.gnu.org/licenses/>. |
21 | */ |
22 | |
23 | #include "config.h" |
24 | #define GLIB_DISABLE_DEPRECATION_WARNINGS |
25 | #include <errno.h> |
26 | #include "gdk-pixbuf-private.h" |
27 | #include "gdk-pixbuf-animation.h" |
28 | #include "gdk-pixbuf-loader.h" |
29 | |
30 | #include <glib/gstdio.h> |
31 | |
32 | /** |
33 | * GdkPixbufAnimation: |
34 | * |
35 | * An opaque object representing an animation. |
36 | * |
37 | * The GdkPixBuf library provides a simple mechanism to load and |
38 | * represent animations. An animation is conceptually a series of |
39 | * frames to be displayed over time. |
40 | * |
41 | * The animation may not be represented as a series of frames |
42 | * internally; for example, it may be stored as a sprite and |
43 | * instructions for moving the sprite around a background. |
44 | * |
45 | * To display an animation you don't need to understand its |
46 | * representation, however; you just ask `GdkPixbuf` what should |
47 | * be displayed at a given point in time. |
48 | */ |
49 | |
50 | /** |
51 | * GdkPixbufAnimationIter: |
52 | * |
53 | * An opaque object representing an iterator which points to a |
54 | * certain position in an animation. |
55 | */ |
56 | |
57 | typedef struct _GdkPixbufNonAnim GdkPixbufNonAnim; |
58 | typedef struct _GdkPixbufNonAnimClass GdkPixbufNonAnimClass; |
59 | |
60 | #define GDK_TYPE_PIXBUF_NON_ANIM (gdk_pixbuf_non_anim_get_type ()) |
61 | #define GDK_PIXBUF_NON_ANIM(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), GDK_TYPE_PIXBUF_NON_ANIM, GdkPixbufNonAnim)) |
62 | #define GDK_IS_PIXBUF_NON_ANIM(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), GDK_TYPE_PIXBUF_NON_ANIM)) |
63 | |
64 | #define GDK_PIXBUF_NON_ANIM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GDK_TYPE_PIXBUF_NON_ANIM, GdkPixbufNonAnimClass)) |
65 | #define GDK_IS_PIXBUF_NON_ANIM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GDK_TYPE_PIXBUF_NON_ANIM)) |
66 | #define GDK_PIXBUF_NON_ANIM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GDK_TYPE_PIXBUF_NON_ANIM, GdkPixbufNonAnimClass)) |
67 | |
68 | /* Private part of the GdkPixbufNonAnim structure */ |
69 | struct _GdkPixbufNonAnim { |
70 | GdkPixbufAnimation parent_instance; |
71 | |
72 | GdkPixbuf *pixbuf; |
73 | }; |
74 | |
75 | struct _GdkPixbufNonAnimClass { |
76 | GdkPixbufAnimationClass parent_class; |
77 | }; |
78 | |
79 | |
80 | typedef struct _GdkPixbufNonAnimIter GdkPixbufNonAnimIter; |
81 | typedef struct _GdkPixbufNonAnimIterClass GdkPixbufNonAnimIterClass; |
82 | |
83 | |
84 | #define GDK_TYPE_PIXBUF_NON_ANIM_ITER (gdk_pixbuf_non_anim_iter_get_type ()) |
85 | #define GDK_PIXBUF_NON_ANIM_ITER(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), GDK_TYPE_PIXBUF_NON_ANIM_ITER, GdkPixbufNonAnimIter)) |
86 | #define GDK_IS_PIXBUF_NON_ANIM_ITER(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), GDK_TYPE_PIXBUF_NON_ANIM_ITER)) |
87 | |
88 | #define GDK_PIXBUF_NON_ANIM_ITER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GDK_TYPE_PIXBUF_NON_ANIM_ITER, GdkPixbufNonAnimIterClass)) |
89 | #define GDK_IS_PIXBUF_NON_ANIM_ITER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GDK_TYPE_PIXBUF_NON_ANIM_ITER)) |
90 | #define GDK_PIXBUF_NON_ANIM_ITER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GDK_TYPE_PIXBUF_NON_ANIM_ITER, GdkPixbufNonAnimIterClass)) |
91 | |
92 | struct _GdkPixbufNonAnimIter { |
93 | GdkPixbufAnimationIter parent_instance; |
94 | |
95 | GdkPixbufNonAnim *non_anim; |
96 | }; |
97 | |
98 | struct _GdkPixbufNonAnimIterClass { |
99 | GdkPixbufAnimationIterClass parent_class; |
100 | |
101 | }; |
102 | |
103 | static GType gdk_pixbuf_non_anim_iter_get_type (void) G_GNUC_CONST; |
104 | |
105 | G_DEFINE_TYPE (GdkPixbufAnimation, gdk_pixbuf_animation, G_TYPE_OBJECT); |
106 | |
107 | static void |
108 | gdk_pixbuf_animation_class_init (GdkPixbufAnimationClass *klass) |
109 | { |
110 | } |
111 | |
112 | static void |
113 | gdk_pixbuf_animation_init (GdkPixbufAnimation *animation) |
114 | { |
115 | } |
116 | |
117 | static void |
118 | noop_size_notify (gint *width, |
119 | gint *height, |
120 | gpointer data) |
121 | { |
122 | } |
123 | |
124 | static void |
125 | prepared_notify (GdkPixbuf *pixbuf, |
126 | GdkPixbufAnimation *anim, |
127 | gpointer user_data) |
128 | { |
129 | if (anim != NULL) |
130 | g_object_ref (anim); |
131 | else |
132 | anim = gdk_pixbuf_non_anim_new (pixbuf); |
133 | |
134 | *((GdkPixbufAnimation **)user_data) = anim; |
135 | } |
136 | |
137 | static void |
138 | noop_updated_notify (GdkPixbuf *pixbuf, |
139 | int x, |
140 | int y, |
141 | int width, |
142 | int height, |
143 | gpointer user_data) |
144 | { |
145 | } |
146 | |
147 | /** |
148 | * gdk_pixbuf_animation_new_from_file: |
149 | * @filename: (type filename): Name of file to load, in the GLib file |
150 | * name encoding |
151 | * @error: return location for error |
152 | * |
153 | * Creates a new animation by loading it from a file. |
154 | * |
155 | * The file format is detected automatically. |
156 | * |
157 | * If the file's format does not support multi-frame images, then an animation |
158 | * with a single frame will be created. |
159 | * |
160 | * Possible errors are in the `GDK_PIXBUF_ERROR` and `G_FILE_ERROR` domains. |
161 | * |
162 | * Return value: (transfer full) (nullable): A newly-created animation |
163 | */ |
164 | GdkPixbufAnimation * |
165 | gdk_pixbuf_animation_new_from_file (const gchar *filename, |
166 | GError **error) |
167 | { |
168 | GdkPixbufAnimation *animation; |
169 | int size; |
170 | FILE *f; |
171 | guchar buffer[SNIFF_BUFFER_SIZE]; |
172 | GdkPixbufModule *image_module; |
173 | gchar *display_name; |
174 | |
175 | g_return_val_if_fail (filename != NULL, NULL); |
176 | g_return_val_if_fail (error == NULL || *error == NULL, NULL); |
177 | |
178 | display_name = g_filename_display_name (filename); |
179 | f = g_fopen (filename: filename, modes: "rb" ); |
180 | if (!f) { |
181 | gint save_errno = errno; |
182 | g_set_error (err: error, |
183 | G_FILE_ERROR, |
184 | code: g_file_error_from_errno (err_no: save_errno), |
185 | _("Failed to open file “%s”: %s" ), |
186 | display_name, |
187 | g_strerror (errnum: save_errno)); |
188 | g_free (mem: display_name); |
189 | return NULL; |
190 | } |
191 | |
192 | size = fread (ptr: &buffer, size: 1, n: sizeof (buffer), stream: f); |
193 | |
194 | if (size == 0) { |
195 | g_set_error (err: error, |
196 | GDK_PIXBUF_ERROR, |
197 | code: GDK_PIXBUF_ERROR_CORRUPT_IMAGE, |
198 | _("Image file “%s” contains no data" ), |
199 | display_name); |
200 | g_free (mem: display_name); |
201 | fclose (stream: f); |
202 | return NULL; |
203 | } |
204 | |
205 | image_module = _gdk_pixbuf_get_module (buffer, size, filename, error); |
206 | if (!image_module) { |
207 | g_free (mem: display_name); |
208 | fclose (stream: f); |
209 | return NULL; |
210 | } |
211 | |
212 | if (image_module->module == NULL) |
213 | if (!_gdk_pixbuf_load_module (image_module, error)) { |
214 | g_free (mem: display_name); |
215 | fclose (stream: f); |
216 | return NULL; |
217 | } |
218 | |
219 | if (image_module->load_animation != NULL) { |
220 | fseek (stream: f, off: 0, SEEK_SET); |
221 | animation = (* image_module->load_animation) (f, error); |
222 | |
223 | if (animation == NULL && error != NULL && *error == NULL) { |
224 | /* I don't trust these crufty longjmp()'ing |
225 | * image libs to maintain proper error |
226 | * invariants, and I don't want user code to |
227 | * segfault as a result. We need to maintain |
228 | * the invariant that error gets set if NULL |
229 | * is returned. |
230 | */ |
231 | g_warning ("Bug! gdk-pixbuf loader '%s' didn't set an error on failure." , |
232 | image_module->module_name); |
233 | g_set_error (err: error, |
234 | GDK_PIXBUF_ERROR, |
235 | code: GDK_PIXBUF_ERROR_FAILED, |
236 | _("Failed to load animation “%s”: reason not known, probably a corrupt animation file" ), |
237 | display_name); |
238 | } |
239 | |
240 | fclose (stream: f); |
241 | } else if (image_module->begin_load != NULL) { |
242 | guchar buffer[4096]; |
243 | size_t length; |
244 | gpointer context; |
245 | gboolean success; |
246 | |
247 | success = FALSE; |
248 | animation = NULL; |
249 | fseek (stream: f, off: 0, SEEK_SET); |
250 | |
251 | context = image_module->begin_load (noop_size_notify, prepared_notify, noop_updated_notify, &animation, error); |
252 | if (!context) |
253 | goto fail_begin_load; |
254 | |
255 | while (!feof (stream: f) && !ferror (stream: f)) { |
256 | length = fread (ptr: buffer, size: 1, n: sizeof (buffer), stream: f); |
257 | if (length > 0) { |
258 | if (!image_module->load_increment (context, buffer, length, error)) { |
259 | error = NULL; |
260 | goto fail_load_increment; |
261 | } |
262 | } |
263 | } |
264 | |
265 | success = TRUE; |
266 | |
267 | fail_load_increment: |
268 | if (!image_module->stop_load (context, error)) |
269 | success = FALSE; |
270 | |
271 | fail_begin_load: |
272 | fclose (stream: f); |
273 | |
274 | if (success) { |
275 | /* If there was no error, there must be an animation that was successfully loaded */ |
276 | g_assert (animation); |
277 | } else { |
278 | if (animation) { |
279 | g_object_unref (object: animation); |
280 | animation = NULL; |
281 | } |
282 | } |
283 | } else { |
284 | GdkPixbuf *pixbuf; |
285 | |
286 | /* Keep this logic in sync with gdk_pixbuf_new_from_file() */ |
287 | |
288 | fseek (stream: f, off: 0, SEEK_SET); |
289 | pixbuf = _gdk_pixbuf_generic_image_load (image_module, f, error); |
290 | fclose (stream: f); |
291 | |
292 | if (pixbuf == NULL && error != NULL && *error == NULL) { |
293 | /* I don't trust these crufty longjmp()'ing image libs |
294 | * to maintain proper error invariants, and I don't |
295 | * want user code to segfault as a result. We need to maintain |
296 | * the invariant that error gets set if NULL is returned. |
297 | */ |
298 | |
299 | g_warning ("Bug! gdk-pixbuf loader '%s' didn't set an error on failure." , |
300 | image_module->module_name); |
301 | g_set_error (err: error, |
302 | GDK_PIXBUF_ERROR, |
303 | code: GDK_PIXBUF_ERROR_FAILED, |
304 | _("Failed to load image “%s”: reason not known, probably a corrupt image file" ), |
305 | display_name); |
306 | } |
307 | |
308 | if (pixbuf == NULL) { |
309 | g_free (mem: display_name); |
310 | animation = NULL; |
311 | goto out; |
312 | } |
313 | |
314 | animation = gdk_pixbuf_non_anim_new (pixbuf); |
315 | |
316 | g_object_unref (object: pixbuf); |
317 | } |
318 | |
319 | g_free (mem: display_name); |
320 | |
321 | out: |
322 | return animation; |
323 | } |
324 | |
325 | #ifdef G_OS_WIN32 |
326 | /** |
327 | * gdk_pixbuf_animation_new_from_file_utf8: |
328 | * @filename: (type filename): Name of file to load, in the GLib file name encoding |
329 | * @error: return location for error |
330 | * |
331 | * Same as gdk_pixbuf_animation_new_from_file() |
332 | * |
333 | * Return value: A newly-created animation with a reference count of 1, or `NULL` |
334 | * if any of several error conditions ocurred: the file could not be opened, |
335 | * there was no loader for the file's format, there was not enough memory to |
336 | * allocate the image buffer, or the image file contained invalid data. |
337 | */ |
338 | GdkPixbufAnimation * |
339 | gdk_pixbuf_animation_new_from_file_utf8 (const gchar *filename, |
340 | GError **error) |
341 | { |
342 | return gdk_pixbuf_animation_new_from_file (filename, error); |
343 | } |
344 | #endif |
345 | |
346 | /** |
347 | * gdk_pixbuf_animation_new_from_stream: |
348 | * @stream: a `GInputStream` to load the pixbuf from |
349 | * @cancellable: (nullable): optional `GCancellable` object |
350 | * @error: Return location for an error |
351 | * |
352 | * Creates a new animation by loading it from an input stream. |
353 | * |
354 | * The file format is detected automatically. |
355 | * |
356 | * If `NULL` is returned, then @error will be set. |
357 | * |
358 | * The @cancellable can be used to abort the operation from another thread. |
359 | * If the operation was cancelled, the error `G_IO_ERROR_CANCELLED` will be |
360 | * returned. Other possible errors are in the `GDK_PIXBUF_ERROR` and |
361 | * `G_IO_ERROR` domains. |
362 | * |
363 | * The stream is not closed. |
364 | * |
365 | * Return value: (transfer full) (nullable): A newly-created animation |
366 | * |
367 | * Since: 2.28 |
368 | */ |
369 | GdkPixbufAnimation * |
370 | gdk_pixbuf_animation_new_from_stream (GInputStream *stream, |
371 | GCancellable *cancellable, |
372 | GError **error) |
373 | { |
374 | GdkPixbufAnimation *animation; |
375 | GdkPixbufLoader *loader; |
376 | gssize n_read; |
377 | guchar buffer[LOAD_BUFFER_SIZE]; |
378 | gboolean res; |
379 | |
380 | g_return_val_if_fail (G_IS_INPUT_STREAM (stream), NULL); |
381 | g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL); |
382 | g_return_val_if_fail (error == NULL || *error == NULL, NULL); |
383 | |
384 | loader = gdk_pixbuf_loader_new (); |
385 | |
386 | res = TRUE; |
387 | while (1) { |
388 | n_read = g_input_stream_read (stream, buffer, count: sizeof (buffer), cancellable, error); |
389 | if (n_read < 0) { |
390 | res = FALSE; |
391 | error = NULL; /* Ignore further errors */ |
392 | break; |
393 | } |
394 | |
395 | if (n_read == 0) |
396 | break; |
397 | |
398 | if (!gdk_pixbuf_loader_write (loader, buf: buffer, count: n_read, error)) { |
399 | res = FALSE; |
400 | error = NULL; |
401 | break; |
402 | } |
403 | } |
404 | |
405 | if (!gdk_pixbuf_loader_close (loader, error)) { |
406 | res = FALSE; |
407 | error = NULL; |
408 | } |
409 | |
410 | if (res) { |
411 | animation = gdk_pixbuf_loader_get_animation (loader); |
412 | if (animation) |
413 | g_object_ref (animation); |
414 | } else { |
415 | animation = NULL; |
416 | } |
417 | |
418 | g_object_unref (object: loader); |
419 | |
420 | return animation; |
421 | } |
422 | |
423 | static void |
424 | animation_new_from_stream_thread (GTask *task, |
425 | gpointer source_object, |
426 | gpointer task_data, |
427 | GCancellable *cancellable) |
428 | { |
429 | GInputStream *stream = G_INPUT_STREAM (source_object); |
430 | GdkPixbufAnimation *animation; |
431 | GError *error = NULL; |
432 | |
433 | animation = gdk_pixbuf_animation_new_from_stream (stream, cancellable, error: &error); |
434 | |
435 | /* Set the new pixbuf as the result, or error out */ |
436 | if (animation == NULL) { |
437 | g_task_return_error (task, error); |
438 | } else { |
439 | g_task_return_pointer (task, result: animation, result_destroy: g_object_unref); |
440 | } |
441 | } |
442 | |
443 | /** |
444 | * gdk_pixbuf_animation_new_from_stream_async: |
445 | * @stream: a #GInputStream from which to load the animation |
446 | * @cancellable: (nullable): optional #GCancellable object |
447 | * @callback: a `GAsyncReadyCallback` to call when the pixbuf is loaded |
448 | * @user_data: the data to pass to the callback function |
449 | * |
450 | * Creates a new animation by asynchronously loading an image from an input stream. |
451 | * |
452 | * For more details see gdk_pixbuf_new_from_stream(), which is the synchronous |
453 | * version of this function. |
454 | * |
455 | * When the operation is finished, `callback` will be called in the main thread. |
456 | * You can then call gdk_pixbuf_animation_new_from_stream_finish() to get the |
457 | * result of the operation. |
458 | * |
459 | * Since: 2.28 |
460 | **/ |
461 | void |
462 | gdk_pixbuf_animation_new_from_stream_async (GInputStream *stream, |
463 | GCancellable *cancellable, |
464 | GAsyncReadyCallback callback, |
465 | gpointer user_data) |
466 | { |
467 | GTask *task; |
468 | |
469 | g_return_if_fail (G_IS_INPUT_STREAM (stream)); |
470 | g_return_if_fail (callback != NULL); |
471 | g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); |
472 | |
473 | task = g_task_new (G_OBJECT (stream), cancellable, callback, callback_data: user_data); |
474 | g_task_set_source_tag (task, gdk_pixbuf_animation_new_from_stream_async); |
475 | g_task_run_in_thread (task, task_func: animation_new_from_stream_thread); |
476 | g_object_unref (object: task); |
477 | } |
478 | |
479 | /** |
480 | * gdk_pixbuf_animation_new_from_stream_finish: |
481 | * @async_result: a #GAsyncResult |
482 | * @error: a #GError, or `NULL` |
483 | * |
484 | * Finishes an asynchronous pixbuf animation creation operation started with |
485 | * [func@GdkPixbuf.PixbufAnimation.new_from_stream_async]. |
486 | * |
487 | * Return value: (transfer full) (nullable): the newly created animation |
488 | * |
489 | * Since: 2.28 |
490 | **/ |
491 | GdkPixbufAnimation * |
492 | gdk_pixbuf_animation_new_from_stream_finish (GAsyncResult *async_result, |
493 | GError **error) |
494 | { |
495 | GTask *task = G_TASK (async_result); |
496 | |
497 | g_return_val_if_fail (G_IS_TASK (async_result), NULL); |
498 | g_return_val_if_fail (!error || (error && !*error), NULL); |
499 | g_warn_if_fail (g_task_get_source_tag (task) == gdk_pixbuf_animation_new_from_stream_async); |
500 | |
501 | return g_task_propagate_pointer (task, error); |
502 | } |
503 | |
504 | /** |
505 | * gdk_pixbuf_animation_new_from_resource: |
506 | * @resource_path: the path of the resource file |
507 | * @error: Return location for an error |
508 | * |
509 | * Creates a new pixbuf animation by loading an image from an resource. |
510 | * |
511 | * The file format is detected automatically. If `NULL` is returned, then |
512 | * @error will be set. |
513 | * |
514 | * Return value: (transfer full) (nullable): A newly-created animation |
515 | * |
516 | * Since: 2.28 |
517 | */ |
518 | GdkPixbufAnimation * |
519 | gdk_pixbuf_animation_new_from_resource (const gchar *resource_path, |
520 | GError **error) |
521 | { |
522 | GInputStream *stream; |
523 | GdkPixbufAnimation *anim; |
524 | GdkPixbuf *pixbuf; |
525 | |
526 | pixbuf = _gdk_pixbuf_new_from_resource_try_pixdata (resource_path); |
527 | if (pixbuf) { |
528 | anim = gdk_pixbuf_non_anim_new (pixbuf); |
529 | g_object_unref (object: pixbuf); |
530 | return anim; |
531 | } |
532 | |
533 | stream = g_resources_open_stream (path: resource_path, lookup_flags: 0, error); |
534 | if (stream == NULL) |
535 | return NULL; |
536 | |
537 | anim = gdk_pixbuf_animation_new_from_stream (stream, NULL, error); |
538 | g_object_unref (object: stream); |
539 | return anim; |
540 | } |
541 | |
542 | /** |
543 | * gdk_pixbuf_animation_ref: (skip) |
544 | * @animation: An animation. |
545 | * |
546 | * Adds a reference to an animation. |
547 | * |
548 | * Return value: The same as the @animation argument. |
549 | * |
550 | * Deprecated: 2.0: Use g_object_ref(). |
551 | */ |
552 | GdkPixbufAnimation * |
553 | gdk_pixbuf_animation_ref (GdkPixbufAnimation *animation) |
554 | { |
555 | return (GdkPixbufAnimation*) g_object_ref (animation); |
556 | } |
557 | |
558 | /** |
559 | * gdk_pixbuf_animation_unref: (skip) |
560 | * @animation: An animation. |
561 | * |
562 | * Removes a reference from an animation. |
563 | * |
564 | * Deprecated: 2.0: Use g_object_unref(). |
565 | */ |
566 | void |
567 | gdk_pixbuf_animation_unref (GdkPixbufAnimation *animation) |
568 | { |
569 | g_object_unref (object: animation); |
570 | } |
571 | |
572 | /** |
573 | * gdk_pixbuf_animation_is_static_image: |
574 | * @animation: a #GdkPixbufAnimation |
575 | * |
576 | * Checks whether the animation is a static image. |
577 | * |
578 | * If you load a file with gdk_pixbuf_animation_new_from_file() and it |
579 | * turns out to be a plain, unanimated image, then this function will |
580 | * return `TRUE`. Use gdk_pixbuf_animation_get_static_image() to retrieve |
581 | * the image. |
582 | * |
583 | * Return value: `TRUE` if the "animation" was really just an image |
584 | */ |
585 | gboolean |
586 | gdk_pixbuf_animation_is_static_image (GdkPixbufAnimation *animation) |
587 | { |
588 | g_return_val_if_fail (GDK_IS_PIXBUF_ANIMATION (animation), FALSE); |
589 | |
590 | return GDK_PIXBUF_ANIMATION_GET_CLASS (animation)->is_static_image (animation); |
591 | } |
592 | |
593 | /** |
594 | * gdk_pixbuf_animation_get_static_image: |
595 | * @animation: a #GdkPixbufAnimation |
596 | * |
597 | * Retrieves a static image for the animation. |
598 | * |
599 | * If an animation is really just a plain image (has only one frame), |
600 | * this function returns that image. |
601 | * |
602 | * If the animation is an animation, this function returns a reasonable |
603 | * image to use as a static unanimated image, which might be the first |
604 | * frame, or something more sophisticated depending on the file format. |
605 | * |
606 | * If an animation hasn't loaded any frames yet, this function will |
607 | * return `NULL`. |
608 | * |
609 | * Return value: (transfer none): unanimated image representing the animation |
610 | */ |
611 | GdkPixbuf* |
612 | gdk_pixbuf_animation_get_static_image (GdkPixbufAnimation *animation) |
613 | { |
614 | g_return_val_if_fail (GDK_IS_PIXBUF_ANIMATION (animation), NULL); |
615 | |
616 | return GDK_PIXBUF_ANIMATION_GET_CLASS (animation)->get_static_image (animation); |
617 | } |
618 | |
619 | /** |
620 | * gdk_pixbuf_animation_get_width: |
621 | * @animation: An animation. |
622 | * |
623 | * Queries the width of the bounding box of a pixbuf animation. |
624 | * |
625 | * Return value: Width of the bounding box of the animation. |
626 | */ |
627 | gint |
628 | gdk_pixbuf_animation_get_width (GdkPixbufAnimation *animation) |
629 | { |
630 | gint width; |
631 | |
632 | g_return_val_if_fail (GDK_IS_PIXBUF_ANIMATION (animation), 0); |
633 | |
634 | width = 0; |
635 | GDK_PIXBUF_ANIMATION_GET_CLASS (animation)->get_size (animation, |
636 | &width, NULL); |
637 | |
638 | return width; |
639 | } |
640 | |
641 | /** |
642 | * gdk_pixbuf_animation_get_height: |
643 | * @animation: An animation. |
644 | * |
645 | * Queries the height of the bounding box of a pixbuf animation. |
646 | * |
647 | * Return value: Height of the bounding box of the animation. |
648 | */ |
649 | gint |
650 | gdk_pixbuf_animation_get_height (GdkPixbufAnimation *animation) |
651 | { |
652 | gint height; |
653 | |
654 | g_return_val_if_fail (GDK_IS_PIXBUF_ANIMATION (animation), 0); |
655 | |
656 | height = 0; |
657 | GDK_PIXBUF_ANIMATION_GET_CLASS (animation)->get_size (animation, |
658 | NULL, &height); |
659 | |
660 | return height; |
661 | } |
662 | |
663 | |
664 | /** |
665 | * gdk_pixbuf_animation_get_iter: |
666 | * @animation: a #GdkPixbufAnimation |
667 | * @start_time: (allow-none): time when the animation starts playing |
668 | * |
669 | * Get an iterator for displaying an animation. |
670 | * |
671 | * The iterator provides the frames that should be displayed at a |
672 | * given time. |
673 | * |
674 | * @start_time would normally come from g_get_current_time(), and marks |
675 | * the beginning of animation playback. After creating an iterator, you |
676 | * should immediately display the pixbuf returned by |
677 | * gdk_pixbuf_animation_iter_get_pixbuf(). Then, you should install |
678 | * a timeout (with g_timeout_add()) or by some other mechanism ensure |
679 | * that you'll update the image after |
680 | * gdk_pixbuf_animation_iter_get_delay_time() milliseconds. Each time |
681 | * the image is updated, you should reinstall the timeout with the new, |
682 | * possibly-changed delay time. |
683 | * |
684 | * As a shortcut, if @start_time is `NULL`, the result of |
685 | * g_get_current_time() will be used automatically. |
686 | * |
687 | * To update the image (i.e. possibly change the result of |
688 | * gdk_pixbuf_animation_iter_get_pixbuf() to a new frame of the animation), |
689 | * call gdk_pixbuf_animation_iter_advance(). |
690 | * |
691 | * If you're using #GdkPixbufLoader, in addition to updating the image |
692 | * after the delay time, you should also update it whenever you |
693 | * receive the area_updated signal and |
694 | * gdk_pixbuf_animation_iter_on_currently_loading_frame() returns |
695 | * `TRUE`. In this case, the frame currently being fed into the loader |
696 | * has received new data, so needs to be refreshed. The delay time for |
697 | * a frame may also be modified after an area_updated signal, for |
698 | * example if the delay time for a frame is encoded in the data after |
699 | * the frame itself. So your timeout should be reinstalled after any |
700 | * area_updated signal. |
701 | * |
702 | * A delay time of -1 is possible, indicating "infinite". |
703 | * |
704 | * Return value: (transfer full): an iterator to move over the animation |
705 | */ |
706 | GdkPixbufAnimationIter* |
707 | gdk_pixbuf_animation_get_iter (GdkPixbufAnimation *animation, |
708 | const GTimeVal *start_time) |
709 | { |
710 | GTimeVal val; |
711 | |
712 | g_return_val_if_fail (GDK_IS_PIXBUF_ANIMATION (animation), NULL); |
713 | |
714 | |
715 | if (start_time) |
716 | val = *start_time; |
717 | else |
718 | g_get_current_time (result: &val); |
719 | |
720 | return GDK_PIXBUF_ANIMATION_GET_CLASS (animation)->get_iter (animation, &val); |
721 | } |
722 | |
723 | G_DEFINE_TYPE (GdkPixbufAnimationIter, gdk_pixbuf_animation_iter, G_TYPE_OBJECT); |
724 | |
725 | static void |
726 | gdk_pixbuf_animation_iter_class_init (GdkPixbufAnimationIterClass *klass) |
727 | { |
728 | } |
729 | |
730 | static void |
731 | gdk_pixbuf_animation_iter_init (GdkPixbufAnimationIter *iter) |
732 | { |
733 | } |
734 | |
735 | /** |
736 | * gdk_pixbuf_animation_iter_get_delay_time: |
737 | * @iter: an animation iterator |
738 | * |
739 | * Gets the number of milliseconds the current pixbuf should be displayed, |
740 | * or -1 if the current pixbuf should be displayed forever. |
741 | * |
742 | * The `g_timeout_add()` function conveniently takes a timeout in milliseconds, |
743 | * so you can use a timeout to schedule the next update. |
744 | * |
745 | * Note that some formats, like GIF, might clamp the timeout values in the |
746 | * image file to avoid updates that are just too quick. The minimum timeout |
747 | * for GIF images is currently 20 milliseconds. |
748 | * |
749 | * Return value: delay time in milliseconds (thousandths of a second) |
750 | */ |
751 | gint |
752 | gdk_pixbuf_animation_iter_get_delay_time (GdkPixbufAnimationIter *iter) |
753 | { |
754 | g_return_val_if_fail (GDK_IS_PIXBUF_ANIMATION_ITER (iter), -1); |
755 | g_return_val_if_fail (GDK_PIXBUF_ANIMATION_ITER_GET_CLASS (iter)->get_delay_time, -1); |
756 | |
757 | return GDK_PIXBUF_ANIMATION_ITER_GET_CLASS (iter)->get_delay_time (iter); |
758 | } |
759 | |
760 | /** |
761 | * gdk_pixbuf_animation_iter_get_pixbuf: |
762 | * @iter: an animation iterator |
763 | * |
764 | * Gets the current pixbuf which should be displayed. |
765 | * |
766 | * The pixbuf might not be the same size as the animation itself |
767 | * (gdk_pixbuf_animation_get_width(), gdk_pixbuf_animation_get_height()). |
768 | * |
769 | * This pixbuf should be displayed for gdk_pixbuf_animation_iter_get_delay_time() |
770 | * milliseconds. |
771 | * |
772 | * The caller of this function does not own a reference to the returned |
773 | * pixbuf; the returned pixbuf will become invalid when the iterator |
774 | * advances to the next frame, which may happen anytime you call |
775 | * gdk_pixbuf_animation_iter_advance(). |
776 | * |
777 | * Copy the pixbuf to keep it (don't just add a reference), as it may get |
778 | * recycled as you advance the iterator. |
779 | * |
780 | * Return value: (transfer none): the pixbuf to be displayed |
781 | */ |
782 | GdkPixbuf* |
783 | gdk_pixbuf_animation_iter_get_pixbuf (GdkPixbufAnimationIter *iter) |
784 | { |
785 | g_return_val_if_fail (GDK_IS_PIXBUF_ANIMATION_ITER (iter), NULL); |
786 | g_return_val_if_fail (GDK_PIXBUF_ANIMATION_ITER_GET_CLASS (iter)->get_pixbuf, NULL); |
787 | |
788 | return GDK_PIXBUF_ANIMATION_ITER_GET_CLASS (iter)->get_pixbuf (iter); |
789 | } |
790 | |
791 | /** |
792 | * gdk_pixbuf_animation_iter_on_currently_loading_frame: |
793 | * @iter: a #GdkPixbufAnimationIter |
794 | * |
795 | * Used to determine how to respond to the area_updated signal on |
796 | * #GdkPixbufLoader when loading an animation. |
797 | * |
798 | * The `::area_updated` signal is emitted for an area of the frame currently |
799 | * streaming in to the loader. So if you're on the currently loading frame, |
800 | * you will need to redraw the screen for the updated area. |
801 | * |
802 | * Return value: `TRUE` if the frame we're on is partially loaded, or the last frame |
803 | */ |
804 | gboolean |
805 | gdk_pixbuf_animation_iter_on_currently_loading_frame (GdkPixbufAnimationIter *iter) |
806 | { |
807 | g_return_val_if_fail (GDK_IS_PIXBUF_ANIMATION_ITER (iter), FALSE); |
808 | g_return_val_if_fail (GDK_PIXBUF_ANIMATION_ITER_GET_CLASS (iter)->on_currently_loading_frame, FALSE); |
809 | |
810 | return GDK_PIXBUF_ANIMATION_ITER_GET_CLASS (iter)->on_currently_loading_frame (iter); |
811 | } |
812 | |
813 | /** |
814 | * gdk_pixbuf_animation_iter_advance: |
815 | * @iter: a #GdkPixbufAnimationIter |
816 | * @current_time: (allow-none): current time |
817 | * |
818 | * Possibly advances an animation to a new frame. |
819 | * |
820 | * Chooses the frame based on the start time passed to |
821 | * gdk_pixbuf_animation_get_iter(). |
822 | * |
823 | * @current_time would normally come from g_get_current_time(), and |
824 | * must be greater than or equal to the time passed to |
825 | * gdk_pixbuf_animation_get_iter(), and must increase or remain |
826 | * unchanged each time gdk_pixbuf_animation_iter_get_pixbuf() is |
827 | * called. That is, you can't go backward in time; animations only |
828 | * play forward. |
829 | * |
830 | * As a shortcut, pass `NULL` for the current time and g_get_current_time() |
831 | * will be invoked on your behalf. So you only need to explicitly pass |
832 | * @current_time if you're doing something odd like playing the animation |
833 | * at double speed. |
834 | * |
835 | * If this function returns `FALSE`, there's no need to update the animation |
836 | * display, assuming the display had been rendered prior to advancing; |
837 | * if `TRUE`, you need to call gdk_pixbuf_animation_iter_get_pixbuf() |
838 | * and update the display with the new pixbuf. |
839 | * |
840 | * Returns: `TRUE` if the image may need updating |
841 | */ |
842 | gboolean |
843 | gdk_pixbuf_animation_iter_advance (GdkPixbufAnimationIter *iter, |
844 | const GTimeVal *current_time) |
845 | { |
846 | GTimeVal val; |
847 | |
848 | g_return_val_if_fail (GDK_IS_PIXBUF_ANIMATION_ITER (iter), FALSE); |
849 | g_return_val_if_fail (GDK_PIXBUF_ANIMATION_ITER_GET_CLASS (iter)->advance, FALSE); |
850 | |
851 | if (current_time) |
852 | val = *current_time; |
853 | else |
854 | g_get_current_time (result: &val); |
855 | |
856 | return GDK_PIXBUF_ANIMATION_ITER_GET_CLASS (iter)->advance (iter, &val); |
857 | } |
858 | |
859 | static void gdk_pixbuf_non_anim_finalize (GObject *object); |
860 | static gboolean gdk_pixbuf_non_anim_is_static_image (GdkPixbufAnimation *animation); |
861 | static GdkPixbuf* gdk_pixbuf_non_anim_get_static_image (GdkPixbufAnimation *animation); |
862 | static void gdk_pixbuf_non_anim_get_size (GdkPixbufAnimation *anim, |
863 | gint *width, |
864 | gint *height); |
865 | static GdkPixbufAnimationIter* gdk_pixbuf_non_anim_get_iter (GdkPixbufAnimation *anim, |
866 | const GTimeVal *start_time); |
867 | |
868 | G_DEFINE_TYPE (GdkPixbufNonAnim, gdk_pixbuf_non_anim, GDK_TYPE_PIXBUF_ANIMATION); |
869 | |
870 | static void |
871 | gdk_pixbuf_non_anim_class_init (GdkPixbufNonAnimClass *klass) |
872 | { |
873 | GObjectClass *object_class = G_OBJECT_CLASS (klass); |
874 | GdkPixbufAnimationClass *anim_class = GDK_PIXBUF_ANIMATION_CLASS (klass); |
875 | |
876 | object_class->finalize = gdk_pixbuf_non_anim_finalize; |
877 | |
878 | anim_class->is_static_image = gdk_pixbuf_non_anim_is_static_image; |
879 | anim_class->get_static_image = gdk_pixbuf_non_anim_get_static_image; |
880 | anim_class->get_size = gdk_pixbuf_non_anim_get_size; |
881 | anim_class->get_iter = gdk_pixbuf_non_anim_get_iter; |
882 | } |
883 | |
884 | static void |
885 | gdk_pixbuf_non_anim_init (GdkPixbufNonAnim *non_anim) |
886 | { |
887 | } |
888 | |
889 | static void |
890 | gdk_pixbuf_non_anim_finalize (GObject *object) |
891 | { |
892 | GdkPixbufNonAnim *non_anim = GDK_PIXBUF_NON_ANIM (object); |
893 | |
894 | if (non_anim->pixbuf) |
895 | g_object_unref (object: non_anim->pixbuf); |
896 | |
897 | G_OBJECT_CLASS (gdk_pixbuf_non_anim_parent_class)->finalize (object); |
898 | } |
899 | |
900 | GdkPixbufAnimation* |
901 | gdk_pixbuf_non_anim_new (GdkPixbuf *pixbuf) |
902 | { |
903 | GdkPixbufNonAnim *non_anim; |
904 | |
905 | non_anim = g_object_new (GDK_TYPE_PIXBUF_NON_ANIM, NULL); |
906 | |
907 | non_anim->pixbuf = pixbuf; |
908 | |
909 | if (pixbuf) |
910 | g_object_ref (pixbuf); |
911 | |
912 | return GDK_PIXBUF_ANIMATION (non_anim); |
913 | } |
914 | |
915 | static gboolean |
916 | gdk_pixbuf_non_anim_is_static_image (GdkPixbufAnimation *animation) |
917 | { |
918 | |
919 | return TRUE; |
920 | } |
921 | |
922 | static GdkPixbuf* |
923 | gdk_pixbuf_non_anim_get_static_image (GdkPixbufAnimation *animation) |
924 | { |
925 | GdkPixbufNonAnim *non_anim; |
926 | |
927 | non_anim = GDK_PIXBUF_NON_ANIM (animation); |
928 | |
929 | return non_anim->pixbuf; |
930 | } |
931 | |
932 | static void |
933 | gdk_pixbuf_non_anim_get_size (GdkPixbufAnimation *anim, |
934 | gint *width, |
935 | gint *height) |
936 | { |
937 | GdkPixbufNonAnim *non_anim; |
938 | |
939 | non_anim = GDK_PIXBUF_NON_ANIM (anim); |
940 | |
941 | if (width) |
942 | *width = gdk_pixbuf_get_width (pixbuf: non_anim->pixbuf); |
943 | |
944 | if (height) |
945 | *height = gdk_pixbuf_get_height (pixbuf: non_anim->pixbuf); |
946 | } |
947 | |
948 | static GdkPixbufAnimationIter* |
949 | gdk_pixbuf_non_anim_get_iter (GdkPixbufAnimation *anim, |
950 | const GTimeVal *start_time) |
951 | { |
952 | GdkPixbufNonAnimIter *iter; |
953 | |
954 | iter = g_object_new (GDK_TYPE_PIXBUF_NON_ANIM_ITER, NULL); |
955 | |
956 | iter->non_anim = GDK_PIXBUF_NON_ANIM (anim); |
957 | |
958 | g_object_ref (iter->non_anim); |
959 | |
960 | return GDK_PIXBUF_ANIMATION_ITER (iter); |
961 | } |
962 | |
963 | static void gdk_pixbuf_non_anim_iter_finalize (GObject *object); |
964 | static gint gdk_pixbuf_non_anim_iter_get_delay_time (GdkPixbufAnimationIter *iter); |
965 | static GdkPixbuf* gdk_pixbuf_non_anim_iter_get_pixbuf (GdkPixbufAnimationIter *iter); |
966 | static gboolean gdk_pixbuf_non_anim_iter_on_currently_loading_frame (GdkPixbufAnimationIter *iter); |
967 | static gboolean gdk_pixbuf_non_anim_iter_advance (GdkPixbufAnimationIter *iter, |
968 | const GTimeVal *current_time); |
969 | |
970 | G_DEFINE_TYPE (GdkPixbufNonAnimIter, gdk_pixbuf_non_anim_iter, GDK_TYPE_PIXBUF_ANIMATION_ITER) |
971 | |
972 | static void |
973 | gdk_pixbuf_non_anim_iter_class_init (GdkPixbufNonAnimIterClass *klass) |
974 | { |
975 | GObjectClass *object_class = G_OBJECT_CLASS (klass); |
976 | GdkPixbufAnimationIterClass *anim_iter_class = |
977 | GDK_PIXBUF_ANIMATION_ITER_CLASS (klass); |
978 | |
979 | object_class->finalize = gdk_pixbuf_non_anim_iter_finalize; |
980 | |
981 | anim_iter_class->get_delay_time = gdk_pixbuf_non_anim_iter_get_delay_time; |
982 | anim_iter_class->get_pixbuf = gdk_pixbuf_non_anim_iter_get_pixbuf; |
983 | anim_iter_class->on_currently_loading_frame = gdk_pixbuf_non_anim_iter_on_currently_loading_frame; |
984 | anim_iter_class->advance = gdk_pixbuf_non_anim_iter_advance; |
985 | } |
986 | |
987 | static void |
988 | gdk_pixbuf_non_anim_iter_init (GdkPixbufNonAnimIter *non_iter) |
989 | { |
990 | } |
991 | |
992 | static void |
993 | gdk_pixbuf_non_anim_iter_finalize (GObject *object) |
994 | { |
995 | GdkPixbufNonAnimIter *iter = GDK_PIXBUF_NON_ANIM_ITER (object); |
996 | |
997 | g_object_unref (object: iter->non_anim); |
998 | |
999 | G_OBJECT_CLASS (gdk_pixbuf_non_anim_iter_parent_class)->finalize (object); |
1000 | } |
1001 | |
1002 | static gint |
1003 | gdk_pixbuf_non_anim_iter_get_delay_time (GdkPixbufAnimationIter *iter) |
1004 | { |
1005 | return -1; /* show only frame forever */ |
1006 | } |
1007 | |
1008 | static GdkPixbuf* |
1009 | gdk_pixbuf_non_anim_iter_get_pixbuf (GdkPixbufAnimationIter *iter) |
1010 | { |
1011 | return GDK_PIXBUF_NON_ANIM_ITER (iter)->non_anim->pixbuf; |
1012 | } |
1013 | |
1014 | |
1015 | static gboolean |
1016 | gdk_pixbuf_non_anim_iter_on_currently_loading_frame (GdkPixbufAnimationIter *iter) |
1017 | { |
1018 | return TRUE; |
1019 | } |
1020 | |
1021 | static gboolean |
1022 | gdk_pixbuf_non_anim_iter_advance (GdkPixbufAnimationIter *iter, |
1023 | const GTimeVal *current_time) |
1024 | { |
1025 | |
1026 | /* Advancing never requires a refresh */ |
1027 | return FALSE; |
1028 | } |
1029 | |