Obsługa dużych Bitmap

Mam kilka adresów URL obrazów. Muszę pobrać te obrazy i wyświetlić je w mojej aplikacji jeden po drugim. Zapisuję obrazy w kolekcji za pomocą SoftReferences, a także na karcie SD, aby uniknąć odruchów i poprawić doświadczenie użytkownika.

Problem w tym, że nie wiem nic o wielkości bitmap. I jak się okazuje, okazjonalnie wychodzę z nałogu, kiedy używam metody BitmapFactory.decodeStream(InputStream). Zdecydowałem się więc obniżyć próbkę obrazów za pomocą BitmapFactory Opcje(wielkość próbki=2). Dało to lepszą wydajność: brak Oom, ale wpływa to na jakość mniejszych obrazów. Jak mam prowadzić takie sprawy? Czy istnieje sposób na selektywne pomniejszanie tylko obrazów o wysokiej rozdzielczości?
Author: tshepang, 2010-02-08

3 answers

Istnieje opcja w BitmapFactory.Options klasie (którą przeoczyłem) o nazwie inJustDecodeBounds, której javadoc brzmi:

Jeśli ustawione na true, dekoder będzie return null (no bitmap), ale Wynocha... pola nadal będą ustawione, pozwalając rozmówcy na odpytywanie bitmap bez konieczności przydzielania pamięć dla swoich pikseli.

Użyłem go, aby dowiedzieć się rzeczywisty rozmiar bitmapy, a następnie zdecydowałem się zmniejszyć próbkę za pomocą opcji inSampleSize. To przynajmniej unika błędów OOM podczas dekodowania plik.

Odniesienie:
1. Obsługa większych Bitmap
2. Jak uzyskać informacje o Bitmapie przed dekodowaniem

 55
Author: Samuh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-02-09 13:17:32

Po kilku dniach zmagania się z unikaniem wszystkich OutOfMemory błędów, które otrzymywałem z różnymi urządzeniami, tworzę to:

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;
}

Ta metoda działa ze wszystkimi urządzeniami, które testowałem, ale myślę, że jakość może być lepsza przy użyciu innego procesu, którego nie jestem świadomy.

Mam nadzieję, że mój kod pomoże innym programistom w tej samej sytuacji. Doceniam również, jeśli starszy programista może pomóc, dając sugestię o innym procesie, aby uniknąć utraty (mniej) jakości w procesie.

 13
Author: Ryan Amaral,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-26 00:29:26

To, co zrobiłem sam, to:

  • użyj inJustDecodeBounds, Aby uzyskać oryginalny rozmiar obrazu
  • mają stałą maksymalną powierzchnię do ładowania bitmapy (powiedzmy 1Mpixels)
  • sprawdź, czy powierzchnia obrazu jest poniżej limitu, jeśli tak, to załaduj ją bezpośrednio
  • Jeśli nie Oblicz idealną szerokość i wysokość, na której powinieneś załadować bitmapę, aby pozostać poniżej maksymalnej powierzchni(jest kilka prostych obliczeń do zrobienia tutaj). Daje to współczynnik pływaka, który chcesz zastosować podczas ładowania bitmap
  • Teraz chcesz przetłumaczyć ten stosunek na odpowiedni inSampleSize (Moc 2, która nie pogarsza jakości obrazu). Używam tej funkcji:
int k = Integer.highestOneBit((int)Math.floor(ratio));
if(k==0) return 1;
else return k;
  • Następnie, ponieważ bitmapy będą ładowane z nieco wyższą rozdzielczością niż maksymalna powierzchnia (ponieważ trzeba było użyć mniejszej mocy 2), będziesz musiał zmienić rozmiar bitmapy, ale będzie to znacznie szybsze.
 12
Author: Greg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-09-14 11:44:42