Обработка больших растровых изображений
У меня есть куча URL-адресов изображений. Я должен загрузить эти изображения и отобразить их в своем приложении один за другим. Я сохраняю изображения в Коллекции, используя SoftReferences
а также на SDCard, чтобы избежать повторных загрузок и улучшить пользовательский опыт.
Проблема в том, что я ничего не знаю о размере растровых изображений. И, как оказалось, я получаю OutOfMemoryExceptions спорадически, когда я использую BitmapFactory.decodeStream(InputStream)
метод. Поэтому я решил уменьшить изображения с помощью параметров BitmapFactory (размер выборки =2). Это дало лучший результат: никаких OOMs, но это влияет на качество меньших изображений.
Как мне справиться с такими случаями? Есть ли способ выборочно уменьшить частоту дискретизации только изображений с высоким разрешением?
3 ответа
Есть вариант в BitmapFactory.Options
класс (тот, который я пропустил) назвал inJustDecodeBounds
, Javadoc которого гласит:
Если установлено значение true, декодер будет возвращать ноль (без растрового изображения), но поля out... будут по-прежнему установлены, что позволяет вызывающей стороне запрашивать растровое изображение без необходимости выделять память для его пикселей.
Я использовал его, чтобы узнать фактический размер растрового изображения, а затем решил сэмплировать его, используя inSampleSize
вариант. Это, по крайней мере, позволяет избежать ошибок OOM при декодировании файла.
Ссылка:
1. Обработка больших растровых изображений
2. Как получить растровую информацию перед декодированием
После нескольких дней, пытаясь избежать всех ошибок 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;
}
Этот метод работает со всеми устройствами, которые я тестировал, но я думаю, что качество может быть лучше, используя другой процесс, который я не знаю.
Я надеюсь, что мой код может помочь другим разработчикам в такой же ситуации. Я также ценю, если старший разработчик может помочь, предлагая другой процесс, чтобы избежать потери (меньшего) качества в процессе.
Что я сделал сам:
- использование
inJustDecodeBounds
получить оригинальный размер изображения - иметь фиксированную максимальную поверхность для загрузки растрового изображения (скажем, 1 мегапикселя)
- проверить, если поверхность изображения ниже предела, если да, то загрузить его напрямую
- если нет, вычислите идеальную ширину и высоту, при которых вы должны загрузить растровое изображение, чтобы оно оставалось ниже максимальной поверхности (здесь есть несколько простых математических действий). Это даст вам коэффициент плавания, который вы хотите применить при загрузке растрового изображения.
- Теперь вы хотите перевести это соотношение в подходящий
inSampleSize
(степень 2, которая не ухудшает качество изображения). Я использую эту функцию:
int k = Integer.highestOneBit ((int) Math.floor (ratio)); if (k == 0) вернет 1; иначе вернуть k;
- тогда, поскольку растровое изображение будет загружено с чуть более высоким разрешением, чем максимальная поверхность (потому что вы должны были использовать меньшую степень 2), вам придется изменить размер растрового изображения, но это будет намного быстрее.