diff --git a/Tests/helper.py b/Tests/helper.py index be3bdb76f..59ba0dd15 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -257,8 +257,23 @@ def netpbm_available(): return bool(shutil.which("ppmquant") and shutil.which("ppmtogif")) -def imagemagick_available(): - return bool(IMCONVERT and shutil.which(IMCONVERT)) +def magick_command(): + if sys.platform == "win32": + magickhome = os.environ.get("MAGICK_HOME", "") + if magickhome: + imagemagick = [os.path.join(magickhome, "convert.exe")] + graphicsmagick = [os.path.join(magickhome, "gm.exe"), "convert"] + else: + imagemagick = None + graphicsmagick = None + else: + imagemagick = ["convert"] + graphicsmagick = ["gm", "convert"] + + if imagemagick and shutil.which(imagemagick[0]): + return imagemagick + elif graphicsmagick and shutil.which(graphicsmagick[0]): + return graphicsmagick def on_appveyor(): @@ -296,14 +311,6 @@ def is_mingw(): return sysconfig.get_platform() == "mingw" -if sys.platform == "win32": - IMCONVERT = os.environ.get("MAGICK_HOME", "") - if IMCONVERT: - IMCONVERT = os.path.join(IMCONVERT, "convert.exe") -else: - IMCONVERT = "convert" - - class cached_property: def __init__(self, func): self.func = func diff --git a/Tests/test_file_palm.py b/Tests/test_file_palm.py index 25d194b62..e1c1c361b 100644 --- a/Tests/test_file_palm.py +++ b/Tests/test_file_palm.py @@ -5,9 +5,7 @@ import pytest from PIL import Image -from .helper import IMCONVERT, assert_image_equal, hopper, imagemagick_available - -_roundtrip = imagemagick_available() +from .helper import assert_image_equal, hopper, magick_command def helper_save_as_palm(tmp_path, mode): @@ -23,13 +21,10 @@ def helper_save_as_palm(tmp_path, mode): assert os.path.getsize(outfile) > 0 -def open_with_imagemagick(tmp_path, f): - if not imagemagick_available(): - raise OSError() - +def open_with_magick(magick, tmp_path, f): outfile = str(tmp_path / "temp.png") rc = subprocess.call( - [IMCONVERT, f, outfile], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT + magick + [f, outfile], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT ) if rc: raise OSError @@ -37,14 +32,15 @@ def open_with_imagemagick(tmp_path, f): def roundtrip(tmp_path, mode): - if not _roundtrip: + magick = magick_command() + if not magick: return im = hopper(mode) outfile = str(tmp_path / "temp.palm") im.save(outfile) - converted = open_with_imagemagick(tmp_path, outfile) + converted = open_with_magick(magick, tmp_path, outfile) assert_image_equal(converted, im) diff --git a/docs/reference/ImageShow.rst b/docs/reference/ImageShow.rst index f1fbd90ce..e4d9805ab 100644 --- a/docs/reference/ImageShow.rst +++ b/docs/reference/ImageShow.rst @@ -18,6 +18,7 @@ All default viewers convert the image to be shown to PNG format. The following viewers may be registered on Unix-based systems, if the given command is found: .. autoclass:: PIL.ImageShow.DisplayViewer + .. autoclass:: PIL.ImageShow.GmDisplayViewer .. autoclass:: PIL.ImageShow.EogViewer .. autoclass:: PIL.ImageShow.XVViewer diff --git a/docs/releasenotes/8.2.0.rst b/docs/releasenotes/8.2.0.rst index 0f7318e3b..920b77dc8 100644 --- a/docs/releasenotes/8.2.0.rst +++ b/docs/releasenotes/8.2.0.rst @@ -83,6 +83,18 @@ instances, so it will only be used by ``im.show()`` or :py:func:`.ImageShow.show if none of the other viewers are available. This means that the behaviour of :py:class:`PIL.ImageShow` will stay the same for most Pillow users. +ImageShow.GmDisplayViewer +^^^^^^^^^^^^^^^^^^^^^^^^^ + +If GraphicsMagick is present, this new :py:class:`PIL.ImageShow.Viewer` subclass will +be registered. It uses GraphicsMagick_, an ImageMagick_ fork, to display images. + +The GraphicsMagick based viewer has a lower priority than its ImageMagick +counterpart. Thus, if both ImageMagick and GraphicsMagick are installed, +``im.show()`` and :py:func:`.ImageShow.show()` prefer the viewer based on +ImageMagick, i.e the behaviour stays the same for Pillow users having +ImageMagick installed. + Saving TIFF with ICC profile ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -143,3 +155,12 @@ PyQt6 Support has been added for PyQt6. If it is installed, it will be used instead of PySide6, PyQt5 or PySide2. + +GraphicsMagick +^^^^^^^^^^^^^^ + +The test suite can now be run on systems which have GraphicsMagick_ but not +ImageMagick_ installed. If both are installed, the tests prefer ImageMagick. + +.. _GraphicsMagick: http://www.graphicsmagick.org/ +.. _ImageMagick: https://imagemagick.org/ diff --git a/src/PIL/ImageShow.py b/src/PIL/ImageShow.py index 3368865a4..6cc420d1b 100644 --- a/src/PIL/ImageShow.py +++ b/src/PIL/ImageShow.py @@ -194,6 +194,15 @@ class DisplayViewer(UnixViewer): return command, executable +class GmDisplayViewer(UnixViewer): + """The GraphicsMagick ``gm display`` command.""" + + def get_command_ex(self, file, **options): + executable = "gm" + command = "gm display" + return command, executable + + class EogViewer(UnixViewer): """The GNOME Image Viewer ``eog`` command.""" @@ -220,6 +229,8 @@ class XVViewer(UnixViewer): if sys.platform not in ("win32", "darwin"): # unixoids if shutil.which("display"): register(DisplayViewer) + if shutil.which("gm"): + register(GmDisplayViewer) if shutil.which("eog"): register(EogViewer) if shutil.which("xv"):