A simple paint tool is one of the most common apps you can find on most computers. It lets the artist make mistakes without fear, choose any color at the click of a button, and change the size of their brush strokes instantly. You can use it to create brand logos, conceptualize user interfaces, and annotate diagrams.

So, how can you build a paint application?

The Tkinter and Pillow Module

To build a paint application, you'll need the Tkinter and Pillow modules. Tkinter is one of the top Python frameworks you can use to customize your GUI. It’s the standard Python GUI module for creating desktop applications. Tkinter comes with a variety of widgets like label, entry, canvas, and button.

Pillow, a fork of the Python Imaging Library (PIL), is an image-processing module for Python. With Pillow, you can open, resize, flip, and crop images. You can convert file formats, build a recipe finder application, and fetch random images.

To install these modules, run:

 pip install tk pillow 

Define the Structure of the Paint Application

You can find the entire source code for this project in this GitHub repository.

Begin by importing the required modules. Define a class, DrawApp. Set the title, the pointer color, and the eraser color. Make the application open in full screen. Call the setup_widgets method.

 import tkinter as tk
from tkinter.ttk import Scale
from tkinter import colorchooser, filedialog, messagebox
import PIL.ImageGrab as ImageGrab

class DrawApp:
    def __init__(self, root):
        self.root = root
        self.root.title("Kids' Paint App")
        self.root.attributes("-fullscreen", True)
        self.pointer = "black"
        self.erase = "white"
        self.setup_widgets()

Define a method called setup_widgets. Define a label that displays a heading. Set the parent element, the text you want to display, the font style, the background color, and the text color. Define a frame for the color palette. Set the parent element, the text it should display, the font styles, and the border width. Set the border to have a ridge-like appearance and the background color as white.

     def setup_widgets(self):
        self.title_label = tk.Label(
            self.root,
            text="Kids' Paint App",
            font=("Comic Sans MS", 30),
            bg="lightblue",
            fg="purple",
        )
        self.title_label.pack(fill=tk.X, pady=10)
        self.color_frame = tk.LabelFrame(
            self.root,
            text="Colors",
            font=("Comic Sans MS", 15),
            bd=5,
            relief=tk.RIDGE,
            bg="white",
        )
        self.color_frame.place(x=10, y=80, width=90, height=180)

Define a set of colors for the color palette in a list. Iterate over it and create a button for each of them. Set the parent element, the background color, the border width, and the appearance. Also set the width and the command each button should run when clicked. Organize all the elements with appropriate padding and the colors in sets of two.

         colors = [
            "blue",
            "red",
            "green",
            "orange",
            "violet",
            "black",
            "yellow",
            "purple",
            "pink",
            "gold",
            "brown",
            "indigo",
        ]
        i, j = 0, 0
        for color in colors:
            tk.Button(
                self.color_frame,
                bg=color,
                bd=2,
                relief=tk.RIDGE,
                width=3,
                command=lambda col=color: self.select_color(col),
            ).grid(row=i, column=j, padx=2, pady=2)
            i += 1
            if i == 4:
                i = 0
                j = 1

Similarly, define a button for the eraser, one to clear the screen, and one to save the image.

         self.eraser_btn = tk.Button(
            self.root,
            text="Eraser",
            bd=4,
            bg="white",
            command=self.eraser,
            width=9,
            relief=tk.RIDGE,
            font=("Comic Sans MS", 12),
        )
        self.eraser_btn.place(x=10, y=310)
        self.clear_screen_btn = tk.Button(
            self.root,
            text="Clear Screen",
            bd=4,
            bg="white",
            command=self.clear_screen,
            width=12,
            relief=tk.RIDGE,
            font=("Comic Sans MS", 12),
        )
        self.clear_screen_btn.place(x=10, y=370)
        self.save_as_btn = tk.Button(
            self.root,
            text="Save Drawing",
            bd=4,
            bg="white",
            command=self.save_as,
            width=12,
            relief=tk.RIDGE,
            font=("Comic Sans MS", 12),
        )
        self.save_as_btn.place(x=10, y=430)
        self.bg_btn = tk.Button(
            self.root,
            text="Background",
            bd=4,
            bg="white",
            command=self.canvas_color,
            width=12,
            relief=tk.RIDGE,
            font=("Comic Sans MS", 12),
        )
        self.bg_btn.place(x=10, y=490)
        self.pointer_frame = tk.LabelFrame(
            self.root,
            text="Size",
            bd=5,
            bg="white",
            font=("Comic Sans MS", 15, "bold"),
            relief=tk.RIDGE,
        )

Define a scale widget to increase or decrease the size of the pointer or eraser. Set the parent element, the orientation, the range, and the length in pixels. Define a canvas and set the parent element, the background color, and the border width. Also, set the relief to have a groove appearance along with its height and width.

