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

From: Cecilio Pardo <[email-masked]>
Date: Mon, 4 Nov 2024 20:19:11 +0100

This patch adds support for :transform-smoothing on windows using GDI+
(since XP).

It converts the HBITMAP to GpBitmap everytime its drawn. We could keep
it around for speed, but take more resources.  Since this may be used on
machines with small memmory, I think its better this way.
From 0fae31b5732f666eb12f2bf792208483d2319ff8 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+
interpolation.
---
 src/dispextern.h |  1 +
 src/image.c      | 10 +++++----
 src/w32gdiplus.h | 27 ++++++++++++++++++++++
 src/w32image.c   | 25 +++++++++++++++++++++
 src/w32term.c    | 58 +++++++++++++++++++++++++++++++++++++++---------
 5 files changed, 107 insertions(+), 14 deletions(-)

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..d3c3b9f590e 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,51 @@ 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);
+	      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,
+						    InterpolationModeHighQualityBicubic);
+			  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
+		{
+		  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