fchiba memo

2009年10月

Javaで画像(PNG/GIF)の各ピクセルごとのパレット番号を取得する方法

BufferedImage image = ImageIO.read(new File("1.gif"));
// 「色」の情報を表すクラス
//パレット形式の場合は ColorModelが IndexColorModelのインスタンスになる
ColorModel model = image.getColorModel();
if(model instanceof IndexColorModel) {
IndexColorModel icm = (IndexColorModel)model;
// パレットの大きさ。未使用の部分があっても、常に256になってしまう…
System.out.println(icm.getMapSize());
// パレットのデータ。RGBAがintにパックされる。
int[] rgb = new int[icm.getMapSize()];
icm.getRGBs(rgb);
for(int i=0;i<rgb.length;i++) {
Color c = new Color(rgb[i]);
System.out.printf("%d,%d,%d,%d\n",c.getRed(),c.getGreen(),c.getBlue(),c.getAlpha());
}
int minx = image.getMinX();
int miny = image.getMinY();
int width = image.getWidth();
int height = image.getHeight();
// 「点」の情報を表すクラス
Raster raster = image.getRaster();
// バンド幅。RGBなら3、RGBAなら4になる。
// IndexColorModelの場合は常に1になる(はず)。
int band = raster.getNumBands();
if(band != 1) {
throw new RuntimeException("unexpected bands");
}
// 実際のピクセルごとのデータ。パレットの何番に該当するかが入っている
// 要素数はwidth*height
// (フルカラーの場合はRGB(A)の数値が入っていて、要素数はbands*width*height)
int[] buf = raster.getPixels(minx, miny, width, height, (int[])null);
for(int y=0;y<height;y++) {
for(int x=0;x<width;x++){
System.out.printf("%02d",buf[x+y*width]);
System.out.print(",");
}
System.out.println("");
}
}



最初のパレットかフルカラーかどうかの判定はif(image.getType() == BufferedImage.TYPE_BYTE_INDEXED) (=13)でも可能。
(ちなみに、JPEGの場合はBufferedImage.TYPE_3BYTE_BGR(=5)、フルカラーPNGの場合、BufferedImage.TYPE_CUSTOM (=0) だった。アルファがあったりするとまた変わるらしい)

C++&SWIGでPythonを拡張したとき、拡張した部分にメモリリークがないかをvalgrindでチェックしたい。
普通にpythonに対して、valgrindを使うと大量にエラーが出てくるが、これを抑制する方法がある。

http://svn.python.org/projects/python/trunk/Misc/valgrind-python.supp
http://svn.python.org/projects/python/trunk/Misc/README.valgrind

valgrind-python.supp の冒頭の翻訳:

#
# このファイルは、valgrindを使うときに使うべき valgrind 用(エラー)抑制ファイルです。
#
# 使い方の例:
#
# cd python/dist/src
# valgrind --tool=memcheck --suppressions=Misc/valgrind-python.supp \
# ./python -E -tt ./Lib/test/regrtest.py -u bsddb,network
#
# Py_ADDRESS_IN_RANGE に関するエラー抑制を行うためには、
# Objects/obmalloc.cを編集して、Py_USING_MEMORY_DEBUGGERをアンコメントする必要があります。
#
# もしPythonのリコンパイルがいやなら、抑制ファイル中のPyObject_Free とPyObject_Reallocに関する部分をアンコメントする必要があります。
#
# より詳しい情報は、Misc/README.valgrind を見てください。


デフォルトで抑制するには、~/.valgrindrc に書いておく。

現在、C++で作っているサーバーソフトをpure Javaな環境で動かす必要が出てきた。
できればJNIなどは使わないほうがよいとのこと。

