Android'de bitmap'leri yeniden boyutlandırmanın bellek açısından en verimli yolu?


115

Sunucudan cihaza görüntülerin gönderildiği, görüntü yoğun bir sosyal uygulama oluşturuyorum. Cihaz daha küçük ekran çözünürlüklerine sahip olduğunda, amaçlanan ekran boyutlarına uyması için cihazdaki bitmap'leri yeniden boyutlandırmam gerekir.

Sorun, createScaledBitmap'in kullanılması, bir grup küçük resim görüntüsünü yeniden boyutlandırdıktan sonra çok sayıda yetersiz bellek hatasıyla karşılaşmama neden olmasıdır.

Android'de bitmap'leri yeniden boyutlandırmanın bellek açısından en verimli yolu nedir?


7
Sunucunuz müşterinizin RAM ve bant genişliğinden tasarruf etmek için doğru boyutu gönderemez mi?
James

2
Bu yalnızca sunucu kaynağına sahip olduğumda geçerliydi, onunla birlikte kullanılabilen bir hesaplama bileşeni vardı ve her durumda, henüz görmediği en boy oranları için görüntülerin tam boyutlarını tahmin edebiliyordu. Dolayısıyla, üçüncü taraf bir
CDN'den

Yanıtlar:


168

Bu yanıt, ölçeği küçültülmüş bir bitmap sürümünü yüklemek için inSampleSize'ın nasıl kullanılacağını açıklayan Büyük bitmap'leri Etkin Şekilde Yükleme bölümünde özetlenmiştir .

Özellikle Ön ölçeklendirme bitmapleri , çeşitli yöntemlerin ayrıntılarını, bunların nasıl birleştirileceğini ve hangilerinin en verimli bellek olduğunu açıklar.

Android'de farklı bellek özelliklerine sahip bir bit eşlemi yeniden boyutlandırmanın üç baskın yolu vardır:

createScaledBitmap API'si

Bu API, mevcut bir bitmap'i alacak ve seçtiğiniz tam boyutlara sahip YENİ bir bitmap oluşturacaktır.

Artı tarafta, tam olarak aradığınız görüntü boyutunu elde edebilirsiniz (nasıl göründüğüne bakılmaksızın). Ancak olumsuz yanı , bu API'nin çalışması için mevcut bir bitmap gerektirmesidir . Yani yeni, daha küçük bir sürüm oluşturmadan önce görüntünün yüklenmesi, kodunun çözülmesi ve bir bitmap oluşturulması gerekir. Bu, tam boyutlarınızı elde etmek açısından idealdir, ancak ek bellek yükü açısından korkunçtur. Bu nedenle, bellek bilincine sahip olma eğiliminde olan çoğu uygulama geliştiricisi için bu bir tür anlaşma kırıcıdır.

inSampleSize bayrağı

BitmapFactory.OptionsinSampleSizegeçici bir bit eşlem olarak kod çözme ihtiyacını ortadan kaldırmak için, kod çözerken görüntünüzü yeniden boyutlandıracak bir özelliğe sahiptir . Burada kullanılan bu tam sayı değeri, 1 / x küçültülmüş boyutta bir görüntü yükleyecektir. Örneğin, inSampleSize2 olarak ayarlamak yarı boyutta bir görüntü verir ve 4 olarak ayarlamak boyutun 1 / 4'ü olan bir görüntü verir. Temel olarak, görüntü boyutları her zaman kaynak boyutunuzdan iki kat daha küçük olacaktır.

Bellek perspektifinden, kullanmak inSampleSizegerçekten hızlı bir işlemdir. Etkili bir şekilde, görüntünüzün yalnızca her X'inci pikselinin kodunu sonuçta elde ettiğiniz bit eşlem olarak çözecektir. Yine de iki ana sorun var inSampleSize:

  • Size kesin kararlar vermez . Bitmap'inizin boyutunu yalnızca 2'nin bir gücü kadar azaltır.

  • En iyi kalitede yeniden boyutlandırma sağlamaz . Çoğu yeniden boyutlandırma filtresi, piksel bloklarını okuyarak ve ardından söz konusu yeniden boyutlandırılmış pikseli oluşturmak için bunları ağırlıklandırarak iyi görünen görüntüler üretir. inSampleSizetüm bunları, sadece birkaç pikselde bir okuyarak önler. Sonuç, oldukça performanslı ve düşük bellek, ancak kalite zarar görüyor.

Görüntünüzü yalnızca bir pow2 boyutunda küçültmekle uğraşıyorsanız ve filtreleme bir sorun değilse, o zaman daha fazla bellek açısından verimli (veya performans açısından verimli) bir yöntem bulamazsınız inSampleSize.

