aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorBasil L. Contovounesios2026-01-16 08:32:03 +0100
committerBasil L. Contovounesios2026-01-24 19:37:30 +0100
commit72c53dcb13e13c170d3094cdabc58a22da806838 (patch)
tree84b8540a34619a517cd248af5897ee6aba45f641 /src
parentdb413c9da7adc2b0d94158be3d39f9034163cff9 (diff)
downloademacs-72c53dcb13e13c170d3094cdabc58a22da806838.tar.gz
emacs-72c53dcb13e13c170d3094cdabc58a22da806838.zip
Improve (WebP) image animation
This adds support for animations with heterogeneous frame durations without sacrificing CPU (bug#47895), and plugs a memory leak in and speeds up WebP animations (bug#66221). * lisp/image.el (image-animate): No need to stash image-multi-frame-p data here, as image-animate-timeout now refetches it for each animation frame. (image-show-frame): Fetch image-multi-frame-p anew when checking bounds; a cached value risks going stale. This is not on the hot path for animations, and is mainly used when framewise stepping through an animation interactively. (image-animate-timeout): Fetch current frame duration anew but do so before image-show-frame to ensure an image cache hit (bug#47895, bug#66221). Include time taken by local arithmetic in 'time-to-load-image'. Update commentary. * src/image.c (parse_image_spec): Simplify using FIXNATP. (filter_image_spec): Remove check for :animate-multi-frame-data as it is no longer used by image.el. [HAVE_ANIMATION && HAVE_GIF] (struct gif_anim_handle): [HAVE_ANIMATION && HAVE_WEBP] (struct webp_anim_handle): New structures formalizing animation cache handles, and allowing for more than two custom fields per image type. (struct anim_cache): Replace generic handle and temp pointers with a union of gif_anim_handle and webp_anim_handle. All uses updated. Update destructor signature accordingly. (anim_create_cache): Use xzalloc to zero-initialize both integer and pointer fields. Initialize frames, width, height to -1 for consistency with index. Mark as ATTRIBUTE_MALLOC. (anim_prune_animation_cache): Check whether destructor (not handle) is null before calling it. (gif_clear_image): Note in commentary that WebP also uses it. (gif_destroy): Free pixmap here now that prune_anim_cache no longer does it automatically. Remove unused gif_err variable. (gif_load): Avoid UB from casting destructor to a different type. Don't redundantly check for null before xfree. Change default frame delay from 15fps to t, which image-multi-frame-p will translate into image-default-frame-delay, which the user can control. [HAVE_WEBP && WINDOWSNT] (init_webp_functions): Reconcile library definitions with current webp_load implementation. (webp_destroy): Free owned copy of input WebP bitstream contents. (webp_load): Ownership of both input and decoded memory is a function of :data vs :file and animated vs still. Make this and transfers of ownership to animation cache clearer by using distinct copy/view variables. Also make resource freeing clearer by using a single unconditional cleanup and exit path. Check animation cache early to avoid rereading bitstream and reparsing headers on each call. Remove redundant call to WebPGetInfo since WebPGetFeatures does the same thing. Check more libwebpdemux return values for failure and fix file name reported in error messages. Remove unset local variable 'file'. If requested :index is ahead, fast-forward instead of restarting from first frame. If requested :index is behind, reset animation decoder to first frame instead of deleting and recreating it. Reuse animation decoder's own WebPAnimInfo and WebPDemuxer instance instead of creating and deleting a separate WebPDemuxer. Fix leak when copying :data to animation cache. Fix frame duration calculation, and return each frame's own duration now that image.el supports it. Return t as a default frame duration, as per gif_load. Consistently use WebPBitstreamFeatures to simplify control flow. Don't pollute lisp_data image-metadata for still images with animation-related properties. (image_types) [HAVE_WEBP]: Use gif_clear_image to clear lisp_data for consistency with GIF code. (syms_of_image): Remove QCanimate_multi_frame_data; no longer used.
Diffstat (limited to 'src')
-rw-r--r--src/image.c516
1 files changed, 305 insertions, 211 deletions
diff --git a/src/image.c b/src/image.c
index f5db851cfbd..59be186a839 100644
--- a/src/image.c
+++ b/src/image.c
@@ -1568,7 +1568,7 @@ parse_image_spec (Lisp_Object spec, struct image_keyword *keywords,
1568 /* Unlike the other integer-related cases, this one does not 1568 /* Unlike the other integer-related cases, this one does not
1569 verify that VALUE fits in 'int'. This is because callers 1569 verify that VALUE fits in 'int'. This is because callers
1570 want EMACS_INT. */ 1570 want EMACS_INT. */
1571 if (!FIXNUMP (value) || XFIXNUM (value) < 0) 1571 if (!FIXNATP (value))
1572 return false; 1572 return false;
1573 break; 1573 break;
1574 1574
@@ -2268,8 +2268,7 @@ filter_image_spec (Lisp_Object spec)
2268 breaks the image cache. Filter those out. */ 2268 breaks the image cache. Filter those out. */
2269 if (!(EQ (key, QCanimate_buffer) 2269 if (!(EQ (key, QCanimate_buffer)
2270 || EQ (key, QCanimate_tardiness) 2270 || EQ (key, QCanimate_tardiness)
2271 || EQ (key, QCanimate_position) 2271 || EQ (key, QCanimate_position)))
2272 || EQ (key, QCanimate_multi_frame_data)))
2273 { 2272 {
2274 out = Fcons (value, out); 2273 out = Fcons (value, out);
2275 out = Fcons (key, out); 2274 out = Fcons (key, out);
@@ -3682,6 +3681,27 @@ cache_image (struct frame *f, struct image *img)
3682 3681
3683#if defined (HAVE_WEBP) || defined (HAVE_GIF) 3682#if defined (HAVE_WEBP) || defined (HAVE_GIF)
3684 3683
3684# ifdef HAVE_GIF
3685struct gif_anim_handle
3686{
3687 struct GifFileType *gif;
3688 unsigned long *pixmap;
3689};
3690# endif /* HAVE_GIF */
3691
3692# ifdef HAVE_WEBP
3693struct webp_anim_handle
3694{
3695 /* Decoder iterator+compositor. */
3696 struct WebPAnimDecoder *dec;
3697 /* Owned copy of input WebP bitstream data consumed by decoder,
3698 which it must outlive unchanged. */
3699 uint8_t *contents;
3700 /* Timestamp in milliseconds of last decoded frame. */
3701 int timestamp;
3702};
3703# endif /* HAVE_WEBP */
3704
3685/* To speed animations up, we keep a cache (based on EQ-ness of the 3705/* To speed animations up, we keep a cache (based on EQ-ness of the
3686 image spec/object) where we put the animator iterator. */ 3706 image spec/object) where we put the animator iterator. */
3687 3707
@@ -3690,12 +3710,20 @@ struct anim_cache
3690 /* 'Key' of this cache entry. 3710 /* 'Key' of this cache entry.
3691 Typically the cdr (plist) of an image spec. */ 3711 Typically the cdr (plist) of an image spec. */
3692 Lisp_Object spec; 3712 Lisp_Object spec;
3693 /* For webp, this will be an iterator, and for libgif, a gif handle. */ 3713 /* Image type dependent animation handle (e.g., WebP iterator), freed
3694 void *handle; 3714 by 'destructor'. The union allows maintaining multiple fields per
3695 /* If we need to maintain temporary data of some sort. */ 3715 image type and image frame without further heap allocations. */
3696 void *temp; 3716 union anim_handle
3717 {
3718# ifdef HAVE_GIF
3719 struct gif_anim_handle gif;
3720# endif /* HAVE_GIF */
3721# ifdef HAVE_WEBP
3722 struct webp_anim_handle webp;
3723# endif /* HAVE_WEBP */
3724 } handle;
3697 /* A function to call to free the handle. */ 3725 /* A function to call to free the handle. */
3698 void (*destructor) (void *); 3726 void (*destructor) (union anim_handle *);
3699 /* Current frame index, and total number of frames. Note that 3727 /* Current frame index, and total number of frames. Note that
3700 different image formats may start at different indices. */ 3728 different image formats may start at different indices. */
3701 int index, frames; 3729 int index, frames;
@@ -3716,17 +3744,15 @@ static struct anim_cache *anim_cache = NULL;
3716/* Return a new animation cache entry for image SPEC (which need not be 3744/* Return a new animation cache entry for image SPEC (which need not be
3717 an image specification, and is typically its cdr/plist). 3745 an image specification, and is typically its cdr/plist).
3718 Freed only by pruning the cache. */ 3746 Freed only by pruning the cache. */
3719static struct anim_cache * 3747static ATTRIBUTE_MALLOC struct anim_cache *
3720anim_create_cache (Lisp_Object spec) 3748anim_create_cache (Lisp_Object spec)
3721{ 3749{
3722 struct anim_cache *cache = xmalloc (sizeof (struct anim_cache)); 3750 struct anim_cache *cache = xzalloc (sizeof *cache);
3723 cache->handle = NULL;
3724 cache->temp = NULL;
3725
3726 cache->index = -1;
3727 cache->next = NULL;
3728 cache->spec = spec; 3751 cache->spec = spec;
3729 cache->byte_size = 0; 3752 cache->index = -1;
3753 cache->frames = -1;
3754 cache->width = -1;
3755 cache->height = -1;
3730 return cache; 3756 return cache;
3731} 3757}
3732 3758
@@ -3748,10 +3774,8 @@ anim_prune_animation_cache (Lisp_Object clear)
3748 || (NILP (clear) && timespec_cmp (old, cache->update_time) > 0) 3774 || (NILP (clear) && timespec_cmp (old, cache->update_time) > 0)
3749 || EQ (clear, cache->spec)) 3775 || EQ (clear, cache->spec))
3750 { 3776 {
3751 if (cache->handle) 3777 if (cache->destructor)
3752 cache->destructor (cache); 3778 cache->destructor (&cache->handle);
3753 if (cache->temp)
3754 xfree (cache->temp);
3755 *pcache = cache->next; 3779 *pcache = cache->next;
3756 xfree (cache); 3780 xfree (cache);
3757 } 3781 }
@@ -9629,7 +9653,8 @@ static const struct image_keyword gif_format[GIF_LAST] =
9629 {":background", IMAGE_STRING_OR_NIL_VALUE, 0} 9653 {":background", IMAGE_STRING_OR_NIL_VALUE, 0}
9630}; 9654};
9631 9655
9632/* Free X resources of GIF image IMG which is used on frame F. */ 9656/* Free X resources of GIF image IMG which is used on frame F.
9657 Also used by other image types. */
9633 9658
9634static void 9659static void
9635gif_clear_image (struct frame *f, struct image *img) 9660gif_clear_image (struct frame *f, struct image *img)
@@ -9821,11 +9846,15 @@ static const int interlace_increment[] = {8, 8, 4, 2};
9821 9846
9822#define GIF_LOCAL_DESCRIPTOR_EXTENSION 249 9847#define GIF_LOCAL_DESCRIPTOR_EXTENSION 249
9823 9848
9849/* Release gif_anim_handle resources. */
9824static void 9850static void
9825gif_destroy (struct anim_cache* cache) 9851gif_destroy (union anim_handle *handle)
9826{ 9852{
9827 int gif_err; 9853 struct gif_anim_handle *h = &handle->gif;
9828 gif_close (cache->handle, &gif_err); 9854 gif_close (h->gif, NULL);
9855 h->gif = NULL;
9856 xfree (h->pixmap);
9857 h->pixmap = NULL;
9829} 9858}
9830 9859
9831static bool 9860static bool
@@ -9842,6 +9871,7 @@ gif_load (struct frame *f, struct image *img)
9842 EMACS_INT idx = -1; 9871 EMACS_INT idx = -1;
9843 int gif_err; 9872 int gif_err;
9844 struct anim_cache* cache = NULL; 9873 struct anim_cache* cache = NULL;
9874 struct gif_anim_handle *anim_handle = NULL;
9845 /* Which sub-image are we to display? */ 9875 /* Which sub-image are we to display? */
9846 Lisp_Object image_number = image_spec_value (img->spec, QCindex, NULL); 9876 Lisp_Object image_number = image_spec_value (img->spec, QCindex, NULL);
9847 intmax_t byte_size = 0; 9877 intmax_t byte_size = 0;
@@ -9852,12 +9882,15 @@ gif_load (struct frame *f, struct image *img)
9852 { 9882 {
9853 /* If this is an animated image, create a cache for it. */ 9883 /* If this is an animated image, create a cache for it. */
9854 cache = anim_get_animation_cache (XCDR (img->spec)); 9884 cache = anim_get_animation_cache (XCDR (img->spec));
9885 anim_handle = &cache->handle.gif;
9855 /* We have an old cache entry, so use it. */ 9886 /* We have an old cache entry, so use it. */
9856 if (cache->handle) 9887 if (anim_handle->gif)
9857 { 9888 {
9858 gif = cache->handle; 9889 gif = anim_handle->gif;
9859 pixmap = cache->temp; 9890 pixmap = anim_handle->pixmap;
9860 /* We're out of sync, so start from the beginning. */ 9891 /* We're out of sync, so start from the beginning.
9892 FIXME: Can't we fast-forward like webp_load does when
9893 idx > cache->index, instead of restarting? */
9861 if (cache->index != idx - 1) 9894 if (cache->index != idx - 1)
9862 cache->index = -1; 9895 cache->index = -1;
9863 } 9896 }
@@ -10014,10 +10047,10 @@ gif_load (struct frame *f, struct image *img)
10014 } 10047 }
10015 10048
10016 /* It's an animated image, so initialize the cache. */ 10049 /* It's an animated image, so initialize the cache. */
10017 if (cache && !cache->handle) 10050 if (cache && !anim_handle->gif)
10018 { 10051 {
10019 cache->handle = gif; 10052 anim_handle->gif = gif;
10020 cache->destructor = (void (*)(void *)) &gif_destroy; 10053 cache->destructor = gif_destroy;
10021 cache->width = width; 10054 cache->width = width;
10022 cache->height = height; 10055 cache->height = height;
10023 cache->byte_size = byte_size; 10056 cache->byte_size = byte_size;
@@ -10046,8 +10079,8 @@ gif_load (struct frame *f, struct image *img)
10046 if (!pixmap) 10079 if (!pixmap)
10047 { 10080 {
10048 pixmap = xmalloc (width * height * sizeof (unsigned long)); 10081 pixmap = xmalloc (width * height * sizeof (unsigned long));
10049 if (cache) 10082 if (anim_handle)
10050 cache->temp = pixmap; 10083 anim_handle->pixmap = pixmap;
10051 } 10084 }
10052 10085
10053 /* Clear the part of the screen image not covered by the image. 10086 /* Clear the part of the screen image not covered by the image.
@@ -10100,7 +10133,7 @@ gif_load (struct frame *f, struct image *img)
10100 int start_frame = 0; 10133 int start_frame = 0;
10101 10134
10102 /* We have animation data in the cache. */ 10135 /* We have animation data in the cache. */
10103 if (cache && cache->temp) 10136 if (cache && anim_handle->pixmap)
10104 { 10137 {
10105 start_frame = cache->index + 1; 10138 start_frame = cache->index + 1;
10106 if (start_frame > idx) 10139 if (start_frame > idx)
@@ -10260,11 +10293,16 @@ gif_load (struct frame *f, struct image *img)
10260 delay |= ext->Bytes[1]; 10293 delay |= ext->Bytes[1];
10261 } 10294 }
10262 } 10295 }
10296 /* FIXME: Expose this via a nicer interface (bug#66221#122). */
10263 img->lisp_data = list2 (Qextension_data, img->lisp_data); 10297 img->lisp_data = list2 (Qextension_data, img->lisp_data);
10298 /* We used to return a default delay of 1/15th of a second.
10299 Meanwhile browsers have settled on 1/10th of a second.
10300 For consistency across image types and to afford user
10301 configuration, we now return a non-nil nonnumeric value that
10302 image-multi-frame-p turns into image-default-frame-delay. */
10264 img->lisp_data 10303 img->lisp_data
10265 = Fcons (Qdelay, 10304 = Fcons (Qdelay,
10266 /* Default GIF delay is 1/15th of a second. */ 10305 Fcons (delay ? make_float (delay / 100.0) : Qt,
10267 Fcons (make_float (delay? delay / 100.0: 1.0 / 15),
10268 img->lisp_data)); 10306 img->lisp_data));
10269 } 10307 }
10270 10308
@@ -10275,8 +10313,7 @@ gif_load (struct frame *f, struct image *img)
10275 10313
10276 if (!cache) 10314 if (!cache)
10277 { 10315 {
10278 if (pixmap) 10316 xfree (pixmap);
10279 xfree (pixmap);
10280 if (gif_close (gif, &gif_err) == GIF_ERROR) 10317 if (gif_close (gif, &gif_err) == GIF_ERROR)
10281 { 10318 {
10282#if HAVE_GIFERRORSTRING 10319#if HAVE_GIFERRORSTRING
@@ -10302,13 +10339,12 @@ gif_load (struct frame *f, struct image *img)
10302 return true; 10339 return true;
10303 10340
10304 gif_error: 10341 gif_error:
10305 if (pixmap) 10342 xfree (pixmap);
10306 xfree (pixmap);
10307 gif_close (gif, NULL); 10343 gif_close (gif, NULL);
10308 if (cache) 10344 if (anim_handle)
10309 { 10345 {
10310 cache->handle = NULL; 10346 anim_handle->gif = NULL;
10311 cache->temp = NULL; 10347 anim_handle->pixmap = NULL;
10312 } 10348 }
10313 return false; 10349 return false;
10314} 10350}
@@ -10381,7 +10417,6 @@ webp_image_p (Lisp_Object object)
10381 10417
10382/* WebP library details. */ 10418/* WebP library details. */
10383 10419
10384DEF_DLL_FN (int, WebPGetInfo, (const uint8_t *, size_t, int *, int *));
10385/* WebPGetFeatures is a static inline function defined in WebP's 10420/* WebPGetFeatures is a static inline function defined in WebP's
10386 decode.h. Since we cannot use that with dynamically-loaded libwebp 10421 decode.h. Since we cannot use that with dynamically-loaded libwebp
10387 DLL, we instead load the internal function it calls and redirect to 10422 DLL, we instead load the internal function it calls and redirect to
@@ -10392,16 +10427,16 @@ DEF_DLL_FN (uint8_t *, WebPDecodeRGBA, (const uint8_t *, size_t, int *, int *));
10392DEF_DLL_FN (uint8_t *, WebPDecodeRGB, (const uint8_t *, size_t, int *, int *)); 10427DEF_DLL_FN (uint8_t *, WebPDecodeRGB, (const uint8_t *, size_t, int *, int *));
10393DEF_DLL_FN (void, WebPFree, (void *)); 10428DEF_DLL_FN (void, WebPFree, (void *));
10394DEF_DLL_FN (uint32_t, WebPDemuxGetI, (const WebPDemuxer *, WebPFormatFeature)); 10429DEF_DLL_FN (uint32_t, WebPDemuxGetI, (const WebPDemuxer *, WebPFormatFeature));
10395DEF_DLL_FN (WebPDemuxer *, WebPDemuxInternal, 10430DEF_DLL_FN (int, WebPAnimDecoderGetInfo,
10396 (const WebPData *, int, WebPDemuxState *, int)); 10431 (const WebPAnimDecoder* dec, WebPAnimInfo* info));
10397DEF_DLL_FN (void, WebPDemuxDelete, (WebPDemuxer *));
10398DEF_DLL_FN (int, WebPAnimDecoderGetNext, 10432DEF_DLL_FN (int, WebPAnimDecoderGetNext,
10399 (WebPAnimDecoder *, uint8_t **, int *)); 10433 (WebPAnimDecoder *, uint8_t **, int *));
10400DEF_DLL_FN (WebPAnimDecoder *, WebPAnimDecoderNewInternal, 10434DEF_DLL_FN (WebPAnimDecoder *, WebPAnimDecoderNewInternal,
10401 (const WebPData *, const WebPAnimDecoderOptions *, int)); 10435 (const WebPData *, const WebPAnimDecoderOptions *, int));
10402DEF_DLL_FN (int, WebPAnimDecoderOptionsInitInternal,
10403 (WebPAnimDecoderOptions *, int));
10404DEF_DLL_FN (int, WebPAnimDecoderHasMoreFrames, (const WebPAnimDecoder *)); 10436DEF_DLL_FN (int, WebPAnimDecoderHasMoreFrames, (const WebPAnimDecoder *));
10437DEF_DLL_FN (void, WebPAnimDecoderReset, (WebPAnimDecoder *));
10438DEF_DLL_FN (const WebPDemuxer *, WebPAnimDecoderGetDemuxer,
10439 (const WebPAnimDecoder *));
10405DEF_DLL_FN (void, WebPAnimDecoderDelete, (WebPAnimDecoder *)); 10440DEF_DLL_FN (void, WebPAnimDecoderDelete, (WebPAnimDecoder *));
10406 10441
10407static bool 10442static bool
@@ -10413,60 +10448,61 @@ init_webp_functions (void)
10413 && (library2 = w32_delayed_load (Qwebpdemux)))) 10448 && (library2 = w32_delayed_load (Qwebpdemux))))
10414 return false; 10449 return false;
10415 10450
10416 LOAD_DLL_FN (library1, WebPGetInfo);
10417 LOAD_DLL_FN (library1, WebPGetFeaturesInternal); 10451 LOAD_DLL_FN (library1, WebPGetFeaturesInternal);
10418 LOAD_DLL_FN (library1, WebPDecodeRGBA); 10452 LOAD_DLL_FN (library1, WebPDecodeRGBA);
10419 LOAD_DLL_FN (library1, WebPDecodeRGB); 10453 LOAD_DLL_FN (library1, WebPDecodeRGB);
10420 LOAD_DLL_FN (library1, WebPFree); 10454 LOAD_DLL_FN (library1, WebPFree);
10421 LOAD_DLL_FN (library2, WebPDemuxGetI); 10455 LOAD_DLL_FN (library2, WebPDemuxGetI);
10422 LOAD_DLL_FN (library2, WebPDemuxInternal); 10456 LOAD_DLL_FN (library2, WebPAnimDecoderGetInfo);
10423 LOAD_DLL_FN (library2, WebPDemuxDelete);
10424 LOAD_DLL_FN (library2, WebPAnimDecoderGetNext); 10457 LOAD_DLL_FN (library2, WebPAnimDecoderGetNext);
10425 LOAD_DLL_FN (library2, WebPAnimDecoderNewInternal); 10458 LOAD_DLL_FN (library2, WebPAnimDecoderNewInternal);
10426 LOAD_DLL_FN (library2, WebPAnimDecoderOptionsInitInternal);
10427 LOAD_DLL_FN (library2, WebPAnimDecoderHasMoreFrames); 10459 LOAD_DLL_FN (library2, WebPAnimDecoderHasMoreFrames);
10460 LOAD_DLL_FN (library2, WebPAnimDecoderReset);
10461 LOAD_DLL_FN (library2, WebPAnimDecoderGetDemuxer);
10428 LOAD_DLL_FN (library2, WebPAnimDecoderDelete); 10462 LOAD_DLL_FN (library2, WebPAnimDecoderDelete);
10429 return true; 10463 return true;
10430} 10464}
10431 10465
10432#undef WebPGetInfo
10433#undef WebPGetFeatures 10466#undef WebPGetFeatures
10434#undef WebPDecodeRGBA 10467#undef WebPDecodeRGBA
10435#undef WebPDecodeRGB 10468#undef WebPDecodeRGB
10436#undef WebPFree 10469#undef WebPFree
10437#undef WebPDemuxGetI 10470#undef WebPDemuxGetI
10438#undef WebPDemux 10471#undef WebPAnimDecoderGetInfo
10439#undef WebPDemuxDelete
10440#undef WebPAnimDecoderGetNext 10472#undef WebPAnimDecoderGetNext
10441#undef WebPAnimDecoderNew 10473#undef WebPAnimDecoderNew
10442#undef WebPAnimDecoderOptionsInit
10443#undef WebPAnimDecoderHasMoreFrames 10474#undef WebPAnimDecoderHasMoreFrames
10475#undef WebPAnimDecoderReset
10476#undef WebPAnimDecoderGetDemuxer
10444#undef WebPAnimDecoderDelete 10477#undef WebPAnimDecoderDelete
10445 10478
10446#define WebPGetInfo fn_WebPGetInfo
10447#define WebPGetFeatures(d,s,f) \ 10479#define WebPGetFeatures(d,s,f) \
10448 fn_WebPGetFeaturesInternal(d,s,f,WEBP_DECODER_ABI_VERSION) 10480 fn_WebPGetFeaturesInternal(d,s,f,WEBP_DECODER_ABI_VERSION)
10449#define WebPDecodeRGBA fn_WebPDecodeRGBA 10481#define WebPDecodeRGBA fn_WebPDecodeRGBA
10450#define WebPDecodeRGB fn_WebPDecodeRGB 10482#define WebPDecodeRGB fn_WebPDecodeRGB
10451#define WebPFree fn_WebPFree 10483#define WebPFree fn_WebPFree
10452#define WebPDemuxGetI fn_WebPDemuxGetI 10484#define WebPDemuxGetI fn_WebPDemuxGetI
10453#define WebPDemux(d) \ 10485#define WebPAnimDecoderGetInfo fn_WebPAnimDecoderGetInfo
10454 fn_WebPDemuxInternal(d,0,NULL,WEBP_DEMUX_ABI_VERSION)
10455#define WebPDemuxDelete fn_WebPDemuxDelete
10456#define WebPAnimDecoderGetNext fn_WebPAnimDecoderGetNext 10486#define WebPAnimDecoderGetNext fn_WebPAnimDecoderGetNext
10457#define WebPAnimDecoderNew(d,o) \ 10487#define WebPAnimDecoderNew(d,o) \
10458 fn_WebPAnimDecoderNewInternal(d,o,WEBP_DEMUX_ABI_VERSION) 10488 fn_WebPAnimDecoderNewInternal(d,o,WEBP_DEMUX_ABI_VERSION)
10459#define WebPAnimDecoderOptionsInit(o) \
10460 fn_WebPAnimDecoderOptionsInitInternal(o,WEBP_DEMUX_ABI_VERSION)
10461#define WebPAnimDecoderHasMoreFrames fn_WebPAnimDecoderHasMoreFrames 10489#define WebPAnimDecoderHasMoreFrames fn_WebPAnimDecoderHasMoreFrames
10490#define WebPAnimDecoderReset fn_WebPAnimDecoderReset
10491#define WebPAnimDecoderGetDemuxer fn_WebPAnimDecoderGetDemuxer
10462#define WebPAnimDecoderDelete fn_WebPAnimDecoderDelete 10492#define WebPAnimDecoderDelete fn_WebPAnimDecoderDelete
10463 10493
10464#endif /* WINDOWSNT */ 10494#endif /* WINDOWSNT */
10465 10495
10496/* Release webp_anim_handle resources. */
10466static void 10497static void
10467webp_destroy (struct anim_cache* cache) 10498webp_destroy (union anim_handle *handle)
10468{ 10499{
10469 WebPAnimDecoderDelete (cache->handle); 10500 struct webp_anim_handle *h = &handle->webp;
10501 WebPAnimDecoderDelete (h->dec);
10502 h->dec = NULL;
10503 xfree (h->contents);
10504 h->contents = NULL;
10505 h->timestamp = 0;
10470} 10506}
10471 10507
10472/* Load WebP image IMG for use on frame F. Value is true if 10508/* Load WebP image IMG for use on frame F. Value is true if
@@ -10475,171 +10511,228 @@ webp_destroy (struct anim_cache* cache)
10475static bool 10511static bool
10476webp_load (struct frame *f, struct image *img) 10512webp_load (struct frame *f, struct image *img)
10477{ 10513{
10478 ptrdiff_t size = 0; 10514 /* Return value. */
10479 uint8_t *contents; 10515 bool success = false;
10480 Lisp_Object file = Qnil; 10516 /* Owned copies and borrowed views of input WebP bitstream data and
10481 int frames = 0; 10517 decoded image/frame, respectively. IOW, contents_cpy and
10482 double delay = 0; 10518 decoded_cpy must always be freed, and contents and decoded must
10483 WebPAnimDecoder* anim = NULL; 10519 never be freed. */
10520 uint8_t *contents_cpy = NULL;
10521 uint8_t const *contents = NULL;
10522 uint8_t *decoded_cpy = NULL;
10523 uint8_t *decoded = NULL;
10484 10524
10485 /* Open the WebP file. */ 10525 /* Non-nil :index suggests the image is animated; check the cache. */
10486 Lisp_Object specified_file = image_spec_value (img->spec, QCfile, NULL); 10526 Lisp_Object image_number = image_spec_value (img->spec, QCindex, NULL);
10487 Lisp_Object specified_data = image_spec_value (img->spec, QCdata, NULL); 10527 struct anim_cache *cache = (NILP (image_number) ? NULL
10528 : anim_get_animation_cache (XCDR (img->spec)));
10529 struct webp_anim_handle *anim_handle = cache ? &cache->handle.webp : NULL;
10530
10531 /* Image spec inputs. */
10532 Lisp_Object specified_data = Qnil;
10533 Lisp_Object specified_file = Qnil;
10534 /* Size of WebP contents. */
10535 ptrdiff_t size = 0;
10536 /* WebP features parsed from bitstream headers. */
10537 WebPBitstreamFeatures features = { 0 };
10488 10538
10489 if (NILP (specified_data)) 10539 if (! (anim_handle && anim_handle->dec))
10490 { 10540 /* If there is no cache entry, read in image contents. */
10491 contents = (uint8_t *) slurp_image (specified_file, &size, "WebP");
10492 if (contents == NULL)
10493 return false;
10494 }
10495 else
10496 { 10541 {
10497 if (!STRINGP (specified_data)) 10542 specified_data = image_spec_value (img->spec, QCdata, NULL);
10543 if (NILP (specified_data))
10498 { 10544 {
10499 image_invalid_data_error (specified_data); 10545 /* Open the WebP file. */
10500 return false; 10546 specified_file = image_spec_value (img->spec, QCfile, NULL);
10547 contents_cpy = (uint8_t *) slurp_image (specified_file,
10548 &size, "WebP");
10549 if (!contents_cpy)
10550 goto cleanup;
10551 contents = contents_cpy;
10552 }
10553 else if (STRINGP (specified_data))
10554 {
10555 contents = SDATA (specified_data);
10556 size = SBYTES (specified_data);
10501 } 10557 }
10502 contents = SDATA (specified_data);
10503 size = SBYTES (specified_data);
10504 }
10505
10506 /* Validate the WebP image header. */
10507 if (!WebPGetInfo (contents, size, NULL, NULL))
10508 {
10509 if (!NILP (file))
10510 image_error ("Not a WebP file: `%s'", file);
10511 else 10558 else
10512 image_error ("Invalid header in WebP image data"); 10559 {
10513 goto webp_error1; 10560 image_invalid_data_error (specified_data);
10514 } 10561 goto cleanup;
10515 10562 }
10516 Lisp_Object image_number = image_spec_value (img->spec, QCindex, NULL);
10517 ptrdiff_t idx = FIXNUMP (image_number) ? XFIXNAT (image_number) : 0;
10518 10563
10519 /* Get WebP features. */ 10564 /* Get WebP features. This can return various error codes while
10520 WebPBitstreamFeatures features; 10565 validating WebP headers, but we (currently) only distinguish
10521 VP8StatusCode result = WebPGetFeatures (contents, size, &features); 10566 success. */
10522 switch (result) 10567 if (WebPGetFeatures (contents, size, &features) != VP8_STATUS_OK)
10523 { 10568 {
10524 case VP8_STATUS_OK: 10569 image_error (NILP (specified_data)
10525 break; 10570 ? "Error parsing WebP headers from file: `%s'"
10526 case VP8_STATUS_NOT_ENOUGH_DATA: 10571 : "Error parsing WebP headers from image data",
10527 case VP8_STATUS_OUT_OF_MEMORY: 10572 specified_file);
10528 case VP8_STATUS_INVALID_PARAM: 10573 goto cleanup;
10529 case VP8_STATUS_BITSTREAM_ERROR: 10574 }
10530 case VP8_STATUS_UNSUPPORTED_FEATURE:
10531 case VP8_STATUS_SUSPENDED:
10532 case VP8_STATUS_USER_ABORT:
10533 default:
10534 /* Error out in all other cases. */
10535 if (!NILP (file))
10536 image_error ("Error when interpreting WebP image data: `%s'", file);
10537 else
10538 image_error ("Error when interpreting WebP image data");
10539 goto webp_error1;
10540 } 10575 }
10541 10576
10542 uint8_t *decoded = NULL; 10577 /* Dimensions of still image or animation frame. */
10543 int width, height; 10578 int width = -1;
10579 int height = -1;
10580 /* Number of animation frames. */
10581 int frames = -1;
10582 /* Current animation frame's duration in ms. */
10583 int duration = -1;
10544 10584
10545 if (features.has_animation) 10585 if ((anim_handle && anim_handle->dec) || features.has_animation)
10586 /* Animated image. */
10546 { 10587 {
10547 /* Animated image. */ 10588 if (!cache)
10548 int timestamp; 10589 /* If the lookup was initially skipped due to the absence of an
10590 :index, do it now. */
10591 {
10592 cache = anim_get_animation_cache (XCDR (img->spec));
10593 anim_handle = &cache->handle.webp;
10594 }
10549 10595
10550 struct anim_cache* cache = anim_get_animation_cache (XCDR (img->spec)); 10596 if (anim_handle->dec)
10551 /* Get the next frame from the animation cache. */ 10597 /* If WebPGetFeatures was skipped, get the already parsed
10552 if (cache->handle && cache->index == idx - 1) 10598 features from the cached decoder. */
10553 { 10599 {
10554 WebPAnimDecoderGetNext (cache->handle, &decoded, &timestamp); 10600 WebPDemuxer const *dmux
10555 delay = timestamp; 10601 = WebPAnimDecoderGetDemuxer (anim_handle->dec);
10556 cache->index++; 10602 uint32_t const flags = WebPDemuxGetI (dmux, WEBP_FF_FORMAT_FLAGS);
10557 anim = cache->handle; 10603 features.has_alpha = !!(flags & ALPHA_FLAG);
10558 width = cache->width; 10604 features.has_animation = !!(flags & ANIMATION_FLAG);
10559 height = cache->height;
10560 frames = cache->frames;
10561 } 10605 }
10562 else 10606 else
10563 { 10607 /* If there was no decoder in the cache, create one now. */
10564 /* Start a new cache entry. */ 10608 {
10565 if (cache->handle) 10609 /* If the data is from a Lisp string, copy it over so that it
10566 WebPAnimDecoderDelete (cache->handle); 10610 doesn't get garbage-collected. If it's fresh from a file,
10611 then another copy isn't needed to keep it alive. Either
10612 way, ownership transfers to the anim cache which frees
10613 memory during pruning. */
10614 anim_handle->contents = (STRINGP (specified_data)
10615 ? (uint8_t *) xlispstrdup (specified_data)
10616 : contents_cpy);
10617 contents_cpy = NULL;
10618 contents = anim_handle->contents;
10619 cache->destructor = webp_destroy;
10620
10621 /* The WebPData docs can be interpreted as requiring it be
10622 allocated, initialized, and cleared via its dedicated API.
10623 However that seems to apply mostly to the mux API that we
10624 don't use; the demux API we use treats WebPData as
10625 read-only POD, so this should be fine. */
10626 WebPData const webp_data = { .bytes = contents, .size = size };
10627 /* We could ask for multithreaded decoding here. */
10628 anim_handle->dec = WebPAnimDecoderNew (&webp_data, NULL);
10629 if (!anim_handle->dec)
10630 {
10631 image_error (NILP (specified_data)
10632 ? "Error parsing WebP file: `%s'"
10633 : "Error parsing WebP image data",
10634 specified_file);
10635 goto cleanup;
10636 }
10567 10637
10568 WebPData webp_data; 10638 /* Get the width/height of the total image. */
10569 if (NILP (specified_data)) 10639 WebPAnimInfo info;
10570 /* If we got the data from a file, then we don't need to 10640 if (!WebPAnimDecoderGetInfo (anim_handle->dec, &info))
10571 copy the data. */
10572 webp_data.bytes = cache->temp = contents;
10573 else
10574 /* We got the data from a string, so copy it over so that
10575 it doesn't get garbage-collected. */
10576 { 10641 {
10577 webp_data.bytes = xmalloc (size); 10642 image_error (NILP (specified_data)
10578 memcpy ((void*) webp_data.bytes, contents, size); 10643 ? ("Error getting global animation info "
10644 "from WebP file: `%s'")
10645 : ("Error getting global animation info "
10646 "from WebP image data"),
10647 specified_file);
10648 goto cleanup;
10579 } 10649 }
10580 /* In any case, we release the allocated memory when we
10581 purge the anim cache. */
10582 webp_data.size = size;
10583 10650
10651 /* Other libwebp[demux] APIs (and WebPAnimInfo internally)
10652 store these values as int, so this should be safe. */
10653 cache->width = info.canvas_width;
10654 cache->height = info.canvas_height;
10655 cache->frames = info.frame_count;
10584 /* This is used just for reporting by `image-cache-size'. */ 10656 /* This is used just for reporting by `image-cache-size'. */
10585 cache->byte_size = size; 10657 cache->byte_size = size;
10658 }
10586 10659
10587 /* Get the width/height of the total image. */ 10660 width = cache->width;
10588 WebPDemuxer* demux = WebPDemux (&webp_data); 10661 height = cache->height;
10589 cache->width = width = WebPDemuxGetI (demux, WEBP_FF_CANVAS_WIDTH); 10662 frames = cache->frames;
10590 cache->height = height = WebPDemuxGetI (demux, 10663
10591 WEBP_FF_CANVAS_HEIGHT); 10664 /* Desired frame number. */
10592 cache->frames = frames = WebPDemuxGetI (demux, WEBP_FF_FRAME_COUNT); 10665 EMACS_INT idx = (FIXNUMP (image_number)
10593 cache->destructor = (void (*)(void *)) webp_destroy; 10666 ? min (XFIXNAT (image_number), frames) : 0);
10594 WebPDemuxDelete (demux); 10667 if (cache->index >= idx)
10595 10668 /* The decoder cannot rewind (nor be queried for the last
10596 WebPAnimDecoderOptions dec_options; 10669 frame's decoded pixels and timestamp), so restart from
10597 WebPAnimDecoderOptionsInit (&dec_options); 10670 the first frame. We could avoid restarting when
10598 anim = WebPAnimDecoderNew (&webp_data, &dec_options); 10671 cache->index == idx by adding more fields to
10599 10672 webp_anim_handle, but it may not be worth it. */
10600 cache->handle = anim; 10673 {
10601 cache->index = idx; 10674 WebPAnimDecoderReset (anim_handle->dec);
10602 10675 anim_handle->timestamp = 0;
10603 while (WebPAnimDecoderHasMoreFrames (anim)) { 10676 cache->index = -1;
10604 WebPAnimDecoderGetNext (anim, &decoded, &timestamp); 10677 }
10605 /* Each frame has its own delay, but we don't really support 10678
10606 that. So just use the delay from the first frame. */ 10679 /* Decode until desired frame number. */
10607 if (delay == 0) 10680 for (;
10608 delay = timestamp; 10681 (cache->index < idx
10609 /* Stop when we get to the desired index. */ 10682 && WebPAnimDecoderHasMoreFrames (anim_handle->dec));
10610 if (idx-- == 0) 10683 cache->index++)
10611 break; 10684 {
10612 } 10685 int timestamp;
10686 if (!WebPAnimDecoderGetNext (anim_handle->dec, &decoded, &timestamp))
10687 {
10688 image_error (NILP (specified_data)
10689 ? "Error decoding frame #%d from WebP file: `%s'"
10690 : "Error decoding frame #%d from WebP image data",
10691 cache->index + 1, specified_file);
10692 goto cleanup;
10693 }
10694 eassert (anim_handle->timestamp >= 0);
10695 eassert (timestamp >= anim_handle->timestamp);
10696 duration = timestamp - anim_handle->timestamp;
10697 anim_handle->timestamp = timestamp;
10613 } 10698 }
10614 } 10699 }
10615 else 10700 else
10701 /* Non-animated image. */
10616 { 10702 {
10617 /* Non-animated image. */ 10703 /* Could performance be improved by using the 'advanced'
10704 WebPDecoderConfig API to request scaling/cropping as
10705 appropriate for Emacs frame and image dimensions,
10706 similarly to the SVG code? */
10618 if (features.has_alpha) 10707 if (features.has_alpha)
10619 /* Linear [r0, g0, b0, a0, r1, g1, b1, a1, ...] order. */ 10708 /* Linear [r0, g0, b0, a0, r1, g1, b1, a1, ...] order. */
10620 decoded = WebPDecodeRGBA (contents, size, &width, &height); 10709 decoded_cpy = WebPDecodeRGBA (contents, size, &width, &height);
10621 else 10710 else
10622 /* Linear [r0, g0, b0, r1, g1, b1, ...] order. */ 10711 /* Linear [r0, g0, b0, r1, g1, b1, ...] order. */
10623 decoded = WebPDecodeRGB (contents, size, &width, &height); 10712 decoded_cpy = WebPDecodeRGB (contents, size, &width, &height);
10713 decoded = decoded_cpy;
10624 } 10714 }
10625 10715
10626 if (!decoded) 10716 if (!decoded)
10627 { 10717 {
10628 image_error ("Error when decoding WebP image data"); 10718 image_error (NILP (specified_data)
10629 goto webp_error1; 10719 ? "Error decoding WebP file: `%s'"
10720 : "Error decoding WebP image data",
10721 specified_file);
10722 goto cleanup;
10630 } 10723 }
10631 10724
10632 if (!(width <= INT_MAX && height <= INT_MAX 10725 if (!(width <= INT_MAX && height <= INT_MAX
10633 && check_image_size (f, width, height))) 10726 && check_image_size (f, width, height)))
10634 { 10727 {
10635 image_size_error (); 10728 image_size_error ();
10636 goto webp_error2; 10729 goto cleanup;
10637 } 10730 }
10638 10731
10639 /* Create the x image and pixmap. */ 10732 /* Create the x image and pixmap. */
10640 Emacs_Pix_Container ximg; 10733 Emacs_Pix_Container ximg;
10641 if (!image_create_x_image_and_pixmap (f, img, width, height, 0, &ximg, false)) 10734 if (!image_create_x_image_and_pixmap (f, img, width, height, 0, &ximg, false))
10642 goto webp_error2; 10735 goto cleanup;
10643 10736
10644 /* Find the background to use if the WebP image contains an alpha 10737 /* Find the background to use if the WebP image contains an alpha
10645 channel. */ 10738 channel. */
@@ -10676,7 +10769,7 @@ webp_load (struct frame *f, struct image *img)
10676 img->corners[RIGHT_CORNER] 10769 img->corners[RIGHT_CORNER]
10677 = img->corners[LEFT_CORNER] + width; 10770 = img->corners[LEFT_CORNER] + width;
10678 10771
10679 uint8_t *p = decoded; 10772 uint8_t const *p = decoded;
10680 for (int y = 0; y < height; ++y) 10773 for (int y = 0; y < height; ++y)
10681 { 10774 {
10682 for (int x = 0; x < width; ++x) 10775 for (int x = 0; x < width; ++x)
@@ -10684,7 +10777,7 @@ webp_load (struct frame *f, struct image *img)
10684 int r, g, b; 10777 int r, g, b;
10685 /* The WebP alpha channel allows 256 levels of partial 10778 /* The WebP alpha channel allows 256 levels of partial
10686 transparency. Blend it with the background manually. */ 10779 transparency. Blend it with the background manually. */
10687 if (features.has_alpha || anim) 10780 if (features.has_alpha || features.has_animation)
10688 { 10781 {
10689 float a = (float) p[3] / UINT8_MAX; 10782 float a = (float) p[3] / UINT8_MAX;
10690 r = (int)(a * p[0] + (1 - a) * bg_color.red) << 8; 10783 r = (int)(a * p[0] + (1 - a) * bg_color.red) << 8;
@@ -10714,29 +10807,31 @@ webp_load (struct frame *f, struct image *img)
10714 img->width = width; 10807 img->width = width;
10715 img->height = height; 10808 img->height = height;
10716 10809
10717 /* Return animation data. */ 10810 if (features.has_animation)
10718 img->lisp_data = Fcons (Qcount, 10811 /* Return animation metadata. */
10719 Fcons (make_fixnum (frames), 10812 {
10720 img->lisp_data)); 10813 eassert (frames > 0);
10721 img->lisp_data = Fcons (Qdelay, 10814 eassert (duration >= 0);
10722 Fcons (make_float (delay / 1000), 10815 img->lisp_data = Fcons (Qcount,
10723 img->lisp_data)); 10816 Fcons (make_fixnum (frames),
10724 10817 img->lisp_data));
10725 /* Clean up. */ 10818 /* WebP spec: interpretation of no/small frame duration is
10726 if (!anim) 10819 implementation-defined. In practice browsers and libwebp tools
10727 WebPFree (decoded); 10820 map small durations to 100ms to protect against annoying
10728 if (NILP (specified_data) && !anim) 10821 images. For consistency across image types and user
10729 xfree (contents); 10822 configurability, we return a non-nil nonnumeric value that
10730 return true; 10823 image-multi-frame-p turns into image-default-frame-delay. */
10731 10824 img->lisp_data
10732 webp_error2: 10825 = Fcons (Qdelay,
10733 if (!anim) 10826 Fcons (duration ? make_float (duration / 1000.0) : Qt,
10734 WebPFree (decoded); 10827 img->lisp_data));
10828 }
10735 10829
10736 webp_error1: 10830 success = true;
10737 if (NILP (specified_data)) 10831 cleanup:
10738 xfree (contents); 10832 WebPFree (decoded_cpy);
10739 return false; 10833 xfree (contents_cpy);
10834 return success;
10740} 10835}
10741 10836
10742#endif /* HAVE_WEBP */ 10837#endif /* HAVE_WEBP */
@@ -12879,7 +12974,7 @@ static struct image_type const image_types[] =
12879 IMAGE_TYPE_INIT (init_xpm_functions) }, 12974 IMAGE_TYPE_INIT (init_xpm_functions) },
12880#endif 12975#endif
12881#if defined HAVE_WEBP 12976#if defined HAVE_WEBP
12882 { SYMBOL_INDEX (Qwebp), webp_image_p, webp_load, image_clear_image, 12977 { SYMBOL_INDEX (Qwebp), webp_image_p, webp_load, gif_clear_image,
12883 IMAGE_TYPE_INIT (init_webp_functions) }, 12978 IMAGE_TYPE_INIT (init_webp_functions) },
12884#endif 12979#endif
12885 { SYMBOL_INDEX (Qxbm), xbm_image_p, xbm_load, image_clear_image }, 12980 { SYMBOL_INDEX (Qxbm), xbm_image_p, xbm_load, image_clear_image },
@@ -13159,7 +13254,6 @@ non-numeric, there is no explicit limit on the size of images. */);
13159 DEFSYM (QCanimate_buffer, ":animate-buffer"); 13254 DEFSYM (QCanimate_buffer, ":animate-buffer");
13160 DEFSYM (QCanimate_tardiness, ":animate-tardiness"); 13255 DEFSYM (QCanimate_tardiness, ":animate-tardiness");
13161 DEFSYM (QCanimate_position, ":animate-position"); 13256 DEFSYM (QCanimate_position, ":animate-position");
13162 DEFSYM (QCanimate_multi_frame_data, ":animate-multi-frame-data");
13163 13257
13164 defsubr (&Simage_transforms_p); 13258 defsubr (&Simage_transforms_p);
13165 13259