サーバサイドでProcessingを動かす

こちら Processing+POIをエクセルグラフウィザードの代替として使う - Object Design に触発されて、Webアプリケーションでも(サーバサイドで) Processing を使って情報視覚化ができないか試してみた。

まずは、Processing をJava Servlet上で動かして画像を生成するところまで*1

とりあえず、Processing の本体であるアプレット(PApplet)の初期化、PAppletで直接サポートしていない生イメージの返却を行うユーティリティを作ってみる。

package example.processing;

import java.awt.image.BufferedImage;

import processing.core.PApplet;
import processing.core.PConstants;
import processing.core.PGraphics;

public final class ProcessingUtils {
    /**
     * callback内で描画した内容をイメージとして返却します。
     */
    public static BufferedImage drawImage(ProcessingCallback callback) {
        PApplet applet = new PApplet();
        applet.init();

        // 初期化までタイムラグがあるので待つ
        // (PApplet.main メソッドのソース参照)
        while (applet.defaultSize && !applet.finished) {
            try {
                Thread.sleep(5);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }

        // ユーザ指定の描画
        callback.draw(applet);

        // BufferedImage に変換
        // PImage#save メソッドのソース参照
        PGraphics g = applet.g;
        g.loadPixels();
        BufferedImage image = new BufferedImage(g.width, g.height,
                (g.format == PConstants.ARGB) ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB);
        image.setRGB(0, 0, g.width, g.height, g.pixels, 0, g.width);

        // いちおう、グラフィックの廃棄
        applet.destroy();

        return image;
    }
}

上の drawImage() の引数として使うコールバック用のインタフェース。Processing の機能を使いたいユーザは、このインタフェースを実装し、初期化や廃棄処理を除いた純粋な描画処理だけを draw() メソッドに定義する。

package example.processing;

import processing.core.PApplet;

public interface ProcessingCallback {
    void draw(PApplet pa);
}

上のような機構を利用して、楕円描画サーブレットを作成してみる。

package example.processing;

import java.awt.image.BufferedImage;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import processing.core.PApplet;

@SuppressWarnings("serial")
@WebServlet(name="Ellipse", urlPatterns={"/ellipse.png"})
public class EllipseServlet extends HttpServlet {

    @Override
    public void doGet(HttpServletRequest req, HttpServletResponse res) {
        BufferedImage image = ProcessingUtils.drawImage(new ProcessingCallback() {
            /**
             * 具体的な描画内容のみを定義
             */
            @Override
            public void draw(PApplet pa) {
                pa.size(200, 100);
                pa.background(225);
                pa.smooth();
                pa.fill(180, 230, 255);
                pa.ellipse(pa.width/2, pa.height/2, pa.width/2, pa.height/2);
                pa.fill(180, 0, 0);
                pa.text("テストです", 10, 10);
            }
        });

        res.setContentType("image/png");
        try {
            // PNGに変換して出力ストリームに流す
            ImageIO.write(image, "png", res.getOutputStream());
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

上記をWebアプリケーションとして、たとえばローカルホストの /processing にデプロイする。*2

デプロイ後、http://localhost:8080/processing/ellipse.png にアクセスすると楕円画像が表示される。

TODO

  • PAppletを使わず、PGraphicsを直接使う(ちょっと試したけどなぜか画像が表示されなくなったため一時断念)
  • 今回はWindows上で行ったので問題なかったが、java.awt.headless=true の純粋なサーバ環境でもうまく動作するか?
  • PApplet生成の負荷は、毎回生成して問題ない程度なのかどうか。リソースリークはないか? JDBC DataSourceのようにプーリングを行う必要はあるか?
  • OpenGL や PDF 等、デフォルトのJava2D以外のレンダラを使えるようにする

*1:Processing.js を使ったほうが早いしサーバリソースにも優しいけど、まあ実験として。

*2:サンプルではServlet 3.0 の仕様を使っているので、Tomcatなら7以降が必要