implement send_to_printer

add dithering to image preview
This commit is contained in:
Brennen Raimer
2025-03-22 12:48:46 -04:00
parent 837f2babcd
commit 1e13e9cd3c

View File

@@ -12,6 +12,7 @@ from pathlib import Path
from platform import system from platform import system
from PIL import ImageOps from PIL import ImageOps
from PIL.ImagePalette import ImagePalette
from PIL.ImageTk import Image, PhotoImage from PIL.ImageTk import Image, PhotoImage
from serial import Serial 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")) 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 # decorator to wrap widget actions in an error handler that displays exceptions in error message windows
@wraps(func) @wraps(func)
def wrapper(event: Event): def wrapper(event: Event):
@@ -43,13 +55,14 @@ def widget_action(func: Callable):
parent=event.widget.master parent=event.widget.master
) )
event.widget.master.quit() quit_photobooth(event)
exit(1)
return wrapper return wrapper
@widget_action @widget_action
def open_preview(event: Event): def open_preview(event: Event) -> None:
file = filedialog.askopenfilename( file = filedialog.askopenfilename(
parent=event.widget.master, parent=event.widget.master,
title="Open Image", title="Open Image",
@@ -64,17 +77,53 @@ def open_preview(event: Event):
elif Path(file).exists() and Path(file).is_file(): elif Path(file).exists() and Path(file).is_file():
# load image data and resize to fit the IWII resolution preserving aspect ratio # load image data and resize to fit the IWII resolution preserving aspect ratio
image_data = ImageOps.contain(Image.open(file), IWII_RESOLUTION) 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 # 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 # 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 # therefore we must create references to them to keep them around
event.widget._displayed_photo=photo event.widget._displayed_photo=photo
event.widget.master.photobooth_image = image_data event.widget.master.photobooth_image = conv
@widget_action @widget_action
def send_to_iwii(event: Event): def send_to_printer(event: Event) -> None:
raise NotImplementedError 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(): def photobooth():
@@ -107,7 +156,9 @@ def photobooth():
preview = Canvas(master=app, height=IWII_RESOLUTION.height, width=IWII_RESOLUTION.width) preview = Canvas(master=app, height=IWII_RESOLUTION.height, width=IWII_RESOLUTION.width)
preview.bind("<space>", open_preview) preview.bind("<space>", open_preview)
preview.bind("<Return>", send_to_iwii) preview.bind("<Return>", send_to_printer)
preview.bind("c", clear_preview)
preview.bind("q", quit_photobooth)
preview.focus_set() preview.focus_set()
preview.pack(side="top") preview.pack(side="top")
app.mainloop() app.mainloop()