How To Create Drawing App Like Paint Using Tkinter Python

How To Create Drawing App Like Paint Using Tkinter Python

Requirements Make sure you have the following before we start:

  1. Python is installed on your computer.
  2. A code editor or integrated development environment (IDE) like Visual Studio Code, PyCharm, or IDLE.
  3. The Tkinter library is usually included with Python installations.
  4. Install Pillow: pip install Pillow

Setting up the Project Let’s create a new directory and set up the files:

  1. Create a new directory named “DrawingApp”.
  2. Inside the “DrawingApp” directory, create a new Python script file named “drawing_app.py”.

Importing Required Libraries Our drawing app relies on Tkinter for the GUI. In “drawing_app.py”, import the necessary libraries:

import time
from tkinter import *
import tkinter as tk
from tkinter import filedialog
import PIL.ImageGrab as ImageGrab
from tkinter import colorchooser, messagebox

In the above code, we import the tkinter module and specifically import the colorchooser and filedialog submodules for color selection and file dialog functionality. We also import the ImageGrab module from the Pillow library for grabbing screenshots of the selected portion, which in our case is the canvas area.

Creating the Drawing Application

Next, let’s create the main application window. Add the following code snippet to the “drawing_app.py” file:

window = Tk()
window.title("Drawing App")

def select_brush():
pass

def change_brush_size(value):
pass

def select_brush_size():
pass

def select_eraser():
pass

def select_eraser_size():
pass

def select_brush_color():
pass

def select_background_color():
pass

def clear_canvas():
pass

def save_image():
pass

# Create the menu bar
menu_bar = Menu(window)

# Create the brush menu
brush_menu = Menu(menu_bar, tearoff=0)
brush_menu.add_command(label="Select Brush", command=select_brush)
brush_menu.add_command(label="Select Brush Size", command=select_brush_size)

# Create the eraser menu
eraser_menu = Menu(menu_bar, tearoff=0)
eraser_menu.add_command(label="Select Eraser", command=select_eraser)
eraser_menu.add_command(label="Select Eraser Size", command=select_eraser_size)

# Create the color menu
color_menu = Menu(menu_bar, tearoff=0)
color_menu.add_command(label="Select Drawing Color", command=select_brush_color)
color_menu.add_command(label="Select Background Color", command=select_background_color)

# Create the clear menu
clear_menu = Menu(menu_bar, tearoff=0)
clear_menu.add_command(label="Clear Canvas", command=clear_canvas)

# Create the save menu
save_menu = Menu(menu_bar, tearoff=0)
save_menu.add_command(label="Save Drawing", command=save_image)

# Add the menus to the menu bar
menu_bar.add_cascade(label="Brush", menu=brush_menu)
menu_bar.add_cascade(label="Eraser", menu=eraser_menu)
menu_bar.add_cascade(label="Color", menu=color_menu)
menu_bar.add_cascade(label="Clear", menu=clear_menu)
menu_bar.add_cascade(label="Save", menu=save_menu)

# Configure the menu bar
window.config(menu=menu_bar)

canvas = Canvas(window, width=800, height=480, bg="white")
canvas.pack()

window.mainloop()

Explanation of above code:

In the provided code, we start by creating a new Tkinter window using tk.Tk() and set its title to “Drawing App” with window.title().

Next, we define several empty functions (select_brush(), select_brush_size(), select_eraser(), select_eraser_size(), select_brush_color(), select_background_color(), clear_canvas(), save_image(), change_brush_size(), etc.) that will handle the functionality of each menu option. These functions will be implemented later to add the desired features.

Then, we create a menu bar using tk.Menu() and define separate menus for brush size, color, polygon, and save options using tk.Menu() as well. Each menu is filled with options using menu.add_command(). The command parameter specifies the function to execute when the option is selected.

Finally, we add the menus to the menu bar using menu_bar.add_cascade() and configure the window to display the menu bar using window.config(menu=menu_bar).

The application enters the main event loop with window.mainloop() to manage user interactions and keep the application running.

Menu Option Setup:

Now that we have set up the menu bar, let’s implement the functionality for each menu option. Modify the previously empty functions as follows:

Select Brush To allow users to select the brush, we’ve introduced a feature called “Select Brush” in the “Brush” menu. Additionally, we have implemented an empty function to handle this functionality, in the previous section.

By default, the brush will remain selected, but it will be necessary to choose the brush again after performing an erasing operation. To incorporate this functionality, please include the following code within the select_brush() function.

def select_brush():
global selected_color
selected_color = previous_color

Code to Select Brush Size:

def change_brush_size(value):
global brush_size
brush_size = int(value)

def select_brush_size():
new_window = Toplevel(window)
new_window.title("Select Brush Size")
new_window.geometry("400x100")

brush_size_label = Label(new_window, text="Brush Size")
brush_size_label.pack()

