Tit   Inh   Ind   1   2   3   4   5   6   7   8   9   10   11   12   13   14   15   16   17   18   19   20   21   22   23   24   25   26   27   28   29   30   31   32   <<   <   >   >> 

2.5 Schritt 4: Grafikausgabe



Die Grafikausgabe erfolgt in allen Java-Programmen durch Aufruf der Methode paint. Ausnahmen davon sind lediglich Terminalausgaben, die vom Programm durch Aufruf von System.out.println erzeugt werden können. paint wird in abgeleiteten Klassen überlagert und sorgt für die ordnungsgemäße Darstellung der Bildschirmausgabe. Die Methode bekommt ein Objekt vom Typ Graphics übergeben, das einen Grafikkontext darstellt. Dieser stellt die Methoden zur Ausgabe von Text und Grafik zur Verfügung und kann am besten als abstraktes Ausgabegerät aufgefaßt werden. Ein Grafikkontext wird auch dann verwendet, wenn Druckausgaben erzeugt werden sollen oder die Ausgabe in einem Offscreen-Image erfolgt. In unserem Fall ruft paint die beiden Methoden paintBorder und paintField auf:

001 public void paint(Graphics g)
002 {
003    paintBorder(g);
004    paintField(g);
005 }
Listing 2.7: Die Methode paint im Schiebepuzzle

An beide Methoden wird der Grafikkontext übergeben, auf dem die Ausgabeoperationen vorgenommen werden. paintBorder ist dafür verantwortlich, den Rahmen um das Spielfeld zu zeichnen:

001 /**
002  * Zeichnet den Rahmen des Spielbretts.
003  */
004 private void paintBorder(Graphics g)
005 {
006    Insets insets    = getInsets();
007    Dimension size   = getSize();
008    size.height      -= insets.top + insets.bottom;
009    size.width       -= insets.left + insets.right;
010    fieldsize        = new Dimension();
011    fieldsize.width  = (size.width - (2 * bordersize)) / 4;
012    fieldsize.height = (size.height - (2 * bordersize)) / 4;
013    g.setColor(Color.black);
014    g.drawRect(
015       insets.left,
016       insets.top,
017       size.width  - 1,
018       size.height - 2
019    );
020    g.drawRect(
021       insets.left + bordersize,
022       insets.top  + bordersize,
023       4 * fieldsize.width,
024       4 * fieldsize.height
025    );
026 }
Listing 2.8: Zeichnen des Rahmens im Schiebepuzzle

Dazu ermittelt die Methode durch Aufruf von getInsets zunächst die Größe aller Randelemente wie Fensterrahmen, Menüzeile oder Statuszeile. Da das Java-Koordinatensystem in der linken oberen Ecke des zugrundliegenden Fensters beginnt (und nicht in der linken oberen Ecke des Client-Bereichs), müssen diese Werte berücksichtigt werden, damit nicht Teile der Ausgabe hinter den Randelementen verschwinden. Des weiteren ermittelt die Methode durch Aufruf von getSize die Größe des Applet-Fensters, um zusammen mit der als Parameter übergebenen Rahmengröße die Abmessung eines einzelnen Spielsteins zu berechnen. Diese wird der Instanzvariable fieldsize vom Typ Dimension zugewiesen und bei der späteren Anzeige des Spielfelds verwendet.

Anschließend wird die Zeichenfarbe auf schwarz geschaltet und die beiden Rechtecke zur Begrenzung des Rahmens gezeichnet. Bei diesem Rahmen handelt es sich natürlich nicht um die oben erwähnten Rahmenelemente des Java-Fensters, sondern um den Rand, den wir zur Begrenzung des Spielfelds selbst zeichnen. Würde innerhalb von paint lediglich paintBorder aufgerufen, so hätte das Applet das in Abbildung 2.4 gezeigte Aussehen.

Abbildung 2.4: Die Rahmendarstellung

Die Darstellung des Spielfelds erfolgt in der Methode paintField, die ebenfalls den Grafikkontext aus paint übergeben bekommt. paintField bestimmt durch Aufruf von getInsets zunächst die linke obere Ecke des Client-Bereichs und setzt dann die Zeichenfarbe auf schwarz. Anschließend wird das Array aFields zeilenweise durchlaufen und für jede der 16 Positionen auf dem Bildschirm die Nummer des darauf befindlichen Spielsteins ermittelt. Falls es sich um die Nummer 15 handelt, wird mit fillRect ein schwarzes Rechteck in der Größe eines Spielsteins gezeichnet, um die Lücke darzustellen. Andernfalls wird der Spielstein gezeichnet. Dazu geht das Programm in drei Schritten vor:

Hier ist der Quelltext von paintField:

