BitmapFactory.decodeStream gibt null zurück, wenn Optionen festgelegt sind

90

Ich habe Probleme mit BitmapFactory.decodeStream(inputStream). Wenn Sie es ohne Optionen verwenden, wird ein Bild zurückgegeben. Aber wenn ich es mit Optionen wie in verwende .decodeStream(inputStream, null, options), werden niemals Bitmaps zurückgegeben.

Ich versuche, eine Bitmap herunterzusampeln, bevor ich sie tatsächlich lade, um Speicherplatz zu sparen. Ich habe einige gute Anleitungen gelesen, aber keine mit .decodeStream.

WERKE NUR FEIN

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

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

FUNKTIONIERT NICHT

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
quelle
1
Was ist die Ausgabe Ihrer System.out.println-Anweisung ("Samplesize:" ...)? Gibt an, dass options.inSampleSize ein akzeptabler Wert ist?
Steve Haley
Ja, es wird jedes Mal ein akzeptabler Wert zurückgegeben.
Robert Foss
Die Anweisung wurde entfernt, da sie debuggt.
Robert Foss
1
Vielen Dank, dass Sie Ihre Lösung veröffentlicht haben, aber es gibt noch etwas zu tun. Diese Frage wird weiterhin in den Listen "Ungelöste Fragen" angezeigt, da Sie eine Antwort nicht als "akzeptiert" markiert haben. Klicken Sie dazu auf das Häkchensymbol neben einer Antwort. Sie könnten Samuhs Antwort akzeptieren, wenn Sie der Meinung sind, dass sie Ihnen bei der Suche nach der Lösung geholfen hat, oder Sie könnten eine eigene Antwort posten und sie akzeptieren. (Normalerweise würden Sie Ihre Lösung in Ihre Antwort aufnehmen, aber da Sie dies bereits durch Bearbeiten Ihrer Frage aufgenommen haben, können Sie sie einfach auf die Frage verweisen.)
Steve Haley
Vielen Dank, dass Sie einem neuen Benutzer bei der Integration in die Community geholfen haben :)
Robert Foss

Antworten:

114

Das Problem war, dass Sie, sobald Sie einen InputStream von einer HttpUrlConnection zum Abrufen von Bildmetadaten verwendet haben, nicht mehr zurückspulen und denselben InputStream erneut verwenden können.

Daher müssen Sie einen neuen InputStream für die eigentliche Abtastung des Bildes erstellen.

  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
quelle
17
Bedeutet dies, dass das Bild zweimal heruntergeladen werden muss? Einmal, um die Größe und einmal, um die Pixeldaten zu erhalten?
user123321
1
@ Robert Sie sollten wahrscheinlich dieses spezielle Verhalten erklären, damit die anderen Benutzer eine klare Vorstellung davon bekommen
Muhammad Babar
1
Ich habe mich gefragt, warum es nicht mit demselben Inputstream funktionieren würde, danke für die kurze Erklärung
kabuto178
1
Sie müssen es nicht neu erstellen, nur das Zurücksetzen würde den Zweck lösen. Siehe meine Antwort
Shashank Tomar
5
Ich muss sagen, Bitmap-Klasse von Android saugt. Es ist so verwirrend und frustrierend zu benutzen.
Neon Warge
30

Versuchen Sie, InputStream mit BufferedInputStream zu verpacken.

InputStream is = new BufferedInputStream(conn.getInputStream());
is.mark(is.available());
// Do the bound decoding
// inJustDecodeBounds =true
is.reset();  
// Do the actual decoding
Jett Hsieh
quelle
2
Hat es bei dir immer funktioniert? Aus irgendeinem Grund bekomme ich mit dieser Methode in einigen sehr spezifischen Fällen null. Ich habe hier einen Beitrag darüber geschrieben: stackoverflow.com/questions/17774442/…
Android-Entwickler
1
es hat funktioniert, also habe ich es upvoted, aber is.available () doc kommt mit der Warnung, dass es nur verwendet werden sollte, um zu überprüfen, ob der Stream leer ist oder nicht, und nicht, um die Größe zu berechnen, da dies unzuverlässig ist.
Abhishek Chauhan
1
Down-Voted, aber die fragliche Inputstream-Verbindung ist eine HTTP-Verbindung und reset () funktioniert nicht ....
Johnny Wu
3

Ich denke, das Problem liegt in der Logik "Skalierungsfaktor berechnen", da der Rest des Codes für mich korrekt aussieht (vorausgesetzt natürlich, dass der Eingabestream nicht null ist).

Es wäre besser, wenn Sie die gesamte Logik der Größenberechnung aus dieser Routine in eine Methode zerlegen könnten (nennen Sie berechneScaleFactor () oder was auch immer) und diese Methode zuerst unabhängig testen.

Etwas wie:

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

und testen Sie getScaleFactor (...) unabhängig voneinander.

Es ist auch hilfreich, den gesamten Code mit dem Block try..catch {} zu umgeben, falls dies noch nicht geschehen ist.

Samuh
quelle
Vielen Dank für die Antwort! Ich habe versucht, einen endgültigen int-Wert wie 'options.inSampleSize = 2' festzulegen. Dies führt jedoch zu denselben Problemen. Logcat liest für jedes Bild, das ich zu dekodieren versuchte, 'SkImageDecoder :: Factory hat null zurückgegeben'. Das Ausführen des Codes in einem Try / Catch-Block würde mir nicht helfen, da er nichts auslöst, oder? BitmapFactory.decodeStream gibt jedoch null zurück, wenn es kein img erstellen kann, was es nicht kann, wenn ich versuche, eine sampleSize zu verwenden.
Robert Foss
Das ist merkwürdig. Können Sie versuchen, die Größe einer in Ihrer Ressource enthaltenen Bitmap zu ändern? Öffnen Sie beispielsweise eine Ressourcendatei und versuchen Sie, sie zu dekodieren. Wenn Sie dies tun können, liegt möglicherweise ein Problem mit dem Remote-Stream vor, das dazu führt, dass die Dekodierung fehlschlägt.
Samuh
BitmapFactory.decodeResource (this.getResources (), R.drawable.icon, options) == null) funktioniert gut mit neuem Sampling. Der erste BitmapFactory.decodeStream mit options.inJustDecodeBounds = true funktioniert und gibt Optionen einwandfrei zurück. Der folgende BitmapFactory.decodeStream mit options.inJustDecodeBounds = false schlägt jedoch jedes Mal fehl.
Robert Foss
Ich fürchte, das ist mir ein Rätsel ... Ich würde gerne wissen, was hier möglicherweise schief gehen könnte, da ich ähnlichen Code verwende und er für mich einwandfrei funktioniert.
Samuh
4
OK. Ich habe es gelöst. Das Problem liegt in der http-Verbindung. Wenn Sie einmal aus dem von HttpUrlConnection bereitgestellten Eingabestream gelesen haben, können Sie nicht mehr daraus lesen und müssen die Verbindung erneut herstellen, um den zweiten decodeStream () auszuführen.
Robert Foss
2

Sie können den InputStream in ein Byte-Array konvertieren und decodeByteArray () verwenden. Beispielsweise,

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.
quelle