BitmapFactory.decodeStream zwraca wartość null, gdy opcje są ustawione

90

Mam problemy z BitmapFactory.decodeStream(inputStream). Używając go bez opcji, zwróci obraz. Ale kiedy używam go z opcjami, ponieważ .decodeStream(inputStream, null, options)nigdy nie zwraca bitmap.

To, co próbuję zrobić, to zmniejszyć próbkowanie mapy bitowej, zanim faktycznie ją załaduję, aby zaoszczędzić pamięć. Przeczytałem kilka dobrych przewodników, ale żaden nie używa .decodeStream.

DZIAŁA WYJĄTKOWO DOBRZE

URL url = new URL(sUrl);
HttpURLConnection connection  = (HttpURLConnection) url.openConnection();

InputStream is = connection.getInputStream();
Bitmap img = BitmapFactory.decodeStream(is, null, options);

NIE DZIAŁA

InputStream is = connection.getInputStream();
Bitmap img = BitmapFactory.decodeStream(is, null, options);

InputStream is = connection.getInputStream();

Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;

BitmapFactory.decodeStream(is, null, options);

Boolean scaleByHeight = Math.abs(options.outHeight - TARGET_HEIGHT) >= Math.abs(options.outWidth - TARGET_WIDTH);

if (options.outHeight * options.outWidth * 2 >= 200*100*2){
    // Load, scaling to smallest power of 2 that'll get it <= desired dimensions
    double sampleSize = scaleByHeight
    ? options.outHeight / TARGET_HEIGHT
    : options.outWidth / TARGET_WIDTH;
    options.inSampleSize =
        (int)Math.pow(2d, Math.floor(
        Math.log(sampleSize)/Math.log(2d)));
}

// Do the actual decoding
options.inJustDecodeBounds = false;
Bitmap img = BitmapFactory.decodeStream(is, null, options);
Robert Foss
źródło
1
Jaki jest wynik instrukcji System.out.println ("Samplesize:" ...)? Czy oznacza to, że options.inSampleSize jest dopuszczalną wartością?
Steve Haley
Tak, za każdym razem zwraca akceptowalną wartość.
Robert Foss
Usunięto instrukcję, ponieważ jest debugowana.
Robert Foss
1
Dziękujemy za przesłanie rozwiązania, ale jest jeszcze jedna rzecz do zrobienia. To pytanie nadal pojawia się na listach „nierozwiązanych pytań”, ponieważ nie oznaczyłeś odpowiedzi jako „zaakceptowaną”. Możesz to zrobić, klikając ikonę zaznaczenia obok odpowiedzi. Możesz zaakceptować odpowiedź Samuha, jeśli uważasz, że pomogła ci ona w znalezieniu rozwiązania, lub możesz opublikować własną odpowiedź i zaakceptować ją. (Zwykle umieściłbyś swoje rozwiązanie w swojej odpowiedzi, ale ponieważ już to uwzględniłeś, edytując swoje pytanie, możesz po prostu odesłać je do pytania).
Steve Haley
Dziękujemy za pomoc nowemu użytkownikowi w integracji ze społecznością :)
Robert Foss

Odpowiedzi:

114

Problem polegał na tym, że po użyciu InputStream z HttpUrlConnection do pobrania metadanych obrazu nie można przewinąć i ponownie użyć tego samego InputStream.

Dlatego musisz utworzyć nowy InputStream dla faktycznego próbkowania obrazu.

  Options options = new BitmapFactory.Options();
  options.inJustDecodeBounds = true;

  BitmapFactory.decodeStream(is, null, options);

  Boolean scaleByHeight = Math.abs(options.outHeight - TARGET_HEIGHT) >= Math.abs(options.outWidth - TARGET_WIDTH);

  if(options.outHeight * options.outWidth * 2 >= 200*200*2){
         // Load, scaling to smallest power of 2 that'll get it <= desired dimensions
        double sampleSize = scaleByHeight
              ? options.outHeight / TARGET_HEIGHT
              : options.outWidth / TARGET_WIDTH;
        options.inSampleSize = 
              (int)Math.pow(2d, Math.floor(
              Math.log(sampleSize)/Math.log(2d)));
     }

        // Do the actual decoding
        options.inJustDecodeBounds = false;

        is.close();
        is = getHTTPConnectionInputStream(sUrl);
        Bitmap img = BitmapFactory.decodeStream(is, null, options);
        is.close();
Robert Foss
źródło
17
Czy to oznacza, że ​​obraz należy pobrać dwukrotnie? Raz, aby uzyskać rozmiar i raz, aby uzyskać dane pikseli?
user123321
1
@Robert, prawdopodobnie powinieneś wyjaśnić to zachowanie, aby inni użytkownicy mieli o tym jasny obraz
Muhammad Babar
1
Zastanawiałem się, dlaczego sam nie działałby z tym samym strumieniem wejściowym, dzięki za krótkie wyjaśnienie
kabuto178
1
nie musisz go odtwarzać, samo zresetowanie rozwiązałoby cel. Zobacz moją odpowiedź
Shashank Tomar
5
Muszę powiedzieć, że klasa Bitmap w Androidzie jest do niczego. To takie mylące i frustrujące w użyciu.
Neon Warge,
30

Spróbuj opakować InputStream za pomocą BufferedInputStream.