brush_size_slider = Scale(new_window, from_=1, to=25,
orient=HORIZONTAL, command=change_brush_size)
brush_size_slider.set(brush_size)
brush_size_slider.pack()

Explanation of the above code:

  1. def change_brush_size(value):: This defines a function change_brush_size that takes a value as an argument. This function is used to update the brush_size variable based on the value selected by the user on the brush size slider.
  2. global brush_size: This line declares that the brush_size variable being used inside the function is a global variable. This allows the function to modify the value of brush_size from outside its scope.
  3. brush_size = int(value): This line sets the brush_size variable to the integer value of the value argument. The value argument is the value selected by the user on the brush size slider.
  4. def select_brush_size():: This defines a function select_brush_size with no arguments. This function is used to create a new window (Toplevel) where the user can select the brush size using a slider.
  5. new_window = Toplevel(window): This creates a new window (Toplevel) that is a child of the main window (window).
  6. new_window.title("Select Brush Size"): This sets the title of the new window to “Select Brush Size”.
  7. new_window.geometry("400x100"): This sets the size of the new window to 400 pixels wide and 100 pixels high.
  8. brush_size_label = Label(new_window, text="Brush Size"): This creates a label (Label) widget in the new window with the text “Brush Size”.
  9. brush_size_label.pack(): This displays the brush size label in the new window.
  10. brush_size_slider = Scale(new_window, from_=1, to=25, orient=HORIZONTAL, command=change_brush_size): This creates a slider (Scale) widget in the new window with values ranging from 1 to 25. The command parameter specifies the function (change_brush_size) to call when the slider value changes.
  11. brush_size_slider.set(brush_size): This sets the initial value of the brush size slider to the current value of the brush_size variable.
  12. brush_size_slider.pack(): This displays the brush size slider in the new window.

Now, let’s write code to select Eraser and eraser size

To implement the functionality of the “Select Eraser” in the “Eraser” menu, insert the following code within the `select_eraser()` function.

def select_eraser():
global selected_color, previous_color
previous_color = selected_color
selected_color = background_color
def select_eraser_size():
new_window = Toplevel(window)
new_window.title("Select Eraser Size")
new_window.geometry("400x100")

eraser_size_label = Label(new_window, text="Eraser Size")
eraser_size_label.pack()

eraser_size_slider = Scale(new_window, from_=1,
to=20, orient=HORIZONTAL, command=change_brush_size)
eraser_size_slider.set(brush_size)
eraser_size_slider.pack()


Adding Multiple Color Options

To enable users to choose different colors for drawing, include the following code within the select_brush_color() function. This enhancement lets users select from a variety of colors using the “Select Drawing Color” option in the “Color” menu

def select_brush_color():
global selected_color, previous_color
color = colorchooser.askcolor(title="Select Color")[1]
if color:
selected_color = color
previous_color = color

Code to change background color:

def select_background_color():
global background_color, canvas
color = colorchooser.askcolor(title="Select Color")[1]
if color:
background_color = color
canvas.config(bg=background_color)

Clear the Canvas

There is an option for “Clear Canvas” in the “Clear” menu. To enhance this feature add the following code within `clear_canvas()` function.


def clear_canvas():
canvas.delete("all")

Save the image you have drawn:

def save_image():
file_path = filedialog.asksaveasfilename(defaultextension=".png")

time.sleep(0.1)

x1 = window.winfo_rootx() + canvas.winfo_x()
y1 = window.winfo_rooty() + canvas.winfo_x()

x2 = x1 + canvas.winfo_width()
y2 = y1 + canvas.winfo_height()

if file_path:
canvas.postscript(file=file_path, colormode='color')
# Format: crop(left, top, right, bottom)
ImageGrab.grab().crop((x1, y1, x2, y2)).save(file_path)

Explanation of the above code:

  1. file_path = filedialog.asksaveasfilename(defaultextension=".png"): Opens a dialog box for the user to choose a file path to save the image with a default extension of .png and assigns the chosen path to the file_path variable.
  2. time.sleep(0.1): Pauses the execution for 0.1 seconds. This is used to ensure that the window positions are correctly captured.
  3. x1 = window.winfo_rootx() + canvas.winfo_x(): Gets the x-coordinate of the top-left corner of the canvas relative to the screen by adding the window’s root x-coordinate to the canvas’s x-coordinate within the window.
  4. y1 = window.winfo_rooty() + canvas.winfo_x(): Gets the y-coordinate of the top-left corner of the canvas relative to the screen by adding the window’s root y-coordinate to the canvas’s y-coordinate within the window.
  5. x2 = x1 + canvas.winfo_width(): Calculates the x-coordinate of the bottom-right corner of the canvas by adding the canvas’s width to the x1 coordinate.
  6. y2 = y1 + canvas.winfo_height(): Calculates the y-coordinate of the bottom-right corner of the canvas by adding the canvas’s height to the y1 coordinate.
  7. if file_path:: Checks if a file path was chosen by the user.
  8. canvas.postscript(file=file_path, colormode='color'): Saves the canvas content as a PostScript file at the specified file_path with the specified colormode.
  9. ImageGrab.grab().crop((x1, y1, x2, y2)).save(file_path): Captures a screenshot of the specified area defined by (x1, y1, x2, y2) using ImageGrab, crops the image to that area, and then saves it at the file_path.

