CoolFace
Apppublic

Aluode/PerceptionLabPortable

sourceHugging Faceupdated 9mo agoView on Hugging Face
0likes
ImageShow.py363 linesDownload Raw Back to PIL
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#
14from __future__ import annotations
15
16import abc
17import os
18import shutil
19import subprocess
20import sys
21from shlex import quote
22from typing import Any
23
24from . import Image
25
26_viewers = []
27
28
29def register(viewer: type[Viewer] | Viewer, order: int = 1) -> None:
30    """
31    The :py:func:`register` function is used to register additional viewers::
32
33        from PIL import ImageShow
34        ImageShow.register(MyViewer())  # MyViewer will be used as a last resort
35        ImageShow.register(MySecondViewer(), 0)  # MySecondViewer will be prioritised
36        ImageShow.register(ImageShow.XVViewer(), 0)  # XVViewer will be prioritised
37
38    :param viewer: The viewer to be registered.
39    :param order:
40        Zero or a negative integer to prepend this viewer to the list,
41        a positive integer to append it.
42    """
43    if isinstance(viewer, type) and issubclass(viewer, Viewer):
44        viewer = viewer()
45    if order > 0:
46        _viewers.append(viewer)
47    else:
48        _viewers.insert(0, viewer)
49
50
51def show(image: Image.Image, title: str | None = None, **options: Any) -> bool:
52    r"""
53    Display a given image.
54
55    :param image: An image object.
56    :param title: Optional title. Not all viewers can display the title.
57    :param \**options: Additional viewer options.
58    :returns: ``True`` if a suitable viewer was found, ``False`` otherwise.
59    """
60    for viewer in _viewers:
61        if viewer.show(image, title=title, **options):
62            return True
63    return False
64
65
66class Viewer:
67    """Base class for viewers."""
68
69    # main api
70
71    def show(self, image: Image.Image, **options: Any) -> int:
72        """
73        The main function for displaying an image.
74        Converts the given image to the target format and displays it.
75        """
76
77        if not (
78            image.mode in ("1", "RGBA")
79            or (self.format == "PNG" and image.mode in ("I;16", "LA"))
80        ):
81            base = Image.getmodebase(image.mode)
82            if image.mode != base:
83                image = image.convert(base)
84
85        return self.show_image(image, **options)
86
87    # hook methods
88
89    format: str | None = None
90    """The format to convert the image into."""
91    options: dict[str, Any] = {}
92    """Additional options used to convert the image."""
93
94    def get_format(self, image: Image.Image) -> str | None:
95        """Return format name, or ``None`` to save as PGM/PPM."""
96        return self.format
97
98    def get_command(self, file: str, **options: Any) -> str:
99        """
100        Returns the command used to display the file.
101        Not implemented in the base class.
102        """
103        msg = "unavailable in base viewer"
104        raise NotImplementedError(msg)
105
106    def save_image(self, image: Image.Image) -> str:
107        """Save to temporary file and return filename."""
108        return image._dump(format=self.get_format(image), **self.options)
109
110    def show_image(self, image: Image.Image, **options: Any) -> int:
111        """Display the given image."""
112        return self.show_file(self.save_image(image), **options)
113
114    def show_file(self, path: str, **options: Any) -> int:
115        """
116        Display given file.
117        """
118        if not os.path.exists(path):
119            raise FileNotFoundError
120        os.system(self.get_command(path, **options))  # nosec
121        return 1
122
123
124# --------------------------------------------------------------------
125
126
127class WindowsViewer(Viewer):
128    """The default viewer on Windows is the default system application for PNG files."""
129
130    format = "PNG"
131    options = {"compress_level": 1, "save_all": True}
132
133    def get_command(self, file: str, **options: Any) -> str:
134        return (
135            f'start "Pillow" /WAIT "{file}" '
136            "&& ping -n 4 127.0.0.1 >NUL "
137            f'&& del /f "{file}"'
138        )
139
140    def show_file(self, path: str, **options: Any) -> int:
141        """
142        Display given file.
143        """
144        if not os.path.exists(path):
145            raise FileNotFoundError
146        subprocess.Popen(
147            self.get_command(path, **options),
148            shell=True,
149            creationflags=getattr(subprocess, "CREATE_NO_WINDOW"),
150        )  # nosec
151        return 1
152
153
154if sys.platform == "win32":
155    register(WindowsViewer)
156
157
158class MacViewer(Viewer):
159    """The default viewer on macOS using ``Preview.app``."""
160
161    format = "PNG"
162    options = {"compress_level": 1, "save_all": True}
163
164    def get_command(self, file: str, **options: Any) -> str:
165        # on darwin open returns immediately resulting in the temp
166        # file removal while app is opening
167        command = "open -a Preview.app"
168        command = f"({command} {quote(file)}; sleep 20; rm -f {quote(file)})&"
169        return command
170
171    def show_file(self, path: str, **options: Any) -> int:
172        """
173        Display given file.
174        """
175        if not os.path.exists(path):
176            raise FileNotFoundError
177        subprocess.call(["open", "-a", "Preview.app", path])
178
179        pyinstaller = getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS")
180        executable = (not pyinstaller and sys.executable) or shutil.which("python3")
181        if executable:
182            subprocess.Popen(
183                [
184                    executable,
185                    "-c",
186                    "import os, sys, time; time.sleep(20); os.remove(sys.argv[1])",
187                    path,
188                ]
189            )
190        return 1
191
192
193if sys.platform == "darwin":
194    register(MacViewer)
195
196
197class UnixViewer(abc.ABC, Viewer):
198    format = "PNG"
199    options = {"compress_level": 1, "save_all": True}
200
201    @abc.abstractmethod
202    def get_command_ex(self, file: str, **options: Any) -> tuple[str, str]:
203        pass
204
205    def get_command(self, file: str, **options: Any) -> str:
206        command = self.get_command_ex(file, **options)[0]
207        return f"{command} {quote(file)}"
208
209
210class XDGViewer(UnixViewer):
211    """
212    The freedesktop.org ``xdg-open`` command.
213    """
214
215    def get_command_ex(self, file: str, **options: Any) -> tuple[str, str]:
216        command = executable = "xdg-open"
217        return command, executable
218
219    def show_file(self, path: str, **options: Any) -> int:
220        """
221        Display given file.
222        """
223        if not os.path.exists(path):
224            raise FileNotFoundError
225        subprocess.Popen(["xdg-open", path])
226        return 1
227
228
229class DisplayViewer(UnixViewer):
230    """
231    The ImageMagick ``display`` command.
232    This viewer supports the ``title`` parameter.
233    """
234
235    def get_command_ex(
236        self, file: str, title: str | None = None, **options: Any
237    ) -> tuple[str, str]:
238        command = executable = "display"
239        if title:
240            command += f" -title {quote(title)}"
241        return command, executable
242
243    def show_file(self, path: str, **options: Any) -> int:
244        """
245        Display given file.
246        """
247        if not os.path.exists(path):
248            raise FileNotFoundError
249        args = ["display"]
250        title = options.get("title")
251        if title:
252            args += ["-title", title]
253        args.append(path)
254
255        subprocess.Popen(args)
256        return 1
257
258
259class GmDisplayViewer(UnixViewer):
260    """The GraphicsMagick ``gm display`` command."""
261
262    def get_command_ex(self, file: str, **options: Any) -> tuple[str, str]:
263        executable = "gm"
264        command = "gm display"
265        return command, executable
266
267    def show_file(self, path: str, **options: Any) -> int:
268        """
269        Display given file.
270        """
271        if not os.path.exists(path):
272            raise FileNotFoundError
273        subprocess.Popen(["gm", "display", path])
274        return 1
275
276
277class EogViewer(UnixViewer):
278    """The GNOME Image Viewer ``eog`` command."""
279
280    def get_command_ex(self, file: str, **options: Any) -> tuple[str, str]:
281        executable = "eog"
282        command = "eog -n"
283        return command, executable
284
285    def show_file(self, path: str, **options: Any) -> int:
286        """
287        Display given file.
288        """
289        if not os.path.exists(path):
290            raise FileNotFoundError
291        subprocess.Popen(["eog", "-n", path])
292        return 1
293
294
295class XVViewer(UnixViewer):
296    """
297    The X Viewer ``xv`` command.
298    This viewer supports the ``title`` parameter.
299    """
300
301    def get_command_ex(
302        self, file: str, title: str | None = None, **options: Any
303    ) -> tuple[str, str]:
304        # note: xv is pretty outdated.  most modern systems have
305        # imagemagick's display command instead.
306        command = executable = "xv"
307        if title:
308            command += f" -name {quote(title)}"
309        return command, executable
310
311    def show_file(self, path: str, **options: Any) -> int:
312        """
313        Display given file.
314        """
315        if not os.path.exists(path):
316            raise FileNotFoundError
317        args = ["xv"]
318        title = options.get("title")
319        if title:
320            args += ["-name", title]
321        args.append(path)
322
323        subprocess.Popen(args)
324        return 1
325
326
327if sys.platform not in ("win32", "darwin"):  # unixoids
328    if shutil.which("xdg-open"):
329        register(XDGViewer)
330    if shutil.which("display"):
331        register(DisplayViewer)
332    if shutil.which("gm"):
333        register(GmDisplayViewer)
334    if shutil.which("eog"):
335        register(EogViewer)
336    if shutil.which("xv"):
337        register(XVViewer)
338
339
340class IPythonViewer(Viewer):
341    """The viewer for IPython frontends."""
342
343    def show_image(self, image: Image.Image, **options: Any) -> int:
344        ipython_display(image)
345        return 1
346
347
348try:
349    from IPython.display import display as ipython_display
350except ImportError:
351    pass
352else:
353    register(IPythonViewer)
354
355
356if __name__ == "__main__":
357    if len(sys.argv) < 2:
358        print("Syntax: python3 ImageShow.py imagefile [title]")
359        sys.exit()
360
361    with Image.open(sys.argv[1]) as im:
362        print(show(im, *sys.argv[2:]))
363 
Aluode/PerceptionLabPortable · CoolFace