web-dev-qa-db-ja.com

大きなビットマップの処理

画像のURLがたくさんあります。これらの画像をダウンロードして、アプリケーションに1つずつ表示する必要があります。再フェッチを回避し、ユーザーエクスペリエンスを向上させるために、SoftReferencesとSdcardを使用してコレクションに画像を保存しています。

問題は、ビットマップのサイズについて何も知らないことです。そして結局のところ、BitmapFactory.decodeStream(InputStream)メソッドを使用していると、OutOfMemoryExceptionsが散発的に発生します。そこで、BitmapFactory Options(sample size = 2)を使用して画像をダウンサンプリングすることにしました。これにより出力が向上しました。OOMはありませんが、これは小さい画像の品質に影響します。

そのような場合はどうすればよいですか?高解像度画像のみを選択的にダウンサンプリングする方法はありますか?

31
Samuh

BitmapFactory.Optionsクラス(私が見落としたもの)にinJustDecodeBoundsという名前のオプションがあります。

Trueに設定すると、デコーダーはnull(ビットマップなし)を返しますが、out ...フィールドは設定されたままなので、呼び出し元はピクセルにメモリを割り当てる必要なくビットマップをクエリできます。

これを使用してビットマップの実際のサイズを確認し、inSampleSizeオプションを使用してダウンサンプルすることを選択しました。これにより、ファイルのデコード中のOOMエラーが少なくとも回避されます。

参照:
1。 大きなビットマップの処理
2。 デコードする前にビットマップ情報を取得する方法

55
Samuh

数日間、さまざまなデバイスで発生していたすべてのOutOfMemoryエラーを回避するのに苦労して、これを作成しました。

private Bitmap getDownsampledBitmap(Context ctx, Uri uri, int targetWidth, int targetHeight) {
    Bitmap bitmap = null;
    try {
        BitmapFactory.Options outDimens = getBitmapDimensions(uri);

        int sampleSize = calculateSampleSize(outDimens.outWidth, outDimens.outHeight, targetWidth, targetHeight);

        bitmap = downsampleBitmap(uri, sampleSize);

    } catch (Exception e) {
        //handle the exception(s)
    }

    return bitmap;
}

private BitmapFactory.Options getBitmapDimensions(Uri uri) throws FileNotFoundException, IOException {
    BitmapFactory.Options outDimens = new BitmapFactory.Options();
    outDimens.inJustDecodeBounds = true; // the decoder will return null (no bitmap)

    InputStream is= getContentResolver().openInputStream(uri);
    // if Options requested only the size will be returned
    BitmapFactory.decodeStream(is, null, outDimens);
    is.close();

    return outDimens;
}

private int calculateSampleSize(int width, int height, int targetWidth, int targetHeight) {
    int inSampleSize = 1;

    if (height > targetHeight || width > targetWidth) {

        // Calculate ratios of height and width to requested height and
        // width
        final int heightRatio = Math.round((float) height
                / (float) targetHeight);
        final int widthRatio = Math.round((float) width / (float) targetWidth);

        // Choose the smallest ratio as inSampleSize value, this will
        // guarantee
        // a final image with both dimensions larger than or equal to the
        // requested height and width.
        inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
    }
    return inSampleSize;
}

private Bitmap downsampleBitmap(Uri uri, int sampleSize) throws FileNotFoundException, IOException {
    Bitmap resizedBitmap;
    BitmapFactory.Options outBitmap = new BitmapFactory.Options();
    outBitmap.inJustDecodeBounds = false; // the decoder will return a bitmap
    outBitmap.inSampleSize = sampleSize;

    InputStream is = getContentResolver().openInputStream(uri);
    resizedBitmap = BitmapFactory.decodeStream(is, null, outBitmap);
    is.close();

    return resizedBitmap;
}

この方法はテストしたすべてのデバイスで機能しますが、気付いていない他のプロセスを使用すると品質が向上する可能性があると思います。

私のコードが同じ状況の他の開発者を助けることを願っています。また、上級開発者が助けて、プロセスの品質低下(低下)を回避するために他のプロセスについて提案してもらえれば幸いです。

13
Ryan Amaral

私が自分でやったことは:

  • inJustDecodeBoundsを使用して画像の元のサイズを取得します
  • ビットマップをロードするための最大面が固定されている(1Mピクセルなど)
  • イメージサーフェスが制限を下回っているかどうかを確認し、そうであれば直接ロードします
  • そうでない場合は、ビットマップをロードして最大サーフェスより下に留まる必要がある理想的な幅と高さを計算します(ここで実行するいくつかの簡単な計算があります)。これにより、ビットマップをロードするときに適用するフロート比が得られます
  • ここで、この比率を適切なinSampleSize(イメージの品質を低下させない2の累乗)に変換する必要があります。私はこの関数を使用します:
 int k = Integer.highestOneBit((int)Math.floor(ratio)); 
 if(k == 0)return 1; 
 else return k; 
  • 次に、ビットマップが最大サーフェスよりも少し高い解像度で読み込まれるため(2の小さい累乗を使用する必要があったため)、ビットマップのサイズを変更する必要がありますが、はるかに高速になります。
12
Greg