import traceback from collections import namedtuple from collections.abc import Callable from tkinter import * import tkinter.messagebox as messagebox import tkinter.filedialog as filedialog from tkinter.ttk import * from functools import wraps from pathlib import Path from platform import system from PIL import ImageOps from PIL.ImageTk import Image, PhotoImage from serial import Serial Resolution = namedtuple("Resolution", ("height", "width")) IWII_RESOLUTION = Resolution(576, 720) SUPPORTED_IMAGE_TYPES = sorted("*"+ext for ext, type_ in Image.registered_extensions().items() if type_ in ("PNG", "GIF", "JPEG", "WEBP", "BMP")) def widget_action(func: Callable): # decorator to wrap widget actions in an error handler that displays exceptions in error message windows @wraps(func) def wrapper(event: Event): try: func(event) except NotImplementedError as e: messagebox.showwarning( title="Warning", message=f"{func.__name__} not implemented", parent=event.widget.master ) except SystemExit: # probably unnecessary, but we don't want to accidentally intercept requests to exit raise except Exception as e: messagebox.showerror( title="Error", message=str(e), detail=traceback.format_exc(), parent=event.widget.master ) event.widget.master.quit() return wrapper @widget_action def open_preview(event: Event): file = filedialog.askopenfilename( parent=event.widget.master, title="Open Image", initialdir=Path.home(), filetypes=[ ("Supported Image Files", " ".join(SUPPORTED_IMAGE_TYPES)) ] ) if not file: return elif Path(file).exists() and Path(file).is_file(): # load image data and resize to fit the IWII resolution preserving aspect ratio image_data = ImageOps.contain(Image.open(file), IWII_RESOLUTION) photo = PhotoImage(image_data, size=IWII_RESOLUTION) event.widget.create_image(0,0, anchor=NW, image=photo) # tkinter does not maintain a reference to the local variables of this function upon return # without references, the photo and its data get garbage collected and nothing is display # therefore we must create references to them to keep them around event.widget._displayed_photo=photo event.widget.master.photobooth_image = image_data @widget_action def send_to_iwii(event: Event): raise NotImplementedError def photobooth(): app = Tk() app.photobooth_image = None app.title("PhotoBooth") style = Style() match system(): case "Windows": try: style.theme_use("winnative") except TclError: style.theme_use("vista") case "Darwin": style.theme_use("aqua") case "Linux": style.theme_use("alt") # https://python-forum.io/thread-31235.html try: # this is so stupid! you have to intentionally do this just to initialize the # required namespace to set variables to hide hidden files by default app.tk.call("tk_getOpenFile", "-intentionalnonsense") except TclError: app.setvar("::tk::dialog::file::showHiddenBtn", 1) app.setvar("::tk::dialog::file::showHiddenVar", 0) case _: style.theme_use("default") preview = Canvas(master=app, height=IWII_RESOLUTION.height, width=IWII_RESOLUTION.width) preview.bind("", open_preview) preview.bind("", send_to_iwii) preview.focus_set() preview.pack(side="top") app.mainloop() if __name__ == "__main__": photobooth()