Skip to content

Java Grafiken Flackerfrei zeichnen

Java und Grafiken – das ist leider ein sehr kompliziertes Thema. Während diverse Pradigmen und Logiken schnell, einfach und portabel implementiert werden können, ist das bei Grafikverarbeitung leider nicht ganz so komfortabel. Java wurde als Programmiersprache entwickelt, die in jedem System, ja sogar einer Kaffeemaschine, zum Einsatz kommen könnte. Es ist somit nicht verwunderlich, dass sämtliche Handymodelle JavaME unterstützen und auch das AndroidOS auf Java aufbaut.

Leider benötigt man gerade bei diesen Beispielen ein sehr variables grafisches Userinterface, dass sich an alle Auflösungen anpassen. Vielen nutzen dafür Grafiken, die sich selbst zeichnen, leider entstehen dabei, gerade bei Neulinge in der Grafikprogrammierung mit Java, Probleme, die man von anderen Programmiersprachen eher nicht kennt. Das häufigste Problem dabei ist das „Flackern“, also das Neuzeichnen des Bildes, das scheinbar mit dem Updateintervall nicht hinterher kommt. Damit ist das eigentliche Problem auch schon beschrieben. Das Flackern entsteht, weil man die Fläche schon Neuzeichnet, obwohl diese sich noch aufbaut.
Um dies zu verdeutlichen hier ein kleiner Auszug aus dem Beispielprojekt, das ich am Ende des Artikels zum Download zur Verfügung stelle.
Hier ein Paint wie es wahrscheinlich jeder zum ersten mal schreiben würde, der die Aufgabe bekommt 10*10 Pixel großes Quadrate zu zeichnen, die sich diagonal über die Fläche bewegen sollen.

Auszug aus der SimpleView.java

  1.  
  2.         public void paint(Graphics g)
  3.         {
  4.                 for (int i = model.getOffset() ; i < this.getWidth() ; i = i+10)
  5.                         g.drawLine(i, 0, i, this.getWidth()-1);
  6.  
  7.                 for (int i = model.getOffset() ; i < this.getHeight() ; i = i+10)
  8.                         g.drawLine(0, i, this.getHeight()-1, i);
  9.         }
  10.  

Der Controller sorgt dafür, dass das Offset im Model um eins nach oben oder bei 10 auf 0 zurück gesetzt wird und, dass durch einen Thread alle 20 ms ein repaint() ausgeführt wird.
Die 20 ms habe ich gewählt, weil dadurch 50 FPS simuliert werden (1 Sekunde = 1000 Millisekunden = 50*20 ms).

Wenn man dem Beispielprogramm in der Main dem Controller den Typ Controller.TYPE_VIEW_SIMPLE, erkennt man schnell, dass es Flackert und nicht nutzbar für die Anwendung ist.

Sollte einen nun der Ehrgeiz gepackt haben um diverse Foren nach Lösungsansätzen durchsuchen, wird man oft auf Double Buffering stoßen. DIes beschreibt ein Verfahren, bei dem man in einem unabhägigen Speicher (dem Buffer) das Bild zeichnet und dieses dann auf die eigentliche Fläche überträgt.
Im Beispiel habe ich dies in der DoubleBufferdView.java implementiert.

  1.  
  2.  
  3.         BufferedImage image;
  4.         Graphics buffer;
  5.  
  6.         public void paint(Graphics g)
  7.         {
  8.                 buffer.setColor(Color.WHITE);
  9.                 buffer.clearRect(0, 0, image.getWidth(), image.getHeight());
  10.                 buffer.fillRect(0, 0, image.getWidth(), image.getHeight());
  11.  
  12.                 buffer.setColor(Color.BLACK);
  13.                 for (int i = model.getOffset() ; i < this.getWidth() ; i = i+10)
  14.                         buffer.drawLine(i, 0, i, this.getWidth()-1);
  15.  
  16.                 for (int i = model.getOffset() ; i < this.getHeight() ; i = i+10)
  17.                         buffer.drawLine(0, i, this.getHeight()-1, i);
  18.  
  19.                 g.drawImage(image, 0, 0, null);
  20.         }
  21.  
  22.         public void init()
  23.         {
  24.                 image = new BufferedImage(this.getWidth(), this.getHeight(), BufferedImage.TYPE_INT_ARGB);
  25.                 buffer = image.getGraphics();
  26.         }
  27.  

