mieki256's diary



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版か否かに起因する、ということになるのだろう…。

Pillowのアルゴリズム指定が変更された。 :

Ubuntu Linux 20.04 LTS + Python 3.8.10 + Pillow 9.1.0 で動かした際、拡大縮小アルゴリズムの種類を示す Image.LANCZOS や Image.NEAREST という指定に対して警告が表示された。

$ 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

zoom や subsample というメソッドもあった。 :

tkinter の PhotoImage についてググってたら、zoom や subsample という、拡大や縮小に使えるメソッドがあると知った。
  • 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

以上です。

過去ログ表示

Prev - 2022/04 - Next
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30

カテゴリで表示

検索機能は Namazu for hns で提供されています。(詳細指定/ヘルプ


注意: 現在使用の日記自動生成システムは Version 2.19.6 です。
公開されている日記自動生成システムは Version 2.19.5 です。

Powered by hns-2.19.6, HyperNikkiSystem Project