BitmapFactory.decodeStream restituisce null quando le opzioni sono impostate


90

Ho problemi con BitmapFactory.decodeStream(inputStream). Quando lo si utilizza senza opzioni, restituirà un'immagine. Ma quando lo uso con le opzioni come in .decodeStream(inputStream, null, options)non restituisce mai Bitmap.

Quello che sto cercando di fare è sottocampionare una bitmap prima di caricarla effettivamente per risparmiare memoria. Ho letto alcune buone guide, ma nessuna le utilizza .decodeStream.

FUNZIONA SEMPLICEMENTE BENE

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

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

NON FUNZIONA

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

1
Qual è l'output dell'istruzione System.out.println ("Samplesize:" ...)? Indica che options.inSampleSize è un valore accettabile?
Steve Haley

Sì, restituisce ogni volta un valore accettabile.
Robert Foss

Rimossa l'istruzione perché in fase di debug.
Robert Foss

1
Grazie per aver pubblicato la tua soluzione, ma c'è solo un'altra cosa da fare. Questa domanda appare ancora negli elenchi "domande irrisolte" perché non hai contrassegnato una risposta come "accettata". Puoi farlo facendo clic sull'icona del segno di spunta accanto a una risposta. Potresti accettare la risposta di Samuh se ritieni che ti abbia aiutato a trovare la soluzione, oppure potresti pubblicare una tua risposta e accettarla. (Normalmente inseriresti la tua soluzione nella risposta, ma poiché l'hai già inclusa modificando la tua domanda, potresti semplicemente rimandarli alla domanda.)
Steve Haley

Grazie per aver aiutato un nuovo utente a integrarsi nella comunità :)
Robert Foss

Risposte:


114

Il problema era che dopo aver utilizzato un InputStream da un HttpUrlConnection per recuperare i metadati dell'immagine, non è possibile riavvolgere e utilizzare di nuovo lo stesso InputStream.

Pertanto è necessario creare un nuovo InputStream per il campionamento effettivo dell'immagine.

  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();

17
Ciò significa che l'immagine deve essere scaricata due volte? Una volta per ottenere le dimensioni e una volta per ottenere i dati dei pixel?
user123321

1
@Robert, probabilmente dovresti spiegare questo particolare comportamento in modo che gli altri utenti abbiano un'idea chiara al riguardo
Muhammad Babar

1
Mi chiedevo perché non avrebbe funzionato con lo stesso inputstream, grazie per la breve spiegazione
kabuto178

1
non è necessario ricrearlo, solo resettarlo risolverebbe lo scopo. Vedi la mia risposta
Shashank Tomar

5
Devo dire che la classe Bitmap di Android fa schifo. È così confuso e frustrante da usare.
Neon Warge

30

Prova a racchiudere InputStream con BufferedInputStream.

InputStream is = new BufferedInputStream(conn.getInputStream());
is.mark(is.available());
// Do the bound decoding
// inJustDecodeBounds =true
is.reset();  
// Do the actual decoding

2
ha sempre funzionato per te? per qualche motivo, ottengo null in alcuni casi molto specifici utilizzando questo metodo. ho scritto un post al riguardo qui: stackoverflow.com/questions/17774442/…
sviluppatore Android

1
ha funzionato quindi l'ho votato in più, ma is.available () doc viene fornito con l'avvertenza che dovrebbe essere usato solo per verificare se il flusso è vuoto o meno e non per il calcolo delle dimensioni in quanto non è affidabile.
Abhishek Chauhan

1
down-votato, ma la connessione inputstream in questione è una connessione HTTP e reset () non funzionerà ...
Johnny Wu

3

Penso che il problema sia con la logica "calcola fattore di scala" perché il resto del codice mi sembra corretto (supponendo ovviamente che inputstream non sia nullo).

Sarebbe meglio se tu potessi scomporre tutta la logica di calcolo delle dimensioni da questa routine in un metodo (chiamalo calcolaScaleFactor () o qualsiasi altra cosa) e testare prima quel metodo in modo indipendente.

Qualcosa di simile a:

// 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);

e prova getScaleFactor (...) in modo indipendente.

Aiuterà anche a racchiudere l'intero codice con il blocco try..catch {}, se non è già stato fatto.


Grazie mille per la risposta! Ho provato a impostare un valore int finale come "options.inSampleSize = 2". Ma risulta negli stessi problemi. Logcat legge 'SkImageDecoder :: Factory ha restituito null', per ogni immagine che ho provato a decodificare. L'esecuzione del codice all'interno di un blocco try / catch non mi aiuterebbe in quanto non lancia nulla, giusto? Tuttavia BitmapFactory.decodeStream restituisce null se non può creare un img, cosa che non può quando provo a usare sampleSize.
Robert Foss

Questo è strano. Puoi provare a ridimensionare alcuni bitmap raggruppati nella tua risorsa? Come aprire un file di risorse e provare a decodificarlo. Se puoi farlo, forse c'è qualche problema con il flusso remoto che sta causando il fallimento della decodifica.
Samuh

BitmapFactory.decodeResource (this.getResources (), R.drawable.icon, options) == null) funziona bene con il ricampionamento. Il primo BitmapFactory.decodeStream con options.inJustDecodeBounds = true funziona e restituisce le opzioni perfettamente. Ma il seguente BitmapFactory.decodeStream con options.inJustDecodeBounds = false fallisce ogni volta.
Robert Foss

Temo che questo sia al di là di me ... Sarei interessato a sapere cosa potrebbe andare storto qui perché sto usando un codice simile e funziona bene per me.
Samuh

4
Ok. L'ho risolto. Il problema sta nella connessione http. Quando hai letto dal flusso di input fornito da HttpUrlConnection una volta, non puoi leggere di nuovo da esso e devi riconnetterti per eseguire il secondo decodeStream ().
Robert Foss

2

È possibile convertire InputStream in una matrice di byte e utilizzare decodeByteArray (). Per esempio,

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;
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.