diff options
| author | Basil L. Contovounesios | 2026-01-16 08:32:03 +0100 |
|---|---|---|
| committer | Basil L. Contovounesios | 2026-01-24 19:37:30 +0100 |
| commit | 72c53dcb13e13c170d3094cdabc58a22da806838 (patch) | |
| tree | 84b8540a34619a517cd248af5897ee6aba45f641 /src | |
| parent | db413c9da7adc2b0d94158be3d39f9034163cff9 (diff) | |
| download | emacs-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.c | 516 |
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 | ||
| 3685 | struct gif_anim_handle | ||
| 3686 | { | ||
| 3687 | struct GifFileType *gif; | ||
| 3688 | unsigned long *pixmap; | ||
| 3689 | }; | ||
| 3690 | # endif /* HAVE_GIF */ | ||
| 3691 | |||
| 3692 | # ifdef HAVE_WEBP | ||
| 3693 | struct 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. */ |
| 3719 | static struct anim_cache * | 3747 | static ATTRIBUTE_MALLOC struct anim_cache * |
| 3720 | anim_create_cache (Lisp_Object spec) | 3748 | anim_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 | ||
| 9634 | static void | 9659 | static void |
| 9635 | gif_clear_image (struct frame *f, struct image *img) | 9660 | gif_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. */ | ||
| 9824 | static void | 9850 | static void |
| 9825 | gif_destroy (struct anim_cache* cache) | 9851 | gif_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 | ||
| 9831 | static bool | 9860 | static 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 | ||
| 10384 | DEF_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 *)); | |||
| 10392 | DEF_DLL_FN (uint8_t *, WebPDecodeRGB, (const uint8_t *, size_t, int *, int *)); | 10427 | DEF_DLL_FN (uint8_t *, WebPDecodeRGB, (const uint8_t *, size_t, int *, int *)); |
| 10393 | DEF_DLL_FN (void, WebPFree, (void *)); | 10428 | DEF_DLL_FN (void, WebPFree, (void *)); |
| 10394 | DEF_DLL_FN (uint32_t, WebPDemuxGetI, (const WebPDemuxer *, WebPFormatFeature)); | 10429 | DEF_DLL_FN (uint32_t, WebPDemuxGetI, (const WebPDemuxer *, WebPFormatFeature)); |
| 10395 | DEF_DLL_FN (WebPDemuxer *, WebPDemuxInternal, | 10430 | DEF_DLL_FN (int, WebPAnimDecoderGetInfo, |
| 10396 | (const WebPData *, int, WebPDemuxState *, int)); | 10431 | (const WebPAnimDecoder* dec, WebPAnimInfo* info)); |
| 10397 | DEF_DLL_FN (void, WebPDemuxDelete, (WebPDemuxer *)); | ||
| 10398 | DEF_DLL_FN (int, WebPAnimDecoderGetNext, | 10432 | DEF_DLL_FN (int, WebPAnimDecoderGetNext, |
| 10399 | (WebPAnimDecoder *, uint8_t **, int *)); | 10433 | (WebPAnimDecoder *, uint8_t **, int *)); |
| 10400 | DEF_DLL_FN (WebPAnimDecoder *, WebPAnimDecoderNewInternal, | 10434 | DEF_DLL_FN (WebPAnimDecoder *, WebPAnimDecoderNewInternal, |
| 10401 | (const WebPData *, const WebPAnimDecoderOptions *, int)); | 10435 | (const WebPData *, const WebPAnimDecoderOptions *, int)); |
| 10402 | DEF_DLL_FN (int, WebPAnimDecoderOptionsInitInternal, | ||
| 10403 | (WebPAnimDecoderOptions *, int)); | ||
| 10404 | DEF_DLL_FN (int, WebPAnimDecoderHasMoreFrames, (const WebPAnimDecoder *)); | 10436 | DEF_DLL_FN (int, WebPAnimDecoderHasMoreFrames, (const WebPAnimDecoder *)); |
| 10437 | DEF_DLL_FN (void, WebPAnimDecoderReset, (WebPAnimDecoder *)); | ||
| 10438 | DEF_DLL_FN (const WebPDemuxer *, WebPAnimDecoderGetDemuxer, | ||
| 10439 | (const WebPAnimDecoder *)); | ||
| 10405 | DEF_DLL_FN (void, WebPAnimDecoderDelete, (WebPAnimDecoder *)); | 10440 | DEF_DLL_FN (void, WebPAnimDecoderDelete, (WebPAnimDecoder *)); |
| 10406 | 10441 | ||
| 10407 | static bool | 10442 | static 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. */ | ||
| 10466 | static void | 10497 | static void |
| 10467 | webp_destroy (struct anim_cache* cache) | 10498 | webp_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) | |||
| 10475 | static bool | 10511 | static bool |
| 10476 | webp_load (struct frame *f, struct image *img) | 10512 | webp_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, ×tamp); | 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, ×tamp); | 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, ×tamp)) | ||
| 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 | ||