123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300 |
- #
- # The Python Imaging Library.
- # $Id$
- #
- # a Tk display interface
- #
- # History:
- # 96-04-08 fl Created
- # 96-09-06 fl Added getimage method
- # 96-11-01 fl Rewritten, removed image attribute and crop method
- # 97-05-09 fl Use PyImagingPaste method instead of image type
- # 97-05-12 fl Minor tweaks to match the IFUNC95 interface
- # 97-05-17 fl Support the "pilbitmap" booster patch
- # 97-06-05 fl Added file= and data= argument to image constructors
- # 98-03-09 fl Added width and height methods to Image classes
- # 98-07-02 fl Use default mode for "P" images without palette attribute
- # 98-07-02 fl Explicitly destroy Tkinter image objects
- # 99-07-24 fl Support multiple Tk interpreters (from Greg Couch)
- # 99-07-26 fl Automatically hook into Tkinter (if possible)
- # 99-08-15 fl Hook uses _imagingtk instead of _imaging
- #
- # Copyright (c) 1997-1999 by Secret Labs AB
- # Copyright (c) 1996-1997 by Fredrik Lundh
- #
- # See the README file for information on usage and redistribution.
- #
- import tkinter
- from io import BytesIO
- from . import Image
- # --------------------------------------------------------------------
- # Check for Tkinter interface hooks
- _pilbitmap_ok = None
- def _pilbitmap_check():
- global _pilbitmap_ok
- if _pilbitmap_ok is None:
- try:
- im = Image.new("1", (1, 1))
- tkinter.BitmapImage(data=f"PIL:{im.im.id}")
- _pilbitmap_ok = 1
- except tkinter.TclError:
- _pilbitmap_ok = 0
- return _pilbitmap_ok
- def _get_image_from_kw(kw):
- source = None
- if "file" in kw:
- source = kw.pop("file")
- elif "data" in kw:
- source = BytesIO(kw.pop("data"))
- if source:
- return Image.open(source)
- # --------------------------------------------------------------------
- # PhotoImage
- class PhotoImage:
- """
- A Tkinter-compatible photo image. This can be used
- everywhere Tkinter expects an image object. If the image is an RGBA
- image, pixels having alpha 0 are treated as transparent.
- The constructor takes either a PIL image, or a mode and a size.
- Alternatively, you can use the ``file`` or ``data`` options to initialize
- the photo image object.
- :param image: Either a PIL image, or a mode string. If a mode string is
- used, a size must also be given.
- :param size: If the first argument is a mode string, this defines the size
- of the image.
- :keyword file: A filename to load the image from (using
- ``Image.open(file)``).
- :keyword data: An 8-bit string containing image data (as loaded from an
- image file).
- """
- def __init__(self, image=None, size=None, **kw):
- # Tk compatibility: file or data
- if image is None:
- image = _get_image_from_kw(kw)
- if hasattr(image, "mode") and hasattr(image, "size"):
- # got an image instead of a mode
- mode = image.mode
- if mode == "P":
- # palette mapped data
- image.load()
- try:
- mode = image.palette.mode
- except AttributeError:
- mode = "RGB" # default
- size = image.size
- kw["width"], kw["height"] = size
- else:
- mode = image
- image = None
- if mode not in ["1", "L", "RGB", "RGBA"]:
- mode = Image.getmodebase(mode)
- self.__mode = mode
- self.__size = size
- self.__photo = tkinter.PhotoImage(**kw)
- self.tk = self.__photo.tk
- if image:
- self.paste(image)
- def __del__(self):
- name = self.__photo.name
- self.__photo.name = None
- try:
- self.__photo.tk.call("image", "delete", name)
- except Exception:
- pass # ignore internal errors
- def __str__(self):
- """
- Get the Tkinter photo image identifier. This method is automatically
- called by Tkinter whenever a PhotoImage object is passed to a Tkinter
- method.
- :return: A Tkinter photo image identifier (a string).
- """
- return str(self.__photo)
- def width(self):
- """
- Get the width of the image.
- :return: The width, in pixels.
- """
- return self.__size[0]
- def height(self):
- """
- Get the height of the image.
- :return: The height, in pixels.
- """
- return self.__size[1]
- def paste(self, im, box=None):
- """
- Paste a PIL image into the photo image. Note that this can
- be very slow if the photo image is displayed.
- :param im: A PIL image. The size must match the target region. If the
- mode does not match, the image is converted to the mode of
- the bitmap image.
- :param box: A 4-tuple defining the left, upper, right, and lower pixel
- coordinate. See :ref:`coordinate-system`. If None is given
- instead of a tuple, all of the image is assumed.
- """
- # convert to blittable
- im.load()
- image = im.im
- if image.isblock() and im.mode == self.__mode:
- block = image
- else:
- block = image.new_block(self.__mode, im.size)
- image.convert2(block, image) # convert directly between buffers
- tk = self.__photo.tk
- try:
- tk.call("PyImagingPhoto", self.__photo, block.id)
- except tkinter.TclError:
- # activate Tkinter hook
- try:
- from . import _imagingtk
- try:
- if hasattr(tk, "interp"):
- # Required for PyPy, which always has CFFI installed
- from cffi import FFI
- ffi = FFI()
- # PyPy is using an FFI CDATA element
- # (Pdb) self.tk.interp
- # <cdata 'Tcl_Interp *' 0x3061b50>
- _imagingtk.tkinit(int(ffi.cast("uintptr_t", tk.interp)), 1)
- else:
- _imagingtk.tkinit(tk.interpaddr(), 1)
- except AttributeError:
- _imagingtk.tkinit(id(tk), 0)
- tk.call("PyImagingPhoto", self.__photo, block.id)
- except (ImportError, AttributeError, tkinter.TclError):
- raise # configuration problem; cannot attach to Tkinter
- # --------------------------------------------------------------------
- # BitmapImage
- class BitmapImage:
- """
- A Tkinter-compatible bitmap image. This can be used everywhere Tkinter
- expects an image object.
- The given image must have mode "1". Pixels having value 0 are treated as
- transparent. Options, if any, are passed on to Tkinter. The most commonly
- used option is ``foreground``, which is used to specify the color for the
- non-transparent parts. See the Tkinter documentation for information on
- how to specify colours.
- :param image: A PIL image.
- """
- def __init__(self, image=None, **kw):
- # Tk compatibility: file or data
- if image is None:
- image = _get_image_from_kw(kw)
- self.__mode = image.mode
- self.__size = image.size
- if _pilbitmap_check():
- # fast way (requires the pilbitmap booster patch)
- image.load()
- kw["data"] = f"PIL:{image.im.id}"
- self.__im = image # must keep a reference
- else:
- # slow but safe way
- kw["data"] = image.tobitmap()
- self.__photo = tkinter.BitmapImage(**kw)
- def __del__(self):
- name = self.__photo.name
- self.__photo.name = None
- try:
- self.__photo.tk.call("image", "delete", name)
- except Exception:
- pass # ignore internal errors
- def width(self):
- """
- Get the width of the image.
- :return: The width, in pixels.
- """
- return self.__size[0]
- def height(self):
- """
- Get the height of the image.
- :return: The height, in pixels.
- """
- return self.__size[1]
- def __str__(self):
- """
- Get the Tkinter bitmap image identifier. This method is automatically
- called by Tkinter whenever a BitmapImage object is passed to a Tkinter
- method.
- :return: A Tkinter bitmap image identifier (a string).
- """
- return str(self.__photo)
- def getimage(photo):
- """Copies the contents of a PhotoImage to a PIL image memory."""
- im = Image.new("RGBA", (photo.width(), photo.height()))
- block = im.im
- photo.tk.call("PyImagingPhotoGet", photo, block.id)
- return im
- def _show(image, title):
- """Helper for the Image.show method."""
- class UI(tkinter.Label):
- def __init__(self, master, im):
- if im.mode == "1":
- self.image = BitmapImage(im, foreground="white", master=master)
- else:
- self.image = PhotoImage(im, master=master)
- super().__init__(master, image=self.image, bg="black", bd=0)
- if not tkinter._default_root:
- raise OSError("tkinter not initialized")
- top = tkinter.Toplevel()
- if title:
- top.title(title)
- UI(top, image).pack()
|