# # 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. # from __future__ import annotations import tkinter from io import BytesIO from typing import TYPE_CHECKING, Any, cast from . import Image, ImageFile if TYPE_CHECKING: from ._typing import CapsuleType # -------------------------------------------------------------------- # Check for Tkinter interface hooks def _get_image_from_kw(kw: dict[str, Any]) -> ImageFile.ImageFile | None: source = None if "file" in kw: source = kw.pop("file") elif "data" in kw: source = BytesIO(kw.pop("data")) if not source: return None return Image.open(source) def _pyimagingtkcall( command: str, photo: PhotoImage | tkinter.PhotoImage, ptr: CapsuleType ) -> None: tk = photo.tk try: tk.call(command, photo, repr(ptr)) except tkinter.TclError: # activate Tkinter hook # may raise an error if it cannot attach to Tkinter from . import _imagingtk _imagingtk.tkinit(tk.interpaddr()) tk.call(command, photo, repr(ptr)) # -------------------------------------------------------------------- # 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: Image.Image | str | None = None, size: tuple[int, int] | None = None, **kw: Any, ) -> None: # Tk compatibility: file or data if image is None: image = _get_image_from_kw(kw) if image is None: msg = "Image is required" raise ValueError(msg) elif isinstance(image, str): mode = image image = None if size is None: msg = "If first argument is mode, size is required" raise ValueError(msg) else: # got an image instead of a mode mode = image.mode if mode == "P": # palette mapped data image.apply_transparency() image.load() mode = image.palette.mode if image.palette else "RGB" size = image.size kw["width"], kw["height"] = size 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) -> None: try: name = self.__photo.name except AttributeError: return self.__photo.name = None try: self.__photo.tk.call("image", "delete", name) except Exception: pass # ignore internal errors def __str__(self) -> str: """ 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) -> int: """ Get the width of the image. :return: The width, in pixels. """ return self.__size[0] def height(self) -> int: """ Get the height of the image. :return: The height, in pixels. """ return self.__size[1] def paste(self, im: Image.Image) -> 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. """ # convert to blittable ptr = im.getim() image = im.im if not image.isblock() or im.mode != self.__mode: block = Image.core.new_block(self.__mode, im.size) image.convert2(block, image) # convert directly between buffers ptr = block.ptr _pyimagingtkcall("PyImagingPhoto", self.__photo, ptr) # -------------------------------------------------------------------- # 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: Image.Image | None = None, **kw: Any) -> None: # Tk compatibility: file or data if image is None: image = _get_image_from_kw(kw) if image is None: msg = "Image is required" raise ValueError(msg) self.__mode = image.mode self.__size = image.size self.__photo = tkinter.BitmapImage(data=image.tobitmap(), **kw) def __del__(self) -> None: try: name = self.__photo.name except AttributeError: return self.__photo.name = None try: self.__photo.tk.call("image", "delete", name) except Exception: pass # ignore internal errors def width(self) -> int: """ Get the width of the image. :return: The width, in pixels. """ return self.__size[0] def height(self) -> int: """ Get the height of the image. :return: The height, in pixels. """ return self.__size[1] def __str__(self) -> str: """ 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: PhotoImage) -> Image.Image: """Copies the contents of a PhotoImage to a PIL image memory.""" im = Image.new("RGBA", (photo.width(), photo.height())) _pyimagingtkcall("PyImagingPhotoGet", photo, im.getim()) return im def _show(image: Image.Image, title: str | None) -> None: """Helper for the Image.show method.""" class UI(tkinter.Label): def __init__(self, master: tkinter.Toplevel, im: Image.Image) -> None: self.image: BitmapImage | PhotoImage if im.mode == "1": self.image = BitmapImage(im, foreground="white", master=master) else: self.image = PhotoImage(im, master=master) if TYPE_CHECKING: image = cast(tkinter._Image, self.image) else: image = self.image super().__init__(master, image=image, bg="black", bd=0) if not getattr(tkinter, "_default_root"): msg = "tkinter not initialized" raise OSError(msg) top = tkinter.Toplevel() if title: top.title(title) UI(top, image).pack()