お絵描きアプリ
マウスで自由に描けるシンプルなペイントアプリ。Canvas上のマウスイベント処理を学びます。
1. アプリ概要
マウスで自由に描けるシンプルなペイントアプリ。Canvas上のマウスイベント処理を学びます。
このアプリは基本カテゴリに分類される実践的なGUIアプリです。使用ライブラリは tkinter(標準ライブラリ)・Pillow で、難易度は ★★☆ です。掲載コードは先頭で from PIL import Image, ImageDraw を読み込みます。実行前に pip install pillow が必要です。描画そのものは tkinter の Canvas で行います。
このアプリは「入門」カテゴリです。ウィンドウ・ウィジェット・イベントの基本構造を反復することで、後から複雑なアプリを作るときの骨格が身につきます。tkinter(標準ライブラリ)の Canvas を活かして実装するこの構造は、他のアプリにも応用が効きます。
完成形を見てから細部の解説に進む流れが効果的です。実行→気になる箇所を解説で確認→自分の手で改造、というサイクルで定着が早まります。
カスタマイズ章では具体的な拡張アイデアを示しています。一つ実装するごとに動作確認することで、変更が予期せぬ副作用を起こさないかも体感できます。
2. 機能一覧
- ループで生成するボタン群を
set_color()に接続 - ボタン(
choose_color())・「消しゴム」ボタン(set_color())・「クリア」ボタン(clear())で操作 - マウス操作
<ButtonPress-1>からstart_draw()を実行 - マウス操作
<B1-Motion>からdraw()を実行 - マウス操作
<ButtonRelease-1>からstop_draw()を実行 - ウィンドウはタイトル「お絵描きアプリ」・サイズ 640x520 で起動
3. 事前準備・環境
Python 3.10 以上 / Windows 11(実機)で起動・基本操作を確認 / Linux Mint 22.3(仮想マシン)で起動・画面表示を確認(macOS は未検証)
Windows 11(実機)で起動と基本操作を確認しています(全機能の網羅テストではありません)。Linux Mint 22.3(仮想マシン)では起動と画面表示を確認しました。macOS は必要なライブラリ(pip install)を入れれば動作する想定ですが、未検証です。
- Python 3.10 以上
- OS: Windows 11(実機で起動・基本操作を確認)・Linux Mint 22.3(仮想マシンで起動・画面表示を確認)
4. 完全なソースコード
右上の「コピー」ボタンをクリックするとコードをクリップボードにコピーできます。
import tkinter as tk
from tkinter import ttk, colorchooser, messagebox
from PIL import Image, ImageDraw
from tkinter import filedialog
class App26:
"""お絵描きアプリ"""
def __init__(self, root):
self.root = root
self.root.title("お絵描きアプリ")
self.root.geometry("640x520")
self.root.configure(bg="#f8f9fc")
self.pen_color = "#222222"
self.pen_size = 4
self.last_x = self.last_y = None
self._build_ui()
def _build_ui(self):
title_frame = tk.Frame(self.root, bg="#3776ab", pady=8)
title_frame.pack(fill=tk.X)
tk.Label(title_frame, text="お絵描きアプリ",
font=("Noto Sans JP", 15, "bold"),
bg="#3776ab", fg="white").pack()
# ツールバー
toolbar = tk.Frame(self.root, bg="#e8eaf0", pady=6)
toolbar.pack(fill=tk.X, padx=4)
# 色ボタン
tk.Label(toolbar, text="色:", bg="#e8eaf0").pack(side=tk.LEFT, padx=4)
self.color_btn = tk.Button(toolbar, bg=self.pen_color, width=3,
relief=tk.RAISED, command=self.choose_color)
self.color_btn.pack(side=tk.LEFT, padx=2)
# よく使う色
for color in ["#000000", "#ffffff", "#e74c3c", "#3498db", "#2ecc71",
"#f39c12", "#9b59b6", "#1abc9c"]:
tk.Button(toolbar, bg=color, width=2, relief=tk.FLAT,
command=lambda c=color: self.set_color(c)).pack(side=tk.LEFT, padx=1)
# 太さ
tk.Label(toolbar, text="太さ:", bg="#e8eaf0", padx=8).pack(side=tk.LEFT)
self.size_var = tk.IntVar(value=self.pen_size)
ttk.Scale(toolbar, variable=self.size_var, from_=1, to=30,
orient=tk.HORIZONTAL, length=80,
command=lambda v: setattr(self, "pen_size", int(float(v)))).pack(side=tk.LEFT)
# 消しゴム・クリア
tk.Button(toolbar, text="消しゴム", bg="#e8eaf0", relief=tk.FLAT,
command=lambda: self.set_color("#ffffff")).pack(side=tk.LEFT, padx=4)
tk.Button(toolbar, text="クリア", bg="#e8eaf0", relief=tk.FLAT,
command=self.clear).pack(side=tk.LEFT, padx=2)
# キャンバス
self.canvas = tk.Canvas(self.root, bg="white", cursor="crosshair",
relief=tk.SUNKEN, bd=1)
self.canvas.pack(fill=tk.BOTH, expand=True, padx=4, pady=4)
self.canvas.bind("<ButtonPress-1>", self.start_draw)
self.canvas.bind("<B1-Motion>", self.draw)
self.canvas.bind("<ButtonRelease-1>", self.stop_draw)
def choose_color(self):
color = colorchooser.askcolor(color=self.pen_color)[1]
if color:
self.set_color(color)
def set_color(self, color):
self.pen_color = color
self.color_btn.config(bg=color)
def start_draw(self, event):
self.last_x = event.x
self.last_y = event.y
def draw(self, event):
if self.last_x and self.last_y:
self.canvas.create_line(self.last_x, self.last_y, event.x, event.y,
fill=self.pen_color, width=self.pen_size,
capstyle=tk.ROUND, smooth=True)
self.last_x = event.x
self.last_y = event.y
def stop_draw(self, event):
self.last_x = self.last_y = None
def clear(self):
self.canvas.delete("all")
if __name__ == "__main__":
root = tk.Tk()
app = App26(root)
root.mainloop()
5. コード解説
お絵描きアプリのコードを、実際に書かれている実装に沿って解説します。
クラス設計とコンストラクタ
App26 クラスにアプリの全機能をまとめています(メソッド8個・全96行)。__init__ ではタイトル「お絵描きアプリ」とウィンドウサイズ 640x520 を設定します。あわせて状態を保持する変数(self.pen_color・self.pen_size・self.last_x・self.last_y)を初期化し、最後に _build_ui() で画面を組み立てます。
※ 該当部分のコード本体は 「4. 完全なソースコード」 をご参照ください(重複表示を避けるため再掲を省略しています)。
ウィジェット構成
画面は tk.Frame×2・tk.Label×3・tk.Button×3(ほかにループでも生成)・ttk.Scale・tk.Canvas で構成しています。
※ 該当部分のコード本体は 「4. 完全なソースコード」 をご参照ください(重複表示を避けるため再掲を省略しています)。
イベント処理とボタンの接続
ボタン(choose_color())・「消しゴム」ボタン(set_color())・「クリア」ボタン(clear())を command= で接続しています。ボタンはループ内で command=lambda c=color: self.set_color(c) の形で生成しています。既定引数でループ変数を固定するのがポイントで、これを書かないと全ボタンが最後の値で動いてしまいます。また bind("<ButtonPress-1>", ...) から start_draw()、bind("<B1-Motion>", ...) から draw()、bind("<ButtonRelease-1>", ...) から stop_draw() を呼べるようにイベントも登録しています。
※ 該当部分のコード本体は 「4. 完全なソースコード」 をご参照ください(重複表示を避けるため再掲を省略しています)。
6. ステップバイステップガイド
このアプリをゼロから自分で作る手順を解説します。コードをコピーするだけでなく、実際に手順を追って自分で書いてみましょう。
-
1ファイルを作成する
新しいファイルを作成して app26.py と保存します。掲載コードは冒頭で
from PIL import Image, ImageDrawを読み込むため、実行前にpip install Pillowが必要です(描画自体は tkinter のCanvasで行っており、PIL は画像保存へ拡張するときの土台として import しています)。 -
2クラスの骨格を作る
App26クラスを定義し、__init__と、末尾のroot = tk.Tk()~mainloop()の最小構成を書いて、まず空のウィンドウが出ることを確認します。 -
3ウィンドウを設定する
title("お絵描きアプリ")・geometry("640x520")・configure(bg="#f8f9fc")をコンストラクタで設定します。 -
4画面部品を並べる
_build_ui()の中で、tk.Frame×2・tk.Label×3・tk.Button×3(ほかにループでも生成)・ttk.Scale・tk.Canvasを作って配置します(掲載コードと同じ並び順で書くとレイアウトが一致します)。まず表示だけ確認しましょう。 -
5イベントを接続する
「2. 機能一覧」で挙げた各ボタンを
command=で対応するメソッド(choose_color()・set_color()・clear())につなぎます。ループ生成のボタンはcommand=lambda c=color: self.set_color(c)の形で接続します。bind("<ButtonPress-1>", ...)・bind("<B1-Motion>", ...)・bind("<ButtonRelease-1>", ...)の登録も忘れずに。 -
6中心になるメソッドを実装する
アプリの本体である
draw()(7行)・choose_color()(4行)・set_color()(3行)など を実装します。 -
7動作確認する
python app26.pyで起動し、各ボタンが反応すること・マウス操作(クリック・ホイールなど)が効くことを確認します。
7. カスタマイズアイデア
基本機能を習得したら、以下のカスタマイズに挑戦してみましょう。少しずつ機能を追加することで、Pythonのスキルが飛躍的に向上します。
💡 ダークモードを追加する
bg色・fg色を辞書で管理し、ボタン1つでダークモード・ライトモードを切り替えられるようにしましょう。
💡 結果をファイルに保存する
このコードに保存処理はないため、表示中の数値や結果はアプリを閉じると消えます。テキストファイルへ書き出して、次回起動時に読み込む機能を追加してみましょう。
💡 入力履歴機能
以前の入力値を覚えておいてComboboxのドロップダウンで再選択できる履歴機能を追加しましょう。
8. よくある問題と解決法
❌ 文字のフォントが崩れる・見た目が違う
原因:このコードは font=("Noto Sans JP", ...) のようにフォント名を直接指定しています。環境にこのフォントが入っていないと、OS が代わりのフォントで表示するため、見本と見た目が変わることがあります。
解決法:動作には支障ありません。気になる場合は font 引数の名前を自分の OS に入っているフォントへ書き換えるか、font 引数を省略してください。
❌ 写経したら全部のボタンが同じ動きになる
原因:ループでボタンを作るとき、このコードは command=lambda c=color: self.set_color(c) のように既定引数でループ変数を固定しています。これを省略してループ変数を直接書くと、ループ終了後の最後の値が全ボタンで共有されてしまいます(Python のクロージャの有名な罠です)。
解決法:掲載コードのとおり、lambda の既定引数でその時点の値を渡してください。
❌ ウィンドウの大きさが画面に合わない
原因:geometry("640x520") の固定サイズで起動するためです。
解決法:この数値を書き換えて起動サイズを調整してください。なお、このコードにはウィンドウサイズを固定する設定(resizable の指定)が無いため、ウィンドウの端をドラッグしたサイズ変更は既定どおり可能です。
9. 練習問題
アプリの理解を深めるための練習問題です。気になるものから挑戦してみてください(課題3は定型の発展課題です)。このアプリは描いた絵を保存しないので、ウィンドウを閉じると絵は消えます。試すときは短い線を数本引けば十分です。
-
課題1:キャンバスの背景色を変えられるようにする
背景は白のまま固定です(57行)。ツールバーから背景の色を選べるようにしましょう。
期待結果:「背景」ボタンを押して水色を選ぶと、キャンバス全体が水色になり、すでに描いた線はそのまま残る
合格条件
- 背景を変えても、描いてある線が消えない
- ペンの色(色ボタンの見た目)は変わらない
- 背景を変えたあとも、そのまま続けて描ける
ヒント①(どこを触るか): 触るのは2か所です。ツールバーにボタンを足す部分(53〜54行の「クリア」ボタンの隣)と、選ばれた色を背景に反映する新しいメソッドです。
ヒント②(使うもの):choose_color()(65〜68行)が手本です。colorchooser.askcolor()は選ばれた色を[1]番目に#rrggbbの文字列で返し、キャンセルするとNoneになります。背景の変更はself.canvas.config(bg=color)です。
つまずきやすい点: 「消しゴム」は52行でset_color("#ffffff")と白を決め打ちしています。背景を白以外にすると、消しゴムでなぞった跡が白い線として残ります。背景と同じ色を消しゴムにも渡すよう、あわせて直す必要があります。 -
課題2:「クリア」を押す前に確認する
いまは「クリア」を押した瞬間に全部消えます(89〜90行)。押し間違いで絵を失わないよう、確認を出しましょう。
期待結果:「クリア」を押すと「すべて消しますか?」の確認が出る。「いいえ」なら絵が残り、「はい」なら今までどおり全部消える
合格条件
- 「いいえ」を選ぶと線が1本も消えない
- 「はい」を選ぶと今までどおりキャンバスが空になる
- 確認のあとも、そのまま続けて描ける
ヒント①(どこを触るか): 触るのは1か所です。
clear()のself.canvas.delete("all")(90行)の手前です。
ヒント②(使うもの):messageboxは2行目で読み込み済みですが、このコードではまだ一度も使っていません。messagebox.askyesno("確認", "…")は「はい」でTrueを返します。
つまずきやすい点:showinfoを使うと「OK」ボタンだけの案内になり、押した答えを受け取れないので必ず消えます。askyesnoの戻り値をifで見て、Falseのときはreturnで抜けます。 -
課題3:保存機能の追加
入力値や計算結果をファイルに保存する機能を追加しましょう。jsonやcsvモジュールを使います。
写経中に赤いエラー文が出たら、Pythonエラー一覧&解決法(英語メッセージ逆引き)で原因と直し方をすぐ確認できます。
写経しながら「なぜこう書くのか」が気になり始めたら、入門書を1冊通して読むと断片的な知識がつながります。Python入門書のおすすめ2冊(当サイトの参考書ランキング総合1〜2位)で、独学者向けの最初の1冊を比較しています。