TkinterのCanvasを使ってあやιぃ教主の選挙ポスターを作る

49, 2019-07-17

目次

あやιぃ教主の選挙ポスターを作る

こんにちは、narupoです。

選挙の季節ですね。
narupoも選挙に立候補しようかと思って、PythonのTkinterとCanvasを使って選挙ポスターを作ってみました。

↓これです。

マニフェストの描画

ソースコードはGitHubで公開しています↓。

この選挙ポスターをどうやって作成したのか解説していきたいと思います。

環境にPillowをインストールする

この選挙ポスターでは画像処理にPillowを使っています。
pipでPillowをインストールするには↓のようにします。

$ pip install Pillow

TkinterはPythonの標準ライブラリなので、インストールの必要はありません。

Tkinterをインポートする

Tkinterをインポートするには↓のようにします。
Tkinterはtkというエイリアスを作るのが慣例のようです。

import tkinter as tk

ウィンドウを作成する

Tkinterでウィンドウを作成します。

import tkinter as tk

class App(tk.Frame):
    def __init__(self, master):
        super().__init__(master)

        # ウィンドウのサイズを設定
        master.geometry('400x500')

        # ウィンドウのタイトルを設定
        master.title('あやιぃ教主のポスター')

        # ウィンドウのリサイズを無効化
        master.resizable(False, False)

        self.pack()

root = tk.Tk()
app = App(root)
app.mainloop()

Appというtk.Frameを継承したクラスを作っています。
このパターンは公式のTkinterのチュートリアルでも紹介されていました。

ウィンドウはどこで作られているのかというと

root = tk.Tk()

の部分です。このrootがウィンドウです。
Appにrootを渡してインスタンス化した後、tk.Frameの持つmainloopメソッドでループします。
Appはtk.Frameを継承したTkinterのウィジェットのひとつなので、構造的にはrootの子になります。

__init__内の↓の部分でmaster, つまりrootの初期化をしています。
masterは親ウィジェットのことですが、Tkinterではmasterと表現するのが慣例のようです。

設計的にはこのmasterの初期化はAppの外に出すというのもひとつの手だと思います。
しかし、Appはアプリケーション全体を指すクラスですから、今回はmasterの初期化処理をAppの中に入れています。

# ウィンドウのサイズを設定
master.geometry('400x500')

# ウィンドウのタイトルを設定
master.title('あやιぃ教主のポスター')

# ウィンドウのリサイズを無効化
master.resizable(False, False)

__init__内の↓の部分でAppを親ウィジェットに配置しています。

self.pack()

このサンプルを実行すると↓のようになります。

ウィンドウの作成

キャンバスの作成

次に↓がキャンバスを作成するサンプルです。
このコードは単体で動作します。

import tkinter as tk

class App(tk.Frame):
    """
    選挙ポスターを表示する
    """

    def __init__(self, master):
        super().__init__(master)

        # ウィンドウの横幅と高さ
        self.WIN_WIDTH = (400)
        self.WIN_HEIGHT = (500)

        # ウィンドウのサイズを設定
        master.geometry('%dx%d' % (self.WIN_WIDTH, self.WIN_HEIGHT))

        # ウィンドウのタイトルを設定
        master.title('あやιぃ教主のポスター')

        self.pack(fill=tk.BOTH)
        self.create_widgets()

    def create_widgets(self):
        # キャンバスを作成
        self.canvas = tk.Canvas(
            self,
            width=self.WIN_WIDTH,
            height=self.WIN_HEIGHT,
            bg='green',
        )
        self.canvas.pack()

__init__内の↓の部分ですが、App内のウィジェットを作成するメソッドcreate_widgetsを呼び出しています。

self.create_widgets()

create_widgets内ではキャンバスを作成して配置しています。

def create_widgets(self):
    # キャンバスを作成
    self.canvas = tk.Canvas(
        self,
        width=self.WIN_WIDTH,
        height=self.WIN_HEIGHT,
        bg='green',
    )
    self.canvas.pack()

このサンプルを実行すると↓のようになります。

キャンバスの作成

背景のグラデーションを描く

次に背景のグラデーションを描く方法です。
今回はPillowを使ってグラデーション画像を作成し、その画像をTkinterのCanvasに配置しています。
↓がサンプルです。このサンプルは単体で動作します。

import tkinter as tk
from PIL import Image, ImageTk, ImageDraw

