web-dev-qa-db-ja.com

Androidでビットマップのサイズを変更する最もメモリ効率の良い方法は?

サーバーからデバイスに画像が送信される画像集約型のソーシャルアプリを構築しています。デバイスの画面解像度が小さい場合、デバイス上のビットマップのサイズを変更して、目的の表示サイズに合わせる必要があります。

問題は、createScaledBitmapを使用すると、大群のサイズを変更した後、多くのメモリ不足エラーが発生することです。サムネイル画像。

Androidでビットマップのサイズを変更する最もメモリ効率の良い方法は何ですか?

113
Colt McAnlis

この回答は 大きなビットマップを効率的にロードする から要約されています。これは、inSampleSizeを使用してダウンスケールされたビットマップバージョンをロードする方法を説明しています。

特に、 Pre-scaling bitmaps は、さまざまなメソッドの詳細、それらを組み合わせる方法、およびメモリ効率が最も高いメソッドについて説明しています。

Androidでビットマップのサイズを変更するには、異なるメモリプロパティを持つ3つの主要な方法があります。

createScaledBitmap API

このAPIは既存のビットマップを取り込み、選択した正確なサイズで新しいビットマップを作成します。

プラスの面では、探している画像サイズを正確に取得できます(外観に関係なく)。しかし、欠点は、このAPIが動作するために既存のビットマップを必要とすることです。新しい小さいバージョンを作成する前に、イメージの意味を読み込んでデコードし、ビットマップを作成する必要があります。これは正確な寸法を取得するという点では理想的ですが、追加のメモリオーバーヘッドという点では恐ろしいです。そのため、これは、メモリを意識する傾向があるほとんどのアプリ開発者にとって、一種の取引ブレーカーです。

inSampleSizeフラグ

BitmapFactory.Optionsには、一時的なビットマップにデコードする必要を回避するために、デコード中にイメージのサイズを変更するinSampleSizeとして示されるプロパティがあります。ここで使用されるこの整数値は、1/x縮小サイズで画像をロードします。たとえば、inSampleSizeを2に設定するとサイズが半分の画像が返され、4に設定するとサイズが1/4の画像が返されます。基本的に、画像のサイズは常にソースのサイズよりも2のべき乗分だけ小さくなります。

メモリの観点から、inSampleSizeの使用は非常に高速な操作です。事実上、イメージのX番目のピクセルごとにデコードして、結果のビットマップに変換します。ただし、inSampleSizeには2つの主な問題があります。

  • 正確な解像度は得られません。ビットマップのサイズを2の累乗だけ小さくするだけです。

  • 最高品質のサイズ変更はできません。ほとんどのサイズ変更フィルターは、ピクセルのブロックを読み取り、問題のサイズ変更されたピクセルを生成するためにそれらを重み付けすることにより、見栄えの良い画像を生成します。 inSampleSizeは、数ピクセルごとに読み取るだけでこれをすべて回避します。結果は非常にパフォーマンスが高く、メモリが少なくなりますが、品質が低下します。

いくつかのpow2サイズで画像を縮小するだけで、フィルタリングが問題にならない場合、inSampleSizeよりもメモリ効率のよい(またはパフォーマンス効率の良い)メソッドを見つけることはできません。

inScaled、inDensity、inTargetDensityフラグ

2の累乗に等しくない次元に画像を拡大縮小する必要がある場合は、inScaledinDensityinTargetDensity、およびBitmapOptionsフラグが必要です。 inScaledフラグが設定されている場合、システムはinTargetDensityinDensity値で除算することにより、ビットマップに適用するスケーリング値を導出します。

mBitmapOptions.inScaled = true;
mBitmapOptions.inDensity = srcWidth;
mBitmapOptions.inTargetDensity =  dstWidth;

// will load & resize the image to be 1/inSampleSize dimensions
mCurrentBitmap = BitmapFactory.decodeResources(getResources(), 
      mImageIDs, mBitmapOptions);

この方法を使用すると、画像のサイズが変更され、「サイズ変更フィルター」が適用されます。つまり、サイズ変更ステップで追加の計算が考慮されるため、最終結果がより良くなります。ただし、注意してください:余分なフィルターステップ、余分な処理時間が必要ですフィルター自体用。

フィルタリングのオーバーヘッドが余分になるため、通常、この手法を目的のサイズよりも大幅に大きい画像に適用することはお勧めできません。

マジックの組み合わせ

メモリとパフォーマンスの観点から、これらのオプションを組み合わせて最良の結果を得ることができます。 (inSampleSizeinScaledinDensity、およびinTargetDensityフラグの設定)

inSampleSizeが最初にイメージに適用され、ターゲットサイズよりも2のべき乗の大きいサイズになります。次に、inDensityinTargetDensityを使用して、結果を必要な正確な寸法にスケーリングし、フィルター操作を適用して画像をクリーンアップします。

inSampleSizeステップにより、結果の密度ベースのステップでサイズ変更フィルターを適用するのに必要なピクセル数が減少するため、これら2つの組み合わせははるかに高速な操作です。

mBitmapOptions.inScaled = true;
mBitmapOptions.inSampleSize = 4;
mBitmapOptions.inDensity = srcWidth;
mBitmapOptions.inTargetDensity =  dstWidth * mBitmapOptions.inSampleSize;

// will load & resize the image to be 1/inSampleSize dimensions
mCurrentBitmap = BitmapFactory.decodeFile(fileName, mBitmapOptions);

画像を特定のサイズに合わせる必要がある場合、およびより優れたフィルタリングが必要な場合、この手法は適切なサイズを得るための最適なブリッジですが、高速で低解像度で行われます-メモリフットプリント操作。

画像寸法の取得

画像全体をデコードせずに画像サイズを取得するビットマップのサイズを変更するには、入力される寸法を知る必要があります。 inJustDecodeBoundsフラグを使用して、実際にピクセルデータをデコードする必要のない画像の寸法を取得できます。

// Decode just the boundaries
mBitmapOptions.inJustDecodeBounds = true;
BitmapFactory.decodeFile(fileName, mBitmapOptions);
srcWidth = mBitmapOptions.outWidth;
srcHeight = mBitmapOptions.outHeight;


//now go resize the image to the size you want

このフラグを使用して最初にサイズをデコードし、次にターゲット解像度にスケーリングするための適切な値を計算できます。

162
Colt McAnlis

この答えと同様にニース(かつ正確)であると同時に、非常に複雑でもあります。車輪を再発明するのではなく、 GlidePicasso[〜#〜] uil [〜#〜] 、-のようなライブラリを検討してください。 Ion 、またはこの複雑でエラーが発生しやすいロジックを実装する他の任意の数。

Colt自身も、 Pre-scaling Bitmaps Performance Patterns Video でGlideとPicassoをご覧になることをお勧めしています。

ライブラリを使用することで、Coltの答えに記載されているすべての効率を得ることができますが、Androidのすべてのバージョンで一貫して動作する非常にシンプルなAPIを使用できます。

13
Sam Judd