mieki256's diary



2022/03/22(火) [n年前の日記]

#1 [python] pycairoの描画結果をtkinterで表示

Python + pycairo + tkinter を再勉強中。pycairo で描画した結果を、tkinter を使ってデスクトップ画面上に表示したい。

実験環境は、Windows10 x64 21H2。

各モジュールについて。 :

一応簡単に説明。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

必要なモジュールのインストール。 :

Windows版Pythonの場合、tkinter (Tkinter) は標準で同梱されているので、別途インストールしないで済む。

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 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()

実行すると、こんな感じのウインドウが表示される。

ss_01_pycairo_in_tkinter.png

左上に赤、右上に緑、左下に青の四角を pycairo で描画して、結果を tkinter で表示してる。

少し解説。 :

Python 2.7 と 3.x に対応させるあたりでちょっと面倒だったのでメモ。

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 が入れ替わってしまう。

ss_02_pycairo_in_tkinter_failure.png


故に、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

以上です。

過去ログ表示

Prev - 2022/03 - 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 31

カテゴリで表示

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


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

Powered by hns-2.19.6, HyperNikkiSystem Project