DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Related

  • I Was Tired of Flying Blind With AI Agents, So I Built AgentDog
  • Prompt Injection Is Real, So I Built a Python Firewall for LLM Pipelines
  • Building Threat Intelligence Pipelines Using Python, APIs, and Elasticsearch
  • From Indicators to Insights: Automating IOC Enrichment Using Python and Threat Feeds

Trending

  • Advanced Error Handling and Retry Patterns in Enterprise REST Integrations
  • Give Your AI Assistant Long-Term Memory With perag
  • DZone's Article Submission Guidelines
  • Persistent Memory for AI Agents Using LangChain's Deep Agents
  1. DZone
  2. Coding
  3. Languages
  4. PySimpleGUI: How to Draw Shapes on an Image With a GUI

PySimpleGUI: How to Draw Shapes on an Image With a GUI

Let's add some flair to that image.

By 
Mike Driscoll user avatar
Mike Driscoll
·
Feb. 27, 21 · Tutorial
Likes (2)
Comment
Save
Tweet
Share
10.0K Views

Join the DZone community and get the full member experience.

Join For Free

Drawing shapes on images is neat. But wouldn’t it be nice if you could draw the shapes interactively? That is the point of this tutorial. You will create a user interface using PySimpleGUI to allow you to draw shapes on images!

The purpose of this user interface is to show you how you can make a GUI that wraps some of the shapes that Pillow supports. The GUI won’t support all of them though. In fact, this GUI only supports the following:

  • ellipse
  • rectangle

The reason for this is that these two shapes take the same arguments. You can take on the challenge yourself to add the other shapes to the GUI!

When your GUI is finished, it will look like this:

PySimpleGUI Image Shape GUI
PySimpleGUI Image Shape GUI

Getting Started

You will need PySimpleGUI and Pillow to be able to follow along with this tutorial. Here’s how to install them with pip:

python3 -m pip install PySimpleGUI pillow

Now you’re ready to create your GUI!

Creating the GUI

Now open up your Python editor and create a new file named drawing_gui.py. Then add this code to your new file:

Python
 




x
158


 
1
# drawing_gui.py
2
 
          
3
import io
4
import os
5
import PySimpleGUI as sg
6
import shutil
7
import tempfile
8
 
          
9
from PIL import Image, ImageColor, ImageDraw
10
 
          
11
file_types = [("JPEG (*.jpg)", "*.jpg"), ("All files (*.*)", "*.*")]
12
tmp_file = tempfile.NamedTemporaryFile(suffix=".jpg").name
13
 
          
14
 
          
15
def get_value(key, values):
16
    value = values[key]
17
    if value.isdigit():
18
        return int(value)
19
    return 0
20
 
          
21
 
          
22
def apply_drawing(values, window):
23
    image_file = values["-FILENAME-"]
24
    shape = values["-SHAPES-"]
25
    begin_x = get_value("-BEGIN_X-", values)
26
    begin_y = get_value("-BEGIN_Y-", values)
27
    end_x = get_value("-END_X-", values)
28
    end_y = get_value("-END_Y-", values)
29
    width = get_value("-WIDTH-", values)
30
    fill_color = values["-FILL_COLOR-"]
31
    outline_color = values["-OUTLINE_COLOR-"]
32
 
          
33
    if os.path.exists(image_file):
34
        shutil.copy(image_file, tmp_file)
35
        image = Image.open(tmp_file)
36
        image.thumbnail((400, 400))
37
        draw = ImageDraw.Draw(image)
38
        if shape == "Ellipse":
39
            draw.ellipse(
40
                (begin_x, begin_y, end_x, end_y),
41
                fill=fill_color,
42
                width=width,
43
                outline=outline_color,
44
            )
45
        elif shape == "Rectangle":
46
            draw.rectangle(
47
                (begin_x, begin_y, end_x, end_y),
48
                fill=fill_color,
49
                width=width,
50
                outline=outline_color,
51
            )
52
        image.save(tmp_file)
53
 
          
54
        bio = io.BytesIO()
55
        image.save(bio, format="PNG")
56
        window["-IMAGE-"].update(data=bio.getvalue())
