Geri git   Programlama ve Elektronik > Programlama Yazılım > Java - Mobile Java

Cevapla
 
Seçenekler Stil
Alt 26. November 2014, 09:27 PM   #1 (permalink)
Tekniker
 
ULAGA - ait Kullanıcı Resmi (Avatar)
 
Üyelik tarihi: Dec 2007
Mesajlar: 448
Standart Bitmap Nesnelerini Etkili Bir Şekilde Görüntüleme (Bölüm I) - Android

Bölüm I – Yüksek Çözünürlüklü Bitmap Nesnelerini Görüntüleme

Kimi zaman geliştirdiğimiz Android uygulamalarında yüksek çözünürlükte resimler görüntülemeyi isteyebiliriz. Ancak Android geliştiricilerinin bir çoğu bu işlemi uygulamalarında herhangi bir kaynaktan çektiği resmi direk olarak ImageView denetimine yüklemek ile gerçekleştirir. Türkiye’deki Android ile ilgili bir çok kitapta da resim yükleme işleminin bu şekilde anlatıldığını gördüm. Eğer yüksek çözünürlüklü resimler bu şekilde gösterilir ise büyük ihtimalle şu hata meydana gelecektir:

"java.lang.OutOfMemoryError: …"

Şunu unutmayın! Yukarıdaki hata o an geliştirme yaptığınız kendi cihanızda vermese bile başka bir cihazda vermesi yüksek ihtimaldir. Zira Google Play’i sık ziyaret edenlerdenseniz bazı uygulamaların altında; “Uygulama bende hata verdi cihazım şu …” diye bir çok yoruma rastlamışsınızdır. Bu tür hataların alınmasının sebebi Android işletim sisteminin bellek yönetimi ile ilgili kurallarından kaynaklanıyor. Zira Android işletim sistemi taşınabilir cihazlara yönelik geliştirildiği için uygulamaların sistem belleğini etkili bir şekilde kullanması gerekir. Yüksek çözünürlük resimler ise bellek tüketimi açısından oldukça açgözlüdürler.

Bu sorunu aşmak için evrensel olarak bilinen SubSampling denilen bir yöntem kullanılmakta. Bu yöntem bir [Sadece Üyelere Linkler Açıktır.Üye Olun !!! Tıklayarak Üye Olun !!!] kaynağının tümünün gösterilmesi yerine çözünürlüğünün düşürülerek gösterilmesi ile gerçekleştiriliyor. [Sadece Üyelere Linkler Açıktır.Üye Olun !!! Tıklayarak Üye Olun !!!] kaynağının çözünürlüğü düşürüldüğünde bellekte daha az yer kaplar. [Sadece Üyelere Linkler Açıktır.Üye Olun !!! Tıklayarak Üye Olun !!!] kaynaklarının bellek kullanımını bir örnekle daha anlaşılabilir bir hale getirebiliriz.

Örneğin elimizde 2 adet [Sadece Üyelere Linkler Açıktır.Üye Olun !!! Tıklayarak Üye Olun !!!] kaynağı olsun. Biri 640×480 (VGA) çözünürlüğünde diğeri de 5 megapiksel çözünürlüğünde olsun. Android [Sadece Üyelere Linkler Açıktır.Üye Olun !!! Tıklayarak Üye Olun !!!] kaynaklarını 4 farklı biçimde bellekte tutar: ALPHA_8 ARGB_4444 ARGB_8888 ve RGB_565 biçimleri. Bunların içinden en çok kullanılanı ARGB_8888 biçimidir. ARGB_8888 biçiminde her bir piksel için bellekte 4-Byte yer ayrılır. RGB bilgisi için 3Byte Alpha (transparanlık) bilgisi için de 1Byte. Bu bilgilerin doğrultusunda;
VGA için; 640*480=307200 => 307200*4=1228800-Byte (1.2MB)
5MP için; 2592*1944=5038848 => 5038848*4=20155392-Byte (19.2MB)

VGA bitmap bellekte 1.2MB yer kaplarken 5MP bitmap 19.2MB yer kaplayacaktır. Hele de 24MP bir resim yüklemeye çalıştığımızda bu yer 96MB olabiliyor. Taşınabilir bir cihaz için gerçektende çok değerli bir bellek alanı. Android uygulamalara her bir işlem (process) için belli bir bellek alanı sunar. Elbette bu ayrılan bellek miktarı da cihazdan cihaza değişir. İşte resim yükleme işlemi kendisine ayrılan bellek miktarını aştığında uygulamayı hata verdirerek çökertir.

