2017/04/19(水) [n年前の日記]
#1 [gimp][python] GIMPのPython-fuでcairo(pycairo)を使えるか実験
GIMP という、フリーで使える高機能な画像編集ソフトがあるのだけど。この GIMP は、TinyScheme (Script-fu)、もしくは Python (Python-Fu、GIMP-Python) を使ってアレコレ自動化できるスクリプト機能を持っていて。
Python が使えるなら、もしかして Python から cairo (pycairo) を使って色々な描画ができないかなー、と思いついて。ちと試してみようかなと。
Python が使えるなら、もしかして Python から cairo (pycairo) を使って色々な描画ができないかなー、と思いついて。ちと試してみようかなと。
◎ cairoを使うメリット。 :
「わざわざ cairo を使って描かなくても、GIMPが持ってる描画機能を呼び出して描けばよくね?」と言われそうな気もするけれど。
しかし、例えば…。
その点、cairo を使えたら…。
しかし、例えば…。
- スクリプトからGIMPのブラシ描画を使うには、事前に、使いたいブラシファイルを用意しなきゃいけない。
- GIMPのグラデーション塗りを使うには、事前に、グラデーションパターンを用意しなきゃいけない。
- スクリプトで前景色や背景色を変更したら、スクリプト終了時にその色が選択された状態になってしまう。
その点、cairo を使えたら…。
- ブラシファイルを用意しなくても線が引ける。
- グラデーションのパターンを用意せずともスクリプト内でいきなり色指定してグラデ塗りができる。
- GIMP側の前景色や背景色を変更しなくても好きな色で描画できる。
◎ そもそもcairoは入っているのか。 :
そもそも GIMP に cairo(pycairo) は同梱されているのだろうか。Windows10 x64 + GIMP 2.8.20 Portable で確認してみたり。
GIMPを起動して、フィルター → Python-Fu → コンソール。
import cairo と打ち込んでみる。エラーは出なかった。ということは、どうやら pycairo も一緒にインストールされてるっぽいなと。
まあ、ほんのちょっと昔、GIMP 2.6 を使ってた頃は、Python-Fu を使うにあたって Python の他にも _pygtk-all-in-one という、PyGTK、PyCairo、PyGObject が全部入ってるブツをインストールする必要があったので…。
GIMP 2.8 からは、別途 Python や pygtk-all-in-one をインストールしなくても済むようになった、ということは、GIMP 2.8 には Python のコア部分や PyGtk、PyCairo、PyGObject が最初から同梱されてるはず、と予想してたけど、どうやら当たってたようで。
いや待て。本当にそうだろうか…。実はOS側・システム側にインストールされている Python 2.7 + pycairo を使ってたりしないか…。でもまあ、それでも、手元の環境では cairo を import できたのだから、同様の環境にすることは可能なはず、ではあるよな…。
GIMPを起動して、フィルター → Python-Fu → コンソール。
import cairo と打ち込んでみる。エラーは出なかった。ということは、どうやら pycairo も一緒にインストールされてるっぽいなと。
まあ、ほんのちょっと昔、GIMP 2.6 を使ってた頃は、Python-Fu を使うにあたって Python の他にも _pygtk-all-in-one という、PyGTK、PyCairo、PyGObject が全部入ってるブツをインストールする必要があったので…。
GIMP 2.8 からは、別途 Python や pygtk-all-in-one をインストールしなくても済むようになった、ということは、GIMP 2.8 には Python のコア部分や PyGtk、PyCairo、PyGObject が最初から同梱されてるはず、と予想してたけど、どうやら当たってたようで。
いや待て。本当にそうだろうか…。実はOS側・システム側にインストールされている Python 2.7 + pycairo を使ってたりしないか…。でもまあ、それでも、手元の環境では cairo を import できたのだから、同様の環境にすることは可能なはず、ではあるよな…。
◎ まずは画像やレイヤーを新規作成。 :
Pythonコンソールからチマチマ打って、動作を一つずつ確認していくことにする。
まずは、新規画像の作成と、新規レイヤーの作成を試す。
やってることは…。
まずは、新規画像の作成と、新規レイヤーの作成を試す。
>>> from gimpfu import * >>> img = gimp.Image(512, 512, RGB) >>> layer = gimp.Layer(img, "layer01", 512, 512, RGBA_IMAGE, 100, NORMAL_MODE) >>> layer.fill(TRANSPARENT_FILL) >>> img.add_layer(layer, 0) >>> gimp.Display(img) <display> >>>GIMPのウインドウ上に画像が表示された。
やってることは…。
- from gimpfu import * は、Python-Fu を使う時のおまじない。
- img = gimp.Image(512, 512, RGB) で、512x512、RGBモードで新規画像を作成。
- layer = gimp.Layer(img, "layer01", 512, 512, RGBA_IMAGE, 100, NORMAL_MODE) で、レイヤーを作成。
- layer.fill(TRANSPARENT_FILL) で、レイヤー全体を透明色で塗り潰す。
- img.add_layer(layer, 0) で、画像にレイヤーを追加する。
- gimp.Display(img) で、画像をGIMPのウインドウに表示。
◎ cairoで描画してGIMPに渡す。 :
cairoで描画した内容を、GIMPの画像、というかレイヤーに渡してみたい。
そんな処理をしてる事例は少ない、というよりほとんど全く存在していないのだけど、諦めきれずにググっていたら以下のスクリプトに遭遇。Python-Fuスクリプトでありながら cairoを使っている。参考にさせてもらった。ありがたや。
_harmony-gimp-plugin/harmony.py at master - yihuang/harmony-gimp-plugin
cairo のサーフェイスを作成して、サーフェイスからコンテキストを作って、そのコンテキストを使って赤い四角を描画してみる。
そして、cairo のサーフェイス内容を取り出して、GIMP のレイヤーに描き込む。
レイヤーに描き込んだだけでは表示されないので、表示されるように反映させる。
一応、GIMP の画像ウインドウ上に、cairoによる描画内容が出てきた。
しかし。赤い四角を描き込んだはずなのに、青い四角が表示されてしまった。何故。
おそらく、cairo のサーフェイスから取り出したデータは BGRAの順になっているのに、GIMP側では RGBAの順でデータを持つから、ではあるまいか…。となると、BGRA の並びのデータを、RGBA の並びのデータに変換しないといけない。
そんな処理をしてる事例は少ない、というよりほとんど全く存在していないのだけど、諦めきれずにググっていたら以下のスクリプトに遭遇。Python-Fuスクリプトでありながら cairoを使っている。参考にさせてもらった。ありがたや。
_harmony-gimp-plugin/harmony.py at master - yihuang/harmony-gimp-plugin
cairo のサーフェイスを作成して、サーフェイスからコンテキストを作って、そのコンテキストを使って赤い四角を描画してみる。
>>> import cairo >>> surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 512, 512) >>> ctx = cairo.Context(surface) >>> ctx.set_source_rgb(1.0, 0.0, 0.0) >>> ctx.rectangle(128, 128, 256, 256) >>> ctx.fill()
そして、cairo のサーフェイス内容を取り出して、GIMP のレイヤーに描き込む。
>>> src = surface.get_data() >>> rgn = layer.get_pixel_rgn(0, 0, 512, 512, True, True) >>> rgn[0:512, 0:512] = str(src)
レイヤーに描き込んだだけでは表示されないので、表示されるように反映させる。
>>> layer.flush() >>> layer.merge_shadow() >>> layer.update(0, 0, 512, 512) >>> pdb.gimp_displays_flush()
一応、GIMP の画像ウインドウ上に、cairoによる描画内容が出てきた。
しかし。赤い四角を描き込んだはずなのに、青い四角が表示されてしまった。何故。
おそらく、cairo のサーフェイスから取り出したデータは BGRAの順になっているのに、GIMP側では RGBAの順でデータを持つから、ではあるまいか…。となると、BGRA の並びのデータを、RGBA の並びのデータに変換しないといけない。
◎ BGRAをRGBAに変換。 :
Ruby を使ってその手の変換処理をした際には unpack() や pack() を使ったけれど。もしかして Python にも unpack() があったりしないかな。
ググってみたら、Python の場合、
_pythonのstructモジュールを触ってみる | KentaKomai Blog
_Pythonでバイナリデータの取り扱い - 猫型エンジニアのブログ
しかし、Ruby のように、unpack("L*") みたいなイイ感じの指定はできないようで。これは4バイトずつ取り出して変換するしかないのかな。
ググってみたら、スライスとやらを使うことで、文字列の中から一部を取り出すことができるっぽい。コレを使ってみよう。
_スライスを使った部分文字列の取得 - 文字列 - Python入門
てなわけで。まずは struct を import してから…。
こんな感じで、4バイト = 1ドット分を取り出せる、かな。
ちなみに、わざわざ struct.unpack("@L", bgra) を使うのは、エンディアンに関係なく ARGB が並んだ4バイトを取り出すため。
cairo 自身は ARGB の並びでデータを保持しているつもりで動いてるけど、リトルエンディアンのCPU(Intel製CPU等)で cairo を動かすと、メモリ上では逆の並びの BGRA で記録される。なので、「そうか! cairo は BGRA の並びでデータを持ってるのだな!」と決めつけて、1バイト単位で取り出して並び替える処理を書いてしまうと、万が一ビッグエンディアンのCPU上でスクリプトを動かした際、おそらくデータは BGRA じゃなくて ARGB で並んでるだろうから、色やアルファチャンネル値がおかしなことになってしまう。はず。たぶん。
でも、struct.unpack("@L", ) を経由させれば…。「"@L"」の指定は、「その環境のネイティブなエンディアンで32bitを取り出せ」という指定なので、ARGB で並んだ 32bit = 4バイトが得られて問題は起きないはず。
さて、ARGB の4バイトが得られたので、RGBA に並べ替える。
これらを踏まえて、BGRA もしくは ARGB で並んでる cairo のサーフェイス内容を RGBA の並びに変換して返す関数、にしてみる。
ググってみたら、Python の場合、
struct.unpack(フォーマット文字列, 文字列)を使うと、unsigned long 等を指定しつつ、文字列から配列に変換できる模様。
_pythonのstructモジュールを触ってみる | KentaKomai Blog
_Pythonでバイナリデータの取り扱い - 猫型エンジニアのブログ
しかし、Ruby のように、unpack("L*") みたいなイイ感じの指定はできないようで。これは4バイトずつ取り出して変換するしかないのかな。
ググってみたら、スライスとやらを使うことで、文字列の中から一部を取り出すことができるっぽい。コレを使ってみよう。
_スライスを使った部分文字列の取得 - 文字列 - Python入門
てなわけで。まずは struct を import してから…。
>>> import struct
こんな感じで、4バイト = 1ドット分を取り出せる、かな。
>>> src = surface.get_data() >>> i0 = 0 >>> i1 = i0 + 4 >>> bgra = src[i0 : i1] >>> bgra '\x00\x00\xff\xff' >>> bgra = struct.unpack("@L", bgra) >>> bgra (4294901760L,)取り出せたっぽい。
ちなみに、わざわざ struct.unpack("@L", bgra) を使うのは、エンディアンに関係なく ARGB が並んだ4バイトを取り出すため。
cairo 自身は ARGB の並びでデータを保持しているつもりで動いてるけど、リトルエンディアンのCPU(Intel製CPU等)で cairo を動かすと、メモリ上では逆の並びの BGRA で記録される。なので、「そうか! cairo は BGRA の並びでデータを持ってるのだな!」と決めつけて、1バイト単位で取り出して並び替える処理を書いてしまうと、万が一ビッグエンディアンのCPU上でスクリプトを動かした際、おそらくデータは BGRA じゃなくて ARGB で並んでるだろうから、色やアルファチャンネル値がおかしなことになってしまう。はず。たぶん。
でも、struct.unpack("@L", ) を経由させれば…。「"@L"」の指定は、「その環境のネイティブなエンディアンで32bitを取り出せ」という指定なので、ARGB で並んだ 32bit = 4バイトが得られて問題は起きないはず。
さて、ARGB の4バイトが得られたので、RGBA に並べ替える。
>>> a = (bgra[0] >> 24) & 0x0ff >>> r = (bgra[0] >> 16) & 0x0ff >>> g = (bgra[0] >> 8) & 0x0ff >>> b = bgra[0] & 0x0ff >>> struct.pack('4B', r, g, b, a) '\xff\x00\x00\xff' >>>並び替えができた。元々、BGRA = 0x0000fff だったものが、RGBA = 0xff0000ff になってるし。
これらを踏まえて、BGRA もしくは ARGB で並んでる cairo のサーフェイス内容を RGBA の並びに変換して返す関数、にしてみる。
import struct def get_rgba_str(bgra_buf): rgba_buf = "" l = len(bgra_buf) for i in range(l / 4): i0 = i * 4 i1 = i0 + 4 bgra = struct.unpack('@L', src[i0 : i1])[0] a = (bgra >> 24) & 0x0ff r = (bgra >> 16) & 0x0ff g = (bgra >> 8) & 0x0ff b = bgra & 0x0ff rgba = struct.pack('4B', r, g, b, a) rgba_buf += rgba return rgba_bufコレをコピーして、GIMP の pythonコンソールに貼り付ければ、get_rgba_str() という関数が定義されて、以後使えるようになる。
◎ 再度cairoで描画して動作確認。 :
レイヤーを追加して。
cairoで赤い四角を描いて。
GIMP側のレイヤーに反映させる。先ほど定義した get_rgba_str() を使う。
赤い四角が表示された。(小さくガッツポーズ)
この調子なら、Python-Fu + cairo (pycairo) を使って、GIMP上で何かを描画するスクリプトを書けそう。後は Python-Fuスクリプトの書き方を調べれば…。
layer = gimp.Layer(img, "layer03", 512, 512, RGBA_IMAGE, 100, NORMAL_MODE) layer.fill(TRANSPARENT_FILL) img.add_layer(layer, 0)
cairoで赤い四角を描いて。
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 512, 512) ctx = cairo.Context(surface) ctx.set_source_rgb(1, 0, 0) ctx.rectangle(64, 64, 256, 256) ctx.fill()
GIMP側のレイヤーに反映させる。先ほど定義した get_rgba_str() を使う。
src = surface.get_data() dst = get_rgba_str(src) rgn = layer.get_pixel_rgn(0, 0, 512, 512, True, True) rgn[0:512, 0:512] = str(dst) layer.flush() layer.merge_shadow() layer.update(0, 0, 512, 512) pdb.gimp_displays_flush()
赤い四角が表示された。(小さくガッツポーズ)
この調子なら、Python-Fu + cairo (pycairo) を使って、GIMP上で何かを描画するスクリプトを書けそう。後は Python-Fuスクリプトの書き方を調べれば…。
◎ 2017/04/20追記。 :
環境変数 PATH を修正してシステム側の Python 2.7 を見つけられない状態にしてから、GIMP の Pythonコンソールで import cairo と打ってみた。この状態でもエラーは出なかった。
つまり、少なくとも GIMP Portable には cairo (pycairo) が同梱されている模様。
VMware Player + Ubuntu Linux 6.04 LTS 上でも、GIMP 2.8.16 をインストールして確認してみた。こちらも import cairo でエラーは出なかった。cairo は使える模様。
ただ、Ubuntu上では、get_rgba_str() がエラーを出すようで。struct.unpack() が、
つまり、少なくとも GIMP Portable には cairo (pycairo) が同梱されている模様。
VMware Player + Ubuntu Linux 6.04 LTS 上でも、GIMP 2.8.16 をインストールして確認してみた。こちらも import cairo でエラーは出なかった。cairo は使える模様。
ただ、Ubuntu上では、get_rgba_str() がエラーを出すようで。struct.unpack() が、
error: unpack requires a string argument of length 8と文句を言ってくる…。何故。
[ ツッコむ ]
以上、1 日分です。