diff options
| author | Lars Ingebrigtsen | 2022-04-11 14:38:27 +0200 |
|---|---|---|
| committer | Lars Ingebrigtsen | 2022-04-11 14:38:27 +0200 |
| commit | 8b7aaf3e56c63cae7e2affc249179e5022451595 (patch) | |
| tree | 9dd4d6d8323d4946f070161d996dea0fdd856ff6 /src | |
| parent | 5141234acf85fe232adcaa3b0278f7766eb0d250 (diff) | |
| download | emacs-8b7aaf3e56c63cae7e2affc249179e5022451595.tar.gz emacs-8b7aaf3e56c63cae7e2affc249179e5022451595.zip | |
Speed up GIF animations
* src/image.c (anim_prune_animation_cache): Tweak the destructor
API.
(gif_destroy): New function.
(gif_load): Use a cache to avoid quadratic CPU usage for animated
images (bug#45224).
(webp_destroy): New function.
(webp_load): Use it.
Diffstat (limited to 'src')
| -rw-r--r-- | src/image.c | 290 |
1 files changed, 179 insertions, 111 deletions
diff --git a/src/image.c b/src/image.c index a3c98684261..967263e2c60 100644 --- a/src/image.c +++ b/src/image.c | |||
| @@ -8484,7 +8484,7 @@ anim_prune_animation_cache (void) | |||
| 8484 | else | 8484 | else |
| 8485 | { | 8485 | { |
| 8486 | if (cache->handle) | 8486 | if (cache->handle) |
| 8487 | cache->destructor (cache->handle); | 8487 | cache->destructor (cache); |
| 8488 | if (cache->temp) | 8488 | if (cache->temp) |
| 8489 | xfree (cache->temp); | 8489 | xfree (cache->temp); |
| 8490 | *pcache = cache->next; | 8490 | *pcache = cache->next; |
| @@ -8754,127 +8754,187 @@ static const int interlace_increment[] = {8, 8, 4, 2}; | |||
| 8754 | 8754 | ||
| 8755 | #define GIF_LOCAL_DESCRIPTOR_EXTENSION 249 | 8755 | #define GIF_LOCAL_DESCRIPTOR_EXTENSION 249 |
| 8756 | 8756 | ||
| 8757 | static void | ||
| 8758 | gif_destroy (struct anim_cache* cache) | ||
| 8759 | { | ||
| 8760 | int gif_err; | ||
| 8761 | gif_close (cache->handle, &gif_err); | ||
| 8762 | } | ||
| 8763 | |||
| 8757 | static bool | 8764 | static bool |
| 8758 | gif_load (struct frame *f, struct image *img) | 8765 | gif_load (struct frame *f, struct image *img) |
| 8759 | { | 8766 | { |
| 8760 | int rc, width, height, x, y, i, j; | 8767 | int rc, width, height, x, y, i, j; |
| 8761 | ColorMapObject *gif_color_map; | 8768 | ColorMapObject *gif_color_map; |
| 8762 | GifFileType *gif; | 8769 | GifFileType *gif = NULL; |
| 8763 | gif_memory_source memsrc; | 8770 | gif_memory_source memsrc; |
| 8764 | Lisp_Object specified_bg = image_spec_value (img->spec, QCbackground, NULL); | 8771 | Lisp_Object specified_bg = image_spec_value (img->spec, QCbackground, NULL); |
| 8765 | Lisp_Object specified_file = image_spec_value (img->spec, QCfile, NULL); | 8772 | Lisp_Object specified_file = image_spec_value (img->spec, QCfile, NULL); |
| 8766 | Lisp_Object specified_data = image_spec_value (img->spec, QCdata, NULL); | 8773 | Lisp_Object specified_data = image_spec_value (img->spec, QCdata, NULL); |
| 8767 | EMACS_INT idx; | 8774 | EMACS_INT idx = -1; |
| 8768 | int gif_err; | 8775 | int gif_err; |
| 8776 | struct anim_cache* cache = NULL; | ||
| 8769 | 8777 | ||
| 8770 | if (NILP (specified_data)) | 8778 | /* Which sub-image are we to display? */ |
| 8779 | { | ||
| 8780 | Lisp_Object image_number = image_spec_value (img->spec, QCindex, NULL); | ||
| 8781 | idx = FIXNUMP (image_number) ? XFIXNAT (image_number) : 0; | ||
| 8782 | } | ||
| 8783 | |||
| 8784 | if (idx != -1) | ||
| 8771 | { | 8785 | { |
| 8772 | Lisp_Object file = image_find_image_file (specified_file); | 8786 | /* If this is an animated image, create a cache for it. */ |
| 8773 | if (!STRINGP (file)) | 8787 | cache = anim_get_animation_cache (img->spec); |
| 8788 | if (cache->handle) | ||
| 8774 | { | 8789 | { |
| 8775 | image_error ("Cannot find image file `%s'", specified_file); | 8790 | /* We have an old cache entry, and it looks correct, so use |
| 8776 | return false; | 8791 | it. */ |
| 8792 | if (cache->index == idx - 1) | ||
| 8793 | gif = cache->handle; | ||
| 8777 | } | 8794 | } |
| 8795 | } | ||
| 8778 | 8796 | ||
| 8779 | Lisp_Object encoded_file = ENCODE_FILE (file); | 8797 | /* If we don't have a cached entry, read the image. */ |
| 8798 | if (! gif) | ||
| 8799 | { | ||
| 8800 | if (NILP (specified_data)) | ||
| 8801 | { | ||
| 8802 | Lisp_Object file = image_find_image_file (specified_file); | ||
| 8803 | if (!STRINGP (file)) | ||
| 8804 | { | ||
| 8805 | image_error ("Cannot find image file `%s'", specified_file); | ||
| 8806 | return false; | ||
| 8807 | } | ||
| 8808 | |||
| 8809 | Lisp_Object encoded_file = ENCODE_FILE (file); | ||
| 8780 | #ifdef WINDOWSNT | 8810 | #ifdef WINDOWSNT |
| 8781 | encoded_file = ansi_encode_filename (encoded_file); | 8811 | encoded_file = ansi_encode_filename (encoded_file); |
| 8782 | #endif | 8812 | #endif |
| 8783 | 8813 | ||
| 8784 | /* Open the GIF file. */ | 8814 | /* Open the GIF file. */ |
| 8785 | #if GIFLIB_MAJOR < 5 | 8815 | #if GIFLIB_MAJOR < 5 |
| 8786 | gif = DGifOpenFileName (SSDATA (encoded_file)); | 8816 | gif = DGifOpenFileName (SSDATA (encoded_file)); |
| 8787 | #else | 8817 | #else |
| 8788 | gif = DGifOpenFileName (SSDATA (encoded_file), &gif_err); | 8818 | gif = DGifOpenFileName (SSDATA (encoded_file), &gif_err); |
| 8789 | #endif | 8819 | #endif |
| 8790 | if (gif == NULL) | 8820 | if (gif == NULL) |
| 8791 | { | 8821 | { |
| 8792 | #if HAVE_GIFERRORSTRING | 8822 | #if HAVE_GIFERRORSTRING |
| 8793 | const char *errstr = GifErrorString (gif_err); | 8823 | const char *errstr = GifErrorString (gif_err); |
| 8794 | if (errstr) | 8824 | if (errstr) |
| 8795 | image_error ("Cannot open `%s': %s", file, build_string (errstr)); | 8825 | image_error ("Cannot open `%s': %s", file, |
| 8796 | else | 8826 | build_string (errstr)); |
| 8827 | else | ||
| 8797 | #endif | 8828 | #endif |
| 8798 | image_error ("Cannot open `%s'", file); | 8829 | image_error ("Cannot open `%s'", file); |
| 8799 | return false; | 8830 | return false; |
| 8831 | } | ||
| 8800 | } | 8832 | } |
| 8801 | } | 8833 | else |
| 8802 | else | ||
| 8803 | { | ||
| 8804 | if (!STRINGP (specified_data)) | ||
| 8805 | { | 8834 | { |
| 8806 | image_error ("Invalid image data `%s'", specified_data); | 8835 | if (!STRINGP (specified_data)) |
| 8807 | return false; | 8836 | { |
| 8808 | } | 8837 | image_error ("Invalid image data `%s'", specified_data); |
| 8838 | return false; | ||
| 8839 | } | ||
| 8809 | 8840 | ||
| 8810 | /* Read from memory! */ | 8841 | /* Read from memory! */ |
| 8811 | current_gif_memory_src = &memsrc; | 8842 | current_gif_memory_src = &memsrc; |
| 8812 | memsrc.bytes = SDATA (specified_data); | 8843 | memsrc.bytes = SDATA (specified_data); |
| 8813 | memsrc.len = SBYTES (specified_data); | 8844 | memsrc.len = SBYTES (specified_data); |
| 8814 | memsrc.index = 0; | 8845 | memsrc.index = 0; |
| 8815 | 8846 | ||
| 8816 | #if GIFLIB_MAJOR < 5 | 8847 | #if GIFLIB_MAJOR < 5 |
| 8817 | gif = DGifOpen (&memsrc, gif_read_from_memory); | 8848 | gif = DGifOpen (&memsrc, gif_read_from_memory); |
| 8818 | #else | 8849 | #else |
| 8819 | gif = DGifOpen (&memsrc, gif_read_from_memory, &gif_err); | 8850 | gif = DGifOpen (&memsrc, gif_read_from_memory, &gif_err); |
| 8851 | #endif | ||
| 8852 | if (!gif) | ||
| 8853 | { | ||
| 8854 | #if HAVE_GIFERRORSTRING | ||
| 8855 | const char *errstr = GifErrorString (gif_err); | ||
| 8856 | if (errstr) | ||
| 8857 | image_error ("Cannot open memory source `%s': %s", | ||
| 8858 | img->spec, build_string (errstr)); | ||
| 8859 | else | ||
| 8820 | #endif | 8860 | #endif |
| 8821 | if (!gif) | 8861 | image_error ("Cannot open memory source `%s'", img->spec); |
| 8862 | return false; | ||
| 8863 | } | ||
| 8864 | } | ||
| 8865 | |||
| 8866 | /* Before reading entire contents, check the declared image size. */ | ||
| 8867 | if (!check_image_size (f, gif->SWidth, gif->SHeight)) | ||
| 8868 | { | ||
| 8869 | image_size_error (); | ||
| 8870 | goto gif_error; | ||
| 8871 | } | ||
| 8872 | |||
| 8873 | /* Read entire contents. */ | ||
| 8874 | rc = DGifSlurp (gif); | ||
| 8875 | if (rc == GIF_ERROR || gif->ImageCount <= 0) | ||
| 8822 | { | 8876 | { |
| 8823 | #if HAVE_GIFERRORSTRING | 8877 | #if HAVE_GIFERRORSTRING |
| 8824 | const char *errstr = GifErrorString (gif_err); | 8878 | const char *errstr = GifErrorString (gif->Error); |
| 8825 | if (errstr) | 8879 | if (errstr) |
| 8826 | image_error ("Cannot open memory source `%s': %s", | 8880 | if (NILP (specified_data)) |
| 8827 | img->spec, build_string (errstr)); | 8881 | image_error ("Error reading `%s' (%s)", img->spec, |
| 8882 | build_string (errstr)); | ||
| 8883 | else | ||
| 8884 | image_error ("Error reading GIF data: %s", | ||
| 8885 | build_string (errstr)); | ||
| 8828 | else | 8886 | else |
| 8829 | #endif | 8887 | #endif |
| 8830 | image_error ("Cannot open memory source `%s'", img->spec); | 8888 | if (NILP (specified_data)) |
| 8831 | return false; | 8889 | image_error ("Error reading `%s'", img->spec); |
| 8890 | else | ||
| 8891 | image_error ("Error reading GIF data"); | ||
| 8892 | goto gif_error; | ||
| 8832 | } | 8893 | } |
| 8833 | } | ||
| 8834 | 8894 | ||
| 8835 | /* Before reading entire contents, check the declared image size. */ | 8895 | width = img->width = gif->SWidth; |
| 8836 | if (!check_image_size (f, gif->SWidth, gif->SHeight)) | 8896 | height = img->height = gif->SHeight; |
| 8897 | |||
| 8898 | /* Check that the selected subimages fit. It's not clear whether | ||
| 8899 | the GIF spec requires this, but Emacs can crash if they don't fit. */ | ||
| 8900 | for (j = 0; j <= idx; ++j) | ||
| 8901 | { | ||
| 8902 | struct SavedImage *subimage = gif->SavedImages + j; | ||
| 8903 | int subimg_width = subimage->ImageDesc.Width; | ||
| 8904 | int subimg_height = subimage->ImageDesc.Height; | ||
| 8905 | int subimg_top = subimage->ImageDesc.Top; | ||
| 8906 | int subimg_left = subimage->ImageDesc.Left; | ||
| 8907 | if (! (subimg_width >= 0 && subimg_height >= 0 | ||
| 8908 | && 0 <= subimg_top && subimg_top <= height - subimg_height | ||
| 8909 | && 0 <= subimg_left && subimg_left <= width - subimg_width)) | ||
| 8910 | { | ||
| 8911 | image_error ("Subimage does not fit in image"); | ||
| 8912 | goto gif_error; | ||
| 8913 | } | ||
| 8914 | } | ||
| 8915 | } | ||
| 8916 | else | ||
| 8837 | { | 8917 | { |
| 8838 | image_size_error (); | 8918 | /* Cached image; set data. */ |
| 8839 | goto gif_error; | 8919 | width = img->width = gif->SWidth; |
| 8920 | height = img->height = gif->SHeight; | ||
| 8840 | } | 8921 | } |
| 8841 | 8922 | ||
| 8842 | /* Read entire contents. */ | 8923 | if (idx < 0 || idx >= gif->ImageCount) |
| 8843 | rc = DGifSlurp (gif); | ||
| 8844 | if (rc == GIF_ERROR || gif->ImageCount <= 0) | ||
| 8845 | { | 8924 | { |
| 8846 | #if HAVE_GIFERRORSTRING | 8925 | image_error ("Invalid image number `%s' in image `%s'", |
| 8847 | const char *errstr = GifErrorString (gif->Error); | 8926 | make_fixnum (idx), img->spec); |
| 8848 | if (errstr) | ||
| 8849 | if (NILP (specified_data)) | ||
| 8850 | image_error ("Error reading `%s' (%s)", img->spec, | ||
| 8851 | build_string (errstr)); | ||
| 8852 | else | ||
| 8853 | image_error ("Error reading GIF data: %s", | ||
| 8854 | build_string (errstr)); | ||
| 8855 | else | ||
| 8856 | #endif | ||
| 8857 | if (NILP (specified_data)) | ||
| 8858 | image_error ("Error reading `%s'", img->spec); | ||
| 8859 | else | ||
| 8860 | image_error ("Error reading GIF data"); | ||
| 8861 | goto gif_error; | 8927 | goto gif_error; |
| 8862 | } | 8928 | } |
| 8863 | 8929 | ||
| 8864 | /* Which sub-image are we to display? */ | 8930 | /* It's an animated image, so initalize the cache. */ |
| 8865 | { | 8931 | if (cache && !cache->handle) |
| 8866 | Lisp_Object image_number = image_spec_value (img->spec, QCindex, NULL); | 8932 | { |
| 8867 | idx = FIXNUMP (image_number) ? XFIXNAT (image_number) : 0; | 8933 | cache->handle = gif; |
| 8868 | if (idx < 0 || idx >= gif->ImageCount) | 8934 | cache->destructor = (void (*)(void *)) &gif_destroy; |
| 8869 | { | 8935 | cache->width = width; |
| 8870 | image_error ("Invalid image number `%s' in image `%s'", | 8936 | cache->height = height; |
| 8871 | image_number, img->spec); | 8937 | } |
| 8872 | goto gif_error; | ||
| 8873 | } | ||
| 8874 | } | ||
| 8875 | |||
| 8876 | width = img->width = gif->SWidth; | ||
| 8877 | height = img->height = gif->SHeight; | ||
| 8878 | 8938 | ||
| 8879 | img->corners[TOP_CORNER] = gif->SavedImages[0].ImageDesc.Top; | 8939 | img->corners[TOP_CORNER] = gif->SavedImages[0].ImageDesc.Top; |
| 8880 | img->corners[LEFT_CORNER] = gif->SavedImages[0].ImageDesc.Left; | 8940 | img->corners[LEFT_CORNER] = gif->SavedImages[0].ImageDesc.Left; |
| @@ -8889,24 +8949,6 @@ gif_load (struct frame *f, struct image *img) | |||
| 8889 | goto gif_error; | 8949 | goto gif_error; |
| 8890 | } | 8950 | } |
| 8891 | 8951 | ||
| 8892 | /* Check that the selected subimages fit. It's not clear whether | ||
| 8893 | the GIF spec requires this, but Emacs can crash if they don't fit. */ | ||
| 8894 | for (j = 0; j <= idx; ++j) | ||
| 8895 | { | ||
| 8896 | struct SavedImage *subimage = gif->SavedImages + j; | ||
| 8897 | int subimg_width = subimage->ImageDesc.Width; | ||
| 8898 | int subimg_height = subimage->ImageDesc.Height; | ||
| 8899 | int subimg_top = subimage->ImageDesc.Top; | ||
| 8900 | int subimg_left = subimage->ImageDesc.Left; | ||
| 8901 | if (! (subimg_width >= 0 && subimg_height >= 0 | ||
| 8902 | && 0 <= subimg_top && subimg_top <= height - subimg_height | ||
| 8903 | && 0 <= subimg_left && subimg_left <= width - subimg_width)) | ||
| 8904 | { | ||
| 8905 | image_error ("Subimage does not fit in image"); | ||
| 8906 | goto gif_error; | ||
| 8907 | } | ||
| 8908 | } | ||
| 8909 | |||
| 8910 | /* Create the X image and pixmap. */ | 8952 | /* Create the X image and pixmap. */ |
| 8911 | Emacs_Pix_Container ximg; | 8953 | Emacs_Pix_Container ximg; |
| 8912 | if (!image_create_x_image_and_pixmap (f, img, width, height, 0, &ximg, 0)) | 8954 | if (!image_create_x_image_and_pixmap (f, img, width, height, 0, &ximg, 0)) |
| @@ -8944,11 +8986,6 @@ gif_load (struct frame *f, struct image *img) | |||
| 8944 | 8986 | ||
| 8945 | /* Read the GIF image into the X image. */ | 8987 | /* Read the GIF image into the X image. */ |
| 8946 | 8988 | ||
| 8947 | /* FIXME: With the current implementation, loading an animated gif | ||
| 8948 | is quadratic in the number of animation frames, since each frame | ||
| 8949 | is a separate struct image. We must provide a way for a single | ||
| 8950 | gif_load call to construct and save all animation frames. */ | ||
| 8951 | |||
| 8952 | init_color_table (); | 8989 | init_color_table (); |
| 8953 | 8990 | ||
| 8954 | unsigned long bgcolor UNINIT; | 8991 | unsigned long bgcolor UNINIT; |
| @@ -8963,7 +9000,19 @@ gif_load (struct frame *f, struct image *img) | |||
| 8963 | #endif | 9000 | #endif |
| 8964 | } | 9001 | } |
| 8965 | 9002 | ||
| 8966 | for (j = 0; j <= idx; ++j) | 9003 | int start_frame = 0; |
| 9004 | |||
| 9005 | /* We have animation data in the cache, so copy it over so that we | ||
| 9006 | can alter it. */ | ||
| 9007 | int cache_image_size = width * height * ximg->bits_per_pixel / 8; | ||
| 9008 | if (cache && cache->temp) | ||
| 9009 | { | ||
| 9010 | memcpy (ximg->data, cache->temp, cache_image_size); | ||
| 9011 | start_frame = cache->index; | ||
| 9012 | cache->index = idx; | ||
| 9013 | } | ||
| 9014 | |||
| 9015 | for (j = start_frame; j <= idx; ++j) | ||
| 8967 | { | 9016 | { |
| 8968 | /* We use a local variable `raster' here because RasterBits is a | 9017 | /* We use a local variable `raster' here because RasterBits is a |
| 8969 | char *, which invites problems with bytes >= 0x80. */ | 9018 | char *, which invites problems with bytes >= 0x80. */ |
| @@ -9074,6 +9123,15 @@ gif_load (struct frame *f, struct image *img) | |||
| 9074 | } | 9123 | } |
| 9075 | } | 9124 | } |
| 9076 | 9125 | ||
| 9126 | if (cache) | ||
| 9127 | { | ||
| 9128 | /* Allocate an area to store what we have computed so far. */ | ||
| 9129 | if (! cache->temp) | ||
| 9130 | cache->temp = xmalloc (cache_image_size); | ||
| 9131 | /* Copy over the data to the cache. */ | ||
| 9132 | memcpy (cache->temp, ximg->data, cache_image_size); | ||
| 9133 | } | ||
| 9134 | |||
| 9077 | #ifdef COLOR_TABLE_SUPPORT | 9135 | #ifdef COLOR_TABLE_SUPPORT |
| 9078 | img->colors = colors_in_color_table (&img->ncolors); | 9136 | img->colors = colors_in_color_table (&img->ncolors); |
| 9079 | free_color_table (); | 9137 | free_color_table (); |
| @@ -9114,17 +9172,20 @@ gif_load (struct frame *f, struct image *img) | |||
| 9114 | Fcons (make_fixnum (gif->ImageCount), | 9172 | Fcons (make_fixnum (gif->ImageCount), |
| 9115 | img->lisp_data)); | 9173 | img->lisp_data)); |
| 9116 | 9174 | ||
| 9117 | if (gif_close (gif, &gif_err) == GIF_ERROR) | 9175 | if (!cache) |
| 9118 | { | 9176 | { |
| 9177 | if (gif_close (gif, &gif_err) == GIF_ERROR) | ||
| 9178 | { | ||
| 9119 | #if HAVE_GIFERRORSTRING | 9179 | #if HAVE_GIFERRORSTRING |
| 9120 | char const *error_text = GifErrorString (gif_err); | 9180 | char const *error_text = GifErrorString (gif_err); |
| 9121 | 9181 | ||
| 9122 | if (error_text) | 9182 | if (error_text) |
| 9123 | image_error ("Error closing `%s': %s", | 9183 | image_error ("Error closing `%s': %s", |
| 9124 | img->spec, build_string (error_text)); | 9184 | img->spec, build_string (error_text)); |
| 9125 | else | 9185 | else |
| 9126 | #endif | 9186 | #endif |
| 9127 | image_error ("Error closing `%s'", img->spec); | 9187 | image_error ("Error closing `%s'", img->spec); |
| 9188 | } | ||
| 9128 | } | 9189 | } |
| 9129 | 9190 | ||
| 9130 | /* Maybe fill in the background field while we have ximg handy. */ | 9191 | /* Maybe fill in the background field while we have ximg handy. */ |
| @@ -9138,7 +9199,8 @@ gif_load (struct frame *f, struct image *img) | |||
| 9138 | return true; | 9199 | return true; |
| 9139 | 9200 | ||
| 9140 | gif_error: | 9201 | gif_error: |
| 9141 | gif_close (gif, NULL); | 9202 | if (!cache) |
| 9203 | gif_close (gif, NULL); | ||
| 9142 | return false; | 9204 | return false; |
| 9143 | } | 9205 | } |
| 9144 | 9206 | ||
| @@ -9292,6 +9354,12 @@ init_webp_functions (void) | |||
| 9292 | 9354 | ||
| 9293 | #endif /* WINDOWSNT */ | 9355 | #endif /* WINDOWSNT */ |
| 9294 | 9356 | ||
| 9357 | static void | ||
| 9358 | webp_destroy (struct anim_cache* cache) | ||
| 9359 | { | ||
| 9360 | WebPAnimDecoderDelete (cache->handle); | ||
| 9361 | } | ||
| 9362 | |||
| 9295 | /* Load WebP image IMG for use on frame F. Value is true if | 9363 | /* Load WebP image IMG for use on frame F. Value is true if |
| 9296 | successful. */ | 9364 | successful. */ |
| 9297 | 9365 | ||
| @@ -9408,7 +9476,7 @@ webp_load (struct frame *f, struct image *img) | |||
| 9408 | cache->height = height = WebPDemuxGetI (demux, | 9476 | cache->height = height = WebPDemuxGetI (demux, |
| 9409 | WEBP_FF_CANVAS_HEIGHT); | 9477 | WEBP_FF_CANVAS_HEIGHT); |
| 9410 | cache->frames = frames = WebPDemuxGetI (demux, WEBP_FF_FRAME_COUNT); | 9478 | cache->frames = frames = WebPDemuxGetI (demux, WEBP_FF_FRAME_COUNT); |
| 9411 | cache->destructor = (void (*)(void *)) &WebPAnimDecoderDelete; | 9479 | cache->destructor = (void (*)(void *)) webp_destroy; |
| 9412 | WebPDemuxDelete (demux); | 9480 | WebPDemuxDelete (demux); |
| 9413 | 9481 | ||
| 9414 | WebPAnimDecoderOptions dec_options; | 9482 | WebPAnimDecoderOptions dec_options; |