root/src/w32image.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. gdiplus_init
  2. gdiplus_startup
  3. w32_gdiplus_shutdown
  4. w32_can_use_native_image_api
  5. decode_delay
  6. w32_frame_delay
  7. w32_select_active_frame
  8. w32_image_bg_color
  9. w32_load_image

     1 /* Implementation of MS-Windows native image API via the GDI+ library.
     2 
     3 Copyright (C) 2020-2023 Free Software Foundation, Inc.
     4 
     5 This file is part of GNU Emacs.
     6 
     7 GNU Emacs is free software: you can redistribute it and/or modify
     8 it under the terms of the GNU General Public License as published by
     9 the Free Software Foundation, either version 3 of the License, or (at
    10 your option) any later version.
    11 
    12 GNU Emacs is distributed in the hope that it will be useful,
    13 but WITHOUT ANY WARRANTY; without even the implied warranty of
    14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    15 GNU General Public License for more details.
    16 
    17 You should have received a copy of the GNU General Public License
    18 along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
    19 
    20 /* Written by Juan Jose Garcia-Ripoll <juanjose.garciaripoll@gmail.com>.  */
    21 
    22 #include <config.h>
    23 #include "lisp.h"
    24 #include "dispextern.h"
    25 #define COBJMACROS
    26 #ifdef MINGW_W64
    27 /* FIXME: Do we need to include objidl.h?  */
    28 #include <objidl.h>
    29 #endif
    30 #include <wtypes.h>
    31 #include <gdiplus.h>
    32 #include <shlwapi.h>
    33 #include "w32common.h"
    34 #include "w32term.h"
    35 #ifdef WINDOWSNT
    36 #include "w32.h"        /* for map_w32_filename, filename_to_utf16 */
    37 #endif
    38 #include "frame.h"
    39 #include "coding.h"
    40 
    41 #ifdef WINDOWSNT
    42 
    43 typedef GpStatus (WINGDIPAPI *GdiplusStartup_Proc)
    44   (ULONG_PTR *, GdiplusStartupInput *, GdiplusStartupOutput *);
    45 typedef VOID (WINGDIPAPI *GdiplusShutdown_Proc) (ULONG_PTR);
    46 typedef GpStatus (WINGDIPAPI *GdipGetPropertyItemSize_Proc)
    47   (GpImage *, PROPID, UINT *);
    48 typedef GpStatus (WINGDIPAPI *GdipGetPropertyItem_Proc)
    49   (GpImage *, PROPID, UINT, PropertyItem *);
    50 typedef GpStatus (WINGDIPAPI *GdipImageGetFrameDimensionsCount_Proc)
    51   (GpImage *, UINT *);
    52 typedef GpStatus (WINGDIPAPI *GdipImageGetFrameDimensionsList_Proc)
    53   (GpImage *, GUID *, UINT);
    54 typedef GpStatus (WINGDIPAPI *GdipImageGetFrameCount_Proc)
    55   (GpImage *, GDIPCONST GUID *, UINT *);
    56 typedef GpStatus (WINGDIPAPI *GdipImageSelectActiveFrame_Proc)
    57   (GpImage*, GDIPCONST GUID *, UINT);
    58 typedef GpStatus (WINGDIPAPI *GdipCreateBitmapFromFile_Proc)
    59   (WCHAR *, GpBitmap **);
    60 typedef GpStatus (WINGDIPAPI *GdipCreateBitmapFromStream_Proc)
    61   (IStream *, GpBitmap **);
    62 typedef IStream * (WINAPI *SHCreateMemStream_Proc) (const BYTE *, UINT);
    63 typedef GpStatus (WINGDIPAPI *GdipCreateHBITMAPFromBitmap_Proc)
    64   (GpBitmap *, HBITMAP *, ARGB);
    65 typedef GpStatus (WINGDIPAPI *GdipDisposeImage_Proc) (GpImage *);
    66 typedef GpStatus (WINGDIPAPI *GdipGetImageHeight_Proc) (GpImage *, UINT *);
    67 typedef GpStatus (WINGDIPAPI *GdipGetImageWidth_Proc) (GpImage *, UINT *);
    68 
    69 GdiplusStartup_Proc fn_GdiplusStartup;
    70 GdiplusShutdown_Proc fn_GdiplusShutdown;
    71 GdipGetPropertyItemSize_Proc fn_GdipGetPropertyItemSize;
    72 GdipGetPropertyItem_Proc fn_GdipGetPropertyItem;
    73 GdipImageGetFrameDimensionsCount_Proc fn_GdipImageGetFrameDimensionsCount;
    74 GdipImageGetFrameDimensionsList_Proc fn_GdipImageGetFrameDimensionsList;
    75 GdipImageGetFrameCount_Proc fn_GdipImageGetFrameCount;
    76 GdipImageSelectActiveFrame_Proc fn_GdipImageSelectActiveFrame;
    77 GdipCreateBitmapFromFile_Proc fn_GdipCreateBitmapFromFile;
    78 GdipCreateBitmapFromStream_Proc fn_GdipCreateBitmapFromStream;
    79 SHCreateMemStream_Proc fn_SHCreateMemStream;
    80 GdipCreateHBITMAPFromBitmap_Proc fn_GdipCreateHBITMAPFromBitmap;
    81 GdipDisposeImage_Proc fn_GdipDisposeImage;
    82 GdipGetImageHeight_Proc fn_GdipGetImageHeight;
    83 GdipGetImageWidth_Proc fn_GdipGetImageWidth;
    84 
    85 static bool
    86 gdiplus_init (void)
    87 {
    88   HANDLE gdiplus_lib, shlwapi_lib;
    89 
    90   if (!((gdiplus_lib = w32_delayed_load (Qgdiplus))
    91         && (shlwapi_lib = w32_delayed_load (Qshlwapi))))
    92     return false;
    93 
    94   fn_GdiplusStartup = (GdiplusStartup_Proc)
    95     get_proc_addr (gdiplus_lib, "GdiplusStartup");
    96   if (!fn_GdiplusStartup)
    97     return false;
    98   fn_GdiplusShutdown = (GdiplusShutdown_Proc)
    99     get_proc_addr (gdiplus_lib, "GdiplusShutdown");
   100   if (!fn_GdiplusShutdown)
   101     return false;
   102   fn_GdipGetPropertyItemSize = (GdipGetPropertyItemSize_Proc)
   103     get_proc_addr (gdiplus_lib, "GdipGetPropertyItemSize");
   104   if (!fn_GdipGetPropertyItemSize)
   105     return false;
   106   fn_GdipGetPropertyItem = (GdipGetPropertyItem_Proc)
   107     get_proc_addr (gdiplus_lib, "GdipGetPropertyItem");
   108   if (!fn_GdipGetPropertyItem)
   109     return false;
   110   fn_GdipImageGetFrameDimensionsCount = (GdipImageGetFrameDimensionsCount_Proc)
   111     get_proc_addr (gdiplus_lib, "GdipImageGetFrameDimensionsCount");
   112   if (!fn_GdipImageGetFrameDimensionsCount)
   113     return false;
   114   fn_GdipImageGetFrameDimensionsList = (GdipImageGetFrameDimensionsList_Proc)
   115     get_proc_addr (gdiplus_lib, "GdipImageGetFrameDimensionsList");
   116   if (!fn_GdipImageGetFrameDimensionsList)
   117     return false;
   118   fn_GdipImageGetFrameCount = (GdipImageGetFrameCount_Proc)
   119     get_proc_addr (gdiplus_lib, "GdipImageGetFrameCount");
   120   if (!fn_GdipImageGetFrameCount)
   121     return false;
   122   fn_GdipImageSelectActiveFrame = (GdipImageSelectActiveFrame_Proc)
   123     get_proc_addr (gdiplus_lib, "GdipImageSelectActiveFrame");
   124   if (!fn_GdipImageSelectActiveFrame)
   125     return false;
   126   fn_GdipCreateBitmapFromFile = (GdipCreateBitmapFromFile_Proc)
   127     get_proc_addr (gdiplus_lib, "GdipCreateBitmapFromFile");
   128   if (!fn_GdipCreateBitmapFromFile)
   129     return false;
   130   fn_GdipCreateBitmapFromStream = (GdipCreateBitmapFromStream_Proc)
   131     get_proc_addr (gdiplus_lib, "GdipCreateBitmapFromStream");
   132   if (!fn_GdipCreateBitmapFromStream)
   133     return false;
   134   fn_GdipCreateHBITMAPFromBitmap = (GdipCreateHBITMAPFromBitmap_Proc)
   135     get_proc_addr (gdiplus_lib, "GdipCreateHBITMAPFromBitmap");
   136   if (!fn_GdipCreateHBITMAPFromBitmap)
   137     return false;
   138   fn_GdipDisposeImage = (GdipDisposeImage_Proc)
   139     get_proc_addr (gdiplus_lib, "GdipDisposeImage");
   140   if (!fn_GdipDisposeImage)
   141     return false;
   142   fn_GdipGetImageHeight = (GdipGetImageHeight_Proc)
   143     get_proc_addr (gdiplus_lib, "GdipGetImageHeight");
   144   if (!fn_GdipGetImageHeight)
   145     return false;
   146   fn_GdipGetImageWidth = (GdipGetImageWidth_Proc)
   147     get_proc_addr (gdiplus_lib, "GdipGetImageWidth");
   148   if (!fn_GdipGetImageWidth)
   149     return false;
   150   /* LOAD_DLL_FN (shlwapi_lib, SHCreateMemStream); */
   151 
   152   /* The following terrible kludge is required to use native image API
   153      on Windows before Vista, because SHCreateMemStream was not
   154      exported by name in those versions, only by ordinal number.  */
   155   fn_SHCreateMemStream = (SHCreateMemStream_Proc)
   156     get_proc_addr (shlwapi_lib, "SHCreateMemStream");
   157   if (!fn_SHCreateMemStream)
   158     {
   159       fn_SHCreateMemStream = (SHCreateMemStream_Proc)
   160         get_proc_addr (shlwapi_lib, MAKEINTRESOURCEA (12));
   161       if (!fn_SHCreateMemStream)
   162         return false;
   163     }
   164 
   165   return true;
   166 }
   167 
   168 # undef GdiplusStartup
   169 # undef GdiplusShutdown
   170 # undef GdipGetPropertyItemSize
   171 # undef GdipGetPropertyItem
   172 # undef GdipImageGetFrameDimensionsCount
   173 # undef GdipImageGetFrameDimensionsList
   174 # undef GdipImageGetFrameCount
   175 # undef GdipImageSelectActiveFrame
   176 # undef GdipCreateBitmapFromFile
   177 # undef GdipCreateBitmapFromStream
   178 # undef SHCreateMemStream
   179 # undef GdipCreateHBITMAPFromBitmap
   180 # undef GdipDisposeImage
   181 # undef GdipGetImageHeight
   182 # undef GdipGetImageWidth
   183 
   184 # define GdiplusStartup fn_GdiplusStartup
   185 # define GdiplusShutdown fn_GdiplusShutdown
   186 # define GdipGetPropertyItemSize fn_GdipGetPropertyItemSize
   187 # define GdipGetPropertyItem fn_GdipGetPropertyItem
   188 # define GdipImageGetFrameDimensionsCount fn_GdipImageGetFrameDimensionsCount
   189 # define GdipImageGetFrameDimensionsList fn_GdipImageGetFrameDimensionsList
   190 # define GdipImageGetFrameCount fn_GdipImageGetFrameCount
   191 # define GdipImageSelectActiveFrame fn_GdipImageSelectActiveFrame
   192 # define GdipCreateBitmapFromFile fn_GdipCreateBitmapFromFile
   193 # define GdipCreateBitmapFromStream fn_GdipCreateBitmapFromStream
   194 # define SHCreateMemStream fn_SHCreateMemStream
   195 # define GdipCreateHBITMAPFromBitmap fn_GdipCreateHBITMAPFromBitmap
   196 # define GdipDisposeImage fn_GdipDisposeImage
   197 # define GdipGetImageHeight fn_GdipGetImageHeight
   198 # define GdipGetImageWidth fn_GdipGetImageWidth
   199 
   200 #endif  /* WINDOWSNT */
   201 
   202 static int gdip_initialized;
   203 static bool gdiplus_started;
   204 static ULONG_PTR token;
   205 static GdiplusStartupInput input;
   206 static GdiplusStartupOutput output;
   207 
   208 
   209 /* Initialize GDI+, return true if successful.  */
   210 static bool
   211 gdiplus_startup (void)
   212 {
   213   GpStatus status;
   214 
   215   if (gdiplus_started)
   216     return true;
   217 #ifdef WINDOWSNT
   218   if (!gdip_initialized)
   219     gdip_initialized = gdiplus_init () ? 1 : -1;
   220 #else
   221   gdip_initialized = 1;
   222 #endif
   223   if (gdip_initialized > 0)
   224     {
   225       input.GdiplusVersion = 1;
   226       input.DebugEventCallback = NULL;
   227       input.SuppressBackgroundThread = FALSE;
   228       input.SuppressExternalCodecs = FALSE;
   229 
   230       status = GdiplusStartup (&token, &input, &output);
   231       if (status == Ok)
   232         gdiplus_started = true;
   233       return (status == Ok);
   234     }
   235   return false;
   236 }
   237 
   238 /* This is called from term_ntproc.  */
   239 void
   240 w32_gdiplus_shutdown (void)
   241 {
   242   if (gdiplus_started)
   243     GdiplusShutdown (token);
   244   gdiplus_started = false;
   245 }
   246 
   247 bool
   248 w32_can_use_native_image_api (Lisp_Object type)
   249 {
   250   if (!w32_use_native_image_api)
   251     return false;
   252   if (!(EQ (type, Qjpeg)
   253         || EQ (type, Qpng)
   254         || EQ (type, Qgif)
   255         || EQ (type, Qtiff)
   256         || EQ (type, Qbmp)
   257         || EQ (type, Qnative_image)))
   258     {
   259       /* GDI+ can also display Exif, ICON, WMF, and EMF images.
   260          But we don't yet support these in image.c.  */
   261       return false;
   262     }
   263   return gdiplus_startup ();
   264 }
   265 
   266 enum PropertyItem_type {
   267   PI_BYTE = 1,
   268   PI_ASCIIZ = 2,
   269   PI_USHORT = 3,
   270   PI_ULONG = 4,
   271   PI_ULONG_PAIR = 5,
   272   PI_BYTE_ANY = 6,
   273   PI_LONG = 7,
   274   PI_LONG_PAIR = 10
   275 };
   276 
   277 static double
   278 decode_delay (PropertyItem *propertyItem, int frame)
   279 {
   280   enum PropertyItem_type type = propertyItem[0].type;
   281   unsigned long udelay;
   282   double retval;
   283 
   284   switch (type)
   285     {
   286     case PI_BYTE:
   287     case PI_BYTE_ANY:
   288       udelay = ((unsigned char *)propertyItem[0].value)[frame];
   289       retval = udelay;
   290       break;
   291     case PI_USHORT:
   292       udelay = ((unsigned short *)propertyItem[0].value)[frame];
   293       retval = udelay;
   294       break;
   295     case PI_ULONG:
   296     case PI_LONG:       /* delay should always be positive */
   297       udelay = ((unsigned long *)propertyItem[0].value)[frame];
   298       retval = udelay;
   299       break;
   300     default:
   301       /* This negative value will cause the caller to disregard the
   302          delay if we cannot determine it reliably.  */
   303       add_to_log ("Invalid or unknown propertyItem type in w32image.c");
   304       retval = -1.0;
   305     }
   306 
   307   return retval;
   308 }
   309 
   310 static double
   311 w32_frame_delay (GpBitmap *pBitmap, int frame)
   312 {
   313   UINT size;
   314   PropertyItem *propertyItem;
   315   double delay = -1.0;
   316 
   317   /* Assume that the image has a property item of type PropertyItemEquipMake.
   318      Get the size of that property item.  This can fail for multi-frame TIFF
   319      images.  */
   320   GpStatus status = GdipGetPropertyItemSize (pBitmap, PropertyTagFrameDelay,
   321                                              &size);
   322 
   323   if (status == Ok)
   324     {
   325       /* Allocate a buffer to receive the property item.  */
   326       propertyItem = malloc (size);
   327       if (propertyItem != NULL)
   328         {
   329           /* Get the property item.  */
   330           GdipGetPropertyItem (pBitmap, PropertyTagFrameDelay, size,
   331                                propertyItem);
   332           delay = decode_delay (propertyItem, frame);
   333           if (delay <= 0)
   334             {
   335               /* In GIF files, unfortunately, delay is only specified
   336                  for the first frame.  */
   337               delay = decode_delay (propertyItem, 0);
   338             }
   339           delay /= 100.0;
   340           free (propertyItem);
   341         }
   342     }
   343   return delay;
   344 }
   345 
   346 static GpStatus
   347 w32_select_active_frame (GpBitmap *pBitmap, int frame, int *nframes,
   348                          double *delay)
   349 {
   350   UINT count, frameCount;
   351   GUID pDimensionIDs[1];
   352   GpStatus status = Ok;
   353 
   354   status = GdipImageGetFrameDimensionsCount (pBitmap, &count);
   355   frameCount = *nframes = 0;
   356   *delay = -1.0;
   357   if (count)
   358     {
   359       /* The following call will fill pDimensionIDs[0] with the
   360          FrameDimensionTime GUID for GIF images, and
   361          FrameDimensionPage GUID for other image types.  Multi-page
   362          GIF and TIFF images expect these values in the
   363          GdipImageSelectActiveFrame call below.  */
   364       status = GdipImageGetFrameDimensionsList (pBitmap, pDimensionIDs, 1);
   365       status = GdipImageGetFrameCount (pBitmap, &pDimensionIDs[0], &frameCount);
   366       if (status == Ok && frameCount > 1)
   367         {
   368           if (frame < 0 || frame >= frameCount)
   369             status = GenericError;
   370           else
   371             {
   372               status = GdipImageSelectActiveFrame (pBitmap, &pDimensionIDs[0],
   373                                                    frame);
   374               *delay = w32_frame_delay (pBitmap, frame);
   375               *nframes = frameCount;
   376             }
   377         }
   378     }
   379   return status;
   380 }
   381 
   382 static ARGB
   383 w32_image_bg_color (struct frame *f, struct image *img)
   384 {
   385   Lisp_Object specified_bg = plist_get (XCDR (img->spec), QCbackground);
   386   Emacs_Color color;
   387 
   388   /* If the user specified a color, try to use it; if not, use the
   389      current frame background, ignoring any default background
   390      color set by the image.  */
   391   if (STRINGP (specified_bg)
   392       ? w32_defined_color (f, SSDATA (specified_bg), &color, false, false)
   393       : (w32_query_frame_background_color (f, &color), true))
   394     /* The user specified ':background', use that.  */
   395     {
   396       DWORD red = (((DWORD) color.red) & 0xff00) << 8;
   397       DWORD green = ((DWORD) color.green) & 0xff00;
   398       DWORD blue = ((DWORD) color.blue) >> 8;
   399       return (ARGB) (red | green | blue);
   400     }
   401   return (ARGB) 0xff000000;
   402 }
   403 
   404 int
   405 w32_load_image (struct frame *f, struct image *img,
   406                 Lisp_Object spec_file, Lisp_Object spec_data)
   407 {
   408   GpStatus status = GenericError;
   409   GpBitmap *pBitmap;
   410   Lisp_Object metadata;
   411 
   412   eassert (valid_image_p (img->spec));
   413 
   414   /* This function only gets called if w32_gdiplus_startup was invoked
   415      and succeeded.  We have a valid token and GDI+ is active.  */
   416   if (STRINGP (spec_file))
   417     {
   418       const char *fn = map_w32_filename (SSDATA (spec_file), NULL);
   419       wchar_t filename_w[MAX_PATH];
   420       filename_to_utf16 (fn, filename_w);
   421       status = GdipCreateBitmapFromFile (filename_w, &pBitmap);
   422     }
   423   else if (STRINGP (spec_data))
   424     {
   425       IStream *pStream = SHCreateMemStream ((BYTE *) SDATA (spec_data),
   426                                             SBYTES (spec_data));
   427       if (pStream != NULL)
   428         {
   429           status = GdipCreateBitmapFromStream (pStream, &pBitmap);
   430           IStream_Release (pStream);
   431         }
   432     }
   433 
   434   metadata = Qnil;
   435   if (status == Ok)
   436     {
   437       /* In multiframe pictures, select the first frame.  */
   438       Lisp_Object lisp_index = plist_get (XCDR (img->spec), QCindex);
   439       int index = FIXNATP (lisp_index) ? XFIXNAT (lisp_index) : 0;
   440       int nframes;
   441       double delay;
   442       status = w32_select_active_frame (pBitmap, index, &nframes, &delay);
   443       if (status == Ok)
   444         {
   445           if (nframes > 1)
   446             metadata = Fcons (Qcount, Fcons (make_fixnum (nframes), metadata));
   447           if (delay >= 0)
   448             metadata = Fcons (Qdelay, Fcons (make_float (delay), metadata));
   449         }
   450     }
   451 
   452   if (status == Ok)
   453     {
   454       ARGB bg_color = w32_image_bg_color (f, img);
   455       Emacs_Pixmap pixmap;
   456 
   457       status = GdipCreateHBITMAPFromBitmap (pBitmap, &pixmap, bg_color);
   458       if (status == Ok)
   459         {
   460           UINT width, height;
   461           GdipGetImageWidth (pBitmap, &width);
   462           GdipGetImageHeight (pBitmap, &height);
   463           img->width = width;
   464           img->height = height;
   465           img->pixmap = pixmap;
   466           img->lisp_data = metadata;
   467         }
   468 
   469       GdipDisposeImage (pBitmap);
   470     }
   471 
   472   if (status != Ok)
   473     {
   474       add_to_log ("Unable to load image %s", img->spec);
   475       return 0;
   476     }
   477   return 1;
   478 }

/* [<][>][^][v][top][bottom][index][help] */