diff options
| author | Lars Ingebrigtsen | 2022-04-10 13:12:30 +0200 |
|---|---|---|
| committer | Lars Ingebrigtsen | 2022-04-10 13:12:41 +0200 |
| commit | d82e1a873df381b2c35bc9036da5665468bdfd31 (patch) | |
| tree | 0b3067244c3c513ac70a4415eca9a1c630040d9a /src/image.c | |
| parent | 735b45191041778824460807ee5bf4d1cebd3421 (diff) | |
| download | emacs-d82e1a873df381b2c35bc9036da5665468bdfd31.tar.gz emacs-d82e1a873df381b2c35bc9036da5665468bdfd31.zip | |
Add support for animated webp images
* configure.ac (HAVE_RSVG): Also include the webpdemux library.
It was new in version 0.4.4, and we require 0.6.0, so it should be
safe.
* src/image.c: Include demux.h.
(enum webp_keyword_index, webp_format): Include :index for
animations.
(init_webp_functions): Add Windows LOAD_DLLs.
(struct webp_cache, webp_create_cache)
(webp_prune_animation_cache, webp_get_animation_cache): New
functions.
(webp_load): Support animated webp images (bug#54242).
Diffstat (limited to 'src/image.c')
| -rw-r--r-- | src/image.c | 218 |
1 files changed, 205 insertions, 13 deletions
diff --git a/src/image.c b/src/image.c index 519eafb9047..64438ef9678 100644 --- a/src/image.c +++ b/src/image.c | |||
| @@ -9053,6 +9053,7 @@ gif_load (struct frame *f, struct image *img) | |||
| 9053 | ***********************************************************************/ | 9053 | ***********************************************************************/ |
| 9054 | 9054 | ||
| 9055 | #include "webp/decode.h" | 9055 | #include "webp/decode.h" |
| 9056 | #include "webp/demux.h" | ||
| 9056 | 9057 | ||
| 9057 | /* Indices of image specification fields in webp_format, below. */ | 9058 | /* Indices of image specification fields in webp_format, below. */ |
| 9058 | 9059 | ||
| @@ -9067,6 +9068,7 @@ enum webp_keyword_index | |||
| 9067 | WEBP_ALGORITHM, | 9068 | WEBP_ALGORITHM, |
| 9068 | WEBP_HEURISTIC_MASK, | 9069 | WEBP_HEURISTIC_MASK, |
| 9069 | WEBP_MASK, | 9070 | WEBP_MASK, |
| 9071 | WEBP_INDEX, | ||
| 9070 | WEBP_BACKGROUND, | 9072 | WEBP_BACKGROUND, |
| 9071 | WEBP_LAST | 9073 | WEBP_LAST |
| 9072 | }; | 9074 | }; |
| @@ -9085,6 +9087,7 @@ static const struct image_keyword webp_format[WEBP_LAST] = | |||
| 9085 | {":conversion", IMAGE_DONT_CHECK_VALUE_TYPE, 0}, | 9087 | {":conversion", IMAGE_DONT_CHECK_VALUE_TYPE, 0}, |
| 9086 | {":heuristic-mask", IMAGE_DONT_CHECK_VALUE_TYPE, 0}, | 9088 | {":heuristic-mask", IMAGE_DONT_CHECK_VALUE_TYPE, 0}, |
| 9087 | {":mask", IMAGE_DONT_CHECK_VALUE_TYPE, 0}, | 9089 | {":mask", IMAGE_DONT_CHECK_VALUE_TYPE, 0}, |
| 9090 | {":index", IMAGE_NON_NEGATIVE_INTEGER_VALUE, 0}, | ||
| 9088 | {":background", IMAGE_STRING_OR_NIL_VALUE, 0} | 9091 | {":background", IMAGE_STRING_OR_NIL_VALUE, 0} |
| 9089 | }; | 9092 | }; |
| 9090 | 9093 | ||
| @@ -9117,6 +9120,17 @@ DEF_DLL_FN (VP8StatusCode, WebPGetFeaturesInternal, | |||
| 9117 | DEF_DLL_FN (uint8_t *, WebPDecodeRGBA, (const uint8_t *, size_t, int *, int *)); | 9120 | DEF_DLL_FN (uint8_t *, WebPDecodeRGBA, (const uint8_t *, size_t, int *, int *)); |
| 9118 | DEF_DLL_FN (uint8_t *, WebPDecodeRGB, (const uint8_t *, size_t, int *, int *)); | 9121 | DEF_DLL_FN (uint8_t *, WebPDecodeRGB, (const uint8_t *, size_t, int *, int *)); |
| 9119 | DEF_DLL_FN (void, WebPFree, (void *)); | 9122 | DEF_DLL_FN (void, WebPFree, (void *)); |
| 9123 | DEF_DLL_FN (uint32_t, WebPDemuxGetI, (const WebPDemuxer* dmux, | ||
| 9124 | WebPFormatFeature feature)); | ||
| 9125 | DEF_DLL_FN (WebPDemuxer*, WebPDemux, (const WebPData* data)); | ||
| 9126 | DEF_DLL_FN (void, WebPDemuxDelete, (WebPDemuxer* dmux)); | ||
| 9127 | DEF_DLL_FN (int, WebPAnimDecoderGetNext, | ||
| 9128 | (WebPAnimDecoder* dec, uint8_t** buf, int* timestamp)); | ||
| 9129 | DEF_DLL_FN (WebPAnimDecoder*, WebPAnimDecoderNew, | ||
| 9130 | (const WebPData* webp_data, | ||
| 9131 | const WebPAnimDecoderOptions* dec_options)); | ||
| 9132 | DEF_DLL_FN (int, WebPAnimDecoderHasMoreFrames, (const WebPAnimDecoder* dec)); | ||
| 9133 | DEF_DLL_FN (void, WebPAnimDecoderDelete, (WebPAnimDecoder* dec)); | ||
| 9120 | 9134 | ||
| 9121 | static bool | 9135 | static bool |
| 9122 | init_webp_functions (void) | 9136 | init_webp_functions (void) |
| @@ -9131,6 +9145,13 @@ init_webp_functions (void) | |||
| 9131 | LOAD_DLL_FN (library, WebPDecodeRGBA); | 9145 | LOAD_DLL_FN (library, WebPDecodeRGBA); |
| 9132 | LOAD_DLL_FN (library, WebPDecodeRGB); | 9146 | LOAD_DLL_FN (library, WebPDecodeRGB); |
| 9133 | LOAD_DLL_FN (library, WebPFree); | 9147 | LOAD_DLL_FN (library, WebPFree); |
| 9148 | LOAD_DLL_FN (library, WebPDemuxGetI); | ||
| 9149 | LOAD_DLL_FN (library, WebPDemux); | ||
| 9150 | LOAD_DLL_FN (library, WebPDemuxDelete); | ||
| 9151 | LOAD_DLL_FN (library, WebPAnimDecoderGetNext); | ||
| 9152 | LOAD_DLL_FN (library, WebPAnimDecoderNew); | ||
| 9153 | LOAD_DLL_FN (library, WebPAnimDecoderHasMoreFrames); | ||
| 9154 | LOAD_DLL_FN (library, WebPAnimDecoderDelete); | ||
| 9134 | return true; | 9155 | return true; |
| 9135 | } | 9156 | } |
| 9136 | 9157 | ||
| @@ -9139,6 +9160,13 @@ init_webp_functions (void) | |||
| 9139 | #undef WebPDecodeRGBA | 9160 | #undef WebPDecodeRGBA |
| 9140 | #undef WebPDecodeRGB | 9161 | #undef WebPDecodeRGB |
| 9141 | #undef WebPFree | 9162 | #undef WebPFree |
| 9163 | #undef WebPDemuxGetI | ||
| 9164 | #undef WebPDemux | ||
| 9165 | #undef WebPDemuxDelete | ||
| 9166 | #undef WebPAnimDecoderGetNext | ||
| 9167 | #undef WebPAnimDecoderNew | ||
| 9168 | #undef WebPAnimDecoderHasMoreFrames | ||
| 9169 | #undef WebPAnimDecoderDelete | ||
| 9142 | 9170 | ||
| 9143 | #define WebPGetInfo fn_WebPGetInfo | 9171 | #define WebPGetInfo fn_WebPGetInfo |
| 9144 | #define WebPGetFeatures(d,s,f) \ | 9172 | #define WebPGetFeatures(d,s,f) \ |
| @@ -9146,9 +9174,92 @@ init_webp_functions (void) | |||
| 9146 | #define WebPDecodeRGBA fn_WebPDecodeRGBA | 9174 | #define WebPDecodeRGBA fn_WebPDecodeRGBA |
| 9147 | #define WebPDecodeRGB fn_WebPDecodeRGB | 9175 | #define WebPDecodeRGB fn_WebPDecodeRGB |
| 9148 | #define WebPFree fn_WebPFree | 9176 | #define WebPFree fn_WebPFree |
| 9177 | #define WebPDemuxGetI fn_WebPDemuxGetI | ||
| 9178 | #define WebPDemux fn_WebPDemux | ||
| 9179 | #define WebPDemuxDelete fn_WebPDemuxDelete | ||
| 9180 | #define WebPAnimDecoderGetNext fn_WebPAnimDecoderGetNext | ||
| 9181 | #define WebPAnimDecoderNew fn_WebPAnimDecoderNew | ||
| 9182 | #define WebPAnimDecoderHasMoreFrames fn_WebPAnimDecoderHasMoreFrames | ||
| 9183 | #define WebPAnimDecoderDelete fn_WebPAnimDecoderDelete | ||
| 9149 | 9184 | ||
| 9150 | #endif /* WINDOWSNT */ | 9185 | #endif /* WINDOWSNT */ |
| 9151 | 9186 | ||
| 9187 | /* To speed webp animations up, we keep a cache (based on EQ-ness of | ||
| 9188 | the image spec/object) where we put the libwebp animator | ||
| 9189 | iterator. */ | ||
| 9190 | |||
| 9191 | struct webp_cache | ||
| 9192 | { | ||
| 9193 | Lisp_Object spec; | ||
| 9194 | WebPAnimDecoder* anim; | ||
| 9195 | int index, width, height, frames; | ||
| 9196 | struct timespec update_time; | ||
| 9197 | struct webp_cache *next; | ||
| 9198 | }; | ||
| 9199 | |||
| 9200 | static struct webp_cache *webp_cache = NULL; | ||
| 9201 | |||
| 9202 | static struct webp_cache * | ||
| 9203 | webp_create_cache (Lisp_Object spec) | ||
| 9204 | { | ||
| 9205 | struct webp_cache *cache = xmalloc (sizeof (struct webp_cache)); | ||
| 9206 | cache->anim = NULL; | ||
| 9207 | |||
| 9208 | cache->index = 0; | ||
| 9209 | cache->next = NULL; | ||
| 9210 | /* FIXME: Does this need gc protection? */ | ||
| 9211 | cache->spec = spec; | ||
| 9212 | return cache; | ||
| 9213 | } | ||
| 9214 | |||
| 9215 | /* Discard cached images that haven't been used for a minute. */ | ||
| 9216 | static void | ||
| 9217 | webp_prune_animation_cache (void) | ||
| 9218 | { | ||
| 9219 | struct webp_cache **pcache = &webp_cache; | ||
| 9220 | struct timespec old = timespec_sub (current_timespec (), | ||
| 9221 | make_timespec (60, 0)); | ||
| 9222 | |||
| 9223 | while (*pcache) | ||
| 9224 | { | ||
| 9225 | struct webp_cache *cache = *pcache; | ||
| 9226 | if (timespec_cmp (old, cache->update_time) <= 0) | ||
| 9227 | pcache = &cache->next; | ||
| 9228 | else | ||
| 9229 | { | ||
| 9230 | if (cache->anim) | ||
| 9231 | WebPAnimDecoderDelete (cache->anim); | ||
| 9232 | *pcache = cache->next; | ||
| 9233 | xfree (cache); | ||
| 9234 | } | ||
| 9235 | } | ||
| 9236 | } | ||
| 9237 | |||
| 9238 | static struct webp_cache * | ||
| 9239 | webp_get_animation_cache (Lisp_Object spec) | ||
| 9240 | { | ||
| 9241 | struct webp_cache *cache; | ||
| 9242 | struct webp_cache **pcache = &webp_cache; | ||
| 9243 | |||
| 9244 | webp_prune_animation_cache (); | ||
| 9245 | |||
| 9246 | while (1) | ||
| 9247 | { | ||
| 9248 | cache = *pcache; | ||
| 9249 | if (! cache) | ||
| 9250 | { | ||
| 9251 | *pcache = cache = webp_create_cache (spec); | ||
| 9252 | break; | ||
| 9253 | } | ||
| 9254 | if (EQ (spec, cache->spec)) | ||
| 9255 | break; | ||
| 9256 | pcache = &cache->next; | ||
| 9257 | } | ||
| 9258 | |||
| 9259 | cache->update_time = current_timespec (); | ||
| 9260 | return cache; | ||
| 9261 | } | ||
| 9262 | |||
| 9152 | /* Load WebP image IMG for use on frame F. Value is true if | 9263 | /* Load WebP image IMG for use on frame F. Value is true if |
| 9153 | successful. */ | 9264 | successful. */ |
| 9154 | 9265 | ||
| @@ -9158,6 +9269,9 @@ webp_load (struct frame *f, struct image *img) | |||
| 9158 | ptrdiff_t size = 0; | 9269 | ptrdiff_t size = 0; |
| 9159 | uint8_t *contents; | 9270 | uint8_t *contents; |
| 9160 | Lisp_Object file = Qnil; | 9271 | Lisp_Object file = Qnil; |
| 9272 | int frames = 0; | ||
| 9273 | double delay = 0; | ||
| 9274 | WebPAnimDecoder* anim = NULL; | ||
| 9161 | 9275 | ||
| 9162 | /* Open the WebP file. */ | 9276 | /* Open the WebP file. */ |
| 9163 | Lisp_Object specified_file = image_spec_value (img->spec, QCfile, NULL); | 9277 | Lisp_Object specified_file = image_spec_value (img->spec, QCfile, NULL); |
| @@ -9201,6 +9315,9 @@ webp_load (struct frame *f, struct image *img) | |||
| 9201 | goto webp_error1; | 9315 | goto webp_error1; |
| 9202 | } | 9316 | } |
| 9203 | 9317 | ||
| 9318 | Lisp_Object image_number = image_spec_value (img->spec, QCindex, NULL); | ||
| 9319 | ptrdiff_t idx = FIXNUMP (image_number) ? XFIXNAT (image_number) : 0; | ||
| 9320 | |||
| 9204 | /* Get WebP features. */ | 9321 | /* Get WebP features. */ |
| 9205 | WebPBitstreamFeatures features; | 9322 | WebPBitstreamFeatures features; |
| 9206 | VP8StatusCode result = WebPGetFeatures (contents, size, &features); | 9323 | VP8StatusCode result = WebPGetFeatures (contents, size, &features); |
| @@ -9224,19 +9341,76 @@ webp_load (struct frame *f, struct image *img) | |||
| 9224 | goto webp_error1; | 9341 | goto webp_error1; |
| 9225 | } | 9342 | } |
| 9226 | 9343 | ||
| 9227 | /* Decode WebP data. */ | 9344 | uint8_t *decoded = NULL; |
| 9228 | uint8_t *decoded; | ||
| 9229 | int width, height; | 9345 | int width, height; |
| 9230 | if (features.has_alpha) | 9346 | |
| 9231 | /* Linear [r0, g0, b0, a0, r1, g1, b1, a1, ...] order. */ | 9347 | if (features.has_animation) |
| 9232 | decoded = WebPDecodeRGBA (contents, size, &width, &height); | 9348 | { |
| 9349 | /* Animated image. */ | ||
| 9350 | WebPData webp_data; | ||
| 9351 | webp_data.bytes = contents; | ||
| 9352 | webp_data.size = size; | ||
| 9353 | int timestamp; | ||
| 9354 | |||
| 9355 | struct webp_cache* cache = webp_get_animation_cache (img->spec); | ||
| 9356 | /* Get the next frame from the animation cache. */ | ||
| 9357 | if (cache->anim && cache->index == idx - 1) | ||
| 9358 | { | ||
| 9359 | WebPAnimDecoderGetNext (cache->anim, &decoded, ×tamp); | ||
| 9360 | delay = timestamp; | ||
| 9361 | cache->index++; | ||
| 9362 | anim = cache->anim; | ||
| 9363 | width = cache->width; | ||
| 9364 | height = cache->height; | ||
| 9365 | frames = cache->frames; | ||
| 9366 | } | ||
| 9367 | else | ||
| 9368 | { | ||
| 9369 | /* Start a new cache entry. */ | ||
| 9370 | if (cache->anim) | ||
| 9371 | WebPAnimDecoderDelete (cache->anim); | ||
| 9372 | |||
| 9373 | /* Get the width/height of the total image. */ | ||
| 9374 | WebPDemuxer* demux = WebPDemux (&webp_data); | ||
| 9375 | cache->width = width = WebPDemuxGetI (demux, WEBP_FF_CANVAS_WIDTH); | ||
| 9376 | cache->height = height = WebPDemuxGetI (demux, | ||
| 9377 | WEBP_FF_CANVAS_HEIGHT); | ||
| 9378 | cache->frames = frames = WebPDemuxGetI (demux, WEBP_FF_FRAME_COUNT); | ||
| 9379 | WebPDemuxDelete (demux); | ||
| 9380 | |||
| 9381 | WebPAnimDecoderOptions dec_options; | ||
| 9382 | WebPAnimDecoderOptionsInit (&dec_options); | ||
| 9383 | anim = WebPAnimDecoderNew (&webp_data, &dec_options); | ||
| 9384 | |||
| 9385 | cache->anim = anim; | ||
| 9386 | cache->index = idx; | ||
| 9387 | |||
| 9388 | while (WebPAnimDecoderHasMoreFrames (anim)) { | ||
| 9389 | WebPAnimDecoderGetNext (anim, &decoded, ×tamp); | ||
| 9390 | /* Each frame has its own delay, but we don't really support | ||
| 9391 | that. So just use the delay from the first frame. */ | ||
| 9392 | if (delay == 0) | ||
| 9393 | delay = timestamp; | ||
| 9394 | /* Stop when we get to the desired index. */ | ||
| 9395 | if (idx-- == 0) | ||
| 9396 | break; | ||
| 9397 | } | ||
| 9398 | } | ||
| 9399 | } | ||
| 9233 | else | 9400 | else |
| 9234 | /* Linear [r0, g0, b0, r1, g1, b1, ...] order. */ | 9401 | { |
| 9235 | decoded = WebPDecodeRGB (contents, size, &width, &height); | 9402 | /* Non-animated image. */ |
| 9403 | if (features.has_alpha) | ||
| 9404 | /* Linear [r0, g0, b0, a0, r1, g1, b1, a1, ...] order. */ | ||
| 9405 | decoded = WebPDecodeRGBA (contents, size, &width, &height); | ||
| 9406 | else | ||
| 9407 | /* Linear [r0, g0, b0, r1, g1, b1, ...] order. */ | ||
| 9408 | decoded = WebPDecodeRGB (contents, size, &width, &height); | ||
| 9409 | } | ||
| 9236 | 9410 | ||
| 9237 | if (!decoded) | 9411 | if (!decoded) |
| 9238 | { | 9412 | { |
| 9239 | image_error ("Error when interpreting WebP image data"); | 9413 | image_error ("Error when decoding WebP image data"); |
| 9240 | goto webp_error1; | 9414 | goto webp_error1; |
| 9241 | } | 9415 | } |
| 9242 | 9416 | ||
| @@ -9255,7 +9429,8 @@ webp_load (struct frame *f, struct image *img) | |||
| 9255 | /* Create an image and pixmap serving as mask if the WebP image | 9429 | /* Create an image and pixmap serving as mask if the WebP image |
| 9256 | contains an alpha channel. */ | 9430 | contains an alpha channel. */ |
| 9257 | if (features.has_alpha | 9431 | if (features.has_alpha |
| 9258 | && !image_create_x_image_and_pixmap (f, img, width, height, 1, &mask_img, true)) | 9432 | && !image_create_x_image_and_pixmap (f, img, width, height, 1, |
| 9433 | &mask_img, true)) | ||
| 9259 | { | 9434 | { |
| 9260 | image_destroy_x_image (ximg); | 9435 | image_destroy_x_image (ximg); |
| 9261 | image_clear_image_1 (f, img, CLEAR_IMAGE_PIXMAP); | 9436 | image_clear_image_1 (f, img, CLEAR_IMAGE_PIXMAP); |
| @@ -9265,6 +9440,13 @@ webp_load (struct frame *f, struct image *img) | |||
| 9265 | /* Fill the X image and mask from WebP data. */ | 9440 | /* Fill the X image and mask from WebP data. */ |
| 9266 | init_color_table (); | 9441 | init_color_table (); |
| 9267 | 9442 | ||
| 9443 | img->corners[TOP_CORNER] = 0; | ||
| 9444 | img->corners[LEFT_CORNER] = 0; | ||
| 9445 | img->corners[BOT_CORNER] | ||
| 9446 | = img->corners[TOP_CORNER] + height; | ||
| 9447 | img->corners[RIGHT_CORNER] | ||
| 9448 | = img->corners[LEFT_CORNER] + width; | ||
| 9449 | |||
| 9268 | uint8_t *p = decoded; | 9450 | uint8_t *p = decoded; |
| 9269 | for (int y = 0; y < height; ++y) | 9451 | for (int y = 0; y < height; ++y) |
| 9270 | { | 9452 | { |
| @@ -9279,7 +9461,7 @@ webp_load (struct frame *f, struct image *img) | |||
| 9279 | image. WebP allows up to 256 levels of partial transparency. | 9461 | image. WebP allows up to 256 levels of partial transparency. |
| 9280 | We handle this like with PNG (which see), using the frame's | 9462 | We handle this like with PNG (which see), using the frame's |
| 9281 | background color to combine the image with. */ | 9463 | background color to combine the image with. */ |
| 9282 | if (features.has_alpha) | 9464 | if (features.has_alpha || anim) |
| 9283 | { | 9465 | { |
| 9284 | if (mask_img) | 9466 | if (mask_img) |
| 9285 | PUT_PIXEL (mask_img, x, y, *p > 0 ? PIX_MASK_DRAW : PIX_MASK_RETAIN); | 9467 | PUT_PIXEL (mask_img, x, y, *p > 0 ? PIX_MASK_DRAW : PIX_MASK_RETAIN); |
| @@ -9310,14 +9492,24 @@ webp_load (struct frame *f, struct image *img) | |||
| 9310 | img->width = width; | 9492 | img->width = width; |
| 9311 | img->height = height; | 9493 | img->height = height; |
| 9312 | 9494 | ||
| 9495 | /* Return animation data. */ | ||
| 9496 | img->lisp_data = Fcons (Qcount, | ||
| 9497 | Fcons (make_fixnum (frames), | ||
| 9498 | img->lisp_data)); | ||
| 9499 | img->lisp_data = Fcons (Qdelay, | ||
| 9500 | Fcons (make_float (delay / 1000), | ||
| 9501 | img->lisp_data)); | ||
| 9502 | |||
| 9313 | /* Clean up. */ | 9503 | /* Clean up. */ |
| 9314 | WebPFree (decoded); | 9504 | if (!anim) |
| 9505 | WebPFree (decoded); | ||
| 9315 | if (NILP (specified_data)) | 9506 | if (NILP (specified_data)) |
| 9316 | xfree (contents); | 9507 | xfree (contents); |
| 9317 | return true; | 9508 | return true; |
| 9318 | 9509 | ||
| 9319 | webp_error2: | 9510 | webp_error2: |
| 9320 | WebPFree (decoded); | 9511 | if (!anim) |
| 9512 | WebPFree (decoded); | ||
| 9321 | 9513 | ||
| 9322 | webp_error1: | 9514 | webp_error1: |
| 9323 | if (NILP (specified_data)) | 9515 | if (NILP (specified_data)) |
| @@ -9484,7 +9676,7 @@ imagemagick_filename_hint (Lisp_Object spec, char hint_buffer[MaxTextExtent]) | |||
| 9484 | (which is the first one, and then there's a number of images that | 9676 | (which is the first one, and then there's a number of images that |
| 9485 | follow. If following images have non-transparent colors, these are | 9677 | follow. If following images have non-transparent colors, these are |
| 9486 | composed "on top" of the master image. So, in general, one has to | 9678 | composed "on top" of the master image. So, in general, one has to |
| 9487 | compute ann the preceding images to be able to display a particular | 9679 | compute all the preceding images to be able to display a particular |
| 9488 | sub-image. | 9680 | sub-image. |
| 9489 | 9681 | ||
| 9490 | Computing all the preceding images is too slow, so we maintain a | 9682 | Computing all the preceding images is too slow, so we maintain a |