TkinterでImage Viewerを制作

#1.はじめに

前回の記事に、**[Tkinterのテンプレート化](https://qiita.com/kotai2003/items/fe9b5e59c7164a95ded8)**についてご紹介しました。
今回の記事では、このテンプレートを元に、Image Viewerのアプリケーションの作り方をご紹介します。


###環境
Windows 10 64 bit
Python 3.7
IDE : Pycharm



#2.Image Viewerの概要

次の図に、今回作成するImage Viewerアプリケーションのポンチ絵を示します。。
このアプリケーションの機能は、画像ファイルを選択し、その画像を表示することだけです。
そして、表示画面を消す機能とアプリケーションを終了する機能を追加します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208980/926322d5-9501-00c0-405a-2c22a1d4762e.png)
`写真の人物は、アメリカの免疫学の権威のRalph教授です。`

#3.Image Viewerの設計

次の図に、Image Viewerのレイアウトを示します。

アプリケーションの**部品であるWidget**を用意します。
1.Canvas Widget : Imageを表示するための入れ箱。
2.Label Frame Widget : ボタンを三つ用意しますが、それらのボタンをまとめるための入れ箱。
3.Button : Load Image, Clear Image, Quit Applicationの三つのボタン。

各Widgetの配置には、gridメソッドを用います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208980/9b050336-7788-eb92-7a74-55772104ae63.png)

###Tkinter テンプレートの説明

**[Tkinterのテンプレート化記事](https://qiita.com/kotai2003/items/fe9b5e59c7164a95ded8)に紹介されたテンプレートのプログラムコードです。

テンプレートの構成　

1. Applicationクラス
    Widget設定＆配置, Event Callback関数を定義
2. main関数

このテンプレートに、前章に説明したWidgetの設定・配置、そしてEvent Callback関数を記入する手順で、コードを**組み立てます。** (骨組み→肉付け）

```Python
import tkinter as tk
from tkinter import ttk

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

        self.master.geometry("300x300")
        self.master.title("Tkinter with Class Template")

        self.create_widgets()

    def create_widgets(self):
        pass

    def callBack(self):
        pass

def main():
    root = tk.Tk()
    app = Application(master=root)#Inherit
    app.mainloop()

if __name__ == "__main__":
    main()

```

##3.1.Canvasの設定

Class Applicationのcreate_widgetsメソッド部分にCanvas Widgetを設定、配置します。
配置には、Gridメソッドを利用します。

```Python
    def create_widgets(self):
        #Canvas
        self.canvas1 = tk.Canvas(self)
        self.canvas1.configure(width=640, height=480, bg='orange')
        self.canvas1.create_rectangle(0,0,120, 70, fill='green')
        self.canvas1.grid(column=1, row=0)
        self.canvas1.grid(padx=20, pady=20)
```


##3.2.File Open -> Image Load
ファイルを開き、Imageファイルをロードする部分です。
Buttonを設定した後、Event Callback関数のloadImage()を定義します。
LoadImage()の内部のif文は、画像の縦横比を見て、Canvasのサイズに合わせ、元画像をリサイズするために追加しました。
そして、画像ファイルの変換として、openCVのRGB変換、PILに変換、imageTKに変換する部分が続きます。
最後に、`self.canvas1.create_image`で画像をCanvas Widgetに表示します。

```Python

    def create_widgets(self):
       
        #File open and Load Image
        self.button_open = ttk.Button(self.frame_button)
        self.button_open.configure(text = 'Load Image')
        self.button_open.grid(column=0, row=1)
        self.button_open.configure(command=self.loadImage)
       
    # Event Call Back
    def loadImage(self):

        self.filename = filedialog.askopenfilename()
        
        self.image_bgr = cv2.imread(self.filename)
        
        self.height, self.width = self.image_bgr.shape[:2]
        if self.width > self.height:
            self.new_size = (640,480)
        else:
            self.new_size = (480,480)
        
        self.image_bgr_resize = cv2.resize(self.image_bgr, self.new_size, interpolation=cv2.INTER_AREA)
        self.image_rgb = cv2.cvtColor( self.image_bgr_resize, cv2.COLOR_BGR2RGB )  # imreadはBGRなのでRGBに変換
        self.image_PIL = Image.fromarray(self.image_rgb) # RGBからPILフォーマットへ変換
        self.image_tk = ImageTk.PhotoImage(self.image_PIL) # ImageTkフォーマットへ変換
        self.canvas1.create_image(320,240, image=self.image_tk)

```
下記の図は、ファイルを開くときにキャプチャした画像です。この機能を使うため、`filedialog.askopenfilename()`を利用します。
しかし、フォルダー名に日本語文字が(UTF-8)ある場合、エラーが出ます。今回は、画像ファイルが格納されるフォルダー名は、英語にするようにお願いします。（もし、この問題の解決方法のご存知の方は、メッセージを残してください。）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208980/ef340fb5-dbea-7bac-cde1-c94e9edf52de.png)


##3.3.画面クリア

Canvas上の画面を消す機能です。`canvas.delete()`メソッドを利用します。

```Python
    def clearImage(self):
        self.canvas1.delete("all")
```

##3.4.Quitボタン

終了の際に、MessageBoxを表示させ「本当に終了するの？」とユーザーの判断をもう一度確認する部分です。


```Python
    def quit_app(self):
        self.Msgbox = tk.messagebox.askquestion("Exit Applictaion", "Are you sure?", icon="warning")
        if self.Msgbox == "yes":
            self.master.destroy()
        else:
            tk.messagebox.showinfo("Return", "you will now return to application screen")
```
下記の図は、MesageBoxが表示されたときの画面です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208980/930a1088-ab76-0a6e-c55c-b5f183181c36.png)

