2022/03/22(火) [n年前の日記]
#1 [python] pycairoの描画結果をtkinterで表示
Python + pycairo + tkinter を再勉強中。pycairo で描画した結果を、tkinter を使ってデスクトップ画面上に表示したい。
実験環境は、Windows10 x64 21H2。
実験環境は、Windows10 x64 21H2。
- Python 2.7.18 32bit + Pillow 6.2.2 + pycairo 1.8.10
- Python 3.9.11 64bit + Pillow 9.0.1 + pycairo 1.21.0
◎ 各モジュールについて。 :
一応簡単に説明。pycairo は、Python から cairo という描画ライブラリを呼び出して図形を描くためのモジュール。
_cairo - Wikipedia
_Overview - Pycairo documentation
tkinter は、Python から Tk というGUIツールキットを呼び出してGUIアプリを作れるモジュール。
_Tcl/Tk - Wikipedia
_tkinter --- Tcl/Tk の Python インターフェース - Python 3.10.0b2 ドキュメント
加えて、Pillow (PIL) という画像処理モジュールも必要。pycairo で描画した結果を tkinter で表示できるデータに変換する際に利用する。
_Pillow - Pillow (PIL Fork) 9.0.1 documentation
_cairo - Wikipedia
_Overview - Pycairo documentation
tkinter は、Python から Tk というGUIツールキットを呼び出してGUIアプリを作れるモジュール。
_Tcl/Tk - Wikipedia
_tkinter --- Tcl/Tk の Python インターフェース - Python 3.10.0b2 ドキュメント
加えて、Pillow (PIL) という画像処理モジュールも必要。pycairo で描画した結果を tkinter で表示できるデータに変換する際に利用する。
_Pillow - Pillow (PIL Fork) 9.0.1 documentation
◎ 必要なモジュールのインストール。 :
Windows版Pythonの場合、tkinter (Tkinter) は標準で同梱されているので、別途インストールしないで済む。
Python 3.x の場合、Pillow と pycairo は pip でインストールできる。
最後につけている「-U」は、更新も許可するオプション。今現在インストールされている版より新しい版がインターネットで公開されている場合は最新版に差し替えてくれる。
Python 2.7 の場合、Pillow は pip でインストールできるけど、pycairo は pip でインストールしようとするとエラーが出る。以下のページから、pygtk-all-in-one-2.24.2.win32-py2.7.msi を入手してインストールすれば、PyGTK、PyGObject、pycairo がインストールされる。
_Index of /binaries/win32/pygtk/2.24/
ちなみに、この PyGTK、PyGObject、pycairo は、昔のGIMP (2.6.x時代) で Python-Fu (GIMP-Python) を動かす際に必要になるモジュールだった。今の GIMP (Windows版、2.8.x以降) には最初から同梱されてると思う。
Python 3.x の場合、Pillow と pycairo は pip でインストールできる。
pip install Pillow -U pip install pycairo -U
最後につけている「-U」は、更新も許可するオプション。今現在インストールされている版より新しい版がインターネットで公開されている場合は最新版に差し替えてくれる。
Python 2.7 の場合、Pillow は pip でインストールできるけど、pycairo は pip でインストールしようとするとエラーが出る。以下のページから、pygtk-all-in-one-2.24.2.win32-py2.7.msi を入手してインストールすれば、PyGTK、PyGObject、pycairo がインストールされる。
_Index of /binaries/win32/pygtk/2.24/
ちなみに、この PyGTK、PyGObject、pycairo は、昔のGIMP (2.6.x時代) で Python-Fu (GIMP-Python) を動かす際に必要になるモジュールだった。今の GIMP (Windows版、2.8.x以降) には最初から同梱されてると思う。
◎ サンプルソース。 :
pycairo で描画して、Pillow で Tk 用に変換して、Tk でGUI表示をする動作サンプルを書いてみる。以下のサンプルを参考にした。
_python - Cairo with tkinter? - Stack Overflow
以下のような感じになった。
_01_pycairo_in_tkinter.py
実行すると、こんな感じのウインドウが表示される。
左上に赤、右上に緑、左下に青の四角を pycairo で描画して、結果を tkinter で表示してる。
_python - Cairo with tkinter? - Stack Overflow
以下のような感じになった。
_01_pycairo_in_tkinter.py
""" pycairo in tkinter. * Windows10 x64 21H2 + Python 2.7.18 32bit + Pillow 6.2.2 + pycairo 1.8.10 * Windows10 x64 21H2 + Python 3.9.11 64bit + Pillow 9.0.1 + pycairo 1.21.0 """ import sys if sys.version_info.major == 2: # Python 2.7 import Tkinter as tk else: # Python 3.x import tkinter as tk from PIL import Image from PIL import ImageTk import cairo def conv_surface_to_photo(surface): """Convert pycairo surface to Tk PhotoImage.""" w, h = surface.get_width(), surface.get_height() m = surface.get_data() if sys.version_info.major == 2: # Python 2.7 im = Image.frombuffer("RGBA", (w, h), m, "raw", "BGRA", 0, 1) else: # Python 3.x im = Image.frombuffer("RGBA", (w, h), m, "raw", "RGBA", 0, 1) # BGRA -> RGBA r, g, b, a = im.split() im = Image.merge("RGBA", (b, g, r, a)) # data = im.tobytes("raw", "BGRA", 0, 1) # im = Image.frombytes("RGBA", (w, h), data) return ImageTk.PhotoImage(im) def main(): print(sys.version) w, h = 640, 480 root = tk.Tk() # root.geometry("{}x{}".format(w, h)) root.surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, w, h) w = root.surface.get_width() h = root.surface.get_height() # draw something with pycairo ctx = cairo.Context(root.surface) ctx.scale(w, h) ctx.rectangle(0, 0, 0.5, 0.5) ctx.set_source_rgba(1, 0, 0, 0.8) ctx.fill() ctx.rectangle(0.5, 0, 0.5, 0.5) ctx.set_source_rgba(0, 1, 0, 0.8) ctx.fill() ctx.rectangle(0, 0.5, 0.5, 0.5) ctx.set_source_rgba(0, 0, 1, 0.8) ctx.fill() # convert cairo image surface to tk.PhotoImage root._image_ref = conv_surface_to_photo(root.surface) label = tk.Label(root, image=root._image_ref) label.pack(expand=True, fill="both") root.mainloop() if __name__ == "__main__": main()
実行すると、こんな感じのウインドウが表示される。
左上に赤、右上に緑、左下に青の四角を pycairo で描画して、結果を tkinter で表示してる。
◎ 少し解説。 :
Python 2.7 と 3.x に対応させるあたりでちょっと面倒だったのでメモ。
Python 2.7 では import Tkinter と記述するけど、Python 3.x では import tkinter と記述するように変わってた。
とりあえず、Python 2.7、3.x、どちらでも動かせるように、Python のメジャーバージョンを取得して、if文で処理を分けることにしてみた。
それと、pycairo の surface と Tk の PhotoImage は、RGBA と BGRA の違いがあるようで…。そのまま表示してしまうと、以下のように、R と B が入れ替わってしまう。
故に、pycairo で何かしらを描画した後、BGRA を RGBA に変換しないといけない。そのあたりは、conv_surface_to_photo(surface) の中で処理している。
ただ、Python 2.7 時代は Pillow 関係で「"BGRA"」という指定が使えたのだけど、Python 3.x では「"BGRA"」という指定はエラーになってしまった。今回は Pillow の .split() を使って R,G,B,Aの各チャンネルに分解してから、.merge() で並びを変更してみた。
あるいは、Pillow の .tobytes() と .frombytes() を使って BGRA と RGBA を変換することもできる。上記のソースではコメントアウトしているけれど、手元で試した感じでは動いてくれた。
Python 2.7 では import Tkinter と記述するけど、Python 3.x では import tkinter と記述するように変わってた。
とりあえず、Python 2.7、3.x、どちらでも動かせるように、Python のメジャーバージョンを取得して、if文で処理を分けることにしてみた。
import sys if sys.version_info.major == 2: # Python 2.7 import Tkinter as tk else: # Python 3.x import tkinter as tk
それと、pycairo の surface と Tk の PhotoImage は、RGBA と BGRA の違いがあるようで…。そのまま表示してしまうと、以下のように、R と B が入れ替わってしまう。
故に、pycairo で何かしらを描画した後、BGRA を RGBA に変換しないといけない。そのあたりは、conv_surface_to_photo(surface) の中で処理している。
def conv_surface_to_photo(surface): """Convert pycairo surface to Tk PhotoImage.""" w, h = surface.get_width(), surface.get_height() m = surface.get_data() if sys.version_info.major == 2: # Python 2.7 im = Image.frombuffer("RGBA", (w, h), m, "raw", "BGRA", 0, 1) else: # Python 3.x im = Image.frombuffer("RGBA", (w, h), m, "raw", "RGBA", 0, 1) # BGRA -> RGBA r, g, b, a = im.split() im = Image.merge("RGBA", (b, g, r, a)) # data = im.tobytes("raw", "BGRA", 0, 1) # im = Image.frombytes("RGBA", (w, h), data) return ImageTk.PhotoImage(im)
ただ、Python 2.7 時代は Pillow 関係で「"BGRA"」という指定が使えたのだけど、Python 3.x では「"BGRA"」という指定はエラーになってしまった。今回は Pillow の .split() を使って R,G,B,Aの各チャンネルに分解してから、.merge() で並びを変更してみた。
あるいは、Pillow の .tobytes() と .frombytes() を使って BGRA と RGBA を変換することもできる。上記のソースではコメントアウトしているけれど、手元で試した感じでは動いてくれた。
◎ 2022/03/24追記。 :
試していたら、Python 2.7.18 32bit Windows版 + tkinter も、import tkinter で動作することに気づいた…。import Tkinter じゃなくても良かったのか…。どこかの時点で変更されたのだろうか。それとも何かのモジュールをインストールすると使えるようになるのだろうか。ググってみたけれど、このあたりについて記述しているページが見つからない…。
「Python 2 は Tkinter だけど、Python 3 で tkinter にリネームされたから気をつけろ」と書かれたページなら見つかるのだけど…。
_tkinter Tutorial -> Getting started with tkinter
_24.1. Tkinter - Tcl/Tk への Python インタフェース - Python 2.7.18 ドキュメント
_TkInter - Python Wiki
「Python 2 は Tkinter だけど、Python 3 で tkinter にリネームされたから気をつけろ」と書かれたページなら見つかるのだけど…。
_tkinter Tutorial -> Getting started with tkinter
_24.1. Tkinter - Tcl/Tk への Python インタフェース - Python 2.7.18 ドキュメント
_TkInter - Python Wiki
[ ツッコむ ]
以上です。