ImageShow.py 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. #
  2. # The Python Imaging Library.
  3. # $Id$
  4. #
  5. # im.show() drivers
  6. #
  7. # History:
  8. # 2008-04-06 fl Created
  9. #
  10. # Copyright (c) Secret Labs AB 2008.
  11. #
  12. # See the README file for information on usage and redistribution.
  13. #
  14. import os
  15. import shutil
  16. import subprocess
  17. import sys
  18. from shlex import quote
  19. from . import Image
  20. _viewers = []
  21. def register(viewer, order=1):
  22. """
  23. The :py:func:`register` function is used to register additional viewers::
  24. from PIL import ImageShow
  25. ImageShow.register(MyViewer()) # MyViewer will be used as a last resort
  26. ImageShow.register(MySecondViewer(), 0) # MySecondViewer will be prioritised
  27. ImageShow.register(ImageShow.XVViewer(), 0) # XVViewer will be prioritised
  28. :param viewer: The viewer to be registered.
  29. :param order:
  30. Zero or a negative integer to prepend this viewer to the list,
  31. a positive integer to append it.
  32. """
  33. try:
  34. if issubclass(viewer, Viewer):
  35. viewer = viewer()
  36. except TypeError:
  37. pass # raised if viewer wasn't a class
  38. if order > 0:
  39. _viewers.append(viewer)
  40. else:
  41. _viewers.insert(0, viewer)
  42. def show(image, title=None, **options):
  43. r"""
  44. Display a given image.
  45. :param image: An image object.
  46. :param title: Optional title. Not all viewers can display the title.
  47. :param \**options: Additional viewer options.
  48. :returns: ``True`` if a suitable viewer was found, ``False`` otherwise.
  49. """
  50. for viewer in _viewers:
  51. if viewer.show(image, title=title, **options):
  52. return True
  53. return False
  54. class Viewer:
  55. """Base class for viewers."""
  56. # main api
  57. def show(self, image, **options):
  58. """
  59. The main function for displaying an image.
  60. Converts the given image to the target format and displays it.
  61. """
  62. if not (
  63. image.mode in ("1", "RGBA")
  64. or (self.format == "PNG" and image.mode in ("I;16", "LA"))
  65. ):
  66. base = Image.getmodebase(image.mode)
  67. if image.mode != base:
  68. image = image.convert(base)
  69. return self.show_image(image, **options)
  70. # hook methods
  71. format = None
  72. """The format to convert the image into."""
  73. options = {}
  74. """Additional options used to convert the image."""
  75. def get_format(self, image):
  76. """Return format name, or ``None`` to save as PGM/PPM."""
  77. return self.format
  78. def get_command(self, file, **options):
  79. """
  80. Returns the command used to display the file.
  81. Not implemented in the base class.
  82. """
  83. raise NotImplementedError
  84. def save_image(self, image):
  85. """Save to temporary file and return filename."""
  86. return image._dump(format=self.get_format(image), **self.options)
  87. def show_image(self, image, **options):
  88. """Display the given image."""
  89. return self.show_file(self.save_image(image), **options)
  90. def show_file(self, path, **options):
  91. """
  92. Display given file.
  93. """
  94. os.system(self.get_command(path, **options)) # nosec
  95. return 1
  96. # --------------------------------------------------------------------
  97. class WindowsViewer(Viewer):
  98. """The default viewer on Windows is the default system application for PNG files."""
  99. format = "PNG"
  100. options = {"compress_level": 1, "save_all": True}
  101. def get_command(self, file, **options):
  102. return (
  103. f'start "Pillow" /WAIT "{file}" '
  104. "&& ping -n 4 127.0.0.1 >NUL "
  105. f'&& del /f "{file}"'
  106. )
  107. if sys.platform == "win32":
  108. register(WindowsViewer)
  109. class MacViewer(Viewer):
  110. """The default viewer on macOS using ``Preview.app``."""
  111. format = "PNG"
  112. options = {"compress_level": 1, "save_all": True}
  113. def get_command(self, file, **options):
  114. # on darwin open returns immediately resulting in the temp
  115. # file removal while app is opening
  116. command = "open -a Preview.app"
  117. command = f"({command} {quote(file)}; sleep 20; rm -f {quote(file)})&"
  118. return command
  119. def show_file(self, path, **options):
  120. """
  121. Display given file.
  122. """
  123. subprocess.call(["open", "-a", "Preview.app", path])
  124. executable = sys.executable or shutil.which("python3")
  125. if executable:
  126. subprocess.Popen(
  127. [
  128. executable,
  129. "-c",
  130. "import os, sys, time; time.sleep(20); os.remove(sys.argv[1])",
  131. path,
  132. ]
  133. )
  134. return 1
  135. if sys.platform == "darwin":
  136. register(MacViewer)
  137. class UnixViewer(Viewer):
  138. format = "PNG"
  139. options = {"compress_level": 1, "save_all": True}
  140. def get_command(self, file, **options):
  141. command = self.get_command_ex(file, **options)[0]
  142. return f"({command} {quote(file)}"
  143. class XDGViewer(UnixViewer):
  144. """
  145. The freedesktop.org ``xdg-open`` command.
  146. """
  147. def get_command_ex(self, file, **options):
  148. command = executable = "xdg-open"
  149. return command, executable
  150. def show_file(self, path, **options):
  151. """
  152. Display given file.
  153. """
  154. subprocess.Popen(["xdg-open", path])
  155. return 1
  156. class DisplayViewer(UnixViewer):
  157. """
  158. The ImageMagick ``display`` command.
  159. This viewer supports the ``title`` parameter.
  160. """
  161. def get_command_ex(self, file, title=None, **options):
  162. command = executable = "display"
  163. if title:
  164. command += f" -title {quote(title)}"
  165. return command, executable
  166. def show_file(self, path, **options):
  167. """
  168. Display given file.
  169. """
  170. args = ["display"]
  171. title = options.get("title")
  172. if title:
  173. args += ["-title", title]
  174. args.append(path)
  175. subprocess.Popen(args)
  176. return 1
  177. class GmDisplayViewer(UnixViewer):
  178. """The GraphicsMagick ``gm display`` command."""
  179. def get_command_ex(self, file, **options):
  180. executable = "gm"
  181. command = "gm display"
  182. return command, executable
  183. def show_file(self, path, **options):
  184. """
  185. Display given file.
  186. """
  187. subprocess.Popen(["gm", "display", path])
  188. return 1
  189. class EogViewer(UnixViewer):
  190. """The GNOME Image Viewer ``eog`` command."""
  191. def get_command_ex(self, file, **options):
  192. executable = "eog"
  193. command = "eog -n"
  194. return command, executable
  195. def show_file(self, path, **options):
  196. """
  197. Display given file.
  198. """
  199. subprocess.Popen(["eog", "-n", path])
  200. return 1
  201. class XVViewer(UnixViewer):
  202. """
  203. The X Viewer ``xv`` command.
  204. This viewer supports the ``title`` parameter.
  205. """
  206. def get_command_ex(self, file, title=None, **options):
  207. # note: xv is pretty outdated. most modern systems have
  208. # imagemagick's display command instead.
  209. command = executable = "xv"
  210. if title:
  211. command += f" -name {quote(title)}"
  212. return command, executable
  213. def show_file(self, path, **options):
  214. """
  215. Display given file.
  216. """
  217. args = ["xv"]
  218. title = options.get("title")
  219. if title:
  220. args += ["-name", title]
  221. args.append(path)
  222. subprocess.Popen(args)
  223. return 1
  224. if sys.platform not in ("win32", "darwin"): # unixoids
  225. if shutil.which("xdg-open"):
  226. register(XDGViewer)
  227. if shutil.which("display"):
  228. register(DisplayViewer)
  229. if shutil.which("gm"):
  230. register(GmDisplayViewer)
  231. if shutil.which("eog"):
  232. register(EogViewer)
  233. if shutil.which("xv"):
  234. register(XVViewer)
  235. class IPythonViewer(Viewer):
  236. """The viewer for IPython frontends."""
  237. def show_image(self, image, **options):
  238. ipython_display(image)
  239. return 1
  240. try:
  241. from IPython.display import display as ipython_display
  242. except ImportError:
  243. pass
  244. else:
  245. register(IPythonViewer)
  246. if __name__ == "__main__":
  247. if len(sys.argv) < 2:
  248. print("Syntax: python3 ImageShow.py imagefile [title]")
  249. sys.exit()
  250. with Image.open(sys.argv[1]) as im:
  251. print(show(im, *sys.argv[2:]))