Position the canvas with appropriate coordinates and set the anchor to the Northwest (top left). Bind the B1-Motion to the paint function. B1 refers to the left mouse button held down and Motion refers to the movement. Overall, you use it to track mouse movement while you press the left button.

         self.pointer_frame.place(x=10, y=580, height=150, width=70)
        self.pointer_size = Scale(
            self.pointer_frame, orient=tk.VERTICAL, from_=48, to=1, length=120
        )
        self.pointer_size.set(1)
        self.pointer_size.grid(row=0, column=1, padx=15)
        self.canvas = tk.Canvas(
            self.root, bg="white", bd=5, relief=tk.GROOVE, height=650, width=1300
        )
        self.canvas.place(x=160, y=120, anchor="nw")
        self.canvas.bind("<B1-Motion>", self.paint)

Define the Features of the Paint Application

Define a method, paint. To paint, the app will continuously draw minute ovals. Subtract 2 from the x and y coordinates of the mouse event to determine the top left corner of the oval. Add 2 to determine the bottom right corner of the oval. Create an oval using these bounding coordinates.

Set the fill color, outline color, and width as per the selection of the pointer.

     def paint(self, event):
        x1, y1 = (event.x - 2), (event.y - 2)
        x2, y2 = (event.x + 2), (event.y + 2)
        self.canvas.create_oval(
            x1,
            y1,
            x2,
            y2,
            fill=self.pointer,
            outline=self.pointer,
            width=self.pointer_size.get(),
        )

Define three functions, select_color, eraser, and, clear_screen. The select_color method takes a color and sets the pointer accordingly. The eraser method sets the pointer to have an eraser-like effect and makes it draw transparent lines. The clear_screen method deletes all the items on the canvas.

     def select_color(self, col):
        self.pointer = col

    def eraser(self):
        self.pointer = self.erase

    def clear_screen(self):
        self.canvas.delete("all")

Define a method, canvas_color. Open a color chooser with all the different colors. Return a tuple containing the color in RGB format and hexadecimal format. If the user chooses a color, use the configure method to set the background color. Set the color of the eraser to the same as the background color.

     def canvas_color(self):
        color = colorchooser.askcolor()
        if color:
            self.canvas.configure(background=color[1])
            self.erase = color[1]

Define a method, save_as. Open a file dialog box asking the user to choose the file name and path. If the user selects a path, use Pillow's ImageGrab class to capture the whole screen. Crop the image using the specified coordinates to gain the canvas region. Experiment with the coordinates to grab the desired part.

Save this result to the desired file path. Display a message box that informs the user that the program has successfully saved the paint as an image. In case of any error, it displays the corresponding error.

     def save_as(self):
        file_path = filedialog.asksaveasfilename(
            defaultextension=".jpg", filetypes=[("Image files", "*.jpg")]
        )
        if file_path:
            try:
                y = 148
                x = 200
                y1 = 978
                x1 = 1840
                ImageGrab.grab().crop((x, y, x1, y1)).save(file_path)
                messagebox.showinfo("Save Drawing", "Image file saved successfully!")
            except Exception as e:
                messagebox.showerror("Error", f"Failed to save the image file: {e}")

Create an instance of the Tk and the DrawApp class. The mainloop() function tells Python to run the Tkinter event loop and listen for events until you close the window.

 if __name__ == "__main__":
    root = tk.Tk()
    app = DrawApp(root)
    root.mainloop()

Testing Different Painting Features Using Python

When you run the paint program, you’ll see an app with a color palette, four buttons, one slider, and a canvas to paint on:

Initial Screen of Paint Application
Source:Screenshot by Sai Ashish -- no attribution required

Click any color to select it. You can then draw on the canvas in that color with the left mouse button:

Drawing using different paint colors
Source:Screenshot by Sai Ashish -- no attribution required

On clicking the Eraser button and dragging the slider vertically up, you’ll select the eraser and increase its size. Test the eraser by dragging it over your drawing to erase the strokes.

Increasing Size and Erasing in Middle
Source:Screenshot by Sai Ashish -- no attribution required

When you click the Clear Screen button, the program clears your previous drawing. Click the Background button to open a color palette and use it to change the background color.

Clearing the screen and adding background color
Source:Screenshot by Sai Ashish -- no attribution required

On clicking the Save Drawing button, a file dialog box opens. Choose a path and name for the file, and the program will save it.

Saving the paint as an image
Source:Screenshot by Sai Ashish -- no attribution required

Enhancing the Paint Application

You can enhance the functionality of the paint application by adding an option to add shapes. You can give an option to select the brush type and opacity. Add an option to add text and stickers. Add an option for undo, redo, resize, and flip images. This will make the drawing process much smoother.

To create shapes, you can use methods such as create_rectangle, create_oval, create_line, and create_polygon. To add text and images, use the create_text and create_image method. To resize and flip images, you can use Pillow's resize and transpose methods.