BitmapFactory.decodeStream devuelve nulo cuando se establecen las opciones

90

Tengo problemas con BitmapFactory.decodeStream(inputStream). Cuando se usa sin opciones, devolverá una imagen. Pero cuando lo uso con opciones, ya .decodeStream(inputStream, null, options)que nunca devuelve Bitmaps.

Lo que intento hacer es reducir la resolución de un mapa de bits antes de cargarlo para ahorrar memoria. He leído algunas buenas guías, pero ninguna las usa .decodeStream.

FUNCIONA MUY BIEN

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

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

NO FUNCIONA

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
fuente
1
¿Cuál es el resultado de su declaración System.out.println ("Samplesize:" ...)? ¿Indica que options.inSampleSize es un valor aceptable?
Steve Haley
Sí, devuelve un valor aceptable cada vez.
Robert Foss
Se eliminó la declaración debido a que estaba siendo depurada.
Robert Foss
1
Gracias por publicar su solución, pero solo queda una cosa más por hacer. Esta pregunta aún aparece en las listas de "preguntas sin resolver" porque no ha marcado una respuesta como "aceptada". Puede hacerlo haciendo clic en el icono de marca de verificación junto a una respuesta. Puede aceptar la respuesta de Samuh si cree que le ayudó a encontrar la solución, o puede publicar una respuesta propia y aceptarla. (Normalmente, pondría su solución en su respuesta, pero como ya lo incluyó al editar su pregunta, podría referirlos a la pregunta)
Steve Haley
Gracias por ayudar a un nuevo usuario a integrarse en la comunidad :)
Robert Foss

Respuestas:

114

El problema es que una vez que ha utilizado un InputStream de una HttpUrlConnection para recuperar metadatos de imagen, no puede rebobinar y utilizar el mismo InputStream de nuevo.

Por lo tanto, debe crear un nuevo InputStream para el muestreo real de la imagen.

  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
fuente
17
¿Significa esto que la imagen tiene que descargarse dos veces? ¿Una vez para obtener el tamaño y una vez para obtener los datos de píxeles?
user123321
1
@Robert, probablemente debería explicar este comportamiento en particular para que los otros usuarios tengan una idea clara al respecto
Muhammad Babar
1
Me preguntaba por qué no funcionaría con el mismo flujo de entrada, gracias por la breve explicación
kabuto178
1
no tiene que volver a crearlo, solo reiniciarlo resolvería el propósito. Vea mi respuesta
Shashank Tomar
5
Tengo que decir que la clase Bitmap de Android apesta. Es tan confuso y frustrante de usar.
Neon Warge
30

Intente envolver 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
Jett Hsieh
fuente
2
¿Siempre funcionó para ti? por alguna razón, obtengo un valor nulo en algunos casos muy específicos usando este método. He escrito una publicación al respecto aquí: stackoverflow.com/questions/17774442/…
desarrollador de Android
1
funcionó, así que voté a favor, pero está disponible (). El documento viene con una advertencia de que solo debe usarse para verificar si la transmisión está vacía o no y no para calcular el tamaño, ya que no es confiable.
Abhishek Chauhan
1
votado en contra, pero la conexión de flujo de entrada en cuestión es una conexión HTTP y reset () no funcionará ....
Johnny Wu
3

Creo que el problema está en la lógica de "calcular-factor de escala" porque el resto del código me parece correcto (asumiendo, por supuesto, que el flujo de entrada no es nulo).

Sería mejor si pudiera factorizar toda la lógica de cálculo de tamaño de esta rutina en un método (llámelo calculateScaleFactor () o lo que sea) y pruebe ese método de forma independiente primero.

Algo como:

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

y pruebe getScaleFactor (...) de forma independiente.

También ayudará rodear todo el código con el bloque try..catch {}, si aún no lo ha hecho.

Samuh
fuente
¡Muchas gracias por la respuesta! Intenté establecer un valor int final como 'options.inSampleSize = 2'. Pero resulta en los mismos problemas. Logcat lee 'SkImageDecoder :: Factory devolvió nulo', para cada imagen que intenté decodificar. Ejecutar el código dentro de un bloque try / catch no me ayudaría ya que no arroja nada, ¿verdad? Sin embargo, BitmapFactory.decodeStream devuelve un valor nulo si no puede crear una imagen, lo que no puede cuando intento usar un sampleSize.
Robert Foss
Esto es extraño. ¿Puedes intentar cambiar el tamaño de algún mapa de bits incluido en tu recurso? Como abrir un archivo de recursos e intentar decodificarlo. Si puede hacer eso, tal vez haya algún problema con la transmisión remota que está causando que falle la decodificación.
Samuh
BitmapFactory.decodeResource (this.getResources (), R.drawable.icon, options) == null) funciona bien con el remuestreo. El primer BitmapFactory.decodeStream con options.inJustDecodeBounds = true funciona y devuelve opciones muy bien. Pero el siguiente BitmapFactory.decodeStream con options.inJustDecodeBounds = false falla cada vez.
Robert Foss
Me temo que esto está más allá de mí ... Me interesaría saber qué podría salir mal aquí porque estoy usando un código similar y funciona bien para mí.
Samuh
4
Okay. Lo he resuelto. El problema radica en la conexión http. Cuando haya leído el flujo de entrada proporcionado por HttpUrlConnection una vez, no podrá volver a leerlo y tendrá que volver a conectarse para realizar el segundo decodeStream ().
Robert Foss
2

Puede convertir InputStream en una matriz de bytes y usar decodeByteArray (). Por ejemplo,

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
fuente