1. ホーム
  2. android

[解決済み] アンドロイドでビットマップのサイズを変更する最もメモリ効率の良い方法とは?

2022-05-14 09:15:48

質問

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

問題は createScaledBitmap は、大量のサムネイル画像のサイズを変更した後に、多くのメモリ不足のエラーに遭遇する原因となります。

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

どのように解決するのですか?

この回答は、以下を要約したものです。 大きなビットマップを効率的に読み込む で、inSampleSizeを使用して縮小されたビットマップを読み込む方法を説明しています。 のバージョンです。

特に プリスケーリングビットマップ では、さまざまな の詳細、それらをどのように組み合わせるか、そしてどれが最もメモリ効率が良いかについて説明します。

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

createScaledBitmap API

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

プラス面では、探している画像サイズを正確に得ることができます(それがどのように見えるかは関係なく)。しかし、欠点もあります。 は、この API が 既存の ビットマップが必要です。 . つまり、画像を読み込み、デコードし、ビットマップを作成してから、新しい小さいバージョンを作成する必要があります。これは、正確な寸法を得るという点では理想的ですが、メモリのオーバーヘッドが増えるという点では恐ろしいことです。そのため、メモリを重視する傾向のあるほとんどのアプリ開発者にとって、これは一種の破格の問題です。

inSampleSizeフラグ

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

メモリの観点からは inSampleSize は実に高速な処理です。事実上、画像の X 番目のピクセルを結果のビットマップにデコードするだけです。には主に2つの問題があります。 inSampleSize とはいえ

  • 正確な解像度が得られない . ビットマップのサイズを 2 のべき乗で減少させるだけです。

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

もし、画像をpow2サイズだけ縮小することだけを扱い、フィルタリングが問題でないなら、メモリ効率の良い(あるいは性能の良い)方法として inSampleSize .

inScaled, inDensity, inTargetDensity フラグ

もし、画像を2の累乗に等しくない寸法に拡大縮小する必要がある場合、そのためには inScaled , inDensityinTargetDensity のフラグが 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);

この方法を使うと、画像のサイズが変更され、さらに「リサイズフィルタ」が適用されます。つまり、リサイズステップの間にいくつかの追加の計算が考慮されるため、最終結果はより良く見えるようになります。しかし、注意してください。 その余分なフィルタリングのステップは、余分な処理時間がかかります 大きな画像の場合、リサイズに時間がかかったり、フィルタ自体に余計なメモリが割り当てられたりして、あっという間に増えてしまいます。

一般に、希望するサイズよりかなり大きな画像にこの手法を適用するのは、余分なフィルタリングのオーバーヘッドのため、良いアイデアとは言えません。

魔法の組み合わせ

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

inSampleSize はまず画像に適用され、目標サイズより 2 のべき乗大きいサイズになります。次に inDensity を追加します。 inTargetDensity は、結果を正確な寸法に拡大縮小するために使用され、画像をきれいにするためにフィルタ操作を適用します。

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

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

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