From 1e13e9cd3cdab498c19b67ad8c73002dbd2c8c3b Mon Sep 17 00:00:00 2001 From: Brennen Raimer <5969754+norweeg@users.noreply.github.com> Date: Sat, 22 Mar 2025 12:48:46 -0400 Subject: [PATCH] implement send_to_printer add dithering to image preview --- iwii_photobooth.py | 69 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 60 insertions(+), 9 deletions(-) diff --git a/iwii_photobooth.py b/iwii_photobooth.py index 917024c..4c71138 100644 --- a/iwii_photobooth.py +++ b/iwii_photobooth.py @@ -12,6 +12,7 @@ from pathlib import Path from platform import system from PIL import ImageOps +from PIL.ImagePalette import ImagePalette from PIL.ImageTk import Image, PhotoImage from serial import Serial @@ -20,7 +21,18 @@ 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): +IWII_PALETTE = ImagePalette( + "P", + [ + 255, 255, 255, #white + 0, 255, 255, #cyan + 255, 0, 255, #magenta + 255, 255, 0, #yellow + 0, 0, 0, #black + ] +) + +def widget_action(func: Callable[[Event], None]) -> Callable[[Event], None]: # decorator to wrap widget actions in an error handler that displays exceptions in error message windows @wraps(func) def wrapper(event: Event): @@ -43,13 +55,14 @@ def widget_action(func: Callable): parent=event.widget.master ) - event.widget.master.quit() + quit_photobooth(event) + exit(1) return wrapper @widget_action -def open_preview(event: Event): +def open_preview(event: Event) -> None: file = filedialog.askopenfilename( parent=event.widget.master, title="Open Image", @@ -64,17 +77,53 @@ def open_preview(event: Event): 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) + + p_img = Image.new('P', image_data.size) + p_img.putpalette(IWII_PALETTE) + + conv = image_data.quantize(palette=p_img).convert("RGB") + + photo = PhotoImage(conv, size=IWII_RESOLUTION) + event.widget.create_image(0,0, anchor=CENTER, 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 + event.widget.master.photobooth_image = conv @widget_action -def send_to_iwii(event: Event): - raise NotImplementedError +def send_to_printer(event: Event) -> None: + if not event.widget.master.photobooth_image: + messagebox.showwarning( + title="Warning", + message="Nothing to print. Please take a picture first.", + parent=event.widget.master + ) + else: + with Serial( + "/dev/ttyUSB0", + baudrate=9600, + xonxoff=True, + rtscts=False + ) as printer: + printer.write(event.widget.master.photobooth_image.tobytes()) + + +@widget_action +def clear_preview(event: Event) -> None: + if event.widget.master.photobooth_image: + # remove the image data + event.widget.master.photobooth_image = None + # clear the canvas + event.widget.delete("all") + # remove the reference to the data formerly displayed on the canvas so it gets GC'd + del event.widget._displayed_photo + + +@widget_action +def quit_photobooth(event: Event) -> None: + event.widget.master.destroy() + event.widget.master.quit() def photobooth(): @@ -107,7 +156,9 @@ def photobooth(): preview = Canvas(master=app, height=IWII_RESOLUTION.height, width=IWII_RESOLUTION.width) preview.bind("", open_preview) - preview.bind("", send_to_iwii) + preview.bind("", send_to_printer) + preview.bind("c", clear_preview) + preview.bind("q", quit_photobooth) preview.focus_set() preview.pack(side="top") app.mainloop()