diff --git a/configure.ac b/configure.ac index a4daf1414d..0808bd86b9 100644 --- a/configure.ac +++ b/configure.ac @@ -433,6 +433,7 @@ AC_DEFUN OPTION_DEFAULT_ON([cairo],[don't compile with Cairo drawing]) OPTION_DEFAULT_ON([xml2],[don't compile with XML parsing support]) OPTION_DEFAULT_OFF([imagemagick],[compile with ImageMagick image support]) +OPTION_DEFAULT_ON([gdiplus], [disable use of GDI+ on Windows for JPEG/TIFF/GIFF/PNG]) OPTION_DEFAULT_ON([json], [don't compile with native JSON support]) OPTION_DEFAULT_ON([xft],[don't use XFT for anti aliased fonts]) @@ -2132,6 +2133,7 @@ AC_DEFUN NTLIB= CM_OBJ="cm.o" XARGS_LIMIT= +HAVE_GDIPLUS=no if test "${HAVE_W32}" = "yes"; then AC_DEFINE(HAVE_NTGUI, 1, [Define to use native MS Windows GUI.]) if test "$with_toolkit_scroll_bars" = "no"; then @@ -2160,8 +2162,14 @@ AC_DEFUN # the rc file), not a linker script. W32_RES_LINK="-Wl,emacs.res" else - W32_OBJ="$W32_OBJ w32.o w32console.o w32heap.o w32inevt.o w32proc.o" - W32_LIBS="$W32_LIBS -lwinmm -lusp10 -lgdi32 -lcomdlg32" + if test "${with_gdiplus}" = yes; then + AC_DEFINE(HAVE_GDIPLUS, 1, [Define to use MS Windows GDI+ for images.]) + HAVE_GDIPLUS=yes + W32_GDIPLUS="w32image.o" + W32_GDILIBS="-lgdiplus -lshlwapi" + fi + W32_OBJ="$W32_OBJ w32.o w32console.o w32heap.o w32inevt.o w32proc.o $W32_GDIPLUS" + W32_LIBS="$W32_LIBS -lwinmm -lusp10 $W32_GDILIBS -lgdi32 -lcomdlg32" W32_LIBS="$W32_LIBS -lmpr -lwinspool -lole32 -lcomctl32" W32_RES_LINK="\$(EMACSRES)" CLIENTRES="emacsclient.res" @@ -3572,8 +3580,8 @@ AC_DEFUN ### Use -ljpeg if available, unless '--with-jpeg=no'. HAVE_JPEG=no LIBJPEG= -if test "${NS_IMPL_COCOA}" = yes; then - : # Cocoa provides its own jpeg support, so do nothing. +if test "${NS_IMPL_COCOA}" = yes || test "${HAVE_GDIPLUS}" = "yes"; then + : # Cocoa and Windows' GDI+ provide their own jpeg support, so do nothing. elif test "${HAVE_X11}" = "yes" || test "${HAVE_W32}" = "yes"; then if test "${with_jpeg}" != "no"; then AC_CACHE_CHECK([for jpeglib 6b or later], @@ -3723,8 +3731,8 @@ AC_DEFUN HAVE_PNG=no LIBPNG= PNG_CFLAGS= -if test "${NS_IMPL_COCOA}" = yes; then - : # Cocoa provides its own png support, so do nothing. +if test "${NS_IMPL_COCOA}" = yes || test "${HAVE_GDIPLUS}" = "yes"; then + : # Cocoa and Windows' GDI+ provide their own png support, so do nothing. elif test "${with_png}" != no; then # mingw32 loads the library dynamically. if test "$opsys" = mingw32; then @@ -3796,7 +3804,9 @@ AC_DEFUN ### mingw32 doesn't use -ltiff, since it loads the library dynamically. HAVE_TIFF=no LIBTIFF= -if test "${opsys}" = "mingw32"; then +if test "${HAVE_GDIPLUS}" = "yes"; then + : # Windows' GDI+ supports TIFF +elif test "${opsys}" = "mingw32"; then if test "${with_tiff}" != "no"; then AC_CHECK_HEADER(tiffio.h, HAVE_TIFF=yes, HAVE_TIFF=no) fi @@ -3824,7 +3834,9 @@ AC_DEFUN ### mingw32 doesn't use -lgif/-lungif, since it loads the library dynamically. HAVE_GIF=no LIBGIF= -if test "${opsys}" = "mingw32"; then +if test "${HAVE_GDIPLUS}" = "yes"; then + : # Windows' GDI+ supports TIFF +elif test "${opsys}" = "mingw32"; then if test "${with_gif}" != "no"; then AC_CHECK_HEADER(gif_lib.h, HAVE_GIF=yes, HAVE_GIF=no) fi @@ -5707,6 +5719,7 @@ AC_DEFUN Does Emacs use a png library? ${HAVE_PNG} $LIBPNG Does Emacs use -lrsvg-2? ${HAVE_RSVG} Does Emacs use cairo? ${HAVE_CAIRO} + Does Emacs use GDI+? ${HAVE_GDIPLUS} Does Emacs use -llcms2? ${HAVE_LCMS2} Does Emacs use imagemagick? ${HAVE_IMAGEMAGICK} Does Emacs support sound? ${HAVE_SOUND} diff --git a/etc/NEWS b/etc/NEWS index 870d39f7ee..6fc327665d 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -297,6 +297,12 @@ such as "2020-01-15T16:12:21-08:00". 'module-file-suffix' now has the value ".dylib" on macOS, but the ".so" suffix is supported as well. ++++ +** Emacs can now use Microsoft Windows GDI+ library to load bitmap images in +JPEG, PNG, GIF and TIFF formats. This support is enabled with --with-gdiplus, +which automatically disables the use of third party libraries for those +formats. + ---------------------------------------------------------------------- This file is part of GNU Emacs. diff --git a/src/Makefile.in b/src/Makefile.in index 552dd2e50a..0dc613352b 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -280,10 +280,12 @@ GNU_OBJC_CFLAGS= ## w32fns.o w32menu.c w32reg.o fringe.o fontset.o w32font.o w32term.o ## w32xfns.o w32select.o image.o w32uniscribe.o w32cygwinx.o if HAVE_W32, ## w32cygwinx.o if CYGWIN but not HAVE_W32, else empty. +## w32image.o if we use GDI+ W32_OBJ=@W32_OBJ@ ## -lkernel32 -luser32 -lusp10 -lgdi32 -lole32 -lcomdlg32 -lcomctl32 ## -lwinspool if HAVE_W32, ## -lkernel32 if CYGWIN but not HAVE_W32, else empty. +## -lshlwapi if we use GDI+ W32_LIBS=@W32_LIBS@ ## emacs.res if HAVE_W32 @@ -435,7 +437,7 @@ SOME_MACHINE_OBJECTS = fontset.o dbusbind.o cygw32.o \ nsterm.o nsfns.o nsmenu.o nsselect.o nsimage.o nsfont.o macfont.o \ w32.o w32console.o w32cygwinx.o w32fns.o w32heap.o w32inevt.o w32notify.o \ - w32menu.o w32proc.o w32reg.o w32select.o w32term.o w32xfns.o \ + w32menu.o w32proc.o w32reg.o w32select.o w32term.o w32image.o w32xfns.o \ w16select.o widget.o xfont.o ftfont.o xftfont.o gtkutil.o \ xsettings.o xgselect.o termcap.o hbfont.o diff --git a/src/image.c b/src/image.c index 65d59254f0..b0af991af7 100644 --- a/src/image.c +++ b/src/image.c @@ -18,6 +18,12 @@ Copyright (C) 1989, 1992-2020 Free Software Foundation, Inc. along with GNU Emacs. If not, see . */ #include +#ifdef HAVE_GDIPLUS +#undef HAVE_JPEG +#undef HAVE_PNG +#undef HAVE_GIF +#undef HAVE_TIFF +#endif #include #include @@ -6235,7 +6241,7 @@ pbm_load (struct frame *f, struct image *img) PNG ***********************************************************************/ -#if defined (HAVE_PNG) || defined (HAVE_NS) +#if defined (HAVE_PNG) || defined (HAVE_NS) || defined (HAVE_GDIPLUS) /* Indices of image specification fields in png_format, below. */ @@ -6289,7 +6295,7 @@ png_image_p (Lisp_Object object) #endif /* HAVE_PNG || HAVE_NS */ -#if defined HAVE_PNG && !defined HAVE_NS +#if defined HAVE_PNG && !defined HAVE_NS && !defined HAVE_GDIPLUS # ifdef WINDOWSNT /* PNG library details. */ @@ -6889,8 +6895,19 @@ png_load (struct frame *f, struct image *img) image_spec_value (img->spec, QCdata, NULL)); } +#elif defined HAVE_GDIPLUS -#endif /* HAVE_NS */ +static bool +png_load (struct frame *f, struct image *img) +{ + return w32_load_image (f, img, + image_spec_value (img->spec, QCfile, NULL), + image_spec_value (img->spec, QCdata, NULL)); +} + +#define init_png_functions w32_gdiplus_startup + +#endif /* HAVE_GDIPLUS */ @@ -6898,7 +6915,7 @@ png_load (struct frame *f, struct image *img) JPEG ***********************************************************************/ -#if defined (HAVE_JPEG) || defined (HAVE_NS) +#if defined (HAVE_JPEG) || defined (HAVE_NS) || defined (HAVE_GDIPLUS) /* Indices of image specification fields in gs_format, below. */ @@ -6950,7 +6967,7 @@ jpeg_image_p (Lisp_Object object) return fmt[JPEG_FILE].count + fmt[JPEG_DATA].count == 1; } -#endif /* HAVE_JPEG || HAVE_NS */ +#endif /* HAVE_JPEG || HAVE_NS || HAVE_GDIPLUS */ #ifdef HAVE_JPEG @@ -7464,6 +7481,19 @@ jpeg_load (struct frame *f, struct image *img) } #endif /* HAVE_NS */ +#ifdef HAVE_GDIPLUS +static bool +jpeg_load (struct frame *f, struct image *img) +{ + return w32_load_image (f, img, + image_spec_value (img->spec, QCfile, NULL), + image_spec_value (img->spec, QCdata, NULL)); +} + +#define init_jpeg_functions w32_gdiplus_startup +#endif /* HAVE_GDIPLUS */ + + #endif /* !HAVE_JPEG */ @@ -7472,7 +7502,7 @@ jpeg_load (struct frame *f, struct image *img) TIFF ***********************************************************************/ -#if defined (HAVE_TIFF) || defined (HAVE_NS) +#if defined (HAVE_TIFF) || defined (HAVE_NS) || defined (HAVE_GDIPLUS) /* Indices of image specification fields in tiff_format, below. */ @@ -7525,7 +7555,7 @@ tiff_image_p (Lisp_Object object) return fmt[TIFF_FILE].count + fmt[TIFF_DATA].count == 1; } -#endif /* HAVE_TIFF || HAVE_NS */ +#endif /* HAVE_TIFF || HAVE_NS || HAVE_GDIPLUS */ #ifdef HAVE_TIFF @@ -7903,6 +7933,18 @@ tiff_load (struct frame *f, struct image *img) image_spec_value (img->spec, QCdata, NULL)); } +#elif defined HAVE_GDIPLUS + +static bool +tiff_load (struct frame *f, struct image *img) +{ + return w32_load_image (f, img, + image_spec_value (img->spec, QCfile, NULL), + image_spec_value (img->spec, QCdata, NULL)); +} + +#define init_tiff_functions w32_gdiplus_startup + #endif @@ -7911,7 +7953,7 @@ tiff_load (struct frame *f, struct image *img) GIF ***********************************************************************/ -#if defined (HAVE_GIF) || defined (HAVE_NS) +#if defined (HAVE_GIF) || defined (HAVE_NS) || defined (HAVE_GDIPLUS) /* Indices of image specification fields in gif_format, below. */ @@ -7973,7 +8015,7 @@ gif_image_p (Lisp_Object object) return fmt[GIF_FILE].count + fmt[GIF_DATA].count == 1; } -#endif /* HAVE_GIF */ +#endif /* HAVE_GIF || HAVE_NS || HAVE_GDIPLUS */ #ifdef HAVE_GIF @@ -8502,6 +8544,17 @@ gif_load (struct frame *f, struct image *img) } #endif /* HAVE_NS */ +#ifdef HAVE_NTGUI +static bool +gif_load (struct frame *f, struct image *img) +{ + return w32_load_image (f, img, + image_spec_value (img->spec, QCfile, NULL), + image_spec_value (img->spec, QCdata, NULL)); +} +#define init_gif_functions w32_gdiplus_startup +#endif /* HAVE_NTGUI */ + #endif /* HAVE_GIF */ @@ -10164,19 +10217,19 @@ initialize_image_type (struct image_type const *type) { SYMBOL_INDEX (Qsvg), svg_image_p, svg_load, image_clear_image, IMAGE_TYPE_INIT (init_svg_functions) }, #endif -#if defined HAVE_PNG || defined HAVE_NS +#if defined HAVE_PNG || defined HAVE_NS || defined HAVE_GDIPLUS { SYMBOL_INDEX (Qpng), png_image_p, png_load, image_clear_image, IMAGE_TYPE_INIT (init_png_functions) }, #endif -#if defined HAVE_GIF || defined HAVE_NS +#if defined HAVE_GIF || defined HAVE_NS || defined HAVE_GDIPLUS { SYMBOL_INDEX (Qgif), gif_image_p, gif_load, gif_clear_image, IMAGE_TYPE_INIT (init_gif_functions) }, #endif -#if defined HAVE_TIFF || defined HAVE_NS +#if defined HAVE_TIFF || defined HAVE_NS || defined HAVE_GDIPLUS { SYMBOL_INDEX (Qtiff), tiff_image_p, tiff_load, image_clear_image, IMAGE_TYPE_INIT (init_tiff_functions) }, #endif -#if defined HAVE_JPEG || defined HAVE_NS +#if defined HAVE_JPEG || defined HAVE_NS || defined HAVE_GDIPLUS { SYMBOL_INDEX (Qjpeg), jpeg_image_p, jpeg_load, image_clear_image, IMAGE_TYPE_INIT (init_jpeg_functions) }, #endif @@ -10315,22 +10368,22 @@ syms_of_image (void) add_image_type (Qxpm); #endif -#if defined (HAVE_JPEG) || defined (HAVE_NS) +#if defined (HAVE_JPEG) || defined (HAVE_NS) || defined (HAVE_GDIPLUS) DEFSYM (Qjpeg, "jpeg"); add_image_type (Qjpeg); #endif -#if defined (HAVE_TIFF) || defined (HAVE_NS) +#if defined (HAVE_TIFF) || defined (HAVE_NS) || defined (HAVE_GDIPLUS) DEFSYM (Qtiff, "tiff"); add_image_type (Qtiff); #endif -#if defined (HAVE_GIF) || defined (HAVE_NS) +#if defined (HAVE_GIF) || defined (HAVE_NS) || defined (HAVE_GDIPLUS) DEFSYM (Qgif, "gif"); add_image_type (Qgif); #endif -#if defined (HAVE_PNG) || defined (HAVE_NS) +#if defined (HAVE_PNG) || defined (HAVE_NS) || defined(HAVE_GDIPLUS) DEFSYM (Qpng, "png"); add_image_type (Qpng); #endif diff --git a/src/w32.c b/src/w32.c index 698e10e234..1d2a52b6df 100644 --- a/src/w32.c +++ b/src/w32.c @@ -10225,6 +10225,10 @@ term_ntproc (int ignored) term_winsock (); term_w32select (); + +#ifdef HAVE_GDIPLUS + w32_gdiplus_shutdown (); +#endif } void diff --git a/src/w32image.c b/src/w32image.c new file mode 100644 index 0000000000..e8469b9c94 --- /dev/null +++ b/src/w32image.c @@ -0,0 +1,275 @@ +/* Implementation of GUI terminal on the Microsoft Windows API. + +Copyright (C) 1989, 1993-2020 Free Software Foundation, Inc. + +This file is part of GNU Emacs. + +GNU Emacs is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or (at +your option) any later version. + +GNU Emacs is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Emacs. If not, see . */ + +#include +#include "lisp.h" +#include "dispextern.h" +#define COBJMACROS +#include +#include +#include +#include +#include "w32term.h" +#include "frame.h" +#include "coding.h" + + +#if 0 +/* + * The following code is not really needed, but it provides an illustration on + * how to probe the list of available codecs. + */ + +static void +print_wchar(FILE *stream, WCHAR *string) +{ + for ( ; *string; ++string) { + fputc (*string, stream); + } +} + +static void +w32_list_decoders (void) +{ + UINT ndecoders, size; + ImageCodecInfo *pCodec; + int i; + + GdipGetImageDecodersSize(&ndecoders, &size); + pCodec = (ImageCodecInfo *)malloc(size); + + GdipGetImageDecoders(ndecoders, size, pCodec); + for (i = 0; i < ndecoders; ++i) + { + fprintf(stderr, "\nCodec name: "); print_wchar (stderr, pCodec[i].CodecName); + fprintf(stderr, "\n format: "); print_wchar (stderr, pCodec[i].FormatDescription); + fprintf(stderr, "\n ext: "); print_wchar (stderr, pCodec[i].FilenameExtension); + fprintf(stderr, "\n mime: "); print_wchar (stderr, pCodec[i].MimeType); + } + fprintf(stderr, "\n"); + + free(pCodec); +} +#endif /* Unused */ + +static int gdip_initialized = 0; +static ULONG_PTR token; +static GdiplusStartupInput input; +static GdiplusStartupOutput output; + +bool +w32_gdiplus_startup (void) +{ + GpStatus status; + + if (gdip_initialized < 0) + return 0; + else if (gdip_initialized) + return 1; + else + { + input.GdiplusVersion = 1; + input.DebugEventCallback = NULL; + input.SuppressBackgroundThread = FALSE; + input.SuppressExternalCodecs = FALSE; + + status = GdiplusStartup(&token, &input, &output); + if (status == Ok) + { + gdip_initialized = 1; + return 1; + } + else + { + gdip_initialized = -1; + return 0; + } + } +} + +void +w32_gdiplus_shutdown (void) +{ + GdiplusShutdown(token); +} + + +static double +w32_frame_delay (GpBitmap *pBitmap, int frame) +{ + UINT size; + PropertyItem *propertyItem; + double delay = 0.0; + + /* Assume that the image has a property item of type PropertyItemEquipMake. + Get the size of that property item. */ + GdipGetPropertyItemSize(pBitmap, PropertyTagFrameDelay, &size); + + /* Allocate a buffer to receive the property item. */ + propertyItem = (PropertyItem*)malloc(size); + if (propertyItem != NULL) + { + /* Get the property item. */ + GdipGetPropertyItem(pBitmap, PropertyTagFrameDelay, size, propertyItem); + delay = ((double)propertyItem[frame].length) / 100; + if (delay == 0) + { + /* In GIF files, unfortunately, delay is only specified for the first + frame. */ + delay = ((double)propertyItem[0].length) / 100; + } + free(propertyItem); + } + return delay; +} + +static UINT +w32_select_active_frame(GpBitmap *pBitmap, int frame, int *nframes, double *delay) +{ + UINT count, frameCount; + GUID pDimensionIDs[1]; + GpStatus status = Ok; + + status = GdipImageGetFrameDimensionsCount(pBitmap, &count); + frameCount = *nframes = 0; + *delay = 0.0; + if (count) + { + status = GdipImageGetFrameDimensionsList (pBitmap, pDimensionIDs, 1); + status = GdipImageGetFrameCount (pBitmap, &pDimensionIDs[0], &frameCount); + if ((status == Ok) && (frameCount > 1)) + { + if (frame < 0 || frame >= frameCount) + { + status = GenericError; + } + else + { + status = GdipImageSelectActiveFrame (pBitmap, &pDimensionIDs[0], frame); + *delay = w32_frame_delay (pBitmap, frame); + *nframes = frameCount; + } + } + } + return status; +} + +static ARGB +w32_image_bg_color(struct frame *f, struct image *img) +{ + /* png_color_16 *image_bg; */ + Lisp_Object specified_bg + = Fplist_get (XCDR (img->spec), QCbackground); + Emacs_Color color; + + /* If the user specified a color, try to use it; if not, use the + current frame background, ignoring any default background + color set by the image. */ + if (STRINGP (specified_bg) + ? FRAME_TERMINAL (f)->defined_color_hook (f, + SSDATA (specified_bg), + &color, + false, + false) + : (FRAME_TERMINAL (f)->query_frame_background_color (f, &color), + true)) + /* The user specified `:background', use that. */ + { + DWORD red = (((DWORD) color.red) & 0xff00) << 8; + DWORD green = ((DWORD) color.green) & 0xff00; + DWORD blue = ((DWORD) color.blue) >> 8; + return red | green | blue; + } + return ((DWORD) 0xff000000); +} + +int +w32_load_image (struct frame *f, struct image *img, + Lisp_Object spec_file, Lisp_Object spec_data) +{ + Emacs_Pixmap pixmap; + GpStatus status = GenericError; + GpBitmap *pBitmap; + wchar_t filename[MAX_PATH]; + ARGB bg_color; + Lisp_Object lisp_index, metadata; + unsigned int index, nframes; + double delay; + + eassert (valid_image_p (img->spec)); + + /* This function only gets called if init_w32_gdiplus () was invoked. We have + a valid token and GDI+ is active. */ + if (STRINGP (spec_file)) + { + filename_to_utf16 (SSDATA (spec_file) , filename); + status = GdipCreateBitmapFromFile (filename, &pBitmap); + } + else if (STRINGP (spec_data)) + { + IStream *pStream = SHCreateMemStream ((BYTE *) SSDATA (spec_data), + SBYTES (spec_data)); + if (pStream != NULL) + { + status = GdipCreateBitmapFromStream (pStream, &pBitmap); + IStream_Release (pStream); + } + } + + metadata = Qnil; + if (status == Ok) + { + /* In multiframe pictures, select the first one */ + lisp_index = Fplist_get (XCDR (img->spec), QCindex); + index = FIXNUMP (lisp_index) ? XFIXNAT (lisp_index) : 0; + status = w32_select_active_frame (pBitmap, index, &nframes, &delay); + if ((status == Ok)) + { + if (nframes > 1) + metadata = Fcons (Qcount, Fcons (make_fixnum (nframes), metadata)); + if (delay) + metadata = Fcons (Qdelay, Fcons (make_float (delay), metadata)); + } + } + + if (status == Ok) + { + bg_color = w32_image_bg_color(f, img); + status = GdipCreateHBITMAPFromBitmap (pBitmap, &pixmap, bg_color); + if (status == Ok) + { + UINT width, height; + GdipGetImageWidth (pBitmap, &width); + GdipGetImageHeight (pBitmap, &height); + img->width = width; + img->height = height; + img->pixmap = pixmap; + img->lisp_data = metadata; + } + + GdipDisposeImage (pBitmap); + } + + if (status != Ok) + { + add_to_log ("Unable to load image %s", img->spec); + return 0; + } + return 1; +} diff --git a/src/w32term.h b/src/w32term.h index f8a8a727e8..365d752767 100644 --- a/src/w32term.h +++ b/src/w32term.h @@ -75,7 +75,10 @@ #define CP_DEFAULT 1004 extern void w32_regenerate_palette (struct frame *f); extern void w32_fullscreen_rect (HWND hwnd, int fsmode, RECT normal, RECT *rect); - +extern int w32_load_image (struct frame *f, struct image *img, + Lisp_Object spec_file, Lisp_Object spec_data); +extern bool w32_gdiplus_startup (void); +extern void w32_gdiplus_shutdown (void); /* For each display (currently only one on w32), we have a structure that records information about it. */