Android

Android Bitmapをあらかじめ縮小してから読み込む(OutOfMemory対策)

More than 3 years have passed since last update.

今の端末だと10Mピクセルを越えるカメラを搭載した端末が普通になってきており、そのファイルサイズも数MBに及びます。

AndroidでBitmapFactoryを利用してフォトライブラリーから画像を読み込んだりすると思いますが、そのままその画像を読み込んでしまうと、しばしばOutOfMemoryとなりアプリが強制終了してしまいます。

そこで、実際に画像を読み込む前にある程度縮小してから読み込むことで、メモリが足りなくなることを防ごうというのが今回のネタです。


BitmapFactory.OptionsのinSampleSizeを指定して画像を縮小した状態で読み込む

処理の流れ的にはまず画像のサイズを取得して、サイズが大きかったら縮小指定してから読み込もう、という感じです。

InputStream inputStream = getContentResolver().openInputStream(data.getData());

// 画像サイズ情報を取得する
BitmapFactory.Options imageOptions = new BitmapFactory.Options();
imageOptions.inJustDecodeBounds = true;
BitmapFactory.decodeStream(inputStream, null, imageOptions);
Log.v("image", "Original Image Size: " + imageOptions.outWidth + " x " + imageOptions.outHeight);

inputStream.close();

// もし、画像が大きかったら縮小して読み込む
// 今回はimageSizeMaxの大きさに合わせる
Bitmap bitmap;
int imageSizeMax = 500;
inputStream = getContentResolver().openInputStream(data.getData());
float imageScaleWidth = (float)imageOptions.outWidth / imageSizeMax;
float imageScaleHeight = (float)imageOptions.outHeight / imageSizeMax;

// もしも、縮小できるサイズならば、縮小して読み込む
if (imageScaleWidth > 2 && imageScaleHeight > 2) {
BitmapFactory.Options imageOptions2 = new BitmapFactory.Options();

// 縦横、小さい方に縮小するスケールを合わせる
int imageScale = (int)Math.floor((imageScaleWidth > imageScaleHeight ? imageScaleHeight : imageScaleWidth));

// inSampleSizeには2のべき上が入るべきなので、imageScaleに最も近く、かつそれ以下の2のべき上の数を探す
for (int i = 2; i <= imageScale; i *= 2) {
imageOptions2.inSampleSize = i;
}

bitmap = BitmapFactory.decodeStream(inputStream, null, imageOptions2);
Log.v("image", "Sample Size: 1/" + imageOptions2.inSampleSize);
} else {
bitmap = BitmapFactory.decodeStream(inputStream);
}

inputStream.close();

BitmapFactory.OptionsのinSampleSizeに2のべき上を入れ、それを指定して画像を読み込むことで、2のべき上で縮小された画像を読み込むことができます。

たとえば、inSampleSize=2であれば、 1/2に縮小された画像が、inSampleSize=4であれば、1/4に縮小された画像が読み込まれます。

inSampleSizeには2,4,8,16と2のべき上を入れます。


読み込む画像の階調を指定して読み込む

読み込む際の画像サイズの縮小は解像度だけでなく、階調を変えることでサイズを落とすこともできます。

BitmapFactory.Options imageOptions = new BitmapFactory.Options();

imageOptions.inPreferredConfig = Bitmap.Config.ARGB_8888;

設定できる階調は以下です。


  • ARGB_8888:alpha/R/G/B 各8bit ー 8*4 = 32bit = 4byte / pixel

  • ARGB_4444:alpha/R/G/B 各4bit ー 4*4 = 16bit = 2byte / pixel

  • RGB_565:R(5bit)/G(6)/B(5) ー 5+6+5 = 16bit = 2byte / pixel

  • ALPHA_8:αのみを8bit ー 8bit = 1byte / pixel

体感的にはアルファー値の無い写真などは、RGB_565とかはそこまで劣化が気にならない程度でサイズが半分になるのでおすすめです。


まとめ

画像取得系はメモリを消費して大きい画像を取得するとすぐにOutOfMemoryで落ちるので、読み込む前にサイズを小さくしよう!