#4.実行結果

次の図に、実行結果を表示します。設計通りに動作することが確認できました。
Clear ImageやQuitもOKであることが確認できました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208980/5f36b9f6-90d8-b827-0c2b-ce7008075d5d.png)

#5.まとめ

1. Tkinterの基本テンプレートを利用して、Image Viewer Applicationを作成しました。
2. 基本テンプレートに、必要なWidgetを追加することで、様々な目的のアプリケーションの作成が可能であることを確認しました。
3. なかなか優れものですね。Tkinter。


#6.全体コード

```Python
import tkinter as tk
from tkinter import ttk

import sys
import os
from tkinter import *
from tkinter import messagebox,filedialog
import numpy as np
from PIL import Image, ImageTk
import cv2
# coding: utf-8
# Folder名に日本語文字を入れたらダメでした。


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

        self.master.geometry("800x600")
        self.master.title("Tkinter with Class Template")

        self.create_widgets()



    def create_widgets(self):
        #Canvas
        self.canvas1 = tk.Canvas(self)
        self.canvas1.configure(width=640, height=480, bg='orange')
        self.canvas1.create_rectangle(0,0,120, 70, fill='green')
        self.canvas1.grid(column=1, row=0)
        self.canvas1.grid(padx=20, pady=20)

        #Frame
        self.frame_button = ttk.LabelFrame(self)
        self.frame_button.configure(text=' Button Frame ')
        self.frame_button.grid(column=1,row=1)
        self.frame_button.grid(padx=20, pady=20)

        #File open and Load Image
        self.button_open = ttk.Button(self.frame_button)
        self.button_open.configure(text = 'Load Image')
        self.button_open.grid(column=0, row=1)
        self.button_open.configure(command=self.loadImage)

        # Clear Button
        self.button_clear = ttk.Button( self.frame_button )
        self.button_clear.configure( text='Clear Image' )
        self.button_clear.grid( column=1, row=1 )
        self.button_clear.configure(command=self.clearImage)

        # Quit Button
        self.button_quit = ttk.Button( self.frame_button )
        self.button_quit.config( text='Quit' )
        self.button_quit.grid( column=2, row=1 )
        self.button_quit.configure(command = self.quit_app)

    # Event Call Back
    def loadImage(self):

        #self.folder_name = filedialog.askdirectory()
        self.filename = filedialog.askopenfilename()
        #print(self.folder_name)
        print(self.filename)

        self.image_bgr = cv2.imread(self.filename)
        self.height, self.width = self.image_bgr.shape[:2]
        print(self.height, self.width)
        if self.width > self.height:
            self.new_size = (640,480)
        else:
            self.new_size = (480,480)

        self.image_bgr_resize = cv2.resize(self.image_bgr, self.new_size, interpolation=cv2.INTER_AREA)
        self.image_rgb = cv2.cvtColor( self.image_bgr_resize, cv2.COLOR_BGR2RGB )  # imreadはBGRなのでRGBに変換

       # self.image_rgb = cv2.cvtColor(self.image_bgr, cv2.COLOR_BGR2RGB) # imreadはBGRなのでRGBに変換
        self.image_PIL = Image.fromarray(self.image_rgb) # RGBからPILフォーマットへ変換
        self.image_tk = ImageTk.PhotoImage(self.image_PIL) # ImageTkフォーマットへ変換
        self.canvas1.create_image(320,240, image=self.image_tk)


    def clearImage(self):
        self.canvas1.delete("all")
    
    def quit_app(self):
        self.Msgbox = tk.messagebox.askquestion("Exit Applictaion", "Are you sure?", icon="warning")
        if self.Msgbox == "yes":
            self.master.destroy()
        else:
            tk.messagebox.showinfo("Return", "you will now return to application screen")

def main():
    root = tk.Tk()
    app = Application(master=root)#Inherit
    app.mainloop()

if __name__ == "__main__":
    main()

```

#7.参考資料
1.[Tkinterのクラス化のテンプレート](https://qiita.com/kotai2003/items/fe9b5e59c7164a95ded8)
2.[【Python】Tkinterのcanvasを使ってみる](https://qiita.com/nnahito/items/2ab3ad0f3adacc3314e6)
2.[Tkinterで作成したウインドウにOpenCV-Pythonの画像を表示](https://qiita.com/mizoe@github/items/bc56441e80b3ccd5b2a4)