Does GUI programming sound interesting to you? If yes, letās build one from scratch!
This guide will help you get started with building GUI applications in Python with the help of Tkinter. There are a lot of GUI frameworks available but weāll be using Tkinter simply for the fact that itās easy to use, and the syntax remains the same no matter whether you are using Windows, Mac, or Ubuntu.
Today, we will be building a Photo Editor GUI with some basic functionalities. We start by importing Tkinter and other required libraries.
import tkinter as tk
from tkinter import ttk
from tkinter import filedialog, colorchooser
from PIL import Image, ImageTk, ImageFilter, ImageEnhance
After importing the very important step is to create a window wherein all that we want to create will be displayed, we do this with the following code:
root = tk.Tk() # To create a window
root.geometry("1100x600")
root.title("Photo Editor")
root.mainloop() # To hold the window
One can create multiple windows and may desire different widgets for each window. To distinguish which window should have which widgets, we write our code between these two lines ā window declaration and .mainloop()
.
Here I have defined the size of the window using root.geometry()
and added a title using root.title()
, running the above code should display a blank window like this:
Notice the top left side displays the title of our window
Now that we have our window screen ready, we can start adding widgets to it. In Tkinter, we can use .pack()
, .place()
, or .grid()
to place any widget.
.pack()
ā places the widgets in one of the 4 available positions i.e. top (default), bottom, left, or right
.place()
ā places the widgets using the x, y coordinates
.grid()
ā places the widget using row and column coordinates
Letās understand what Buttons, Labels, Entry, and Combobox are.
ttk.Button()
ā creates a clickable button which can perform certain action
ttk.Label()
ā creates a label which can display text or an image
ttk.Entry()
ā lets user enter information, can be text or numbers
ttk.Combobox()
ā used to display a list of options to choose from (basically a dropdown list)
Iām planning to add a couple of buttons for performing some actions like opening an image, applying filter, picking a border color, applying border, clearing all the applied filters and finally saving the image.
root = tk.Tk()
root.geometry("1150x600")
root.title("Photo Editor")
ttk.Button(root, text="Open Image").grid(row=0, column=0, padx=10, pady=10)
ttk.Label(root, text="Filter:").grid(row=0, column=1, padx=10, pady=10)
filter_options = ["Black and White", "Sepia"]
ttk.Combobox(root, values=filter_options).grid(row=0, column=2, padx=10, pady=10)
ttk.Button(root, text="Apply Filter").grid(row=0, column=3, padx=10, pady=10)
ttk.Label(root, text="Border Size:").grid(row=0, column=4, padx=10, pady=10)
ttk.Entry(root).grid(row=0, column=5, padx=10, pady=10)
ttk.Label(root, text="Border Color:").grid(row=0, column=6, padx=10, pady=10)
ttk.Button(root, text="Pick Color").grid(row=0, column=7, padx=10, pady=10)
ttk.Button(root, text="Add Border").grid(row=0, column=8, padx=10, pady=10)
ttk.Button(root, text="Clear All Filters").grid(row=0, column=9, padx=10, pady=10)
ttk.Button(root, text="Save Image").grid(row=0, column=10, padx=10, pady=10)
root.mainloop()
Iāve used ttk.Combobox()
which lets the user select filter to be applied and ttk.Entry() to lets the user decide what border size he/she wants to be applied.
Further you may notice how .grid()
lets you easily place your widgets and padding allows you to add space between the widgets, have a look at the following images!
Left Image (With Padding) Vs Right Image (Without Padding) So far this is how your window should look like. But wait none of the buttons are working because we havenāt defined any action to these buttons, letās add them!
filter_var = tk.StringVar()
border_size = tk.IntVar()
border_color = tk.StringVar()
original_image = None
edited_image = None
def process_image(root, filter_var, border_size, border_color):
global original_image, edited_image
file_path = filedialog.askopenfilename(filetypes=[("Image files", "*.png;*.jpg;*.jpeg;*.gif")])
if file_path:
image_path = file_path
original_image = Image.open(image_path)
edited_image = original_image.copy()
display_image(edited_image)
We first start by opening the image, we do this with the help of filedialog.askopenfilename() and then make a copy of the original image and save it as edited image (Donāt worry weāll keep updating this image!)
def apply_filter():
global edited_image
selected_filter = filter_var.get()
if edited_image and selected_filter == "Black and White":
edited_image = ImageEnhance.Color(edited_image).enhance(0.0)
elif edited_image and selected_filter == "Sepia":
edited_image = apply_sepia_filter(edited_image)
display_image(edited_image)
def apply_sepia_filter(image):
sepia = Image.new('RGB', image.size, (112, 66, 20))
sepia = Image.blend(image, sepia, 0.5)
return sepia
Now we start by applying filters, for now we will only have 2 filters ā Black & White and Sepia.
The .enhance()
in the apply_filter()
is used to enhance the image, here we set its value to 0.0 which means there should be no color enhancement in the image eventually making it a black and white image and saving it in the edited_image For applying the sepia filter, I have defined another function and assigned its RGB triplet i.e. (112, 66, 20).
border_size.set(20) # Default border size: 20
border_color.set("#000000") # Default border color: Black
def add_border():
global edited_image
if edited_image:
size = border_size.get()
color = border_color.get()
# Adding a border
new_width = edited_image.width + 2 * size
new_height = edited_image.height + 2 * size
bordered_image = Image.new("RGB", (new_width, new_height), color)
bordered_image.paste(edited_image, (size, size))
edited_image = bordered_image
display_image(edited_image)
def pick_border_color():
color = colorchooser.askcolor(title="Pick a Border Color", initialcolor=border_color.get())[1]
if color:
border_color.set(color)
Next up, I have added border to the image. The default border size is 20, and the color is Black. But what if you donāt want a black color? Yesss, you can change it. I have used colorchooser.askcolor() so when you click on the āPick Colorā button it will pop up a color selection window and you can select a color of your choice!
def clear_filters():
global original_image, edited_image
edited_image = original_image.copy()
display_image(edited_image)
def save_image():
global edited_image
save_path = filedialog.asksaveasfilename(defaultextension=".png",
filetypes=[("PNG files", "*.png"),
("JPEG files", "*.jpg;*.jpeg")])
if save_path:
edited_image.save(save_path)
Not happy with the edits? Iāve added a āClear Filterā button which will erase all the edits you have made so far and restores your original image. Lastly added a āSave Imageā button which will save your edited image in your system!
def display_image(edited_image):
global canvas
if edited_image: # Resizing image to fit the canvas
resized_image = edited_image.resize((500, 500), Image.ANTIALIAS)
photo = ImageTk.PhotoImage(resized_image)
# Updating canvas with the new image
canvas.config(width=photo.width(), height=photo.height())
canvas.create_image(0, 0, anchor=tk.NW, image=photo)
canvas.image = photo
Wait, where are we going to see all this? The answer is canvas, the display_image() helps in displaying the image on the canvas.
Now to link these functions to their respective buttons Iāve made used of the command argument. Hereās the fully functional code:
import tkinter as tk
from tkinter import ttk
from tkinter import filedialog, colorchooser
from PIL import Image, ImageTk, ImageFilter, ImageEnhance
def create_widgets(root, filter_var, border_size, border_color):
ttk.Button(root, text="Open Image", command=lambda: process_image(root, filter_var, border_size, border_color)).grid(row=0, column=0, padx=10, pady=10)
ttk.Label(root, text="Filter:").grid(row=0, column=1, padx=10, pady=10)
filter_options = ["Black and White", "Sepia"]
ttk.Combobox(root, textvariable=filter_var, values=filter_options).grid(row=0, column=2, padx=10, pady=10)
ttk.Button(root, text="Apply Filter", command=apply_filter).grid(row=0, column=3, padx=10, pady=10)
ttk.Label(root, text="Border Size:").grid(row=0, column=4, padx=10, pady=10)
ttk.Entry(root, textvariable=border_size).grid(row=0, column=5, padx=10, pady=10)
ttk.Label(root, text="Border Color:").grid(row=0, column=6, padx=10, pady=10)
ttk.Button(root, text="Pick Color", command=pick_border_color).grid(row=0, column=7, padx=10, pady=10)
ttk.Button(root, text="Add Border", command=add_border).grid(row=0, column=8, padx=10, pady=10)
ttk.Button(root, text="Clear All Filters", command=clear_filters).grid(row=0, column=9, padx=10, pady=10)
ttk.Button(root, text="Save Image", command=save_image).grid(row=0, column=10, padx=10, pady=10)
canvas = tk.Canvas(root, width=500, height=500)
canvas.grid(row=1, column=0, columnspan=11, padx=10, pady=10)
for i in range(11):
root.columnconfigure(i, weight=1)
root.rowconfigure(1, weight=1)
return canvas
def process_image(root, filter_var, border_size, border_color):
global original_image, edited_image
file_path = filedialog.askopenfilename(filetypes=[("Image files", "*.png;*.jpg;*.jpeg;*.gif")])
if file_path:
image_path = file_path
original_image = Image.open(image_path)
edited_image = original_image.copy()
display_image(edited_image)
def apply_filter():
global edited_image
selected_filter = filter_var.get()
if edited_image and selected_filter == "Black and White":
edited_image = ImageEnhance.Color(edited_image).enhance(0.0)
elif edited_image and selected_filter == "Sepia":
edited_image = apply_sepia_filter(edited_image)
display_image(edited_image)
def apply_sepia_filter(image):
sepia = Image.new('RGB', image.size, (112, 66, 20))
sepia = Image.blend(image, sepia, 0.5)
return sepia
def add_border():
global edited_image
if edited_image:
size = border_size.get()
color = border_color.get()
# Adding a border
new_width = edited_image.width + 2 * size
new_height = edited_image.height + 2 * size
bordered_image = Image.new("RGB", (new_width, new_height), color)
bordered_image.paste(edited_image, (size, size))
edited_image = bordered_image
display_image(edited_image)
def pick_border_color():
color = colorchooser.askcolor(title="Pick a Border Color", initialcolor=border_color.get())[1]
if color:
border_color.set(color)
def clear_filters():
global original_image, edited_image
edited_image = original_image.copy()
display_image(edited_image)
def save_image():
global edited_image
save_path = filedialog.asksaveasfilename(defaultextension=".png",
filetypes=[("PNG files", "*.png"),
("JPEG files", "*.jpg;*.jpeg")])
if save_path:
edited_image.save(save_path)
def display_image(edited_image):
global canvas
if edited_image: # Resizing image to fit the canvas
resized_image = edited_image.resize((500, 500), Image.ANTIALIAS)
photo = ImageTk.PhotoImage(resized_image)
# Updating canvas with the new image
canvas.config(width=photo.width(), height=photo.height())
canvas.create_image(0, 0, anchor=tk.NW, image=photo)
canvas.image = photo
root = tk.Tk()
root.geometry("1100x600")
root.title("Photo Editor")
filter_var = tk.StringVar()
border_size = tk.IntVar()
border_color = tk.StringVar()
border_size.set(20) # Default border size: 20
border_color.set("#000000") # Default border color: Black
canvas = create_widgets(root, filter_var, border_size, border_color)
original_image = None
edited_image = None
root.mainloop()
Thatās how I built this very basic Photo Editor in Python.
I hope this helped you get familiar with GUI!
Thanks for reading!