How to Create a Drawing App In Python Tkinter
In this Python tutorial, we will see how to create a drawing application using the Tkinter library for the graphical user interface.
This application provides a graphical user interface (GUI) where users can draw various shapes on a canvas, choose colors, and save their drawings.
The "Choose Color" button opens a color chooser dialog, allowing the user to select a drawing color.
The application supports three types of shapes: Freehand, Rectangle, and Oval, which users can select using dedicated buttons.
Mouse listeners capture user interactions for drawing shapes on the canvas, with the PaintPanel and the ControlPanel classes handling mouse events for drawing shapes.
The "Clear Canvas" button clears all drawn shapes from the canvas.
The "Save as Image" button allows users to save the current drawing as a PNG image file, using a file chooser dialog to select the destination for saving the image.
What We Are Gonna Use In This Project:
- Python Programming Language.- Tkinter for GUI.
- VS Code Editor.
- VS Code Editor.
Project Source Code:
import tkinter as tk
from tkinter import colorchooser, filedialog, messagebox
from enum import Enum
from io import BytesIO
# pip install Pillow
from PIL import Image, ImageDraw, ImageTk, ImageGrab
# Enum to represent different types of shapes
class ShapeType(Enum):
FREEHAND = 1
RECTANGLE = 2
OVAL = 3
LINE = 4
# Main application class
class DrawingApp:
def __init__(self, root):
# Initialize the DrawingApp.
self.root = root
self.root.title("Drawing App")
self.root.geometry("800x600")
# Create a PaintPanel and ControlPanel
self.paint_panel = PaintPanel(root)
self.paint_panel.pack(fill=tk.BOTH, expand=True)
self.controle_panel = ControlPanel(root, self.paint_panel)
self.controle_panel.pack(fill=tk.X)
# Canvas for drawing
class PaintPanel(tk.Canvas):
def __init__(self, master, *args, **kwargs):
# Initialize the PaintPanel.
super().__init__(master, *args, **kwargs)
# Initialize variables
self.image = Image.new("RGB", (800, 600), "white")
self.shapes = []
self.current_color = "black"
self.current_shape_type = ShapeType.FREEHAND
self.background_color = "white"
# Bind events to methods
self.bind("<Button-1>", self.start_drawing)
self.bind("<B1-Motion>", self.draw)
# Start drawing a shape
def start_drawing(self, event):
start_point = (event.x, event.y)
shape = self.create_shape(self.current_shape_type, self.current_color,
start_point)
self.shapes.append(shape)
# Continue drawing a shape
def draw(self, event):
if self.shapes:
current_shape = self.shapes[-1]
current_shape.add_point((event.x, event.y))
self.update_canvas()
# Create a shape based on shape type
def create_shape(self, shape_type, color, start_point):
if shape_type == ShapeType.FREEHAND:
return FreehandShape(color, start_point)
elif shape_type == ShapeType.RECTANGLE:
return RectangleShape(color, start_point)
elif shape_type == ShapeType.OVAL:
return OvalShape(color, start_point)
elif shape_type == ShapeType.LINE:
return LineShape(color, start_point)
# Update the canvas with shapes
def update_canvas(self):
self.delete("all")
self.create_rectangle(0, 0, self.winfo_width(), self.winfo_height(),
fill=self.background_color)
for shape in self.shapes:
shape.draw(self)
self.photo = ImageTk.PhotoImage(self.image)
# Change the background color
def change_background_color(self, color):
self.background_color = color
self.update_canvas()
# Save the drawing as an image
def save_drawing(self):
# problem caused by the video recorder
file_path = filedialog.asksaveasfilename(defaultextension=".png",
filetypes=[("PNG Files", "*.png")])
x = self.master.winfo_rootx() + self.winfo_x()
y = self.master.winfo_rooty() + self.winfo_y()
x1 = x + self.winfo_width()
y1 = y + self.winfo_height()
ImageGrab.grab(bbox=(x, y, x1, y1)).save(file_path)
print(f"Drawing saved as {file_path}")
# Control panel with buttons for various actions
class ControlPanel(tk.Frame):
def __init__(self, master, paint_panel, *args, **kwargs):
super().__init__(master, *args, **kwargs)
self.paint_panel = paint_panel
# Create buttons and pack them
self.clear_button = self.create_button("Clear Canvas", "red",
self.clear_canvas)
self.color_button = self.create_button("Choose Color", "#3f51b5",
self.choose_color)
self.freehand_button = self.create_button("Freehand", "#D35300",
lambda : self.set_shape_type(ShapeType.FREEHAND))
self.rectangle_button = self.create_button("Rectangle", "#8e44ad",
lambda : self.set_shape_type(ShapeType.RECTANGLE))
self.oval_button = self.create_button("Oval", "#27ae60",
lambda : self.set_shape_type(ShapeType.OVAL))
self.line_button = self.create_button("Line", "#FFC107",
lambda : self.set_shape_type(ShapeType.LINE))
self.bg_color_button = self.create_button("Change BG Color", "#604d8b",
self.change_bg_color)
self.save_button = self.create_button("Save as image", "green",
self.paint_panel.save_drawing)
self.pack_buttons()
# Create a button with specified properties
def create_button(self, text, bg_color, command):
button = tk.Button(self, text=text, bg=bg_color, fg="white", command=command)
button.configure(font = ("Arial", 10, "bold"))
button.pack(side = tk.LEFT, padx = 5)
return button
# Pack all buttons
def pack_buttons(self):
buttons = [
self.clear_button,
self.color_button,
self.freehand_button,
self.rectangle_button,
self.oval_button,
self.line_button,
self.bg_color_button,
self.save_button
]
for button in buttons:
button.pack(side = tk.LEFT, padx = 5)
# Change the background color
def change_bg_color(self):
color = colorchooser.askcolor(title="Choose Background Color")[1]
if color:
self.paint_panel.change_background_color(color)
# Choose a drawing color
def choose_color(self):
color = colorchooser.askcolor(title = "Choose Color")[1]
if color:
self.paint_panel.current_color = color
# Set the current shape type
def set_shape_type(self, shape_type):
self.paint_panel.current_shape_type = shape_type
# Clear the canvas
def clear_canvas(self):
self.paint_panel.shapes = []
self.paint_panel.update_canvas()
# Base class for different shapes
class Shape:
def __init__(self, color, start_point):
"""
Initialize a shape.
Args:
color: The color of the shape.
start_point: The starting point of the shape.
"""
self.color = color
self.points = [start_point]
def add_point(self, point):
"""
Add a point to the shape.
Args:
point: The point to add.
"""
self.points.append(point)
def draw(self, canvas):
# Draw the shape on the canvas
# Abstract method for drawing a shape
pass
# Freehand shape class
class FreehandShape(Shape):
def draw(self, canvas):
try:
canvas.create_line(self.points, fill = self.color, width = 2)
except tk.TclError as e:
print(f"Failed to draw freehand shape: {e}")
# Rectangle shape class
class RectangleShape(Shape):
def draw(self, canvas):
start_x, start_y = self.points[0]
end_x, end_y = self.points[-1]
canvas.create_rectangle(start_x, start_y, end_x, end_y,
outline = self.color, width = 2)
# Oval shape class
class OvalShape(Shape):
def draw(self, canvas):
start_x, start_y = self.points[0]
end_x, end_y = self.points[-1]
canvas.create_oval(start_x, start_y, end_x, end_y, outline = self.color,
width = 2)
# Line class
class LineShape(Shape):
def draw(self, canvas):
start_x, start_y = self.points[0]
end_x, end_y = self.points[-1]
canvas.create_line(start_x, start_y, end_x, end_y, fill = self.color,
width = 2)
if __name__ == "__main__":
root = tk.Tk()
app = DrawingApp(root)
root.mainloop()