PySimpleGUIで画像ビュアー機能を実装するTipsの備忘録(シンプルなサンプルコード付)。
画像コンポーネントをウィンドウ全体に広げて配置する方法や、クリック位置の取得方法を日本語で見つけたい人向け。
できるもの
とてもシンプルなフォトフレーム的画像ビュアー。
カレントフォルダのimagesフォルダから画像を取得し、タイトルバーのないウィンドウに表示して3秒ごとに画像を切り替える。
画面の左半分をクリックすると前の画像を表示、右半分をクリックすると次の画像を表示する。
画面上部(ウィンドウ高さの1/10まで)をクリックすると終了ダイアログを表示する。
ESCキーで即時終了する。
ラズパイとかをタッチスクリーンにつないでフォトフレームにする時の参考になるかも。
シンプルなサンプルコード
import PySimpleGUI as sg
from PIL import Image
import io
from glob import glob
image_dir = "images" # 画像フォルダのパス
interval = 1000 * 3 # 画像切り替えインターバル(3秒)
exts = ["jpg", "png", "bmp"] # 表示する画像の拡張子
sg.theme("Black") # カラーテーマ(背景色を黒にするため)
# 画面を最大化する場合
from screeninfo import get_monitors
monitor = get_monitors()[0]
window_size = (monitor.width, monitor.height)
# 画面を最大化しない場合(下の行の#を削除すること)
#window_size = (800, 600)
# リストの画像を表示し、indexにループ補正した値を返す
def show_image(window, images, index):
image_size = (window_size[0] - 20, window_size[1] - 20) # 画像が若干見切れる対策
# indexがマイナスならリストの上限値、indexがリスト上限より上なら0を設定する
index = len(images) - 1 if index < 0 else 0 if index >= len(images) else index
path = images[index]
# 画像を縮小表示
image = Image.open(path)
image.thumbnail(image_size)
bio = io.BytesIO()
image.save(bio, format="PNG")
window["-IMAGE-"].update(data=bio.getvalue())
return index
def main():
# 画像リスト取得
images = [x for ext in exts for x in glob(f"{image_dir}/*.{ext}")] # 内包表記2回で二次元配列を一次元に平坦化
# ウィンドウ初期化
layout = [
[sg.Image(key="-IMAGE-", enable_events=True, expand_x=True, expand_y=True)],
]
window = sg.Window("Image Viewer", layout, size=window_size, return_keyboard_events=True, finalize=True, no_titlebar=True)
# メインループ
index = show_image(window, images, 0) # ループ前に先頭の画像表示
while True:
event, values = window.read(timeout=interval)
if event == "Exit" or event == sg.WIN_CLOSED or event.startswith("Escape"):
break
elif event == "__TIMEOUT__":
index = show_image(window, images, index + 1)
elif event == "-IMAGE-":
# 画像コンポーネントクリックイベント
widget = window["-IMAGE-"].Widget
y = widget.winfo_pointery() - widget.winfo_rooty()
if y < (window_size[1] // 10):
if sg.popup_ok_cancel("終了しますか?") == "OK":
break
x = widget.winfo_pointerx() - widget.winfo_rootx()
if x < (window_size[0] // 2):
index = show_image(window, images, index - 1)
else:
index = show_image(window, images, index + 1)
window.close()
if __name__ == "__main__":
main()
事前準備
Python 3.7以上に下記のパッケージをpipインストールすること
-
PySimpleGUI
- GUIパッケージ。これがないと記事が始まらない
-
Pillow
- 画像の縮小表示に必要
-
screeninfo
- 画面解像度を楽に取得できる
- 最大化しない場合やサイズ決め打ちで指定する場合はサンプルコードの
# 画面を最大化する場合
以下4行を削除して良い
実行時のフォルダにimages
サブフォルダを作成し、その中に任意の.jpg
/.png
/.bmp
画像いずれかを1個以上入れること
備忘録
モニタの解像度を取得する
screeninfoのgetmonitors
からwidth
とheight
を取得できる。
from screeninfo import get_monitors
monitor = get_monitors()[0]
window_size = (monitor.width, monitor.height)
ウィンドウをフォトフレーム向けに設定する
Windowのreturn_keyboard_events
でキーボードイベントを有効にし、初期表示で画像を表示するためにfinalize
を設定し、no_titlebar
でタイトルバーを消す。
return_keyboard_events=True
にするとESC
キーを打った時にEscape:27
の文字がevent
変数に格納される。
finalize=True
でないとwindow.read()
する前にshow_image
する処理がエラーになる。
window = sg.Window("Image Viewer", layout, size=window_size, return_keyboard_events=True, finalize=True, no_titlebar=True)
# メインループ
index = show_image(window, images, 0) # ループ前に先頭の画像表示
while True:
event, values = window.read(timeout=interval)
if event == "Exit" or event == sg.WIN_CLOSED or event.startswith("Escape"):
画像を縮小表示する
原寸で表示するだけならPySimpleGUI.Imageにファイルパスを送るだけでいいけど、アスペクト比を保ったまま縮小表示するならPillowのImage.thumbnailを使うのが便利。
記事執筆時点ではImage
はGIFとPNGしか表示できないから下記のコードでJPEG
やBMP
をPNG
化する処理も兼ねている。
このコードではアニメーションGIFを考慮していない。
image = Image.open(path)
image.thumbnail(image_size)
bio = io.BytesIO()
image.save(bio, format="PNG")
window["-IMAGE-"].update(data=bio.getvalue())
PySimpleGUIのコンポーネントを広げて表示する
Image
コンポーネントは左上に画像サイズで表示され、クリックイベントも発生しない。
expand_x
とexpand_y
を指定することでコンポーネントのサイズをウィンドウ内で広げて配置することができる。
なお、Image
コンストラクタのexpand_x
やexpand_y
引数は数か月前に追加されたので、古いバージョンだとエラーになる。
sg.Image(key="-IMAGE-", enable_events=True, expand_x=True, expand_y=True)
Image
コンポーネントのクリック座標を取得する
今回書きたかったことはこれ。
クリック座標を取得する方法をpysimplegui click position
とかpysimplegui get mouse location
とかでWeb検索しても全然見つからなかった。
でもWidgetで裏側のTkinterにアクセスできる。
クリックしたときにwinfo pointerx
などでマウスの位置を取得すればクリック座標が分かる。
widget = window["-IMAGE-"].Widget
x = widget.winfo_pointerx() - widget.winfo_rootx()
y = widget.winfo_pointery() - widget.winfo_rooty()
今回のオチ
記事を書く調べものして気づく。
あれ?
公式サンプルのPySimpleGUI/DemoPrograms/Demo_Img_Viewer.pyで大体似たようなことやってる…。
マウスホイールスクロールで切り替えたい人はそちらを参考にどうぞ。