This function essentially saves the canvas content as an image file at the specified file path, including only the area covered by the canvas in the screenshot.

Implementing Drawing Functionality

# Default Values
is_drawing = False
last_x = 0
last_y = 0
brush_size = 2
selected_color = "black"
previous_color = "black"
background_color = "white"

def start_drawing(event):
global is_drawing, last_x, last_y
is_drawing = True
last_x = event.x
last_y = event.y

def draw(event):
global is_drawing, last_x, last_y
if is_drawing:
x, y = event.x, event.y
canvas.create_line(last_x, last_y, x, y,
width=brush_size, fill=selected_color)
last_x = x
last_y = y

def stop_drawing(event):
global is_drawing
is_drawing = False

def erase_drawing():
global selected_color
selected_color = "white"

Explanation of the above code:

  1. is_drawing = False: Initializes a variable is_drawing to False to indicate that the user is not currently drawing.
  2. last_x = 0: Initializes a variable last_x to 0 to store the last x-coordinate of the mouse cursor.
  3. last_y = 0: Initializes a variable last_y to 0 to store the last y-coordinate of the mouse cursor.
  4. brush_size = 2: Initializes a variable brush_size to 2 as the default brush size.
  5. selected_color = "black": Initializes a variable selected_color to "black" as the default drawing color.
  6. previous_color = "black": Initializes a variable previous_color to "black" to store the previous drawing color.
  7. background_color = "white": Initializes a variable background_color to "white" as the default background color.
  8. def start_drawing(event):: Defines a function start_drawing that takes an event as input when the user starts drawing.
  9. global is_drawing, last_x, last_y: Declares that the variables is_drawing, last_x, and last_y inside the function refer to the global variables with the same names.
  10. is_drawing = True: Sets is_drawing to True to indicate that the user has started drawing.
  11. last_x = event.x: Stores the current x-coordinate of the mouse cursor in last_x.
  12. last_y = event.y: Stores the current y-coordinate of the mouse cursor in last_y.
  13. def draw(event):: Defines a function draw that takes an event as input to draw lines when the user moves the mouse while drawing.
  14. if is_drawing:: Checks if is_drawing is True to determine if the user is currently drawing.
  15. x, y = event.x, event.y: Gets the current x and y coordinates of the mouse cursor.
  16. canvas.create_line(last_x, last_y, x, y, width=brush_size, fill=selected_color): Draws a line on the canvas from the last coordinates (last_x, last_y) to the current coordinates (x, y) with the specified brush_size and selected_color.
  17. last_x = x: Updates last_x to the current x-coordinate.
  18. last_y = y: Updates last_y to the current y-coordinate.
  19. def stop_drawing(event):: Defines a function stop_drawing that takes an event as input when the user stops drawing.
  20. global is_drawing: Declares that the variable is_drawing inside the function refers to the global variable with the same name.
  21. is_drawing = False: Sets is_drawing to False to indicate that the user has stopped drawing.
  22. def erase_drawing():: Defines a function erase_drawing to change the drawing color to white for erasing.
  23. global selected_color: Declares that the variable selected_color inside the function refers to the global variable with the same name.
  24. selected_color = "white": Sets selected_color to "white" to change the drawing color to white for erasing.

Event Handlers:

canvas.bind('<Button-1>', start_drawing)
canvas.bind('<B1-Motion>', draw)
canvas.bind('<ButtonRelease-1>', stop_drawing)

Whole Code:

import time
from tkinter import *
import tkinter as tk
from tkinter import filedialog, colorchooser, messagebox
import PIL.ImageGrab as ImageGrab

window = Tk()
window.title("Drawing App")

# Default Values
is_drawing = False
last_x = 0
last_y = 0
brush_size = 2
selected_color = "black"
previous_color = "black"
background_color = "white"

def select_brush():
global selected_color
selected_color = previous_color

def change_brush_size(value):
global brush_size
brush_size = int(value)

def select_brush_size():
new_window = Toplevel(window)
new_window.title("Select Brush Size")
new_window.geometry("400x100")

brush_size_label = Label(new_window, text="Brush Size")
brush_size_label.pack()

brush_size_slider = Scale(new_window, from_=1, to=25,
orient=HORIZONTAL, command=change_brush_size)
brush_size_slider.set(brush_size)
brush_size_slider.pack()