Wir erstellen uns also ein Bild, das genauso groß wie unsere Fläche ist. Auf das Graphics Objekt dieses Bildes malen wir unsere Quadrate und malen dann das Bild auf das Graphics Objekt des Canvases (alle Views, die ich hier verwendet sind von View und dieses von Canvas abgeleitet). Um dieses im Beispiel zu bewundern müssen wir den Typ beim Konstruktur des Controllers auf Controller.TYPE_VIEW_DOUBLEBUFFERED setzen. Wenn wir nun das Programm starten erwarten wir, dass das Flackern verschwunden ist. Leider ist dies nicht der Fall. Wir können das Flackern zwar verringern, indem anstelle von BufferedImage.TYPE_INT_ARGB der ImageType BufferedImage.TYPE_INT_RGB verwendet wird, jedoch bleibt das ungeliebte Flackern vorhanden.

Viele werden sich jetzt Fragen, warum das Bild denn immer noch Flackern.
Das Problem ist in der Logik des repaint() zu finden. Das repaint() ruft das update auf und wenn man sich die Klassen anschaut und etwas debuggt findet man herraus, dass bevor das paint() aufgerufen wird erst der gesamte Hintergrund gelöscht wird. Dieser zusätzlich Befehl sorgt dafür, dass immer noch ein Flackern wahrgenommen wird. Die Lösung ist denkbar einfach: das update wird in unser abgeleiteten Klasse überschrieben und wir rufen darin direkt und einzig das paint auf.

In der FlickeringFreeView.java wird dies genutzt.

  1.  
  2.         Graphics buffer;
  3.  
  4.         public void update(Graphics g)
  5.         {
  6.                 paint(g);
  7.         }
  8.  
  9.         public void paint(Graphics g)
  10.         {
  11.                 buffer.setColor(Color.WHITE);
  12.                 buffer.clearRect(0, 0, image.getWidth(), image.getHeight());
  13.                 buffer.fillRect(0, 0, image.getWidth(), image.getHeight());
  14.  
  15.                 buffer.setColor(Color.BLACK);
  16.                 for (int i = model.getOffset() ; i < this.getWidth() ; i = i+10)
  17.                         buffer.drawLine(i, 0, i, this.getWidth()-1);
  18.  
  19.                 for (int i = model.getOffset() ; i < this.getHeight() ; i = i+10)
  20.                         buffer.drawLine(0, i, this.getHeight()-1, i);
  21.  
  22.                 g.drawImage(image, 0, 0, null);
  23.         }
  24.  
  25.         public void init()
  26.         {
  27.                 image = new BufferedImage(this.getWidth(), this.getHeight(), BufferedImage.TYPE_INT_ARGB);
  28.                 buffer = image.getGraphics();
  29.         }
  30.  

Um dieses Beispiel zu testen muss man den Typ im Konstruktor des Controllers auf Controller.TYPE_VIEW_FLICKERINGFREE setzen.
Für alle, die sich das Beispielprojekt ansehen und testen möchten :
Als Entwicklungsumgebung wurde Eclipse genutzt, entpackt kann man das Projekt einfach importieren. Zudem ist das Projekt mit Java 6 Syntax geschrieben. Wenn man alle Annotations entfernt, sollte es auch mit Java 5 laufen. Desweiteren ist das Testprojekt nachdem Model-View-Controller Paradigma aufgebaut. Ich kann nur jeden raten, dieses für die grafische Programmierung mit Java einzusetzen, weil man damit Grafik und Logik systematisch trennt.

Beispiel flackerfreies zeichnen in Java

Jens Altmann

Avatar Jens Altmann

Jens Altmann bloggt auf gefruckelt.de regelmäßig über alle Themen, die ihn interessieren. Neben seiner Tätigkeit als Softwarearchitekt studiert er Wirtschaftsinformatik an der Uni Potsdam.

Weitere Informationen über Jens Altmann

Interessante Artikel

Kommentare

2 Kommentare

  1. PuNkBaStaRd Mai 3, 2010

    okey… da hab ich wieder mal was gelernt^^
    so nebenbei: dein review ist fertig, auf eine rückmeldung wird gehofft =))

    http://www.s-t-x.com/ein-blog-ueber-software-internet-und-co.html

  2. Anonym April 24, 2015

    Exception in thread „main“ java.lang.UnsupportedClassVersionError: de/gefruckelt/test/flickeringfreepaint/Main : Unsupported major.minor version 52.0

Kommentiere den Artikel

Required

Required

Optional