そっくり移植するという手もあるが、似たようなコードを2重メンテするのは手間。
ということで、外部とのインターフェースはネイティブコードで書いて、ロジックの複雑なコア部分を他の言語で書くことにした。
処理速度の都合上、末端の処理はやはりネイティブ言語で書く必要があったため、
  外部インターフェース → コア(複雑なロジック) → 末端の重いけど単純な処理
      C++         組み込み言語(共通)    C++
      Java         組み込み言語(共通)    Java
というマクロ言語的な使い方となる。

当初はLuaを考えていたが LuaJavaが外部ライブラリを使う仕様となっていたため断念。
他に、上記のようにネイティブ→組み込み、組み込み→ネイティブの呼び出しができるのは、Pythonぐらいしかみつからなかった。Pythonは今まで使ったことがなかったが、ちょうどいい機会なので覚えてしまうことにする。

Pythonから末端のC++の呼び出しのためにラッパーが必要となるが、今回は SWIG を使用。
(boost::pythonやCythonなども検討したが、いろいろあって見送り。)
組み込みの方法は
http://lahosken.san-francisco.ca.us/frivolity/prog/embswig/doc.html
が参考になった。

しかし、上記の方法ではPythonのプロキシクラスを毎回ディスクから直接ロードすることになる。
これが嫌だったので、
http://fchiba.blog114.fc2.com/blog-entry-23.html
により、ソース中にプロキシクラスを埋め込むことにした。

また、Python→Javaの呼び出し時にはパッケージ名を指定してimportするが、
Pythonソースを共通化するためには、C++も同じパッケージにする必要がある。
そこで、
http://fchiba.blog114.fc2.com/blog-entry-24.html
で、SWIGで生成したコードにパッケージ名をつけた。

とりあえず簡単なコードで上記の実証実験が行えたので、これから実際のソースに適用してみることにする。

SWIGで単純にC/C++のラッパーを作ると、1階層のモジュールしかできない。

これをパッケージ名つきでimportするには、xxxx_wrap.cxx を以下のように修正する。

例)helloモジュールをaaa.bbb.hello としたい場合

hello_wrap.cxx
#define SWIG_name    "_hello"
#define SWIG_name    "aaa.bbb._hello"

  m = Py_InitModule((char *) SWIG_name, SwigMethods);
#endif
d = PyModule_GetDict(m);

PyObject* m_aaa = Py_InitModule("aaa", NULL); // aaa を作る
PyObject* m_bbb = Py_InitModule("aaa.bbb", NULL); // aaa.bbb を作る
m = Py_InitModule((char *) SWIG_name, SwigMethods); // aaa.bbb.hello を作る

#endif
PyObject* d_aaa = PyModule_GetDict(m_aaa);
PyObject* d_bbb = PyModule_GetDict(m_bbb);
d = PyModule_GetDict(m);

PyDict_SetItemString(d_aaa, "bbb", m_bbb); // aaa.bbb(変数) に bbb(モジュール) を登録
PyDict_SetItemString(d_bbb, "_hello", m); // aaa.bbb.hello(変数) に _hello(モジュール) を登録

上位のモジュールがすでに存在する場合は、Py_InitModuleの代わりにPyImport_AddModuleを使う。

PyImport_ExecCodeModuleでモジュールを実行した後、importしないと使えない。

#include <Python.h>

int main(void) {
Py_Initialize();

FILE *fp;

const char* module_code =
"class Hello:\n"
" def __init__(self, message):\n"
" self.message = message\n"
" def say(self):\n"
" print 'hello %s' % self.message\n"
;

// エラーハンドリング省略
PyObject* compiled_code = Py_CompileString(module_code, "hello.py", Py_file_input);
PyObject* module = PyImport_ExecCodeModule("hello", compiled_code);
Py_XDECREF(compiled_code);

// ↓これでは動かない
// PyImport_ImportModule("hello");

const char* main_code =
"import hello\n" // ←これが必要
"h = hello.Hello('world')\n"
"h.say()"
;

PyRun_SimpleString(main_code);

Py_Finalize();

return 0;
}

このページのトップヘ