[Sadece Üyelere Linkler Açıktır.Üye Olun !!! Tıklayarak Üye Olun !!!] kaynaklarına Subsampling işlemini uygulamadan önce [Sadece Üyelere Linkler Açıktır.Üye Olun !!! Tıklayarak Üye Olun !!!] nesnesinden bahsetmek istiyorum. Bu nesne birçok şekilde [Sadece Üyelere Linkler Açıktır.Üye Olun !!! Tıklayarak Üye Olun !!!] nesnesi oluşturabilir. Örneğin proje paketinde bulunan bir kaynaktan cihaz belleğindeki bir dosyadan tanımlanmış bir streamden ya da bir Byte dizisinden. Subsampling işlemi için [Sadece Üyelere Linkler Açıktır.Üye Olun !!! Tıklayarak Üye Olun !!!] sınıfının alt sınıfı olan [Sadece Üyelere Linkler Açıktır.Üye Olun !!! Tıklayarak Üye Olun !!!] sınıfını kullanacağız. [Sadece Üyelere Linkler Açıktır.Üye Olun !!! Tıklayarak Üye Olun !!!] sınıfının [Sadece Üyelere Linkler Açıktır.Üye Olun !!! Tıklayarak Üye Olun !!!] adında bir özelliği var. Bu özellik; 1 2 4 8 16 … diye 2’nin kuvvetlerine göre işlem yapar. Değer olarak 6 girilse dahi 4 değeri üzerinden işlem yapar. Örneğin [Sadece Üyelere Linkler Açıktır.Üye Olun !!! Tıklayarak Üye Olun !!!] oluştururken [Sadece Üyelere Linkler Açıktır.Üye Olun !!! Tıklayarak Üye Olun !!!] sınıfının [Sadece Üyelere Linkler Açıktır.Üye Olun !!! Tıklayarak Üye Olun !!!] özelliğini 8 yaparsak 2592×1944 çözünürlüğündeki bir resmi 648×486 çözünürlüğüne düşürür. Yani genişlik ve yüksekliği 1/8 şeklinde azaltır. İşte bu işleme SubSampling işlemi denir.

Peki bu [Sadece Üyelere Linkler Açıktır.Üye Olun !!! Tıklayarak Üye Olun !!!] değerini nasıl elde edebiliriz? Bu değeri elde etmek için öncelikle elimizdeki Bitmap kaynağının çözünürlüğünü hangi çözünürlüğe düşürmemiz gerektiğini bilmemiz gerekiyor. Örneğin elimizdeki cihazın Nexus 4 olduğunu düşünelim. Bu cihazın çözünürlüğü dikey olarak 768×1280 pikseldir. 5MP’lik bir resmi 2592×1944 çözünürlüğünde göstermenin hiçbir anlamı yok sanırım bu ekranda. O yüzden bu resmi dikey ekranda göstereceğimiz zaman genişliği en fazla 768 piksel olacak şekilde değiştirebiliriz. [Sadece Üyelere Linkler Açıktır.Üye Olun !!! Tıklayarak Üye Olun !!!] değerini etkin bir şekilde elde edebilmek için Anroid Developers sayfasında yayınlanan örnek bir kod var:

