最近Processing用のライブラリをJavaで実装していて、Java界のBufferedImageを何度もPImageとして渡す必要がありました。最初は素直に次のようなコードを書いていたのですが…

void draw() {
  BufferedImage bufImg = library.getImage();
  PImage img = new PImage(bufImg);
  image(img, 0, 0);
}

draw()のなかで何度もこれを呼んでいると、OutOfMemoryExceptionが出るようになってしまいました。PImageのコンストラクタは渡したbufImgをコピーするようになっているため、毎フレーム画素値用の配列が新しく確保されてしまい、Garbage Collectionが追いつかなくなってしまったようです。

そこで、予めPImageオブジェクトを用意しておいて、もらってきたbufImgの画素値をPImageにコピーするように書き換えてみました。

PImage img;
WritableRaster wr;

void draw() {
  BufferedImage bufImg = library.getImage();
  if (img == null) {
    img = new PImage(bufImg);
    DataBufferInt dbi = new DataBufferInt(img.pixels, img.pixels.length);
    wr = Raster.createWritableRaster(bufImg.getSampleModel(), dbi, new Point(0, 0));
  } else {
    bufImg.copyData(wr);
    img.updatePixels();
  }
  image(img, 0, 0);
}

ポイントは、int型配列への参照をPImageとWritableRasterで共有し、BufferedImageからWritableRasterにデータをコピーするようにしたことです。これは単なるコピー操作(System.arraycopy)であり、new PImageのときのように新しい配列用のメモリが確保されたりしません。最後にimg.updatePixelsでPImageの内部状態を同期させてから、ふつうのPImageのように画面に貼り付けることができます。

途中でBufferedImageの画像のサイズが変わったりする場合は、最初のif文にサイズのチェックも加える必要があるでしょう。また、Processingライブラリの実装としての完成度を上げるなら、BufferedImage型を返すlibrary.getImage()とは別にPImage型を返すlibrary.getPImage()のようなAPIを用意して、その中に処理を隠蔽すればよいと思います。


No Comments

Be the first to start a conversation

Leave a Reply

  • (will not be published)