BitmapFactory.decodeStream retornando nulo quando as opções são definidas

90

Estou tendo problemas com BitmapFactory.decodeStream(inputStream). Ao usá-lo sem opções, ele retornará uma imagem. Mas quando eu uso com opções como .decodeStream(inputStream, null, options)nunca retorna Bitmaps.

O que estou tentando fazer é reduzir a resolução de um bitmap antes de realmente carregá-lo para economizar memória. Eu li alguns bons guias, mas nenhum usando .decodeStream.

FUNCIONA MUITO BEM

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

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

NÃO 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
fonte
1
Qual é a saída de sua instrução System.out.println ("Samplesize:" ...)? Está indicando que options.inSampleSize é um valor aceitável?
Steve Haley
Sim, sempre retorna um valor aceitável.
Robert Foss
Removida a instrução por estar sendo depurada.
Robert Foss
1
Obrigado por postar sua solução, mas há apenas mais uma coisa a fazer. Esta pergunta ainda aparece nas listas de "perguntas não resolvidas" porque você não marcou uma resposta como "aceita". Você pode fazer isso clicando no ícone da marca de seleção ao lado de uma resposta. Você pode aceitar a resposta de Samuh se achar que ela o ajudou a encontrar a solução, ou pode postar uma resposta sua e aceitá-la. (Normalmente você colocaria sua solução em sua resposta, mas como você já incluiu isso editando sua pergunta, você pode simplesmente encaminhá-los para a pergunta.)
Steve Haley
Obrigado por ajudar um novo usuário a se integrar à comunidade :)
Robert Foss

Respostas:

114

O problema era que, uma vez que você usou um InputStream de um HttpUrlConnection para buscar metadados de imagem, você não pode retroceder e usar o mesmo InputStream novamente.

Portanto, você deve criar um novo InputStream para a amostragem real da imagem.

  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
fonte
17
Isso significa que a imagem precisa ser baixada duas vezes? Uma vez para obter o tamanho e outra para obter os dados de pixel?
user123321
1
@Robert, você provavelmente deve explicar esse comportamento em particular para que os outros usuários tenham uma ideia clara sobre isso
Muhammad Babar
1
Eu estava me perguntando por que não funcionaria com o mesmo fluxo de entrada, obrigado pela breve explicação
kabuto178
1
você não precisa recriá-lo, apenas redefini-lo resolveria o propósito. Veja minha resposta
Shashank Tomar
5
Eu tenho que dizer que a classe Bitmap do Android é uma droga. É tão confuso e frustrante de usar.
Neon Warge
30

Tente agrupar InputStream com 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
fonte
2
sempre funcionou para você? por algum motivo, recebo nulo em alguns casos muito específicos usando esse método. escrevi um post sobre isso aqui: stackoverflow.com/questions/17774442/…
desenvolvedor Android
1
funcionou, então eu votei a favor, mas is.available () doc vem com um aviso de que só deve ser usado para verificar se o fluxo está vazio ou não e não para calcular o tamanho, pois isso não é confiável.
Abhishek Chauhan
1
votação negativa, mas a conexão de fluxo de entrada em questão é uma conexão HTTP e reset () não funcionará ....
Johnny Wu
3

Acho que o problema é com a lógica "calcular fator de escala" porque o resto do código parece correto para mim (assumindo, é claro, que o fluxo de entrada não seja nulo).

Seria melhor se você pudesse fatorar toda a lógica de cálculo de tamanho dessa rotina em um método (chame-o calculeScaleFactor () ou qualquer outro) e teste esse método independentemente primeiro.

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

e teste getScaleFactor (...) independentemente.

Também ajudará a envolver todo o código com o bloco try..catch {}, se ainda não tiver sido feito.

Samuh
fonte
Muito obrigado pela resposta! Tentei definir um valor int final como 'options.inSampleSize = 2'. Mas isso resulta nos mesmos problemas. Logcat lê 'SkImageDecoder :: Factory retornou nulo', para cada imagem que tentei decodificar. Executar o código dentro de um bloco try / catch não me ajudaria, pois não está jogando nada, certo? No entanto, BitmapFactory.decodeStream retorna null se não puder criar um img, o que não é possível quando tento usar um sampleSize.
Robert Foss
Isto é estranho. Você pode tentar redimensionar algum bitmap incluído no seu recurso? Como abrir um arquivo de recurso e tentar decodificá-lo. Se você puder fazer isso, talvez haja algum problema com o fluxo remoto que está causando a falha na decodificação.
Samuh
BitmapFactory.decodeResource (this.getResources (), R.drawable.icon, options) == null) funciona bem com reamostragem. O primeiro BitmapFactory.decodeStream com options.inJustDecodeBounds = true funciona e retorna opções perfeitamente. Mas o seguinte BitmapFactory.decodeStream com options.inJustDecodeBounds = false falha sempre.
Robert Foss
Receio que isso esteja além de mim ... Eu estaria interessado em saber o que pode dar errado aqui porque estou usando um código semelhante e ele funciona muito bem para mim.
Samuh
4
Está bem. Eu resolvi isso. O problema está na conexão http. Depois de ler o fluxo de entrada fornecido pelo HttpUrlConnection uma vez, você não pode ler novamente e precisa se reconectar para fazer o segundo decodeStream ().
Robert Foss
2

Você pode converter o InputStream em uma matriz de bytes e usar o decodeByteArray (). Por exemplo,

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
fonte