BitmapFactory.decodeStream mengembalikan null saat opsi disetel

90

Saya mengalami masalah dengan BitmapFactory.decodeStream(inputStream). Saat menggunakannya tanpa opsi, itu akan mengembalikan gambar. Tetapi ketika saya menggunakannya dengan opsi seperti di .decodeStream(inputStream, null, options)dalamnya tidak pernah mengembalikan Bitmap.

Apa yang saya coba lakukan adalah men-downsample Bitmap sebelum saya benar-benar memuatnya untuk menghemat memori. Saya telah membaca beberapa panduan bagus, tetapi tidak ada yang menggunakan .decodeStream.

KARYA HANYA HALUS

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

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

TIDAK BEKERJA

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
sumber
1
Apa output dari pernyataan System.out.println ("Samplesize:" ...) Anda? Apakah menunjukkan bahwa options.inSampleSize adalah nilai yang dapat diterima?
Steve Haley
Ya, ini mengembalikan nilai yang dapat diterima setiap saat.
Robert Foss
Menghapus pernyataan karena sedang debug.
Robert Foss
1
Terima kasih telah memposting solusi Anda, tetapi hanya ada satu hal lagi yang harus dilakukan. Pertanyaan ini masih muncul di daftar "pertanyaan yang belum terselesaikan" karena Anda belum menandai tanggapan sebagai "diterima". Anda dapat melakukannya dengan mengklik ikon tanda centang di sebelah jawaban. Anda dapat menerima jawaban Samuh jika Anda merasa itu membantu Anda menemukan solusinya, atau Anda dapat memposting jawaban Anda sendiri dan menerimanya. (Biasanya Anda akan memasukkan solusi Anda ke dalam jawaban Anda, tetapi karena Anda telah memasukkannya dengan mengedit pertanyaan Anda, Anda bisa merujuk mereka ke pertanyaan itu.)
Steve Haley
Terima kasih telah membantu pengguna baru untuk berintegrasi ke dalam komunitas :)
Robert Foss

Jawaban:

114

Masalahnya adalah setelah Anda menggunakan InputStream dari HttpUrlConnection untuk mengambil metadata gambar, Anda tidak dapat memutar ulang dan menggunakan InputStream yang sama lagi.

Oleh karena itu, Anda harus membuat InputStream baru untuk pengambilan sampel gambar yang sebenarnya.

  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
sumber
17
Apakah ini berarti gambar harus diunduh dua kali? Sekali untuk mendapatkan ukuran dan sekali untuk mendapatkan data piksel?
pengguna123321
1
@Robert Anda mungkin harus menjelaskan perilaku khusus ini sehingga pengguna lain mendapatkan gambaran yang jelas tentang itu
Muhammad Babar
1
Saya bertanya-tanya mengapa itu tidak akan bekerja dengan inputstream yang sama sendiri, terima kasih atas penjelasan singkatnya
kabuto178
1
Anda tidak perlu membuatnya kembali, hanya dengan menyetel ulang itu akan menyelesaikan tujuan. Lihat jawaban saya
Shashank Tomar
5
Saya harus mengatakan kelas Bitmap Android menyebalkan. Sangat membingungkan dan membuat frustasi untuk digunakan.
Neon Warge
30

Coba bungkus InputStream dengan 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
sumber
2
apakah itu selalu berhasil untukmu? untuk beberapa alasan, saya mendapatkan null pada beberapa kasus yang sangat spesifik menggunakan metode ini. Saya telah menulis posting tentang itu di sini: stackoverflow.com/questions/17774442/…
pengembang android
1
itu bekerja jadi saya upvoted tetapi is.available () doc dilengkapi dengan peringatan bahwa itu harus digunakan hanya untuk memeriksa apakah aliran kosong atau tidak dan bukan untuk menghitung ukuran karena ini tidak dapat diandalkan.
Abhishek Chauhan
1
tidak memilih, tetapi koneksi inputstream yang dimaksud adalah koneksi HTTP dan reset () tidak akan berfungsi ....
Johnny Wu
3

Saya pikir masalahnya adalah dengan logika "menghitung-skala-faktor" karena sisa kode terlihat benar bagi saya (dengan asumsi tentu saja bahwa inputstream tidak null).

Akan lebih baik jika Anda dapat memfaktorkan semua logika kalkulasi ukuran dari rutinitas ini ke dalam sebuah metode (sebut saja kalkulasiScaleFactor () atau apa pun) dan uji metode itu secara independen terlebih dahulu.

Sesuatu seperti:

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

dan uji getScaleFactor (...) secara independen.

Ini juga akan membantu untuk mengelilingi seluruh kode dengan blok try..catch {}, jika belum dilakukan.

Samuh
sumber
Terima kasih banyak atas jawabannya! Saya mencoba mengatur nilai int akhir seperti 'options.inSampleSize = 2'. Tapi itu menghasilkan masalah yang sama. Logcat membaca 'SkImageDecoder :: Factory return null', untuk setiap gambar yang saya coba dekode. Menjalankan kode di dalam blok coba / tangkap tidak akan membantu saya karena kode tidak melempar apa pun, bukan? Namun BitmapFactory.decodeStream mengembalikan null jika tidak dapat membuat img, yang tidak bisa ketika saya mencoba menggunakan sampleSize.
Robert Foss
Ini aneh. Bisakah Anda mencoba mengubah ukuran beberapa Bitmap yang dibundel dalam sumber daya Anda? Seperti membuka file sumber daya dan mencoba memecahkan kodenya. Jika Anda dapat melakukannya, mungkin ada masalah dengan aliran jarak jauh yang menyebabkan decode gagal.
Samuh
BitmapFactory.decodeResource (this.getResources (), R.drawable.icon, options) == null) berfungsi baik dengan pengambilan sampel ulang. BitmapFactory.decodeStream pertama dengan options.inJustDecodeBounds = true berfungsi dan mengembalikan opsi dengan baik. Tetapi BitmapFactory.decodeStream berikut dengan options.inJustDecodeBounds = false selalu gagal.
Robert Foss
Saya khawatir ini di luar kemampuan saya ... Saya akan tertarik untuk mengetahui apa yang mungkin salah di sini karena saya menggunakan kode serupa dan berfungsi dengan baik untuk saya.
Samuh
4
Baik. Saya sudah memecahkannya. Masalahnya terletak pada koneksi-http. Saat Anda telah membaca dari input-stream yang disediakan oleh HttpUrlConnection sekali, Anda tidak dapat membacanya lagi, dan harus menyambungkan kembali untuk melakukan decodeStream () kedua.
Robert Foss
2

Anda dapat mengonversi InputStream ke array byte, dan menggunakan decodeByteArray (). Sebagai contoh,

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
sumber