57
 
          
58
 
          
59
def create_coords_elements(label, begin_x, begin_y, key1, key2):
60
    return [
61
        sg.Text(label),
62
        sg.Input(begin_x, size=(5, 1), key=key1, enable_events=True),
63
        sg.Input(begin_y, size=(5, 1), key=key2, enable_events=True),
64
    ]
65
 
          
66
 
          
67
def save_image(values):
68
    save_filename = sg.popup_get_file(
69
        "File", file_types=file_types, save_as=True, no_window=True
70
    )
71
    if save_filename == values["-FILENAME-"]:
72
        sg.popup_error(
73
            "You are not allowed to overwrite the original image!")
74
    else:
75
        if save_filename:
76
            shutil.copy(tmp_file, save_filename)
77
            sg.popup(f"Saved: {save_filename}")
78
 
          
79
 
          
80
def main():
81
    colors = list(ImageColor.colormap.keys())
82
    layout = [
83
        [sg.Image(key="-IMAGE-")],
84
        [
85
            sg.Text("Image File"),
86
            sg.Input(
87
                size=(25, 1), key="-FILENAME-"
88
            ),
89
            sg.FileBrowse(file_types=file_types),
90
            sg.Button("Load Image"),
91
        ],
92
        [
93
            sg.Text("Shapes"),
94
            sg.Combo(
95
                ["Ellipse", "Rectangle"],
96
                default_value="Ellipse",
97
                key="-SHAPES-",
98
                enable_events=True,
99
                readonly=True,
100
            ),
101
        ],
102
        [
103
            *create_coords_elements(
104
                "Begin Coords", "10", "10", "-BEGIN_X-", "-BEGIN_Y-"
105
            ),
106
            *create_coords_elements(
107
                "End Coords", "100", "100", "-END_X-", "-END_Y-"
108
            ),
109
        ],
110
        [
111
            sg.Text("Fill"),
112
            sg.Combo(
113
                colors,
114
                default_value=colors[0],
115
                key="-FILL_COLOR-",
116
                enable_events=True,
117
                readonly=True
118
            ),
119
            sg.Text("Outline"),
120
            sg.Combo(
121
                colors,
122
                default_value=colors[0],
123
                key="-OUTLINE_COLOR-",
124
                enable_events=True,
125
                readonly=True
126
            ),
127
            sg.Text("Width"),
128
            sg.Input("3", size=(5, 1), key="-WIDTH-", enable_events=True),
129
        ],
130
        [sg.Button("Save")],
131
    ]
132
 
          
133
    window = sg.Window("Drawing GUI", layout, size=(450, 500))
134
 
          
135
    events = [
136
        "Load Image",
137
        "-BEGIN_X-",
138
        "-BEGIN_Y-",
139
        "-END_X-",
140
        "-END_Y-",
141
        "-FILL_COLOR-",
142
        "-OUTLINE_COLOR-",
143
        "-WIDTH-",
144
        "-SHAPES-",
145
    ]
146
    while True:
147
        event, values = window.read()
148
        if event == "Exit" or event == sg.WIN_CLOSED:
149
            break
150
        if event in events:
151
            apply_drawing(values, window)
152
        if event == "Save" and values["-FILENAME-"]:
153
            save_image(values)
154
    window.close()
155
 
          
156
 
          
157
if __name__ == "__main__":
158
    main()



That’s a bunch of code! To make things easier, you will go over this code in smaller chunks.

The first chunk is the code at the top of the file:

Python
 




xxxxxxxxxx
1
12


 
1
# drawing_gui.py
2
 
          
3
import io
4
import os
5
import PySimpleGUI as sg
6
import shutil
7
import tempfile
8
 
          
9
from PIL import Image, ImageColor, ImageDraw
10
 
          
11
file_types = [("JPEG (*.jpg)", "*.jpg"), ("All files (*.*)", "*.*")]
12
tmp_file = tempfile.NamedTemporaryFile(suffix=".jpg").name



These lines of code define the imports of the packages and modules that you need. It also sets up two variables:

  • file_types – Which you will use for browsing and saving your images
  • tmp_file – A temporary file that is created to save your intermediate image file

