
【PythonでGUIを作りたい】Tkinterで作る画像編集ソフト#3:内部設計の検討
こんにちは。すうちです。
最近みたい映画がたまっていて週末はその中の1つ「ハケンアニメ!」を観てきました。数年前に小説の方は読んでましたが、基本は原作通り(アニメ業界に関わってなくても)胸が熱くなり心に響いた作品でした。観れてよかったです。
それでは本題です。少し前から新しい試みで連載形式の記事を投稿しています。
前回の記事はこちらです。
今回はTkinterを使ったGUI(画像編集ソフト)構築の内部設計に関する話を書きたいと思います。
◆はじめに
GUIソフトのイメージ(作りたいもの)
再び最初に書いたメモです。
・ユーザ操作はGUI(今回はTkinter)
・画像の簡単な加工(クリップ、回転等)
・動画の簡単な編集(画像加工も可)
・動画や画像ファイルの読み書き可
できるだけPythonの簡単なプログラムやTkinterの基本機能で実現することが目標です。
やることリスト(TODO)
前回のユーザ視点の仕様からコードを書くためにやることをリストにしてみます。
・全体の内部構造、構成単位
→クラス定義、役割分担を決定
・構成単位のデータ入出力方法
→クラスの公開メソッド(関数)定義
・処理実装の不明点
→やりたいこと実現するため不明点を調査
これらを元に、内部構造や各イベント処理を考えます。
◆クラス化(役割分担と入出力定義)
Pythonは単純に関数(処理)を並べてもプログラムを作れますが、オブジェクト指向設計を取り入れてクラス(関連情報や機能をまとめた構成単位)を定義することも可能です。
今回想定の画像編集ソフトの規模であれば、1ファイルに関数を並べて書くでも良いですが、一応クラスを定義して機能単位でファイルも分けたいと思います。
実現したい機能を抽象的にとらえると、以下のキーワードがありそうです。
・GUI外観表示(部品レイアウト)
・ユーザ操作のアクション(イベント処理)
・イベント処理の計算(画像編集の結果取得)
・編集結果とGUI表示の同期(イベント処理の反映)
MVCモデル
クラスをどう定義するか?これはいくつか考え方があり実際の開発などでは前提条件で選択は変わってきますが、一つの方法としてよく使われている設計思想やデザインパターンを参考にすることがあります。
GUIなどのソフトウェア設計方法では、MVC(Model View Controller)モデルという考え方があります。
ざっくりViewはユーザ入出力や表示を担当。Modelは論理や実処理を担当。ControllerはViewとModelの仲介を担当するイメージです。Controllerは役割上お互いの公開された情報を知りえますが、ViewとModelはお互いの存在を基本的に意識しません。
最近はMVVM(Model View ViewModel)の方がよく耳にする印象ですが、こちらはMVCを発展させて更に互いの独立性を高める設計思想です。
今回の画像編集ソフトでは、MVCモデルの考え方をクラス定義の参考にしたいと思います。
クラス定義(役割分担)
クラスは、ViewGUI、ControlGUI、ModelImage3つを定義します。


クラスメソッド(入出力)
各クラス間で使うメソッドは、以下のように定義しました(補足:クラスの操作は通常メソッドと呼ばれますが、クラスが持つ関数と考えて頂ければと思います)。

図1のようにユーザ操作のイベントに沿って、Controllerの対応メソッドが呼ばれますがViewは実処理の中身は知りません(Modelの処理はControllerで隠蔽される)。これは仮にModelの処理を変えた場合もViewはその影響がないということです。
ちなみに、画像表示はViewのCanvas情報をModelまで渡して描画します(これは正直迷いました)。MVCの考え方に沿うとModelで編集した画像をViewまで戻して描画する方が良い気もしますが少し冗長な気がしたので今の定義にしてます。
自分で新規に作る場合、正直この辺の自由度は高いです。これがベストの意味ではなく、現状これが良さそうという程度で決めました(今後見直す可能性もあり)。
また動画編集は今回含めてません。こちらは機能拡張時に検討したいと思います。
◆ユーザ操作イベント処理
クラス定義(役割分担)とデータの入出力方法は決まりました。ここからはユーザ操作によるイベントの実装を考えます。
フォルダ選択(set_folder)
tkinterのfiledialog.askdirectoryを使いたいと思います。戻り値は選択したフォルダのパスです。選択後はフォルダのファイルリストをos.listdirで作成します。
今回はディレクトリですが、filedialogはファイルエクスプローラの様な使い方ができるライブラリです。
from tkinter import filedialog
dir_path = filedialog.askdirectory(initialdir=init_dir_path)
file_list = os.listdir(dir_path)

