ETC1を使ってデータ量を少なくする
Androidで画像リソースを利用する場合はPNGが多いと思いますが、PNGは可逆圧縮のフォーマットのためどうしても一定サイズ以下にデータを小さくできません。
そこでよく行われるのPNGを最適化して使ったり、WebP、JPGなどが良くあります。
最近ではVectorDrawableを利用されるケースもあります。
しかし今回はOpenGLなどで使われるETC(Ericsson Texture Compression)を使って画像表示を行ってみましょう。
ETCとは
ETCはEricsson Reserchが開発した圧縮テクスチャというものです。
他にもPVRTC、DXTCなどが取り上げられたりします。
iOSはPVRTCをサポートしているが、AndroidはPVRTCをサポートしている端末が一部のためETCを利用されるケースが多いそうです。
ETCはAndroidのすべての端末がサポートしておりますが、非可逆圧縮のため多少画像が劣化します。
またアルファ値を扱うことができないためアルファ値を利用する画像には不向きです。
メリットとしてはAndroidのGPUでのサポートがされているため変換などをしないでもそのままテクスチャとしてOpenGLで描画することができます 。
さらにAndroidの開発者ツールにPNGをETCのファイルに変換するツールが有ります。
etc1toolの使い方
Androidの開発環境を整えたらAndroidのSDKがダウンロードされると思います。(Macなら/Users/<ユーザー名>/Library/Android/sdkにダウンロードされると思います)
そのSDK内のplatform-tools内にetc1toolというのがあると思います。
これはPNGファイルをETCのファイルへ変換することと逆にETCのファイルをPNGに変換することができます。
今回は下図のファイル(techium.png)を例に実行してみましょう。
[techium.png]
コマンドラインで下記のようにするとPNGをETCにすることができます。
$ etc1tool techium.png --encode
コマンドを実行するとtechium.pkmというのができます。
ls -lしたら下記のような結果が得られます。
$ ls -l -rw-r--r-- 1 *** *** 8976 6 12 14:32 techium.pkm -rw-r--r-- 1 *** *** 25719 6 12 00:48 techium.png
容量が1/3になっているのがわかります。
これを逆にPNGに戻すには下記のコマンドを実行します。
$ etc1tool techium.pkm --decode
ここで注意事項ですが、ETCはアルファ値に対応していないためもしアルファ値が入ったものをエンコードしてデコードすると下図のようになってしまい、全く別の画像になってしまうので注意しましょう。
Androidで表示する
Androidで作成したETCのファイルを表示してみましょう。
OpenGLのテクスチャ表示は次のサイトを参考にさせていただきました。
それではpkmを表示してみましょう。
表示させるコードは次の3つのファイルの記述を行いました。
[MainActivity.java]
public class MainActivity extends AppCompatActivity { private EtcGlView mEtcGlView; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); try { InputStream is = getAssets().open("techium.pkm"); ETC1Util.ETC1Texture texture = ETC1Util.createTexture(is); mEtcGlView = new EtcGlView(this, texture); } catch (IOException e) { e.printStackTrace(); } setContentView(mEtcGlView); } @Override protected void onResume() { super.onResume(); mEtcGlView.onResume(); } @Override protected void onPause() { super.onPause(); mEtcGlView.onPause(); } }
[EtcGlView.java]
public class EtcGlView extends GLSurfaceView { public EtcGlView(Context context, ETC1Util.ETC1Texture texture) { super(context); setRenderer(new EtcRenderer(texture)); } }
[EtcRenderer.java]
public class EtcRenderer implements GLSurfaceView.Renderer { private ETC1Util.ETC1Texture mEtc1Texture; public EtcRenderer(ETC1Util.ETC1Texture etc1Texture) { mEtc1Texture = etc1Texture; } @Override public void onSurfaceCreated(GL10 gl10, EGLConfig config) { } @Override public void onSurfaceChanged(GL10 gl10, int width, int height) { gl10.glViewport(0, 0, width, height); gl10.glEnable(GL10.GL_TEXTURE_2D); int[] buffers = new int[1]; gl10.glGenTextures(1, buffers, 0); int texture = buffers[0]; gl10.glBindTexture(GL10.GL_TEXTURE_2D, texture); GLES10.glCompressedTexImage2D(GL10.GL_TEXTURE_2D, 0, ETC1.ETC1_RGB8_OES, mEtc1Texture.getWidth(), mEtc1Texture.getHeight(), 0, mEtc1Texture.getData().remaining(), mEtc1Texture.getData()); gl10.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST); gl10.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_NEAREST); } @Override public void onDrawFrame(GL10 gl10) { gl10.glClearColor(0.0f, 1.0f, 1.0f, 1.0f); gl10.glClear(GL10.GL_COLOR_BUFFER_BIT); float uv[] = { 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, }; ByteBuffer bbuv = ByteBuffer.allocateDirect(uv.length * 4); bbuv.order(ByteOrder.nativeOrder()); FloatBuffer fbuv = bbuv.asFloatBuffer(); fbuv.put(uv); fbuv.position(0); gl10.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY); gl10.glTexCoordPointer(2, GL10.GL_FLOAT, 0, fbuv); float positions[] = { -1.0f, 1.0f, 0.0f, -1.0f, -1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, -1.0f, 0.0f, }; ByteBuffer bb = ByteBuffer.allocateDirect(positions.length * 4); bb.order(ByteOrder.nativeOrder()); FloatBuffer fb = bb.asFloatBuffer(); fb.put(positions); fb.position(0); gl10.glEnableClientState(GL10.GL_VERTEX_ARRAY); gl10.glVertexPointer(3, GL10.GL_FLOAT, 0, fb); gl10.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 4); } }
まずMainActivityでtechium.pkmの読み込みを行います。
今回はpkmファイルをassetsフォルダ内に置き、それをgetAssets().open("techium.pkm")を使ってInputStreamを取得します。
そしてETC1Util.createTexture(is)でInputStreamを渡しpkmファイル内の読み込みを行ってもらいます。
この時にpkmファイル内の画像データの高さ、横幅、実データの3種類に分割されETC1Textureというクラスに入れられ返されます。
それをEtcRendererのonSurfaceChanged時にテクスチャのデータを指定しますがGLES10.glCompressedTexImage2Dを読み込んだデータを渡し、表示してもらいます。
注意しないといけないのは第1引数はGL10.GL_TEXTURE_2D、第2引数は0しか指定できませんので注意しましょう。
あとは画像の高さ、横幅、実データを渡すと下図のように表示されます。
あと、ETC1.decodeImageというAPIがありpkmをPNGに戻すことができそうなコードがあります。
実際に使ってみると実データ部分のデコード結果が-1で埋められて返却されてきて復元できませんでした。
どうやったら使えるか引き続き調査してみます。
これで画像データは1/3にすることができました。
ネットワーク越しで配信するが画像とかも同じようにETCで配信したら通信料を減らすことができてユーザーは喜ぶかもしれません。
試してみてください。