【Python】Tkinter PhotoImage の get・put を利用してピクセル単位で画像処理を行う(透過処理も)

今回は Tkinter の PhotoImage クラスが提供する getput メソッドを利用してピクセル単位で画像処理を行う方法について解説していきたいと思います。

Tkinter は GUI アプリの開発に非常に便利なモジュールです。

さらに、Tkinter が提供する PhotoImage クラスを利用することで、画像ファイルの読み込みを行い、簡単な画像処理(リサイズ)を行うことも可能です。

この辺りについては下記ページで解説していますので、是非こちらも読んでみてください。

アプリ上に画像を表示する方法の解説ページアイキャッチ【Python】Tkinter のみで GUI アプリに画像を表示する

ただし、PhotoImage クラスが提供する画像処理の機能自体は非常に少ないです。

一方で、PhotoImage クラスでは get メソッドにより画像のピクセルデータを取得し、put メソッドにより画像のピクセルデータを更新することができるようになっています。

そして、これを利用すれば様々な画像処理機能を実現することもできます(画像処理機能は自分でプログラミングする必要があります)。

このページでは主に get メソッドと put メソッドを利用して画像処理を行う方法について解説していきたいと思います。

動作確認環境

このページで紹介するサンプルスクリプトは下記環境で動作確認を行っています

  • OS:macOS Catalina
  • Python:3.8
  • Tkinter:8.6

get メソッドによるピクセルデータの取得

PhotoImage クラスが提供する get メソッドは、指定した座標のピクセルデータ(R, G, B の3つの値が格納されたタプル)を取得するメソッドになります。

ピクセルデータの取得

座標(x, y)のピクセルデータを取得する際には、get メソッドを下記のように実行します。

getの使用例
# image は PhotoImage クラスのインスタンス
pixel = image.get(x, y)

put メソッドによるピクセルデータの更新

PhotoImage クラスが提供する put メソッドは、指定したカラーコードの色を指定した座標に設定するメソッドになります。

ピクセルデータの設定

カラーコードとは色を「#FFDD00」などのように、「#」と6桁の16進数の英数字で表現したものになります。

6桁の英数字はそれぞれ下記を表します。

  • 最初の2桁 :赤色の強さ(0〜255の値を16進数で表現)
  • 真ん中の2桁:緑色の強さ(0〜255の値を16進数で表現)
  • 最後の2桁 :青色の強さ(0〜255の値を16進数で表現)

座標(x, y)の色を color に設定する際には、put メソッドを下記のように実行します。

putの使用例
# image は PhotoImage クラスのインスタンス
color = "#FFDD00"
pixel = image.putt(color, to=(x, y))

引数 to には color を設定する座標をタプル形式で指定します。

スポンサーリンク

ピクセルデータに対する透過処理

画像フォーマットによっては各ピクセルに対して透過設定を行うことが可能です(例えば GIF や PNG など)。透過設定が ON のピクセルは画像を表示すると透けて見えます。

これらの透過設定を行うために、PhotoImage クラスでは transparency_get メソッドと transparency_set メソッドが用意されています。

transparency_get メソッドによる透過設定の取得

transparency_get メソッドは指定した座標の透過設定を取得するメソッドです。

透過設定の取得

座標(x, y)の透過設定を取得する際には、transparency_get メソッドを下記のように実行します。

transparency_getの使用例
# image は PhotoImage クラスのインスタンス
transparency = image.transparency_get(x, y)

座標(x, y)の透過設定が ON の場合は transparency に True が格納され、OFF の場合は False が格納されます。

透過設定の取得

transparency_get では「完全に透明」 or 「それ以外の情報」しか取得できないようです(PNG 画像で確認・Tkinter version 8.6 で確認)

完全に透明の場合は True が返却され、それ以外は False が返却されます

半分程度だけ透明に設定されていても  False が返却されるので注意してください

transparency_set メソッドによる透過設定の更新

transparency_set メソッドは指定した座標に透過設定を行うメソッドです。

透過設定

座標(x, y)に透過設定を ON にする際には、transparency_set メソッドを下記のように実行します。

transparency_setの使用例
# image は PhotoImage クラスのインスタンス
image.transparency_set(x, y, True)

ピクセル単位で画像処理を行うアプリのサンプルスクリプト

最後にピクセル単位で画像処理を行うアプリのサンプルスクリプトを紹介しておきます。

サンプルスクリプト

下記の Python スクリプトは、ここまで解説してきた get メソッド、put メソッド、transparency_get メソッド、transparency_set メソッドを利用し、画像の左右反転を行うアプリになります。

画像の左右反転を行うアプリ
import tkinter
import tkinter.filedialog

