* bug#51296: [PATCH] Add WebP format support @ 2021-10-19 23:27 Stefan Kangas 2021-10-20 8:42 ` Lars Ingebrigtsen 2021-10-20 13:02 ` Stefan Kangas 0 siblings, 2 replies; 21+ messages in thread From: Stefan Kangas @ 2021-10-19 23:27 UTC (permalink / raw) To: 51296 [-- Attachment #1: Type: text/plain, Size: 882 bytes --] Severity: wishlist WebP is an image format promoted by Google since 2010 with a promise of decreased file sizes. Adoption in web browsers have been slow, but in 2019, Firefox added support for it, and other browsers followed.[1] Since then, this image format has become increasingly common on the web, and is reported to be used on 2.5 % of all websites.[2] I think it's important for Emacs to support it, both to support general web development and to display images properly in e.g. EWW. Given that we no longer promote ImageMagick, I think adding our own support for it is the way to go. The attached patch does that, by using libwebp where it is available.[3] Reviews are welcome, as usual. Footnotes: [1] https://bugzilla.mozilla.org/show_bug.cgi?id=1294490 [2] https://w3techs.com/technologies/details/im-webp [3] https://developers.google.com/speed/webp/download [-- Attachment #2: 0001-Add-WebP-format-support.patch --] [-- Type: text/x-diff, Size: 39381 bytes --] From 33f21cb15b9f5c2b7724995efc6eff6009525397 Mon Sep 17 00:00:00 2001 From: Stefan Kangas <stefan@marxist.se> Date: Fri, 15 Oct 2021 05:25:39 +0200 Subject: [PATCH] Add WebP format support * configure.ac (--with-webp): New option. (HAVE_WEBP): New variable. * src/image.c (enum webp_keyword_index) [HAVE_WEBP]: New enum. (webp_format) [HAVE_WEBP]: New variable. (webp_image_p, init_webp_functions, webp_load) [HAVE_WEBP]: New functions for WebP support. (image_types) [HAVE_WEBP]: Define WebP format. (syms_of_image) <Qwebp> [HAVE_WEBP]: New DEFSYM. Add image type Qwebp. * src/Makefile.in (LIBIMAGE): Add WEBP_LIBS. * lisp/files.el (auto-mode-alist): * lisp/image-file.el (image-file-name-extensions): * lisp/image.el (image-type-header-regexps) (image-type-file-name-regexps, image-type-auto-detectable): Add WebP support. * INSTALL: * admin/CPP-DEFINES: * doc/lispref/display.texi (Image Formats, Other Image Types): * nt/INSTALL: Document WebP support. * test/lisp/image-tests.el (image-find-image) (image-type-from-file-name): Expand tests. * test/src/image-tests.el (image-tests--files): Add WebP. (image-tests-image-size/webp, image-tests-image-mask-p/webp) (image-tests-image-metadata/webp): New tests. * test/data/image/black.webp: New file. --- INSTALL | 2 + admin/CPP-DEFINES | 1 + configure.ac | 19 +++ doc/lispref/display.texi | 11 +- lisp/files.el | 1 + lisp/image-file.el | 2 +- lisp/image.el | 3 + nt/INSTALL | 9 +- src/Makefile.in | 2 +- src/image.c | 285 +++++++++++++++++++++++++++++++++++++ test/data/image/black.webp | Bin 0 -> 37780 bytes test/lisp/image-tests.el | 8 +- test/src/image-tests.el | 23 ++- 13 files changed, 353 insertions(+), 13 deletions(-) create mode 100644 test/data/image/black.webp diff --git a/INSTALL b/INSTALL index 6207f43cec..21298422af 100644 --- a/INSTALL +++ b/INSTALL @@ -187,6 +187,7 @@ X11 is being used. X libtiff for TIFF: http://www.simplesystems.org/libtiff/ X libgif for GIF: http://giflib.sourceforge.net/ librsvg2 for SVG: https://wiki.gnome.org/Projects/LibRsvg + libwebp for WebP: https://developers.google.com/speed/webp/ If you supply the appropriate --without-LIB option, 'configure' will omit the corresponding library from Emacs, even if that makes for a @@ -313,6 +314,7 @@ or more of these options: --without-gif for GIF image support --without-png for PNG image support --without-rsvg for SVG image support + --without-webp for WebP image support Although ImageMagick support is disabled by default due to security and stability concerns, you can enable it with --with-imagemagick. diff --git a/admin/CPP-DEFINES b/admin/CPP-DEFINES index 68c12438f5..634d6f3f3b 100644 --- a/admin/CPP-DEFINES +++ b/admin/CPP-DEFINES @@ -287,6 +287,7 @@ HAVE_UTIMENSAT HAVE_UTMP_H HAVE_VFORK HAVE_VFORK_H +HAVE_WEBP HAVE_WCHAR_H HAVE_WCHAR_T HAVE_WINDOW_SYSTEM diff --git a/configure.ac b/configure.ac index 9ab0314428..17ac106d84 100644 --- a/configure.ac +++ b/configure.ac @@ -447,6 +447,7 @@ AC_DEFUN OPTION_DEFAULT_ON([gif],[don't compile with GIF image support]) OPTION_DEFAULT_ON([png],[don't compile with PNG image support]) OPTION_DEFAULT_ON([rsvg],[don't compile with SVG image support]) +OPTION_DEFAULT_ON([webp],[don't compile with WebP image support]) OPTION_DEFAULT_ON([lcms2],[don't compile with Little CMS support]) OPTION_DEFAULT_ON([libsystemd],[don't compile with libsystemd support]) OPTION_DEFAULT_ON([cairo],[don't compile with Cairo drawing]) @@ -2588,6 +2589,23 @@ AC_DEFUN fi fi +HAVE_WEBP=no +if test "${HAVE_X11}" = "yes" || test "${HAVE_NS}" = "yes" || test "${opsys}" = "mingw32"; then + if test "${with_webp}" != "no"; then + WEBP_REQUIRED=0.6.0 + WEBP_MODULE="libwebp >= $WEBP_REQUIRED" + + EMACS_CHECK_MODULES([WEBP], [$WEBP_MODULE]) + AC_SUBST(WEBP_CFLAGS) + AC_SUBST(WEBP_LIBS) + + if test $HAVE_WEBP = yes; then + AC_DEFINE(HAVE_WEBP, 1, [Define to 1 if using libwebp.]) + CFLAGS="$CFLAGS $WEBP_CFLAGS" + fi + fi +fi + HAVE_IMAGEMAGICK=no if test "${HAVE_X11}" = "yes" || test "${HAVE_NS}" = "yes" || test "${HAVE_W32}" = "yes"; then if test "${with_imagemagick}" != "no"; then @@ -5928,6 +5946,7 @@ AC_DEFUN Does Emacs use a gif library? ${HAVE_GIF} $LIBGIF Does Emacs use a png library? ${HAVE_PNG} $LIBPNG Does Emacs use -lrsvg-2? ${HAVE_RSVG} + Does Emacs use -lwebp? ${HAVE_WEBP} Does Emacs use cairo? ${HAVE_CAIRO} Does Emacs use -llcms2? ${HAVE_LCMS2} Does Emacs use imagemagick? ${HAVE_IMAGEMAGICK} diff --git a/doc/lispref/display.texi b/doc/lispref/display.texi index 16577d13c1..9c378a3027 100644 --- a/doc/lispref/display.texi +++ b/doc/lispref/display.texi @@ -5264,13 +5264,13 @@ Image Formats Supported image formats (and the required support libraries) include PBM and XBM (which do not depend on support libraries and are always available), XPM (@code{libXpm}), GIF (@code{libgif} or -@code{libungif}), JPEG (@code{libjpeg}), TIFF -(@code{libtiff}), PNG (@code{libpng}), and SVG (@code{librsvg}). +@code{libungif}), JPEG (@code{libjpeg}), TIFF (@code{libtiff}), PNG +(@code{libpng}), SVG (@code{librsvg}), and WebP (@code{libwebp}). Each of these image formats is associated with an @dfn{image type symbol}. The symbols for the above formats are, respectively, -@code{pbm}, @code{xbm}, @code{xpm}, @code{gif}, -@code{jpeg}, @code{tiff}, @code{png}, and @code{svg}. +@code{pbm}, @code{xbm}, @code{xpm}, @code{gif}, @code{jpeg}, +@code{tiff}, @code{png}, @code{svg}, and @code{webp}. Furthermore, if you build Emacs with ImageMagick (@code{libMagickWand}) support, Emacs can display any image format @@ -6274,6 +6274,9 @@ Other Image Types @item TIFF Image type @code{tiff}. Supports the @code{:index} property. @xref{Multi-Frame Images}. + +@item WebP +Image type @code{webp}. @end table @node Defining Images diff --git a/lisp/files.el b/lisp/files.el index 5a6a33721b..e6b94a4a1a 100644 --- a/lisp/files.el +++ b/lisp/files.el @@ -2758,6 +2758,7 @@ auto-mode-alist ("\\.gif\\'" . image-mode) ("\\.png\\'" . image-mode) ("\\.jpe?g\\'" . image-mode) + ("\\.webp\\'" . image-mode) ("\\.te?xt\\'" . text-mode) ("\\.[tT]e[xX]\\'" . tex-mode) ("\\.ins\\'" . tex-mode) ;Installation files for TeX packages. diff --git a/lisp/image-file.el b/lisp/image-file.el index fbc9eaaf94..6df43f737d 100644 --- a/lisp/image-file.el +++ b/lisp/image-file.el @@ -37,7 +37,7 @@ ;;;###autoload (defcustom image-file-name-extensions - (purecopy '("png" "jpeg" "jpg" "gif" "tiff" "tif" "xbm" "xpm" "pbm" "pgm" "ppm" "pnm" "svg")) + (purecopy '("png" "jpeg" "jpg" "gif" "tiff" "tif" "xbm" "xpm" "pbm" "pgm" "ppm" "pnm" "svg" "webp")) "A list of image-file filename extensions. Filenames having one of these extensions are considered image files, in addition to those matching `image-file-name-regexps'. diff --git a/lisp/image.el b/lisp/image.el index 2022b41d1f..5343e26d03 100644 --- a/lisp/image.el +++ b/lisp/image.el @@ -48,6 +48,7 @@ image-type-header-regexps ("\\`\\(?:MM\0\\*\\|II\\*\0\\)" . tiff) ("\\`[\t\n\r ]*%!PS" . postscript) ("\\`\xff\xd8" . jpeg) ; used to be (image-jpeg-p . jpeg) + ("\\`RIFF....WEBPVP8" . webp) (,(let* ((incomment-re "\\(?:[^-]\\|-[^-]\\)") (comment-re (concat "\\(?:!--" incomment-re "*-->[ \t\r\n]*<\\)"))) (concat "\\(?:<\\?xml[ \t\r\n]+[^>]*>\\)?[ \t\r\n]*<" @@ -67,6 +68,7 @@ image-type-file-name-regexps '(("\\.png\\'" . png) ("\\.gif\\'" . gif) ("\\.jpe?g\\'" . jpeg) + ("\\.webp\\'" . webp) ("\\.bmp\\'" . bmp) ("\\.xpm\\'" . xpm) ("\\.pbm\\'" . pbm) @@ -92,6 +94,7 @@ image-type-auto-detectable (jpeg . maybe) (tiff . maybe) (svg . maybe) + (webp . maybe) (postscript . nil)) "Alist of (IMAGE-TYPE . AUTODETECT) pairs used to auto-detect image files. \(See `image-type-auto-detected-p'). diff --git a/nt/INSTALL b/nt/INSTALL index 9f543151a9..a39057c66c 100644 --- a/nt/INSTALL +++ b/nt/INSTALL @@ -488,6 +488,7 @@ build will run on Windows 9X and newer systems). Does Emacs use a gif library? yes Does Emacs use a png library? yes Does Emacs use -lrsvg-2? yes + Does Emacs use -lwebp? yes Does Emacs use cairo? no Does Emacs use -llcms2? yes Does Emacs use imagemagick? no @@ -597,8 +598,8 @@ build will run on Windows 9X and newer systems). * Optional image library support In addition to its "native" image formats (pbm and xbm), Emacs can - handle other image types: xpm, tiff, gif, png, jpeg and experimental - support for svg. + handle other image types: xpm, tiff, gif, png, jpeg, webp and + experimental support for svg. To build Emacs with support for them, the corresponding headers must be in the include path and libraries should be where the linker @@ -736,6 +737,10 @@ build will run on Windows 9X and newer systems). without it by specifying the --without-rsvg switch to the configure script. + For WebP images you will need libwebp: + + https://developers.google.com/speed/webp/ + Binaries for the other image libraries can be found on the ezwinports site or at the GnuWin32 project (the latter are generally very old, so not recommended). Note specifically that, due to some diff --git a/src/Makefile.in b/src/Makefile.in index 6d75e3537a..7c977e34ea 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -124,7 +124,7 @@ LIB_MATH= ## -lpthread, or empty. LIB_PTHREAD=@LIB_PTHREAD@ -LIBIMAGE=@LIBTIFF@ @LIBJPEG@ @LIBPNG@ @LIBGIF@ @LIBXPM@ +LIBIMAGE=@LIBTIFF@ @LIBJPEG@ @LIBPNG@ @LIBGIF@ @LIBXPM@ @WEBP_LIBS@ XCB_LIBS=@XCB_LIBS@ XFT_LIBS=@XFT_LIBS@ diff --git a/src/image.c b/src/image.c index ff05741b2c..6ff172504f 100644 --- a/src/image.c +++ b/src/image.c @@ -8739,8 +8739,284 @@ gif_load (struct frame *f, struct image *img) #endif /* HAVE_GIF */ +#ifdef HAVE_WEBP + +\f +/*********************************************************************** + WebP + ***********************************************************************/ + +#include "webp/decode.h" + +/* Indices of image specification fields in webp_format, below. */ + +enum webp_keyword_index +{ + WEBP_TYPE, + WEBP_DATA, + WEBP_FILE, + WEBP_ASCENT, + WEBP_MARGIN, + WEBP_RELIEF, + WEBP_ALGORITHM, + WEBP_HEURISTIC_MASK, + WEBP_MASK, + WEBP_BACKGROUND, + WEBP_LAST +}; + +/* Vector of image_keyword structures describing the format + of valid user-defined image specifications. */ + +static const struct image_keyword webp_format[WEBP_LAST] = +{ + {":type", IMAGE_SYMBOL_VALUE, 1}, + {":data", IMAGE_STRING_VALUE, 0}, + {":file", IMAGE_STRING_VALUE, 0}, + {":ascent", IMAGE_ASCENT_VALUE, 0}, + {":margin", IMAGE_NON_NEGATIVE_INTEGER_VALUE_OR_PAIR, 0}, + {":relief", IMAGE_INTEGER_VALUE, 0}, + {":conversion", IMAGE_DONT_CHECK_VALUE_TYPE, 0}, + {":heuristic-mask", IMAGE_DONT_CHECK_VALUE_TYPE, 0}, + {":mask", IMAGE_DONT_CHECK_VALUE_TYPE, 0}, + {":background", IMAGE_STRING_OR_NIL_VALUE, 0} +}; + +/* Return true if OBJECT is a valid WebP image specification. */ + +static bool +webp_image_p (Lisp_Object object) +{ + struct image_keyword fmt[WEBP_LAST]; + memcpy (fmt, webp_format, sizeof fmt); + + if (!parse_image_spec (object, fmt, WEBP_LAST, Qwebp)) + return false; + + /* Must specify either the :data or :file keyword. */ + return fmt[WEBP_FILE].count + fmt[WEBP_DATA].count == 1; +} + +#ifdef WINDOWSNT + +/* WebP library details. */ + +DEF_DLL_FN (int, WebPGetInfo, (const uint8_t* data, size_t data_size, int* width, int* height)); +DEF_DLL_FN (VP8StatusCode, WebPGetFeatures, + (const uint8_t* data, size_t data_size, WebPBitstreamFeatures* features)); +DEF_DLL_FN (uint8_t*, WebPDecodeRGB, + (const uint8_t* data, size_t data_size, int* width, int* height)); +DEF_DLL_FN (uint8_t*, WebPDecodeBGR, + (const uint8_t* data, size_t data_size, int* width, int* height)); +DEF_DLL_FN (void, WebPFreeDecBuffer + (WebPDecBuffer* buffer)); + +static bool +init_webp_functions (void) +{ + HMODULE library; + + if (!(library = w32_delayed_load (Qwebp))) + return 0; + + LOAD_DLL_FN (library, WebPGetInfo); + LOAD_DLL_FN (library, WebPGetFeatures); + LOAD_DLL_FN (library, WebPDecodeRGBA); + LOAD_DLL_FN (library, WebPDecodeRGB); + LOAD_DLL_FN (library, WebPFree); + return true; +} + +#undef WebPGetInfo +#undef WebPGetFeatures +#undef WebPDecodeRGBA +#undef WebPDecodeRGB +#undef WebPFree + +#define WebPGetInfo fn_WebPGetInfo +#define WebPGetFeatures fn_WebPGetFeatures +#define WebPDecodeRGBA fn_WebPDecodeRGBA +#define WebPDecodeRGB fn_WebPDecodeRGB +#define WebPFree fn_WebPFree + +#endif /* WINDOWSNT */ + +/* Load WebP image IMG for use on frame F. Value is true if + successful. */ + +static bool +webp_load (struct frame *f, struct image *img) +{ + ptrdiff_t size = 0; + uint8_t *contents; + Lisp_Object file; + + /* Open the WebP file. */ + Lisp_Object specified_file = image_spec_value (img->spec, QCfile, NULL); + Lisp_Object specified_data = image_spec_value (img->spec, QCdata, NULL); + + if (NILP (specified_data)) + { + int fd; + file = image_find_image_fd (specified_file, &fd); + if (!STRINGP (file)) + { + image_error ("Cannot find image file `%s'", specified_file); + return false; + } + + contents = (uint8_t *) slurp_file (fd, &size); + if (contents == NULL) + { + image_error ("Error loading WebP image `%s'", file); + return false; + } + } + else + { + if (!STRINGP (specified_data)) + { + image_error ("Invalid image data `%s'", specified_data); + return false; + } + contents = (uint8_t*) SSDATA (specified_data); + size = SBYTES (specified_data); + } + + /* Validate the WebP image header. */ + if (!WebPGetInfo (contents, size, NULL, NULL)) + { + if (!NILP (specified_data)) + image_error ("Not a WebP file: `%s'", file); + else + image_error ("Invalid WebP data"); + goto webp_error1; + } + + /* Get WebP features. */ + WebPBitstreamFeatures features; + VP8StatusCode result = WebPGetFeatures (contents, size, &features); + switch (result) + { + case VP8_STATUS_OK: + break; + case VP8_STATUS_NOT_ENOUGH_DATA: + case VP8_STATUS_OUT_OF_MEMORY: + case VP8_STATUS_INVALID_PARAM: + case VP8_STATUS_BITSTREAM_ERROR: + case VP8_STATUS_UNSUPPORTED_FEATURE: + case VP8_STATUS_SUSPENDED: + case VP8_STATUS_USER_ABORT: + default: + /* Error out in all other cases. */ + if (!NILP (specified_data)) + image_error ("Error when interpreting WebP data: `%s'", file); + else + image_error ("Error when interpreting WebP data"); + goto webp_error1; + } + + /* Decode WebP data. */ + uint8_t *decoded; + int width, height; + if (features.has_alpha) + /* Linear [r0, g0, b0, a0, r1, g1, b1, a1, ...] order. */ + decoded = WebPDecodeRGBA (contents, size, &width, &height); + else + /* Linear [r0, g0, b0, r1, g1, b1, ...] order. */ + decoded = WebPDecodeRGB (contents, size, &width, &height); + + if (!(width <= INT_MAX && height <= INT_MAX + && check_image_size (f, width, height))) + { + image_size_error (); + goto webp_error2; + } + + /* Create the x image and pixmap. */ + Emacs_Pix_Container ximg, mask_img; + if (!image_create_x_image_and_pixmap (f, img, width, height, 0, &ximg, false)) + goto webp_error2; + + /* Create an image and pixmap serving as mask if the WebP image + contains an alpha channel. */ + if (features.has_alpha + && !image_create_x_image_and_pixmap (f, img, width, height, 1, &mask_img, true)) + { + image_destroy_x_image (ximg); + image_clear_image_1 (f, img, CLEAR_IMAGE_PIXMAP); + goto webp_error2; + } + + /* Fill the X image and mask from WebP data. */ + init_color_table (); + + uint8_t *p = decoded; + for (int y = 0; y < height; ++y) + { + for (int x = 0; x < width; ++x) + { + int r = *p++ << 8; + int g = *p++ << 8; + int b = *p++ << 8; + PUT_PIXEL (ximg, x, y, lookup_rgb_color (f, r, g, b)); + + /* An alpha channel associates variable transparency with an + image. WebP allows up to 256 levels of partial transparency. + We handle this like with PNG (which see), using the frame's + background color to combine the image with. */ + if (features.has_alpha) + { + if (mask_img) + PUT_PIXEL (mask_img, x, y, *p > 0 ? PIX_MASK_DRAW : PIX_MASK_RETAIN); + ++p; + } + } + } + +#ifdef COLOR_TABLE_SUPPORT + /* Remember colors allocated for this image. */ + img->colors = colors_in_color_table (&img->ncolors); + free_color_table (); +#endif /* COLOR_TABLE_SUPPORT */ + + /* Put ximg into the image. */ + image_put_x_image (f, img, ximg, 0); + + /* Same for the mask. */ + if (mask_img) + { + /* Fill in the background_transparent field while we have the + mask handy. Casting avoids a GCC warning. */ + image_background_transparent (img, f, (Emacs_Pix_Context)mask_img); + + image_put_x_image (f, img, mask_img, 1); + } + + img->width = width; + img->height = height; + + /* Clean up. */ + WebPFree (decoded); + if (NILP (specified_data)) + xfree (contents); + return true; + + webp_error2: + WebPFree (decoded); + + webp_error1: + if (NILP (specified_data)) + xfree (contents); + return false; +} + +#endif /* HAVE_WEBP */ + + #ifdef HAVE_IMAGEMAGICK +\f /*********************************************************************** ImageMagick ***********************************************************************/ @@ -10725,6 +11001,10 @@ initialize_image_type (struct image_type const *type) #if defined HAVE_XPM || defined HAVE_NS { SYMBOL_INDEX (Qxpm), xpm_image_p, xpm_load, image_clear_image, IMAGE_TYPE_INIT (init_xpm_functions) }, +#endif +#if defined HAVE_WEBP + { SYMBOL_INDEX (Qwebp), webp_image_p, webp_load, image_clear_image, + IMAGE_TYPE_INIT (init_webp_functions) }, #endif { SYMBOL_INDEX (Qxbm), xbm_image_p, xbm_load, image_clear_image }, { SYMBOL_INDEX (Qpbm), pbm_image_p, pbm_load, image_clear_image }, @@ -10891,6 +11171,11 @@ syms_of_image (void) add_image_type (Qpng); #endif +#if defined (HAVE_WEBP) + DEFSYM (Qwebp, "webp"); + add_image_type (Qwebp); +#endif + #if defined (HAVE_IMAGEMAGICK) DEFSYM (Qimagemagick, "imagemagick"); add_image_type (Qimagemagick); diff --git a/test/data/image/black.webp b/test/data/image/black.webp new file mode 100644 index 0000000000000000000000000000000000000000..5dbe716415b16e6111ac92ec3978411d95be08e3 GIT binary patch literal 37780 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 literal 0 HcmV?d00001 diff --git a/test/lisp/image-tests.el b/test/lisp/image-tests.el index aa8600609c..c34c152cc9 100644 --- a/test/lisp/image-tests.el +++ b/test/lisp/image-tests.el @@ -49,12 +49,14 @@ image--set-property (should (equal image '(image))))) (ert-deftest image-find-image () - (find-image '((:type xpm :file "undo.xpm"))) - (find-image '((:type png :file "newsticker/rss-feed.png" :ascent center)))) + (should (listp (find-image '((:type xpm :file "undo.xpm"))))) + (should (listp (find-image '((:type png :file "newsticker/rss-feed.png" :ascent center))))) + (should-not (find-image '((:type png :file "does-not-exist-foo-bar.png"))))) (ert-deftest image-type-from-file-name () (should (eq (image-type-from-file-name "foo.jpg") 'jpeg)) - (should (eq (image-type-from-file-name "foo.png") 'png))) + (should (eq (image-type-from-file-name "foo.png") 'png)) + (should (eq (image-type-from-file-name "foo.webp") 'webp))) (ert-deftest image-type/from-filename () ;; On emba, `image-types' and `image-load-path' do not exist. diff --git a/test/src/image-tests.el b/test/src/image-tests.el index d5e3a7cc5c..b921739a05 100644 --- a/test/src/image-tests.el +++ b/test/src/image-tests.el @@ -44,6 +44,8 @@ image-tests--files (tiff . ,(expand-file-name "nextstep/GNUstep/Emacs.base/Resources/emacs.tiff" source-directory)) + (webp . ,(expand-file-name "test/data/image/black.webp" + source-directory)) (xbm . ,(find-image '((:file "gnus/gnus.xbm" :type xbm)))) (xpm . ,(find-image '((:file "splash.xpm" :type xpm)))) ;; TODO: gif @@ -86,6 +88,13 @@ image-tests-image-size/tiff (should (floatp a)) (should (floatp b))))) +(ert-deftest image-tests-image-size/webp () + (image-skip-unless 'webp) + (pcase (image-size (create-image (cdr (assq 'webp image-tests--files)))) + (`(,a . ,b) + (should (floatp a)) + (should (floatp b))))) + (ert-deftest image-tests-image-size/xbm () (image-skip-unless 'xbm) (pcase (image-size (cdr (assq 'xbm image-tests--files))) @@ -130,7 +139,12 @@ image-tests-image-mask-p/svg (ert-deftest image-tests-image-mask-p/tiff () (image-skip-unless 'tiff) (should-not (image-mask-p (create-image - (cdr (assq 'tiff image-tests--files)))))) + (cdr (assq 'tiff image-tests--files)))))) + +(ert-deftest image-tests-image-mask-p/webp () + (image-skip-unless 'webp) + (should-not (image-mask-p (create-image + (cdr (assq 'webp image-tests--files)))))) (ert-deftest image-tests-image-mask-p/xbm () (image-skip-unless 'xbm) @@ -173,7 +187,12 @@ image-tests-image-metadata/svg (ert-deftest image-tests-image-metadata/tiff () (image-skip-unless 'tiff) (should-not (image-metadata - (create-image (cdr (assq 'tiff image-tests--files)))))) + (create-image (cdr (assq 'tiff image-tests--files)))))) + +(ert-deftest image-tests-image-metadata/webp () + (image-skip-unless 'webp) + (should-not (image-metadata + (create-image (cdr (assq 'webp image-tests--files)))))) (ert-deftest image-tests-image-metadata/xbm () (image-skip-unless 'xbm) -- 2.30.2 ^ permalink raw reply related [flat|nested] 21+ messages in thread
* bug#51296: [PATCH] Add WebP format support 2021-10-19 23:27 bug#51296: [PATCH] Add WebP format support Stefan Kangas @ 2021-10-20 8:42 ` Lars Ingebrigtsen 2021-10-20 12:40 ` Stefan Kangas 2021-10-20 13:02 ` Stefan Kangas 1 sibling, 1 reply; 21+ messages in thread From: Lars Ingebrigtsen @ 2021-10-20 8:42 UTC (permalink / raw) To: Stefan Kangas; +Cc: 51296 Stefan Kangas <stefan@marxist.se> writes: > WebP is an image format promoted by Google since 2010 with a promise of > decreased file sizes. Adoption in web browsers have been slow, but in > 2019, Firefox added support for it, and other browsers followed.[1] > Since then, this image format has become increasingly common on the web, > and is reported to be used on 2.5 % of all websites.[2] Yeah, it doesn't seem to be going away, so I think it makes sense to add support for it in Emacs. (Haven't tried the patch, though.) > The attached patch does that, by using libwebp where it is available.[3] [...] > [3] https://developers.google.com/speed/webp/download libwebp seems to be available in Debian/bookwork, at least: libwebp6/testing,now 0.6.1-2.1 amd64 [installed,automatic] -- (domestic pets only, the antidote for overdose, milk.) bloggy blog: http://lars.ingebrigtsen.no ^ permalink raw reply [flat|nested] 21+ messages in thread
* bug#51296: [PATCH] Add WebP format support 2021-10-20 8:42 ` Lars Ingebrigtsen @ 2021-10-20 12:40 ` Stefan Kangas 0 siblings, 0 replies; 21+ messages in thread From: Stefan Kangas @ 2021-10-20 12:40 UTC (permalink / raw) To: Lars Ingebrigtsen; +Cc: 51296 Lars Ingebrigtsen <larsi@gnus.org> writes: > libwebp seems to be available in Debian/bookwork, at least: > > libwebp6/testing,now 0.6.1-2.1 amd64 [installed,automatic] I think it should be available in any reasonably current distribution at this point, see e.g.: https://src.fedoraproject.org/rpms/libwebp https://archlinux.org/packages/extra/x86_64/libwebp/ https://guix.gnu.org/packages/libwebp-1.1.0/ https://software.opensuse.org/package/libwebp Looking at the dependencies in the various distributions, it seems to be used by chromium and gimp (hard dependencies in Debian stable/bookworm), but also e.g. ffmpeg and graphicsmagick. I should also add that libwebp is BSD licensed. ^ permalink raw reply [flat|nested] 21+ messages in thread
* bug#51296: [PATCH] Add WebP format support 2021-10-19 23:27 bug#51296: [PATCH] Add WebP format support Stefan Kangas 2021-10-20 8:42 ` Lars Ingebrigtsen @ 2021-10-20 13:02 ` Stefan Kangas 2021-10-20 13:14 ` Eli Zaretskii 1 sibling, 1 reply; 21+ messages in thread From: Stefan Kangas @ 2021-10-20 13:02 UTC (permalink / raw) To: 51296 Stefan Kangas <stefan@marxist.se> writes: > Reviews are welcome, as usual. Another thing that I forgot to add is that this is 100 % untested on MS-Windows. I made an attempt to add in the DLL-stuff based on pattern recognition and guesswork, but it'd hardly be surprising if I messed up somewhere. ^ permalink raw reply [flat|nested] 21+ messages in thread
* bug#51296: [PATCH] Add WebP format support 2021-10-20 13:02 ` Stefan Kangas @ 2021-10-20 13:14 ` Eli Zaretskii 2021-10-20 15:22 ` Stefan Kangas 0 siblings, 1 reply; 21+ messages in thread From: Eli Zaretskii @ 2021-10-20 13:14 UTC (permalink / raw) To: Stefan Kangas; +Cc: 51296 > From: Stefan Kangas <stefan@marxist.se> > Date: Wed, 20 Oct 2021 06:02:09 -0700 > > Stefan Kangas <stefan@marxist.se> writes: > > > Reviews are welcome, as usual. > > Another thing that I forgot to add is that this is 100 % untested on > MS-Windows. I made an attempt to add in the DLL-stuff based on pattern > recognition and guesswork, but it'd hardly be surprising if I messed up > somewhere. You missed the fact that the MW-Windows build loads image libraries on-demand, when/if the library is first required. That affects the way we support these in configure.ac, and it also needs an addition to dynamic-library-alist in w32-win.el. It should be easy to add those nits (assuming the code works ;-) ^ permalink raw reply [flat|nested] 21+ messages in thread
* bug#51296: [PATCH] Add WebP format support 2021-10-20 13:14 ` Eli Zaretskii @ 2021-10-20 15:22 ` Stefan Kangas 2021-10-20 16:35 ` Eli Zaretskii 0 siblings, 1 reply; 21+ messages in thread From: Stefan Kangas @ 2021-10-20 15:22 UTC (permalink / raw) To: Eli Zaretskii; +Cc: 51296 [-- Attachment #1: Type: text/plain, Size: 550 bytes --] Eli Zaretskii <eliz@gnu.org> writes: > You missed the fact that the MW-Windows build loads image libraries > on-demand, when/if the library is first required. That affects the > way we support these in configure.ac, and it also needs an addition to > dynamic-library-alist in w32-win.el. > > It should be easy to add those nits (assuming the code works ;-) I attempted to add the `dynamic-library-alist' part in the attached, and I also added a NEWS item. I suppose someone who actually knows MS-Windows stuff and can test it should take a look. [-- Attachment #2: 0001-Add-WebP-format-support.patch --] [-- Type: text/x-diff, Size: 40537 bytes --] From 577aa42da0e0bc1a30c934779038556af7985c52 Mon Sep 17 00:00:00 2001 From: Stefan Kangas <stefan@marxist.se> Date: Fri, 15 Oct 2021 05:25:39 +0200 Subject: [PATCH] Add WebP format support * configure.ac (--with-webp): New option. (HAVE_WEBP): New variable. * src/image.c (enum webp_keyword_index) [HAVE_WEBP]: New enum. (webp_format) [HAVE_WEBP]: New variable. (webp_image_p, init_webp_functions, webp_load) [HAVE_WEBP]: New functions for WebP support. (image_types) [HAVE_WEBP]: Define WebP format. (syms_of_image) <Qwebp> [HAVE_WEBP]: New DEFSYM. Add image type Qwebp. * src/Makefile.in (LIBIMAGE): Add WEBP_LIBS. * lisp/files.el (auto-mode-alist): * lisp/image-file.el (image-file-name-extensions): * lisp/image.el (image-type-header-regexps) (image-type-file-name-regexps, image-type-auto-detectable): Add WebP support. * lisp/term/w32-win.el (dynamic-library-alist): Add the libwebp DLL. * INSTALL: * admin/CPP-DEFINES: * doc/lispref/display.texi (Image Formats, Other Image Types): * nt/INSTALL: Document WebP support. * test/lisp/image-tests.el (image-find-image) (image-type-from-file-name): Expand tests. * test/src/image-tests.el (image-tests--files): Add WebP. (image-tests-image-size/webp, image-tests-image-mask-p/webp) (image-tests-image-metadata/webp): New tests. * test/data/image/black.webp: New file. --- INSTALL | 2 + admin/CPP-DEFINES | 1 + configure.ac | 19 +++ doc/lispref/display.texi | 11 +- etc/NEWS | 5 + lisp/files.el | 1 + lisp/image-file.el | 2 +- lisp/image.el | 3 + lisp/term/w32-win.el | 1 + nt/INSTALL | 9 +- src/Makefile.in | 2 +- src/image.c | 285 +++++++++++++++++++++++++++++++++++++ test/data/image/black.webp | Bin 0 -> 37780 bytes test/lisp/image-tests.el | 8 +- test/src/image-tests.el | 23 ++- 15 files changed, 359 insertions(+), 13 deletions(-) create mode 100644 test/data/image/black.webp diff --git a/INSTALL b/INSTALL index 6207f43cec..21298422af 100644 --- a/INSTALL +++ b/INSTALL @@ -187,6 +187,7 @@ X11 is being used. X libtiff for TIFF: http://www.simplesystems.org/libtiff/ X libgif for GIF: http://giflib.sourceforge.net/ librsvg2 for SVG: https://wiki.gnome.org/Projects/LibRsvg + libwebp for WebP: https://developers.google.com/speed/webp/ If you supply the appropriate --without-LIB option, 'configure' will omit the corresponding library from Emacs, even if that makes for a @@ -313,6 +314,7 @@ or more of these options: --without-gif for GIF image support --without-png for PNG image support --without-rsvg for SVG image support + --without-webp for WebP image support Although ImageMagick support is disabled by default due to security and stability concerns, you can enable it with --with-imagemagick. diff --git a/admin/CPP-DEFINES b/admin/CPP-DEFINES index 68c12438f5..634d6f3f3b 100644 --- a/admin/CPP-DEFINES +++ b/admin/CPP-DEFINES @@ -287,6 +287,7 @@ HAVE_UTIMENSAT HAVE_UTMP_H HAVE_VFORK HAVE_VFORK_H +HAVE_WEBP HAVE_WCHAR_H HAVE_WCHAR_T HAVE_WINDOW_SYSTEM diff --git a/configure.ac b/configure.ac index 9ab0314428..17ac106d84 100644 --- a/configure.ac +++ b/configure.ac @@ -447,6 +447,7 @@ AC_DEFUN OPTION_DEFAULT_ON([gif],[don't compile with GIF image support]) OPTION_DEFAULT_ON([png],[don't compile with PNG image support]) OPTION_DEFAULT_ON([rsvg],[don't compile with SVG image support]) +OPTION_DEFAULT_ON([webp],[don't compile with WebP image support]) OPTION_DEFAULT_ON([lcms2],[don't compile with Little CMS support]) OPTION_DEFAULT_ON([libsystemd],[don't compile with libsystemd support]) OPTION_DEFAULT_ON([cairo],[don't compile with Cairo drawing]) @@ -2588,6 +2589,23 @@ AC_DEFUN fi fi +HAVE_WEBP=no +if test "${HAVE_X11}" = "yes" || test "${HAVE_NS}" = "yes" || test "${opsys}" = "mingw32"; then + if test "${with_webp}" != "no"; then + WEBP_REQUIRED=0.6.0 + WEBP_MODULE="libwebp >= $WEBP_REQUIRED" + + EMACS_CHECK_MODULES([WEBP], [$WEBP_MODULE]) + AC_SUBST(WEBP_CFLAGS) + AC_SUBST(WEBP_LIBS) + + if test $HAVE_WEBP = yes; then + AC_DEFINE(HAVE_WEBP, 1, [Define to 1 if using libwebp.]) + CFLAGS="$CFLAGS $WEBP_CFLAGS" + fi + fi +fi + HAVE_IMAGEMAGICK=no if test "${HAVE_X11}" = "yes" || test "${HAVE_NS}" = "yes" || test "${HAVE_W32}" = "yes"; then if test "${with_imagemagick}" != "no"; then @@ -5928,6 +5946,7 @@ AC_DEFUN Does Emacs use a gif library? ${HAVE_GIF} $LIBGIF Does Emacs use a png library? ${HAVE_PNG} $LIBPNG Does Emacs use -lrsvg-2? ${HAVE_RSVG} + Does Emacs use -lwebp? ${HAVE_WEBP} Does Emacs use cairo? ${HAVE_CAIRO} Does Emacs use -llcms2? ${HAVE_LCMS2} Does Emacs use imagemagick? ${HAVE_IMAGEMAGICK} diff --git a/doc/lispref/display.texi b/doc/lispref/display.texi index 16577d13c1..9c378a3027 100644 --- a/doc/lispref/display.texi +++ b/doc/lispref/display.texi @@ -5264,13 +5264,13 @@ Image Formats Supported image formats (and the required support libraries) include PBM and XBM (which do not depend on support libraries and are always available), XPM (@code{libXpm}), GIF (@code{libgif} or -@code{libungif}), JPEG (@code{libjpeg}), TIFF -(@code{libtiff}), PNG (@code{libpng}), and SVG (@code{librsvg}). +@code{libungif}), JPEG (@code{libjpeg}), TIFF (@code{libtiff}), PNG +(@code{libpng}), SVG (@code{librsvg}), and WebP (@code{libwebp}). Each of these image formats is associated with an @dfn{image type symbol}. The symbols for the above formats are, respectively, -@code{pbm}, @code{xbm}, @code{xpm}, @code{gif}, -@code{jpeg}, @code{tiff}, @code{png}, and @code{svg}. +@code{pbm}, @code{xbm}, @code{xpm}, @code{gif}, @code{jpeg}, +@code{tiff}, @code{png}, @code{svg}, and @code{webp}. Furthermore, if you build Emacs with ImageMagick (@code{libMagickWand}) support, Emacs can display any image format @@ -6274,6 +6274,9 @@ Other Image Types @item TIFF Image type @code{tiff}. Supports the @code{:index} property. @xref{Multi-Frame Images}. + +@item WebP +Image type @code{webp}. @end table @node Defining Images diff --git a/etc/NEWS b/etc/NEWS index f9fe72e91f..0a44e6885f 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -53,6 +53,11 @@ time. Jumping to source from "*Help*" buffer moves the point when the source buffer is already open. Now, the old point is pushed to mark ring. +** Support for the WebP image format. +This support is built by default when the libwebp library is +available. To disable it, use the '--without-webp' configure flag. +Image specifiers can now use ':type webp'. + \f * Editing Changes in Emacs 29.1 diff --git a/lisp/files.el b/lisp/files.el index 5a6a33721b..e6b94a4a1a 100644 --- a/lisp/files.el +++ b/lisp/files.el @@ -2758,6 +2758,7 @@ auto-mode-alist ("\\.gif\\'" . image-mode) ("\\.png\\'" . image-mode) ("\\.jpe?g\\'" . image-mode) + ("\\.webp\\'" . image-mode) ("\\.te?xt\\'" . text-mode) ("\\.[tT]e[xX]\\'" . tex-mode) ("\\.ins\\'" . tex-mode) ;Installation files for TeX packages. diff --git a/lisp/image-file.el b/lisp/image-file.el index fbc9eaaf94..6df43f737d 100644 --- a/lisp/image-file.el +++ b/lisp/image-file.el @@ -37,7 +37,7 @@ ;;;###autoload (defcustom image-file-name-extensions - (purecopy '("png" "jpeg" "jpg" "gif" "tiff" "tif" "xbm" "xpm" "pbm" "pgm" "ppm" "pnm" "svg")) + (purecopy '("png" "jpeg" "jpg" "gif" "tiff" "tif" "xbm" "xpm" "pbm" "pgm" "ppm" "pnm" "svg" "webp")) "A list of image-file filename extensions. Filenames having one of these extensions are considered image files, in addition to those matching `image-file-name-regexps'. diff --git a/lisp/image.el b/lisp/image.el index 2022b41d1f..5343e26d03 100644 --- a/lisp/image.el +++ b/lisp/image.el @@ -48,6 +48,7 @@ image-type-header-regexps ("\\`\\(?:MM\0\\*\\|II\\*\0\\)" . tiff) ("\\`[\t\n\r ]*%!PS" . postscript) ("\\`\xff\xd8" . jpeg) ; used to be (image-jpeg-p . jpeg) + ("\\`RIFF....WEBPVP8" . webp) (,(let* ((incomment-re "\\(?:[^-]\\|-[^-]\\)") (comment-re (concat "\\(?:!--" incomment-re "*-->[ \t\r\n]*<\\)"))) (concat "\\(?:<\\?xml[ \t\r\n]+[^>]*>\\)?[ \t\r\n]*<" @@ -67,6 +68,7 @@ image-type-file-name-regexps '(("\\.png\\'" . png) ("\\.gif\\'" . gif) ("\\.jpe?g\\'" . jpeg) + ("\\.webp\\'" . webp) ("\\.bmp\\'" . bmp) ("\\.xpm\\'" . xpm) ("\\.pbm\\'" . pbm) @@ -92,6 +94,7 @@ image-type-auto-detectable (jpeg . maybe) (tiff . maybe) (svg . maybe) + (webp . maybe) (postscript . nil)) "Alist of (IMAGE-TYPE . AUTODETECT) pairs used to auto-detect image files. \(See `image-type-auto-detected-p'). diff --git a/lisp/term/w32-win.el b/lisp/term/w32-win.el index 5d1dc60667..04da530da5 100644 --- a/lisp/term/w32-win.el +++ b/lisp/term/w32-win.el @@ -274,6 +274,7 @@ libgnutls-version '(gif "libgif-6.dll" "giflib5.dll" "gif.dll") '(gif "libgif-5.dll" "giflib4.dll" "libungif4.dll" "libungif.dll"))) '(svg "librsvg-2-2.dll") + '(libwebp "libwebp.dll") '(gdk-pixbuf "libgdk_pixbuf-2.0-0.dll") '(glib "libglib-2.0-0.dll") '(gio "libgio-2.0-0.dll") diff --git a/nt/INSTALL b/nt/INSTALL index 9f543151a9..a39057c66c 100644 --- a/nt/INSTALL +++ b/nt/INSTALL @@ -488,6 +488,7 @@ build will run on Windows 9X and newer systems). Does Emacs use a gif library? yes Does Emacs use a png library? yes Does Emacs use -lrsvg-2? yes + Does Emacs use -lwebp? yes Does Emacs use cairo? no Does Emacs use -llcms2? yes Does Emacs use imagemagick? no @@ -597,8 +598,8 @@ build will run on Windows 9X and newer systems). * Optional image library support In addition to its "native" image formats (pbm and xbm), Emacs can - handle other image types: xpm, tiff, gif, png, jpeg and experimental - support for svg. + handle other image types: xpm, tiff, gif, png, jpeg, webp and + experimental support for svg. To build Emacs with support for them, the corresponding headers must be in the include path and libraries should be where the linker @@ -736,6 +737,10 @@ build will run on Windows 9X and newer systems). without it by specifying the --without-rsvg switch to the configure script. + For WebP images you will need libwebp: + + https://developers.google.com/speed/webp/ + Binaries for the other image libraries can be found on the ezwinports site or at the GnuWin32 project (the latter are generally very old, so not recommended). Note specifically that, due to some diff --git a/src/Makefile.in b/src/Makefile.in index 6d75e3537a..7c977e34ea 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -124,7 +124,7 @@ LIB_MATH= ## -lpthread, or empty. LIB_PTHREAD=@LIB_PTHREAD@ -LIBIMAGE=@LIBTIFF@ @LIBJPEG@ @LIBPNG@ @LIBGIF@ @LIBXPM@ +LIBIMAGE=@LIBTIFF@ @LIBJPEG@ @LIBPNG@ @LIBGIF@ @LIBXPM@ @WEBP_LIBS@ XCB_LIBS=@XCB_LIBS@ XFT_LIBS=@XFT_LIBS@ diff --git a/src/image.c b/src/image.c index ff05741b2c..6ff172504f 100644 --- a/src/image.c +++ b/src/image.c @@ -8739,8 +8739,284 @@ gif_load (struct frame *f, struct image *img) #endif /* HAVE_GIF */ +#ifdef HAVE_WEBP + +\f +/*********************************************************************** + WebP + ***********************************************************************/ + +#include "webp/decode.h" + +/* Indices of image specification fields in webp_format, below. */ + +enum webp_keyword_index +{ + WEBP_TYPE, + WEBP_DATA, + WEBP_FILE, + WEBP_ASCENT, + WEBP_MARGIN, + WEBP_RELIEF, + WEBP_ALGORITHM, + WEBP_HEURISTIC_MASK, + WEBP_MASK, + WEBP_BACKGROUND, + WEBP_LAST +}; + +/* Vector of image_keyword structures describing the format + of valid user-defined image specifications. */ + +static const struct image_keyword webp_format[WEBP_LAST] = +{ + {":type", IMAGE_SYMBOL_VALUE, 1}, + {":data", IMAGE_STRING_VALUE, 0}, + {":file", IMAGE_STRING_VALUE, 0}, + {":ascent", IMAGE_ASCENT_VALUE, 0}, + {":margin", IMAGE_NON_NEGATIVE_INTEGER_VALUE_OR_PAIR, 0}, + {":relief", IMAGE_INTEGER_VALUE, 0}, + {":conversion", IMAGE_DONT_CHECK_VALUE_TYPE, 0}, + {":heuristic-mask", IMAGE_DONT_CHECK_VALUE_TYPE, 0}, + {":mask", IMAGE_DONT_CHECK_VALUE_TYPE, 0}, + {":background", IMAGE_STRING_OR_NIL_VALUE, 0} +}; + +/* Return true if OBJECT is a valid WebP image specification. */ + +static bool +webp_image_p (Lisp_Object object) +{ + struct image_keyword fmt[WEBP_LAST]; + memcpy (fmt, webp_format, sizeof fmt); + + if (!parse_image_spec (object, fmt, WEBP_LAST, Qwebp)) + return false; + + /* Must specify either the :data or :file keyword. */ + return fmt[WEBP_FILE].count + fmt[WEBP_DATA].count == 1; +} + +#ifdef WINDOWSNT + +/* WebP library details. */ + +DEF_DLL_FN (int, WebPGetInfo, (const uint8_t* data, size_t data_size, int* width, int* height)); +DEF_DLL_FN (VP8StatusCode, WebPGetFeatures, + (const uint8_t* data, size_t data_size, WebPBitstreamFeatures* features)); +DEF_DLL_FN (uint8_t*, WebPDecodeRGB, + (const uint8_t* data, size_t data_size, int* width, int* height)); +DEF_DLL_FN (uint8_t*, WebPDecodeBGR, + (const uint8_t* data, size_t data_size, int* width, int* height)); +DEF_DLL_FN (void, WebPFreeDecBuffer + (WebPDecBuffer* buffer)); + +static bool +init_webp_functions (void) +{ + HMODULE library; + + if (!(library = w32_delayed_load (Qwebp))) + return 0; + + LOAD_DLL_FN (library, WebPGetInfo); + LOAD_DLL_FN (library, WebPGetFeatures); + LOAD_DLL_FN (library, WebPDecodeRGBA); + LOAD_DLL_FN (library, WebPDecodeRGB); + LOAD_DLL_FN (library, WebPFree); + return true; +} + +#undef WebPGetInfo +#undef WebPGetFeatures +#undef WebPDecodeRGBA +#undef WebPDecodeRGB +#undef WebPFree + +#define WebPGetInfo fn_WebPGetInfo +#define WebPGetFeatures fn_WebPGetFeatures +#define WebPDecodeRGBA fn_WebPDecodeRGBA +#define WebPDecodeRGB fn_WebPDecodeRGB +#define WebPFree fn_WebPFree + +#endif /* WINDOWSNT */ + +/* Load WebP image IMG for use on frame F. Value is true if + successful. */ + +static bool +webp_load (struct frame *f, struct image *img) +{ + ptrdiff_t size = 0; + uint8_t *contents; + Lisp_Object file; + + /* Open the WebP file. */ + Lisp_Object specified_file = image_spec_value (img->spec, QCfile, NULL); + Lisp_Object specified_data = image_spec_value (img->spec, QCdata, NULL); + + if (NILP (specified_data)) + { + int fd; + file = image_find_image_fd (specified_file, &fd); + if (!STRINGP (file)) + { + image_error ("Cannot find image file `%s'", specified_file); + return false; + } + + contents = (uint8_t *) slurp_file (fd, &size); + if (contents == NULL) + { + image_error ("Error loading WebP image `%s'", file); + return false; + } + } + else + { + if (!STRINGP (specified_data)) + { + image_error ("Invalid image data `%s'", specified_data); + return false; + } + contents = (uint8_t*) SSDATA (specified_data); + size = SBYTES (specified_data); + } + + /* Validate the WebP image header. */ + if (!WebPGetInfo (contents, size, NULL, NULL)) + { + if (!NILP (specified_data)) + image_error ("Not a WebP file: `%s'", file); + else + image_error ("Invalid WebP data"); + goto webp_error1; + } + + /* Get WebP features. */ + WebPBitstreamFeatures features; + VP8StatusCode result = WebPGetFeatures (contents, size, &features); + switch (result) + { + case VP8_STATUS_OK: + break; + case VP8_STATUS_NOT_ENOUGH_DATA: + case VP8_STATUS_OUT_OF_MEMORY: + case VP8_STATUS_INVALID_PARAM: + case VP8_STATUS_BITSTREAM_ERROR: + case VP8_STATUS_UNSUPPORTED_FEATURE: + case VP8_STATUS_SUSPENDED: + case VP8_STATUS_USER_ABORT: + default: + /* Error out in all other cases. */ + if (!NILP (specified_data)) + image_error ("Error when interpreting WebP data: `%s'", file); + else + image_error ("Error when interpreting WebP data"); + goto webp_error1; + } + + /* Decode WebP data. */ + uint8_t *decoded; + int width, height; + if (features.has_alpha) + /* Linear [r0, g0, b0, a0, r1, g1, b1, a1, ...] order. */ + decoded = WebPDecodeRGBA (contents, size, &width, &height); + else + /* Linear [r0, g0, b0, r1, g1, b1, ...] order. */ + decoded = WebPDecodeRGB (contents, size, &width, &height); + + if (!(width <= INT_MAX && height <= INT_MAX + && check_image_size (f, width, height))) + { + image_size_error (); + goto webp_error2; + } + + /* Create the x image and pixmap. */ + Emacs_Pix_Container ximg, mask_img; + if (!image_create_x_image_and_pixmap (f, img, width, height, 0, &ximg, false)) + goto webp_error2; + + /* Create an image and pixmap serving as mask if the WebP image + contains an alpha channel. */ + if (features.has_alpha + && !image_create_x_image_and_pixmap (f, img, width, height, 1, &mask_img, true)) + { + image_destroy_x_image (ximg); + image_clear_image_1 (f, img, CLEAR_IMAGE_PIXMAP); + goto webp_error2; + } + + /* Fill the X image and mask from WebP data. */ + init_color_table (); + + uint8_t *p = decoded; + for (int y = 0; y < height; ++y) + { + for (int x = 0; x < width; ++x) + { + int r = *p++ << 8; + int g = *p++ << 8; + int b = *p++ << 8; + PUT_PIXEL (ximg, x, y, lookup_rgb_color (f, r, g, b)); + + /* An alpha channel associates variable transparency with an + image. WebP allows up to 256 levels of partial transparency. + We handle this like with PNG (which see), using the frame's + background color to combine the image with. */ + if (features.has_alpha) + { + if (mask_img) + PUT_PIXEL (mask_img, x, y, *p > 0 ? PIX_MASK_DRAW : PIX_MASK_RETAIN); + ++p; + } + } + } + +#ifdef COLOR_TABLE_SUPPORT + /* Remember colors allocated for this image. */ + img->colors = colors_in_color_table (&img->ncolors); + free_color_table (); +#endif /* COLOR_TABLE_SUPPORT */ + + /* Put ximg into the image. */ + image_put_x_image (f, img, ximg, 0); + + /* Same for the mask. */ + if (mask_img) + { + /* Fill in the background_transparent field while we have the + mask handy. Casting avoids a GCC warning. */ + image_background_transparent (img, f, (Emacs_Pix_Context)mask_img); + + image_put_x_image (f, img, mask_img, 1); + } + + img->width = width; + img->height = height; + + /* Clean up. */ + WebPFree (decoded); + if (NILP (specified_data)) + xfree (contents); + return true; + + webp_error2: + WebPFree (decoded); + + webp_error1: + if (NILP (specified_data)) + xfree (contents); + return false; +} + +#endif /* HAVE_WEBP */ + + #ifdef HAVE_IMAGEMAGICK +\f /*********************************************************************** ImageMagick ***********************************************************************/ @@ -10725,6 +11001,10 @@ initialize_image_type (struct image_type const *type) #if defined HAVE_XPM || defined HAVE_NS { SYMBOL_INDEX (Qxpm), xpm_image_p, xpm_load, image_clear_image, IMAGE_TYPE_INIT (init_xpm_functions) }, +#endif +#if defined HAVE_WEBP + { SYMBOL_INDEX (Qwebp), webp_image_p, webp_load, image_clear_image, + IMAGE_TYPE_INIT (init_webp_functions) }, #endif { SYMBOL_INDEX (Qxbm), xbm_image_p, xbm_load, image_clear_image }, { SYMBOL_INDEX (Qpbm), pbm_image_p, pbm_load, image_clear_image }, @@ -10891,6 +11171,11 @@ syms_of_image (void) add_image_type (Qpng); #endif +#if defined (HAVE_WEBP) + DEFSYM (Qwebp, "webp"); + add_image_type (Qwebp); +#endif + #if defined (HAVE_IMAGEMAGICK) DEFSYM (Qimagemagick, "imagemagick"); add_image_type (Qimagemagick); diff --git a/test/data/image/black.webp b/test/data/image/black.webp new file mode 100644 index 0000000000000000000000000000000000000000..5dbe716415b16e6111ac92ec3978411d95be08e3 GIT binary patch literal 37780 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 literal 0 HcmV?d00001 diff --git a/test/lisp/image-tests.el b/test/lisp/image-tests.el index aa8600609c..c34c152cc9 100644 --- a/test/lisp/image-tests.el +++ b/test/lisp/image-tests.el @@ -49,12 +49,14 @@ image--set-property (should (equal image '(image))))) (ert-deftest image-find-image () - (find-image '((:type xpm :file "undo.xpm"))) - (find-image '((:type png :file "newsticker/rss-feed.png" :ascent center)))) + (should (listp (find-image '((:type xpm :file "undo.xpm"))))) + (should (listp (find-image '((:type png :file "newsticker/rss-feed.png" :ascent center))))) + (should-not (find-image '((:type png :file "does-not-exist-foo-bar.png"))))) (ert-deftest image-type-from-file-name () (should (eq (image-type-from-file-name "foo.jpg") 'jpeg)) - (should (eq (image-type-from-file-name "foo.png") 'png))) + (should (eq (image-type-from-file-name "foo.png") 'png)) + (should (eq (image-type-from-file-name "foo.webp") 'webp))) (ert-deftest image-type/from-filename () ;; On emba, `image-types' and `image-load-path' do not exist. diff --git a/test/src/image-tests.el b/test/src/image-tests.el index d5e3a7cc5c..b921739a05 100644 --- a/test/src/image-tests.el +++ b/test/src/image-tests.el @@ -44,6 +44,8 @@ image-tests--files (tiff . ,(expand-file-name "nextstep/GNUstep/Emacs.base/Resources/emacs.tiff" source-directory)) + (webp . ,(expand-file-name "test/data/image/black.webp" + source-directory)) (xbm . ,(find-image '((:file "gnus/gnus.xbm" :type xbm)))) (xpm . ,(find-image '((:file "splash.xpm" :type xpm)))) ;; TODO: gif @@ -86,6 +88,13 @@ image-tests-image-size/tiff (should (floatp a)) (should (floatp b))))) +(ert-deftest image-tests-image-size/webp () + (image-skip-unless 'webp) + (pcase (image-size (create-image (cdr (assq 'webp image-tests--files)))) + (`(,a . ,b) + (should (floatp a)) + (should (floatp b))))) + (ert-deftest image-tests-image-size/xbm () (image-skip-unless 'xbm) (pcase (image-size (cdr (assq 'xbm image-tests--files))) @@ -130,7 +139,12 @@ image-tests-image-mask-p/svg (ert-deftest image-tests-image-mask-p/tiff () (image-skip-unless 'tiff) (should-not (image-mask-p (create-image - (cdr (assq 'tiff image-tests--files)))))) + (cdr (assq 'tiff image-tests--files)))))) + +(ert-deftest image-tests-image-mask-p/webp () + (image-skip-unless 'webp) + (should-not (image-mask-p (create-image + (cdr (assq 'webp image-tests--files)))))) (ert-deftest image-tests-image-mask-p/xbm () (image-skip-unless 'xbm) @@ -173,7 +187,12 @@ image-tests-image-metadata/svg (ert-deftest image-tests-image-metadata/tiff () (image-skip-unless 'tiff) (should-not (image-metadata - (create-image (cdr (assq 'tiff image-tests--files)))))) + (create-image (cdr (assq 'tiff image-tests--files)))))) + +(ert-deftest image-tests-image-metadata/webp () + (image-skip-unless 'webp) + (should-not (image-metadata + (create-image (cdr (assq 'webp image-tests--files)))))) (ert-deftest image-tests-image-metadata/xbm () (image-skip-unless 'xbm) -- 2.30.2 ^ permalink raw reply related [flat|nested] 21+ messages in thread
* bug#51296: [PATCH] Add WebP format support 2021-10-20 15:22 ` Stefan Kangas @ 2021-10-20 16:35 ` Eli Zaretskii 2021-10-20 17:13 ` Eli Zaretskii 2021-10-20 21:02 ` Stefan Kangas 0 siblings, 2 replies; 21+ messages in thread From: Eli Zaretskii @ 2021-10-20 16:35 UTC (permalink / raw) To: Stefan Kangas; +Cc: 51296 > From: Stefan Kangas <stefan@marxist.se> > Date: Wed, 20 Oct 2021 08:22:44 -0700 > Cc: 51296@debbugs.gnu.org > > > You missed the fact that the MW-Windows build loads image libraries > > on-demand, when/if the library is first required. That affects the > > way we support these in configure.ac, and it also needs an addition to > > dynamic-library-alist in w32-win.el. > > > > It should be easy to add those nits (assuming the code works ;-) > > I attempted to add the `dynamic-library-alist' part in the attached, and > I also added a NEWS item. I suppose someone who actually knows > MS-Windows stuff and can test it should take a look. Thanks. If you install this when I'm awake, I can fix it in almost real time. For now, just a couple of comments based on reading the patch: > +HAVE_WEBP=no > +if test "${HAVE_X11}" = "yes" || test "${HAVE_NS}" = "yes" || test "${opsys}" = "mingw32"; then > + if test "${with_webp}" != "no"; then > + WEBP_REQUIRED=0.6.0 > + WEBP_MODULE="libwebp >= $WEBP_REQUIRED" > + > + EMACS_CHECK_MODULES([WEBP], [$WEBP_MODULE]) > + AC_SUBST(WEBP_CFLAGS) > + AC_SUBST(WEBP_LIBS) > + > + if test $HAVE_WEBP = yes; then > + AC_DEFINE(HAVE_WEBP, 1, [Define to 1 if using libwebp.]) > + CFLAGS="$CFLAGS $WEBP_CFLAGS" > + fi > + fi > +fi This is sub-optimal: it causes the Windows build to link with -lwebp, which then makes the binary _require_ to have libwebp DLL when Emacs starts. (Unlike on Posix platforms, Windows binaries linked with a shared library insist on finding it at startup, because the Windows dynamic linking functionality needs that to resolve all the entry points.) This would require people who download the prebuilt binaries to also have the library installed, or else Emacs will refuse to start, even if the user has no use for WebP images. So we instead do this (this example is for libpng): # mingw32 loads the library dynamically. if test "$opsys" = mingw32; then AC_CHECK_HEADER([png.h], [HAVE_PNG=yes]) elif test "${HAVE_X11}" = "yes" || test "${HAVE_W32}" = "yes" \ || test "${HAVE_NS}" = "yes"; then EMACS_CHECK_MODULES([PNG], [libpng >= 1.0.0]) if test $HAVE_PNG = yes; then LIBPNG=$PNG_LIBS That is, for MS-Windows we only check for the header, because we need that to compile the code which supports the image type. But we don't add -lFOO to the linker switches. (If you wonder what is HAVE_W32 about, then it's for Cygwin builds that use native w32 GUI "toolkit" instead of X; Cygwin builds are linked like on Unix, and don't load the image libraries on demand.) > --- a/lisp/term/w32-win.el > +++ b/lisp/term/w32-win.el > @@ -274,6 +274,7 @@ libgnutls-version > '(gif "libgif-6.dll" "giflib5.dll" "gif.dll") > '(gif "libgif-5.dll" "giflib4.dll" "libungif4.dll" "libungif.dll"))) > '(svg "librsvg-2-2.dll") > + '(libwebp "libwebp.dll") This requires some Internet search, because DLLs on Windows usually have a numeric version indication in their names, as you see in the other cases. In this case, the name produced by current versions of libwebp seems to be libwebp-7.dll, not just libwebp.dll. But it's probably a good idea to leave libwebp.dll as a fallback. So this should be '(libwebp "libwebp-7.dll" "libwebp.dll") ^ permalink raw reply [flat|nested] 21+ messages in thread
* bug#51296: [PATCH] Add WebP format support 2021-10-20 16:35 ` Eli Zaretskii @ 2021-10-20 17:13 ` Eli Zaretskii 2021-10-20 17:41 ` Stefan Kangas 2021-10-20 21:02 ` Stefan Kangas 1 sibling, 1 reply; 21+ messages in thread From: Eli Zaretskii @ 2021-10-20 17:13 UTC (permalink / raw) To: stefan; +Cc: 51296 > Date: Wed, 20 Oct 2021 19:35:17 +0300 > From: Eli Zaretskii <eliz@gnu.org> > Cc: 51296@debbugs.gnu.org > > > + '(libwebp "libwebp.dll") > > This requires some Internet search, because DLLs on Windows usually > have a numeric version indication in their names, as you see in the > other cases. In this case, the name produced by current versions of > libwebp seems to be libwebp-7.dll, not just libwebp.dll. But it's > probably a good idea to leave libwebp.dll as a fallback. So this > should be > > '(libwebp "libwebp-7.dll" "libwebp.dll") Btw, the Windows binaries available from the libwebp site don't seem to include DLLs, only static libraries. Caveat emptor! ^ permalink raw reply [flat|nested] 21+ messages in thread
* bug#51296: [PATCH] Add WebP format support 2021-10-20 17:13 ` Eli Zaretskii @ 2021-10-20 17:41 ` Stefan Kangas 2021-10-20 18:19 ` Eli Zaretskii 0 siblings, 1 reply; 21+ messages in thread From: Stefan Kangas @ 2021-10-20 17:41 UTC (permalink / raw) To: Eli Zaretskii; +Cc: 51296 Eli Zaretskii <eliz@gnu.org> writes: > Btw, the Windows binaries available from the libwebp site don't seem > to include DLLs, only static libraries. Caveat emptor! Does this change anything with regards to the patch besides the things you already mentioned? ^ permalink raw reply [flat|nested] 21+ messages in thread
* bug#51296: [PATCH] Add WebP format support 2021-10-20 17:41 ` Stefan Kangas @ 2021-10-20 18:19 ` Eli Zaretskii 0 siblings, 0 replies; 21+ messages in thread From: Eli Zaretskii @ 2021-10-20 18:19 UTC (permalink / raw) To: Stefan Kangas; +Cc: 51296 > From: Stefan Kangas <stefan@marxist.se> > Date: Wed, 20 Oct 2021 10:41:34 -0700 > Cc: 51296@debbugs.gnu.org > > Eli Zaretskii <eliz@gnu.org> writes: > > > Btw, the Windows binaries available from the libwebp site don't seem > > to include DLLs, only static libraries. Caveat emptor! > > Does this change anything with regards to the patch besides the things > you already mentioned? No, it just means that people shouldn't download the binaries from there. They were compiled with MSVC, so could depend on proprietary libraries which are part of MSVC installation, anyway. ^ permalink raw reply [flat|nested] 21+ messages in thread
* bug#51296: [PATCH] Add WebP format support 2021-10-20 16:35 ` Eli Zaretskii 2021-10-20 17:13 ` Eli Zaretskii @ 2021-10-20 21:02 ` Stefan Kangas 2021-10-21 0:45 ` Stefan Kangas 1 sibling, 1 reply; 21+ messages in thread From: Stefan Kangas @ 2021-10-20 21:02 UTC (permalink / raw) To: Eli Zaretskii; +Cc: 51296 [-- Attachment #1: Type: text/plain, Size: 459 bytes --] Eli Zaretskii <eliz@gnu.org> writes: > Thanks. If you install this when I'm awake, I can fix it in almost > real time. Excellent, thank you. > This is sub-optimal: it causes the Windows build to link with -lwebp, > which then makes the binary _require_ to have libwebp DLL when Emacs > starts. [...] Thanks for that detailed explanation. I believe this should be all fixed in the attached. > '(libwebp "libwebp-7.dll" "libwebp.dll") Also fixed. [-- Attachment #2: 0001-Add-WebP-format-support.patch --] [-- Type: text/x-diff, Size: 40787 bytes --] From 5e7680613bc60c7b8709a50232fa1ec770814f42 Mon Sep 17 00:00:00 2001 From: Stefan Kangas <stefan@marxist.se> Date: Fri, 15 Oct 2021 05:25:39 +0200 Subject: [PATCH] Add WebP format support * configure.ac (--with-webp): New option. (HAVE_WEBP): New variable. * src/image.c (enum webp_keyword_index) [HAVE_WEBP]: New enum. (webp_format) [HAVE_WEBP]: New variable. (webp_image_p, init_webp_functions, webp_load) [HAVE_WEBP]: New functions for WebP support. (image_types) [HAVE_WEBP]: Define WebP format. (syms_of_image) <Qwebp> [HAVE_WEBP]: New DEFSYM. Add image type Qwebp. * src/Makefile.in (LIBIMAGE): Add WEBP_LIBS. * lisp/files.el (auto-mode-alist): * lisp/image-file.el (image-file-name-extensions): * lisp/image.el (image-type-header-regexps) (image-type-file-name-regexps, image-type-auto-detectable): Add WebP support. * lisp/term/w32-win.el (dynamic-library-alist): Add the libwebp DLL. * INSTALL: * admin/CPP-DEFINES: * doc/lispref/display.texi (Image Formats, Other Image Types): * nt/INSTALL: Document WebP support. * test/lisp/image-tests.el (image-find-image) (image-type-from-file-name): Expand tests. * test/src/image-tests.el (image-tests--files): Add WebP. (image-tests-image-size/webp, image-tests-image-mask-p/webp) (image-tests-image-metadata/webp): New tests. * test/data/image/black.webp: New file. --- INSTALL | 2 + admin/CPP-DEFINES | 1 + configure.ac | 24 ++++ doc/lispref/display.texi | 11 +- etc/NEWS | 5 + lisp/files.el | 1 + lisp/image-file.el | 2 +- lisp/image.el | 3 + lisp/term/w32-win.el | 1 + nt/INSTALL | 9 +- src/Makefile.in | 2 +- src/image.c | 285 +++++++++++++++++++++++++++++++++++++ test/data/image/black.webp | Bin 0 -> 37780 bytes test/lisp/image-tests.el | 8 +- test/src/image-tests.el | 23 ++- 15 files changed, 364 insertions(+), 13 deletions(-) create mode 100644 test/data/image/black.webp diff --git a/INSTALL b/INSTALL index 6207f43cec..21298422af 100644 --- a/INSTALL +++ b/INSTALL @@ -187,6 +187,7 @@ X11 is being used. X libtiff for TIFF: http://www.simplesystems.org/libtiff/ X libgif for GIF: http://giflib.sourceforge.net/ librsvg2 for SVG: https://wiki.gnome.org/Projects/LibRsvg + libwebp for WebP: https://developers.google.com/speed/webp/ If you supply the appropriate --without-LIB option, 'configure' will omit the corresponding library from Emacs, even if that makes for a @@ -313,6 +314,7 @@ or more of these options: --without-gif for GIF image support --without-png for PNG image support --without-rsvg for SVG image support + --without-webp for WebP image support Although ImageMagick support is disabled by default due to security and stability concerns, you can enable it with --with-imagemagick. diff --git a/admin/CPP-DEFINES b/admin/CPP-DEFINES index 68c12438f5..634d6f3f3b 100644 --- a/admin/CPP-DEFINES +++ b/admin/CPP-DEFINES @@ -287,6 +287,7 @@ HAVE_UTIMENSAT HAVE_UTMP_H HAVE_VFORK HAVE_VFORK_H +HAVE_WEBP HAVE_WCHAR_H HAVE_WCHAR_T HAVE_WINDOW_SYSTEM diff --git a/configure.ac b/configure.ac index 9ab0314428..959275398e 100644 --- a/configure.ac +++ b/configure.ac @@ -447,6 +447,7 @@ AC_DEFUN OPTION_DEFAULT_ON([gif],[don't compile with GIF image support]) OPTION_DEFAULT_ON([png],[don't compile with PNG image support]) OPTION_DEFAULT_ON([rsvg],[don't compile with SVG image support]) +OPTION_DEFAULT_ON([webp],[don't compile with WebP image support]) OPTION_DEFAULT_ON([lcms2],[don't compile with Little CMS support]) OPTION_DEFAULT_ON([libsystemd],[don't compile with libsystemd support]) OPTION_DEFAULT_ON([cairo],[don't compile with Cairo drawing]) @@ -2588,6 +2589,28 @@ AC_DEFUN fi fi +### Use -lwebp if available, unless '--with-webp=no' +### mingw32 doesn't use -lwebp, since it loads the library dynamically. +HAVE_WEBP=no +if test "${with_webp}" != "no"; then + if test "$opsys" = mingw32; then + AC_CHECK_HEADER([webp/decode.h], [HAVE_WEBP=yes]) + elif test "${HAVE_X11}" = "yes" || test "${HAVE_W32}" = "yes" \ + || test "${HAVE_NS}" = "yes" then + WEBP_REQUIRED=0.6.0 + WEBP_MODULE="libwebp >= $WEBP_REQUIRED" + + EMACS_CHECK_MODULES([WEBP], [$WEBP_MODULE]) + AC_SUBST(WEBP_CFLAGS) + AC_SUBST(WEBP_LIBS) + + if test $HAVE_WEBP = yes; then + AC_DEFINE(HAVE_WEBP, 1, [Define to 1 if using libwebp.]) + CFLAGS="$CFLAGS $WEBP_CFLAGS" + fi + fi +fi + HAVE_IMAGEMAGICK=no if test "${HAVE_X11}" = "yes" || test "${HAVE_NS}" = "yes" || test "${HAVE_W32}" = "yes"; then if test "${with_imagemagick}" != "no"; then @@ -5928,6 +5951,7 @@ AC_DEFUN Does Emacs use a gif library? ${HAVE_GIF} $LIBGIF Does Emacs use a png library? ${HAVE_PNG} $LIBPNG Does Emacs use -lrsvg-2? ${HAVE_RSVG} + Does Emacs use -lwebp? ${HAVE_WEBP} Does Emacs use cairo? ${HAVE_CAIRO} Does Emacs use -llcms2? ${HAVE_LCMS2} Does Emacs use imagemagick? ${HAVE_IMAGEMAGICK} diff --git a/doc/lispref/display.texi b/doc/lispref/display.texi index 16577d13c1..9c378a3027 100644 --- a/doc/lispref/display.texi +++ b/doc/lispref/display.texi @@ -5264,13 +5264,13 @@ Image Formats Supported image formats (and the required support libraries) include PBM and XBM (which do not depend on support libraries and are always available), XPM (@code{libXpm}), GIF (@code{libgif} or -@code{libungif}), JPEG (@code{libjpeg}), TIFF -(@code{libtiff}), PNG (@code{libpng}), and SVG (@code{librsvg}). +@code{libungif}), JPEG (@code{libjpeg}), TIFF (@code{libtiff}), PNG +(@code{libpng}), SVG (@code{librsvg}), and WebP (@code{libwebp}). Each of these image formats is associated with an @dfn{image type symbol}. The symbols for the above formats are, respectively, -@code{pbm}, @code{xbm}, @code{xpm}, @code{gif}, -@code{jpeg}, @code{tiff}, @code{png}, and @code{svg}. +@code{pbm}, @code{xbm}, @code{xpm}, @code{gif}, @code{jpeg}, +@code{tiff}, @code{png}, @code{svg}, and @code{webp}. Furthermore, if you build Emacs with ImageMagick (@code{libMagickWand}) support, Emacs can display any image format @@ -6274,6 +6274,9 @@ Other Image Types @item TIFF Image type @code{tiff}. Supports the @code{:index} property. @xref{Multi-Frame Images}. + +@item WebP +Image type @code{webp}. @end table @node Defining Images diff --git a/etc/NEWS b/etc/NEWS index f9fe72e91f..0a44e6885f 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -53,6 +53,11 @@ time. Jumping to source from "*Help*" buffer moves the point when the source buffer is already open. Now, the old point is pushed to mark ring. +** Support for the WebP image format. +This support is built by default when the libwebp library is +available. To disable it, use the '--without-webp' configure flag. +Image specifiers can now use ':type webp'. + \f * Editing Changes in Emacs 29.1 diff --git a/lisp/files.el b/lisp/files.el index 5a6a33721b..e6b94a4a1a 100644 --- a/lisp/files.el +++ b/lisp/files.el @@ -2758,6 +2758,7 @@ auto-mode-alist ("\\.gif\\'" . image-mode) ("\\.png\\'" . image-mode) ("\\.jpe?g\\'" . image-mode) + ("\\.webp\\'" . image-mode) ("\\.te?xt\\'" . text-mode) ("\\.[tT]e[xX]\\'" . tex-mode) ("\\.ins\\'" . tex-mode) ;Installation files for TeX packages. diff --git a/lisp/image-file.el b/lisp/image-file.el index fbc9eaaf94..6df43f737d 100644 --- a/lisp/image-file.el +++ b/lisp/image-file.el @@ -37,7 +37,7 @@ ;;;###autoload (defcustom image-file-name-extensions - (purecopy '("png" "jpeg" "jpg" "gif" "tiff" "tif" "xbm" "xpm" "pbm" "pgm" "ppm" "pnm" "svg")) + (purecopy '("png" "jpeg" "jpg" "gif" "tiff" "tif" "xbm" "xpm" "pbm" "pgm" "ppm" "pnm" "svg" "webp")) "A list of image-file filename extensions. Filenames having one of these extensions are considered image files, in addition to those matching `image-file-name-regexps'. diff --git a/lisp/image.el b/lisp/image.el index 2022b41d1f..5343e26d03 100644 --- a/lisp/image.el +++ b/lisp/image.el @@ -48,6 +48,7 @@ image-type-header-regexps ("\\`\\(?:MM\0\\*\\|II\\*\0\\)" . tiff) ("\\`[\t\n\r ]*%!PS" . postscript) ("\\`\xff\xd8" . jpeg) ; used to be (image-jpeg-p . jpeg) + ("\\`RIFF....WEBPVP8" . webp) (,(let* ((incomment-re "\\(?:[^-]\\|-[^-]\\)") (comment-re (concat "\\(?:!--" incomment-re "*-->[ \t\r\n]*<\\)"))) (concat "\\(?:<\\?xml[ \t\r\n]+[^>]*>\\)?[ \t\r\n]*<" @@ -67,6 +68,7 @@ image-type-file-name-regexps '(("\\.png\\'" . png) ("\\.gif\\'" . gif) ("\\.jpe?g\\'" . jpeg) + ("\\.webp\\'" . webp) ("\\.bmp\\'" . bmp) ("\\.xpm\\'" . xpm) ("\\.pbm\\'" . pbm) @@ -92,6 +94,7 @@ image-type-auto-detectable (jpeg . maybe) (tiff . maybe) (svg . maybe) + (webp . maybe) (postscript . nil)) "Alist of (IMAGE-TYPE . AUTODETECT) pairs used to auto-detect image files. \(See `image-type-auto-detected-p'). diff --git a/lisp/term/w32-win.el b/lisp/term/w32-win.el index 5d1dc60667..366992cbbf 100644 --- a/lisp/term/w32-win.el +++ b/lisp/term/w32-win.el @@ -274,6 +274,7 @@ libgnutls-version '(gif "libgif-6.dll" "giflib5.dll" "gif.dll") '(gif "libgif-5.dll" "giflib4.dll" "libungif4.dll" "libungif.dll"))) '(svg "librsvg-2-2.dll") + '(libwebp "libwebp-7.dll" "libwebp.dll") '(gdk-pixbuf "libgdk_pixbuf-2.0-0.dll") '(glib "libglib-2.0-0.dll") '(gio "libgio-2.0-0.dll") diff --git a/nt/INSTALL b/nt/INSTALL index 9f543151a9..a39057c66c 100644 --- a/nt/INSTALL +++ b/nt/INSTALL @@ -488,6 +488,7 @@ build will run on Windows 9X and newer systems). Does Emacs use a gif library? yes Does Emacs use a png library? yes Does Emacs use -lrsvg-2? yes + Does Emacs use -lwebp? yes Does Emacs use cairo? no Does Emacs use -llcms2? yes Does Emacs use imagemagick? no @@ -597,8 +598,8 @@ build will run on Windows 9X and newer systems). * Optional image library support In addition to its "native" image formats (pbm and xbm), Emacs can - handle other image types: xpm, tiff, gif, png, jpeg and experimental - support for svg. + handle other image types: xpm, tiff, gif, png, jpeg, webp and + experimental support for svg. To build Emacs with support for them, the corresponding headers must be in the include path and libraries should be where the linker @@ -736,6 +737,10 @@ build will run on Windows 9X and newer systems). without it by specifying the --without-rsvg switch to the configure script. + For WebP images you will need libwebp: + + https://developers.google.com/speed/webp/ + Binaries for the other image libraries can be found on the ezwinports site or at the GnuWin32 project (the latter are generally very old, so not recommended). Note specifically that, due to some diff --git a/src/Makefile.in b/src/Makefile.in index 6d75e3537a..7c977e34ea 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -124,7 +124,7 @@ LIB_MATH= ## -lpthread, or empty. LIB_PTHREAD=@LIB_PTHREAD@ -LIBIMAGE=@LIBTIFF@ @LIBJPEG@ @LIBPNG@ @LIBGIF@ @LIBXPM@ +LIBIMAGE=@LIBTIFF@ @LIBJPEG@ @LIBPNG@ @LIBGIF@ @LIBXPM@ @WEBP_LIBS@ XCB_LIBS=@XCB_LIBS@ XFT_LIBS=@XFT_LIBS@ diff --git a/src/image.c b/src/image.c index ff05741b2c..6ff172504f 100644 --- a/src/image.c +++ b/src/image.c @@ -8739,8 +8739,284 @@ gif_load (struct frame *f, struct image *img) #endif /* HAVE_GIF */ +#ifdef HAVE_WEBP + +\f +/*********************************************************************** + WebP + ***********************************************************************/ + +#include "webp/decode.h" + +/* Indices of image specification fields in webp_format, below. */ + +enum webp_keyword_index +{ + WEBP_TYPE, + WEBP_DATA, + WEBP_FILE, + WEBP_ASCENT, + WEBP_MARGIN, + WEBP_RELIEF, + WEBP_ALGORITHM, + WEBP_HEURISTIC_MASK, + WEBP_MASK, + WEBP_BACKGROUND, + WEBP_LAST +}; + +/* Vector of image_keyword structures describing the format + of valid user-defined image specifications. */ + +static const struct image_keyword webp_format[WEBP_LAST] = +{ + {":type", IMAGE_SYMBOL_VALUE, 1}, + {":data", IMAGE_STRING_VALUE, 0}, + {":file", IMAGE_STRING_VALUE, 0}, + {":ascent", IMAGE_ASCENT_VALUE, 0}, + {":margin", IMAGE_NON_NEGATIVE_INTEGER_VALUE_OR_PAIR, 0}, + {":relief", IMAGE_INTEGER_VALUE, 0}, + {":conversion", IMAGE_DONT_CHECK_VALUE_TYPE, 0}, + {":heuristic-mask", IMAGE_DONT_CHECK_VALUE_TYPE, 0}, + {":mask", IMAGE_DONT_CHECK_VALUE_TYPE, 0}, + {":background", IMAGE_STRING_OR_NIL_VALUE, 0} +}; + +/* Return true if OBJECT is a valid WebP image specification. */ + +static bool +webp_image_p (Lisp_Object object) +{ + struct image_keyword fmt[WEBP_LAST]; + memcpy (fmt, webp_format, sizeof fmt); + + if (!parse_image_spec (object, fmt, WEBP_LAST, Qwebp)) + return false; + + /* Must specify either the :data or :file keyword. */ + return fmt[WEBP_FILE].count + fmt[WEBP_DATA].count == 1; +} + +#ifdef WINDOWSNT + +/* WebP library details. */ + +DEF_DLL_FN (int, WebPGetInfo, (const uint8_t* data, size_t data_size, int* width, int* height)); +DEF_DLL_FN (VP8StatusCode, WebPGetFeatures, + (const uint8_t* data, size_t data_size, WebPBitstreamFeatures* features)); +DEF_DLL_FN (uint8_t*, WebPDecodeRGB, + (const uint8_t* data, size_t data_size, int* width, int* height)); +DEF_DLL_FN (uint8_t*, WebPDecodeBGR, + (const uint8_t* data, size_t data_size, int* width, int* height)); +DEF_DLL_FN (void, WebPFreeDecBuffer + (WebPDecBuffer* buffer)); + +static bool +init_webp_functions (void) +{ + HMODULE library; + + if (!(library = w32_delayed_load (Qwebp))) + return 0; + + LOAD_DLL_FN (library, WebPGetInfo); + LOAD_DLL_FN (library, WebPGetFeatures); + LOAD_DLL_FN (library, WebPDecodeRGBA); + LOAD_DLL_FN (library, WebPDecodeRGB); + LOAD_DLL_FN (library, WebPFree); + return true; +} + +#undef WebPGetInfo +#undef WebPGetFeatures +#undef WebPDecodeRGBA +#undef WebPDecodeRGB +#undef WebPFree + +#define WebPGetInfo fn_WebPGetInfo +#define WebPGetFeatures fn_WebPGetFeatures +#define WebPDecodeRGBA fn_WebPDecodeRGBA +#define WebPDecodeRGB fn_WebPDecodeRGB +#define WebPFree fn_WebPFree + +#endif /* WINDOWSNT */ + +/* Load WebP image IMG for use on frame F. Value is true if + successful. */ + +static bool +webp_load (struct frame *f, struct image *img) +{ + ptrdiff_t size = 0; + uint8_t *contents; + Lisp_Object file; + + /* Open the WebP file. */ + Lisp_Object specified_file = image_spec_value (img->spec, QCfile, NULL); + Lisp_Object specified_data = image_spec_value (img->spec, QCdata, NULL); + + if (NILP (specified_data)) + { + int fd; + file = image_find_image_fd (specified_file, &fd); + if (!STRINGP (file)) + { + image_error ("Cannot find image file `%s'", specified_file); + return false; + } + + contents = (uint8_t *) slurp_file (fd, &size); + if (contents == NULL) + { + image_error ("Error loading WebP image `%s'", file); + return false; + } + } + else + { + if (!STRINGP (specified_data)) + { + image_error ("Invalid image data `%s'", specified_data); + return false; + } + contents = (uint8_t*) SSDATA (specified_data); + size = SBYTES (specified_data); + } + + /* Validate the WebP image header. */ + if (!WebPGetInfo (contents, size, NULL, NULL)) + { + if (!NILP (specified_data)) + image_error ("Not a WebP file: `%s'", file); + else + image_error ("Invalid WebP data"); + goto webp_error1; + } + + /* Get WebP features. */ + WebPBitstreamFeatures features; + VP8StatusCode result = WebPGetFeatures (contents, size, &features); + switch (result) + { + case VP8_STATUS_OK: + break; + case VP8_STATUS_NOT_ENOUGH_DATA: + case VP8_STATUS_OUT_OF_MEMORY: + case VP8_STATUS_INVALID_PARAM: + case VP8_STATUS_BITSTREAM_ERROR: + case VP8_STATUS_UNSUPPORTED_FEATURE: + case VP8_STATUS_SUSPENDED: + case VP8_STATUS_USER_ABORT: + default: + /* Error out in all other cases. */ + if (!NILP (specified_data)) + image_error ("Error when interpreting WebP data: `%s'", file); + else + image_error ("Error when interpreting WebP data"); + goto webp_error1; + } + + /* Decode WebP data. */ + uint8_t *decoded; + int width, height; + if (features.has_alpha) + /* Linear [r0, g0, b0, a0, r1, g1, b1, a1, ...] order. */ + decoded = WebPDecodeRGBA (contents, size, &width, &height); + else + /* Linear [r0, g0, b0, r1, g1, b1, ...] order. */ + decoded = WebPDecodeRGB (contents, size, &width, &height); + + if (!(width <= INT_MAX && height <= INT_MAX + && check_image_size (f, width, height))) + { + image_size_error (); + goto webp_error2; + } + + /* Create the x image and pixmap. */ + Emacs_Pix_Container ximg, mask_img; + if (!image_create_x_image_and_pixmap (f, img, width, height, 0, &ximg, false)) + goto webp_error2; + + /* Create an image and pixmap serving as mask if the WebP image + contains an alpha channel. */ + if (features.has_alpha + && !image_create_x_image_and_pixmap (f, img, width, height, 1, &mask_img, true)) + { + image_destroy_x_image (ximg); + image_clear_image_1 (f, img, CLEAR_IMAGE_PIXMAP); + goto webp_error2; + } + + /* Fill the X image and mask from WebP data. */ + init_color_table (); + + uint8_t *p = decoded; + for (int y = 0; y < height; ++y) + { + for (int x = 0; x < width; ++x) + { + int r = *p++ << 8; + int g = *p++ << 8; + int b = *p++ << 8; + PUT_PIXEL (ximg, x, y, lookup_rgb_color (f, r, g, b)); + + /* An alpha channel associates variable transparency with an + image. WebP allows up to 256 levels of partial transparency. + We handle this like with PNG (which see), using the frame's + background color to combine the image with. */ + if (features.has_alpha) + { + if (mask_img) + PUT_PIXEL (mask_img, x, y, *p > 0 ? PIX_MASK_DRAW : PIX_MASK_RETAIN); + ++p; + } + } + } + +#ifdef COLOR_TABLE_SUPPORT + /* Remember colors allocated for this image. */ + img->colors = colors_in_color_table (&img->ncolors); + free_color_table (); +#endif /* COLOR_TABLE_SUPPORT */ + + /* Put ximg into the image. */ + image_put_x_image (f, img, ximg, 0); + + /* Same for the mask. */ + if (mask_img) + { + /* Fill in the background_transparent field while we have the + mask handy. Casting avoids a GCC warning. */ + image_background_transparent (img, f, (Emacs_Pix_Context)mask_img); + + image_put_x_image (f, img, mask_img, 1); + } + + img->width = width; + img->height = height; + + /* Clean up. */ + WebPFree (decoded); + if (NILP (specified_data)) + xfree (contents); + return true; + + webp_error2: + WebPFree (decoded); + + webp_error1: + if (NILP (specified_data)) + xfree (contents); + return false; +} + +#endif /* HAVE_WEBP */ + + #ifdef HAVE_IMAGEMAGICK +\f /*********************************************************************** ImageMagick ***********************************************************************/ @@ -10725,6 +11001,10 @@ initialize_image_type (struct image_type const *type) #if defined HAVE_XPM || defined HAVE_NS { SYMBOL_INDEX (Qxpm), xpm_image_p, xpm_load, image_clear_image, IMAGE_TYPE_INIT (init_xpm_functions) }, +#endif +#if defined HAVE_WEBP + { SYMBOL_INDEX (Qwebp), webp_image_p, webp_load, image_clear_image, + IMAGE_TYPE_INIT (init_webp_functions) }, #endif { SYMBOL_INDEX (Qxbm), xbm_image_p, xbm_load, image_clear_image }, { SYMBOL_INDEX (Qpbm), pbm_image_p, pbm_load, image_clear_image }, @@ -10891,6 +11171,11 @@ syms_of_image (void) add_image_type (Qpng); #endif +#if defined (HAVE_WEBP) + DEFSYM (Qwebp, "webp"); + add_image_type (Qwebp); +#endif + #if defined (HAVE_IMAGEMAGICK) DEFSYM (Qimagemagick, "imagemagick"); add_image_type (Qimagemagick); diff --git a/test/data/image/black.webp b/test/data/image/black.webp new file mode 100644 index 0000000000000000000000000000000000000000..5dbe716415b16e6111ac92ec3978411d95be08e3 GIT binary patch literal 37780 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 literal 0 HcmV?d00001 diff --git a/test/lisp/image-tests.el b/test/lisp/image-tests.el index aa8600609c..c34c152cc9 100644 --- a/test/lisp/image-tests.el +++ b/test/lisp/image-tests.el @@ -49,12 +49,14 @@ image--set-property (should (equal image '(image))))) (ert-deftest image-find-image () - (find-image '((:type xpm :file "undo.xpm"))) - (find-image '((:type png :file "newsticker/rss-feed.png" :ascent center)))) + (should (listp (find-image '((:type xpm :file "undo.xpm"))))) + (should (listp (find-image '((:type png :file "newsticker/rss-feed.png" :ascent center))))) + (should-not (find-image '((:type png :file "does-not-exist-foo-bar.png"))))) (ert-deftest image-type-from-file-name () (should (eq (image-type-from-file-name "foo.jpg") 'jpeg)) - (should (eq (image-type-from-file-name "foo.png") 'png))) + (should (eq (image-type-from-file-name "foo.png") 'png)) + (should (eq (image-type-from-file-name "foo.webp") 'webp))) (ert-deftest image-type/from-filename () ;; On emba, `image-types' and `image-load-path' do not exist. diff --git a/test/src/image-tests.el b/test/src/image-tests.el index d5e3a7cc5c..b921739a05 100644 --- a/test/src/image-tests.el +++ b/test/src/image-tests.el @@ -44,6 +44,8 @@ image-tests--files (tiff . ,(expand-file-name "nextstep/GNUstep/Emacs.base/Resources/emacs.tiff" source-directory)) + (webp . ,(expand-file-name "test/data/image/black.webp" + source-directory)) (xbm . ,(find-image '((:file "gnus/gnus.xbm" :type xbm)))) (xpm . ,(find-image '((:file "splash.xpm" :type xpm)))) ;; TODO: gif @@ -86,6 +88,13 @@ image-tests-image-size/tiff (should (floatp a)) (should (floatp b))))) +(ert-deftest image-tests-image-size/webp () + (image-skip-unless 'webp) + (pcase (image-size (create-image (cdr (assq 'webp image-tests--files)))) + (`(,a . ,b) + (should (floatp a)) + (should (floatp b))))) + (ert-deftest image-tests-image-size/xbm () (image-skip-unless 'xbm) (pcase (image-size (cdr (assq 'xbm image-tests--files))) @@ -130,7 +139,12 @@ image-tests-image-mask-p/svg (ert-deftest image-tests-image-mask-p/tiff () (image-skip-unless 'tiff) (should-not (image-mask-p (create-image - (cdr (assq 'tiff image-tests--files)))))) + (cdr (assq 'tiff image-tests--files)))))) + +(ert-deftest image-tests-image-mask-p/webp () + (image-skip-unless 'webp) + (should-not (image-mask-p (create-image + (cdr (assq 'webp image-tests--files)))))) (ert-deftest image-tests-image-mask-p/xbm () (image-skip-unless 'xbm) @@ -173,7 +187,12 @@ image-tests-image-metadata/svg (ert-deftest image-tests-image-metadata/tiff () (image-skip-unless 'tiff) (should-not (image-metadata - (create-image (cdr (assq 'tiff image-tests--files)))))) + (create-image (cdr (assq 'tiff image-tests--files)))))) + +(ert-deftest image-tests-image-metadata/webp () + (image-skip-unless 'webp) + (should-not (image-metadata + (create-image (cdr (assq 'webp image-tests--files)))))) (ert-deftest image-tests-image-metadata/xbm () (image-skip-unless 'xbm) -- 2.30.2 ^ permalink raw reply related [flat|nested] 21+ messages in thread
* bug#51296: [PATCH] Add WebP format support 2021-10-20 21:02 ` Stefan Kangas @ 2021-10-21 0:45 ` Stefan Kangas 2021-10-21 8:19 ` Eli Zaretskii 0 siblings, 1 reply; 21+ messages in thread From: Stefan Kangas @ 2021-10-21 0:45 UTC (permalink / raw) To: Eli Zaretskii; +Cc: 51296 [-- Attachment #1: Type: text/plain, Size: 105 bytes --] Stefan Kangas <stefan@marxist.se> writes: > Also fixed. I spotted a typo, fixed in the attached patch. [-- Attachment #2: 0001-Add-WebP-format-support.patch --] [-- Type: text/x-diff, Size: 40788 bytes --] From 69f56713d404214c5fc1f2f8bfb86746668573a0 Mon Sep 17 00:00:00 2001 From: Stefan Kangas <stefan@marxist.se> Date: Fri, 15 Oct 2021 05:25:39 +0200 Subject: [PATCH] Add WebP format support * configure.ac (--with-webp): New option. (HAVE_WEBP): New variable. * src/image.c (enum webp_keyword_index) [HAVE_WEBP]: New enum. (webp_format) [HAVE_WEBP]: New variable. (webp_image_p, init_webp_functions, webp_load) [HAVE_WEBP]: New functions for WebP support. (image_types) [HAVE_WEBP]: Define WebP format. (syms_of_image) <Qwebp> [HAVE_WEBP]: New DEFSYM. Add image type Qwebp. * src/Makefile.in (LIBIMAGE): Add WEBP_LIBS. * lisp/files.el (auto-mode-alist): * lisp/image-file.el (image-file-name-extensions): * lisp/image.el (image-type-header-regexps) (image-type-file-name-regexps, image-type-auto-detectable): Add WebP support. * lisp/term/w32-win.el (dynamic-library-alist): Add the libwebp DLL. * INSTALL: * admin/CPP-DEFINES: * doc/lispref/display.texi (Image Formats, Other Image Types): * nt/INSTALL: Document WebP support. * test/lisp/image-tests.el (image-find-image) (image-type-from-file-name): Expand tests. * test/src/image-tests.el (image-tests--files): Add WebP. (image-tests-image-size/webp, image-tests-image-mask-p/webp) (image-tests-image-metadata/webp): New tests. * test/data/image/black.webp: New file. --- INSTALL | 2 + admin/CPP-DEFINES | 1 + configure.ac | 24 ++++ doc/lispref/display.texi | 11 +- etc/NEWS | 5 + lisp/files.el | 1 + lisp/image-file.el | 2 +- lisp/image.el | 3 + lisp/term/w32-win.el | 1 + nt/INSTALL | 9 +- src/Makefile.in | 2 +- src/image.c | 285 +++++++++++++++++++++++++++++++++++++ test/data/image/black.webp | Bin 0 -> 37780 bytes test/lisp/image-tests.el | 8 +- test/src/image-tests.el | 23 ++- 15 files changed, 364 insertions(+), 13 deletions(-) create mode 100644 test/data/image/black.webp diff --git a/INSTALL b/INSTALL index 6207f43cec..21298422af 100644 --- a/INSTALL +++ b/INSTALL @@ -187,6 +187,7 @@ X11 is being used. X libtiff for TIFF: http://www.simplesystems.org/libtiff/ X libgif for GIF: http://giflib.sourceforge.net/ librsvg2 for SVG: https://wiki.gnome.org/Projects/LibRsvg + libwebp for WebP: https://developers.google.com/speed/webp/ If you supply the appropriate --without-LIB option, 'configure' will omit the corresponding library from Emacs, even if that makes for a @@ -313,6 +314,7 @@ or more of these options: --without-gif for GIF image support --without-png for PNG image support --without-rsvg for SVG image support + --without-webp for WebP image support Although ImageMagick support is disabled by default due to security and stability concerns, you can enable it with --with-imagemagick. diff --git a/admin/CPP-DEFINES b/admin/CPP-DEFINES index 68c12438f5..634d6f3f3b 100644 --- a/admin/CPP-DEFINES +++ b/admin/CPP-DEFINES @@ -287,6 +287,7 @@ HAVE_UTIMENSAT HAVE_UTMP_H HAVE_VFORK HAVE_VFORK_H +HAVE_WEBP HAVE_WCHAR_H HAVE_WCHAR_T HAVE_WINDOW_SYSTEM diff --git a/configure.ac b/configure.ac index 9ab0314428..18868680cd 100644 --- a/configure.ac +++ b/configure.ac @@ -447,6 +447,7 @@ AC_DEFUN OPTION_DEFAULT_ON([gif],[don't compile with GIF image support]) OPTION_DEFAULT_ON([png],[don't compile with PNG image support]) OPTION_DEFAULT_ON([rsvg],[don't compile with SVG image support]) +OPTION_DEFAULT_ON([webp],[don't compile with WebP image support]) OPTION_DEFAULT_ON([lcms2],[don't compile with Little CMS support]) OPTION_DEFAULT_ON([libsystemd],[don't compile with libsystemd support]) OPTION_DEFAULT_ON([cairo],[don't compile with Cairo drawing]) @@ -2588,6 +2589,28 @@ AC_DEFUN fi fi +### Use -lwebp if available, unless '--with-webp=no' +### mingw32 doesn't use -lwebp, since it loads the library dynamically. +HAVE_WEBP=no +if test "${with_webp}" != "no"; then + if test "$opsys" = mingw32; then + AC_CHECK_HEADER([webp/decode.h], [HAVE_WEBP=yes]) + elif test "${HAVE_X11}" = "yes" || test "${HAVE_W32}" = "yes" \ + || test "${HAVE_NS}" = "yes"; then + WEBP_REQUIRED=0.6.0 + WEBP_MODULE="libwebp >= $WEBP_REQUIRED" + + EMACS_CHECK_MODULES([WEBP], [$WEBP_MODULE]) + AC_SUBST(WEBP_CFLAGS) + AC_SUBST(WEBP_LIBS) + + if test $HAVE_WEBP = yes; then + AC_DEFINE(HAVE_WEBP, 1, [Define to 1 if using libwebp.]) + CFLAGS="$CFLAGS $WEBP_CFLAGS" + fi + fi +fi + HAVE_IMAGEMAGICK=no if test "${HAVE_X11}" = "yes" || test "${HAVE_NS}" = "yes" || test "${HAVE_W32}" = "yes"; then if test "${with_imagemagick}" != "no"; then @@ -5928,6 +5951,7 @@ AC_DEFUN Does Emacs use a gif library? ${HAVE_GIF} $LIBGIF Does Emacs use a png library? ${HAVE_PNG} $LIBPNG Does Emacs use -lrsvg-2? ${HAVE_RSVG} + Does Emacs use -lwebp? ${HAVE_WEBP} Does Emacs use cairo? ${HAVE_CAIRO} Does Emacs use -llcms2? ${HAVE_LCMS2} Does Emacs use imagemagick? ${HAVE_IMAGEMAGICK} diff --git a/doc/lispref/display.texi b/doc/lispref/display.texi index 16577d13c1..9c378a3027 100644 --- a/doc/lispref/display.texi +++ b/doc/lispref/display.texi @@ -5264,13 +5264,13 @@ Image Formats Supported image formats (and the required support libraries) include PBM and XBM (which do not depend on support libraries and are always available), XPM (@code{libXpm}), GIF (@code{libgif} or -@code{libungif}), JPEG (@code{libjpeg}), TIFF -(@code{libtiff}), PNG (@code{libpng}), and SVG (@code{librsvg}). +@code{libungif}), JPEG (@code{libjpeg}), TIFF (@code{libtiff}), PNG +(@code{libpng}), SVG (@code{librsvg}), and WebP (@code{libwebp}). Each of these image formats is associated with an @dfn{image type symbol}. The symbols for the above formats are, respectively, -@code{pbm}, @code{xbm}, @code{xpm}, @code{gif}, -@code{jpeg}, @code{tiff}, @code{png}, and @code{svg}. +@code{pbm}, @code{xbm}, @code{xpm}, @code{gif}, @code{jpeg}, +@code{tiff}, @code{png}, @code{svg}, and @code{webp}. Furthermore, if you build Emacs with ImageMagick (@code{libMagickWand}) support, Emacs can display any image format @@ -6274,6 +6274,9 @@ Other Image Types @item TIFF Image type @code{tiff}. Supports the @code{:index} property. @xref{Multi-Frame Images}. + +@item WebP +Image type @code{webp}. @end table @node Defining Images diff --git a/etc/NEWS b/etc/NEWS index f9fe72e91f..0a44e6885f 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -53,6 +53,11 @@ time. Jumping to source from "*Help*" buffer moves the point when the source buffer is already open. Now, the old point is pushed to mark ring. +** Support for the WebP image format. +This support is built by default when the libwebp library is +available. To disable it, use the '--without-webp' configure flag. +Image specifiers can now use ':type webp'. + \f * Editing Changes in Emacs 29.1 diff --git a/lisp/files.el b/lisp/files.el index 5a6a33721b..e6b94a4a1a 100644 --- a/lisp/files.el +++ b/lisp/files.el @@ -2758,6 +2758,7 @@ auto-mode-alist ("\\.gif\\'" . image-mode) ("\\.png\\'" . image-mode) ("\\.jpe?g\\'" . image-mode) + ("\\.webp\\'" . image-mode) ("\\.te?xt\\'" . text-mode) ("\\.[tT]e[xX]\\'" . tex-mode) ("\\.ins\\'" . tex-mode) ;Installation files for TeX packages. diff --git a/lisp/image-file.el b/lisp/image-file.el index fbc9eaaf94..6df43f737d 100644 --- a/lisp/image-file.el +++ b/lisp/image-file.el @@ -37,7 +37,7 @@ ;;;###autoload (defcustom image-file-name-extensions - (purecopy '("png" "jpeg" "jpg" "gif" "tiff" "tif" "xbm" "xpm" "pbm" "pgm" "ppm" "pnm" "svg")) + (purecopy '("png" "jpeg" "jpg" "gif" "tiff" "tif" "xbm" "xpm" "pbm" "pgm" "ppm" "pnm" "svg" "webp")) "A list of image-file filename extensions. Filenames having one of these extensions are considered image files, in addition to those matching `image-file-name-regexps'. diff --git a/lisp/image.el b/lisp/image.el index 2022b41d1f..5343e26d03 100644 --- a/lisp/image.el +++ b/lisp/image.el @@ -48,6 +48,7 @@ image-type-header-regexps ("\\`\\(?:MM\0\\*\\|II\\*\0\\)" . tiff) ("\\`[\t\n\r ]*%!PS" . postscript) ("\\`\xff\xd8" . jpeg) ; used to be (image-jpeg-p . jpeg) + ("\\`RIFF....WEBPVP8" . webp) (,(let* ((incomment-re "\\(?:[^-]\\|-[^-]\\)") (comment-re (concat "\\(?:!--" incomment-re "*-->[ \t\r\n]*<\\)"))) (concat "\\(?:<\\?xml[ \t\r\n]+[^>]*>\\)?[ \t\r\n]*<" @@ -67,6 +68,7 @@ image-type-file-name-regexps '(("\\.png\\'" . png) ("\\.gif\\'" . gif) ("\\.jpe?g\\'" . jpeg) + ("\\.webp\\'" . webp) ("\\.bmp\\'" . bmp) ("\\.xpm\\'" . xpm) ("\\.pbm\\'" . pbm) @@ -92,6 +94,7 @@ image-type-auto-detectable (jpeg . maybe) (tiff . maybe) (svg . maybe) + (webp . maybe) (postscript . nil)) "Alist of (IMAGE-TYPE . AUTODETECT) pairs used to auto-detect image files. \(See `image-type-auto-detected-p'). diff --git a/lisp/term/w32-win.el b/lisp/term/w32-win.el index 5d1dc60667..366992cbbf 100644 --- a/lisp/term/w32-win.el +++ b/lisp/term/w32-win.el @@ -274,6 +274,7 @@ libgnutls-version '(gif "libgif-6.dll" "giflib5.dll" "gif.dll") '(gif "libgif-5.dll" "giflib4.dll" "libungif4.dll" "libungif.dll"))) '(svg "librsvg-2-2.dll") + '(libwebp "libwebp-7.dll" "libwebp.dll") '(gdk-pixbuf "libgdk_pixbuf-2.0-0.dll") '(glib "libglib-2.0-0.dll") '(gio "libgio-2.0-0.dll") diff --git a/nt/INSTALL b/nt/INSTALL index 9f543151a9..a39057c66c 100644 --- a/nt/INSTALL +++ b/nt/INSTALL @@ -488,6 +488,7 @@ build will run on Windows 9X and newer systems). Does Emacs use a gif library? yes Does Emacs use a png library? yes Does Emacs use -lrsvg-2? yes + Does Emacs use -lwebp? yes Does Emacs use cairo? no Does Emacs use -llcms2? yes Does Emacs use imagemagick? no @@ -597,8 +598,8 @@ build will run on Windows 9X and newer systems). * Optional image library support In addition to its "native" image formats (pbm and xbm), Emacs can - handle other image types: xpm, tiff, gif, png, jpeg and experimental - support for svg. + handle other image types: xpm, tiff, gif, png, jpeg, webp and + experimental support for svg. To build Emacs with support for them, the corresponding headers must be in the include path and libraries should be where the linker @@ -736,6 +737,10 @@ build will run on Windows 9X and newer systems). without it by specifying the --without-rsvg switch to the configure script. + For WebP images you will need libwebp: + + https://developers.google.com/speed/webp/ + Binaries for the other image libraries can be found on the ezwinports site or at the GnuWin32 project (the latter are generally very old, so not recommended). Note specifically that, due to some diff --git a/src/Makefile.in b/src/Makefile.in index 6d75e3537a..7c977e34ea 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -124,7 +124,7 @@ LIB_MATH= ## -lpthread, or empty. LIB_PTHREAD=@LIB_PTHREAD@ -LIBIMAGE=@LIBTIFF@ @LIBJPEG@ @LIBPNG@ @LIBGIF@ @LIBXPM@ +LIBIMAGE=@LIBTIFF@ @LIBJPEG@ @LIBPNG@ @LIBGIF@ @LIBXPM@ @WEBP_LIBS@ XCB_LIBS=@XCB_LIBS@ XFT_LIBS=@XFT_LIBS@ diff --git a/src/image.c b/src/image.c index ff05741b2c..6ff172504f 100644 --- a/src/image.c +++ b/src/image.c @@ -8739,8 +8739,284 @@ gif_load (struct frame *f, struct image *img) #endif /* HAVE_GIF */ +#ifdef HAVE_WEBP + +\f +/*********************************************************************** + WebP + ***********************************************************************/ + +#include "webp/decode.h" + +/* Indices of image specification fields in webp_format, below. */ + +enum webp_keyword_index +{ + WEBP_TYPE, + WEBP_DATA, + WEBP_FILE, + WEBP_ASCENT, + WEBP_MARGIN, + WEBP_RELIEF, + WEBP_ALGORITHM, + WEBP_HEURISTIC_MASK, + WEBP_MASK, + WEBP_BACKGROUND, + WEBP_LAST +}; + +/* Vector of image_keyword structures describing the format + of valid user-defined image specifications. */ + +static const struct image_keyword webp_format[WEBP_LAST] = +{ + {":type", IMAGE_SYMBOL_VALUE, 1}, + {":data", IMAGE_STRING_VALUE, 0}, + {":file", IMAGE_STRING_VALUE, 0}, + {":ascent", IMAGE_ASCENT_VALUE, 0}, + {":margin", IMAGE_NON_NEGATIVE_INTEGER_VALUE_OR_PAIR, 0}, + {":relief", IMAGE_INTEGER_VALUE, 0}, + {":conversion", IMAGE_DONT_CHECK_VALUE_TYPE, 0}, + {":heuristic-mask", IMAGE_DONT_CHECK_VALUE_TYPE, 0}, + {":mask", IMAGE_DONT_CHECK_VALUE_TYPE, 0}, + {":background", IMAGE_STRING_OR_NIL_VALUE, 0} +}; + +/* Return true if OBJECT is a valid WebP image specification. */ + +static bool +webp_image_p (Lisp_Object object) +{ + struct image_keyword fmt[WEBP_LAST]; + memcpy (fmt, webp_format, sizeof fmt); + + if (!parse_image_spec (object, fmt, WEBP_LAST, Qwebp)) + return false; + + /* Must specify either the :data or :file keyword. */ + return fmt[WEBP_FILE].count + fmt[WEBP_DATA].count == 1; +} + +#ifdef WINDOWSNT + +/* WebP library details. */ + +DEF_DLL_FN (int, WebPGetInfo, (const uint8_t* data, size_t data_size, int* width, int* height)); +DEF_DLL_FN (VP8StatusCode, WebPGetFeatures, + (const uint8_t* data, size_t data_size, WebPBitstreamFeatures* features)); +DEF_DLL_FN (uint8_t*, WebPDecodeRGB, + (const uint8_t* data, size_t data_size, int* width, int* height)); +DEF_DLL_FN (uint8_t*, WebPDecodeBGR, + (const uint8_t* data, size_t data_size, int* width, int* height)); +DEF_DLL_FN (void, WebPFreeDecBuffer + (WebPDecBuffer* buffer)); + +static bool +init_webp_functions (void) +{ + HMODULE library; + + if (!(library = w32_delayed_load (Qwebp))) + return 0; + + LOAD_DLL_FN (library, WebPGetInfo); + LOAD_DLL_FN (library, WebPGetFeatures); + LOAD_DLL_FN (library, WebPDecodeRGBA); + LOAD_DLL_FN (library, WebPDecodeRGB); + LOAD_DLL_FN (library, WebPFree); + return true; +} + +#undef WebPGetInfo +#undef WebPGetFeatures +#undef WebPDecodeRGBA +#undef WebPDecodeRGB +#undef WebPFree + +#define WebPGetInfo fn_WebPGetInfo +#define WebPGetFeatures fn_WebPGetFeatures +#define WebPDecodeRGBA fn_WebPDecodeRGBA +#define WebPDecodeRGB fn_WebPDecodeRGB +#define WebPFree fn_WebPFree + +#endif /* WINDOWSNT */ + +/* Load WebP image IMG for use on frame F. Value is true if + successful. */ + +static bool +webp_load (struct frame *f, struct image *img) +{ + ptrdiff_t size = 0; + uint8_t *contents; + Lisp_Object file; + + /* Open the WebP file. */ + Lisp_Object specified_file = image_spec_value (img->spec, QCfile, NULL); + Lisp_Object specified_data = image_spec_value (img->spec, QCdata, NULL); + + if (NILP (specified_data)) + { + int fd; + file = image_find_image_fd (specified_file, &fd); + if (!STRINGP (file)) + { + image_error ("Cannot find image file `%s'", specified_file); + return false; + } + + contents = (uint8_t *) slurp_file (fd, &size); + if (contents == NULL) + { + image_error ("Error loading WebP image `%s'", file); + return false; + } + } + else + { + if (!STRINGP (specified_data)) + { + image_error ("Invalid image data `%s'", specified_data); + return false; + } + contents = (uint8_t*) SSDATA (specified_data); + size = SBYTES (specified_data); + } + + /* Validate the WebP image header. */ + if (!WebPGetInfo (contents, size, NULL, NULL)) + { + if (!NILP (specified_data)) + image_error ("Not a WebP file: `%s'", file); + else + image_error ("Invalid WebP data"); + goto webp_error1; + } + + /* Get WebP features. */ + WebPBitstreamFeatures features; + VP8StatusCode result = WebPGetFeatures (contents, size, &features); + switch (result) + { + case VP8_STATUS_OK: + break; + case VP8_STATUS_NOT_ENOUGH_DATA: + case VP8_STATUS_OUT_OF_MEMORY: + case VP8_STATUS_INVALID_PARAM: + case VP8_STATUS_BITSTREAM_ERROR: + case VP8_STATUS_UNSUPPORTED_FEATURE: + case VP8_STATUS_SUSPENDED: + case VP8_STATUS_USER_ABORT: + default: + /* Error out in all other cases. */ + if (!NILP (specified_data)) + image_error ("Error when interpreting WebP data: `%s'", file); + else + image_error ("Error when interpreting WebP data"); + goto webp_error1; + } + + /* Decode WebP data. */ + uint8_t *decoded; + int width, height; + if (features.has_alpha) + /* Linear [r0, g0, b0, a0, r1, g1, b1, a1, ...] order. */ + decoded = WebPDecodeRGBA (contents, size, &width, &height); + else + /* Linear [r0, g0, b0, r1, g1, b1, ...] order. */ + decoded = WebPDecodeRGB (contents, size, &width, &height); + + if (!(width <= INT_MAX && height <= INT_MAX + && check_image_size (f, width, height))) + { + image_size_error (); + goto webp_error2; + } + + /* Create the x image and pixmap. */ + Emacs_Pix_Container ximg, mask_img; + if (!image_create_x_image_and_pixmap (f, img, width, height, 0, &ximg, false)) + goto webp_error2; + + /* Create an image and pixmap serving as mask if the WebP image + contains an alpha channel. */ + if (features.has_alpha + && !image_create_x_image_and_pixmap (f, img, width, height, 1, &mask_img, true)) + { + image_destroy_x_image (ximg); + image_clear_image_1 (f, img, CLEAR_IMAGE_PIXMAP); + goto webp_error2; + } + + /* Fill the X image and mask from WebP data. */ + init_color_table (); + + uint8_t *p = decoded; + for (int y = 0; y < height; ++y) + { + for (int x = 0; x < width; ++x) + { + int r = *p++ << 8; + int g = *p++ << 8; + int b = *p++ << 8; + PUT_PIXEL (ximg, x, y, lookup_rgb_color (f, r, g, b)); + + /* An alpha channel associates variable transparency with an + image. WebP allows up to 256 levels of partial transparency. + We handle this like with PNG (which see), using the frame's + background color to combine the image with. */ + if (features.has_alpha) + { + if (mask_img) + PUT_PIXEL (mask_img, x, y, *p > 0 ? PIX_MASK_DRAW : PIX_MASK_RETAIN); + ++p; + } + } + } + +#ifdef COLOR_TABLE_SUPPORT + /* Remember colors allocated for this image. */ + img->colors = colors_in_color_table (&img->ncolors); + free_color_table (); +#endif /* COLOR_TABLE_SUPPORT */ + + /* Put ximg into the image. */ + image_put_x_image (f, img, ximg, 0); + + /* Same for the mask. */ + if (mask_img) + { + /* Fill in the background_transparent field while we have the + mask handy. Casting avoids a GCC warning. */ + image_background_transparent (img, f, (Emacs_Pix_Context)mask_img); + + image_put_x_image (f, img, mask_img, 1); + } + + img->width = width; + img->height = height; + + /* Clean up. */ + WebPFree (decoded); + if (NILP (specified_data)) + xfree (contents); + return true; + + webp_error2: + WebPFree (decoded); + + webp_error1: + if (NILP (specified_data)) + xfree (contents); + return false; +} + +#endif /* HAVE_WEBP */ + + #ifdef HAVE_IMAGEMAGICK +\f /*********************************************************************** ImageMagick ***********************************************************************/ @@ -10725,6 +11001,10 @@ initialize_image_type (struct image_type const *type) #if defined HAVE_XPM || defined HAVE_NS { SYMBOL_INDEX (Qxpm), xpm_image_p, xpm_load, image_clear_image, IMAGE_TYPE_INIT (init_xpm_functions) }, +#endif +#if defined HAVE_WEBP + { SYMBOL_INDEX (Qwebp), webp_image_p, webp_load, image_clear_image, + IMAGE_TYPE_INIT (init_webp_functions) }, #endif { SYMBOL_INDEX (Qxbm), xbm_image_p, xbm_load, image_clear_image }, { SYMBOL_INDEX (Qpbm), pbm_image_p, pbm_load, image_clear_image }, @@ -10891,6 +11171,11 @@ syms_of_image (void) add_image_type (Qpng); #endif +#if defined (HAVE_WEBP) + DEFSYM (Qwebp, "webp"); + add_image_type (Qwebp); +#endif + #if defined (HAVE_IMAGEMAGICK) DEFSYM (Qimagemagick, "imagemagick"); add_image_type (Qimagemagick); diff --git a/test/data/image/black.webp b/test/data/image/black.webp new file mode 100644 index 0000000000000000000000000000000000000000..5dbe716415b16e6111ac92ec3978411d95be08e3 GIT binary patch literal 37780 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 literal 0 HcmV?d00001 diff --git a/test/lisp/image-tests.el b/test/lisp/image-tests.el index aa8600609c..c34c152cc9 100644 --- a/test/lisp/image-tests.el +++ b/test/lisp/image-tests.el @@ -49,12 +49,14 @@ image--set-property (should (equal image '(image))))) (ert-deftest image-find-image () - (find-image '((:type xpm :file "undo.xpm"))) - (find-image '((:type png :file "newsticker/rss-feed.png" :ascent center)))) + (should (listp (find-image '((:type xpm :file "undo.xpm"))))) + (should (listp (find-image '((:type png :file "newsticker/rss-feed.png" :ascent center))))) + (should-not (find-image '((:type png :file "does-not-exist-foo-bar.png"))))) (ert-deftest image-type-from-file-name () (should (eq (image-type-from-file-name "foo.jpg") 'jpeg)) - (should (eq (image-type-from-file-name "foo.png") 'png))) + (should (eq (image-type-from-file-name "foo.png") 'png)) + (should (eq (image-type-from-file-name "foo.webp") 'webp))) (ert-deftest image-type/from-filename () ;; On emba, `image-types' and `image-load-path' do not exist. diff --git a/test/src/image-tests.el b/test/src/image-tests.el index d5e3a7cc5c..b921739a05 100644 --- a/test/src/image-tests.el +++ b/test/src/image-tests.el @@ -44,6 +44,8 @@ image-tests--files (tiff . ,(expand-file-name "nextstep/GNUstep/Emacs.base/Resources/emacs.tiff" source-directory)) + (webp . ,(expand-file-name "test/data/image/black.webp" + source-directory)) (xbm . ,(find-image '((:file "gnus/gnus.xbm" :type xbm)))) (xpm . ,(find-image '((:file "splash.xpm" :type xpm)))) ;; TODO: gif @@ -86,6 +88,13 @@ image-tests-image-size/tiff (should (floatp a)) (should (floatp b))))) +(ert-deftest image-tests-image-size/webp () + (image-skip-unless 'webp) + (pcase (image-size (create-image (cdr (assq 'webp image-tests--files)))) + (`(,a . ,b) + (should (floatp a)) + (should (floatp b))))) + (ert-deftest image-tests-image-size/xbm () (image-skip-unless 'xbm) (pcase (image-size (cdr (assq 'xbm image-tests--files))) @@ -130,7 +139,12 @@ image-tests-image-mask-p/svg (ert-deftest image-tests-image-mask-p/tiff () (image-skip-unless 'tiff) (should-not (image-mask-p (create-image - (cdr (assq 'tiff image-tests--files)))))) + (cdr (assq 'tiff image-tests--files)))))) + +(ert-deftest image-tests-image-mask-p/webp () + (image-skip-unless 'webp) + (should-not (image-mask-p (create-image + (cdr (assq 'webp image-tests--files)))))) (ert-deftest image-tests-image-mask-p/xbm () (image-skip-unless 'xbm) @@ -173,7 +187,12 @@ image-tests-image-metadata/svg (ert-deftest image-tests-image-metadata/tiff () (image-skip-unless 'tiff) (should-not (image-metadata - (create-image (cdr (assq 'tiff image-tests--files)))))) + (create-image (cdr (assq 'tiff image-tests--files)))))) + +(ert-deftest image-tests-image-metadata/webp () + (image-skip-unless 'webp) + (should-not (image-metadata + (create-image (cdr (assq 'webp image-tests--files)))))) (ert-deftest image-tests-image-metadata/xbm () (image-skip-unless 'xbm) -- 2.30.2 ^ permalink raw reply related [flat|nested] 21+ messages in thread
* bug#51296: [PATCH] Add WebP format support 2021-10-21 0:45 ` Stefan Kangas @ 2021-10-21 8:19 ` Eli Zaretskii 2021-10-21 18:36 ` Stefan Kangas 0 siblings, 1 reply; 21+ messages in thread From: Eli Zaretskii @ 2021-10-21 8:19 UTC (permalink / raw) To: Stefan Kangas; +Cc: 51296 > From: Stefan Kangas <stefan@marxist.se> > Date: Wed, 20 Oct 2021 17:45:28 -0700 > Cc: 51296@debbugs.gnu.org > > > Also fixed. > > I spotted a typo, fixed in the attached patch. Thanks, now only a few minor issues remain: > +### mingw32 doesn't use -lwebp, since it loads the library dynamically. > +HAVE_WEBP=no > +if test "${with_webp}" != "no"; then > + if test "$opsys" = mingw32; then > + AC_CHECK_HEADER([webp/decode.h], [HAVE_WEBP=yes]) > + elif test "${HAVE_X11}" = "yes" || test "${HAVE_W32}" = "yes" \ > + || test "${HAVE_NS}" = "yes"; then > + WEBP_REQUIRED=0.6.0 > + WEBP_MODULE="libwebp >= $WEBP_REQUIRED" > + > + EMACS_CHECK_MODULES([WEBP], [$WEBP_MODULE]) > + AC_SUBST(WEBP_CFLAGS) > + AC_SUBST(WEBP_LIBS) > + > + if test $HAVE_WEBP = yes; then > + AC_DEFINE(HAVE_WEBP, 1, [Define to 1 if using libwebp.]) > + CFLAGS="$CFLAGS $WEBP_CFLAGS" > + fi > + fi > +fi That's OK, but I believe you should also add WEBP to the list of Emacs configuration features around line 5885 in configure.ac, so that WebP support could be reported by system-configuration-features. > +DEF_DLL_FN (int, WebPGetInfo, (const uint8_t* data, size_t data_size, int* width, int* height)); Our style is to separate the '*' from the type, like this: DEF_DLL_FN (int, WebPGetInfo, (const uint8_t *, size_t, int *, int *)); ^^ ^^ ^^ Also note that there's no need to use names of parameters in prototypes, they just make the code longer, but don't add anything useful. So I removed them in the above. > +static bool > +init_webp_functions (void) > +{ > + HMODULE library; > + > + if (!(library = w32_delayed_load (Qwebp))) > + return 0; ^^^^^^^^ "return false" is more consistent. > + contents = (uint8_t*) SSDATA (specified_data); Space before '*' again. Also, is the type cast really needed? If not, it is better to drop it. > + /* Validate the WebP image header. */ > + if (!WebPGetInfo (contents, size, NULL, NULL)) > + { > + if (!NILP (specified_data)) > + image_error ("Not a WebP file: `%s'", file); > + else > + image_error ("Invalid WebP data"); This last error message could IMO be more useful, if it said something like "Non-WebP header in WebP image data". > + image_error ("Error when interpreting WebP data: `%s'", file); Suggest to say "Error when interpreting WebP data from file `%s'" instead, otherwise it may not be clear to the user what is that string after the colon. > + image_error ("Error when interpreting WebP data"); I'd suggest "Error when interpreting WebP image data". ^ permalink raw reply [flat|nested] 21+ messages in thread
* bug#51296: [PATCH] Add WebP format support 2021-10-21 8:19 ` Eli Zaretskii @ 2021-10-21 18:36 ` Stefan Kangas 2021-10-21 18:39 ` Eli Zaretskii 0 siblings, 1 reply; 21+ messages in thread From: Stefan Kangas @ 2021-10-21 18:36 UTC (permalink / raw) To: Eli Zaretskii; +Cc: 51296 [-- Attachment #1: Type: text/plain, Size: 2393 bytes --] Eli Zaretskii <eliz@gnu.org> writes: > Thanks, now only a few minor issues remain: All your comments should be fixed, see below for details. I've also attached an updated patch. Thank you for reviewing. > That's OK, but I believe you should also add WEBP to the list of Emacs > configuration features around line 5885 in configure.ac, so that WebP > support could be reported by system-configuration-features. Ah, right. Fixed. > Our style is to separate the '*' from the type, like this: > > DEF_DLL_FN (int, WebPGetInfo, (const uint8_t *, size_t, int *, int *)); > ^^ ^^ ^^ > Also note that there's no need to use names of parameters in > prototypes, they just make the code longer, but don't add anything > useful. So I removed them in the above. Fixed. >> + if (!(library = w32_delayed_load (Qwebp))) >> + return 0; > ^^^^^^^^ > "return false" is more consistent. Fixed. >> + contents = (uint8_t*) SSDATA (specified_data); > > Space before '*' again. Also, is the type cast really needed? If > not, it is better to drop it. Fixed the style issue. The cast fixes this warning, so I kept it and added a comment saying "Casting avoids a GCC warning": image.c: In function ‘webp_load’: image.c:8878:16: warning: pointer targets in assignment from ‘char *’ to ‘uint8_t *’ {aka ‘unsigned char *’} differ in signedness [-Wpointer-sign] 8878 | contents = SSDATA (specified_data); | ^ >> + /* Validate the WebP image header. */ >> + if (!WebPGetInfo (contents, size, NULL, NULL)) >> + { >> + if (!NILP (specified_data)) >> + image_error ("Not a WebP file: `%s'", file); >> + else >> + image_error ("Invalid WebP data"); > > This last error message could IMO be more useful, if it said something > like "Non-WebP header in WebP image data". I went with: "Invalid header in WebP image data". >> + image_error ("Error when interpreting WebP data: `%s'", file); > > Suggest to say "Error when interpreting WebP data from file `%s'" > instead, otherwise it may not be clear to the user what is that string > after the colon. > >> + image_error ("Error when interpreting WebP data"); > > I'd suggest "Error when interpreting WebP image data". Yes, that's better. Fixed. [-- Attachment #2: 0001-Add-WebP-format-support.patch --] [-- Type: text/x-diff, Size: 41281 bytes --] From f4be53db6cbf93c69988e35dce207294fdf1f574 Mon Sep 17 00:00:00 2001 From: Stefan Kangas <stefan@marxist.se> Date: Thu, 21 Oct 2021 20:24:13 +0200 Subject: [PATCH] Add WebP format support * configure.ac (--with-webp): New option. (HAVE_WEBP): New variable. (emacs_config_features): Add webp. * src/image.c (enum webp_keyword_index) [HAVE_WEBP]: New enum. (webp_format) [HAVE_WEBP]: New variable. (webp_image_p, init_webp_functions, webp_load) [HAVE_WEBP]: New functions for WebP support. (image_types) [HAVE_WEBP]: Define WebP format. (syms_of_image) <Qwebp> [HAVE_WEBP]: New DEFSYM. Add image type Qwebp. * src/Makefile.in (LIBIMAGE): Add WEBP_LIBS. * lisp/files.el (auto-mode-alist): * lisp/image-file.el (image-file-name-extensions): * lisp/image.el (image-type-header-regexps) (image-type-file-name-regexps, image-type-auto-detectable): Add WebP support. * lisp/term/w32-win.el (dynamic-library-alist): Add the libwebp DLL. * INSTALL: * admin/CPP-DEFINES: * doc/lispref/display.texi (Image Formats, Other Image Types): * nt/INSTALL: Document WebP support. * test/lisp/image-tests.el (image-find-image) (image-type-from-file-name): Expand tests. * test/src/image-tests.el (image-tests--files): Add WebP. (image-tests-image-size/webp, image-tests-image-mask-p/webp) (image-tests-image-metadata/webp): New tests. * test/data/image/black.webp: New file. --- INSTALL | 2 + admin/CPP-DEFINES | 1 + configure.ac | 28 +++- doc/lispref/display.texi | 11 +- etc/NEWS | 6 + lisp/files.el | 1 + lisp/image-file.el | 2 +- lisp/image.el | 3 + lisp/term/w32-win.el | 1 + nt/INSTALL | 9 +- src/Makefile.in | 2 +- src/image.c | 282 +++++++++++++++++++++++++++++++++++++ test/data/image/black.webp | Bin 0 -> 37780 bytes test/lisp/image-tests.el | 8 +- test/src/image-tests.el | 23 ++- 15 files changed, 364 insertions(+), 15 deletions(-) create mode 100644 test/data/image/black.webp diff --git a/INSTALL b/INSTALL index 6207f43cec..21298422af 100644 --- a/INSTALL +++ b/INSTALL @@ -187,6 +187,7 @@ X11 is being used. X libtiff for TIFF: http://www.simplesystems.org/libtiff/ X libgif for GIF: http://giflib.sourceforge.net/ librsvg2 for SVG: https://wiki.gnome.org/Projects/LibRsvg + libwebp for WebP: https://developers.google.com/speed/webp/ If you supply the appropriate --without-LIB option, 'configure' will omit the corresponding library from Emacs, even if that makes for a @@ -313,6 +314,7 @@ or more of these options: --without-gif for GIF image support --without-png for PNG image support --without-rsvg for SVG image support + --without-webp for WebP image support Although ImageMagick support is disabled by default due to security and stability concerns, you can enable it with --with-imagemagick. diff --git a/admin/CPP-DEFINES b/admin/CPP-DEFINES index 68c12438f5..634d6f3f3b 100644 --- a/admin/CPP-DEFINES +++ b/admin/CPP-DEFINES @@ -287,6 +287,7 @@ HAVE_UTIMENSAT HAVE_UTMP_H HAVE_VFORK HAVE_VFORK_H +HAVE_WEBP HAVE_WCHAR_H HAVE_WCHAR_T HAVE_WINDOW_SYSTEM diff --git a/configure.ac b/configure.ac index 9ab0314428..d091866b87 100644 --- a/configure.ac +++ b/configure.ac @@ -447,6 +447,7 @@ AC_DEFUN OPTION_DEFAULT_ON([gif],[don't compile with GIF image support]) OPTION_DEFAULT_ON([png],[don't compile with PNG image support]) OPTION_DEFAULT_ON([rsvg],[don't compile with SVG image support]) +OPTION_DEFAULT_ON([webp],[don't compile with WebP image support]) OPTION_DEFAULT_ON([lcms2],[don't compile with Little CMS support]) OPTION_DEFAULT_ON([libsystemd],[don't compile with libsystemd support]) OPTION_DEFAULT_ON([cairo],[don't compile with Cairo drawing]) @@ -2588,6 +2589,28 @@ AC_DEFUN fi fi +### Use -lwebp if available, unless '--with-webp=no' +### mingw32 doesn't use -lwebp, since it loads the library dynamically. +HAVE_WEBP=no +if test "${with_webp}" != "no"; then + if test "$opsys" = mingw32; then + AC_CHECK_HEADER([webp/decode.h], [HAVE_WEBP=yes]) + elif test "${HAVE_X11}" = "yes" || test "${HAVE_W32}" = "yes" \ + || test "${HAVE_NS}" = "yes"; then + WEBP_REQUIRED=0.6.0 + WEBP_MODULE="libwebp >= $WEBP_REQUIRED" + + EMACS_CHECK_MODULES([WEBP], [$WEBP_MODULE]) + AC_SUBST(WEBP_CFLAGS) + AC_SUBST(WEBP_LIBS) + + if test $HAVE_WEBP = yes; then + AC_DEFINE(HAVE_WEBP, 1, [Define to 1 if using libwebp.]) + CFLAGS="$CFLAGS $WEBP_CFLAGS" + fi + fi +fi + HAVE_IMAGEMAGICK=no if test "${HAVE_X11}" = "yes" || test "${HAVE_NS}" = "yes" || test "${HAVE_W32}" = "yes"; then if test "${with_imagemagick}" != "no"; then @@ -5882,8 +5905,8 @@ AC_DEFUN for opt in ACL CAIRO DBUS FREETYPE GCONF GIF GLIB GMP GNUTLS GPM GSETTINGS \ HARFBUZZ IMAGEMAGICK JPEG JSON LCMS2 LIBOTF LIBSELINUX LIBSYSTEMD LIBXML2 \ M17N_FLT MODULES NATIVE_COMP NOTIFY NS OLDXMENU PDUMPER PNG RSVG SECCOMP \ - SOUND THREADS TIFF \ - TOOLKIT_SCROLL_BARS UNEXEC X11 XAW3D XDBE XFT XIM XPM XWIDGETS X_TOOLKIT \ + SOUND THREADS TIFF TOOLKIT_SCROLL_BARS \ + UNEXEC WEBP X11 XAW3D XDBE XFT XIM XPM XWIDGETS X_TOOLKIT \ ZLIB; do case $opt in @@ -5928,6 +5951,7 @@ AC_DEFUN Does Emacs use a gif library? ${HAVE_GIF} $LIBGIF Does Emacs use a png library? ${HAVE_PNG} $LIBPNG Does Emacs use -lrsvg-2? ${HAVE_RSVG} + Does Emacs use -lwebp? ${HAVE_WEBP} Does Emacs use cairo? ${HAVE_CAIRO} Does Emacs use -llcms2? ${HAVE_LCMS2} Does Emacs use imagemagick? ${HAVE_IMAGEMAGICK} diff --git a/doc/lispref/display.texi b/doc/lispref/display.texi index 16577d13c1..9c378a3027 100644 --- a/doc/lispref/display.texi +++ b/doc/lispref/display.texi @@ -5264,13 +5264,13 @@ Image Formats Supported image formats (and the required support libraries) include PBM and XBM (which do not depend on support libraries and are always available), XPM (@code{libXpm}), GIF (@code{libgif} or -@code{libungif}), JPEG (@code{libjpeg}), TIFF -(@code{libtiff}), PNG (@code{libpng}), and SVG (@code{librsvg}). +@code{libungif}), JPEG (@code{libjpeg}), TIFF (@code{libtiff}), PNG +(@code{libpng}), SVG (@code{librsvg}), and WebP (@code{libwebp}). Each of these image formats is associated with an @dfn{image type symbol}. The symbols for the above formats are, respectively, -@code{pbm}, @code{xbm}, @code{xpm}, @code{gif}, -@code{jpeg}, @code{tiff}, @code{png}, and @code{svg}. +@code{pbm}, @code{xbm}, @code{xpm}, @code{gif}, @code{jpeg}, +@code{tiff}, @code{png}, @code{svg}, and @code{webp}. Furthermore, if you build Emacs with ImageMagick (@code{libMagickWand}) support, Emacs can display any image format @@ -6274,6 +6274,9 @@ Other Image Types @item TIFF Image type @code{tiff}. Supports the @code{:index} property. @xref{Multi-Frame Images}. + +@item WebP +Image type @code{webp}. @end table @node Defining Images diff --git a/etc/NEWS b/etc/NEWS index c1b8adc511..36d04aa2d8 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -61,6 +61,12 @@ Emacs previously didn't distinguish between the "regular" weight and the "medium" weight, but it now also supports the (heavier) "medium" weight. ++++ +** Support for the WebP image format. +This support is built by default when the libwebp library is +available. To disable it, use the '--without-webp' configure flag. +Image specifiers can now use ':type webp'. + \f * Editing Changes in Emacs 29.1 diff --git a/lisp/files.el b/lisp/files.el index 5a6a33721b..e6b94a4a1a 100644 --- a/lisp/files.el +++ b/lisp/files.el @@ -2758,6 +2758,7 @@ auto-mode-alist ("\\.gif\\'" . image-mode) ("\\.png\\'" . image-mode) ("\\.jpe?g\\'" . image-mode) + ("\\.webp\\'" . image-mode) ("\\.te?xt\\'" . text-mode) ("\\.[tT]e[xX]\\'" . tex-mode) ("\\.ins\\'" . tex-mode) ;Installation files for TeX packages. diff --git a/lisp/image-file.el b/lisp/image-file.el index fbc9eaaf94..6df43f737d 100644 --- a/lisp/image-file.el +++ b/lisp/image-file.el @@ -37,7 +37,7 @@ ;;;###autoload (defcustom image-file-name-extensions - (purecopy '("png" "jpeg" "jpg" "gif" "tiff" "tif" "xbm" "xpm" "pbm" "pgm" "ppm" "pnm" "svg")) + (purecopy '("png" "jpeg" "jpg" "gif" "tiff" "tif" "xbm" "xpm" "pbm" "pgm" "ppm" "pnm" "svg" "webp")) "A list of image-file filename extensions. Filenames having one of these extensions are considered image files, in addition to those matching `image-file-name-regexps'. diff --git a/lisp/image.el b/lisp/image.el index 2022b41d1f..5343e26d03 100644 --- a/lisp/image.el +++ b/lisp/image.el @@ -48,6 +48,7 @@ image-type-header-regexps ("\\`\\(?:MM\0\\*\\|II\\*\0\\)" . tiff) ("\\`[\t\n\r ]*%!PS" . postscript) ("\\`\xff\xd8" . jpeg) ; used to be (image-jpeg-p . jpeg) + ("\\`RIFF....WEBPVP8" . webp) (,(let* ((incomment-re "\\(?:[^-]\\|-[^-]\\)") (comment-re (concat "\\(?:!--" incomment-re "*-->[ \t\r\n]*<\\)"))) (concat "\\(?:<\\?xml[ \t\r\n]+[^>]*>\\)?[ \t\r\n]*<" @@ -67,6 +68,7 @@ image-type-file-name-regexps '(("\\.png\\'" . png) ("\\.gif\\'" . gif) ("\\.jpe?g\\'" . jpeg) + ("\\.webp\\'" . webp) ("\\.bmp\\'" . bmp) ("\\.xpm\\'" . xpm) ("\\.pbm\\'" . pbm) @@ -92,6 +94,7 @@ image-type-auto-detectable (jpeg . maybe) (tiff . maybe) (svg . maybe) + (webp . maybe) (postscript . nil)) "Alist of (IMAGE-TYPE . AUTODETECT) pairs used to auto-detect image files. \(See `image-type-auto-detected-p'). diff --git a/lisp/term/w32-win.el b/lisp/term/w32-win.el index 5d1dc60667..366992cbbf 100644 --- a/lisp/term/w32-win.el +++ b/lisp/term/w32-win.el @@ -274,6 +274,7 @@ libgnutls-version '(gif "libgif-6.dll" "giflib5.dll" "gif.dll") '(gif "libgif-5.dll" "giflib4.dll" "libungif4.dll" "libungif.dll"))) '(svg "librsvg-2-2.dll") + '(libwebp "libwebp-7.dll" "libwebp.dll") '(gdk-pixbuf "libgdk_pixbuf-2.0-0.dll") '(glib "libglib-2.0-0.dll") '(gio "libgio-2.0-0.dll") diff --git a/nt/INSTALL b/nt/INSTALL index 9f543151a9..a39057c66c 100644 --- a/nt/INSTALL +++ b/nt/INSTALL @@ -488,6 +488,7 @@ build will run on Windows 9X and newer systems). Does Emacs use a gif library? yes Does Emacs use a png library? yes Does Emacs use -lrsvg-2? yes + Does Emacs use -lwebp? yes Does Emacs use cairo? no Does Emacs use -llcms2? yes Does Emacs use imagemagick? no @@ -597,8 +598,8 @@ build will run on Windows 9X and newer systems). * Optional image library support In addition to its "native" image formats (pbm and xbm), Emacs can - handle other image types: xpm, tiff, gif, png, jpeg and experimental - support for svg. + handle other image types: xpm, tiff, gif, png, jpeg, webp and + experimental support for svg. To build Emacs with support for them, the corresponding headers must be in the include path and libraries should be where the linker @@ -736,6 +737,10 @@ build will run on Windows 9X and newer systems). without it by specifying the --without-rsvg switch to the configure script. + For WebP images you will need libwebp: + + https://developers.google.com/speed/webp/ + Binaries for the other image libraries can be found on the ezwinports site or at the GnuWin32 project (the latter are generally very old, so not recommended). Note specifically that, due to some diff --git a/src/Makefile.in b/src/Makefile.in index 6d75e3537a..7c977e34ea 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -124,7 +124,7 @@ LIB_MATH= ## -lpthread, or empty. LIB_PTHREAD=@LIB_PTHREAD@ -LIBIMAGE=@LIBTIFF@ @LIBJPEG@ @LIBPNG@ @LIBGIF@ @LIBXPM@ +LIBIMAGE=@LIBTIFF@ @LIBJPEG@ @LIBPNG@ @LIBGIF@ @LIBXPM@ @WEBP_LIBS@ XCB_LIBS=@XCB_LIBS@ XFT_LIBS=@XFT_LIBS@ diff --git a/src/image.c b/src/image.c index ff05741b2c..f65c231836 100644 --- a/src/image.c +++ b/src/image.c @@ -8739,8 +8739,281 @@ gif_load (struct frame *f, struct image *img) #endif /* HAVE_GIF */ +#ifdef HAVE_WEBP + +\f +/*********************************************************************** + WebP + ***********************************************************************/ + +#include "webp/decode.h" + +/* Indices of image specification fields in webp_format, below. */ + +enum webp_keyword_index +{ + WEBP_TYPE, + WEBP_DATA, + WEBP_FILE, + WEBP_ASCENT, + WEBP_MARGIN, + WEBP_RELIEF, + WEBP_ALGORITHM, + WEBP_HEURISTIC_MASK, + WEBP_MASK, + WEBP_BACKGROUND, + WEBP_LAST +}; + +/* Vector of image_keyword structures describing the format + of valid user-defined image specifications. */ + +static const struct image_keyword webp_format[WEBP_LAST] = +{ + {":type", IMAGE_SYMBOL_VALUE, 1}, + {":data", IMAGE_STRING_VALUE, 0}, + {":file", IMAGE_STRING_VALUE, 0}, + {":ascent", IMAGE_ASCENT_VALUE, 0}, + {":margin", IMAGE_NON_NEGATIVE_INTEGER_VALUE_OR_PAIR, 0}, + {":relief", IMAGE_INTEGER_VALUE, 0}, + {":conversion", IMAGE_DONT_CHECK_VALUE_TYPE, 0}, + {":heuristic-mask", IMAGE_DONT_CHECK_VALUE_TYPE, 0}, + {":mask", IMAGE_DONT_CHECK_VALUE_TYPE, 0}, + {":background", IMAGE_STRING_OR_NIL_VALUE, 0} +}; + +/* Return true if OBJECT is a valid WebP image specification. */ + +static bool +webp_image_p (Lisp_Object object) +{ + struct image_keyword fmt[WEBP_LAST]; + memcpy (fmt, webp_format, sizeof fmt); + + if (!parse_image_spec (object, fmt, WEBP_LAST, Qwebp)) + return false; + + /* Must specify either the :data or :file keyword. */ + return fmt[WEBP_FILE].count + fmt[WEBP_DATA].count == 1; +} + +#ifdef WINDOWSNT + +/* WebP library details. */ + +DEF_DLL_FN (int, WebPGetInfo, (const uint8_t *, size_t, int *, int *)); +DEF_DLL_FN (VP8StatusCode, WebPGetFeatures, (const uint8_t *, size_t, WebPBitstreamFeatures *)); +DEF_DLL_FN (uint8_t *, WebPDecodeRGB, (const uint8_t *, size_t, int *, int *)); +DEF_DLL_FN (uint8_t *, WebPDecodeBGR, (const uint8_t *, size_t, int *, int *)); +DEF_DLL_FN (void, WebPFreeDecBuffer (WebPDecBuffer *)); + +static bool +init_webp_functions (void) +{ + HMODULE library; + + if (!(library = w32_delayed_load (Qwebp))) + return false; + + LOAD_DLL_FN (library, WebPGetInfo); + LOAD_DLL_FN (library, WebPGetFeatures); + LOAD_DLL_FN (library, WebPDecodeRGBA); + LOAD_DLL_FN (library, WebPDecodeRGB); + LOAD_DLL_FN (library, WebPFree); + return true; +} + +#undef WebPGetInfo +#undef WebPGetFeatures +#undef WebPDecodeRGBA +#undef WebPDecodeRGB +#undef WebPFree + +#define WebPGetInfo fn_WebPGetInfo +#define WebPGetFeatures fn_WebPGetFeatures +#define WebPDecodeRGBA fn_WebPDecodeRGBA +#define WebPDecodeRGB fn_WebPDecodeRGB +#define WebPFree fn_WebPFree + +#endif /* WINDOWSNT */ + +/* Load WebP image IMG for use on frame F. Value is true if + successful. */ + +static bool +webp_load (struct frame *f, struct image *img) +{ + ptrdiff_t size = 0; + uint8_t *contents; + Lisp_Object file; + + /* Open the WebP file. */ + Lisp_Object specified_file = image_spec_value (img->spec, QCfile, NULL); + Lisp_Object specified_data = image_spec_value (img->spec, QCdata, NULL); + + if (NILP (specified_data)) + { + int fd; + file = image_find_image_fd (specified_file, &fd); + if (!STRINGP (file)) + { + image_error ("Cannot find image file `%s'", specified_file); + return false; + } + + contents = (uint8_t *) slurp_file (fd, &size); + if (contents == NULL) + { + image_error ("Error loading WebP image `%s'", file); + return false; + } + } + else + { + if (!STRINGP (specified_data)) + { + image_error ("Invalid image data `%s'", specified_data); + return false; + } + /* Casting avoids a GCC warning. */ + contents = (uint8_t *) SSDATA (specified_data); + size = SBYTES (specified_data); + } + + /* Validate the WebP image header. */ + if (!WebPGetInfo (contents, size, NULL, NULL)) + { + if (!NILP (specified_data)) + image_error ("Not a WebP file: `%s'", file); + else + image_error ("Invalid header in WebP image data"); + goto webp_error1; + } + + /* Get WebP features. */ + WebPBitstreamFeatures features; + VP8StatusCode result = WebPGetFeatures (contents, size, &features); + switch (result) + { + case VP8_STATUS_OK: + break; + case VP8_STATUS_NOT_ENOUGH_DATA: + case VP8_STATUS_OUT_OF_MEMORY: + case VP8_STATUS_INVALID_PARAM: + case VP8_STATUS_BITSTREAM_ERROR: + case VP8_STATUS_UNSUPPORTED_FEATURE: + case VP8_STATUS_SUSPENDED: + case VP8_STATUS_USER_ABORT: + default: + /* Error out in all other cases. */ + if (!NILP (specified_data)) + image_error ("Error when interpreting WebP image data: `%s'", file); + else + image_error ("Error when interpreting WebP image data"); + goto webp_error1; + } + + /* Decode WebP data. */ + uint8_t *decoded; + int width, height; + if (features.has_alpha) + /* Linear [r0, g0, b0, a0, r1, g1, b1, a1, ...] order. */ + decoded = WebPDecodeRGBA (contents, size, &width, &height); + else + /* Linear [r0, g0, b0, r1, g1, b1, ...] order. */ + decoded = WebPDecodeRGB (contents, size, &width, &height); + + if (!(width <= INT_MAX && height <= INT_MAX + && check_image_size (f, width, height))) + { + image_size_error (); + goto webp_error2; + } + + /* Create the x image and pixmap. */ + Emacs_Pix_Container ximg, mask_img; + if (!image_create_x_image_and_pixmap (f, img, width, height, 0, &ximg, false)) + goto webp_error2; + + /* Create an image and pixmap serving as mask if the WebP image + contains an alpha channel. */ + if (features.has_alpha + && !image_create_x_image_and_pixmap (f, img, width, height, 1, &mask_img, true)) + { + image_destroy_x_image (ximg); + image_clear_image_1 (f, img, CLEAR_IMAGE_PIXMAP); + goto webp_error2; + } + + /* Fill the X image and mask from WebP data. */ + init_color_table (); + + uint8_t *p = decoded; + for (int y = 0; y < height; ++y) + { + for (int x = 0; x < width; ++x) + { + int r = *p++ << 8; + int g = *p++ << 8; + int b = *p++ << 8; + PUT_PIXEL (ximg, x, y, lookup_rgb_color (f, r, g, b)); + + /* An alpha channel associates variable transparency with an + image. WebP allows up to 256 levels of partial transparency. + We handle this like with PNG (which see), using the frame's + background color to combine the image with. */ + if (features.has_alpha) + { + if (mask_img) + PUT_PIXEL (mask_img, x, y, *p > 0 ? PIX_MASK_DRAW : PIX_MASK_RETAIN); + ++p; + } + } + } + +#ifdef COLOR_TABLE_SUPPORT + /* Remember colors allocated for this image. */ + img->colors = colors_in_color_table (&img->ncolors); + free_color_table (); +#endif /* COLOR_TABLE_SUPPORT */ + + /* Put ximg into the image. */ + image_put_x_image (f, img, ximg, 0); + + /* Same for the mask. */ + if (mask_img) + { + /* Fill in the background_transparent field while we have the + mask handy. Casting avoids a GCC warning. */ + image_background_transparent (img, f, (Emacs_Pix_Context)mask_img); + + image_put_x_image (f, img, mask_img, 1); + } + + img->width = width; + img->height = height; + + /* Clean up. */ + WebPFree (decoded); + if (NILP (specified_data)) + xfree (contents); + return true; + + webp_error2: + WebPFree (decoded); + + webp_error1: + if (NILP (specified_data)) + xfree (contents); + return false; +} + +#endif /* HAVE_WEBP */ + + #ifdef HAVE_IMAGEMAGICK +\f /*********************************************************************** ImageMagick ***********************************************************************/ @@ -10725,6 +10998,10 @@ initialize_image_type (struct image_type const *type) #if defined HAVE_XPM || defined HAVE_NS { SYMBOL_INDEX (Qxpm), xpm_image_p, xpm_load, image_clear_image, IMAGE_TYPE_INIT (init_xpm_functions) }, +#endif +#if defined HAVE_WEBP + { SYMBOL_INDEX (Qwebp), webp_image_p, webp_load, image_clear_image, + IMAGE_TYPE_INIT (init_webp_functions) }, #endif { SYMBOL_INDEX (Qxbm), xbm_image_p, xbm_load, image_clear_image }, { SYMBOL_INDEX (Qpbm), pbm_image_p, pbm_load, image_clear_image }, @@ -10891,6 +11168,11 @@ syms_of_image (void) add_image_type (Qpng); #endif +#if defined (HAVE_WEBP) + DEFSYM (Qwebp, "webp"); + add_image_type (Qwebp); +#endif + #if defined (HAVE_IMAGEMAGICK) DEFSYM (Qimagemagick, "imagemagick"); add_image_type (Qimagemagick); diff --git a/test/data/image/black.webp b/test/data/image/black.webp new file mode 100644 index 0000000000000000000000000000000000000000..5dbe716415b16e6111ac92ec3978411d95be08e3 GIT binary patch literal 37780 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 literal 0 HcmV?d00001 diff --git a/test/lisp/image-tests.el b/test/lisp/image-tests.el index aa8600609c..c34c152cc9 100644 --- a/test/lisp/image-tests.el +++ b/test/lisp/image-tests.el @@ -49,12 +49,14 @@ image--set-property (should (equal image '(image))))) (ert-deftest image-find-image () - (find-image '((:type xpm :file "undo.xpm"))) - (find-image '((:type png :file "newsticker/rss-feed.png" :ascent center)))) + (should (listp (find-image '((:type xpm :file "undo.xpm"))))) + (should (listp (find-image '((:type png :file "newsticker/rss-feed.png" :ascent center))))) + (should-not (find-image '((:type png :file "does-not-exist-foo-bar.png"))))) (ert-deftest image-type-from-file-name () (should (eq (image-type-from-file-name "foo.jpg") 'jpeg)) - (should (eq (image-type-from-file-name "foo.png") 'png))) + (should (eq (image-type-from-file-name "foo.png") 'png)) + (should (eq (image-type-from-file-name "foo.webp") 'webp))) (ert-deftest image-type/from-filename () ;; On emba, `image-types' and `image-load-path' do not exist. diff --git a/test/src/image-tests.el b/test/src/image-tests.el index d5e3a7cc5c..b921739a05 100644 --- a/test/src/image-tests.el +++ b/test/src/image-tests.el @@ -44,6 +44,8 @@ image-tests--files (tiff . ,(expand-file-name "nextstep/GNUstep/Emacs.base/Resources/emacs.tiff" source-directory)) + (webp . ,(expand-file-name "test/data/image/black.webp" + source-directory)) (xbm . ,(find-image '((:file "gnus/gnus.xbm" :type xbm)))) (xpm . ,(find-image '((:file "splash.xpm" :type xpm)))) ;; TODO: gif @@ -86,6 +88,13 @@ image-tests-image-size/tiff (should (floatp a)) (should (floatp b))))) +(ert-deftest image-tests-image-size/webp () + (image-skip-unless 'webp) + (pcase (image-size (create-image (cdr (assq 'webp image-tests--files)))) + (`(,a . ,b) + (should (floatp a)) + (should (floatp b))))) + (ert-deftest image-tests-image-size/xbm () (image-skip-unless 'xbm) (pcase (image-size (cdr (assq 'xbm image-tests--files))) @@ -130,7 +139,12 @@ image-tests-image-mask-p/svg (ert-deftest image-tests-image-mask-p/tiff () (image-skip-unless 'tiff) (should-not (image-mask-p (create-image - (cdr (assq 'tiff image-tests--files)))))) + (cdr (assq 'tiff image-tests--files)))))) + +(ert-deftest image-tests-image-mask-p/webp () + (image-skip-unless 'webp) + (should-not (image-mask-p (create-image + (cdr (assq 'webp image-tests--files)))))) (ert-deftest image-tests-image-mask-p/xbm () (image-skip-unless 'xbm) @@ -173,7 +187,12 @@ image-tests-image-metadata/svg (ert-deftest image-tests-image-metadata/tiff () (image-skip-unless 'tiff) (should-not (image-metadata - (create-image (cdr (assq 'tiff image-tests--files)))))) + (create-image (cdr (assq 'tiff image-tests--files)))))) + +(ert-deftest image-tests-image-metadata/webp () + (image-skip-unless 'webp) + (should-not (image-metadata + (create-image (cdr (assq 'webp image-tests--files)))))) (ert-deftest image-tests-image-metadata/xbm () (image-skip-unless 'xbm) -- 2.30.2 ^ permalink raw reply related [flat|nested] 21+ messages in thread
* bug#51296: [PATCH] Add WebP format support 2021-10-21 18:36 ` Stefan Kangas @ 2021-10-21 18:39 ` Eli Zaretskii 2021-10-21 21:19 ` Stefan Kangas 0 siblings, 1 reply; 21+ messages in thread From: Eli Zaretskii @ 2021-10-21 18:39 UTC (permalink / raw) To: Stefan Kangas; +Cc: 51296 > From: Stefan Kangas <stefan@marxist.se> > Date: Thu, 21 Oct 2021 11:36:45 -0700 > Cc: 51296@debbugs.gnu.org > > >> + contents = (uint8_t*) SSDATA (specified_data); > > > > Space before '*' again. Also, is the type cast really needed? If > > not, it is better to drop it. > > Fixed the style issue. > > The cast fixes this warning, so I kept it and added a comment saying > "Casting avoids a GCC warning": > > image.c: In function ‘webp_load’: > image.c:8878:16: warning: pointer targets in assignment from ‘char *’ > to ‘uint8_t *’ {aka ‘unsigned char *’} differ in signedness > [-Wpointer-sign] > 8878 | contents = SSDATA (specified_data); > | ^ And if you use SDATA instead of SSDATA? Otherwise, I think this is good to go, thanks. ^ permalink raw reply [flat|nested] 21+ messages in thread
* bug#51296: [PATCH] Add WebP format support 2021-10-21 18:39 ` Eli Zaretskii @ 2021-10-21 21:19 ` Stefan Kangas 2021-10-22 6:16 ` Eli Zaretskii 0 siblings, 1 reply; 21+ messages in thread From: Stefan Kangas @ 2021-10-21 21:19 UTC (permalink / raw) To: Eli Zaretskii; +Cc: 51296 Eli Zaretskii <eliz@gnu.org> writes: > And if you use SDATA instead of SSDATA? Yes, that fixed it. I guess SSDATA is there specifically to avoid casting warnings, but in this case only produced one... > Otherwise, I think this is good to go, thanks. Great! If it's fine by you, I will push this to master tomorrow (or the day after) at a time when you are typically online, just in case there is any fallout on MS-Windows. ^ permalink raw reply [flat|nested] 21+ messages in thread
* bug#51296: [PATCH] Add WebP format support 2021-10-21 21:19 ` Stefan Kangas @ 2021-10-22 6:16 ` Eli Zaretskii 2021-10-22 9:03 ` Stefan Kangas 0 siblings, 1 reply; 21+ messages in thread From: Eli Zaretskii @ 2021-10-22 6:16 UTC (permalink / raw) To: Stefan Kangas; +Cc: 51296 > From: Stefan Kangas <stefan@marxist.se> > Date: Thu, 21 Oct 2021 14:19:26 -0700 > Cc: 51296@debbugs.gnu.org > > Eli Zaretskii <eliz@gnu.org> writes: > > > And if you use SDATA instead of SSDATA? > > Yes, that fixed it. I guess SSDATA is there specifically to avoid > casting warnings, but in this case only produced one... Yes. The original macro was SDATA, and we added SSDATA later, when GCC started emitting these annoying warnings. > > Otherwise, I think this is good to go, thanks. > > Great! If it's fine by you, I will push this to master tomorrow (or the > day after) at a time when you are typically online, just in case there > is any fallout on MS-Windows. Please go ahead, and thanks. ^ permalink raw reply [flat|nested] 21+ messages in thread
* bug#51296: [PATCH] Add WebP format support 2021-10-22 6:16 ` Eli Zaretskii @ 2021-10-22 9:03 ` Stefan Kangas 2021-10-22 12:06 ` Eli Zaretskii 0 siblings, 1 reply; 21+ messages in thread From: Stefan Kangas @ 2021-10-22 9:03 UTC (permalink / raw) To: Eli Zaretskii; +Cc: 51296 close 51296 29.1 thanks Eli Zaretskii <eliz@gnu.org> writes: > Please go ahead, and thanks. Now pushed to master (commit bc2a5c1127). I'm closing this bug as fixed, but we can obviously continue discussing any remaining issues here. ^ permalink raw reply [flat|nested] 21+ messages in thread
* bug#51296: [PATCH] Add WebP format support 2021-10-22 9:03 ` Stefan Kangas @ 2021-10-22 12:06 ` Eli Zaretskii 2021-10-22 12:47 ` Eli Zaretskii 0 siblings, 1 reply; 21+ messages in thread From: Eli Zaretskii @ 2021-10-22 12:06 UTC (permalink / raw) To: Stefan Kangas; +Cc: 51296 > From: Stefan Kangas <stefan@marxist.se> > Date: Fri, 22 Oct 2021 02:03:22 -0700 > Cc: 51296@debbugs.gnu.org > > close 51296 29.1 > thanks > > Eli Zaretskii <eliz@gnu.org> writes: > > > Please go ahead, and thanks. > > Now pushed to master (commit bc2a5c1127). Thanks. This has some issues, and currently won't work on MS-Windows. I'm working on that. ^ permalink raw reply [flat|nested] 21+ messages in thread
* bug#51296: [PATCH] Add WebP format support 2021-10-22 12:06 ` Eli Zaretskii @ 2021-10-22 12:47 ` Eli Zaretskii 2021-10-22 14:27 ` Stefan Kangas 0 siblings, 1 reply; 21+ messages in thread From: Eli Zaretskii @ 2021-10-22 12:47 UTC (permalink / raw) To: stefan; +Cc: 51296 > Date: Fri, 22 Oct 2021 15:06:40 +0300 > From: Eli Zaretskii <eliz@gnu.org> > Cc: 51296@debbugs.gnu.org > > > Now pushed to master (commit bc2a5c1127). > > Thanks. This has some issues, and currently won't work on MS-Windows. > I'm working on that. Now done and installed. WebP support should now work on MS-Windows. ^ permalink raw reply [flat|nested] 21+ messages in thread
* bug#51296: [PATCH] Add WebP format support 2021-10-22 12:47 ` Eli Zaretskii @ 2021-10-22 14:27 ` Stefan Kangas 0 siblings, 0 replies; 21+ messages in thread From: Stefan Kangas @ 2021-10-22 14:27 UTC (permalink / raw) To: Eli Zaretskii; +Cc: 51296 Eli Zaretskii <eliz@gnu.org> writes: > Now done and installed. WebP support should now work on MS-Windows. Thank you. ^ permalink raw reply [flat|nested] 21+ messages in thread
end of thread, other threads:[~2021-10-22 14:27 UTC | newest] Thread overview: 21+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2021-10-19 23:27 bug#51296: [PATCH] Add WebP format support Stefan Kangas 2021-10-20 8:42 ` Lars Ingebrigtsen 2021-10-20 12:40 ` Stefan Kangas 2021-10-20 13:02 ` Stefan Kangas 2021-10-20 13:14 ` Eli Zaretskii 2021-10-20 15:22 ` Stefan Kangas 2021-10-20 16:35 ` Eli Zaretskii 2021-10-20 17:13 ` Eli Zaretskii 2021-10-20 17:41 ` Stefan Kangas 2021-10-20 18:19 ` Eli Zaretskii 2021-10-20 21:02 ` Stefan Kangas 2021-10-21 0:45 ` Stefan Kangas 2021-10-21 8:19 ` Eli Zaretskii 2021-10-21 18:36 ` Stefan Kangas 2021-10-21 18:39 ` Eli Zaretskii 2021-10-21 21:19 ` Stefan Kangas 2021-10-22 6:16 ` Eli Zaretskii 2021-10-22 9:03 ` Stefan Kangas 2021-10-22 12:06 ` Eli Zaretskii 2021-10-22 12:47 ` Eli Zaretskii 2021-10-22 14:27 ` Stefan Kangas
Code repositories for project(s) associated with this external index https://git.savannah.gnu.org/cgit/emacs.git https://git.savannah.gnu.org/cgit/emacs/org-mode.git This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.