2022/04/07(木) [n年前の日記]
#1 [python] tkinterのキャンバスで画像を拡大縮小させるスクリプトの動作確認中
_昨日作成した
Python + tkinter + Pillow を使って画像表示するスクリプトを、Ubuntu Linux 20.04 LTS (64bit)上でも動かしてみた。
◎ メモリエラーの問題。 :
Windows10 x64 21H2 + Python 2.7.18 32bit 上では、1024 x 1024の画像を8倍(8192 x 8192)まで拡大表示した際にメモリエラーが発生していたのだけど。
Ubuntu Linux 20.04 LTS上なら、Python 2.7.18 64bit、Python 3.8.10 64bit、どちらも8倍まで拡大表示できた。
つまり、この問題は、Python や Pillow のバージョンが原因では無く、32bit版か否かに起因する、ということになるのだろう…。
Ubuntu Linux 20.04 LTS上なら、Python 2.7.18 64bit、Python 3.8.10 64bit、どちらも8倍まで拡大表示できた。
つまり、この問題は、Python や Pillow のバージョンが原因では無く、32bit版か否かに起因する、ということになるのだろう…。
◎ Pillowのアルゴリズム指定が変更された。 :
Ubuntu Linux 20.04 LTS + Python 3.8.10 + Pillow 9.1.0 で動かした際、拡大縮小アルゴリズムの種類を示す Image.LANCZOS や Image.NEAREST という指定に対して警告が表示された。
Image.LANCZOS や Image.NEAREST は Pillow 10.x で削除される予定らしい…。Image.Resampleing.LANCZOS や Image.Resampling.NEAREST と記述せよとのこと。
しかし、新しい記述の仕方をしたら、例えば Python 2.7.x + Pillow 6.x.x で動かないスクリプトになりそうな気がする…。まあ、以下のような書き方をすれば動きそう。たぶん。
$ python3 04_canvas_zoom4.py 04_canvas_zoom4.py:173: DeprecationWarning: LANCZOS is deprecated and will be removed in Pillow 10 (2023-07-01). Use Resampling.LANCZOS instead. self.im_pad = self.im.resize((w, h), Image.LANCZOS) 04_canvas_zoom4.py:194: DeprecationWarning: LANCZOS is deprecated and will be removed in Pillow 10 (2023-07-01). Use Resampling.LANCZOS instead. self.im_pad = ImageOps.pad(self.im, (cw - 4, ch - 4), method=Image.LANCZOS) 04_canvas_zoom4.py:179: DeprecationWarning: NEAREST is deprecated and will be removed in Pillow 10 (2023-07-01). Use Resampling.NEAREST or Dither.NONE instead. self.im_pad = self.im.resize((w, h), Image.NEAREST)
Image.LANCZOS や Image.NEAREST は Pillow 10.x で削除される予定らしい…。Image.Resampleing.LANCZOS や Image.Resampling.NEAREST と記述せよとのこと。
しかし、新しい記述の仕方をしたら、例えば Python 2.7.x + Pillow 6.x.x で動かないスクリプトになりそうな気がする…。まあ、以下のような書き方をすれば動きそう。たぶん。
try: mkind = Image.Resampling.LANCZOS except Exception: mkind = Image.LANCZOS self.im_pad = self.im.resize((w, h), mkind)
◎ 修正版のスクリプト。 :
そんなわけで、スクリプトを少し修正。Windows10 x64 21H2 上でも、Ubuntu Linux 20.04 LTS 上でも、どちらでも動いてくれた。
_04_canvas_zoom4.py
使用画像は以下。
_tex_1024.png
_04_canvas_zoom4.py
使用画像は以下。
_tex_1024.png
◎ zoom や subsample というメソッドもあった。 :
tkinter の PhotoImage についてググってたら、zoom や subsample という、拡大や縮小に使えるメソッドがあると知った。
整数を指定することしかできないので、細かいリサイズはできないけれど、これらのメソッドを使えば、Pillow を使って画像をリサイズするのと比べて、メモリエラーが出にくいのではと思えてきた。Pillow の ImageTk.PhotoImage() に、リサイズ後の巨大な Image を渡してしまうとメモリエラーが発生するわけだけど、PhotoImage.zoom() を使って拡大処理をすれば、Pillow の ImageTk.PhotoImage() は元々の画像サイズで変換することになるので、メモリエラーを回避できるのでは、と…。
ということで、試してみた。
_01_photoimage_zoom.py
使用画像は以下。
_tex_1024.png
動作確認環境は以下。
py 01_photoimage_zoom.py で実行。以下のような感じで動作した。
Python 2.7.18 32bit + Pillow 6.2.2 で処理をした場合、1024 x 1024 の画像を8倍(8192 x 8192)まで拡大させるとメモリエラーが発生してしまうけれど。.zoom() を使うと、Python 2.7.18 32bit上で9倍(9216 x 9216)まで拡大してもメモリエラーは出なかった。
しかし、どこまでも拡大できるわけでもないようで…。11倍(11264 x 11264)まで拡大しようとしたらメモリエラーが発生した。
また、10倍(10240 x 10240)まで拡大してから、9倍に戻そうとしたら、そのタイミングでもメモリエラーが発生した。
ということで、.zoom() を使えば、Python 32bit版上でも、Pillow でリサイズ処理をする場合と比べて、もうちょっと大きく拡大表示ができることが分かった。ただ、拡大率を上げていくと、そのうちどこかでメモリエラーが発生するし、拡大すると処理時間が目に見えて遅くなるあたりはどちらも同じなので、五十歩百歩な感も否めない…。
余談。Pillow の ImageTk.PhotoImage() を使って得られた PhotoImage に対して、.zoom() や .subsample() を使いたい場合は、hoge._PhotoImage__photo.zoom(n) といった具合に記述するらしい。「._PhotoImage__photo.」を挿入することで、.zoom() 等が呼べるようになる模様。
_python - PhotoImage zoom - Stack Overflow
_Tkinter Photoimage | Delft Stack
- PhotoImage.zoom(n) : 整数倍(n倍)で拡大する。
- PhotoImage.subsample(n) : 整数分の一(1/n)で縮小する。
整数を指定することしかできないので、細かいリサイズはできないけれど、これらのメソッドを使えば、Pillow を使って画像をリサイズするのと比べて、メモリエラーが出にくいのではと思えてきた。Pillow の ImageTk.PhotoImage() に、リサイズ後の巨大な Image を渡してしまうとメモリエラーが発生するわけだけど、PhotoImage.zoom() を使って拡大処理をすれば、Pillow の ImageTk.PhotoImage() は元々の画像サイズで変換することになるので、メモリエラーを回避できるのでは、と…。
ということで、試してみた。
_01_photoimage_zoom.py
try: import Tkinter as tk except Exception: import tkinter as tk from PIL import Image, ImageTk class App(tk.Frame): def __init__(self, master=None): tk.Frame.__init__(self, master) self.frm = tk.Frame(self.master) self.frm.pack(expand=1, fill=tk.BOTH) self.frm.rowconfigure(1, weight=1) self.frm.columnconfigure(2, weight=1) self.btn0 = tk.Button(self.frm, text="-", command=lambda: self.dec_ratio()) self.btn1 = tk.Button(self.frm, text="+", command=lambda: self.inc_ratio()) self.zoomstr = tk.StringVar() self.zoomlbl = tk.Label(self.frm, textvariable=self.zoomstr) self.btn0.grid(row=0, column=0, ipadx=4) self.btn1.grid(row=0, column=1, ipadx=4) self.zoomlbl.grid(row=0, column=2) self.canvas = tk.Canvas(self.frm, bg="#666666") self.canvas.grid(row=1, column=0, columnspan=3, sticky=tk.N + tk.S + tk.W + tk.E) self.im = Image.open("tex_1024.png") self.photo_image = ImageTk.PhotoImage(image=self.im) self.ratio = 1 self.set_image() def set_image(self): self.canvas.delete("all") if self.ratio == 1: # 1:1 self.zoomstr.set("%d%%" % (self.ratio * 100)) self.zoom_image = self.photo_image elif self.ratio > 1: # zoom (x 2, 3, 4, ...) zm = int(self.ratio) self.zoomstr.set("%d%%" % (self.ratio * 100)) self.zoom_image = self.photo_image._PhotoImage__photo.zoom(zm) else: # subsample (/ 2, 3, 4, ...) zm = -self.ratio r = int(100 / zm) self.zoomstr.set("%d%%" % r) self.zoom_image = self.photo_image._PhotoImage__photo.subsample(zm) self.canvas.create_image(0, 0, image=self.zoom_image, anchor=tk.NW) def inc_ratio(self): maxratio = 16 if self.ratio < maxratio: self.ratio = self.ratio + 1 self.ratio = min([maxratio, self.ratio]) if -1 <= self.ratio <= 1: self.ratio = 1 self.set_image() def dec_ratio(self): minratio = -10 if self.ratio > minratio: self.ratio = self.ratio - 1 self.ratio = max([minratio, self.ratio]) if -1 <= self.ratio <= 0: self.ratio = -2 self.set_image() def main(): root = tk.Tk() app = App(master=root) app.mainloop() if __name__ == '__main__': main()
使用画像は以下。
_tex_1024.png
動作確認環境は以下。
- Windows10 x64 21H2 + Python 3.9.12 64bit + Pillow 9.1.0
- Windows10 x64 21H2 + Python 2.7.18 32bit + Pillow 6.2.2
py 01_photoimage_zoom.py で実行。以下のような感じで動作した。
Python 2.7.18 32bit + Pillow 6.2.2 で処理をした場合、1024 x 1024 の画像を8倍(8192 x 8192)まで拡大させるとメモリエラーが発生してしまうけれど。.zoom() を使うと、Python 2.7.18 32bit上で9倍(9216 x 9216)まで拡大してもメモリエラーは出なかった。
しかし、どこまでも拡大できるわけでもないようで…。11倍(11264 x 11264)まで拡大しようとしたらメモリエラーが発生した。
> py -2 01_photoimage_zoom.py Exception in Tkinter callback Traceback (most recent call last): File "C:\Python\Python27\lib\lib-tk\Tkinter.py", line 1547, in __call__ return self.func(*args) File "01_photoimage_zoom.py", line 32, in <lambda> self.btn1 = tk.Button(self.frm, text="+", command=lambda: self.inc_ratio()) File "01_photoimage_zoom.py", line 75, in inc_ratio self.set_image() File "01_photoimage_zoom.py", line 58, in set_image self.zoom_image = self.photo_image._PhotoImage__photo.zoom(zm) File "C:\Python\Python27\lib\lib-tk\Tkinter.py", line 3400, in zoom self.tk.call(destImage, 'copy', self.name, '-zoom',x,y) TclError: not enough free memory for image buffer
また、10倍(10240 x 10240)まで拡大してから、9倍に戻そうとしたら、そのタイミングでもメモリエラーが発生した。
ということで、.zoom() を使えば、Python 32bit版上でも、Pillow でリサイズ処理をする場合と比べて、もうちょっと大きく拡大表示ができることが分かった。ただ、拡大率を上げていくと、そのうちどこかでメモリエラーが発生するし、拡大すると処理時間が目に見えて遅くなるあたりはどちらも同じなので、五十歩百歩な感も否めない…。
余談。Pillow の ImageTk.PhotoImage() を使って得られた PhotoImage に対して、.zoom() や .subsample() を使いたい場合は、hoge._PhotoImage__photo.zoom(n) といった具合に記述するらしい。「._PhotoImage__photo.」を挿入することで、.zoom() 等が呼べるようになる模様。
_python - PhotoImage zoom - Stack Overflow
_Tkinter Photoimage | Delft Stack
[ ツッコむ ]
以上です。