inScaled, inDensity, inTargetDensity işaretleri

Bir görüntüyü ikinin kuvvetine eşit olmayan bir boyuta ölçeklemeniz gerekiyorsa inScaled, inDensityve inTargetDensityişaretlerine ihtiyacınız olacak BitmapOptions. Ne zaman inScaledbayrağı ayarlandı, ölçeklendirme değer elde edecek sistem bölerek bitmap uygulamak için inTargetDensitytarafından inDensitydeğerlerden.

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

Bu yöntemi kullanmak resminizi yeniden boyutlandıracak ve ayrıca ona bir 'yeniden boyutlandırma filtresi' uygulayacaktır, yani sonuç daha iyi görünecektir, çünkü yeniden boyutlandırma adımında bazı ek matematik hesaba katılmıştır. Ancak dikkat edin: bu ekstra filtre adımı fazladan işlem süresi alır ve büyük görüntüler için hızlı bir şekilde toplanabilir , bu da yavaş yeniden boyutlandırmalara ve filtrenin kendisi için ekstra bellek ayırmalarına neden olabilir.

Ekstra filtreleme ek yükü nedeniyle, bu tekniği istediğiniz boyuttan önemli ölçüde daha büyük bir görüntüye uygulamak genellikle iyi bir fikir değildir.

Sihirli Kombinasyon

Bellek ve performans açısından, en iyi sonuçlar için bu seçenekleri birleştirebilirsiniz. (ayarı inSampleSize, inScaled, inDensityve inTargetDensitybayraklar)

inSampleSizeilk olarak resme uygulanacak ve onu hedef boyutunuzdan daha büyük olan bir sonraki ikiye katlayacak. Ardından, sonucu tam olarak istediğiniz boyutlara ölçeklendirmek için inDensity& inTargetDensitykullanılır, görüntüyü temizlemek için bir filtre işlemi uygulanır.

Bu ikisini birleştirmek çok daha hızlı bir işlemdir, çünkü bu inSampleSizeadım sonuçta ortaya çıkan Yoğunluğa dayalı adımın yeniden boyutlandırma filtresini uygulamak için ihtiyaç duyacağı piksel sayısını azaltacaktır.

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

Bir görüntüyü belirli boyutlara sığdırmanız ve daha iyi bir filtreleme yapmanız gerekiyorsa, bu teknik doğru boyutu elde etmek için en iyi köprüdür, ancak hızlı, düşük bellek alanıyla yapılır.

Görüntü boyutlarını alma

Tüm görüntünün kodunu çözmeden görüntü boyutunu elde etme Bit eşleminizi yeniden boyutlandırmak için, gelen boyutları bilmeniz gerekir. inJustDecodeBoundsPiksel verilerinin şifresini çözmeniz gerekmeden, görüntünün boyutlarını elde etmenize yardımcı olması için bayrağı kullanabilirsiniz .

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

Bu bayrağı, önce boyutu çözmek ve ardından hedef çözünürlüğünüze ölçeklemek için uygun değerleri hesaplamak için kullanabilirsiniz.


1
dstWidth'in ne olduğunu bize söylerseniz harika olurdu?
k0sh

@ k0sh dstWIdth, ImageView’un ie destination widthveya kısaca dstWidth'e gideceği yerin genişliğidir
tyczj

@tyczj Cevabınız için teşekkürler, ne olduğunu biliyorum, ama bazıları bilmiyor olabilir ve Colt bu soruyu gerçekten cevapladığına göre, belki de insanların kafasını karıştırmasın diye açıklayabilir.
k0sh

Güzel gönderi ... daha önce inScaled, inDensity, inTargetDensity bayraklarını bilmiyordum ...
maveroid

Android performans desen serisini izliyorum ve çok şey öğrendim!
Anis

13

Bu cevap ne kadar güzel (ve doğru) olsa da, aynı zamanda çok karmaşık. Tekerleği yeniden icat etmek yerine, Glide , Picasso , UIL , Ion gibi kütüphaneleri veya bu karmaşık ve hataya açık mantığı sizin için uygulayan diğer kütüphaneleri düşünün .

Colt'un kendisi, Ön Ölçeklendirme Bitmap Performans Modelleri Videosu'nda Glide ve Picasso'ya bir göz atmanızı bile tavsiye ediyor .

Kitaplıkları kullanarak, Colt'un yanıtında bahsedilen her bir verimliliği elde edebilirsiniz, ancak Android'in her sürümünde tutarlı bir şekilde çalışan çok daha basit API'larla.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.