Kod:
public static int calculateInSampleSize(BitmapFactory.Options options int reqWidth int reqHeight) {
    // Yüklenecek olan resmin orijinal yükseklik ve genişliği
    final int height = options.outHeight;
    final int width = options.outWidth;
    int inSampleSize = 1;
 
    if (height > reqHeight || width > reqWidth) {
 
        final int halfHeight = height / 2;
        final int halfWidth = width / 2;
 
        // 2'nin kuvveti olacak şekilde en büyük inSampleSize değerini hesaplar.
        // Hesaplanan çözünürlük istenilen çözünürlükten büyük olduğu sürece 2'nin kuvvetini arttırır.
        while ((halfHeight / inSampleSize) > reqHeight && (halfWidth / inSampleSize) > reqWidth) {
            inSampleSize *= 2;
        }
    }
 
    return inSampleSize;
}
Yukarıdaki işlev ilk parametre olarak bir [Sadece Üyelere Linkler Açıktır.Üye Olun !!! Tıklayarak Üye Olun !!!] bilgi ve seçeneklerini barındıran [Sadece Üyelere Linkler Açıktır.Üye Olun !!! Tıklayarak Üye Olun !!!] nesnesini alır. Bu parametre [Sadece Üyelere Linkler Açıktır.Üye Olun !!! Tıklayarak Üye Olun !!!] kaynağının orijinal çözünürlüğünü elde etmek için kullanılacaktır. 2. ve 3. parametre ise istenilen genişlik ve yükseklik değerleridir. Bu çözünürlük değerlerine göre [Sadece Üyelere Linkler Açıktır.Üye Olun !!! Tıklayarak Üye Olun !!!] değeri hesaplanacaktır. Ancak yukarıdaki işlevi kendi uygulamalarımda denediğimde şöyle bir şey farkettim. Ortaya çıkan [Sadece Üyelere Linkler Açıktır.Üye Olun !!! Tıklayarak Üye Olun !!!] değeri 2’nin bir sonraki kuvveti olması gerekiyor. Zira [Sadece Üyelere Linkler Açıktır.Üye Olun !!! Tıklayarak Üye Olun !!!] seçeneklerinde [Sadece Üyelere Linkler Açıktır.Üye Olun !!! Tıklayarak Üye Olun !!!] değerini örneğin 4 olarak girdiğimde resmin yükseklik ve genişlik değerlerini 2’ye bölüyor. Bu yüzden bir [Sadece Üyelere Linkler Açıktır.Üye Olun !!! Tıklayarak Üye Olun !!!] kaynağını istediğim çözünürlüğe düşürememiş oluyorum. Bunun teknik sebebini şuan için bilmiyorum. O yüzden yukarıdaki işlevi çok daha duyarlı bir hale getirdim. Böylelikle istenilen çözünürlük için en uygun [Sadece Üyelere Linkler Açıktır.Üye Olun !!! Tıklayarak Üye Olun !!!] değerini bulan aşağıdaki işlevi yazdım.

Kod:
/**
 * Bitmap'in orijinal boyut bilgisini ve çevirilmesi istenilen boyut
 * bilgisini alarak gerekli olan inSampleSize değerini hesaplar.
 * 
 * @param _Options
 *            Bitmap bilgilerini barındırır.
 * @param reqWidth
 *            İstenilen çözünürlüğün genişlik değeri.
 * @param reqHeight
 *            İstenilen çözünürlüğün yükseklik değeri.
 * @return Hesaplanmış inSampleSize değeri. (2'nin kuvvetleri olabilir)
 */
public static int calculateInSampleSize(BitmapFactory.Options _Options int reqWidth int reqHeight) {
	int inSampleSize = 1;
	// Bitmap kaynağının genişliği ya da yüksekliği istenilen genişlik ya
	// da yükseklikten büyük olduğu sürece inSampleSize değeri
	// hesaplanıyor...
	if (_Options.outWidth > reqWidth || _Options.outHeight > reqHeight) {
		inSampleSize = 2;
		int calculatedWidth = _Options.outWidth / inSampleSize;
		int calculatedHeight = _Options.outHeight / inSampleSize;
		// inSampleSize değeri (2'nin kuvveti olarak) hesaplanır.
		// Hesaplanan çözünürlük (reqWidth + (reqWidth / 2)) değerinden
		// büyük olduğu sürece 2'nin kuvveti artırılır.
		while (calculatedWidth > (reqWidth + (reqWidth / 2)) && calculatedHeight > (reqHeight + (reqHeight / 2))) {
			inSampleSize *= 2;
			calculatedWidth = _Options.outWidth / (inSampleSize / 2);
			calculatedHeight = _Options.outHeight / (inSampleSize / 2);
		}
		int estimatedWidth = _Options.outWidth / inSampleSize;
		int estimatedHeight = _Options.outHeight / inSampleSize;
		// Güncel inSampleSize değeri ile oluşturulacak olan yeni çözünürlük
		// değerleri halen yüksek ise inSampleSize değeri 2 ile çarpılır.
		// Bunun sebebi; Bitmap oluşturucusunun yeni oluşturulacağı Bitmap
		// için orijinal Bitmap çözünürlüğünü inSampleSize değerinin
		// yarısına bölüyor olmasıdır.
		if (reqWidth < (estimatedWidth + (estimatedWidth / 2)) && reqHeight < (estimatedHeight + (estimatedHeight / 2)))
			inSampleSize *= 2;
	}
	return inSampleSize;
}
[Sadece Üyelere Linkler Açıktır.Üye Olun !!! Tıklayarak Üye Olun !!!] değerinin nasıl elde edileceğini öğrendiğimize göre artık bu değerin nerede nasıl kullanılacağını öğrenmenin vakti geldi. Seçtiğimiz bir resmi arayüzdeki herhangi bir ImageView denetimine atmadan önce seçilen resmi elde ettimiz [Sadece Üyelere Linkler Açıktır.Üye Olun !!! Tıklayarak Üye Olun !!!] değeri ile SubSampling işlemine tabi tutmamız gerekiyor. Bunun için aşağıdaki işlevi kullanabilirsiniz.

