From 6d42449788e4c05a76cc7c9c81a7c8b2a40d099e Mon Sep 17 00:00:00 2001
From: Andrew Murray <3112309+radarhere@users.noreply.github.com>
Date: Sun, 30 Mar 2025 03:25:13 +1100
Subject: [PATCH] Allow loading of EMF images at a given DPI (#8536)

Co-authored-by: Andrew Murray <radarhere@users.noreply.github.com>
---
 Tests/images/drawing_emf_ref_72_144.png | Bin 0 -> 984 bytes
 Tests/test_file_wmf.py                  |  14 ++++++++++++++
 src/PIL/WmfImagePlugin.py               |  24 ++++++++++++++----------
 3 files changed, 28 insertions(+), 10 deletions(-)
 create mode 100644 Tests/images/drawing_emf_ref_72_144.png

diff --git a/Tests/images/drawing_emf_ref_72_144.png b/Tests/images/drawing_emf_ref_72_144.png
new file mode 100644
index 0000000000000000000000000000000000000000..000377b6c7b1e05688fc17d5da09d163a18c5b4b
GIT binary patch
literal 984
zcmV;}11J26P)<h;3K|Lk000e1NJLTq002?|005*20ssI2i-qpU000A@Nkl<Zc%1E>
z+m?eM3`K+g|If}tT8An^fFxX!{W??4_QVUcOTu}cFoF=ms9ghq-o8T!`G3$n3L4r;
z;S(Tv7<A-iwPWGpZsMaLEia{w!p)n;Lm(%ANNhBOaCSHY0Dio9IcSDC?aq)B8w3D>
z*g4bg5BZ5u>}=ZTECjnbkG7~Y!fVc;t>BC>n)hm}IU`)=UE0dd2#a~U_7G>J-@H+K
zpfl2G-l#p+8R@B^MO*Hfv6kjaTC`_~8fk9zYVCQVM%pr{(;j{$OVW@;o%V#z&{S20
z_H6APQ(GHVd(QU0sJ*sPwP$ulswyOD&)nWI^g2n}^GA))>lB$n90)P+vi2$+jt~Pc
zYp>GbRTQ+>iW;HRT+m)IYD&#H?G>X&ik0WISBx4dR=(Q}jL56@x*d+>>wnc=x5JTq
z{odpet9ST^cZ;<4>K$IhoBYcr9ge)XB(%5haPTI##a(-=6B|hx-L);8*x*fWiy!R`
zPi*Aj^`mX%#D-XV+o%EHVdv+zC0yGQuDvz4pF4cCC;yEGJ66)Z;o6pP?cIql<_Flj
zjDxAPV_e%3u5Ag|wuEb2!nG~o+Lo}TeT_YBX<r-$2issb?a>8{;W*E8=kK*&u$uPh
z0#pg#mvPzBzHn_zxV9x++Y+vA3D>rSYg^&~0E0trLup^5PB5h%<Jy*RZA*N#x4(zQ
zmvOM3wl!Sa66UoBE>oqCm9%f69=AWL)}qDpk;FvW&-2%W_7m4ewmZF(V~zdOPTrXJ
z*G{sz_S<MWrM=PV{r2d&dD?HdcFY!io|z`wIql4t^8FpgGNnwy`#X#m@AG_Y+ctSV
zw(Q&Jxl2)_Sb6qs^vm{nvGUSN6MUtpL8-a4(!_#+Q)=G!l9q?Cc#c99d=RT8ES|Ge
zpA)O4tq0auiW>AfY3qS45;(n1@>+kbrKnL=A$hI8>3{A})shuAu$f!EHj>I!TPwG&
zL#U6Wa@E!;18=CRere*`4+zs%Pqp@J*S59>Y+8SNnpSTPpm8WNL*NZpvWrIT;jP}|
z3_SzSf##jg&^g{7V&3lz{nHG}<}A*@GP|N?&gBeTlS(?~j5kiUxinGpz<tZ)oI(^b
z$KK|BMq-{hMRP9sz<NIaY+2|@6W`C(vuPn=L~5QK+Y)1^hkyOQs58i={lA0MQbgA-
zlSf2hs3WfZ*$r(SCbhAhlJ<SC-=i>U3_C`9IR4e2CH??qha)B=@Nu*N0000<MNUMn
GLSTaTnENdN

