Androidにおける画像の取扱い

Androidにおける画像の取扱い

Androidでの開発、特に画像周りを触っていると悩まされるあのにっくき「OutOfMemoryError」によるアプリの強制終了。
このOutOfMemoryErrorはAndroidアプリ内ではtry~catchなどでハンドリングすることができないようで、そもそも「起こらないように」作るしかないようです。
このOutOfMemoryErrorはAndroidアプリ内のtry~catchで捕まえられないこともあり、そもそも「起こらないように」作るのが(当然ですが)好ましいでしょう。
(2010/12/20修正)

AndroidでBitmapFactoryを使ってサイズの大きな画像を読み込むサンプル – hoge256ブログ
OutOfMemoryErrorを知る – hdk_embeddedの日記
これらの記事が大変参考になりました。

ざっくりまとめておきますとアプローチは大きく分けて3つ、

  • そもそも縮小して表示するから小さく持ってくる
  • 1ピクセル辺りのバイト数を減らす(ビット深度・カラー深度を落とす)
  • オブジェクトを使いまわす

があります。

1つ目の「そもそも縮小して表示するから」というのは例えば大きな画像をサムネイルとして表示するなどの用途ですね。
仮に800600の画像を300200のサイズに収まるように読み込む場合だと

BitmapFactory.Options opt = new BitmapFactory.Options();
// オプション設定用のオブジェクト
opt.inJustDecodeBounds = true;
// 実際の画像本体は読まずにサイズ情報のみ取得するフラグをセット

BitmapFactory.decodeFile(path, opt);
// サイズ情報を取得する
int scaleW = opt.outWidth / 300; // →2
int scaleH = opt.outHeight / 200; // →3
// 取得した画像サイズは
// BitmapFactory.Options#outWidth
// BitmapFactory.Options#outHeight
// にそれぞれ入り、表示サイズで割ることで縮小時の整数比率を求める

int sampleSize = Math.max(scaleW, scaleH); // →3
opt.inSampleSize = sampleSize;
// 画像を何分の1で取得するかを求めます
// 2の乗数に丸められるのでここでは3で指定しても2と同様に扱われる
opt.inJustDecodeBounds = false;
// 今度は実際に画像を取得するためのフラグをセット
Bitmap bmp = BitmapFactory.decodeFile(path, opt);
// inSampleSizeに3を指定し、処理としては2の乗数に丸められるので2
// つまり元画像の1/2で読み込まれる
int w = bmp.getWidth(); // →400
int h = bmp.getHeight(); // →300
float scale = Math.min((float)300/w, (float)200/h); // →0.6666667
// 最終的なサイズにするための縮小率を求める
Matrix matrix = new Matrix();
matrix.postScale(scale, scale);
// 画像変形用のオブジェクトに拡大・縮小率をセットし
bmp = Bitmap.createBitmap(bmp, 0, 0, w, h, matrix, true);
// 取得した画像を元にして変形画像を生成・再設定

こんな感じになります。

2つ目の「1ピクセル辺りのバイト数を減らす」は色々な指定の仕方がありますが、例えば800*600の場合

Bitmap bmp = Bitmap.createBitmap(800, 600, Bitmap.Config.ARGB_8888);
// 1ピクセル4バイト(αチャネル、RGB、各8ビット)
Bitmap bmp = Bitmap.createBitmap(800, 600, Bitmap.Config.ARGB_4444);
// 1ピクセル2バイト(αチャネル、RGB、各4ビット)
Bitmap bmp = Bitmap.createBitmap(800, 600, Bitmap.Config.RGB_565);
// 1ピクセル2バイト(αチャネルなし、R5ビット、G6ビット、B5ビット)

必要に応じてこれらを使い分けるとオブジェクトのサイズを節約できます。
ARGB_8888→ARGB_4444で単純に半分になったりはしませんが・・・

余談ですがRGB_565で緑だけが1ビット情報量が多いのは人間の目で一番感度が良いとされているからだったりします。
逆に一番感度が悪いのは青で、AndroidにはありませんがRGB332形式というのがあって青の階調が少なかったりします。

最後、3つ目の「オブジェクトを使いまわす」は、しばらくはとあるBitmapオブジェクトを使わないときに呼んでおくとGCの対象になります。

Bitmap bmp;
bmp = BitmapFactory.decodeFile(...);
// 色々な処理
bmp.recycle();
// しばらくは必要ないのでメモリ開放対象としておく

if(bmp.isRecycle()) {
// 再度利用する時は開放済みかどうかを確認して再度読み込む
bmp = BitmapFactory.decodeFile(...);
}

BitmapFactory.Options#inPurgeable
BitmapFactory.Options#inInputShareable
辺りも関連しそうですがこれに関しては未検証のため言及を避けておきます。

TAG

  • このエントリーをはてなブックマークに追加
やまま
スペシャリスト やまま yamama

マンガとアニメとゲームから錬成された宇宙大好きエンジニア。 軌道エレベーターで行ける静止軌道上のコロニーに住まいを移し、ゲームやってマンガ読んでアニメ見て爆睡、ゲームやってマンガ読んでアニメ見て爆睡、という生活を夢見ながら今日もコードを書き続けるのだった。