def select_eraser():
global selected_color, previous_color
previous_color = selected_color
selected_color = background_color

def select_eraser_size():
new_window = Toplevel(window)
new_window.title("Select Eraser Size")
new_window.geometry("400x100")

eraser_size_label = Label(new_window, text="Eraser Size")
eraser_size_label.pack()

eraser_size_slider = Scale(new_window, from_=1,
to=20, orient=HORIZONTAL, command=change_brush_size)
eraser_size_slider.set(brush_size)
eraser_size_slider.pack()

def select_brush_color():
global selected_color, previous_color
color = colorchooser.askcolor(title="Select Color")[1]
if color:
selected_color = color
previous_color = color

def select_background_color():
global background_color, canvas
color = colorchooser.askcolor(title="Select Color")[1]
if color:
background_color = color
canvas.config(bg=background_color)

def clear_canvas():
canvas.delete("all")

def save_image():
file_path = filedialog.asksaveasfilename(defaultextension=".png")

time.sleep(0.1)

x1 = window.winfo_rootx() + canvas.winfo_x()
y1 = window.winfo_rooty() + canvas.winfo_x()

x2 = x1 + canvas.winfo_width()
y2 = y1 + canvas.winfo_height()

if file_path:
canvas.postscript(file=file_path, colormode='color')
ImageGrab.grab().crop((x1, y1, x2, y2)).save(file_path)

# Create the menu bar
menu_bar = Menu(window)

# Create the brush menu
brush_menu = Menu(menu_bar, tearoff=0)
brush_menu.add_command(label="Select Brush", command=select_brush)
brush_menu.add_command(label="Select Brush Size", command=select_brush_size)

# Create the eraser menu
eraser_menu = Menu(menu_bar, tearoff=0)
eraser_menu.add_command(label="Select Eraser", command=select_eraser)
eraser_menu.add_command(label="Select Eraser Size", command=select_eraser_size)

# Create the color menu
color_menu = Menu(menu_bar, tearoff=0)
color_menu.add_command(label="Select Drawing Color", command=select_brush_color)
color_menu.add_command(label="Select Background Color", command=select_background_color)

# Create the clear menu
clear_menu = Menu(menu_bar, tearoff=0)
clear_menu.add_command(label="Clear Canvas", command=clear_canvas)

# Create the save menu
save_menu = Menu(menu_bar, tearoff=0)
save_menu.add_command(label="Save Drawing", command=save_image)

# Add the menus to the menu bar
menu_bar.add_cascade(label="Brush", menu=brush_menu)
menu_bar.add_cascade(label="Eraser", menu=eraser_menu)
menu_bar.add_cascade(label="Color", menu=color_menu)
menu_bar.add_cascade(label="Clear", menu=clear_menu)
menu_bar.add_cascade(label="Save", menu=save_menu)

# Configure the menu bar
window.config(menu=menu_bar)

def start_drawing(event):
global is_drawing, last_x, last_y
is_drawing = True
last_x = event.x
last_y = event.y

def draw(event):
global is_drawing, last_x, last_y
if is_drawing:
x, y = event.x, event.y
canvas.create_line(last_x, last_y, x, y, width=brush_size, fill=selected_color)
last_x = x
last_y = y

def stop_drawing(event):
global is_drawing
is_drawing = False

canvas = Canvas(window, width=800, height=480, bg="white")
canvas.pack()

# Event handlers
canvas.bind('<Button-1>', start_drawing)
canvas.bind('<B1-Motion>', draw)
canvas.bind('<ButtonRelease-1>', stop_drawing)

window.mainloop()

Output:

Conclusion:

In conclusion, using Tkinter to create a drawing application offers a versatile and beginner-friendly approach to developing graphical user interfaces (GUIs) in Python. Tkinter’s simplicity and robustness make it a suitable choice for projects that require interactive elements such as drawing tools.

Throughout this article, we explored various features and functionalities of Tkinter, including creating menus, handling user inputs, and managing colors and brush sizes. We learned how to implement these features to build a basic drawing application with essential drawing and erasing capabilities.

Tkinter’s ease of use allows developers to quickly prototype and develop applications with graphical interfaces. However, it may lack some advanced features and customization options found in other GUI libraries. For more complex applications requiring advanced graphics or animations, other libraries like Pygame or Kivy might be more suitable.

Overall, Tkinter provides a solid foundation for creating simple drawing applications and is an excellent tool for beginners to get started with GUI development in Python. With its rich set of widgets and straightforward approach, Tkinter enables developers to create functional and visually appealing applications with relative ease.

Author

Sona Avatar

Written by

Leave a Reply

Trending

CodeMagnet

Your Magnetic Resource, For Coding Brilliance

Programming Languages

Web Development

Data Science and Visualization

Career Section

<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-4205364944170772"
     crossorigin="anonymous"></script>