Обработка больших растровых изображений

У меня есть куча URL-адресов изображений. Я должен загрузить эти изображения и отобразить их в своем приложении один за другим. Я сохраняю изображения в коллекции с помощью SoftReferences, а также на Sdcard, чтобы избежать повторений и улучшения пользовательского интерфейса.

Проблема в том, что я ничего не знаю о размере растровых изображений. И, как выясняется, я получаю OutOfMemoryExceptions спорадически, когда я использую метод BitmapFactory.decodeStream(InputStream). Таким образом, я решил уменьшить размер изображений с помощью параметров BitmapFactory (размер выборки = 2). Это дало лучший результат: никаких OOM, но это влияет на качество меньших изображений.

Как мне обращаться с такими случаями? Есть ли способ выборочно уменьшить только изображения с высоким разрешением?

Ответ 1

В классе BitmapFactory.Options есть опция (одна из которых я пропустил) с именем inJustDecodeBounds, javadoc которой читает:

Если установлено значение true, декодер будет return null (no bitmap), но out... все еще будут установлены, позволяя вызывающему абоненту запрашивать растровое изображение без необходимости выделять памяти для своих пикселей.

Я использовал его, чтобы узнать фактический размер растрового изображения, а затем решил сбрасывать его с помощью опции inSampleSize. Это, по крайней мере, позволяет избежать ошибок OOM при декодировании файла.

Справка:
1. Обработка больших растровых изображений
2. Как получить информацию об Bitmap перед тем, как я декодирую

Ответ 2

Я сделал сам:

  • используйте inJustDecodeBounds, чтобы получить исходный размер изображения
  • имеют фиксированную максимальную поверхность для загрузки Bitmap (скажем, 1Mpixels)
  • проверьте, находится ли поверхность изображения ниже предела, если да, а затем загрузите ее напрямую.
  • Если вы не вычисляете идеальную ширину и высоту, при которой вы должны загружать Bitmap, чтобы оставаться ниже максимальной поверхности (здесь есть несколько простых математических соображений). Это даст вам соотношение поплавка, которое вы хотите применить при загрузке растрового изображения.
  • Теперь вы хотите перевести это соотношение в подходящий inSampleSize (мощность 2, которая не ухудшает качество изображения). Я использую эту функцию:
int k = Integer.highestOneBit((int)Math.floor(ratio));
if(k==0) return 1;
else return k;
  • потому что битмап будет загружен с чуть более высоким разрешением, чем максимальная поверхность (потому что вам нужно было использовать меньшую мощность 2), вам придется изменить размер растрового изображения, но оно будет намного быстрее.

Ответ 3

После нескольких дней, пытаясь избежать всех ошибок 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;
}

Этот метод работает со всеми проверенными мной устройствами, но я думаю, что качество может быть лучше с использованием другого процесса, о котором я не знаю.

Надеюсь, мой код может помочь другим разработчикам в той же ситуации. Я также благодарен, если старший разработчик может помочь, давая предложение о другом процессе, чтобы избежать потери (меньше) качества в процессе.