Subject: Re: bug#57166: 28.1; image :transform-smoothing dose not work

From: Cecilio Pardo <[email-masked]>
Date: Tue, 5 Nov 2024 00:11:52 +0100

New patch with NEWS entry and bilinear interpolation.

From bbe02f4218b6ba0ffd5a8356978a35bf920d1727 Mon Sep 17 00:00:00 2001
From: Cecilio Pardo <[email-masked]>
Date: Mon, 4 Nov 2024 18:58:40 +0100
Subject: [PATCH] Support :transform-smoothing on images (MS-Windows)
 (bug#57166)

* src/dispextern.h (struct image): Add field 'smoothing' for
NTGUI.
* src/image.c (image_set_transform): Assign the 'smoothing'
field of the image struct.
* src/w32gdiplus.h: Add references to more GDI+ functions.
* src/w32image.c (gdiplus_init): Add references to more GDI+
functions.
* src/w32term.c (w32_draw_image_foreground): If the image is
marked for smoothing and GDI+ is available, draw it with GDI+
bilinear interpolation.
* etc/NEWS: New entry for this change.
---
 etc/NEWS         |  3 +++
 src/dispextern.h |  1 +
 src/image.c      | 10 ++++----
 src/w32gdiplus.h | 27 ++++++++++++++++++++++
 src/w32image.c   | 25 ++++++++++++++++++++
 src/w32term.c    | 60 ++++++++++++++++++++++++++++++++++++++++--------
 6 files changed, 112 insertions(+), 14 deletions(-)

diff --git a/etc/NEWS b/etc/NEWS
index 41a76d1cd95..33da804d63e 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -830,6 +830,9 @@ color Emoji font installed on your system for the 'emoji' script.
 This command inserts clipboard data of different formats into the
 current buffer, if the major mode supports it.
 
+---
+** Images on MS-Windows now support the :transform-smoothing flag.
+Bilinear interpolation with GDI+ is used to smooth images.
 
 
 ----------------------------------------------------------------------
diff --git a/src/dispextern.h b/src/dispextern.h
index cc248a4472e..004eb82d87a 100644
--- a/src/dispextern.h
+++ b/src/dispextern.h
@@ -3172,6 +3172,7 @@ reset_mouse_highlight (Mouse_HLInfo *hlinfo)
 #endif /* HAVE_ANDROID */
 #ifdef HAVE_NTGUI
   XFORM xform;
+  bool smoothing;
 #endif
 #ifdef HAVE_HAIKU
   /* The affine transformation to apply to this image.  */
diff --git a/src/image.c b/src/image.c
index 34936977a40..db7f6acd171 100644
--- a/src/image.c
+++ b/src/image.c
@@ -3049,12 +3049,10 @@ image_set_transform (struct frame *f, struct image *img)
   flip = !NILP (image_spec_value (img->spec, QCflip, NULL));
 
 # if defined USE_CAIRO || defined HAVE_XRENDER || defined HAVE_NS || defined HAVE_HAIKU \
-  || defined HAVE_ANDROID
+  || defined HAVE_ANDROID || defined HAVE_NTGUI
   /* We want scale up operations to use a nearest neighbor filter to
      show real pixels instead of munging them, but scale down
-     operations to use a blended filter, to avoid aliasing and the like.
-
-     TODO: implement for Windows.  */
+     operations to use a blended filter, to avoid aliasing and the like.  */
   bool smoothing;
   Lisp_Object s = image_spec_value (img->spec, QCtransform_smoothing, NULL);
   if (NILP (s))
@@ -3067,6 +3065,10 @@ image_set_transform (struct frame *f, struct image *img)
   img->use_bilinear_filtering = smoothing;
 #endif
 
+#ifdef HAVE_NTGUI
+  img->smoothing = smoothing;
+#endif
+
   /* Perform scale transformation.  */
 
   matrix3x3 matrix
diff --git a/src/w32gdiplus.h b/src/w32gdiplus.h
index 9d05ae6c190..b438b1a64f8 100644
--- a/src/w32gdiplus.h
+++ b/src/w32gdiplus.h
@@ -2,6 +2,9 @@
 typedef GpStatus (WINGDIPAPI *GdiplusStartup_Proc)
   (ULONG_PTR *, GdiplusStartupInput *, GdiplusStartupOutput *);
 typedef VOID (WINGDIPAPI *GdiplusShutdown_Proc) (ULONG_PTR);
+typedef GpStatus (WINGDIPAPI *GdipCreateFromHDC_Proc)
+  (HDC hdc, GpGraphics **graphics);
+typedef GpStatus (WINGDIPAPI *GdipDeleteGraphics_Proc) (GpGraphics *graphics);
 typedef GpStatus (WINGDIPAPI *GdipGetPropertyItemSize_Proc)
   (GpImage *, PROPID, UINT *);
 typedef GpStatus (WINGDIPAPI *GdipGetPropertyItem_Proc)
@@ -20,6 +23,15 @@
   (IStream *, GpBitmap **);
 typedef GpStatus (WINGDIPAPI *GdipCreateBitmapFromScan0_Proc)
   (INT, INT, INT, PixelFormat, BYTE*, GpBitmap**);
+typedef GpStatus (WINGDIPAPI *GdipCreateBitmapFromHBITMAP_Proc)
+  (HBITMAP hbm, HPALETTE hpal, GpBitmap** bitmap);
+typedef GpStatus (WINGDIPAPI *GdipSetInterpolationMode_Proc)
+  (GpGraphics *graphics, InterpolationMode interpolationMode);
+typedef GpStatus (WINGDIPAPI *GdipDrawImageRectRectI_Proc)
+  (GpGraphics *graphics, GpImage *image, INT dstx, INT dsty, INT dstwidth,
+   INT dstheight, INT srcx, INT srcy, INT srcwidth, INT srcheight,
+   GpUnit srcUnit, GDIPCONST GpImageAttributes* imageAttributes,
+   DrawImageAbort callback, VOID * callbackData);
 typedef IStream * (WINAPI *SHCreateMemStream_Proc) (const BYTE *, UINT);
 typedef GpStatus (WINGDIPAPI *GdipCreateHBITMAPFromBitmap_Proc)
   (GpBitmap *, HBITMAP *, ARGB);
@@ -41,6 +53,8 @@
 
 extern GdiplusStartup_Proc fn_GdiplusStartup;
 extern GdiplusShutdown_Proc fn_GdiplusShutdown;
+extern GdipCreateFromHDC_Proc fn_GdipCreateFromHDC;
+extern GdipDeleteGraphics_Proc fn_GdipDeleteGraphics;
 extern GdipGetPropertyItemSize_Proc fn_GdipGetPropertyItemSize;
 extern GdipGetPropertyItem_Proc fn_GdipGetPropertyItem;
 extern GdipImageGetFrameDimensionsCount_Proc fn_GdipImageGetFrameDimensionsCount;
@@ -49,6 +63,9 @@
 extern GdipImageSelectActiveFrame_Proc fn_GdipImageSelectActiveFrame;
 extern GdipCreateBitmapFromFile_Proc fn_GdipCreateBitmapFromFile;
 extern GdipCreateBitmapFromStream_Proc fn_GdipCreateBitmapFromStream;
+extern GdipCreateBitmapFromHBITMAP_Proc fn_GdipCreateBitmapFromHBITMAP;
+extern GdipDrawImageRectRectI_Proc fn_GdipDrawImageRectRectI;
+extern GdipSetInterpolationMode_Proc fn_GdipSetInterpolationMode;
 extern GdipCreateBitmapFromScan0_Proc fn_GdipCreateBitmapFromScan0;
 extern SHCreateMemStream_Proc fn_SHCreateMemStream;
 extern GdipCreateHBITMAPFromBitmap_Proc fn_GdipCreateHBITMAPFromBitmap;
@@ -73,6 +90,11 @@
 # undef GdipCreateBitmapFromFile
 # undef GdipCreateBitmapFromStream
 # undef GdipCreateBitmapFromScan0
+# undef GdipCreateBitmapFromHBITMAP
+# undef GdipCreateFromHDC
+# undef GdipDrawImageRectRectI
+# undef GdipSetInterpolationMode
+# undef GdipDeleteGraphics
 # undef SHCreateMemStream
 # undef GdipCreateHBITMAPFromBitmap
 # undef GdipDisposeImage
@@ -96,6 +118,11 @@
 # define GdipCreateBitmapFromFile fn_GdipCreateBitmapFromFile
 # define GdipCreateBitmapFromStream fn_GdipCreateBitmapFromStream
 # define GdipCreateBitmapFromScan0 fn_GdipCreateBitmapFromScan0
+# define GdipCreateBitmapFromHBITMAP fn_GdipCreateBitmapFromHBITMAP
+# define GdipCreateFromHDC fn_GdipCreateFromHDC
+# define GdipDrawImageRectRectI fn_GdipDrawImageRectRectI
+# define GdipSetInterpolationMode fn_GdipSetInterpolationMode
+# define GdipDeleteGraphics  fn_GdipDeleteGraphics
 # define SHCreateMemStream fn_SHCreateMemStream
 # define GdipCreateHBITMAPFromBitmap fn_GdipCreateHBITMAPFromBitmap
 # define GdipDisposeImage fn_GdipDisposeImage
diff --git a/src/w32image.c b/src/w32image.c
index 44eed087528..da4d6843ba9 100644
--- a/src/w32image.c
+++ b/src/w32image.c
@@ -42,6 +42,8 @@ #define COBJMACROS
 #ifdef WINDOWSNT
 GdiplusStartup_Proc fn_GdiplusStartup;
 GdiplusShutdown_Proc fn_GdiplusShutdown;
+GdipCreateFromHDC_Proc fn_GdipCreateFromHDC;
+GdipDeleteGraphics_Proc fn_GdipDeleteGraphics;
 GdipGetPropertyItemSize_Proc fn_GdipGetPropertyItemSize;
 GdipGetPropertyItem_Proc fn_GdipGetPropertyItem;
 GdipImageGetFrameDimensionsCount_Proc fn_GdipImageGetFrameDimensionsCount;
@@ -53,6 +55,9 @@ #define COBJMACROS
 GdipCreateBitmapFromScan0_Proc fn_GdipCreateBitmapFromScan0;
 SHCreateMemStream_Proc fn_SHCreateMemStream;
 GdipCreateHBITMAPFromBitmap_Proc fn_GdipCreateHBITMAPFromBitmap;
+GdipCreateBitmapFromHBITMAP_Proc fn_GdipCreateBitmapFromHBITMAP;
+GdipDrawImageRectRectI_Proc fn_GdipDrawImageRectRectI;
+GdipSetInterpolationMode_Proc fn_GdipSetInterpolationMode;
 GdipDisposeImage_Proc fn_GdipDisposeImage;
 GdipGetImageHeight_Proc fn_GdipGetImageHeight;
 GdipGetImageWidth_Proc fn_GdipGetImageWidth;
@@ -80,6 +85,14 @@ gdiplus_init (void)
     get_proc_addr (gdiplus_lib, "GdiplusShutdown");
   if (!fn_GdiplusShutdown)
     return false;
+  fn_GdipCreateFromHDC = (GdipCreateFromHDC_Proc)
+    get_proc_addr (gdiplus_lib, "GdipCreateFromHDC");
+  if (!fn_GdipCreateFromHDC)
+    return false;
+  fn_GdipDeleteGraphics = (GdipDeleteGraphics_Proc)
+    get_proc_addr (gdiplus_lib, "GdipDeleteGraphics");
+  if (!fn_GdipDeleteGraphics)
+    return false;
   fn_GdipGetPropertyItemSize = (GdipGetPropertyItemSize_Proc)
     get_proc_addr (gdiplus_lib, "GdipGetPropertyItemSize");
   if (!fn_GdipGetPropertyItemSize)
@@ -120,6 +133,18 @@ gdiplus_init (void)
     get_proc_addr (gdiplus_lib, "GdipCreateHBITMAPFromBitmap");
   if (!fn_GdipCreateHBITMAPFromBitmap)
     return false;
+  fn_GdipCreateBitmapFromHBITMAP = (GdipCreateBitmapFromHBITMAP_Proc)
+    get_proc_addr (gdiplus_lib, "GdipCreateBitmapFromHBITMAP");
+  if (!fn_GdipCreateBitmapFromHBITMAP)
+    return false;
+  fn_GdipDrawImageRectRectI = (GdipDrawImageRectRectI_Proc)
+    get_proc_addr (gdiplus_lib, "GdipDrawImageRectRectI");
+  if (!fn_GdipDrawImageRectRectI)
+    return false;
+  fn_GdipSetInterpolationMode = (GdipSetInterpolationMode_Proc)
+    get_proc_addr (gdiplus_lib, "GdipSetInterpolationMode");
+  if (!fn_GdipSetInterpolationMode)
+    return false;
   fn_GdipDisposeImage = (GdipDisposeImage_Proc)
     get_proc_addr (gdiplus_lib, "GdipDisposeImage");
   if (!fn_GdipDisposeImage)
diff --git a/src/w32term.c b/src/w32term.c
index 88622700386..e18f39dd2a8 100644
--- a/src/w32term.c
+++ b/src/w32term.c
@@ -24,6 +24,9 @@ Copyright (C) 1989, 1993-2024 Free Software Foundation, Inc.
 #include "blockinput.h"
 #include "w32term.h"
 #include "w32common.h"	/* for OS version info */
+#include <wtypes.h>
+#include <gdiplus.h>
+#include "w32gdiplus.h"
 
 #include <ctype.h>
 #include <errno.h>
@@ -2106,16 +2109,53 @@ w32_draw_image_foreground (struct glyph_string *s)
 		    compat_hdc, s->slice.x, s->slice.y, SRCCOPY);
 	  else
 	    {
-	      int pmode = 0;
-	      /* Windows 9X doesn't support HALFTONE.  */
-	      if (os_subtype == OS_SUBTYPE_NT
-		  && (pmode = SetStretchBltMode (s->hdc, HALFTONE)) != 0)
-		SetBrushOrgEx (s->hdc, 0, 0, NULL);
-	      StretchBlt (s->hdc, x, y, s->slice.width, s->slice.height,
-			  compat_hdc, orig_slice_x, orig_slice_y,
-			  orig_slice_width, orig_slice_height, SRCCOPY);
-	      if (pmode)
-		SetStretchBltMode (s->hdc, pmode);
+#ifdef HAVE_NATIVE_IMAGE_API
+	      if (s->img->smoothing && w32_gdiplus_startup ())
+		{
+		  GpGraphics *graphics;
+		  if (GdipCreateFromHDC (s->hdc, &graphics) == Ok)
+		    {
+		      GpBitmap *gp_bitmap;
+		      /* Can't create a GpBitmap from a HBITMAP that was
+			 ever selected into a DC, so we need to copy.  */
+		      HBITMAP copy
+			= CopyImage (GetCurrentObject (compat_hdc, OBJ_BITMAP),
+				     IMAGE_BITMAP, 0, 0, 0);
+		      if (GdipCreateBitmapFromHBITMAP (copy, NULL,
+						       &gp_bitmap) == Ok)
+			{
+			  GdipSetInterpolationMode (graphics,
+						    InterpolationModeHighQualityBilinear);
+			  GdipDrawImageRectRectI (graphics,
+						  gp_bitmap, x, y,
+						  s->slice.width,
+						  s->slice.height,
+						  orig_slice_x,
+						  orig_slice_y,
+						  orig_slice_width,
+						  orig_slice_height,
+						  UnitPixel,
+						  NULL, NULL, NULL);
+			  GdipDisposeImage (gp_bitmap);
+			}
+		      DeleteObject (copy);
+		      GdipDeleteGraphics (graphics);
+		    }
+		}
+	      else
+#endif
+		{
+		  int pmode = 0;
+		  /* Windows 9X doesn't support HALFTONE.  */
+		  if (os_subtype == OS_SUBTYPE_NT
+		      && (pmode = SetStretchBltMode (s->hdc, HALFTONE)) != 0)
+		    SetBrushOrgEx (s->hdc, 0, 0, NULL);
+		  StretchBlt (s->hdc, x, y, s->slice.width, s->slice.height,
+			      compat_hdc, orig_slice_x, orig_slice_y,
+			      orig_slice_width, orig_slice_height, SRCCOPY);
+		  if (pmode)
+		    SetStretchBltMode (s->hdc, pmode);
+		}
 	    }
 
 	  /* When the image has a mask, we can expect that at
-- 
2.35.1.windows.2