Using python and the tkinter library, I want to create a program that will open an image, then draw a completely black layer on top of it, and using mouse click + movement, delete parts of that black layer (think of it as a fog-of-war in a table top game) or restore parts of the black layer.
When I want to delete part of the black layer, I basically draw a transparent shape, and when I restore the black layer, I just draw a fully black shape. Here is my current implementation:
import tkinter as tk
from tkinter import filedialog
from PIL import Image, ImageTk, ImageDraw
class ImageEraserApp:
def __init__(self, root):
self.root = root
self.root.title("Image Eraser App")
# Open image file dialog
self.image_path = filedialog.askopenfilename(
filetypes=[("Image files", "*.jpg *.jpeg *.png *.bmp *.gif")]
)
if not self.image_path:
self.root.quit()
# Load original image and create initial resized image
self.original_image = Image.open(self.image_path)
self.resized_image = self.original_image.copy()
self.tk_image = ImageTk.PhotoImage(self.resized_image)
# Create canvas to display image
self.canvas = tk.Canvas(self.root)
self.canvas.pack(fill=tk.BOTH, expand=True)
self.canvas_image = self.canvas.create_image(0, 0, anchor=tk.NW, image=self.tk_image)
# Initialize black layer for erasing and painting
self.black_layer = Image.new('RGBA', self.original_image.size, (0, 0, 0, 255))
self.layer_draw = ImageDraw.Draw(self.black_layer)
self.tk_black_layer = ImageTk.PhotoImage(self.black_layer)
# Eraser size
self.eraser_size = 20
# Bind mouse events
self.canvas.bind("<B1-Motion>", self.erase)
self.canvas.bind("<B3-Motion>", self.paint_black)
self.canvas.bind("<MouseWheel>", self.adjust_eraser_size)
# Display initial black layer
self.black_layer_id = self.canvas.create_image(0, 0, anchor=tk.NW, image=self.tk_black_layer)
def adjust_eraser_size(self, event):
# Adjust the eraser size based on mouse wheel scroll
if event.delta > 0:
self.eraser_size = min(100, self.eraser_size + 20)
elif event.delta < 0:
self.eraser_size = max(20, self.eraser_size - 20)
def erase(self, event):
# Draw on the black layer to "erase" it
self.layer_draw.ellipse([
(event.x - self.eraser_size, event.y - self.eraser_size),
(event.x + self.eraser_size, event.y + self.eraser_size)
], fill=(0, 0, 0, 0))
# Update the black layer on the canvas
self.tk_black_layer = ImageTk.PhotoImage(self.black_layer)
self.canvas.itemconfig(self.black_layer_id, image=self.tk_black_layer)
def paint_black(self, event):
# Draw on the black layer to "paint" it
self.layer_draw.ellipse([
(event.x - self.eraser_size, event.y - self.eraser_size),
(event.x + self.eraser_size, event.y + self.eraser_size)
], fill=(0, 0, 0, 255))
# Update the black layer on the canvas
self.tk_black_layer = ImageTk.PhotoImage(self.black_layer)
self.canvas.itemconfig(self.black_layer_id, image=self.tk_black_layer)
if __name__ == "__main__":
root = tk.Tk()
app = ImageEraserApp(root)
root.mainloop()
Now as the number of the shapes I draw grows, the app becomes more and more unresponsive (if I try to do some resizing operations/zoom-in/zoom-out on top of this, it gets even worse (easiest to notice when loading a very large image)
My question is how can I optimize this? Is the way I am trying to implement it even the correct way to solve this kind of problem?