dothikoのカクカクワールド2D REBOOT

プログラミングとかLinux関連(特にOSSのグラフィックツール関連)とかレトロゲームとか色々。

PythonでSWIGのC++関数中でGdkPixbufにアクセスしてみた

Pythonで作ったGdkPixbufを、SWIGC++関数に送り込んでC++の中で最速描画ということを考えつきました。

さらに、そのpixbufはcairoから作ると、なお痛快でしょう。 と前々から思っていたのですが、遂に重い腰を上げて作ってみました。

そんなわけでそのサンプルをここに置いておきますね…(´・ω・`) 引数チェックは気分的にしたい部分以外はしていないですがテストなので…

testlib.i

SWIGのインターフェースファイルです。

%module testlib
// Inserting block
%{
#include "testlib.hpp"
%}
// Combining block
%include "testlibcore.hpp"
%init %{
%}

testlib.hpp

結合用のヘッダです。testlib.ccに埋め込まれるはず。

#include <Python.h>

// Write my own libraries
#include "testlibcore.hpp"

testlibcore.hpp

宣言だけなので書く必要無いですが一応

#ifndef __TESTLIBCORE_H__
#define __TESTLIBCORE_H__

#include <Python.h>

PyObject* testfunc(PyObject* pixbuf);

#endif 

testlibcore.cpp

#include "testlibcore.hpp"
#include <pygobject.h>
#include <stdio.h>
#include <gtk/gtk.h> //これでOK

PyObject* testfunc(PyObject* pixbuf) {

    GObject *obj = pygobject_get(pixbuf);
    if(obj != NULL) {
        printf("--- success to get gobject ---\n");
        GdkPixbuf* gpix = GDK_PIXBUF(obj);
        if(gpix != NULL) {
            printf(
                "the pixbuf is w:%d, h:%d, channnels:%d\n" ,
                gdk_pixbuf_get_width(gpix),
                gdk_pixbuf_get_height(gpix),
                gdk_pixbuf_get_n_channels(gpix)
            );
            // writing blue box
            int rowstride = gdk_pixbuf_get_rowstride (gpix);
            guchar *pixels = gdk_pixbuf_get_pixels (gpix);
            int n_channels = gdk_pixbuf_get_n_channels (gpix);

            int sx = 30;
            int sy = 30;
            int w = 48;
            int h = 20;

            for (int y=sy; y < sy+h; y++) {
                for (int x=sx; x < sx+w; x++) {
                    guchar *p = pixels + y * rowstride + x * n_channels;
                    p[0] = 0; //r
                    p[1] = 0;    //g
                    p[2] = 255;     //b
                    p[3] = 255;        //a
                }
            }
        }
        else {
            printf("--- too bad, failed gdkpixbuf ---\n");
        }
    }

    Py_RETURN_NONE;
}

ビルド用のSConstruct

いちおう、scons用のファイルも置いときます。いやべつにmakeでもいいんですが難しいので…

Target='_testlib.so' # 先頭に_が必要(超重要!)
srcs = "testlibcore.cpp testlib.i" # testlib.c is automatically generated.

e = Environment(SWIGFLAGS=['-c++', '-python'])
e.Append(CCFLAGS=['-Wall', '-g', '-O0', '-fPIC', '-fdiagnostics-color'])

e.ParseConfig('pkg-config pygobject-2.0 glib-2.0 gtk+-3.0 --cflags --libs')
e.ParseConfig('python-config --includes --ldflags')

e['SHLIBPREFIX'] = '' # To avoid add 'lib_' prefix to target library file.
e.SharedLibrary(Target, Split(srcs))

テスト実行用のcairo_pixbuf.py

#!/usr/bin/env python
# -*- coding: UTF-8 -*-

import cairo
from gi.repository import Gdk, GdkPixbuf
import testlib

def main():
    w = 320
    h = 240
    surf = cairo.ImageSurface(cairo.FORMAT_A8, w, h)
    cr = cairo.Context(surf)
    cr.set_source_rgba(0, 0, 0, 1.0)
    cr.rectangle(8, 8, 100, 200)
    cr.fill()
    pixbuf = Gdk.pixbuf_get_from_surface(surf, 0, 0, w, h)
    print("---from python")
    print("sample bits:%d" % pixbuf.get_bits_per_sample())
    print("channels:%d" % pixbuf.get_n_channels())
    print("---calling c")
    testlib.testfunc(pixbuf)

    pixbuf.savev("/tmp/test_blued.png", "png", [], []) 

if __name__ == '__main__':
    main()

これを実行すると/tmp/test_blued.pngとして、 python側でcairoで四角を書かれたpixbufに青い四角が書かれたものが保存されるという。 まったくどうでもよい単なるテスト機能が発動するのでした。 なお、どうも上記のようにFORMAT_A8でグレースケールのcairoサーフェスに描画しても、その時はグレースケールで処理されますが、その後Gdk.pixbuf_get_from_surfaceでpixbuf変換すると32bitのRGBAになってしまうようです。まぁ構いませんが。

しかし、これは実に応用できそう… PygameサーフェスにCからアクセスしたりとか、前々から妄想していたのです。 これをやれば超高速BGタイル描画とか出来そうです(pythonは配列アクセスが遅いので…でも、どうせプロトタイプにしか使わないし、あんま意味ないかw) んでいろいろ調べましたが、コレも結構簡単に出来そうです。_pygame.hによるとPygameサーフェスはPySurfaceObjectで、PySurface_AsSurface(x)マクロがあるようです。

さらにPygameの実質後継と言われているらしいPySDL2も出来たら良いね、と思って調べましたが… PySDL2は、ctypes使ってるんですね。 PyOpenGL3もそうですが… つまりPySDL2とかでは同様にctypesで自作C関数にアクセスすれば、そのままぶっこめるという、実にしまらない落ちに(^^;