use PIL for image handling

This commit is contained in:
Brennen Raimer
2025-03-18 16:42:52 -04:00
parent 12af0cee8d
commit 7e84886bb3

View File

@@ -1,33 +1,40 @@
import mimetypes
import traceback
from collections import namedtuple
from collections.abc import Callable
import tkinter as tk
from tkinter import *
import tkinter.messagebox as messagebox
import tkinter.filedialog as filedialog
import tkinter.ttk as ttk
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
if not mimetypes.inited:
mimetypes.init()
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 handle_action_exceptions(func: Callable):
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: tk.Event):
def wrapper(event: Event):
try:
func(event)
except NotImplementedError as e:
messagebox.showwarning(
title="Warning",
message=f"{func.__name__} Not Implemented",
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",
@@ -41,51 +48,66 @@ def handle_action_exceptions(func: Callable):
return wrapper
@handle_action_exceptions
def open_preview(event: tk.Event):
@widget_action
def open_preview(event: Event):
file = filedialog.askopenfilename(
parent=event.widget.master,
title="Open Image",
initialdir=Path.home(),
filetypes=[
(mimetype, "*"+ext) for ext, mimetype in mimetypes.types_map.items()
if ext in (".png",".gif", ".pgm", ".ppm")
("Supported Image Files", " ".join(SUPPORTED_IMAGE_TYPES))
]
)
if Path(file).exists():
# don't GC the photo when this function exits
global _photo
_photo=tk.PhotoImage(file=file)
event.widget.create_image(0,0, anchor=tk.NW, image=_photo)
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
@handle_action_exceptions
def send_to_iwii(event: tk.Event):
@widget_action
def send_to_iwii(event: Event):
raise NotImplementedError
def photobooth():
app = tk.Tk()
app = Tk()
app.photobooth_image = None
app.title("PhotoBooth")
style = ttk.Style()
style = Style()
match system():
case "Windows":
try:
style.theme_use("winnative")
except tk.TclError:
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 = tk.Canvas(master=app, height=576, width=720)
preview = Canvas(master=app, height=IWII_RESOLUTION.height, width=IWII_RESOLUTION.width)
preview.bind("<space>", open_preview)
preview.bind("<return>", send_to_iwii)
preview.bind("<Return>", send_to_iwii)
preview.focus_set()
preview.pack(side="top")
app.mainloop()