#Pixmapとは
用意された画像からではなくメモリで加工した画像をもとにTextureを作りたいことはよくあります。
そんなときに利用するのがPixmapです。
今回は画像ファイルを用意せずにコードのみでTextureを作成します。
注意点としていつものApplicationListenerインターフェースを実装するのではなくApplicationAdapterを継承しています。
これはSwingなどでもよくあるAdapterクラスのようにインターフェースを空実装しているクラスになります。必要な個所のみオーバーライドして利用することが可能です。主にこの特徴はイベントで発揮されます。ApplicationListenerもイベントといえますね。
package test.libgdx.basic18;
import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.backends.lwjgl.LwjglApplication;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
public class MainListener18 extends ApplicationAdapter{
SpriteBatch batch;
Texture tex;
Pixmap pmap;
@Override
public void create() {
batch = new SpriteBatch();
//64x64のメモリイメージを作成
pmap = new Pixmap(64, 64, Pixmap.Format.RGBA8888);
pmap.setColor(Color.DARK_GRAY);
pmap.fill();//塗りつぶし
pmap.setColor(Color.RED);
pmap.drawRectangle(0, 0, 64, 64);//四角
pmap.setColor(Color.CYAN);
pmap.drawLine(0, 0, 64, 64);//線
pmap.setColor(Color.YELLOW);
pmap.drawCircle(31, 31, 31);//円
//それをもとにテクスチャ作成
tex = new Texture(pmap);
}
@Override
public void render() {
Gdx.gl.glClearColor(0, 0, 0.2f, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
//描画
batch.begin();
batch.draw(tex, 100, 100);
batch.end();
}
@Override
public void dispose() {
tex.dispose();
pmap.dispose();
}
public static void main(String[] args) {
new LwjglApplication(new MainListener18(), "libGDX", 256, 256);
}
}
###実行結果
ソースを見るとわかるように、基本的な描画命令はあります。これらの実装はJNIで各種プラットフォームごとにコンパイルされたバイナリによって実行されます。ユーザーはピクセルフォーマットなどを気にせず利用可能です。
#毎フレームテクスチャを更新
Pixmapによって生成されたTextureはそのもととなったPixmapを更新しても変更されません。
そんなときにはTexture#draw(pixmap, x, y)
メソッドを利用します。
//~省略~
int color = 0;//フィールドに追加
//~省略~
@Override
public void render() {
Gdx.gl.glClearColor(0, 0, 0.2f, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
//pixmapへ書き込み
pmap.setColor(color/100f,0,0,1f);
pmap.drawRectangle(0, 0, 48, 48);
//テクスチャへ転送
tex.draw(pmap, 0, 0);
color++;
if(color > 100){
color = 0;
}
//描画
batch.begin();
batch.draw(tex, 100, 100);
batch.end();
}
###実行結果
実行すると黒から赤い色が徐々に濃くなっていき最大まで赤くなった時また黒に戻るのを繰り返します。テクスチャが動的に更新されているのがわかります。
Texture#draw
メソッドの引数にxとyがあることからわかるように、OpenGLの実装としてはglTexSubImage2D
が利用されています。そのためTextureのサイズとPixmapのサイズは一致しなくてもかまいません。ただし、Texture内に収まる必要があるため、TextureはPixmapより小さいサイズにはできません。
#Pixmapのメモリイメージをダイレクトに触る
実はPixmapは画像ファイルからTextureを作り出すときにも利用されていて、今までのサンプルでも内部的には利用されていました。
PixmapとはVRAMへ転送するテクスチャのフォーマットに合わせたメモリイメージそのものです。そのため、メモリはJavaのヒープ外にあります。Javaの配列などのように気軽に触ることはできません。そのかわりJNI経由でCの実装部分ががんばって処理しています。
とはいえ、JNI部分を何度も呼び出すのはオーバーヘッドです。1ピクセルごとに転送する命令はありますが、それを何度も呼び出していてはパフォーマンスにも影響します。
先ほどの点滅する赤い部分
//pixmapへ書き込み
pmap.setColor(color/100f,0,0,1f);
pmap.fillRectangle(0, 0, 48, 48);
はおおよそ63マイクロ秒かかりました。
これと同じことをピクセル単位のメソッド経由で行うと以下のようになります。
//pixmapへ書き込み
pmap.setColor(color/100f,0,0,1f);
for(int y=0; y<48 ;y++){
for(int x=0; x<48 ;x++){
pmap.drawPixel(x, y);
}
}
おおよそ126マイクロ秒かかります。やはりオーバーヘッドが大きいですね。
PixmapのメモリはNIOによってアクセス可能です。ネイティブのバイトオーダーになること、ダイレクトバッファなので配列アクセスはできないことなどに注意です。
//pixmapへ書き込み
int c = Color.rgba8888(color/100f, 0, 0, 1);
IntBuffer buf = pmap.getPixels().order(ByteOrder.nativeOrder()).asIntBuffer();
for(int y=0; y<48 ;y++){
buf.position(y*64);
for(int x=0; x<48 ;x++){
buf.put(c);
}
}
これでおおよそ5マイクロ秒まで短縮されます。
さらにチューニングして1ライン単位でダイレクトバッファに転送するようにします。
//フィールドに追加
int[] src = new int[48];
//~省略~
@Override
public void render() {
Gdx.gl.glClearColor(0, 0, 0.2f, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
//pixmapへ書き込み
int c = Color.rgba8888(color/100f, 0, 0, 1);
int[] wk = src;
//いったん配列に1ライン分生成
for(int i=0; i<48 ;i++){
wk[i] = c;
}
IntBuffer buf = pmap.getPixels().order(ByteOrder.nativeOrder()).asIntBuffer();
for(int y=0; y<48 ;y++){
buf.position(y*64);
buf.put(wk);//48ピクセル分転送
}
//~省略~
これでおおよそ2マイクロ秒になりました。
今回は一ライン単位、それも同じ色で塗りつぶしましたが、配列からのTexture生成ができるということは昔ながらのプログラムのように書けるということです。
説明のためわかりやすくRGBA8888フルカラーで行いましたが、そもそも転送時間や描画時間を気にするならばピクセルフォーマットRGBA4444などにするべきでしょう。パソコン版では問題がなくてもスマホではこの辺のちょっとしたことで大きく数値が変わります。