Now you can take a look at your first function:

Python
 




xxxxxxxxxx
1


 
1
def get_value(key, values):
2
    value = values[key]
3
    if value.is_digit():
4
        return int(value)
5
    return 0



This function is used to convert strings to integers. If you enter an alphabetical character or a special character, it will cause this code to throw an error. Feel free to catch those kinds of things here or implement a filter to prevent users from entering anything other than integers.

If the user empties the Element of its contents, you force it to return a zero. This allows the user interface to continue to function. Another improvement that you could add here is to take the image bounds into account. You could make it so that the user cannot enter negative numbers or numbers that are larger than the image.

The next function is a meaty one:

Python
 




xxxxxxxxxx
1
35


 
1
def apply_drawing(values, window):
2
    image_file = values["-FILENAME-"]
3
    shape = values["-SHAPES-"]
4
    begin_x = get_value("-BEGIN_X-", values)
5
    begin_y = get_value("-BEGIN_Y-", values)
6
    end_x = get_value("-END_X-", values)
7
    end_y = get_value("-END_Y-", values)
8
    width = get_value("-WIDTH-", values)
9
    fill_color = values["-FILL_COLOR-"]
10
    outline_color = values["-OUTLINE_COLOR-"]
11
 
          
12
    if image_file and os.path.exists(image_file):
13
        shutil.copy(image_file, tmp_file)
14
        image = Image.open(tmp_file)
15
        image.thumbnail((400, 400))
16
        draw = ImageDraw.Draw(image)
17
        if shape == "Ellipse":
18
            draw.ellipse(
19
                (begin_x, begin_y, end_x, end_y),
20
                fill=fill_color,
21
                width=width,
22
                outline=outline_color,
23
            )
24
        elif shape == "Rectangle":
25
            draw.rectangle(
26
                (begin_x, begin_y, end_x, end_y),
27
                fill=fill_color,
28
                width=width,
29
                outline=outline_color,
30
            )
31
        image.save(tmp_file)
32
 
          
33
        bio = io.BytesIO()
34
        image.save(bio, format="PNG")
35
        window["-IMAGE-"].update(data=bio.getvalue())



This code is used for creating the two shapes you want to draw on your image. It gets all the various settings that you need to create an ellipse or a rectangle. The settings that you can change are:

  • The image filename
  • The shape combobox
  • The beginning coordinates (-BEGIN_X-, -BEGIN_Y-)
  • The ending coordinates (-END_X-, -END_Y-)
  • The width of the outline
  • The fill color to be used (uses color names)
  • The outline color (uses color names)

If the user has opened up an image, then your code will automatically draw a shape using the default settings. When you edit any of those settings, this function will get called and the image will update.

Note: The drawing is applied to a thumbnail version of the image rather than a copy of the image. This is done to keep the image visible in your GUI. Many photos have a higher resolution than can be shown on a typical monitor.

The next function to look at is called create_coords_elements():

Python
 




x
6


 
1
def create_coords_elements(label, begin_x, begin_y, key1, key2):
2
    return [
3
        sg.Text(label),
4
        sg.Input(begin_x, size=(5, 1), key=key1, enable_events=True),
5
        sg.Input(begin_y, size=(5, 1), key=key2, enable_events=True),
6
    ]



This function returns a Python list that contains three Elements in it. One label (sg.Text) and two text boxes (sg.Input). Because these elements are in a single list, they will be added as a horizontal row to your user interface.

Now you’re ready to go on to the save_image() function:

Python
 




x


 
1
def save_image(values):
2
    save_filename = sg.popup_get_file(
3
        "File", file_types=file_types, save_as=True, no_window=True
4
    )
5
    if save_filename == values["-FILENAME-"]:
6
        sg.popup_error(
7
            "You are not allowed to overwrite the original image!")
8
    else:
9
        if save_filename:
10
            shutil.copy(tmp_file, save_filename)
11
            sg.popup(f"Saved: {save_filename}")



This function asks the user where to save your file. It will also prevent the user from trying to overwrite the original image.

The last function is your main() one:

