Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ImageShow support for GraphicsMagick #5349

Merged
merged 5 commits into from Apr 1, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
27 changes: 17 additions & 10 deletions Tests/helper.py
Expand Up @@ -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():
Expand Down Expand Up @@ -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
Expand Down
16 changes: 6 additions & 10 deletions Tests/test_file_palm.py
Expand Up @@ -5,9 +5,7 @@

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):
Expand All @@ -23,28 +21,26 @@ 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
return Image.open(outfile)


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)


Expand Down
1 change: 1 addition & 0 deletions docs/reference/ImageShow.rst
Expand Up @@ -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

Expand Down
21 changes: 21 additions & 0 deletions docs/releasenotes/8.2.0.rst
Expand Up @@ -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
^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Expand All @@ -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/
11 changes: 11 additions & 0 deletions src/PIL/ImageShow.py
Expand Up @@ -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."""

Expand All @@ -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"):
Expand Down