Introduction
A paint app is the most fun way to learn event-driven drawing: the mouse moves, the app responds, lines appear. Tkinter's Canvas widget does the heavy lifting — you bind three mouse events and a drawing app materializes. This build includes freehand drawing, a color picker, adjustable brush size, an eraser, and one-click clear, in about 80 lines.
It is the desktop cousin of the photo booth: both turn input devices into visuals, one via camera, one via mouse.
Features
Prerequisites
sudo apt install python3-tk on Ubuntu).Step 1: Create the Script
Save as paint_app.py:
import tkinter as tk
from tkinter import colorchooser
class PaintApp:
def __init__(self, root):
self.root = root
self.root.title("Paint")
self.color = "#10b981"
self.brush_size = 4
self.erasing = False
self.last_x = self.last_y = None
self.BG = "#ffffff"
toolbar = tk.Frame(root, bg="#f3f4f6")
toolbar.pack(fill="x")
tk.Button(toolbar, text="🎨 Color", command=self.pick_color).pack(side="left", padx=4, pady=4)
self.color_swatch = tk.Label(toolbar, bg=self.color, width=3, relief="solid")
self.color_swatch.pack(side="left", padx=2)
tk.Button(toolbar, text="🧽 Eraser", command=self.toggle_eraser, relief="raised").pack(side="left", padx=4)
self.eraser_btn = toolbar.winfo_children()[-1]
tk.Scale(toolbar, from_=1, to=30, orient="horizontal",
command=self.set_size, length=140).pack(side="left", padx=8)
tk.Button(toolbar, text="🗑 Clear", command=self.clear).pack(side="right", padx=4)
self.canvas = tk.Canvas(root, bg=self.BG, cursor="pencil")
self.canvas.pack(fill="both", expand=True)
self.canvas.bind("<B1-Motion>", self.draw)
self.canvas.bind("<ButtonRelease-1>", self.reset)
def pick_color(self):
chosen = colorchooser.askcolor(self.color)
if chosen[1]:
self.color = chosen[1]
self.color_swatch.config(bg=self.color)
self.erasing = False
self.eraser_btn.config(relief="raised")
def toggle_eraser(self):
self.erasing = not self.erasing
self.eraser_btn.config(relief="sunken" if self.erasing else "raised")
def set_size(self, value):
self.brush_size = int(value)
def draw(self, event):
color = self.BG if self.erasing else self.color
if self.last_x is not None:
self.canvas.create_line(
self.last_x, self.last_y, event.x, event.y,
fill=color, width=self.brush_size,
capstyle="round", smooth=True,
)
self.last_x, self.last_y = event.x, event.y
def reset(self, event):
self.last_x = self.last_y = None
def clear(self):
self.canvas.delete("all")
if __name__ == "__main__":
root = tk.Tk()
PaintApp(root)
root.mainloop()Step 2: Run the App
python paint_app.pyDraw freely, switch colors, resize the brush, erase mistakes — the full paint loop in under a hundred lines.
How It Works
Drawing is three event bindings. <B1-Motion> fires continuously while the left button is held; each firing draws a short line segment from the *previous* mouse position to the current one. <ButtonRelease-1> fires on mouse-up and clears the stored previous position — without it, the next stroke would start with a long stray line from where the last one ended. That reset handler is the difference between a paint app and a bug.
The Canvas stores every segment as a line object — which is both the strength and the ceiling of this approach. Strength: each segment is a real widget you could manipulate. Ceiling: a long drawing session accumulates thousands of objects and slows down; a real paint program would draw into an offscreen image instead. For a utility app, the object model is perfect.
The eraser is honest about what erasing means here: it draws in the background color. On a plain white canvas that's indistinguishable from true erasing — and one line of code instead of compositing logic.
smooth=True with capstyle="round" turns the polyline segments into what looks like one continuous stroke — free smoothing from Tkinter's line renderer.
Common Errors & Fixes
last_x/last_y, or you bound motion without the release binding.<B1-Motion>; draw a tiny circle on <Button-1> if dot-painting matters.self.BG as the draw color when self.erasing is true.Key Concepts
What to Try Next
create_rectangle.canvas.postscript() plus Pillow conversion.FAQ
Why does drawing get slow after a while?
Every segment is a Canvas object; thousands accumulate. Periodically flattening into an image (Pillow) or restarting the canvas keeps it snappy.
Can I use this on a touchscreen?
Yes — Tkinter maps touch to mouse events on most platforms, so finger painting works out of the box.
How do I make the eraser size independent?
Use a larger width when self.erasing is true — e.g. self.brush_size * 2 in the draw call.