class App(tk.Frame):
    """
    選挙ポスターを表示する
    """

    def __init__(self, master):
        super().__init__(master)

        # ウィンドウの横幅と高さ
        self.WIN_WIDTH = (400)
        self.WIN_HEIGHT = (500)

        # ウィンドウのサイズを設定
        master.geometry('%dx%d' % (self.WIN_WIDTH, self.WIN_HEIGHT))

        # ウィンドウのタイトルを設定
        master.title('あやιぃ教主のポスター')

        self.pack(fill=tk.BOTH)
        self.create_widgets()

    def create_widgets(self):
        # キャンバスを作成
        self.canvas = tk.Canvas(
            self,
            width=self.WIN_WIDTH,
            height=self.WIN_HEIGHT,
        )
        self.canvas.pack()

        # 背景のグラデーション用の画像を作成
        self.bg_image = Image.new(
            'RGB',
            (self.WIN_WIDTH, self.WIN_HEIGHT),
            (255, 255, 255), # 背景は真っ白
        )

        # グラデーションを描く
        draw = ImageDraw.Draw(self.bg_image)
        for x in range(self.WIN_WIDTH):
            for y in range(self.WIN_HEIGHT):
                color = y
                draw.point((x, y), (color, 255, color))

        # グラデーション画像をキャンバスに置く
        self.bg_photo_image = ImageTk.PhotoImage(self.bg_image)
        self.canvas.create_image(self.bg_image.size[0]//2, self.bg_image.size[1]//2, image=self.bg_photo_image)


root = tk.Tk()
app = App(root)
app.mainloop()

このサンプルを実行すると↓のようになります。

グラデーションの描画

create_widgets内の↓の部分がグラデーション画像を作成してキャンバスに配置している部分です。
まずImage.newでPillowのImage, bg_imageを作成します。

その後、ImageDraw.Drawに作成したbg_imageを渡して描画用コンテキストdrawを作成します。
このdrawを使ってグラデーションを描画します。
グラデーションを描画するキャンバスのサイズ分、for文を回しています。

その後、描画が完了したbg_imageをImageTk.PhotoImageに渡してTkinter用のイメージ、bg_photo_imageを作成します。
このbg_photo_imageはキャンバスで描画できるので、canvas.create_imageに渡して画像をキャンバスに描画します。

# 背景のグラデーション用の画像を作成
self.bg_image = Image.new(
    'RGB',
    (self.WIN_WIDTH, self.WIN_HEIGHT),
    (255, 255, 255), # 背景は真っ白
)

# グラデーションを描く
draw = ImageDraw.Draw(self.bg_image)
for x in range(self.WIN_WIDTH):
    for y in range(self.WIN_HEIGHT):
        color = y
        draw.point((x, y), (color, 255, color))

# グラデーション画像をキャンバスに置く
self.bg_photo_image = ImageTk.PhotoImage(self.bg_image)
self.canvas.create_image(self.bg_image.size[0]//2, self.bg_image.size[1]//2, image=self.bg_photo_image)

注意点として、bg_photo_imageselfに持たせて参照をキープする必要があります。
参照をキープしないとキャンバスに描画されません。

プロフィール画像を描く

次にプロフィール画像を描きます。
↓がサンプルです。
このサンプルは単体で動作します。

import tkinter as tk
from PIL import Image, ImageTk, ImageDraw

class App(tk.Frame):
    """
    選挙ポスターを表示する
    """

    def __init__(self, master):
        super().__init__(master)

        # ウィンドウの横幅と高さ
        self.WIN_WIDTH = (400)
        self.WIN_HEIGHT = (500)

        # ウィンドウのサイズを設定
        master.geometry('%dx%d' % (self.WIN_WIDTH, self.WIN_HEIGHT))

        # ウィンドウのタイトルを設定
        master.title('あやιぃ教主のポスター')

        self.pack(fill=tk.BOTH)
        self.create_widgets()

    def create_widgets(self):
        # キャンバスを作成
        self.canvas = tk.Canvas(
            self,
            width=self.WIN_WIDTH,
            height=self.WIN_HEIGHT,
        )
        self.canvas.pack()

        # 背景のグラデーション用の画像を作成
        self.bg_image = Image.new(
            'RGB',
            (self.WIN_WIDTH, self.WIN_HEIGHT),
            (255, 255, 255), # 背景は真っ白
        )

        # グラデーションを描く
        draw = ImageDraw.Draw(self.bg_image)
        for x in range(self.WIN_WIDTH):
            for y in range(self.WIN_HEIGHT):
                color = y
                draw.point((x, y), (color, 255, color))

        # グラデーション画像をキャンバスに置く
        self.bg_photo_image = ImageTk.PhotoImage(self.bg_image)
        self.canvas.create_image(self.bg_image.size[0]//2, self.bg_image.size[1]//2, image=self.bg_photo_image)

        # self.profile_photo_imageで画像の参照をキープしていることに注意
        # 参照をキープしないとキャンバスに描画されない
        self.PROFILE_MARGIN_TOP = 20
        self.profile_image = Image.open('profile.png')
        self.profile_photo_image = ImageTk.PhotoImage(self.profile_image)

        # プロフィール画像に枠線を描く
        # 画像より奥に表示したいので画像の作成前に作成する
        x = ((self.WIN_WIDTH - self.profile_image.size[0]) // 2)
        y = self.PROFILE_MARGIN_TOP
        self.canvas.create_rectangle(x, y, x+self.profile_image.size[0], y+self.profile_image.size[1], fill='gray', outline='black', width=3)

        # 作成する画像の座標の計算
        x = (self.profile_image.size[0] // 2) + ((self.WIN_WIDTH - self.profile_image.size[0]) // 2)
        y = (self.profile_image.size[1] // 2) + self.PROFILE_MARGIN_TOP

        # キャンバスに画像を作成
        self.canvas.create_image(x, y, image=self.profile_photo_image)

root = tk.Tk()
app = App(root)
app.mainloop()

このサンプルを実行すると↓のようになります。

プロフィール画像の描画

create_widgets内の↓の部分がプロフィール画像を描いている部分です。

# self.profile_photo_imageで画像の参照をキープしていることに注意
# 参照をキープしないとキャンバスに描画されない
self.PROFILE_MARGIN_TOP = 20
self.profile_image = Image.open('profile.png')
self.profile_photo_image = ImageTk.PhotoImage(self.profile_image)

# プロフィール画像に枠線を描く
# 画像より奥に表示したいので画像の作成前に作成する
x = ((self.WIN_WIDTH - self.profile_image.size[0]) // 2)
y = self.PROFILE_MARGIN_TOP
self.canvas.create_rectangle(x, y, x+self.profile_image.size[0], y+self.profile_image.size[1], fill='gray', outline='black', width=3)

# 作成する画像の座標の計算
x = (self.profile_image.size[0] // 2) + ((self.WIN_WIDTH - self.profile_image.size[0]) // 2)
y = (self.profile_image.size[1] // 2) + self.PROFILE_MARGIN_TOP

# キャンバスに画像を作成
self.canvas.create_image(x, y, image=self.profile_photo_image)

まず、プロフィール画像のもとになる画像profile.pngをPillowのImage.openで開きます。
それからこの画像をImageTk.PhotoImageにします。

self.profile_image = Image.open('profile.png')
self.profile_photo_image = ImageTk.PhotoImage(self.profile_image)

そのあとに画像の枠線を描く処理を加えます。
canvas.create_rectangleは四角形を描くメソッドです。
このメソッドでプロフィール画像のサイズ分、四角形を描きます。

x = ((self.WIN_WIDTH - self.profile_image.size[0]) // 2)
y = self.PROFILE_MARGIN_TOP
self.canvas.create_rectangle(x, y, x+self.profile_image.size[0], y+self.profile_image.size[1], fill='gray', outline='black', width=3)

そのあとにプロフィール画像を描画する位置を計算して、canvas.create_imageで画像を描画します。
座標計算のところがゴマゴマしてますが、やってることは描画位置の調整です。大したことはしていません。

# 作成する画像の座標の計算
x = (self.profile_image.size[0] // 2) + ((self.WIN_WIDTH - self.profile_image.size[0]) // 2)
y = (self.profile_image.size[1] // 2) + self.PROFILE_MARGIN_TOP

# キャンバスに画像を作成
self.canvas.create_image(x, y, image=self.profile_photo_image)

マニフェストを描く

最後に選挙のマニフェストを描きます。
↓がコード全文です。

import tkinter as tk
from PIL import Image, ImageTk, ImageDraw

class App(tk.Frame):
    """
    選挙ポスターを表示する
    """

    def __init__(self, master):
        super().__init__(master)

        # ウィンドウの横幅と高さ
        self.WIN_WIDTH = (400)
        self.WIN_HEIGHT = (500)

        # ウィンドウのサイズを設定
        master.geometry('%dx%d' % (self.WIN_WIDTH, self.WIN_HEIGHT))

        # ウィンドウのタイトルを設定
        master.title('あやιぃ教主のポスター')

        self.pack(fill=tk.BOTH)
        self.create_widgets()

    def create_widgets(self):
        # キャンバスを作成
        self.canvas = tk.Canvas(
            self,
            width=self.WIN_WIDTH,
            height=self.WIN_HEIGHT,
        )
        self.canvas.pack()

        # 背景のグラデーション用の画像を作成
        self.bg_image = Image.new(
            'RGB',
            (self.WIN_WIDTH, self.WIN_HEIGHT),
            (255, 255, 255), # 背景は真っ白
        )

        # グラデーションを描く
        draw = ImageDraw.Draw(self.bg_image)
        for x in range(self.WIN_WIDTH):
            for y in range(self.WIN_HEIGHT):
                color = y
                draw.point((x, y), (color, 255, color))

        # グラデーション画像をキャンバスに置く
        self.bg_photo_image = ImageTk.PhotoImage(self.bg_image)
        self.canvas.create_image(self.bg_image.size[0]//2, self.bg_image.size[1]//2, image=self.bg_photo_image)

        # self.profile_photo_imageで画像の参照をキープしていることに注意
        # 参照をキープしないとキャンバスに描画されない
        self.PROFILE_MARGIN_TOP = 20
        self.profile_image = Image.open('profile.png')
        self.profile_photo_image = ImageTk.PhotoImage(self.profile_image)

        # プロフィール画像に枠線を描く
        # 画像より奥に表示したいので画像の作成前に作成する
        x = ((self.WIN_WIDTH - self.profile_image.size[0]) // 2)
        y = self.PROFILE_MARGIN_TOP
        self.canvas.create_rectangle(x, y, x+self.profile_image.size[0], y+self.profile_image.size[1], fill='gray', outline='black', width=3)

        # 作成する画像の座標の計算
        x = (self.profile_image.size[0] // 2) + ((self.WIN_WIDTH - self.profile_image.size[0]) // 2)
        y = (self.profile_image.size[1] // 2) + self.PROFILE_MARGIN_TOP

        # キャンバスに画像を作成
        self.canvas.create_image(x, y, image=self.profile_photo_image)

        # メインタイトル
        font = ('Meiryo', 24, 'bold')
        x = self.WIN_WIDTH//2
        y = self.profile_image.size[1] + self.PROFILE_MARGIN_TOP + 40
        self.canvas.create_text(x, y, font=font, text='narupo教の教主')

        # マニフェストを作成する
        font = ('Meiryo', 16)
        y += 50
        self.canvas.create_text(x, y, font=font, fill='red', text='「猫をあがめよ」')

        y += 40
        self.canvas.create_text(x, y, font=font, fill='green', text='「すべての人民に平和を」')

        y += 40
        self.canvas.create_text(x, y, font=font, fill='blue', text='「反乱分子と共存を」')


root = tk.Tk()
app = App(root)
app.mainloop()

このコードを実行すると↓のようになります。

マニフェストの描画

create_widgets内の↓がマニフェストを描画している部分です。
テキストの描画位置を調整し、canvas.create_textでテキストをキャンバスに描画しています。
特に難しいことはしていません。

# メインタイトル
font = ('Meiryo', 24, 'bold')
x = self.WIN_WIDTH//2
y = self.profile_image.size[1] + self.PROFILE_MARGIN_TOP + 40
self.canvas.create_text(x, y, font=font, text='narupo教の教主')

# マニフェストを作成する
font = ('Meiryo', 16)
y += 50
self.canvas.create_text(x, y, font=font, fill='red', text='「猫をあがめよ」')

y += 40
self.canvas.create_text(x, y, font=font, fill='green', text='「すべての人民に平和を」')

y += 40
self.canvas.create_text(x, y, font=font, fill='blue', text='「反乱分子と共存を」')

おわりに

以上で解説はおわりです。
いかがでしたでしょうか。

Tkinterのキャンバスを使えば簡単に画像や文字を描けます。
また、Pillowとも連携が簡単で、応用範囲も広いですね。
みなさんもTkinterを使ってみてください。

以上、narupoでした。

清き一票を

スポンサーリンク

スポンサーリンク

スポンサーリンク

スポンサーリンク