InputStream is = new BufferedInputStream(conn.getInputStream());
is.mark(is.available());
// Do the bound decoding
// inJustDecodeBounds =true
is.reset();  
// Do the actual decoding
Jett Hsieh
źródło
2
czy to zawsze działało dla ciebie? z jakiegoś powodu otrzymuję wartość null w niektórych bardzo szczególnych przypadkach przy użyciu tej metody. napisałem na ten temat post tutaj: stackoverflow.com/questions/17774442/ ...
programista Androida
1
zadziałało, więc zagłosowałem za nim, ale dokument is.available () zawiera ostrzeżenie, że powinien być używany tylko do sprawdzania, czy strumień jest pusty, czy nie, a nie do obliczania rozmiaru, ponieważ jest to zawodne.
Abhishek Chauhan
1
down-głosowanie, ale połączenie InputStream w pytaniu jest połączenie HTTP i reset () nie będzie działać ....
Johnny Wu
3

Myślę, że problem tkwi w logice "obliczania współczynnika skali", ponieważ reszta kodu wygląda na poprawną (zakładając oczywiście, że strumień wejściowy nie jest zerowy).

Byłoby lepiej, gdybyś mógł rozliczyć całą logikę obliczania rozmiaru z tej procedury na metodę (wywołać ją calcScaleFactor () lub cokolwiek innego) i najpierw przetestować tę metodę niezależnie.

Coś jak:

// Get the stream 
InputStream is = mUrl.openStream();

// get the Image bounds
BitmapFactory.Options options=new BitmapFactory.Options(); 
options.inJustDecodeBounds = true;

bitmap = BitmapFactory.decodeStream(is,null,options);

//get actual width x height of the image and calculate the scale factor
options.inSampleSize = getScaleFactor(options.outWidth,options.outHeight,
                view.getWidth(),view.getHeight());

options.inJustDecodeBounds = false;
bitmap=BitmapFactory.decodeStream(mUrl.openStream(),null,options);

i niezależnie przetestuj getScaleFactor (...).

Pomoże również otoczyć cały kod blokiem try..catch {}, jeśli nie zostało to jeszcze zrobione.

Samuh
źródło
Wielkie dzięki za odpowiedź! Próbowałem ustawić ostateczną wartość int, taką jak „options.inSampleSize = 2”. Ale prowadzi to do tych samych problemów. Logcat odczytuje „SkImageDecoder :: Factory zwrócił wartość null” dla każdego obrazu, który próbowałem zdekodować. Uruchomienie kodu w bloku try / catch nie pomogłoby, ponieważ niczego nie rzuca, prawda? Jednak BitmapFactory.decodeStream zwraca wartość null, jeśli nie może utworzyć obrazu img, czego nie może, gdy próbuję użyć sampleSize.
Robert Foss
To jest dziwne. Czy możesz spróbować zmienić rozmiar niektórych bitmap dołączonych do twojego zasobu? Na przykład otwórz plik zasobów i spróbuj go zdekodować. Jeśli możesz to zrobić, być może występuje problem ze strumieniem zdalnym, który powoduje niepowodzenie dekodowania.
Samuh
BitmapFactory.decodeResource (this.getResources (), R.drawable.icon, options) == null) działa dobrze z ponownym próbkowaniem. Pierwszy BitmapFactory.decodeStream z options.inJustDecodeBounds = true działa i zwraca opcje w porządku. Ale następujący BitmapFactory.decodeStream z options.inJustDecodeBounds = false zawodzi za każdym razem.
Robert Foss
Obawiam się, że to mnie przerasta ... Chciałbym wiedzieć, co może tu pójść nie tak, ponieważ używam podobnego kodu i dla mnie działa dobrze.
Samuh
4
Ok. Rozwiązałem to. Problem polega na połączeniu http. Kiedy raz przeczytałeś ze strumienia wejściowego dostarczonego przez HttpUrlConnection, nie możesz odczytać z niego ponownie i musisz ponownie połączyć się, aby wykonać drugą decodeStream ().
Robert Foss
2

Możesz przekonwertować InputStream na tablicę bajtów i użyć decodeByteArray (). Na przykład,

public static Bitmap decodeSampledBitmapFromStream(InputStream inputStream, int reqWidth, int reqHeight) {
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    try {
        int n;
        byte[] buffer = new byte[1024];
        while ((n = inputStream.read(buffer)) > 0) {
            outputStream.write(buffer, 0, n);
        }
        return decodeSampledBitmapFromByteArray(outputStream.toByteArray(), reqWidth, reqHeight);
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            outputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    return null;
}

public static Bitmap decodeSampledBitmapFromByteArray(byte[] data, int reqWidth, int reqHeight) {
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeByteArray(data, 0, data.length, options);
    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
    options.inJustDecodeBounds = false;
    return BitmapFactory.decodeByteArray(data, 0, data.length, options);
}

private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int
        reqHeight) {
    int width = options.outWidth;
    int height = options.outHeight;
    int inSampleSize = 1;
    if (width > reqWidth || height > reqHeight) {
        int halfWidth = width / 2;
        int halfHeight = height / 2;
        while (halfWidth / inSampleSize >= reqWidth && halfHeight / inSampleSize >= reqHeight) {
            inSampleSize *= 2;
        }
    }
    return inSampleSize;
}
Jimmy Sun
źródło