Cara paling hemat memori untuk mengubah ukuran bitmap di android?

115

Saya sedang membangun aplikasi sosial intensif gambar tempat gambar dikirim dari server ke perangkat. Jika perangkat memiliki resolusi layar yang lebih kecil, saya perlu mengubah ukuran bitmap, di perangkat, agar sesuai dengan ukuran tampilan yang diinginkan.

Masalahnya adalah bahwa menggunakan createScaledBitmap menyebabkan saya mengalami banyak kesalahan kehabisan memori setelah mengubah ukuran sekumpulan gambar mini.

Apa cara paling hemat memori untuk mengubah ukuran bitmap di Android?

Colt McAnlis
sumber
7
Bisakah server Anda tidak mengirimkan ukuran yang benar sehingga Anda menghemat RAM dan bandwidth pelanggan Anda !?
Yakobus
2
Itu hanya valid jika saya memiliki sumber daya server, ia memiliki komponen komputasi yang tersedia dengannya, dan dalam semua kasus, ia dapat memprediksi dimensi gambar yang tepat untuk rasio aspek yang belum dilihatnya. Jadi, jika Anda memuat konten sumber daya dari CDN pihak ketiga (seperti saya), ini tidak berfungsi :(
Colt McAnlis

Jawaban:

168

Jawaban ini diringkas dari Memuat bitmap besar secara Efisien yang menjelaskan cara menggunakan inSampleSize untuk memuat versi bitmap yang diperkecil.

Secara khusus, bitmap Pra-penskalaan menjelaskan detail berbagai metode, cara menggabungkannya, dan mana yang paling hemat memori.

Ada tiga cara dominan untuk mengubah ukuran bitmap di Android yang memiliki properti memori berbeda:

createScaledBitmap API

API ini akan menggunakan bitmap yang ada, dan membuat bitmap BARU dengan dimensi yang tepat yang Anda pilih.

Di sisi positifnya, Anda bisa mendapatkan ukuran gambar yang Anda cari (terlepas dari tampilannya). Namun sisi negatifnya, API ini membutuhkan bitmap yang sudah ada agar dapat berfungsi . Artinya gambar harus dimuat, didekodekan, dan dibuat bitmap, sebelum dapat membuat versi baru yang lebih kecil. Ini sangat ideal dalam hal mendapatkan dimensi yang tepat, tetapi mengerikan dalam hal overhead memori tambahan. Dengan demikian, ini adalah semacam pemecah kesepakatan bagi sebagian besar pengembang aplikasi yang cenderung sadar memori

tanda inSampleSize

BitmapFactory.Optionsmemiliki properti inSampleSizeyang akan mengubah ukuran gambar Anda saat mendekodekannya, untuk menghindari kebutuhan untuk mendekode ke bitmap sementara. Nilai integer yang digunakan di sini akan memuat gambar dengan ukuran yang diperkecil 1 / x. Misalnya, menyetel inSampleSizeke 2 mengembalikan gambar berukuran setengah, dan Menyetelnya ke 4 mengembalikan gambar berukuran 1/4. Pada dasarnya ukuran gambar akan selalu lebih kecil dari ukuran sumber Anda.

Dari perspektif memori, penggunaan inSampleSizeadalah operasi yang sangat cepat. Secara efektif, ini hanya akan mendekode setiap piksel X dari gambar Anda ke bitmap yang dihasilkan. Ada dua masalah utama dengan inSampleSize:

  • Itu tidak memberi Anda resolusi yang tepat . Ini hanya mengurangi ukuran bitmap Anda dengan beberapa pangkat 2.

  • Itu tidak menghasilkan pengubahan ukuran kualitas terbaik . Sebagian besar filter pengubah ukuran menghasilkan gambar yang terlihat bagus dengan membaca blok piksel, dan kemudian menimbangnya untuk menghasilkan piksel yang diubah ukurannya. inSampleSizemenghindari semua ini dengan hanya membaca setiap beberapa piksel. Hasilnya cukup baik, dan memori rendah, tetapi kualitas buruk.

Jika Anda hanya berurusan dengan mengecilkan gambar Anda dengan beberapa ukuran pow2, dan pemfilteran tidak menjadi masalah, maka Anda tidak dapat menemukan metode yang lebih efisien memori (atau kinerja efisien) daripada inSampleSize.

tanda inScaled, inDensity, inTargetDensity

Jika Anda perlu menskalakan gambar ke dimensi yang tidak sama dengan pangkat dua, Anda memerlukan inScaled, inDensitydan inTargetDensitybendera BitmapOptions. Ketika inScaledbendera telah disetel, sistem akan mendapatkan nilai skala untuk diterapkan ke bitmap Anda dengan membaginya inTargetDensitydengan inDensitynilai.

mBitmapOptions.inScaled = true;
mBitmapOptions.inDensity = srcWidth;
mBitmapOptions.inTargetDensity =  dstWidth;

// will load & resize the image to be 1/inSampleSize dimensions
mCurrentBitmap = BitmapFactory.decodeResources(getResources(), 
      mImageIDs, mBitmapOptions);

Menggunakan metode ini akan mengubah ukuran gambar Anda, dan juga menerapkan 'filter pengubah ukuran', hasil akhirnya akan terlihat lebih baik karena beberapa perhitungan tambahan telah diperhitungkan selama langkah pengubahan ukuran. Namun berhati-hatilah: langkah filter ekstra itu, membutuhkan waktu pemrosesan ekstra , dan dapat dengan cepat bertambah untuk gambar besar, mengakibatkan pengubahan ukuran yang lambat, dan alokasi memori ekstra untuk filter itu sendiri.

Biasanya bukan ide yang baik untuk menerapkan teknik ini pada gambar yang secara signifikan lebih besar dari ukuran yang Anda inginkan, karena overhead pemfilteran ekstra.

Kombinasi Ajaib

Dari perspektif memori dan kinerja, Anda dapat menggabungkan opsi ini untuk hasil terbaik. (pengaturan inSampleSize, inScaled, inDensitydan inTargetDensitybendera)

inSampleSizepertama-tama akan diterapkan pada gambar, membuatnya menjadi kekuatan dua LEBIH BESAR berikutnya dari ukuran target Anda. Kemudian, inDensity& inTargetDensitydigunakan untuk menskalakan hasil ke dimensi yang tepat yang Anda inginkan, menerapkan operasi filter untuk membersihkan gambar.

Menggabungkan keduanya adalah operasi yang jauh lebih cepat, karena inSampleSizelangkah tersebut akan mengurangi jumlah piksel yang diperlukan untuk menerapkan langkah berbasis Densitas untuk menerapkan filter pengubahan ukurannya.

mBitmapOptions.inScaled = true;
mBitmapOptions.inSampleSize = 4;
mBitmapOptions.inDensity = srcWidth;
mBitmapOptions.inTargetDensity =  dstWidth * mBitmapOptions.inSampleSize;

// will load & resize the image to be 1/inSampleSize dimensions
mCurrentBitmap = BitmapFactory.decodeFile(fileName, mBitmapOptions);

Jika Anda perlu menyesuaikan gambar dengan dimensi tertentu, dan beberapa pemfilteran yang lebih baik, maka teknik ini adalah jembatan terbaik untuk mendapatkan ukuran yang tepat, tetapi dilakukan dalam operasi footprint yang cepat dan memiliki memori rendah.

Mendapatkan dimensi gambar

Mendapatkan ukuran gambar tanpa mendekode seluruh gambar Untuk mengubah ukuran bitmap Anda, Anda harus mengetahui dimensi yang masuk. Anda dapat menggunakan inJustDecodeBoundsbendera untuk membantu Anda mendapatkan dimensi gambar, tanpa perlu benar-benar mendekode data piksel.

// Decode just the boundaries
mBitmapOptions.inJustDecodeBounds = true;
BitmapFactory.decodeFile(fileName, mBitmapOptions);
srcWidth = mBitmapOptions.outWidth;
srcHeight = mBitmapOptions.outHeight;


//now go resize the image to the size you want

Anda dapat menggunakan tanda ini untuk mendekode ukuran terlebih dahulu, lalu menghitung nilai yang tepat untuk penskalaan ke resolusi target Anda.

Colt McAnlis
sumber
1
akan sangat bagus jika Anda bisa memberi tahu kami apa itu dstWidth?
k0sh
@ k0sh dstWIdth adalah lebar ImageView untuk tujuan destination widthmisinya atau singkatnya
dstWidth
@tyczj terima kasih atas jawabannya, saya tahu apa itu, tetapi ada beberapa yang mungkin tidak mengetahuinya dan karena Colt yang benar-benar menjawab pertanyaan ini, mungkin dia bisa menjelaskannya sehingga orang tidak bingung.
k0sh
Pos bagus ... tidak tahu tentang tanda inScaled, inDensity, inTargetDensity sebelumnya ...
maveroid
Saya telah menonton seri pola kinerja android, dan saya telah belajar banyak!
Anis
13

Jawaban ini bagus (dan akurat), tetapi juga sangat rumit. Daripada menemukan kembali roda, pertimbangkan pustaka seperti Glide , Picasso , UIL , Ion , atau beberapa lainnya yang menerapkan logika kompleks dan rawan kesalahan ini untuk Anda.

Colt sendiri bahkan merekomendasikan untuk melihat Glide dan Picasso dalam Video Pola Kinerja Bitmaps Pra-penskalaan .

Dengan menggunakan pustaka, Anda bisa mendapatkan setiap efisiensi yang disebutkan dalam jawaban Colt, tetapi dengan API yang jauh lebih sederhana yang bekerja secara konsisten di setiap versi Android.

Sam Judd
sumber