İçindekilerGirişİndex
YukarıİlkÖncekiSonrakiSon
Geriİleri
Yazdır
Zafer Teker
tekzaf@yahoo.com

Double Buffering ve Animasyon Teknikleri

Animasyon Teknikler

Java animasyon için geliştirilmiş hiç bir API içermez. Buna rağmen java ile animasyon yapmak çok kolaydır. Bunu kolaylaştıran en büyük etken java'da thread yapmanın diğer dillere göre çok kolay olmasıdır. Hatta o kadar kolaydır ki programcı, programa bir çok gereksiz thread'ler eklemek ister. Ben de bundan cesaret alarak bir animasyon yapmak istemiştim. Bu yazıda bu animasyonun yapılış öyküsünü ve bazı sorunları okuyacaksınız. İşte Double Buffering yöntemi önemli bir sorunun çözüm yöntemidir. Bu çözümü göreceğiz.

Animasyonun mantığı son derece basittir. Bir film gibi resimler arka arkaya gösterilirler. Son resim gösterildikten sonra aynı işlem tekrarlanır. Burada ihtiyacınız olan şey bir image dizisi, ve sırayla bu image'ları gösteren bir thread'tir. Bu thread belirli zaman aralıklarıyla bir fonksiyonu çağıracak ve fonksiyonda bir sonraki image'ı ekrana çizecek. Bunun için bir index değişkeni tutmak yeterlidir. index çizilmiş olan image'ın image dizisinde ki index'idir. Böylece çağrılan fonksiyon index değeri ile hangi image'ı çizeceğini bulabilir. Image'ı çizme işlemini bitirdikten sonra index değerini bir artıtır. Eğer son image ise index değerini tekrar 0 yapar.

Bir animasyon yapmak için Component sınıflarının paint,update ve repaint method'larının anlaşılması gerekir. Şimdi biz bir applet'in bu üç önemli method'unun ne işe yaradığını ve nasıl kullanılması gerektiği ile uğraşacağız.

paint(), update(), repaint() Üç Troyka