Python
 




x
54


 
1
def main():
2
    colors = list(ImageColor.colormap.keys())
3
    layout = [
4
        [sg.Image(key="-IMAGE-")],
5
        [
6
            sg.Text("Image File"),
7
            sg.Input(
8
                size=(25, 1), key="-FILENAME-"
9
            ),
10
            sg.FileBrowse(file_types=file_types),
11
            sg.Button("Load Image"),
12
        ],
13
        [
14
            sg.Text("Shapes"),
15
            sg.Combo(
16
                ["Ellipse", "Rectangle"],
17
                default_value="Ellipse",
18
                key="-SHAPES-",
19
                enable_events=True,
20
                readonly=True,
21
            ),
22
        ],
23
        [
24
            *create_coords_elements(
25
                "Begin Coords", "10", "10", "-BEGIN_X-", "-BEGIN_Y-"
26
            ),
27
            *create_coords_elements(
28
                "End Coords", "100", "100", "-END_X-", "-END_Y-"
29
            ),
30
        ],
31
        [
32
            sg.Text("Fill"),
33
            sg.Combo(
34
                colors,
35
                default_value=colors[0],
36
                key="-FILL_COLOR-",
37
                enable_events=True,
38
                readonly=True
39
            ),
40
            sg.Text("Outline"),
41
            sg.Combo(
42
                colors,
43
                default_value=colors[0],
44
                key="-OUTLINE_COLOR-",
45
                enable_events=True,
46
                readonly=True
47
            ),
48
            sg.Text("Width"),
49
            sg.Input("3", size=(5, 1), key="-WIDTH-", enable_events=True),
50
        ],
51
        [sg.Button("Save")],
52
    ]
53
 
          
54
    window = sg.Window("Drawing GUI", layout, size=(450, 500))



This is one of the most complex user interfaces you have written. It adds lots of Elements in rows. You can also see that you are using a special syntax to extract items from a list:

  • *create_coords_elements()

When you call create_coords_elements(), it returns a list. But you want both the beginning and ending coordinates on the same line. So you extract the elements from the list.

This little code example illustrates what is happening:

Python
 




x
54
9


 
1
>>> def get_elements():
2
...     return ["element_one", "element_two"]
3
... 
4
>>> [*get_elements()]
5
['element_one', 'element_two']



If you don’t use the asterisk, you will end up with a nested list instead of a list that has three elements in it.

Here are the last few lines of code from the main() function:

Python
 




x


 
1
    events = [
2
        "Load Image",
3
        "-BEGIN_X-",
4
        "-BEGIN_Y-",
5
        "-END_X-",
6
        "-END_Y-",
7
        "-FILL_COLOR-",
8
        "-OUTLINE_COLOR-",
9
        "-WIDTH-",
10
        "-SHAPES-",
11
    ]
12
    while True:
13
        event, values = window.read()
14
        if event == "Exit" or event == sg.WIN_CLOSED:
15
            break
16
        if event in events:
17
            apply_drawing(values, window)
18
        if event == "Save" and values["-FILENAME-"]:
19
            save_image(values)
20
    window.close()
21
 
          
22
if __name__ == "__main__":
23
    main()



The first line defines the events that you use to know when to update the drawing. The last conditional will save the image where the user chooses. Give your new GUI a try. Then you can start planning how you will improve it!

Wrapping Up

Now you have a handy tool for testing out a couple of Pillow’s drawing functions. You can make this GUI even better by adding some of Pillow’s other shapes to the mix. You could do that by adding a tabbed interface with the different options under different tabs, for example.

Give it a try and see what you can come up with!

Python (language)

Published at DZone with permission of Mike Driscoll. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • I Was Tired of Flying Blind With AI Agents, So I Built AgentDog
  • Prompt Injection Is Real, So I Built a Python Firewall for LLM Pipelines
  • Building Threat Intelligence Pipelines Using Python, APIs, and Elasticsearch
  • From Indicators to Insights: Automating IOC Enrichment Using Python and Threat Feeds

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

  • RSS
  • X
  • Facebook

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 215
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

  • RSS
  • X
  • Facebook