Обработка больших растровых изображений - PullRequest
31 голосов
/ 08 февраля 2010

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

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

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

Ответы [ 3 ]

55 голосов
/ 09 февраля 2010

В классе BitmapFactory.Options есть опция (которую я пропустил) с именем inJustDecodeBounds, javadoc которой гласит:

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

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

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

13 голосов
/ 15 июня 2013

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

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

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

12 голосов
/ 14 сентября 2010

То, что я сделал сам, это:

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