From ef864d72f10fe7c918d4cbc07fc16d4fef4b30bc Mon Sep 17 00:00:00 2001 From: Latosha Maltba Date: Sun, 21 Mar 2021 14:36:18 +0000 Subject: [PATCH 1/5] TestSuite: Add support for GraphicsMagick Add support to run the tests using GraphicsMagick's "gm convert" instead of ImageMagick's "convert". --- Tests/helper.py | 24 +++++++++++++++++++++--- Tests/test_file_palm.py | 12 ++++++------ 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/Tests/helper.py b/Tests/helper.py index be3bdb76faa..5573c78c43a 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -257,8 +257,16 @@ def netpbm_available(): return bool(shutil.which("ppmquant") and shutil.which("ppmtogif")) +def convert_available(): + return imagemagick_available() or graphicsmagick_available() + + def imagemagick_available(): - return bool(IMCONVERT and shutil.which(IMCONVERT)) + return bool(IMCONVERT and shutil.which(IMCONVERT[0])) + + +def graphicsmagick_available(): + return bool(GMCONVERT and shutil.which(GMCONVERT[0])) def on_appveyor(): @@ -298,10 +306,20 @@ def is_mingw(): if sys.platform == "win32": IMCONVERT = os.environ.get("MAGICK_HOME", "") + GMCONVERT = None if IMCONVERT: - IMCONVERT = os.path.join(IMCONVERT, "convert.exe") + IMCONVERT = [os.path.join(IMCONVERT, "convert.exe")] + GMCONVERT = [os.path.join(IMCONVERT, "gm.exe"), "convert"] +else: + IMCONVERT = ["convert"] + GMCONVERT = ["gm", "convert"] + +if imagemagick_available(): + CONVERT = IMCONVERT +elif graphicsmagick_available(): + CONVERT = GMCONVERT else: - IMCONVERT = "convert" + CONVERT = None class cached_property: diff --git a/Tests/test_file_palm.py b/Tests/test_file_palm.py index 25d194b628f..bbbab25d85c 100644 --- a/Tests/test_file_palm.py +++ b/Tests/test_file_palm.py @@ -5,9 +5,9 @@ from PIL import Image -from .helper import IMCONVERT, assert_image_equal, hopper, imagemagick_available +from .helper import CONVERT, assert_image_equal, convert_available, hopper -_roundtrip = imagemagick_available() +_roundtrip = convert_available() def helper_save_as_palm(tmp_path, mode): @@ -23,13 +23,13 @@ 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(): +def open_with_convert(tmp_path, f): + if not convert_available(): raise OSError() outfile = str(tmp_path / "temp.png") rc = subprocess.call( - [IMCONVERT, f, outfile], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT + CONVERT + [f, outfile], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT ) if rc: raise OSError @@ -44,7 +44,7 @@ def roundtrip(tmp_path, mode): outfile = str(tmp_path / "temp.palm") im.save(outfile) - converted = open_with_imagemagick(tmp_path, outfile) + converted = open_with_convert(tmp_path, outfile) assert_image_equal(converted, im) From bb88d8d0170d63e0824b6dfe684421426364db40 Mon Sep 17 00:00:00 2001 From: Latosha Maltba Date: Sun, 21 Mar 2021 14:36:18 +0000 Subject: [PATCH 2/5] Add support to use GraphicsMagick's "gm display" as viewer --- docs/reference/ImageShow.rst | 1 + src/PIL/ImageShow.py | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/docs/reference/ImageShow.rst b/docs/reference/ImageShow.rst index f1fbd90ce3a..e4d9805ab4c 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/src/PIL/ImageShow.py b/src/PIL/ImageShow.py index 3368865a46d..6cc420d1b01 100644 --- a/src/PIL/ImageShow.py +++ b/src/PIL/ImageShow.py @@ -194,6 +194,15 @@ def get_command_ex(self, file, **options): 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 @@ def get_command_ex(self, file, title=None, **options): 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"): From 35943372f08d7c4e344dd4b77db1547644af73ca Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 22 Mar 2021 07:48:13 +1100 Subject: [PATCH 3/5] Removed CONVERT helper variables --- Tests/helper.py | 42 +++++++++++++++-------------------------- Tests/test_file_palm.py | 16 ++++++---------- 2 files changed, 21 insertions(+), 37 deletions(-) diff --git a/Tests/helper.py b/Tests/helper.py index 5573c78c43a..ad8138ccc06 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -257,16 +257,22 @@ def netpbm_available(): return bool(shutil.which("ppmquant") and shutil.which("ppmtogif")) -def convert_available(): - return imagemagick_available() or graphicsmagick_available() - - -def imagemagick_available(): - return bool(IMCONVERT and shutil.which(IMCONVERT[0])) - +def magick_command(): + if sys.platform == "win32": + imagemagick = os.environ.get("MAGICK_HOME", "") + if imagemagick: + imagemagick = [os.path.join(imagemagick, "convert.exe")] + graphicsmagick = [os.path.join(imagemagick, "gm.exe"), "convert"] + else: + graphicsmagick = None + else: + imagemagick = ["convert"] + graphicsmagick = ["gm", "convert"] -def graphicsmagick_available(): - return bool(GMCONVERT and shutil.which(GMCONVERT[0])) + if imagemagick and shutil.which(imagemagick[0]): + return imagemagick + elif graphicsmagick and shutil.which(graphicsmagick[0]): + return graphicsmagick def on_appveyor(): @@ -304,24 +310,6 @@ def is_mingw(): return sysconfig.get_platform() == "mingw" -if sys.platform == "win32": - IMCONVERT = os.environ.get("MAGICK_HOME", "") - GMCONVERT = None - if IMCONVERT: - IMCONVERT = [os.path.join(IMCONVERT, "convert.exe")] - GMCONVERT = [os.path.join(IMCONVERT, "gm.exe"), "convert"] -else: - IMCONVERT = ["convert"] - GMCONVERT = ["gm", "convert"] - -if imagemagick_available(): - CONVERT = IMCONVERT -elif graphicsmagick_available(): - CONVERT = GMCONVERT -else: - CONVERT = None - - 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 bbbab25d85c..e1c1c361b1e 100644 --- a/Tests/test_file_palm.py +++ b/Tests/test_file_palm.py @@ -5,9 +5,7 @@ from PIL import Image -from .helper import CONVERT, assert_image_equal, convert_available, hopper - -_roundtrip = convert_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_convert(tmp_path, f): - if not convert_available(): - raise OSError() - +def open_with_magick(magick, tmp_path, f): outfile = str(tmp_path / "temp.png") rc = subprocess.call( - CONVERT + [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_convert(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_convert(tmp_path, outfile) + converted = open_with_magick(magick, tmp_path, outfile) assert_image_equal(converted, im) From 52794432f02112eb7c912ef8855c6a6fe82a99d1 Mon Sep 17 00:00:00 2001 From: Latosha Maltba Date: Mon, 22 Mar 2021 18:17:40 +0000 Subject: [PATCH 4/5] Make code for ImageMagick/GraphicsMagick more symmetric --- Tests/helper.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Tests/helper.py b/Tests/helper.py index ad8138ccc06..59ba0dd15d3 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -259,11 +259,12 @@ def netpbm_available(): def magick_command(): if sys.platform == "win32": - imagemagick = os.environ.get("MAGICK_HOME", "") - if imagemagick: - imagemagick = [os.path.join(imagemagick, "convert.exe")] - graphicsmagick = [os.path.join(imagemagick, "gm.exe"), "convert"] + 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"] From 0f8fffbb11616ee3d9abd1379a2718c192871198 Mon Sep 17 00:00:00 2001 From: Latosha Maltba Date: Tue, 23 Mar 2021 07:16:56 +0000 Subject: [PATCH 5/5] Mention GraphicsMagick support release notes 8.2.0 Co-authored-by: Andrew Murray --- docs/releasenotes/8.2.0.rst | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/docs/releasenotes/8.2.0.rst b/docs/releasenotes/8.2.0.rst index d82bf45c22e..83ba5b0c018 100644 --- a/docs/releasenotes/8.2.0.rst +++ b/docs/releasenotes/8.2.0.rst @@ -65,6 +65,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 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -86,3 +98,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/