Build a GUI from Scratch: A Step-by-Step Guide
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:
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!
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 !!