自動解放を抑制しないと python3, tkinter で画像が表示されない
結論:
画像を表示している間はその PhotoImage インスタンスを保持しておく必要がある。
(厳密には tkinter.Image のサブクラスのインスタンス)
保持していないと画像は表示されない。
環境:
tkinter.TkVersion: 8.6
python3 --version: Python 3.8.10
問題:
次のコードは期待通りに動く。赤いウィンドウにうずまき模様を書いたgif画像を表示するもの。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import tkinter
base64_encoded_gif = (
"R0lGODdhMgAyAIABAP8A/7LN1SwAAAAAMgAyAAACaYyPqcvtD6OctNqLs968+w+G4kiW5omm6"
"sq27gvH8hwDAYDnrm0fPcu7BVvDIfBmMK54ut9S4VTlplEajTndIX1a5vbYU0q34hS2jKpW01"
"Sc9Z0UZonf+ti7PhXtZjI/LYemB7XTlvdSAAA7")
window = tkinter.Tk()
canvas = tkinter.Canvas(window, bg="red")
canvas.place(x=0, y=0)
def func():
photo = tkinter.PhotoImage(data=base64_encoded_gif)
canvas.create_image(0, 0, image=photo, anchor=tkinter.NW)
global photo_holder
photo_holder = photo
func()
window.mainloop()
コードから photo_holder = photo
を消すと photo
は描画されない(期待に反する)。エラーも出ない。
mainloop()
が動いている最中でも同様の問題が起きる。
例えば次のような場合
threading.Thread
を使った別スレッドで、新たなPhotoImage
を作成し
canvas.itemconfig(created_image, image=new_photoimage)
などで表示画像を変更しようとした場合でも、
new_photoimage
の中身であるPhotoImageインスタンス
を保持する変数がスコープからいなくなると、期待した描画は起こらない。エラーも出ない。
window.mainloop()
内のイベント処理がされる前にphoto
の中身が解放されてしまうため(たぶん)。
( 解放されるとしたらfunc()
を抜けたタイミングか? )
解放されないようにphoto
の中身であるPhotoImageインスタンス
を保持しておく必要がある。
上の例ではグローバルスコープ変数にPhotoImageインスタンス
を入れることで解放を抑制している。
PhotoImage
の初期化方法にかかわらずこの問題は起きる。
例えば:
tkinter.PhotoImage(file="image.png")
PIL.ImageTk.PhotoImage(file="image2.png")
PIL.ImageTk.PhotoImage(image=rgb_pil_array)
・・・なんで?:
python
にはC++
のようなスコープ抜け時の解放処理はない(そもそもpython
のオブジェクト
はプリミティブ型
を除いてC++
で言えばポインター扱い。だからC++
だったとしてもデストラクタは走らない)- だったとしても
create_image
を通じてtk
がphoto
を保持するはず(←tkinterにこの仕様が抜けている?) - ※
create_image
の結果は int か str であってオブジェクトではない