Kod:
/**
 * Alınan kaynak ile istenilen çözünürlüğe uygun olarak yeni bir Bitmap
 * nesnesi oluşturur.
 * 
 * @param _Resources
 *            İçerisinde Image verisi barındıran kaynaklar nesnesi.
 * @param resID
 *            SubSample'ı alınacak olan kaynağın ID'si.
 * @param reqWidth
 *            SubSampling işlemi için gerekli olan genişlik değeri.
 * @param reqHeight
 *            SubSampling işlemi için gerekli olan yükseklik değeri.
 * @return SubSampling işlemine tabi tutulmuş olan yeni Bitmap nesnesi.
 */
static Bitmap decodeSampledBitmapFromResource(Resources _Resources int resID int reqWidth int reqHeight) {
	final BitmapFactory.Options _Options = new BitmapFactory.Options();
	// Sadece kaynağa ait temel bilgilerin alınması sağlanıyor...
	_Options.inJustDecodeBounds = true;
	// inJustDecodeBounds değeri true olarak ayarlandığı için aşağıdaki
	// Bitmap için kaynak çözücüsü sadece verilen kaynağın bilgilerini
	// oluşturur. Geriye Bitmap döndermez.
	BitmapFactory.decodeResource(_Resources resID _Options);
	// inSampleSize değeri hesaplanıyor...
	_Options.inSampleSize = calculateInSampleSize(_Options reqWidth reqHeight);
	// inSampleSize değeri artık bulunduğu için çözülecek olan kaynaktan
	// Bitmap nesnesi oluşturulması da sağlanıyor...
	_Options.inJustDecodeBounds = false;
	// Hesaplanmış yeni inSampleSize değeri ile verilen kaynaktan Bitmap
	// nesnesi oluşturuluyor...
	return BitmapFactory.decodeResource(_Resources resID _Options);
}
Artık istenilen çözünürlüğe getirilmiş olan yeni [Sadece Üyelere Linkler Açıktır.Üye Olun !!! Tıklayarak Üye Olun !!!] nesnesini de elde ettiğimize göre bu [Sadece Üyelere Linkler Açıktır.Üye Olun !!! Tıklayarak Üye Olun !!!] nesnesini arayüzümüzdeki herhangi bir [Sadece Üyelere Linkler Açıktır.Üye Olun !!! Tıklayarak Üye Olun !!!] denetimine yükleme vakti geldi. Aşağıdaki kodu kullanarak bu işlemi de gerçekleştirebilirsiniz.

Kod:
ImageView _ImageView = (ImageView) findViewById(R.id.ivImage);
_ImageView.setImageBitmap(decodeSampledBitmapFromResource(getResources() R.drawable.img_large 800 480));
Böylelikle çok yüksek çözünürlüklü bir resmi 800×480 çözünürlüğünde olan bir cihaz için uygun hale getirmiş olduk.

Kaynaklar :
[Sadece Üyelere Linkler Açıktır.Üye Olun !!! Tıklayarak Üye Olun !!!]
ULAGA isimli Üye şimdilik offline konumundadır   Alıntı ile Cevapla
Cevapla

Seçenekler
Stil

Yetkileriniz
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is Açık
Smileler Açık
[IMG] Kodları Açık
HTML-KodlarıKapalı
Trackbacks are Kapalı
Pingbacks are Kapalı
Refbacks are Kapalı