Расшифровка растровых изображений в Android с нужным размером

Я декодирую растровые изображения с SD-карты, используя BitmapFactory.decodeFile, Иногда растровые изображения больше, чем нужно приложению или что позволяет куча, поэтому я использую BitmapFactory.Options.inSampleSize запросить субдискретизированное (меньшее) растровое изображение.

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

От http://developer.android.com/reference/android/graphics/BitmapFactory.Options.html:

Примечание: декодер попытается выполнить этот запрос, но результирующее растровое изображение может иметь разные размеры, которые точно соответствуют тому, что было запрошено. Кроме того, степени 2 часто быстрее / легче для почитателя.

Как мне декодировать растровые изображения с SD-карты, чтобы получить растровое изображение именно того размера, который мне нужен, при этом потребляя как можно меньше памяти для его декодирования?

Редактировать:

Текущий исходный код:

BitmapFactory.Options bounds = new BitmapFactory.Options();
this.bounds.inJustDecodeBounds = true;
BitmapFactory.decodeFile(filePath, bounds);
if (bounds.outWidth == -1) { // TODO: Error }
int width = bounds.outWidth;
int height = bounds.outHeight;
boolean withinBounds = width <= maxWidth && height <= maxHeight;
if (!withinBounds) {
    int newWidth = calculateNewWidth(int width, int height);
    float sampleSizeF = (float) width / (float) newWidth;
    int sampleSize = Math.round(sampleSizeF);
    BitmapFactory.Options resample = new BitmapFactory.Options();
    resample.inSampleSize = sampleSize;
    bitmap = BitmapFactory.decodeFile(filePath, resample);
}

10 ответов

Решение

Вы на правильном пути, однако вы пытаетесь сделать две вещи одновременно: прочитайте файл и масштабируйте его до подходящего размера.

Первым шагом является чтение файла в растровое изображение, немного большее, чем требуется, с помощью BitmapFactory.Options.inSampleSize, чтобы гарантировать, что вы не потребляете чрезмерную память для чтения большого растрового изображения, когда все, что вам нужно, - это уменьшенное изображение или изображение с разрешением экрана.

Вторым шагом является вызов Bitmap.createScaledBitmap() для создания нового растрового изображения с точным разрешением, которое вам требуется.

Убедитесь, что вы очистили временное растровое изображение, чтобы восстановить его память. (Либо оставьте переменную вне области видимости и разрешите GC справиться с ней, либо вызовите.recycle() для нее, если вы загружаете много изображений и нехватаете памяти.)

Вы можете использовать inJustDecodeBounds, Установите это TRUE и загрузите файл как есть.

Изображение не будет загружено в память. Но свойства outheight и outwidth в BitmapFactory.Options будут содержать параметры фактического размера указанного изображения. Подсчитайте, сколько вы хотите подвыборки. т.е. 1/2 или 1/4 или 1/8 и т. д., и присвойте 2/4/8 и т. д. соответственно inSampleSize.

Сейчас установлено inJustDecodeBounds в FALSE и позвонить BitmapFactory.decodeFile() загрузить изображение точного размера, как рассчитано выше.

Сначала вам нужно сэмплировать изображение до ближайшего значения выборки, чтобы растровое изображение стало эффективным для памяти, что вы прекрасно описали. Как только это будет сделано, вы можете увеличить его до размера экрана.

// This function taking care of sampling
backImage =decodeSampledBitmapFromResource(getResources(),R.drawable.back, width, height);

// This will scale it to your screen width and height. you need to pass screen width and height.
backImage = Bitmap.createScaledBitmap(backImage, width, height, false); 

Поскольку API уже заявляет, что размер выборки может или не может быть соблюден. Так что я думаю, что вам не повезло при использовании BitmapFactory,

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

Я хотел бы попытаться не слишком беспокоиться о небольшом дополнительном потреблении памяти, но попытаться выяснить, почему не соблюдаются эти значения. К сожалению, я понятия не имею об этом:(

Для тех, кто ищет точный размер изображения из ресурсов.

Для точной ширины передайте reqHight=0 и для точной высоты передайте reqWidth=0

public static Bitmap decodeBitmapResource(Context context, int resId, int reqWidth, int reqHeight)
{
    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeResource(context.getResources(), resId, options);
    float scale = 1;
    if (reqWidth != 0 && reqHeight == 0)
    {
        options.inSampleSize = (int) Math.ceil(options.outWidth / (float) reqWidth);
        scale = (float) options.outWidth / (float) reqWidth;
    }
    else if (reqHeight != 0 && reqWidth == 0)
    {
        options.inSampleSize = (int) Math.ceil(options.outHeight / (float) reqHeight);
        scale = (float) options.outHeight / (float) reqHeight;
    }
    else if (reqHeight != 0 && reqWidth != 0)
    {
        float x = (float) options.outWidth / (float) reqWidth;
        float y = (float) options.outHeight / (float) reqHeight;
        scale = x > y ? x : y;
    }
    reqWidth = (int) ((float) options.outWidth / scale);
    reqHeight = (int) ((float) options.outHeight / scale);
    options.inJustDecodeBounds = false;
    Bitmap tmp = BitmapFactory.decodeResource(context.getResources(), resId, options);
    Bitmap out = Bitmap.createScaledBitmap(tmp, reqWidth, reqHeight, false);
    tmp.recycle();
    tmp = null;
    return out;
}
ThumbnailUtils.extractThumbnail(BitmapFactory.decodeFile(filesPath), THUMBSIZE, THUMBSIZE))

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

Мне удалось приблизительно получить "правильный размер" путем изменения масштаба файла декодированного изображения до 70% его размера растрового изображения.

Bitmap myBitmap = BitmapFactory.decodeFile(path);
if(myBitmap != null)
{
  //reduce to 70% size; bitmaps produce larger than actual image size
  Bitmap rescaledMyBitmap = Bitmap.createScaledBitmap(
                                                  myBitmap, 
                                                  myBitmap.getWidth()/10*7, 
                                                  myBitmap.getHeight()/10*7, 
                                                  false);

  image.setImageBitmap(rescaledMyBitmap);
}                

Я надеюсь, что это поможет вам как-то! Мир!

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

Если установлен флаг inScaled (по умолчанию это TRUE), если inDensity и inTargetDensity не равны 0, растровое изображение будет масштабироваться так, чтобы соответствовать значению inTargetDensity при загрузке, а не полагаться на графическую систему, масштабирующую его каждый раз, когда оно обращается к Canvas. Так что просто установите inScaled в false.

BitmapFactory.Options options = new BitmapFactory.Options();
options.inScaled = false;

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

Как сказал the100rabh, если вы используете BitmapFactory, у вас нет особого выбора. Однако растровое декодирование действительно простое, и в зависимости от используемого вами алгоритма масштабирования обычно достаточно просто масштабировать его на лету, не считывая большие части исходного растрового изображения в память (как правило, вы будете нужно только удвоить объем памяти, необходимый для 1-2 строк исходного растрового изображения).

Другие вопросы по тегам