ファイル切替(prev/next)
作成したファイルリストにファイル名を保存。ファイル切替時は現在のファイル位置を更新します。切替はリスト終端と先頭をつなげる形で更新します。

画像編集(rotate/flip)
Tkinterはpngやjpeg画像を直接扱えないようです。そのため一旦Pillowライブラリを介してファイル操作を行います。Image.openで画像データ取得後、描画前にImageTk.PhotoImageでTkinterで扱える形に変換します。
画像の回転と反転はNumpyのflipとrot90を使いたいので、事前にarrayでNumpyに変換します(仕様はコメント参照)。編集後にImage.fromarrayでPillowイメージに戻します。
from PIL import Image, ImageTk
import numpy as np
pil_img= Image.open(file_path) # Pillowイメージを取得
:
tk_img = ImageTk.PhotoImage(image=pil_img)
np_img = np.array(pil_img) # Numpyに変換
:
np.flip(np_img, axis=xx) # axis=0:上下 axis=1:左右 反転
np.rot90(np_img, num) # 90°単位でnum回数分 回転
:
pil_img = Image.fromarray(np_img) # Pillowに変換
画像保存(save)
今回ファイル入力はPillowなのでファイル保存も同様にしたいと思います。Pillowイメージはsave()で保存可能です。
編集ファイルは上書きを避けるためファイル名を変更します。ファイル名は、datetime.now()で現在日時を取得後、dt.strftime()の引数(例:%H%M%S)で時:分:秒(テキスト)を追加した形です。
from datetime import datetime
dt = datetime.now()
fname = dir_path + '/' + file_name + '_' + dt.strftime('%H%M%S') + '.png'
pil_img.save(fname) # PIL_image.save("書き込み先のファイルパス指定")
編集取消(undo)
編集取消は編集前のオリジナル画像を保存しておき、イベント発生時はオリジナル画像を参照する形にします。
画像編集(clip)
クリップはマウス操作で領域指定する前提です。
Tkinterはマウスイベントは取れそうですが、GUI上の表示画像はリサイズされているので実画像のサイズと違います。GUI上のクリップ位置を本来の画像位置にマッピングが必要です。意外と考えることが多いので、もう少し実装を検討します。
◆一部仕様の見直し(設計検討後の反映)
回転・反転メニュー
当初は0/90/180/270度で回転。上(U)/下(D)、左(L)/右(R)で反転操作を考えてましたが回転は基本的に90度の連続実行で代用可能。反転は上と下。左と右は同じ結果なので、今更ですが個別に必要ないと気づきました。
編集前に戻す操作はUndoで可能なので、回転は90/180/270度の3種。反転はU/LとL/Rの2種にしたいと思います。
フォルダ選択後のファイルリスト
以前は選択ファイルの情報がなく画像表示されるまでわからない状況でした。そこでフォルダ内のファイル表示(コンボBOX)を追加します。
またプルダウンからファイル選択、ファイル切替ボタンと同様に画像表示もできるようにしたいと思います。
GUI外観(見直し後)
上記を反映した画面構成です。

今後の予定
ユーザ仕様:
GUI実現機能などの仕様検討
内部設計:
目標仕様の実現に向けた設計検討
実装・コーディング:
設計方針に基づく実装やコーディング
テスト・デバッグ:
作成したプログラムの評価
仕様変更・機能追加:
一旦作成したGUIに機能追加など検討
次回は、設計方針に基づいた全体の実装やテストを予定しています。
最後に
今回はここまでです。
前述のクラス定義やメソッドはあっさり決まった印象を持たれた方もいるかもしれませんが、実際は仮実装など含めて何回か考えなおした結果です。
私の場合、いくら頭の中で考えて事前に設計するとしても限界があるので、そういう時は仮で簡単に作ってみて設計を見直していく事も多いです。
クリップ以外は実装も固まりつつあるので、次回までに静止画編集の機能は一旦形にしたいと思います。
GUI構築の流れを大まかに把握したい方、Tkinterを使うことを検討されている方に、何か参考になれば幸いです。
最後まで読んで頂き、ありがとうございました。
ーーー
ソフトウェアを作る過程を体系的に学びたい方向けの本を書きました(連載記事に大幅に加筆・修正)
Kindle Unlimited 会員の方は無料で読めます。
#連載記事の一覧
ーーー
いいなと思ったら応援しよう!