class Application(tkinter.Tk):
    def __init__(self):
        super().__init__()
        
        # キャンバスのサイズ
        self.canvas_width = 400
        self.canvas_height = 400

        # アプリのウィンドウのサイズ設定
        self.geometry("1000x430")

        # 1つ目のキャンバスの作成と配置
        self.before_canvas = tkinter.Canvas(
            self,
            width=self.canvas_width,
            height=self.canvas_height,
            bg="red"
        )
        self.before_canvas.grid(row=1, column=1)

        # 2つ目のキャンバスの作成と配置
        self.after_canvas = tkinter.Canvas(
            self,
            width=self.canvas_width,
            height=self.canvas_height,
            bg="blue"
        )
        self.after_canvas.grid(row=1, column=2)

        # ボタンを配置するフレームの作成と配置
        self.button_frame = tkinter.Frame()
        self.button_frame.grid(row=1, column=3)

        # ファイル読み込みボタンの作成と配置
        self.load_button = tkinter.Button(
            self.button_frame,
            text="ファイル選択",
            command=self.push_load_button
        )
        self.load_button.pack()

        # 反転ボタンの作成と配置
        self.flip_button = tkinter.Button(
            self.button_frame,
            text="反転",
            command=self.push_flip_button
        )
        self.flip_button.pack()

        # 画像オブジェクトの設定(初期はNone)
        self.before_image = None
        self.after_image = None

        # キャンバスに描画中の画像(初期はNone)
        self.before_canvas_obj= None
        self.after_canvas_obj = None

    def push_load_button(self):
        'ファイル選択ボタンが押された時の処理'

        # ファイル選択画面を表示
        file_path = tkinter.filedialog.askopenfilename(
            initialdir="."
        )

        if len(file_path) != 0:

            # Tkinter用画像オブジェクトの作成
            self.before_image = tkinter.PhotoImage(file=file_path)

            # 画像の描画位置を調節
            x = int(self.canvas_width / 2)
            y = int(self.canvas_height / 2)

            # キャンバスに描画中の画像を削除
            if self.before_canvas_obj is not None:
                self.before_canvas.delete(self.before_canvas_obj)

            # 画像を1つ目のキャンバスに描画
            self.before_canvas_obj = self.before_canvas.create_image(
                x, y,
                image=self.before_image
            )

    def push_flip_button(self):
        '反転ボタンが押された時の処理'

        if self.before_image is not None:
            # 1つ目のキャンバスに描画している画像をコピー
            self.after_image = self.before_image.copy()


            # ここから画像の反転処理

            # 画像の幅と高さを取得
            width = self.before_image.width()
            height = self.before_image.height()

            # 全ピクセルに対するループ
            for y in range(height):
                for x in range(width):

                    # (x,y)座標のピクセルデータを取得
                    pixel = self.before_image.get(x, y)

                    # (x,y)座標の透過設定を取得
                    transparency = self.before_image.transparency_get(x, y)

                    # ピクセルデータを r, g, b に分解
                    r, g, b = pixel

                    # r, g, b 値をカラーコードに変換
                    color = "#" + format(r, '02x') + format(g, '02x') + format(b, '02x')

                    # 取得した色と透過設定を左右方向に反転した座標のピクセルデータに設定
                    self.after_image.put(color, to=(width - x - 1, y))
                    self.after_image.transparency_set(width - x - 1, y, transparency)

            # 画像の描画位置を調節
            x = int(self.canvas_width / 2)
            y = int(self.canvas_height / 2)

            # キャンバスに描画中の画像を削除
            if self.after_canvas_obj is not None:
                self.after_canvas.delete(self.after_canvas_obj)

            # 画像を描画
            self.after_canvas_obj = self.after_canvas.create_image(
                x, y,
                image=self.after_image
            )
    
app = Application()
app.mainloop()

スクリプトの解説

まずアプリ全体の解説をしておきます。

スクリプトを実行すると、下のようなアプリが起動します。

起動するアプリ

「ファイル選択」から画像ファイル(GIF や PNG など)を選択すると、その画像が右側のキャンバスに描画されます(透過 GIF を選択すると下の図のように背景が透けて見えます)。

ファイル選択した結果

さらに「反転」ボタンを押すと、画像を左右反転した結果が右側のキャンバスに描画されます。

画像を反転した結果

猫以外のピクセルは透過設定を ON にしていますので、左右のキャンバスの背景の色が透けて見えているのが確認できると思います。

この「反転」ボタンを押した時には push_flip_button メソッドが実行されます。

下記のように全ピクセルに対してループを行い、

全ピクセルに対するループ

# 画像の幅と高さを取得
width = self.before_image.width()
height = self.before_image.height()

# 全ピクセルに対するループ
for y in range(width):
    for x in range(height):

このループの中では、下記のように座標(x, y)のピクセルデータと透過設定を取得しています。

ピクセルデータと透過設定の取得
# (x,y)座標のピクセルデータを取得
pixel = self.before_image.get(x, y)

# (x,y)座標の透過設定を取得
transparency = self.before_image.transparency_get(x, y)

さらに、下記により取得したピクセルデータをカラーコードに変換後、そのカラーコードと透過設定を座標(x, y)を左右反転した座標(width - x - 1, y)に設定しています。

ピクセルデータと透過設定の設定
# 画素値を r, g, b に分解
r, g, b = pixel

# r, g, b 値をカラーコードに変換
color = "#" + format(r, '02x') + format(g, '02x') + format(b, '02x')

# 取得した色と透過設定を左右方向に反転した位置の画素に設定
self.after_image.put(color, to=(width - x - 1, y))
self.after_image.transparency_set(width - x - 1, y, transparency)

こんな感じで、各座標のピクセルデータと透過設定を取得し、その取得した情報を左右反転した位置となる座標に設定することで、画像の左右反転を行っています。

まとめ

このページでは、PhotoImage クラスが提供する get メソッドと set メソッド、さらには transparency_get メソッドと transparency_set メソッドを利用し、各座標の情報の取得と設定を行うことで画像処理を行う方法について解説しました。

今回は画像の左右反転する例を用いましたが、同様にして画像のリサイズや回転なども自分でプログラミングすれば Tkinter だけで様々な画像処理を行えるアプリを作ることができます。

でも処理速度はめちゃめちゃ遅いですね…。実用性を考えると素直に OpenCV や PIL を利用する方が良いと思います。

ただ、今回紹介した方法は Tkinter のみで気軽に画像処理アプリが作成できるメリットもあります。

まずはアプリ開発の入門として参考にしてみてはいかがでしょう?!

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です