+ /* It's OK to truncate the hint if it has MaxTextExtent or more bytes,
+ as ImageMagick would ignore the extra bytes anyway. */
+ snprintf (hint_buffer, MaxTextExtent, "/tmp/foo.%s", SSDATA (val));
+ return hint_buffer;
+}
+
+/* Animated images (e.g., GIF89a) are composed from one "master image"
+ (which is the first one, and then there's a number of images that
+ follow. If following images have non-transparent colors, these are
+ composed "on top" of the master image. So, in general, one has to
+ compute ann the preceding images to be able to display a particular
+ sub-image.
+
+ Computing all the preceding images is too slow, so we maintain a
+ cache of previously computed images. We have to maintain a cache
+ separate from the image cache, because the images may be scaled
+ before display. */
+
+struct animation_cache
+{
+ MagickWand *wand;
+ int index;
+ struct timespec update_time;
+ struct animation_cache *next;
+ char signature[FLEXIBLE_ARRAY_MEMBER];
+};
+
+static struct animation_cache *animation_cache = NULL;
+
+static struct animation_cache *
+imagemagick_create_cache (char *signature)
+{
+ struct animation_cache *cache
+ = xmalloc (offsetof (struct animation_cache, signature)
+ + strlen (signature) + 1);
+ cache->wand = 0;
+ cache->index = 0;
+ cache->next = 0;
+ strcpy (cache->signature, signature);
+ return cache;
+}
+
+/* Discard cached images that haven't been used for a minute. */
+static void
+imagemagick_prune_animation_cache (void)
+{
+ struct animation_cache **pcache = &animation_cache;
+ struct timespec old = timespec_sub (current_timespec (),
+ make_timespec (60, 0));
+
+ while (*pcache)
+ {
+ struct animation_cache *cache = *pcache;
+ if (timespec_cmp (old, cache->update_time) <= 0)
+ pcache = &cache->next;
+ else
+ {
+ if (cache->wand)
+ DestroyMagickWand (cache->wand);
+ *pcache = cache->next;
+ xfree (cache);
+ }
+ }
+}
+
+static struct animation_cache *
+imagemagick_get_animation_cache (MagickWand *wand)
+{
+ char *signature = MagickGetImageSignature (wand);
+ struct animation_cache *cache;
+ struct animation_cache **pcache = &animation_cache;
+
+ imagemagick_prune_animation_cache ();
+
+ while (1)
+ {
+ cache = *pcache;
+ if (! cache)
+ {
+ *pcache = cache = imagemagick_create_cache (signature);
+ break;
+ }
+ if (strcmp (signature, cache->signature) == 0)
+ break;
+ pcache = &cache->next;
+ }
+
+ DestroyString (signature);
+ cache->update_time = current_timespec ();
+ return cache;
+}
+
+static MagickWand *
+imagemagick_compute_animated_image (MagickWand *super_wand, int ino)
+{
+ int i;
+ MagickWand *composite_wand;
+ size_t dest_width, dest_height;
+ struct animation_cache *cache = imagemagick_get_animation_cache (super_wand);
+
+ MagickSetIteratorIndex (super_wand, 0);
+
+ if (ino == 0 || cache->wand == NULL || cache->index > ino)
+ {
+ composite_wand = MagickGetImage (super_wand);
+ if (cache->wand)
+ DestroyMagickWand (cache->wand);
+ }
+ else
+ composite_wand = cache->wand;
+
+ dest_width = MagickGetImageWidth (composite_wand);
+ dest_height = MagickGetImageHeight (composite_wand);
+
+ for (i = max (1, cache->index + 1); i <= ino; i++)
+ {
+ MagickWand *sub_wand;
+ PixelIterator *source_iterator, *dest_iterator;
+ PixelWand **source, **dest;
+ size_t source_width, source_height;
+ ssize_t source_left, source_top;
+ MagickPixelPacket pixel;
+ DisposeType dispose;
+ ptrdiff_t lines = 0;
+
+ MagickSetIteratorIndex (super_wand, i);
+ sub_wand = MagickGetImage (super_wand);
+
+ MagickGetImagePage (sub_wand, &source_width, &source_height,
+ &source_left, &source_top);
+
+ /* This flag says how to handle transparent pixels. */
+ dispose = MagickGetImageDispose (sub_wand);
+
+ source_iterator = NewPixelIterator (sub_wand);
+ if (! source_iterator)
+ {
+ DestroyMagickWand (composite_wand);
+ DestroyMagickWand (sub_wand);
+ cache->wand = NULL;
+ image_error ("Imagemagick pixel iterator creation failed",
+ Qnil, Qnil);
+ return NULL;
+ }
+
+ dest_iterator = NewPixelIterator (composite_wand);
+ if (! dest_iterator)
+ {
+ DestroyMagickWand (composite_wand);
+ DestroyMagickWand (sub_wand);
+ DestroyPixelIterator (source_iterator);
+ cache->wand = NULL;
+ image_error ("Imagemagick pixel iterator creation failed",
+ Qnil, Qnil);
+ return NULL;
+ }
+
+ /* The sub-image may not start at origin, so move the destination
+ iterator to where the sub-image should start. */
+ if (source_top > 0)
+ {
+ PixelSetIteratorRow (dest_iterator, source_top);
+ lines = source_top;
+ }
+
+ while ((source = PixelGetNextIteratorRow (source_iterator, &source_width))
+ != NULL)
+ {
+ ptrdiff_t x;
+
+ /* Sanity check. This shouldn't happen, but apparently
+ does in some pictures. */
+ if (++lines >= dest_height)
+ break;
+
+ dest = PixelGetNextIteratorRow (dest_iterator, &dest_width);
+ for (x = 0; x < source_width; x++)
+ {
+ /* Sanity check. This shouldn't happen, but apparently
+ also does in some pictures. */
+ if (x + source_left > dest_width - 1)
+ break;
+ /* Normally we only copy over non-transparent pixels,
+ but if the disposal method is "Background", then we
+ copy over all pixels. */
+ if (dispose == BackgroundDispose ||
+ PixelGetAlpha (source[x]))
+ {
+ PixelGetMagickColor (source[x], &pixel);
+ PixelSetMagickColor (dest[x + source_left], &pixel);
+ }
+ }
+ PixelSyncIterator (dest_iterator);
+ }
+
+ DestroyPixelIterator (source_iterator);
+ DestroyPixelIterator (dest_iterator);
+ DestroyMagickWand (sub_wand);
+ }
+
+ /* Cache a copy for the next iteration. The current wand will be
+ destroyed by the caller. */
+ cache->wand = CloneMagickWand (composite_wand);
+ cache->index = ino;
+
+ return composite_wand;