I am using Tkinter, Python and PIL to attempt to create a simple photo editing application.
My troubles currently are in the "add_text_function" all the way at the bottom. I am editing an image using PIL ImagdeDraw.Draw and .text to add text to the image.
However, when I try to load the edited image onto the canvas it does not work :( If I do a simple current_image.show() then it will show the edits made but in a another window. I'd like for it to update my canvas so everything takes place in the application. The image_container that I am trying to update can be found in the edit_screen function and is a global variable.
Your help would be greatly appreciated. Please find my code pasted below:
from tkinter import *
from tkinter.filedialog import askopenfilename, asksaveasfilename
from PIL import Image, ImageTk, ImageFont, ImageDraw, ImageFilter
# GLOBAL VARIABLES
# INITIALIZE WINDOW
home_window = Tk()
# INITIALIZE IMAGE STORAGE VARIABLE
all_files = list()
# INITIALIZE IMAGE BUTTON COUNT VARIABLE
add_images_button_count = 0
# INITIALIZE X AND Y POSITION FOR IMAGE PLACEMENT
x = 0
y = 0
# INITIALIZE MAX IMAGE COUNT VARIABLE
max_image = []
# CREATE CANVAS
canvas = Canvas(home_window, width=600, height=400, bg="white")
canvas.place(rely=0.1)
# CURRENT IMAGE
current_image = None
# IMAGE COUNTER
image_counter = 0
# IMAGE CONTAINER FOR CANVAS
image_container = None
# HOME SCREEN
def home_screen():
# REFERENCE GLOBAL WINDOW
global home_window
# SPECIFY WINDOW SIZE
home_window.geometry("600x450")
# SPECIFY WINDOW TITLE
home_window.title("Foto Editor")
# CREATES TOP FRAME AND DOESN'T ALLOW WINDOW SHRINKAGE
header_frame = Frame(home_window, width=600, height=50, bd=5, bg="grey")
header_frame.grid(row=0)
# BUTTONS
close_app = Button(header_frame, text="Close App", bg="white", fg="blue", command=home_window.destroy)
close_app.place(relx=0.015, rely=0.2, anchor="nw")
back = Button(header_frame, text="Back", bg="white", fg="blue", state="disabled")
back.place(relx=0.13, rely=0.2, anchor="nw")
add_images = Button(header_frame, text="Add Images", bg="white", fg="blue", command=add_button)
add_images.place(relx=0.45, rely=0.2, anchor="n")
clear = Button(header_frame, text="Clear", bg="white", fg="blue", command=clear_button)
clear.place(relx=0.55, rely=0.2, anchor="n")
next_step = Button(header_frame, text="Next Step", bg="blue", fg="white", command=next_step_button)
next_step.place(relx=0.985, rely=0.2, anchor="ne")
# RUN A NON RESIZEABLE TKINTER WINDOW
home_window.resizable(False, False)
home_window.mainloop()
# PHOTO EDITING SCREEN
def edit_screen():
# REFERENCE GLOBAL WINDOW
global home_window, current_image, image_counter, all_files, image_container, canvas
# CLEAR PREVIOUS WINDOW CANVAS
clear_screen()
# SPECIFY WINDOW SIZE
home_window.geometry("600x450")
# CREATES TOP FRAME AND DOESN'T ALLOW WINDOW SHRINKAGE
header_frame = Frame(home_window, width=600, height=50, bd=5, bg="grey")
header_frame.grid(row=0)
# BUTTONS
close_app = Button(header_frame, text="Close App", bg="white", fg="blue", command=home_window.destroy)
close_app.place(relx=0.015, rely=0.2, anchor="nw")
back = Button(header_frame, text="Back", bg="white", fg="blue", command=back_button)
back.place(relx=0.13, rely=0.2, anchor="nw")
add_text = Button(header_frame, text="Add Text", bg="white", fg="blue", command=add_text_button)
add_text.place(relx=0.4, rely=0.2, anchor="nw")
clear = Button(header_frame, text="Clear", bg="white", fg="blue")
clear.place(relx=0.605, rely=0.2, anchor="n")
save = Button(header_frame, text="Save", bg="blue", fg="white", command=save_button)
save.place(relx=0.985, rely=0.2, anchor="ne")
# LOGIC TO DISPLAY EACH PICTURE
# ACCESS CURRENT IMAGE
f = all_files[image_counter]
# OPEN AS IMAGE
current_image = Image.open(f)
# TK PHOTO
resized_img = ImageTk.PhotoImage(current_image.resize((500, 400)))
# PLACE IMAGE ON CANVAS
image_container = canvas.create_image(300, 210, image=resized_img)
# RUN A NON RESIZEABLE TKINTER WINDOW
home_window.resizable(False, False)
home_window.mainloop()
# BACK BUTTON
def back_button():
# REFERENCE GLOBAL VARIABLES
global canvas
# CLEAR IMAGES
for child in canvas.winfo_children():
child.destroy()
# LOAD HOME SCREEN
home_screen()
# ADD IMAGES BUTTON
def add_button():
# BUTTON COUNTER
global home_window, canvas, add_images_button_count, x, y, max_image, all_files
# SPECIFY FILE TYPES
filetypes = [('JPG Files', '*.jpg'), ('PNG Files', '*.png')]
# OPEN FILE EXPLORER AND ALLOW USER TO SELECT PHOTO
filename = askopenfilename(multiple=True, filetypes=filetypes)
# STORE ALL FILES
all_files.extend(filename)
# APPEND NEW FILE TO MAX IMAGE COUNTER
max_image.append(filename)
# CHECK IF MAX IMAGE AMOUNT OF 6 IS REACHED, INFORM USER
if len(max_image) > 6:
max_image_warning()
return
# FIRST CLICK
if add_images_button_count == 0:
# START POSITION FOR IMAGE
x = 0.1
y = 0.25
# INCREMENT BUTTON COUNTER
add_images_button_count += 1
# NOT FIRST CLICK
else:
# IMAGE POSITIONING LOGIC
# START NEW ROW AFTER THIRD COLUMN
if x == 0.7:
x = 0.1
y = 0.6
# ELSE CONTINUE TO INCREMENT COLUMN
else:
x += 0.3
# MULTIPLE IMAGES METHOD
for f in filename:
# OPEN AS IMAGE
img = Image.open(f)
# RESIZE IMAGE
img_resized = img.resize((100, 100))
# TK PHOTO
img = ImageTk.PhotoImage(img_resized)
# CREATE LABEL TO DISPLAY IMAGE
panel = Label(canvas)
# IMAGE POSITION
panel.place(relx=x, rely=y)
# ASSIGN IMAGE TO LABEL DIRECTLY
panel.image = img
# GARBAGE COLLECTION
panel['image'] = img
# CLEAR BUTTON
def clear_button():
# REFERENCE GLOBAL VARIABLE
global canvas, add_images_button_count, all_files
# RESET ALL FILES VARIABLE
all_files = []
# CLEAR IMAGES
for child in canvas.winfo_children():
child.destroy()
# RESET BUTTON COUNTER TO ZERO
add_images_button_count = 0
# AUTOMATICALLY CLEAR SCREEN
def clear_screen():
# REFERENCE GLOBAL VARIABLE
global canvas, add_images_button_count
# CLEAR IMAGES
for child in canvas.winfo_children():
child.destroy()
# RESET BUTTON COUNTER TO ZERO
add_images_button_count = 0
# NEXT STEP BUTTON
def next_step_button():
# REFERENCE GLOBAL VARIABLES
global home_window, all_files
# TRY TO NAVIGATE TO EDIT SCREEN
# try:
try:
edit_screen()
# CATCH EXCEPTION WHERE USER HASN'T SELECTED PHOTOS YET
except TypeError:
# PROMPT USER TO SELECT PHOTOS BEFORE CONTINUING
# INITIALIZE POP-UP WINDOW
pop = Toplevel(home_window)
# SET POP-UP WINDOW SIZE
pop.geometry("300x200")
# SET TITLE FOR WINDOW
pop.title("Warning")
# SET WARNING MESSAGE
Label(pop, text="Please select at least one photo to continue!").pack(pady=20)
# CREATE BUTTON
Button(pop, text="Understood", command=pop.destroy).pack(pady=40)
# REROUTE BACK TO HOME SCREEN
home_screen()
# CATCH EXCEPTION WHERE USER HASN'T SELECTED PHOTOS YET
except IndexError:
# PROMPT USER TO SELECT PHOTOS BEFORE CONTINUING
# INITIALIZE POP-UP WINDOW
pop = Toplevel(home_window)
# SET POP-UP WINDOW SIZE
pop.geometry("300x200")
# SET TITLE FOR WINDOW
pop.title("Warning")
# SET WARNING MESSAGE
Label(pop, text="Please select at least one photo to continue!").pack(pady=20)
# CREATE BUTTON
Button(pop, text="Understood", command=pop.destroy).pack(pady=40)
# REROUTE BACK TO HOME SCREEN
home_screen()
# SAVE BUTTON
def save_button():
# SPECIFY FILE TYPES
filetypes = [('JPG Files', '*.jpg'), ('PNG Files', '*.png')]
# STORE SAVED NAME
file = asksaveasfilename(defaultextension=".jpg", filetypes=filetypes)
# SAVE IMAGE
current_image.save(file)
# MAX IMAGE WARNING
def max_image_warning():
global home_window
pop = Toplevel(home_window)
pop.geometry("300x200")
pop.title("Warning")
Label(pop, text="Max amount of images reached!").pack(pady=20)
Button(pop, text="Understood", command=pop.destroy).pack(pady=40)
# ADD TEXT
def add_text_button():
# INITIALIZE POP-UP WINDOW
pop = Toplevel(home_window)
# SET POP-UP WINDOW SIZE
pop.geometry("400x375")
# SET TITLE FOR WINDOW
pop.title("Add Text")
# OBTAIN X POSITION
Label(pop, text="X position:").grid(row=0, column=0, padx=50, pady=25)
x_text_position = Entry(pop, width=10)
x_text_position.grid(row=0, column=1)
# OBTAIN Y POSITION
Label(pop, text="Y position:").grid(row=1, column=0, padx=25)
y_text_position = Entry(pop, width=10)
y_text_position.grid(row=1, column=1)
# OBTAIN DESIRED TEXT
Label(pop, text="Text:").grid(row=2, column=0, padx=20, pady=30)
text = Entry(pop, width=25)
text.grid(row=2, column=1)
# OBTAIN DESIRED TEXT COLOR
# DATATYPE OF MENU TEXT
variable = StringVar(home_window)
# DEFAULT VALUE
variable.set("Black")
Label(pop, text="Text Color").grid(row=3, column=0)
text_color = OptionMenu(pop, variable, "Red", "Blue", "Green", "Yellow", "Orange")
text_color.grid(row=3, column=1)
# OBTAIN DESIRED FONT SIZE
# SET SCALE DATATYPE
v1 = IntVar()
Label(pop, text="Text Size").grid(row=4, column=0)
text_size = Scale(pop, variable=v1, from_=1, to=100, orient=HORIZONTAL)
text_size.grid(row=4, column=1, pady=25)
# SAVE BUTTON
save = Button(pop, text="Impose Edits", width=10,
command=lambda : add_text_function(pop=pop,
x_text_position=x_text_position,
y_text_position=y_text_position,
text=text,
text_color=variable,
text_size=text_size))
save.grid(row=5, columnspan=3, pady=40)
def add_text_function(pop, x_text_position, y_text_position, text, text_color, text_size):
# REFERENCE GLOBAL VARIABLES
global current_image, canvas, image_container
# ENABLE IMAGE EDITING
edited_image = ImageDraw.Draw(current_image)
# CUSTOM FONT SIZE
myFont = ImageFont.truetype(font="arial.ttf", size=text_size.get())
# ADD TEXT IMAGE
final_image = edited_image.text((int(x_text_position.get()), int(y_text_position.get())), text.get(), fill=text_color.get(), font=myFont)
# CLOSE POP WINDOW
pop.destroy()
# UPDATE CANVAS WITH FINAL IMAGE
canvas.itemconfigure(image_container, image=final_image)
# THIS METHOD WORKS BUT WOULD PREFER NOT TO USE IT
current_image.show()
# RUN HOME SCREEN
home_screen()