001 /**
002  * Zeichnet die Spielsteine auf dem Brett.
003  */
004 private void paintField(Graphics g)
005 {
006    int imagenumber, image_i, image_j;
007    Insets insets = getInsets();
008    Point topleft = new Point();
009    topleft.x     = insets.left + bordersize;
010    topleft.y     = insets.top  + bordersize;
011    g.setColor(Color.black);
012    for (int i = 0; i <= 3; ++i) {
013       for (int j = 0; j <= 3; ++j) {
014          imagenumber = aFields[i][j];
015          if (imagenumber == 15) {
016             //Lücke zeichnen
017             g.fillRect(
018                topleft.x + j * fieldsize.width,
019                topleft.y + i * fieldsize.height,
020                fieldsize.width,
021                fieldsize.height
022             );
023          } else {
024             //Image darstellen
025             image_i = imagenumber / 4;
026             image_j = imagenumber % 4;
027             g.drawImage(
028                image,
029                topleft.x + j * fieldsize.width,
030                topleft.y + i * fieldsize.height,
031                topleft.x+j*fieldsize.width+fieldsize.width,
032                topleft.y+i*fieldsize.height+fieldsize.height,
033                image_j*(imagesize.width/4),
034                image_i*(imagesize.height/4),
035                image_j*(imagesize.width/4)+imagesize.width/4,
036                image_i*(imagesize.height/4)+imagesize.height/4,
037                this
038             );
039             //Rahmen
040             g.drawRect(
041                topleft.x + j * fieldsize.width,
042                topleft.y + i * fieldsize.height,
043                fieldsize.width,
044                fieldsize.height
045             );
046             //Beschriftung
047             g.drawString(
048                "" + imagenumber,
049                topleft.x + j * fieldsize.width + 2,
050                topleft.y + i * fieldsize.height + 12
051             );
052          }
053       }
054    }
055 }
Listing 2.9: Zeichnen der Spielsteine des Schiebepuzzles

Mit den beiden bisher beschriebenen Methoden wäre die Grafikausgabe eigentlich schon komplett. Die zusätzlich vorhandene Methode update, die normalerweise nicht überlagert werden muß, dient dazu, das Bildschirmflackern zu vermindern:

 Tip 

001 public void update(Graphics g)
002 {
003    Image     dbImage;
004    Graphics  dbGraphics;
005 
006    //Double-Buffer initialisieren
007    dbImage = createImage(getSize().width,getSize().height);
008    dbGraphics = dbImage.getGraphics();
009    //Hintergrund löschen
010    dbGraphics.setColor(getBackground());
011    dbGraphics.fillRect(0,0,getSize().width,getSize().height);
012    //Vordergrund zeichnen
013    dbGraphics.setColor(getForeground());
014    paint(dbGraphics);
015    //Offscreen-Image anzeigen
016    g.drawImage(dbImage,0,0,this);
017    dbGraphics.dispose();
018 }
Listing 2.10: Die überlagerte update-Methode im Schiebepuzzle

Ihre Arbeitsweise beruht auf dem Einsatz eines Offscreen-Images, das zur Zwischenspeicherung der Grafikausgabe verwendet wird. Das Verfahren wird auch als Doppelpufferung bezeichnet, weil ein zweiter Bildschirmpuffer zur Ausgabe verwendet wird. update wird in der Aufrufkette, die bei einem Neuzeichnen des Bildschirms ausgelöst wird, unmittelbar vor paint aufgerufen. Ihre Aufgabe besteht darin, zunächst den Hintergrund mit der aktuellen Hintergrundfarbe zu füllen, dann die Vordergrundfarbe zu aktivieren und schließlich paint aufzurufen.

Genau das erledigt auch unsere Variante von update. Alle Zeichenoperationen spielen sich dabei allerdings in einem separaten Puffer ab, der erst nach kompletter Fertigstellung in den Bildschirm eingeblendet wird. Das unschöne Bildschirmflackern, das dadurch entsteht, daß unmittelbar vor der Darstellung dunkler Flächen die darunterliegende helle Fläche des Hintergrunds neu gezeichnet wird, kann so vermieden werden. Der Nachteil ist die etwas verringerte Ausgabegeschwindigkeit und der recht hohe Speicherbedarf, denn bei jedem Neuzeichnen wird ein neues Offscreen-Image erzeugt.

Weiterführende Informationen zur Grafikausgabe finden sich in Kapitel 14 und den folgenden Kapiteln. Kapitel 14 erläutert dabei insbesondere die grundlegenden Zeichenfunktionen und beschreibt den Grafikkontext. Kapitel 15 erläutert die Textausgabe und Kapitel 16 das Farbsystem von Java. Die zeilenorientierte Ausgabe von einfachen Texten wird in Kapitel 3 erläutert und in Kapitel 12 noch einmal aufgegriffen. In Kapitel 24 werden verschiedene Techniken zur Reduzierung des Bildschirmflackerns vorgestellt.

 Hinweis 


 Tit   Inh   Ind   1   2   3   4   5   6   7   8   9   10   11   12   13   14   15   16   17   18   19   20   21   22   23   24   25   26   27   28   29   30   31   32   <<   <   >   >> 
Go To Java 2, Addison Wesley, Version 1.0.2, © 1999 Guido Krüger, http://www.gkrueger.com