Bir animasyon yapmak için java'da bu üç troyka'yı anlamanız gerekmektedir. Bir component (örneğin applet) kendisini ekrana paint() ile çizmektedir. Örneğin eğer bir applet'te ekrana bir image çizmek istiyorsanız paint() method'unu override edebilirsiniz. (yani paint method'unun içini siz yazarsınız.) paint methodu g Graphics nesnesini parametre olarak almaktadır. Bu applet'in çizim yapılabilen yeridir. Bu g nesnesine yapılan çizimler applet'te görünürler. Örneğimize dönersek applet'imizde bir image çizmek için paint method'u aşağıdaki gibi override edilir.

....
public void paint(Graphics g){
	g.drawImage(image,0,0,this);
}
...

burada g nesnesi applet'in grafik nesnesidir. image daha önceden yaratılmış bir Image nesnesidir. this ise Applet'in kendisidi temsil eden bir keyword'tur. Bir Graphics nesnesine Image çizmek için ImageObserver nesnesini vermeniz gerekir. Biz burada ImageObserver olarak applet'in kendisini verdik.

Burada dikkat edilmesi gereken çizimlerin applet'in Graphics nesnesine yapılmasıdır. Çizim yapmak için paint method'u override etmek zorunda değilsiniz. Örneğin Applet'in herhangi bir yerinde

getGraphics().drawImage(image,0,0,this);

şeklinde de çizim yapabilirsiniz. Ancak burada bir sorun karşımıza çıkar. Bu şekilde image'ı çizdiğinizi varsayalım. Image ekran'da gözükür. Burada bir sorun yok. Ancak applet'in bulunduğu pencerenin önüne başka bir pencere gelirse veya pencere simge durumuna getirilirse sorun çıkacaktır. Bir daha applet'i görülebilir hale getirdiğinizde daha önce çizdiğiniz image'ın orada olmadığını göreceksiniz. Sanki kuş olup uçmuştur.

Burada olan şudur. Eğer bir applet ekranda görünümü engellendikten sonra yeniden görülür hale geldiğinde applet'in repaint() method'u çağrılacaktır. repaint() ise update() method'unu çağıracaktır. (Aslında repaint() update() methodunu çağırmaz. Sadece çağrılmasını sağlar. ). update methodunun yaptığı işlem sonucu image'ınız görünmeyecektir. Çünkü update methodu aşağıdaki kod satırlarını yürütür.

g.setColor(getBackground());
g.fillRect(0,0,width,height);
g.setColor(getForeground());
paint(g);

Bu satırları yorumlarsak daha önce çizmiş olduğumuz image'ın neden kaybolduğunu anlayabiliriz. İlk yapılan applet'in Graphics'in rengi background'un rengi yapılır. Bir Graphics nesnesinin(burada g) rengini set etmek demek bunda sonra bu nesneye yapılacak tüm çizimlerin bu renkle gözküceği anlamına gelir. Örneğin g nesnesinin rengini kırmızı yaptıktan sonra bir dikdörtgen çizersek dikdörtgenin çizgileri kırmızı olur. fillRect methodu ise bir dikdörtgen içinin rengini boyamak içindir. Yukarıdaki dikdörtgen applet'in tamamıdır. Bu şekilde fillRect ile applet'in tamamı kendi background rengi ile boyanır. Bu arada sizin resminiz üstünden de geçilir. Bu yüzden artık resminizi göremezsiniz. Daha sonra yapılan da g nesnesinin rengini applet'in kabul ettiği foreground rengi yapmaktır. Bu işlemler bittikten sonra paint(g) methodu çağrılır. Siz paint() method'unu override etmediğiniz için hiç bir şey çizilmeyecektir. Böylece resminizin olmadığı boş bir applet ile karşılaşaksınız.

Bunu engellemek için paint() method'unu ve update() method'unu override etmelisiniz. update() method'u override ederseniz image'ınızın üstüne background rengi ile kapanmasına engel olabilirsiniz.

Özetlersek: Applet'in görüntüsü yenilenmesi gerektiği zaman repaint() method'u çağrılmaktadır. repaint() methodu hiç bir şey yapmaz. Sadece update() method'un çağrılmasını sağlar. Eğer repaint() çağrısı gelmemişse update() çağrılmaz. update() ise önce ekranı temizler ve daha sonra paint method'unu çağırır.

Ekranın Yanıp Sönmesi

Eğer bir animasyon yapıyorsanız repaint() methodu'nu sık sık çağırmanız gerekir. Applet zaten görünür durumdaysa ve yenilenmesine gerek yoksa repaint() çağrılmaz. Eğer siz bir image'ı çizdikten sonra diğer image'ı ekrana çizmişseniz eskisinin yerine yeni image'ın görülmesi için repaint() method'u çağırmalısınız. repaint() methodunu çağırma işini thread ile yapabilirsiniz. Eğer yaptığınız applet Runnable interface'i implements etmişse bu applet'i Thread nesnesini kurucusuna verebilirsiniz.

public class AnimationApplet extends Applet implements Runnable
{
	............
	Thread t;
	public void init(){
		t=new Thread(this);
		t.start();
	}
	...
	public void run(){
		//burası belirli aralıklarla çağrılacak.
	}
	....
}

Applet'in init methodu ilk çağrılan method'dur. Burada bir t thread nesnesi yaratıyoruz ve kurucuda applet'in kendisini paremetre olarak veriyoruz. Böylece applet'e bulunan run method'u devamlı çağrılacaktır. Eğer belirli zaman aralığında çağrılmasını istiyorsanız run method'unun içine aşağıdaki kodu yerleştirmelisiniz.

while(true){
	try{
		Thread.sleep(1000);
	}catch(){}	
	....
}

Böylece 1000 mili saniye süresince thread'in beklemesi sağlanır.

Her run method'u çağrıldığında repaint() methodu çağrılır. repaint() method'unda ise image çizimini yaparız. Her çizimden sonra index değerini bir artırmaya dikkat etmeliyiz. Eğer son index'te ise index değerini tekrar 0 yapmalıyız. Bu işlem devamlı tekrarlanacaktır.

Eğer update() methodunu override etmemişseniz ekranda bir yanıp sönme farkedeceksiniz. Bu yanıp sönme yukarıda anlattığımız update() method'unun çalışmasından dolayı gerçekleşir. update() method'u ekranı background rengiyle boyadığı çok kısa bir süre gözükür. Daha sonra paint() çağrıldığı için sizin resimler çizilir. Bu insanın gözüne yanıp sönme şeklinde gözükür. İşte bu yanıp sönme için iki çözümümüz vardır.

Basit çözüm update() method'unun ekranı background rengi ile boyamasına engel olmaktır. Bunun için update() method'u override edilebilir ve sadece

paint(g);

işlemi yapılır. Böylece ekranın titremesinden kurtulunur. Ancak bu çözüm iyi bir çözüm değildir.

update() method'unu yukarıdaki gibi override ederseniz applet'in background'u hiç bir zaman yenilenmez. Animasyon'da kullandığımız image'ların transparan olduğunu varsayalım. (veya boyları farklı olabilir) Bir büyük daire resminden sonra bir küçük daire resmi görünsün. Küçük daire çizildiği zaman büyük daire'nin üzerine çizilecektir. Çünkü ekran temizlenmediği için büyük daire'in görüntüsü ekran'da durmaya devam edecektir. Veya sadece tek bir resmin sağa doğru hareket ettirildiğini düşünelim. Önce resim 0,0 koordinatlarında olsun. Bir adım sonra resim 10,0 koordinatlarında olacaktır. Ancak görüntü temizlenmediği için 0,0'daki resim hale gözükmeye devam edecektir. Resim her haraket ettiğinde önceki resimler görünmeye devam edecektir. Halbuki eski resimlerin temizlenmesini ve sadece yeni resmin görüntülenmesini istiyoruz. Ancak eski görüntüyü temizlediğimiz zaman da yanıp sönen bir görüntü elde ediyoruz. Bunu çözmek için çok güzel bir çözüm vardır: Double Buffering-Çift Tamponlama

Double Buffering Yöntemi

Tüm ekranı temizleyip yeni görüntüyü çizmek yanıp sönmeye neden oluyordu. Temizleme işlemini engelediğimiz zaman da temizlenmesi gereken eski görüntüler kalıyordu. Çünkü bu son yöntem sadece birbirinin üzerine çizilen animasyonlar için geçerli olabilir. Genel çözümün adı olan Double Buffering bilgisayar dünyasında bir çok yerde kullanılan bir tekniktir. Bu teknik ile performans kazancı da elde edilmektedir. Yöntemin temeli şudur. Yeni çizimler applet'e yapmak yerine hafızada geçici olarak yarattığımız bir nesneye çizilir. Bu nesne boş bir Image nesnesi olabilir. Tüm çizimler bu nesneye yapıldıktan sonra eski görüntü ile yeni görüntü yerdeğiştirilir. Daha doğrusu yeni görüntü eski görüntünün üzerine çizilir. Bu çizme işlemi tek bir seferde yapıldığı için insan gözü yanıp sönmeyi farkedemez. Bunun için applet'te kullanılan yöntem applet'in boyutlarında bir boş bir image yaratmaktır. Çizimler bu image'ın üzerine yapılır. Ve en son bu image applet'in tamamını kaplayacak şekilde apple'e çizilir. Aşağıda bu yöntemi nasıl kullanılacağı anlatılmaktadır

Image offImage;
Graphics offG;
...
public void update(Graphics g){
	paint(g);
}
public void paint(Graphics g){
	if(offImage==null){
		offImage=createImage(size().width,size().height);	
		offG=offImage.getGraphics();
	}else{
		offG.setColor(getBackground());
      	offG.fillRect(0,0,size().width,size().height);
	    offG.setColor(getForeground());	
	}
	//Burada tüm çizimleri offG üzerine yap.
	.....
	.....
	g.drawImage(offImage,0,0,this);
}
......

Önce update() methodunu override ederek sadece paint() method'unu çağırmasını sağlıyoruz. offImage nesnesi hafızada çizimin yapıldığı image nesnesidir. Eğer bu image nesnesi null ise yaratıyoruz. Değilse eski görüntüsünü temizliyoruz. Image yaratırken boyunu applet ile aynı yapıyoruz. offG ise offImage'ın Graphics nesnesidir. Tüm çizmlerimizi bu offG nesnesine yapıyoruz. Tüm işlemlerimiz bittikten sonra hafızada yarattığımız image'ı applet'e çiziyoruz. Böylece tek bir çizimle tüm görüntü değişmiş oluyor. Böylece yanıp sönmeden kurtulmuş oluyoruz.

Sonuç olarak Double Buffering yöntemi bir programcının (özellikle thread ve görüntünün bir arada kullanıldığı programlar yapan programcının) bilmesi gereken bir yöntemdir. Bunu da çözmüş olduğu sorun ile bize göstermiştir.

İçindekilerGirişİndex
YukarıİlkÖncekiSonrakiSon
Geriİleri
Yazdır