BitmapFactory.decodeStream renvoyant null lorsque les options sont définies

90

J'ai des problèmes avec BitmapFactory.decodeStream(inputStream). Lorsque vous l'utilisez sans options, il renverra une image. Mais quand je l'utilise avec des options car .decodeStream(inputStream, null, options)il ne renvoie jamais de Bitmaps.

Ce que j'essaie de faire, c'est de sous-échantillonner un Bitmap avant de le charger pour économiser de la mémoire. J'ai lu quelques bons guides, mais aucun n'utilise .decodeStream.

Fonctionne très bien

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

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

NE FONCTIONNE PAS

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
la source
1
Quelle est la sortie de votre instruction System.out.println ("Samplesize:" ...)? Indique que options.inSampleSize est une valeur acceptable?
Steve Haley
Oui, il renvoie une valeur acceptable à chaque fois.
Robert Foss
Suppression de l'instruction en raison du débogage.
Robert Foss
1
Merci d'avoir publié votre solution, mais il ne reste qu'une chose à faire. Cette question apparaît toujours dans les listes de "questions non résolues" car vous n'avez pas marqué une réponse comme "acceptée". Vous pouvez le faire en cliquant sur l'icône de coche à côté d'une réponse. Vous pouvez accepter la réponse de Samuh si vous pensez qu'elle vous a aidé à trouver la solution, ou vous pouvez poster une réponse de votre choix et l'accepter. (Normalement, vous mettez votre solution dans votre réponse, mais comme vous l'avez déjà incluse en éditant votre question, vous pouvez simplement les renvoyer à la question.)
Steve Haley
Merci d'aider un nouvel utilisateur à s'intégrer dans la communauté :)
Robert Foss

Réponses:

114

Le problème était qu'une fois que vous avez utilisé un InputStream à partir d'un HttpUrlConnection pour récupérer les métadonnées d'image, vous ne pouvez plus rembobiner et utiliser à nouveau le même InputStream.

Par conséquent, vous devez créer un nouveau InputStream pour l'échantillonnage réel de l'image.

  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
la source
17
Cela signifie-t-il que l'image doit être téléchargée deux fois? Une fois pour obtenir la taille et une fois pour obtenir les données de pixels?
user123321
1
@Robert, vous devriez probablement expliquer ce comportement particulier afin que les autres utilisateurs aient une idée claire à ce sujet
Muhammad Babar
1
Je me demandais pourquoi cela ne fonctionnerait pas avec le même flux d'entrée moi-même, merci pour la brève explication
kabuto178
1
vous n'avez pas à le recréer, le simple fait de le réinitialiser résoudrait l'objectif. Voir ma réponse
Shashank Tomar
5
Je dois dire que la classe Bitmap d'Android est nul. C'est tellement déroutant et frustrant à utiliser.
Neon Warge
30

Essayez d'envelopper InputStream avec 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
la source
2
cela a-t-il toujours fonctionné pour vous? pour une raison quelconque, j'obtiens null sur certains cas très spécifiques en utilisant cette méthode. J'ai écrit un article à ce sujet ici: stackoverflow.com/questions/17774442/…
développeur android
1
cela a fonctionné, je l'ai donc voté à la hausse mais est disponible () doc est livré avec un avertissement qu'il ne devrait être utilisé que pour vérifier si le flux est vide ou non et non pour calculer la taille car cela n'est pas fiable.
Abhishek Chauhan
1
voté à la baisse, mais la connexion de flux d'entrée en question est une connexion HTTP et reset () ne fonctionnera pas ....
Johnny Wu
3

Je pense que le problème vient de la logique "calculer-facteur d'échelle" parce que le reste du code me semble correct (en supposant bien sûr que le flux d'entrée n'est pas nul).

Ce serait mieux si vous pouvez factoriser toute la logique de calcul de taille de cette routine dans une méthode (appelez-la CalculateScaleFactor () ou autre) et tester cette méthode indépendamment d'abord.

Quelque chose comme:

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

et testez getScaleFactor (...) indépendamment.

Cela aidera également à entourer tout le code du bloc try..catch {}, si ce n'est déjà fait.

Samuh
la source
Merci beaucoup pour la réponse! J'ai essayé de définir une valeur int finale comme 'options.inSampleSize = 2'. Mais cela entraîne les mêmes problèmes. Logcat lit 'SkImageDecoder :: Factory a retourné null', pour chaque image que j'ai essayé de décoder. Exécuter le code dans un bloc try / catch ne m'aiderait pas car il ne lance rien, non? Cependant, BitmapFactory.decodeStream renvoie null s'il ne peut pas créer une image, ce qu'il ne peut pas lorsque j'essaye d'utiliser un sampleSize.
Robert Foss
Cela est étrange. Pouvez-vous essayer de redimensionner certains Bitmap inclus dans votre ressource? Comme ouvrir un fichier de ressources et essayer de le décoder. Si vous pouvez le faire, il y a peut-être un problème avec le flux distant qui provoque l'échec du décodage.
Samuh
BitmapFactory.decodeResource (this.getResources (), R.drawable.icon, options) == null) fonctionne très bien avec le rééchantillonnage. Le premier BitmapFactory.decodeStream avec options.inJustDecodeBounds = true fonctionne et renvoie les options très bien. Mais le BitmapFactory.decodeStream suivant avec options.inJustDecodeBounds = false échoue à chaque fois.
Robert Foss
J'ai peur que cela ne me dépasse ... Je serais intéressé de savoir ce qui pourrait mal se passer ici parce que j'utilise un code similaire et cela fonctionne très bien pour moi.
Samuh
4
D'accord. Je l'ai résolu. Le problème réside dans la connexion http. Lorsque vous avez lu une fois à partir du flux d'entrée fourni par HttpUrlConnection, vous ne pouvez plus le lire et vous devez vous reconnecter pour faire le deuxième decodeStream ().
Robert Foss
2

Vous pouvez convertir InputStream en un tableau d'octets et utiliser decodeByteArray (). Par exemple,

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
la source