diff --git a/CHANGES.rst b/CHANGES.rst index 8693df466..3a2a45441 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,12 @@ Changelog (Pillow) 7.0.0 (unreleased) ------------------ +- Allow loading of WMF images at a given DPI #4311 + [radarhere] + +- Added reduce operation #4251 + [homm] + - Raise ValueError for io.StringIO in Image.open #4302 [radarhere, hugovk] diff --git a/Tests/images/drawing_wmf_ref_144.png b/Tests/images/drawing_wmf_ref_144.png new file mode 100644 index 000000000..20ed9ce59 Binary files /dev/null and b/Tests/images/drawing_wmf_ref_144.png differ diff --git a/Tests/test_file_wmf.py b/Tests/test_file_wmf.py index 01a802bfb..0aa36c33c 100644 --- a/Tests/test_file_wmf.py +++ b/Tests/test_file_wmf.py @@ -53,6 +53,17 @@ class TestFileWmf(PillowTestCase): with Image.open("Tests/images/drawing_roundDown.emf") as im: self.assertEqual(im.info["dpi"], 1426) + def test_load_set_dpi(self): + with Image.open("Tests/images/drawing.wmf") as im: + self.assertEquals(im.size, (82, 82)) + + if hasattr(Image.core, "drawwmf"): + im.load(144) + self.assertEquals(im.size, (164, 164)) + + with Image.open("Tests/images/drawing_wmf_ref_144.png") as expected: + self.assert_image_similar(im, expected, 2.0) + def test_save(self): im = hopper() diff --git a/Tests/test_main.py b/Tests/test_main.py index 4f52149b6..d0f1410e7 100644 --- a/Tests/test_main.py +++ b/Tests/test_main.py @@ -1,17 +1,10 @@ import os import subprocess import sys -import unittest from unittest import TestCase -from .helper import is_pypy, is_win32, on_github_actions - class TestMain(TestCase): - @unittest.skipIf( - is_win32() and is_pypy() and on_github_actions(), - "Failing on Windows on GitHub Actions running PyPy", - ) def test_main(self): out = subprocess.check_output([sys.executable, "-m", "PIL"]).decode("utf-8") lines = out.splitlines() diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index 088fd5a03..0e068c1e4 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -1019,6 +1019,43 @@ this format. By default, a Quake2 standard palette is attached to the texture. To override the palette, use the putpalette method. +WMF +^^^ + +Pillow can identify WMF files. + +On Windows, it can read WMF files. By default, it will load the image at 72 +dpi. To load it at another resolution: + +.. code-block:: python + + from PIL import Image + with Image.open("drawing.wmf") as im: + im.load(dpi=144) + +To add other read or write support, use +:py:func:`PIL.WmfImagePlugin.register_handler` to register a WMF handler. + +.. code-block:: python + + from PIL import Image + from PIL import WmfImagePlugin + + class WmfHandler: + def open(self, im): + ... + def load(self, im): + ... + return image + def save(self, im, fp, filename): + ... + + wmf_handler = WmfHandler() + + WmfImagePlugin.register_handler(wmf_handler) + + im = Image.open("sample.wmf") + XPM ^^^ @@ -1176,35 +1213,3 @@ MPEG ^^^^ Pillow identifies MPEG files. - -WMF -^^^ - -Pillow can identify playable WMF files. - -In PIL 1.1.4 and earlier, the WMF driver provides some limited rendering -support, but not enough to be useful for any real application. - -In PIL 1.1.5 and later, the WMF driver is a stub driver. To add WMF read or -write support to your application, use -:py:func:`PIL.WmfImagePlugin.register_handler` to register a WMF handler. - -:: - - from PIL import Image - from PIL import WmfImagePlugin - - class WmfHandler: - def open(self, im): - ... - def load(self, im): - ... - return image - def save(self, im, fp, filename): - ... - - wmf_handler = WmfHandler() - - WmfImagePlugin.register_handler(wmf_handler) - - im = Image.open("sample.wmf") diff --git a/docs/releasenotes/7.0.0.rst b/docs/releasenotes/7.0.0.rst index 4abc216df..2facd42fa 100644 --- a/docs/releasenotes/7.0.0.rst +++ b/docs/releasenotes/7.0.0.rst @@ -73,18 +73,6 @@ bounds of resulting image. This may be useful in a subsequent .. _chain methods: https://en.wikipedia.org/wiki/Method_chaining -API Changes -=========== - -Deprecations -^^^^^^^^^^^^ - -TODO -~~~~ - -TODO - - API Additions ============= @@ -121,6 +109,18 @@ to reduce an image by integer times. Normally, it shouldn't be used directly. Used internally by :py:meth:`~PIL.Image.Image.resize` and :py:meth:`~PIL.Image.Image.thumbnail` methods to speed up resize when a new argument ``reducing_gap`` is set. +Loading WMF images at a given DPI +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +On Windows, Pillow can read WMF files, with a default DPI of 72. An image can +now also be loaded at another resolution: + +.. code-block:: python + + from PIL import Image + with Image.open("drawing.wmf") as im: + im.load(dpi=144) + Other Changes ============= diff --git a/src/PIL/WmfImagePlugin.py b/src/PIL/WmfImagePlugin.py index 7695a68e7..024222c9b 100644 --- a/src/PIL/WmfImagePlugin.py +++ b/src/PIL/WmfImagePlugin.py @@ -78,6 +78,7 @@ class WmfStubImageFile(ImageFile.StubImageFile): format_description = "Windows Metafile" def _open(self): + self._inch = None # check placable header s = self.fp.read(80) @@ -87,7 +88,7 @@ class WmfStubImageFile(ImageFile.StubImageFile): # placeable windows metafile # get units per inch - inch = word(s, 14) + self._inch = word(s, 14) # get bounding box x0 = short(s, 6) @@ -96,12 +97,14 @@ class WmfStubImageFile(ImageFile.StubImageFile): y1 = short(s, 12) # normalize size to 72 dots per inch - size = (x1 - x0) * 72 // inch, (y1 - y0) * 72 // inch + self.info["dpi"] = 72 + size = ( + (x1 - x0) * self.info["dpi"] // self._inch, + (y1 - y0) * self.info["dpi"] // self._inch, + ) self.info["wmf_bbox"] = x0, y0, x1, y1 - self.info["dpi"] = 72 - # sanity check (standard metafile header) if s[22:26] != b"\x01\x00\t\x00": raise SyntaxError("Unsupported WMF file format") @@ -118,7 +121,6 @@ class WmfStubImageFile(ImageFile.StubImageFile): # get frame (in 0.01 millimeter units) frame = _long(s, 24), _long(s, 28), _long(s, 32), _long(s, 36) - # normalize size to 72 dots per inch size = x1 - x0, y1 - y0 # calculate dots per inch from bbox and frame @@ -145,6 +147,16 @@ class WmfStubImageFile(ImageFile.StubImageFile): def _load(self): return _handler + def load(self, dpi=None): + if dpi is not None and self._inch is not None: + self.info["dpi"] = int(dpi + 0.5) + x0, y0, x1, y1 = self.info["wmf_bbox"] + self._size = ( + (x1 - x0) * self.info["dpi"] // self._inch, + (y1 - y0) * self.info["dpi"] // self._inch, + ) + super().load() + def _save(im, fp, filename): if _handler is None or not hasattr(_handler, "save"):