literal 0
HcmV?d00001

diff --git a/Tests/test_file_wmf.py b/Tests/test_file_wmf.py
index 928493a0f..a752f8013 100644
--- a/Tests/test_file_wmf.py
+++ b/Tests/test_file_wmf.py
@@ -97,6 +97,20 @@ def test_load_set_dpi() -> None:
 
             assert_image_similar_tofile(im, "Tests/images/drawing_wmf_ref_144.png", 2.1)
 
+    with Image.open("Tests/images/drawing.emf") as im:
+        assert im.size == (1625, 1625)
+
+        if not hasattr(Image.core, "drawwmf"):
+            return
+        im.load(im.info["dpi"])
+        assert im.size == (1625, 1625)
+
+    with Image.open("Tests/images/drawing.emf") as im:
+        im.load((72, 144))
+        assert im.size == (82, 164)
+
+        assert_image_equal_tofile(im, "Tests/images/drawing_emf_ref_72_144.png")
+
 
 @pytest.mark.parametrize("ext", (".wmf", ".emf"))
 def test_save(ext: str, tmp_path: Path) -> None:
diff --git a/src/PIL/WmfImagePlugin.py b/src/PIL/WmfImagePlugin.py
index 04abd52f0..f709d026b 100644
--- a/src/PIL/WmfImagePlugin.py
+++ b/src/PIL/WmfImagePlugin.py
@@ -80,8 +80,6 @@ class WmfStubImageFile(ImageFile.StubImageFile):
     format_description = "Windows Metafile"
 
     def _open(self) -> None:
-        self._inch = None
-
         # check placable header
         s = self.fp.read(80)
 
@@ -89,10 +87,11 @@ class WmfStubImageFile(ImageFile.StubImageFile):
             # placeable windows metafile
 
             # get units per inch
-            self._inch = word(s, 14)
-            if self._inch == 0:
+            inch = word(s, 14)
+            if inch == 0:
                 msg = "Invalid inch"
                 raise ValueError(msg)
+            self._inch: tuple[float, float] = inch, inch
 
             # get bounding box
             x0 = short(s, 6)
@@ -103,8 +102,8 @@ class WmfStubImageFile(ImageFile.StubImageFile):
             # normalize size to 72 dots per inch
             self.info["dpi"] = 72
             size = (
-                (x1 - x0) * self.info["dpi"] // self._inch,
-                (y1 - y0) * self.info["dpi"] // self._inch,
+                (x1 - x0) * self.info["dpi"] // inch,
+                (y1 - y0) * self.info["dpi"] // inch,
             )
 
             self.info["wmf_bbox"] = x0, y0, x1, y1
@@ -138,6 +137,7 @@ class WmfStubImageFile(ImageFile.StubImageFile):
                 self.info["dpi"] = xdpi
             else:
                 self.info["dpi"] = xdpi, ydpi
+            self._inch = xdpi, ydpi
 
         else:
             msg = "Unsupported file format"
@@ -153,13 +153,17 @@ class WmfStubImageFile(ImageFile.StubImageFile):
     def _load(self) -> ImageFile.StubHandler | None:
         return _handler
 
-    def load(self, dpi: int | None = None) -> Image.core.PixelAccess | None:
-        if dpi is not None and self._inch is not None:
+    def load(
+        self, dpi: float | tuple[float, float] | None = None
+    ) -> Image.core.PixelAccess | None:
+        if dpi is not None:
             self.info["dpi"] = dpi
             x0, y0, x1, y1 = self.info["wmf_bbox"]
+            if not isinstance(dpi, tuple):
+                dpi = dpi, dpi
             self._size = (
-                (x1 - x0) * self.info["dpi"] // self._inch,
-                (y1 - y0) * self.info["dpi"] // self._inch,
+                int((x1 - x0) * dpi[0] / self._inch[0]),
+                int((y1 - y0) * dpi[1] / self._inch[1]),
             )
         return super().load()